diff --git a/.github/workflows/build-website.yml b/.github/workflows/build-website.yml index bfdc36c7d..efcd2bfcd 100644 --- a/.github/workflows/build-website.yml +++ b/.github/workflows/build-website.yml @@ -11,6 +11,8 @@ on: - instructions - hooks - workflows + - extensions + - .schemas/canvas.schema.json permissions: contents: read diff --git a/.github/workflows/validate-canvas-extensions.yml b/.github/workflows/validate-canvas-extensions.yml index ceb2124c1..558a5766c 100644 --- a/.github/workflows/validate-canvas-extensions.yml +++ b/.github/workflows/validate-canvas-extensions.yml @@ -6,130 +6,133 @@ on: types: [opened, synchronize, reopened] paths: - "extensions/**" + - ".schemas/canvas.schema.json" permissions: contents: read - pull-requests: write jobs: validate: runs-on: ubuntu-latest steps: - - name: Checkout code + - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: fetch-depth: 0 - - name: Validate changed canvas extensions - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - script: | - const fs = require('fs'); - const path = require('path'); - - // Collect changed extension directories from the PR diff - const { execSync } = require('child_process'); - const changedFiles = execSync( - `git diff --name-only origin/${{ github.base_ref }}...HEAD` - ).toString().trim().split('\n').filter(Boolean); - - const EXTENSIONS_DIR = 'extensions'; - const EXTERNAL_ASSETS_DIR = 'external-assets'; - - const changedExtDirs = new Set(); - for (const file of changedFiles) { - const parts = file.split('/'); - if (parts[0] === EXTENSIONS_DIR && parts.length >= 2) { - const extName = parts[1]; - // Skip the external-assets directory — it's not a canvas extension - // Also skip external.json and other files at extensions root level - if (extName !== EXTERNAL_ASSETS_DIR && !extName.includes('.')) { - changedExtDirs.add(path.join(EXTENSIONS_DIR, extName)); - } - } - } - - if (changedExtDirs.size === 0) { - console.log('No canvas extension directories changed — skipping validation.'); - return; - } - - console.log(`Validating ${changedExtDirs.size} extension(s): ${[...changedExtDirs].join(', ')}`); - - const errors = []; - - for (const extDir of changedExtDirs) { - if (!fs.existsSync(extDir)) { - // Directory was deleted — skip - console.log(`${extDir} no longer exists (deleted?), skipping.`); - continue; - } - - const extName = path.basename(extDir); - - // Rule 1: must contain extension.mjs - const mainFile = path.join(extDir, 'extension.mjs'); - if (!fs.existsSync(mainFile)) { - errors.push( - `**\`${extDir}\`**: missing required \`extension.mjs\`. ` + - `Canvas extensions must have their entry point named \`extension.mjs\`.` - ); - } - - // Rule 2: must contain assets/preview.png - const previewFile = path.join(extDir, 'assets', 'preview.png'); - if (!fs.existsSync(previewFile)) { - errors.push( - `**\`${extDir}\`**: missing required \`assets/preview.png\`. ` + - `Canvas extensions must include a screenshot at \`assets/preview.png\` ` + - `so reviewers and users can preview the extension before installing it.` - ); - } - } - - if (errors.length === 0) { - console.log('✅ All changed canvas extensions pass validation.'); - return; - } - - const isFork = context.payload.pull_request.head.repo.fork; - const body = [ - '❌ **Canvas extension validation failed**', - '', - 'The following issue(s) were found in changed canvas extension(s):', - '', - ...errors.map(e => `- ${e}`), - '', - '---', - '', - '### Required structure for canvas extensions', - '', - 'Each extension folder under `extensions/` must contain:', - '', - '| Path | Required | Description |', - '|------|----------|-------------|', - '| `extension.mjs` | ✅ | Entry point for the canvas extension |', - '| `assets/preview.png` | ✅ | Screenshot shown on the website and in the marketplace |', - '', - 'Please add the missing file(s) and push an update to this PR.', - ].join('\n'); - - if (!isFork) { - try { - await github.rest.pulls.createReview({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: context.issue.number, - event: 'REQUEST_CHANGES', - body - }); - } catch (error) { - core.warning(`Could not post PR review: ${error.message}`); - core.warning(body); - } - } else { - core.warning('PR is from a fork — skipping createReview to avoid permission errors.'); - core.warning(body); - } - - core.setFailed(`Canvas extension validation failed with ${errors.length} error(s).`); + node-version: "22" + cache: "npm" + + - name: Install dependencies + run: npm ci --ignore-scripts + + - name: Validate changed canvas extensions + run: | + set -euo pipefail + + # Validate schema structure once, even for schema-only PRs. + if ! node ./eng/validate-json-schema.mjs --schema .schemas/canvas.schema.json; then + echo "❌ .schemas/canvas.schema.json failed schema compilation" + exit 1 + fi + + # Collect changed extension directories. + # Use null-terminated (-z) output from git diff so filenames containing newlines + # or other special characters are read atomically (matches the pattern in skill-check.yml). + # Each extracted name is then validated against a strict allowlist regex before use, + # rejecting anything containing shell metacharacters ($, (, ), spaces, etc.). + declare -A seen_dirs=() + changed_extensions=() + errors=() + + while IFS= read -r -d '' file; do + case "$file" in + extensions/*) + ext_name="${file#extensions/}" + ext_name="${ext_name%%/*}" + + if [ "$ext_name" = "external-assets" ]; then + continue + fi + + # Allowlist: extension directory names must be lowercase alphanumeric + hyphens only. + # Fail for disallowed names so a PR cannot bypass validation by using a nonconforming folder. + if [[ ! "$ext_name" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then + errors+=("\`extensions/$ext_name\`: invalid extension directory name (must match ^[a-z0-9][a-z0-9-]*$).") + continue + fi + + if [ -z "${seen_dirs[$ext_name]+x}" ]; then + seen_dirs["$ext_name"]=1 + changed_extensions+=("extensions/$ext_name") + fi + ;; + esac + done < <(git diff --name-only -z "origin/${{ github.base_ref }}...HEAD") + + if [ "${#changed_extensions[@]}" -eq 0 ]; then + if [ "${#errors[@]}" -ne 0 ]; then + echo "❌ Canvas extension validation failed:" + for error in "${errors[@]}"; do + echo "- $error" + done + exit 1 + fi + echo "No canvas extension directories changed — skipping validation." + exit 0 + fi + + echo "Validating ${#changed_extensions[@]} extension(s): ${changed_extensions[*]}" + + for ext_dir in "${changed_extensions[@]}"; do + if [ ! -d "$ext_dir" ]; then + echo "$ext_dir no longer exists (deleted?), skipping." + continue + fi + + if [ ! -f "$ext_dir/extension.mjs" ]; then + errors+=("\`$ext_dir\`: missing required \`extension.mjs\`.") + fi + + if [ ! -f "$ext_dir/canvas.json" ]; then + errors+=("\`$ext_dir\`: missing required \`canvas.json\`.") + continue + fi + + if [ ! -f "$ext_dir/assets/preview.png" ]; then + errors+=("\`$ext_dir\`: missing required \`assets/preview.png\`.") + fi + + if ! schema_output="$(node ./eng/validate-json-schema.mjs --schema .schemas/canvas.schema.json --data "$ext_dir/canvas.json" 2>&1)"; then + condensed_output="$(echo "$schema_output" | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g')" + errors+=("\`$ext_dir/canvas.json\`: schema validation failed against \`.schemas/canvas.schema.json\` ($condensed_output).") + continue + fi + + mapfile -t screenshot_paths < <( + node -e 'const fs = require("fs"); const manifest = JSON.parse(fs.readFileSync(process.argv[1], "utf8")); const paths = [manifest?.screenshots?.icon?.path, manifest?.screenshots?.gallery?.path].filter((value) => typeof value === "string" && value.trim().length > 0); for (const value of [...new Set(paths)]) { console.log(value); }' "$ext_dir/canvas.json" + ) + + for screenshot_path in "${screenshot_paths[@]}"; do + if [[ ! "$screenshot_path" =~ ^assets/([A-Za-z0-9_-]+/)*[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)*\.(png|jpe?g|gif|webp|svg)$ ]]; then + errors+=("\`$ext_dir/canvas.json\`: screenshot path \`$screenshot_path\` is not a valid assets path.") + continue + fi + if [ ! -f "$ext_dir/$screenshot_path" ]; then + errors+=("\`$ext_dir/canvas.json\`: screenshot path \`$screenshot_path\` does not exist in the extension directory.") + fi + done + done + + if [ "${#errors[@]}" -ne 0 ]; then + echo "❌ Canvas extension validation failed:" + for error in "${errors[@]}"; do + echo "- $error" + done + exit 1 + fi + + echo "✅ All changed canvas extensions passed validation." \ No newline at end of file diff --git a/.schemas/canvas.schema.json b/.schemas/canvas.schema.json new file mode 100644 index 000000000..d1dc26569 --- /dev/null +++ b/.schemas/canvas.schema.json @@ -0,0 +1,121 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Canvas Extension Manifest", + "description": "Schema for extensions//canvas.json files", + "type": "object", + "required": [ + "id", + "name", + "description", + "version", + "keywords", + "screenshots" + ], + "additionalProperties": false, + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the canvas extension", + "pattern": "^[a-z0-9-]+$", + "minLength": 1, + "maxLength": 100 + }, + "name": { + "type": "string", + "description": "Display name for the extension", + "minLength": 1, + "maxLength": 100 + }, + "description": { + "type": "string", + "description": "Human-friendly description of what the extension does", + "minLength": 1, + "maxLength": 500 + }, + "version": { + "type": "string", + "description": "Semantic version of the extension metadata", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?(?:\\+[0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)?$" + }, + "author": { + "type": "object", + "description": "Optional author metadata", + "required": [ + "name" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "minLength": 1, + "maxLength": 100 + }, + "url": { + "type": "string", + "format": "uri", + "maxLength": 2048 + } + } + }, + "keywords": { + "type": "array", + "description": "Keywords used for search and filtering", + "items": { + "type": "string", + "pattern": "^[a-z0-9-]+$", + "minLength": 1, + "maxLength": 50 + }, + "minItems": 1, + "maxItems": 20, + "uniqueItems": true + }, + "screenshots": { + "type": "object", + "description": "Screenshot metadata for icon and gallery cards", + "required": [ + "icon", + "gallery" + ], + "additionalProperties": false, + "properties": { + "icon": { + "$ref": "#/definitions/screenshot" + }, + "gallery": { + "$ref": "#/definitions/screenshot" + } + } + } + }, + "definitions": { + "screenshot": { + "type": "object", + "required": [ + "path", + "type" + ], + "additionalProperties": false, + "properties": { + "path": { + "type": "string", + "description": "Path relative to the extension root", + "pattern": "^assets/(?:[A-Za-z0-9_-]+/)*[A-Za-z0-9_-]+(?:\\.[A-Za-z0-9_-]+)*\\.(png|jpg|jpeg|gif|webp|svg)$", + "minLength": 1, + "maxLength": 200 + }, + "type": { + "type": "string", + "description": "MIME type for the referenced image", + "enum": [ + "image/png", + "image/jpeg", + "image/gif", + "image/webp", + "image/svg+xml" + ] + } + } + } + } +} diff --git a/eng/validate-json-schema.mjs b/eng/validate-json-schema.mjs new file mode 100644 index 000000000..e1eba3bf7 --- /dev/null +++ b/eng/validate-json-schema.mjs @@ -0,0 +1,75 @@ +#!/usr/bin/env node + +import fs from "fs"; +import path from "path"; +import Ajv from "ajv"; +import addFormats from "ajv-formats"; + +function parseArgs(argv) { + const args = { schema: null, data: null }; + for (let i = 2; i < argv.length; i += 1) { + const arg = argv[i]; + if (arg === "--schema") { + args.schema = argv[i + 1] || null; + i += 1; + } else if (arg === "--data") { + args.data = argv[i + 1] || null; + i += 1; + } + } + return args; +} + +function readJson(filePath) { + const content = fs.readFileSync(filePath, "utf8"); + return JSON.parse(content); +} + +const args = parseArgs(process.argv); +if (!args.schema) { + console.error("Missing required argument: --schema "); + process.exit(1); +} + +const schemaPath = path.resolve(process.cwd(), args.schema); +let schema; +try { + schema = readJson(schemaPath); +} catch (error) { + console.error(`Invalid schema JSON at ${args.schema}: ${error.message}`); + process.exit(1); +} + +const ajv = new Ajv({ strict: false, allErrors: true }); +addFormats(ajv); + +let validate; +try { + validate = ajv.compile(schema); +} catch (error) { + console.error(`Invalid schema at ${args.schema}: ${error.message}`); + process.exit(1); +} + +if (!args.data) { + console.log(`Schema is valid: ${args.schema}`); + process.exit(0); +} + +const dataPath = path.resolve(process.cwd(), args.data); +let data; +try { + data = readJson(dataPath); +} catch (error) { + console.error(`Invalid data JSON at ${args.data}: ${error.message}`); + process.exit(1); +} + +const valid = validate(data); +if (!valid) { + const message = ajv.errorsText(validate.errors, { separator: "; " }); + console.error(`Schema validation failed for ${args.data}: ${message}`); + process.exit(1); +} + +console.log(`Schema validation passed: ${args.data}`); \ No newline at end of file diff --git a/eng/yaml-parser.mjs b/eng/yaml-parser.mjs index 19bb3f73c..3e202668a 100644 --- a/eng/yaml-parser.mjs +++ b/eng/yaml-parser.mjs @@ -1,6 +1,6 @@ // YAML parser for frontmatter parsing using vfile-matter import fs from "fs"; -import yaml from "js-yaml"; +import * as yaml from "js-yaml"; import path from "path"; import { VFile } from "vfile"; import { matter } from "vfile-matter"; diff --git a/package-lock.json b/package-lock.json index 12e29e3af..83a21d6d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,8 @@ }, "devDependencies": { "@microsoft/vally": "^0.6.0", + "ajv": "^8.20.0", + "ajv-formats": "^3.0.1", "all-contributors-cli": "^6.26.1" } }, @@ -29,9 +31,9 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.64-3", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.64-3.tgz", - "integrity": "sha512-Q9nBMYEHX1bJLXzzJocQx2nZvORJ0E9gvK6ly/FCtmtA7ad96BWZvf4EHzkCNDsn56aI3zNaUSfKHUKcmIAzSg==", + "version": "1.0.67", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.67.tgz", + "integrity": "sha512-5YEY9LNXBT9Q8uShjCdYcornJJJhGtdIzSYla2+pjfXYpHsDVibqYubzYjfgffOUKFChyzOpH7n/868+t56iIg==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "dependencies": { @@ -41,20 +43,20 @@ "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.64-3", - "@github/copilot-darwin-x64": "1.0.64-3", - "@github/copilot-linux-arm64": "1.0.64-3", - "@github/copilot-linux-x64": "1.0.64-3", - "@github/copilot-linuxmusl-arm64": "1.0.64-3", - "@github/copilot-linuxmusl-x64": "1.0.64-3", - "@github/copilot-win32-arm64": "1.0.64-3", - "@github/copilot-win32-x64": "1.0.64-3" + "@github/copilot-darwin-arm64": "1.0.67", + "@github/copilot-darwin-x64": "1.0.67", + "@github/copilot-linux-arm64": "1.0.67", + "@github/copilot-linux-x64": "1.0.67", + "@github/copilot-linuxmusl-arm64": "1.0.67", + "@github/copilot-linuxmusl-x64": "1.0.67", + "@github/copilot-win32-arm64": "1.0.67", + "@github/copilot-win32-x64": "1.0.67" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.64-3", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.64-3.tgz", - "integrity": "sha512-wlV6mRoAd/wG2V08TG+BOJ0nyOjroya24FSyA5A49z7PnUUuQXYRpa/GljvI5j3PM8aUl0DyBkXuB/DcFU818g==", + "version": "1.0.67", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.67.tgz", + "integrity": "sha512-CO3mpgFXcN6e7ZsSmjMkt1AKxMfb1+mjdn3yrf2DRnnWIURSK9kGvw+E+E1+YE37D1MBiUn/VOBmhRad5+vl0A==", "cpu": [ "arm64" ], @@ -69,9 +71,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.64-3", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.64-3.tgz", - "integrity": "sha512-mk48PIESL2keeemX7tLRmWRDxKwl0q3cFI1ORD2QcrieNK7pSqI3eVbfoB7MqoUUI27yzIkl67xqgl8Qq28IUQ==", + "version": "1.0.67", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.67.tgz", + "integrity": "sha512-M20Hpn3bOJRkVwAIVRK4ZlX66AqtmGfXZRxZBRFQC045QIwcfmVUP45sTSgXDb4uHWeK0cZgdTdniHwKGtMplw==", "cpu": [ "x64" ], @@ -86,9 +88,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.64-3", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.64-3.tgz", - "integrity": "sha512-rCgtK3/rofQW5StSbeU0TwDUlOl2bvS2HGKyapVxow1Nvz3Q/TDB+eFRQc5ocBdv5tNSor+Caw2JGkRx5v508w==", + "version": "1.0.67", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.67.tgz", + "integrity": "sha512-b4ePtFBow+Ior+aVLKA1hHxhR5wF+ql5CD7TSg/NHGYgc1kwD+3a9uKSENy05J5Lit/G/DZ9C6JwowvvdMWSKg==", "cpu": [ "arm64" ], @@ -103,9 +105,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.64-3", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.64-3.tgz", - "integrity": "sha512-FAiBMw1h07mURSBLi3ztj5yzbP+uTbo9mhxOym1Xysut5LDpO2kYUzTYk2DlIyLGZhmH/HDOZE+b6U7lOUQy0g==", + "version": "1.0.67", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.67.tgz", + "integrity": "sha512-4ynZyfKnWAdvEPAFDDBIz1wpFttcOTJu4Y8Mlz5oXCBA0NM/rwr8K4l7Adp8UzwbfmdrMJ9y+zivqRBMDbPInA==", "cpu": [ "x64" ], @@ -120,9 +122,9 @@ } }, "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.64-3", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.64-3.tgz", - "integrity": "sha512-vn8P6grPf0y2mNskkdVbAz0i46b1sP9uSXv7z6kgycjprl0CdIYPDf3WEkG60vpyopfQna+iCqCLMWRnNyCk3g==", + "version": "1.0.67", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.67.tgz", + "integrity": "sha512-IjezxBU8fYUr/b5hEiniXqzwoOrJ4egrQSBbG96M+roLTqd9txP0MgxZtcRtKV7phRIdIGE109wwrn4H6hSqmA==", "cpu": [ "arm64" ], @@ -137,9 +139,9 @@ } }, "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.64-3", - "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.64-3.tgz", - "integrity": "sha512-atdHimNd6nzRRwHybXUY6/84bYzXeKbDOeYN/N/DsX23+AQOPSu5BD8MD8166I/5kNHui0XOmeTSydVNBUwcJw==", + "version": "1.0.67", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.67.tgz", + "integrity": "sha512-Zy/rbja1lnhzDoNfn051H0EybCseCvjvH7WmbcHCayjXUjzXeKF6OmAt4hvqFZH87ttT3KbKtQ8/6oDUhhM2YQ==", "cpu": [ "x64" ], @@ -154,13 +156,13 @@ } }, "node_modules/@github/copilot-sdk": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-1.0.3.tgz", - "integrity": "sha512-ujnH2QVw3+xvjgo9cbpY0wik4fNxAmdMDSFnxGScDSvRuK2vUCL2xWW4V2ANc9pWwRHPBpEpMuNJMtmydmLCIQ==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-1.0.4.tgz", + "integrity": "sha512-c6/gpatP7LAuP9stlc2uXXOrB+TJMFSs3Jrzu9FG8lJJYFGmdzbBvGz2UdL1jww84fp6D7cohEZa4UGq1+iuyA==", "dev": true, "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.64-1", + "@github/copilot": "^1.0.65", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -169,9 +171,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.64-3", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.64-3.tgz", - "integrity": "sha512-jUTS9meoHEXQR8nMDOjwC0baqV273lYtLxY46W7TiOFszhsqhbhWxQMkNQBfT3GEfPp+40igzMPq3reaUTuvag==", + "version": "1.0.67", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.67.tgz", + "integrity": "sha512-O3VFRS5v9NXRP8o+N1SvcFbBqECDzZP7XQBeBj2Vcrma80gdJc5GQub/w2mwmr1w5UbwgzJkRasm0Ec/jxbcoA==", "cpu": [ "arm64" ], @@ -186,9 +188,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.64-3", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.64-3.tgz", - "integrity": "sha512-gHUhS500Q91hjtH9fqKDblaIs0mO09G4ifpZ1woDPXbkdKe/W29uwB7g2fn0+KczNRyPxFSWlqjnOon4Fe8svA==", + "version": "1.0.67", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.67.tgz", + "integrity": "sha512-td5tQ/nve5dB7RPvNglBZwa/6DJqiOBgacXXa1GpYcohqpCzoI8gONNkeaeyr6oF4iu5wXJ9krUNr6QXL4yB5Q==", "cpu": [ "x64" ], @@ -221,6 +223,41 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/all-contributors-cli": { "version": "6.26.1", "resolved": "https://registry.npmjs.org/all-contributors-cli/-/all-contributors-cli-6.26.1.tgz", @@ -473,6 +510,30 @@ "node": ">=4" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -618,6 +679,13 @@ "node": ">=10" } }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -632,9 +700,9 @@ } }, "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true, "license": "MIT" }, @@ -817,6 +885,16 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", diff --git a/package.json b/package.json index 92c25cd24..871fe7708 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,8 @@ "author": "GitHub", "license": "MIT", "devDependencies": { + "ajv": "^8.20.0", + "ajv-formats": "^3.0.1", "@microsoft/vally": "^0.6.0", "all-contributors-cli": "^6.26.1" }, diff --git a/website/src/pages/contributors.astro b/website/src/pages/contributors.astro index 2f9a4e9ff..fe77a4fd3 100644 --- a/website/src/pages/contributors.astro +++ b/website/src/pages/contributors.astro @@ -499,9 +499,8 @@ import PageHeader from '../components/PageHeader.astro'; - - Add your contributions - + + Add your contributions