From b6ecca49f127942b598f0e0f329c75d8f3db702c Mon Sep 17 00:00:00 2001 From: Devraj Mehta Date: Tue, 1 Sep 2026 13:44:43 -0400 Subject: [PATCH 01/30] Use Copilot CLI releases for Node runtime Remove the Node SDK dependency on @github/copilot, pin the CLI release and checksums in the repository, and acquire verified platform runtime assets from GitHub Releases. Update packaging, code generation, tests, and cross-SDK version consumers to use the shared release pin. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3043824d-becf-4b5d-b62b-4754511894f7 --- .github/actions/setup-copilot/action.yml | 11 +- .github/copilot-instructions.md | 4 +- .github/workflows/java-sdk-tests.yml | 7 +- .github/workflows/publish.yml | 3 - .github/workflows/rust-sdk-tests.yml | 12 +- .github/workflows/sdk-canary.yml | 763 +++-- .../workflows/update-copilot-dependency.yml | 24 +- dotnet/src/GitHub.Copilot.SDK.csproj | 4 +- go/cmd/bundler/main.go | 50 +- java/copilot-native/pom.xml | 16 +- java/copilot-native/scripts/fetch-native.mjs | 100 +- .../scripts/fetch-native.test.mjs | 57 +- .../scripts/validate-native-artifact.mjs | 15 +- .../scripts/validate-native-artifact.test.mjs | 22 +- .../adr/adr-007-native-bundling-strategy.md | 2 +- java/sdk/pom.xml | 35 +- .../com/github/copilot/E2ETestContext.java | 46 +- .../java/com/github/copilot/TestUtil.java | 50 +- nodejs/README.md | 10 + nodejs/package-lock.json | 347 +-- nodejs/package.json | 5 +- nodejs/samples/package-lock.json | 2744 ++++++++++++++++- nodejs/scripts/prepare-runtime.ts | 14 + nodejs/scripts/set-cli-version.js | 57 + nodejs/src/cliVersion.ts | 12 + nodejs/src/client.ts | 91 +- nodejs/src/runtimeArtifacts.ts | 215 +- .../test/e2e/extension_env_access.e2e.test.ts | 6 +- nodejs/test/e2e/factory.e2e.test.ts | 2 +- nodejs/test/e2e/harness/sdkTestContext.ts | 25 +- nodejs/test/e2e/ui_elicitation.e2e.test.ts | 4 +- nodejs/test/runtimeArtifacts.test.ts | 98 +- python/e2e/conftest.py | 6 +- python/scripts/inject-cli-version.mjs | 18 +- rust/Cargo.toml | 1 - rust/README.md | 2 +- rust/build/in_process.rs | 137 +- rust/build/out_of_process.rs | 49 +- rust/scripts/snapshot-bundled-cli-version.sh | 35 +- .../snapshot-bundled-in-process-version.sh | 42 +- rust/tests/cli_resolution_test.rs | 2 +- scripts/codegen/utils.ts | 54 +- test/harness/package-lock.json | 421 +-- test/harness/package.json | 1 - 44 files changed, 3983 insertions(+), 1636 deletions(-) create mode 100644 nodejs/scripts/prepare-runtime.ts create mode 100644 nodejs/scripts/set-cli-version.js create mode 100644 nodejs/src/cliVersion.ts diff --git a/.github/actions/setup-copilot/action.yml b/.github/actions/setup-copilot/action.yml index 3769bc3751..506f472ca2 100644 --- a/.github/actions/setup-copilot/action.yml +++ b/.github/actions/setup-copilot/action.yml @@ -23,16 +23,15 @@ runs: - name: Set CLI path id: cli-path run: | - # As of CLI 1.0.64-1 the @github/copilot package is a thin loader; the - # runnable index.js ships in the installed platform package - # (e.g. @github/copilot-linux-x64). Exactly one is installed. - cli_path=$(ls "$(pwd)"/nodejs/node_modules/@github/copilot-*/index.js 2>/dev/null | head -n1) + cli_path=$(npm --prefix "$(pwd)/nodejs" run --silent prepare:runtime -- --print-path) if [ -z "$cli_path" ]; then - echo "Could not find @github/copilot platform package (index.js) under nodejs/node_modules" >&2 + echo "Could not prepare the Copilot CLI runtime" >&2 exit 1 fi echo "path=$cli_path" >> $GITHUB_OUTPUT shell: bash - name: Verify CLI works - run: node ${{ steps.cli-path.outputs.path }} --version + run: | + legacy_cli=$(npm --prefix "$(pwd)/nodejs" run --silent prepare:runtime -- --print-legacy-path) + node "$legacy_cli" --version shell: bash diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a9bc22d0ec..476f4e5689 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -47,7 +47,7 @@ - Tools: each SDK has helper APIs to expose functions as tools; prefer the language's `DefineTool`/`@define_tool`/`CopilotTool.DefineTool` patterns (see language READMEs). - Infinite sessions are enabled by default and persist workspace state to `~/.copilot/session-state/{sessionId}`; compaction events are emitted (`session.compaction_start`, `session.compaction_complete`). See language READMEs for usage. - Streaming: when `streaming`/`Streaming=true` you receive delta events (`assistant.message_delta`, `assistant.reasoning_delta`) and final events (`assistant.message`, `assistant.reasoning`) — tests expect this behavior. -- Type generation is centralized in `nodejs/scripts/generate-session-types.ts` and requires the `@github/copilot` schema to be present (often via `npm link` or installed package). +- Type generation is centralized in `scripts/codegen/` and downloads schemas from the pinned `github/copilot-cli` release. - Java code style: 4-space indent (Spotless + Eclipse formatter), fluent setter pattern for config classes, Javadoc required on public APIs (enforced by Checkstyle, except `json`/`events` packages). - Java handlers return `CompletableFuture` (the Java equivalent of C# `async/await`). When porting from .NET: convert properties → getters/fluent setters, use Jackson (`ObjectMapper`, `@JsonProperty`) for serialization. @@ -64,7 +64,7 @@ - SDK code: `nodejs/src`, `python/copilot`, `go`, `dotnet/src`, `rust/src`, `java/sdk/src/main/java` - Unit tests: `nodejs/test`, `python/*`, `go/*`, `dotnet/test`, `rust/tests`, `java/sdk/src/test/java` - E2E tests: `*/e2e/` folders that use the shared replay proxy and `test/snapshots/`, `java/sdk/src/test/java/**/e2e/` -- Generated types: update schema in `@github/copilot` then run `cd nodejs && npm run generate:session-types` and commit generated files in `src/generated` or language generated location. Java generated types: `java/sdk/src/generated/java` +- Generated types: update the pinned Copilot CLI version, run `cd nodejs && npm run generate`, and commit generated files in each language's generated location. Java generated types: `java/sdk/src/generated/java` ## Boundaries — files you must NOT hand-edit ⛔ diff --git a/.github/workflows/java-sdk-tests.yml b/.github/workflows/java-sdk-tests.yml index 88185deb14..0b6b62a0d9 100644 --- a/.github/workflows/java-sdk-tests.yml +++ b/.github/workflows/java-sdk-tests.yml @@ -458,7 +458,12 @@ jobs: run: mvn javadoc:javadoc -q - name: Verify CLI works - run: node ../nodejs/node_modules/@github/copilot/npm-loader.js --version + run: | + npm --prefix ../nodejs ci --ignore-scripts + cli_path=$(npm --prefix ../nodejs run --silent prepare:runtime -- --print-path) + test -x "$cli_path" + legacy_cli=$(npm --prefix ../nodejs run --silent prepare:runtime -- --print-legacy-path) + node "$legacy_cli" --version - name: Run spotless check if: matrix.test-jdk == '25' diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 98bf236900..486e59fa09 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -327,9 +327,6 @@ jobs: node-version: "22.x" - name: Set up uv uses: astral-sh/setup-uv@v7 - - name: Install Node.js dependencies (for CLI version) - working-directory: ./nodejs - run: npm ci --ignore-scripts - name: Set version run: sed -i "s/^version = .*/version = \"${{ needs.version.outputs.version }}\"/" pyproject.toml - name: Inject CLI version diff --git a/.github/workflows/rust-sdk-tests.yml b/.github/workflows/rust-sdk-tests.yml index da030ff999..2c8b768459 100644 --- a/.github/workflows/rust-sdk-tests.yml +++ b/.github/workflows/rust-sdk-tests.yml @@ -43,11 +43,11 @@ jobs: prefix-key: v1-rust-no-bin cache-bin: false - - name: Read pinned @github/copilot CLI version + - name: Read pinned Copilot CLI version id: cli-version working-directory: ./nodejs run: | - version=$(node -p "require('./package-lock.json').packages['node_modules/@github/copilot'].version") + version=$(node -p "require('./package.json').copilotCliVersion") echo "version=$version" >> "$GITHUB_OUTPUT" echo "Pinned CLI version: $version" @@ -231,11 +231,11 @@ jobs: prefix-key: v1-rust-no-bin cache-bin: false - - name: Read pinned @github/copilot CLI version + - name: Read pinned Copilot CLI version id: cli-version working-directory: ./nodejs run: | - version=$(node -p "require('./package-lock.json').packages['node_modules/@github/copilot'].version") + version=$(node -p "require('./package.json').copilotCliVersion") echo "version=$version" >> "$GITHUB_OUTPUT" echo "Pinned CLI version: $version" @@ -307,11 +307,11 @@ jobs: prefix-key: v1-rust-no-bin cache-bin: false - - name: Read pinned @github/copilot CLI version + - name: Read pinned Copilot CLI version id: cli-version working-directory: ./nodejs run: | - version=$(node -p "require('./package-lock.json').packages['node_modules/@github/copilot'].version") + version=$(node -p "require('./package.json').copilotCliVersion") echo "version=$version" >> "$GITHUB_OUTPUT" echo "Pinned CLI version: $version" diff --git a/.github/workflows/sdk-canary.yml b/.github/workflows/sdk-canary.yml index 95f8b1c926..a05bb9bcfa 100644 --- a/.github/workflows/sdk-canary.yml +++ b/.github/workflows/sdk-canary.yml @@ -1,391 +1,372 @@ -name: "SDK Canary Test/Publish" - -# Nightly-style canary pipeline. First installs an explicit version of the -# @github/copilot runtime, builds the Node SDK, and runs the Node e2e suite -# against it to prove runtime <-> SDK compatibility. When that gate passes (and -# mode allows), publishes an SDK canary pinned to the tested runtime to the -# internal Azure Artifacts feed only (never public npm). - -env: - HUSKY: 0 - # Internal org-scoped Azure Artifacts feed — single source of truth so the - # feed name isn't repeated across steps. The SDK canary publishes here and - # (when runtime_source=internal) installs the runtime from here; it must NEVER - # reach public npm (@github/copilot-sdk is a live public package). - FEED_URL: https://pkgs.dev.azure.com/devdiv/_packaging/copilot-canary/npm/registry/ - # Azure DevOps resource ID used to mint an ADO access token for the feed. - ADO_RESOURCE: 499b84ac-1321-427f-aa17-267ca6975798 - -on: - workflow_dispatch: - inputs: - runtime_version: - description: "Exact @github/copilot version to test (e.g. 1.0.69 or 1.0.70-canary.)" - required: true - type: string - runtime_source: - description: "Where to install the runtime from" - required: true - type: choice - options: - - public - - internal - default: public - mode: - description: "publish (tests must pass), publish-force (publish even if tests fail), or tests-only (run gate, never publish)" - required: false - type: choice - default: publish - options: - - publish - - publish-force - - tests-only - repository_dispatch: - types: [runtime-canary] - -permissions: - contents: read - id-token: write - -# Serialize runs per ref so two overlapping canary runs can't race the feed -# publish. cancel-in-progress: false — never kill an in-flight publish. -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: false - -jobs: - resolve: - name: "Resolve runtime inputs" - if: github.event.repository.fork == false - runs-on: ubuntu-latest - permissions: {} - outputs: - RUNTIME_VERSION: ${{ steps.normalize.outputs.RUNTIME_VERSION }} - RUNTIME_SOURCE: ${{ steps.normalize.outputs.RUNTIME_SOURCE }} - PUBLISH_MODE: ${{ steps.normalize.outputs.PUBLISH_MODE }} - steps: - # Normalize whichever trigger fired into a single (RUNTIME_VERSION, - # RUNTIME_SOURCE, PUBLISH_MODE) triple that every downstream step - # references. workflow_dispatch reads the human-supplied inputs; - # repository_dispatch reads client_payload and forces source=internal - # (a runtime canary only exists on the feed), defaulting mode to publish. - - name: Normalize inputs - id: normalize - env: - EVENT_NAME: ${{ github.event_name }} - INPUT_VERSION: ${{ inputs.runtime_version }} - INPUT_SOURCE: ${{ inputs.runtime_source }} - INPUT_MODE: ${{ inputs.mode }} - PAYLOAD_VERSION: ${{ github.event.client_payload.runtime_version }} - PAYLOAD_SOURCE: ${{ github.event.client_payload.runtime_source }} - PAYLOAD_MODE: ${{ github.event.client_payload.mode }} - run: | - set -euo pipefail - case "$EVENT_NAME" in - workflow_dispatch) - VERSION="$INPUT_VERSION" - SOURCE="$INPUT_SOURCE" - MODE="$INPUT_MODE" - ;; - repository_dispatch) - VERSION="$PAYLOAD_VERSION" - # A runtime canary only ever exists on the internal feed. - SOURCE="${PAYLOAD_SOURCE:-internal}" - MODE="${PAYLOAD_MODE:-publish}" - ;; - *) - echo "::error::Unsupported event '$EVENT_NAME'." - exit 1 - ;; - esac - if [ -z "$VERSION" ]; then echo "::error::Could not determine runtime version."; exit 1; fi - if [ -z "$SOURCE" ]; then SOURCE="public"; fi - case "$SOURCE" in - public|internal) ;; - *) echo "::error::Invalid runtime source '$SOURCE'. Expected one of: public, internal."; exit 1 ;; - esac - if [ -z "$MODE" ]; then MODE="publish"; fi - case "$MODE" in - publish|publish-force|tests-only) ;; - *) echo "::error::Invalid publish mode '$MODE'. Expected one of: publish, publish-force, tests-only."; exit 1 ;; - esac - echo "Resolved RUNTIME_VERSION=$VERSION RUNTIME_SOURCE=$SOURCE PUBLISH_MODE=$MODE" - echo "RUNTIME_VERSION=$VERSION" >> "$GITHUB_OUTPUT" - echo "RUNTIME_SOURCE=$SOURCE" >> "$GITHUB_OUTPUT" - echo "PUBLISH_MODE=$MODE" >> "$GITHUB_OUTPUT" - - - name: Validate runtime version (semver) - env: - RUNTIME_VERSION: ${{ steps.normalize.outputs.RUNTIME_VERSION }} - run: | - if [[ ! "$RUNTIME_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then - echo "::error::Invalid runtime version '$RUNTIME_VERSION'. Expected semver (e.g. 1.0.69 or 1.0.70-canary.abc123)." - exit 1 - fi - - test: - name: "E2E tests (${{ matrix.os }})" - needs: resolve - if: github.event.repository.fork == false - environment: cicd - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - runs-on: ${{ matrix.os }} - env: - POWERSHELL_UPDATECHECK: Off - RUNTIME_VERSION: ${{ needs.resolve.outputs.RUNTIME_VERSION }} - RUNTIME_SOURCE: ${{ needs.resolve.outputs.RUNTIME_SOURCE }} - defaults: - run: - shell: bash - working-directory: ./nodejs - steps: - - uses: actions/checkout@v6.0.2 - - - uses: actions/setup-node@v6 - with: - cache: "npm" - cache-dependency-path: "./nodejs/package-lock.json" - node-version: 22 - - - name: Install SDK dependencies - run: npm ci --ignore-scripts - - - name: Install test harness dependencies - working-directory: ./test/harness - run: npm ci --ignore-scripts - - - name: Azure Login (OIDC -> id-cpd-ci) - if: env.RUNTIME_SOURCE == 'internal' - uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 - with: - client-id: "${{ vars.CPD_ID_CLIENT_ID }}" # id-cpd-ci - tenant-id: "${{ vars.CPD_ID_TENANT_ID }}" - allow-no-subscriptions: true - - # Route ONLY @github/* (the runtime + its 8 platform packages) to the - # internal feed via a scoped registry. All other deps (e.g. detect-libc) - # still resolve from public npm. A global --registry would break because - # detect-libc is not on the feed. - - name: Configure canary feed (.npmrc) - if: env.RUNTIME_SOURCE == 'internal' - run: | - set -euo pipefail - TOKEN="$(az account get-access-token --resource "$ADO_RESOURCE" --query accessToken -o tsv)" - echo "::add-mask::$TOKEN" - # Derive the protocol-relative auth scopes from FEED_URL so the feed - # name lives in exactly one place (the workflow-level env). - FEED_AUTH_REGISTRY="${FEED_URL#https:}" - FEED_AUTH_BASE="${FEED_AUTH_REGISTRY%registry/}" - NPMRC="$(printf '%s\n' \ - "@github:registry=${FEED_URL}" \ - "${FEED_AUTH_REGISTRY}:_authToken=${TOKEN}" \ - "${FEED_AUTH_BASE}:_authToken=${TOKEN}")" - printf '%s\n' "$NPMRC" > .npmrc - echo "Wrote scoped @github registry .npmrc to ./nodejs" - - - name: Override runtime version - run: | - set -euo pipefail - echo "Installing @github/copilot@${RUNTIME_VERSION} (source: ${RUNTIME_SOURCE})" - npm install "@github/copilot@${RUNTIME_VERSION}" --save-exact --ignore-scripts - - - name: Verify installed runtime - run: | - set -euo pipefail - node -e ' - const fs = require("fs"); - const expected = process.env.RUNTIME_VERSION; - const pkg = require("./node_modules/@github/copilot/package.json"); - if (pkg.version !== expected) { - console.error(`::error::Installed @github/copilot version ${pkg.version} does not match requested ${expected}`); - process.exit(1); - } - const dir = "./node_modules/@github"; - const entries = fs.readdirSync(dir).filter((d) => d.startsWith("copilot-")); - const plat = process.platform === "win32" ? "win32" : process.platform === "darwin" ? "darwin" : "linux"; - const arch = process.arch; - const match = entries.find((d) => d.includes(plat) && d.includes(arch)); - if (!match) { - console.error(`::error::No @github/copilot platform optional dep for ${plat}-${arch}. Present: ${entries.join(", ") || "(none)"}`); - process.exit(1); - } - const platPkg = require(`${dir}/${match}/package.json`); - if (platPkg.version !== expected) { - console.error(`::error::Platform package @github/${match} version ${platPkg.version} does not match requested ${expected}`); - process.exit(1); - } - console.log(`Verified @github/copilot@${pkg.version} with platform package @github/${match}@${platPkg.version}`); - ' - - - name: Build SDK - run: npm run build - - - name: Warm up PowerShell - if: runner.os == 'Windows' - run: pwsh.exe -Command "Write-Host 'PowerShell ready'" - - - name: Run Node.js SDK e2e tests - env: - COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }} - run: npm test - - publish: - name: "Publish SDK canary (internal feed)" - needs: [resolve, test] - # Publish runs only when the gate permits it. Mode governs behavior: - # - tests-only: never publish (skips this job entirely). - # - publish: publish only when the e2e gate is green (the default for both - # the human and automated triggers). - # - publish-force: publish even on a non-green gate — a human-acknowledged - # flake override, audited via the ::warning:: step below and the run actor. - # publish-force only skips the e2e *signal* — the publish job still runs the - # build (so a broken build can't publish) and enforces the feed-only guards. - if: > - !cancelled() && - github.event.repository.fork == false && - needs.resolve.result == 'success' && - needs.resolve.outputs.PUBLISH_MODE != 'tests-only' && - (needs.test.result == 'success' || - needs.resolve.outputs.PUBLISH_MODE == 'publish-force') - environment: cicd - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - env: - RUNTIME_VERSION: ${{ needs.resolve.outputs.RUNTIME_VERSION }} - defaults: - run: - shell: bash - working-directory: ./nodejs - steps: - - name: Warn — publishing despite failed e2e gate (publish-force) - # always() so this audit is never skipped by prior-step status; it fires - # specifically when publish proceeded on a non-green gate via publish-force. - # Runs at the workspace root because it executes before checkout, so the - # job's default working-directory (./nodejs) does not exist yet. - if: always() && needs.test.result != 'success' && needs.resolve.outputs.PUBLISH_MODE == 'publish-force' - working-directory: ${{ github.workspace }} - run: | - echo "::warning title=e2e gate bypassed::Publishing SDK canary despite a non-passing e2e gate (test job result: ${{ needs.test.result }}) via publish-force. Triggered by '${{ github.actor }}' through '${{ github.event_name }}'. The e2e signal was bypassed; build + feed-only guards still apply." - - - uses: actions/checkout@v6.0.2 - - - uses: actions/setup-node@v6 - with: - node-version: 22 - - # Default public registry: installs build deps and the currently pinned - # runtime. Do NOT write any feed .npmrc or scoped @github:registry line - # here, or npm ci would try to fetch the runtime from the upstream-less - # feed and 404. - - name: Install SDK dependencies - run: npm ci --ignore-scripts - - - name: Compute SDK canary version - id: sdkver - env: - RUN_NUMBER: ${{ github.run_number }} - SHA: ${{ github.sha }} - run: | - set -euo pipefail - SHORT_SHA="${SHA:0:7}" - # Base the canary on the NEXT patch of the public SDK latest so canaries - # correlate with public releases: they sort ABOVE the current public - # latest and BELOW the eventual real release of that next patch (a - # prerelease of X.Y.Z always sorts below X.Y.Z), so a canary can never - # shadow the real release when it ships. - # Reuse the repo's own version helper (scripts/get-version.js) so this - # stays consistent with publish.yml: `current` returns the latest public - # dist-tag version, read-only from public npm (never the feed), then - # we bump the patch ourselves to keep strict patch+1 semantics. - PUBLIC_LATEST="$(node scripts/get-version.js current || true)" - BASE="${PUBLIC_LATEST%%-*}"; BASE="${BASE%%+*}" - if [[ "$BASE" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then - NEXT="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.$(( BASH_REMATCH[3] + 1 ))" - else - echo "::error::Could not resolve public SDK latest version (got '$PUBLIC_LATEST'); refusing to publish a canary with an unknown base." - exit 1 - fi - SDK_VERSION="${NEXT}-canary.${RUN_NUMBER}.g${SHORT_SHA}" - if [[ ! "$SDK_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then - echo "::error::Computed SDK canary version '$SDK_VERSION' is not valid semver." - exit 1 - fi - echo "SDK canary version: $SDK_VERSION" - echo "SDK_VERSION=$SDK_VERSION" >> "$GITHUB_OUTPUT" - - - name: Set package version and pin runtime dependency - env: - SDK_VERSION: ${{ steps.sdkver.outputs.SDK_VERSION }} - run: | - set -euo pipefail - npm version "$SDK_VERSION" --no-git-tag-version --allow-same-version - # Exact pin (no caret) so the published SDK canary depends on precisely - # the runtime version that was just tested by the e2e gate. - npm pkg set "dependencies.@github/copilot=$RUNTIME_VERSION" - echo "Pinned @github/copilot to $(npm pkg get dependencies.@github/copilot)" - - - name: Build SDK - run: npm run build - - - name: Azure Login (OIDC -> id-cpd-ci) - uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 - with: - client-id: "${{ vars.CPD_ID_CLIENT_ID }}" # id-cpd-ci - tenant-id: "${{ vars.CPD_ID_TENANT_ID }}" - allow-no-subscriptions: true - - # Auth-only .npmrc: just the two token lines, NO scoped registry line. - # The publish target is supplied explicitly via publishConfig + --registry. - - name: Configure feed auth (.npmrc) - run: | - set -euo pipefail - TOKEN="$(az account get-access-token --resource "$ADO_RESOURCE" --query accessToken -o tsv)" - echo "::add-mask::$TOKEN" - # Derive the protocol-relative auth scopes from FEED_URL (single source - # of truth). NO scoped @github:registry line here — publish target is - # supplied explicitly via publishConfig + --registry. - FEED_AUTH_REGISTRY="${FEED_URL#https:}" - FEED_AUTH_BASE="${FEED_AUTH_REGISTRY%registry/}" - printf '%s\n' \ - "${FEED_AUTH_REGISTRY}:_authToken=${TOKEN}" \ - "${FEED_AUTH_BASE}:_authToken=${TOKEN}" > .npmrc - echo "Wrote auth-only .npmrc to ./nodejs" - - # Belt and suspenders (2 of 3): pin the publish target in the package too. - - name: Set publishConfig registry - run: npm pkg set "publishConfig.registry=$FEED_URL" - - # Belt and suspenders (3 of 3): fail loudly unless the effective publish - # target is the internal feed. Guards against ever reaching public npm. - - name: Assert publish target is the internal feed - run: | - set -euo pipefail - EFFECTIVE="$(npm pkg get publishConfig.registry | tr -d '"')" - echo "Effective publishConfig.registry: $EFFECTIVE" - if [ "$EFFECTIVE" != "$FEED_URL" ]; then - echo "::error::publishConfig.registry ('$EFFECTIVE') is not the internal feed ('$FEED_URL'). Refusing to publish." - exit 1 - fi - - - name: Publish SDK canary to internal feed - run: npm publish --registry "$FEED_URL" - - - name: Summarize published canary - env: - SDK_VERSION: ${{ steps.sdkver.outputs.SDK_VERSION }} - run: | - set -euo pipefail - { - echo "## SDK canary published" - echo "" - echo "| | |" - echo "| --- | --- |" - echo "| Runtime consumed | \`@github/copilot@${RUNTIME_VERSION}\` |" - echo "| Canary SDK produced | \`@github/copilot-sdk@${SDK_VERSION}\` |" - echo "| Feed | ${FEED_URL} |" - } >> "$GITHUB_STEP_SUMMARY" +name: "SDK Canary Test/Publish" + +# Nightly-style canary pipeline. First installs an explicit version of the +# @github/copilot runtime, builds the Node SDK, and runs the Node e2e suite +# against it to prove runtime <-> SDK compatibility. When that gate passes (and +# mode allows), publishes an SDK canary pinned to the tested runtime to the +# internal Azure Artifacts feed only (never public npm). + +env: + HUSKY: 0 + # Internal org-scoped Azure Artifacts feed — single source of truth so the + # feed name isn't repeated across steps. The SDK canary publishes here and + # (when runtime_source=internal) installs the runtime from here; it must NEVER + # reach public npm (@github/copilot-sdk is a live public package). + FEED_URL: https://pkgs.dev.azure.com/devdiv/_packaging/copilot-canary/npm/registry/ + # Azure DevOps resource ID used to mint an ADO access token for the feed. + ADO_RESOURCE: 499b84ac-1321-427f-aa17-267ca6975798 + +on: + workflow_dispatch: + inputs: + runtime_version: + description: "Exact @github/copilot version to test (e.g. 1.0.69 or 1.0.70-canary.)" + required: true + type: string + runtime_source: + description: "Where to install the runtime from" + required: true + type: choice + options: + - public + - internal + default: public + mode: + description: "publish (tests must pass), publish-force (publish even if tests fail), or tests-only (run gate, never publish)" + required: false + type: choice + default: publish + options: + - publish + - publish-force + - tests-only + repository_dispatch: + types: [runtime-canary] + +permissions: + contents: read + id-token: write + +# Serialize runs per ref so two overlapping canary runs can't race the feed +# publish. cancel-in-progress: false — never kill an in-flight publish. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + resolve: + name: "Resolve runtime inputs" + if: github.event.repository.fork == false + runs-on: ubuntu-latest + permissions: {} + outputs: + RUNTIME_VERSION: ${{ steps.normalize.outputs.RUNTIME_VERSION }} + RUNTIME_SOURCE: ${{ steps.normalize.outputs.RUNTIME_SOURCE }} + PUBLISH_MODE: ${{ steps.normalize.outputs.PUBLISH_MODE }} + steps: + # Normalize whichever trigger fired into a single (RUNTIME_VERSION, + # RUNTIME_SOURCE, PUBLISH_MODE) triple that every downstream step + # references. workflow_dispatch reads the human-supplied inputs; + # repository_dispatch reads client_payload and forces source=internal + # (a runtime canary only exists on the feed), defaulting mode to publish. + - name: Normalize inputs + id: normalize + env: + EVENT_NAME: ${{ github.event_name }} + INPUT_VERSION: ${{ inputs.runtime_version }} + INPUT_SOURCE: ${{ inputs.runtime_source }} + INPUT_MODE: ${{ inputs.mode }} + PAYLOAD_VERSION: ${{ github.event.client_payload.runtime_version }} + PAYLOAD_SOURCE: ${{ github.event.client_payload.runtime_source }} + PAYLOAD_MODE: ${{ github.event.client_payload.mode }} + run: | + set -euo pipefail + case "$EVENT_NAME" in + workflow_dispatch) + VERSION="$INPUT_VERSION" + SOURCE="$INPUT_SOURCE" + MODE="$INPUT_MODE" + ;; + repository_dispatch) + VERSION="$PAYLOAD_VERSION" + # A runtime canary only ever exists on the internal feed. + SOURCE="${PAYLOAD_SOURCE:-internal}" + MODE="${PAYLOAD_MODE:-publish}" + ;; + *) + echo "::error::Unsupported event '$EVENT_NAME'." + exit 1 + ;; + esac + if [ -z "$VERSION" ]; then echo "::error::Could not determine runtime version."; exit 1; fi + if [ -z "$SOURCE" ]; then SOURCE="public"; fi + case "$SOURCE" in + public|internal) ;; + *) echo "::error::Invalid runtime source '$SOURCE'. Expected one of: public, internal."; exit 1 ;; + esac + if [ -z "$MODE" ]; then MODE="publish"; fi + case "$MODE" in + publish|publish-force|tests-only) ;; + *) echo "::error::Invalid publish mode '$MODE'. Expected one of: publish, publish-force, tests-only."; exit 1 ;; + esac + echo "Resolved RUNTIME_VERSION=$VERSION RUNTIME_SOURCE=$SOURCE PUBLISH_MODE=$MODE" + echo "RUNTIME_VERSION=$VERSION" >> "$GITHUB_OUTPUT" + echo "RUNTIME_SOURCE=$SOURCE" >> "$GITHUB_OUTPUT" + echo "PUBLISH_MODE=$MODE" >> "$GITHUB_OUTPUT" + + - name: Validate runtime version (semver) + env: + RUNTIME_VERSION: ${{ steps.normalize.outputs.RUNTIME_VERSION }} + run: | + if [[ ! "$RUNTIME_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then + echo "::error::Invalid runtime version '$RUNTIME_VERSION'. Expected semver (e.g. 1.0.69 or 1.0.70-canary.abc123)." + exit 1 + fi + + test: + name: "E2E tests (${{ matrix.os }})" + needs: resolve + if: github.event.repository.fork == false + environment: cicd + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runs-on: ${{ matrix.os }} + env: + POWERSHELL_UPDATECHECK: Off + RUNTIME_VERSION: ${{ needs.resolve.outputs.RUNTIME_VERSION }} + RUNTIME_SOURCE: ${{ needs.resolve.outputs.RUNTIME_SOURCE }} + defaults: + run: + shell: bash + working-directory: ./nodejs + steps: + - uses: actions/checkout@v6.0.2 + + - uses: actions/setup-node@v6 + with: + cache: "npm" + cache-dependency-path: "./nodejs/package-lock.json" + node-version: 22 + + - name: Install SDK dependencies + run: npm ci --ignore-scripts + + - name: Install test harness dependencies + working-directory: ./test/harness + run: npm ci --ignore-scripts + + - name: Azure Login (OIDC -> id-cpd-ci) + if: env.RUNTIME_SOURCE == 'internal' + uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 + with: + client-id: "${{ vars.CPD_ID_CLIENT_ID }}" # id-cpd-ci + tenant-id: "${{ vars.CPD_ID_TENANT_ID }}" + allow-no-subscriptions: true + + # Route ONLY @github/* (the runtime + its 8 platform packages) to the + # internal feed via a scoped registry. All other deps (e.g. detect-libc) + # still resolve from public npm. A global --registry would break because + # detect-libc is not on the feed. + - name: Configure canary feed (.npmrc) + if: env.RUNTIME_SOURCE == 'internal' + run: | + set -euo pipefail + TOKEN="$(az account get-access-token --resource "$ADO_RESOURCE" --query accessToken -o tsv)" + echo "::add-mask::$TOKEN" + # Derive the protocol-relative auth scopes from FEED_URL so the feed + # name lives in exactly one place (the workflow-level env). + FEED_AUTH_REGISTRY="${FEED_URL#https:}" + FEED_AUTH_BASE="${FEED_AUTH_REGISTRY%registry/}" + NPMRC="$(printf '%s\n' \ + "@github:registry=${FEED_URL}" \ + "${FEED_AUTH_REGISTRY}:_authToken=${TOKEN}" \ + "${FEED_AUTH_BASE}:_authToken=${TOKEN}")" + printf '%s\n' "$NPMRC" > .npmrc + echo "Wrote scoped @github registry .npmrc to ./nodejs" + + - name: Override runtime version + run: | + set -euo pipefail + echo "Pinning github/copilot-cli release ${RUNTIME_VERSION} (source: ${RUNTIME_SOURCE})" + node scripts/set-cli-version.js "$RUNTIME_VERSION" + npm install --ignore-scripts + + - name: Verify release runtime + run: | + set -euo pipefail + runtime_path=$(npm run --silent prepare:runtime -- --print-path) + test -x "$runtime_path" || [ "$RUNNER_OS" = "Windows" ] + test -s "$(dirname "$runtime_path")/runtime.node" + legacy_path=$(npm run --silent prepare:runtime -- --print-legacy-path) + node "$legacy_path" --version | grep -F "$RUNTIME_VERSION" + + - name: Build SDK + run: npm run build + + - name: Warm up PowerShell + if: runner.os == 'Windows' + run: pwsh.exe -Command "Write-Host 'PowerShell ready'" + + - name: Run Node.js SDK e2e tests + env: + COPILOT_HMAC_KEY: ${{ secrets.COPILOT_DEVELOPER_CLI_INTEGRATION_HMAC_KEY }} + run: npm test + + publish: + name: "Publish SDK canary (internal feed)" + needs: [resolve, test] + # Publish runs only when the gate permits it. Mode governs behavior: + # - tests-only: never publish (skips this job entirely). + # - publish: publish only when the e2e gate is green (the default for both + # the human and automated triggers). + # - publish-force: publish even on a non-green gate — a human-acknowledged + # flake override, audited via the ::warning:: step below and the run actor. + # publish-force only skips the e2e *signal* — the publish job still runs the + # build (so a broken build can't publish) and enforces the feed-only guards. + if: > + !cancelled() && + github.event.repository.fork == false && + needs.resolve.result == 'success' && + needs.resolve.outputs.PUBLISH_MODE != 'tests-only' && + (needs.test.result == 'success' || + needs.resolve.outputs.PUBLISH_MODE == 'publish-force') + environment: cicd + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + env: + RUNTIME_VERSION: ${{ needs.resolve.outputs.RUNTIME_VERSION }} + defaults: + run: + shell: bash + working-directory: ./nodejs + steps: + - name: Warn — publishing despite failed e2e gate (publish-force) + # always() so this audit is never skipped by prior-step status; it fires + # specifically when publish proceeded on a non-green gate via publish-force. + # Runs at the workspace root because it executes before checkout, so the + # job's default working-directory (./nodejs) does not exist yet. + if: always() && needs.test.result != 'success' && needs.resolve.outputs.PUBLISH_MODE == 'publish-force' + working-directory: ${{ github.workspace }} + run: | + echo "::warning title=e2e gate bypassed::Publishing SDK canary despite a non-passing e2e gate (test job result: ${{ needs.test.result }}) via publish-force. Triggered by '${{ github.actor }}' through '${{ github.event_name }}'. The e2e signal was bypassed; build + feed-only guards still apply." + + - uses: actions/checkout@v6.0.2 + + - uses: actions/setup-node@v6 + with: + node-version: 22 + + # Default public registry: installs build deps and the currently pinned + # runtime. Do NOT write any feed .npmrc or scoped @github:registry line + # here, or npm ci would try to fetch the runtime from the upstream-less + # feed and 404. + - name: Install SDK dependencies + run: npm ci --ignore-scripts + + - name: Compute SDK canary version + id: sdkver + env: + RUN_NUMBER: ${{ github.run_number }} + SHA: ${{ github.sha }} + run: | + set -euo pipefail + SHORT_SHA="${SHA:0:7}" + # Base the canary on the NEXT patch of the public SDK latest so canaries + # correlate with public releases: they sort ABOVE the current public + # latest and BELOW the eventual real release of that next patch (a + # prerelease of X.Y.Z always sorts below X.Y.Z), so a canary can never + # shadow the real release when it ships. + # Reuse the repo's own version helper (scripts/get-version.js) so this + # stays consistent with publish.yml: `current` returns the latest public + # dist-tag version, read-only from public npm (never the feed), then + # we bump the patch ourselves to keep strict patch+1 semantics. + PUBLIC_LATEST="$(node scripts/get-version.js current || true)" + BASE="${PUBLIC_LATEST%%-*}"; BASE="${BASE%%+*}" + if [[ "$BASE" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + NEXT="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.$(( BASH_REMATCH[3] + 1 ))" + else + echo "::error::Could not resolve public SDK latest version (got '$PUBLIC_LATEST'); refusing to publish a canary with an unknown base." + exit 1 + fi + SDK_VERSION="${NEXT}-canary.${RUN_NUMBER}.g${SHORT_SHA}" + if [[ ! "$SDK_VERSION" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$ ]]; then + echo "::error::Computed SDK canary version '$SDK_VERSION' is not valid semver." + exit 1 + fi + echo "SDK canary version: $SDK_VERSION" + echo "SDK_VERSION=$SDK_VERSION" >> "$GITHUB_OUTPUT" + + - name: Set package and runtime versions + env: + SDK_VERSION: ${{ steps.sdkver.outputs.SDK_VERSION }} + run: | + set -euo pipefail + npm version "$SDK_VERSION" --no-git-tag-version --allow-same-version + node scripts/set-cli-version.js "$RUNTIME_VERSION" + npm install --package-lock-only --ignore-scripts + echo "Pinned github/copilot-cli release to $(npm pkg get copilotCliVersion)" + + - name: Build SDK + run: npm run build + + - name: Azure Login (OIDC -> id-cpd-ci) + uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 + with: + client-id: "${{ vars.CPD_ID_CLIENT_ID }}" # id-cpd-ci + tenant-id: "${{ vars.CPD_ID_TENANT_ID }}" + allow-no-subscriptions: true + + # Auth-only .npmrc: just the two token lines, NO scoped registry line. + # The publish target is supplied explicitly via publishConfig + --registry. + - name: Configure feed auth (.npmrc) + run: | + set -euo pipefail + TOKEN="$(az account get-access-token --resource "$ADO_RESOURCE" --query accessToken -o tsv)" + echo "::add-mask::$TOKEN" + # Derive the protocol-relative auth scopes from FEED_URL (single source + # of truth). NO scoped @github:registry line here — publish target is + # supplied explicitly via publishConfig + --registry. + FEED_AUTH_REGISTRY="${FEED_URL#https:}" + FEED_AUTH_BASE="${FEED_AUTH_REGISTRY%registry/}" + printf '%s\n' \ + "${FEED_AUTH_REGISTRY}:_authToken=${TOKEN}" \ + "${FEED_AUTH_BASE}:_authToken=${TOKEN}" > .npmrc + echo "Wrote auth-only .npmrc to ./nodejs" + + # Belt and suspenders (2 of 3): pin the publish target in the package too. + - name: Set publishConfig registry + run: npm pkg set "publishConfig.registry=$FEED_URL" + + # Belt and suspenders (3 of 3): fail loudly unless the effective publish + # target is the internal feed. Guards against ever reaching public npm. + - name: Assert publish target is the internal feed + run: | + set -euo pipefail + EFFECTIVE="$(npm pkg get publishConfig.registry | tr -d '"')" + echo "Effective publishConfig.registry: $EFFECTIVE" + if [ "$EFFECTIVE" != "$FEED_URL" ]; then + echo "::error::publishConfig.registry ('$EFFECTIVE') is not the internal feed ('$FEED_URL'). Refusing to publish." + exit 1 + fi + + - name: Publish SDK canary to internal feed + run: npm publish --registry "$FEED_URL" + + - name: Summarize published canary + env: + SDK_VERSION: ${{ steps.sdkver.outputs.SDK_VERSION }} + run: | + set -euo pipefail + { + echo "## SDK canary published" + echo "" + echo "| | |" + echo "| --- | --- |" + echo "| Runtime consumed | \`@github/copilot@${RUNTIME_VERSION}\` |" + echo "| Canary SDK produced | \`@github/copilot-sdk@${SDK_VERSION}\` |" + echo "| Feed | ${FEED_URL} |" + } >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/update-copilot-dependency.yml b/.github/workflows/update-copilot-dependency.yml index 9646366ad5..8dc550edbe 100644 --- a/.github/workflows/update-copilot-dependency.yml +++ b/.github/workflows/update-copilot-dependency.yml @@ -1,10 +1,10 @@ -name: "Update @github/copilot Dependency" +name: "Update Copilot CLI Version" on: workflow_dispatch: inputs: version: - description: "Target version of @github/copilot (e.g. 0.0.420)" + description: "Target github/copilot-cli release version (e.g. 1.0.83-0)" required: true type: string @@ -14,7 +14,7 @@ permissions: jobs: update: - name: "Update @github/copilot to ${{ inputs.version }}" + name: "Update Copilot CLI to ${{ inputs.version }}" runs-on: ubuntu-latest steps: - name: Validate version input @@ -56,17 +56,13 @@ jobs: toolchain: nightly-2026-04-14 components: rustfmt - - name: Update @github/copilot in nodejs + - name: Update the Node.js CLI release pin env: VERSION: ${{ inputs.version }} working-directory: ./nodejs - run: npm install "@github/copilot@$VERSION" - - - name: Update @github/copilot in test harness - env: - VERSION: ${{ inputs.version }} - working-directory: ./test/harness - run: npm install "@github/copilot@$VERSION" + run: | + node scripts/set-cli-version.js "$VERSION" + npm install --ignore-scripts - name: Refresh nodejs/samples lockfile working-directory: ./nodejs/samples @@ -167,7 +163,7 @@ jobs: git commit -m "Update @github/copilot to $VERSION - - Updated nodejs and test harness dependencies + - Updated the Node.js CLI release pin - Re-ran code generators - Formatted generated code" @@ -177,7 +173,7 @@ jobs: Automated update of `@github/copilot` to version `PLACEHOLDER_VERSION`. ### Changes - - Updated `@github/copilot` in `nodejs/package.json` and `test/harness/package.json` + - Updated the Copilot CLI release pin in `nodejs/package.json` - Re-ran all code generators (`scripts/codegen`) - Formatted generated output - Updated Java codegen dependency, POM property, and regenerated Java types @@ -208,7 +204,7 @@ jobs: ### Next steps When ready, click **Ready for review** to trigger CI checks. - > Created by the **Update @github/copilot Dependency** workflow. + > Created by the **Update Copilot CLI Version** workflow. BODY_EOF ) PR_BODY="${PR_BODY//PLACEHOLDER_VERSION/$VERSION}" diff --git a/dotnet/src/GitHub.Copilot.SDK.csproj b/dotnet/src/GitHub.Copilot.SDK.csproj index f48fb802d7..e5a2d9fb98 100644 --- a/dotnet/src/GitHub.Copilot.SDK.csproj +++ b/dotnet/src/GitHub.Copilot.SDK.csproj @@ -63,10 +63,10 @@ - + - + diff --git a/go/cmd/bundler/main.go b/go/cmd/bundler/main.go index 89f99daf1b..763cd00f2d 100644 --- a/go/cmd/bundler/main.go +++ b/go/cmd/bundler/main.go @@ -8,7 +8,7 @@ // --platform: Target platform using Go conventions (linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64, windows/arm64). Defaults to current platform. // --output: Output directory for embedded artifacts. Defaults to the current directory. // --cli-version: CLI version to download. If not specified, automatically detects from the copilot-sdk version in go.mod. -// --check-only: Check that embedded CLI version matches the detected version from package-lock.json without downloading. Exits with error if versions don't match. +// --check-only: Check that embedded CLI version matches the detected version from package.json without downloading. Exits with error if versions don't match. package main import ( @@ -37,6 +37,7 @@ import ( const ( // Keep these URLs centralized so reviewers can verify all outbound calls in one place. sdkModule = "github.com/github/copilot-sdk/go" + packageJSONURLFmt = "https://raw.githubusercontent.com/github/copilot-sdk/%s/nodejs/package.json" packageLockURLFmt = "https://raw.githubusercontent.com/github/copilot-sdk/%s/nodejs/package-lock.json" tarballURLFmt = "https://registry.npmjs.org/@github/copilot-%s/-/copilot-%s-%s.tgz" licenseTarballFmt = "https://registry.npmjs.org/@github/copilot/-/copilot-%s.tgz" @@ -262,8 +263,8 @@ func detectPackageName(dir, goos, goarch string) (string, error) { // detectCLIVersion detects the CLI version by: // 1. Running "go list -m" to get the copilot-sdk version from the user's go.mod -// 2. Fetching the package-lock.json from the SDK repo at that version -// 3. Extracting the @github/copilot CLI version from it +// 2. Fetching package.json from the SDK repo at that version +// 3. Extracting the pinned Copilot CLI version from it func detectCLIVersion() (string, error) { // Get the SDK version from the user's go.mod sdkVersion, err := getSDKVersion() @@ -273,7 +274,7 @@ func detectCLIVersion() (string, error) { fmt.Printf("Found copilot-sdk %s in go.mod\n", sdkVersion) - // Fetch package-lock.json from the SDK repo at that version + // Fetch package.json from the SDK repo at that version cliVersion, err := fetchCLIVersionFromRepo(sdkVersion) if err != nil { return "", fmt.Errorf("failed to fetch CLI version: %w", err) @@ -301,7 +302,7 @@ func getSDKVersion() (string, error) { return version, nil } -// fetchCLIVersionFromRepo fetches package-lock.json from GitHub and extracts the CLI version. +// fetchCLIVersionFromRepo fetches package.json from GitHub and extracts the CLI version. func fetchCLIVersionFromRepo(sdkVersion string) (string, error) { // Convert Go module version to Git ref // v0.1.0 -> v0.1.0 @@ -319,7 +320,7 @@ func fetchCLIVersionFromRepo(sdkVersion string) (string, error) { } } - url := fmt.Sprintf(packageLockURLFmt, gitRef) + url := fmt.Sprintf(packageJSONURLFmt, gitRef) fmt.Printf("Fetching %s...\n", url) resp, err := http.Get(url) @@ -329,7 +330,35 @@ func fetchCLIVersionFromRepo(sdkVersion string) (string, error) { defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("failed to fetch package-lock.json: %s", resp.Status) + return "", fmt.Errorf("failed to fetch package.json: %s", resp.Status) + } + + var packageJSON struct { + CopilotCLIVersion string `json:"copilotCliVersion"` + } + + if err := json.NewDecoder(resp.Body).Decode(&packageJSON); err != nil { + return "", fmt.Errorf("failed to parse package.json: %w", err) + } + + if packageJSON.CopilotCLIVersion == "" { + return fetchLegacyCLIVersionFromRepo(gitRef) + } + + return packageJSON.CopilotCLIVersion, nil +} + +func fetchLegacyCLIVersionFromRepo(gitRef string) (string, error) { + url := fmt.Sprintf(packageLockURLFmt, gitRef) + fmt.Printf("Falling back to %s...\n", url) + + resp, err := http.Get(url) + if err != nil { + return "", fmt.Errorf("failed to fetch legacy package-lock.json: %w", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("failed to fetch legacy package-lock.json: %s", resp.Status) } var packageLock struct { @@ -337,16 +366,13 @@ func fetchCLIVersionFromRepo(sdkVersion string) (string, error) { Version string `json:"version"` } `json:"packages"` } - if err := json.NewDecoder(resp.Body).Decode(&packageLock); err != nil { - return "", fmt.Errorf("failed to parse package-lock.json: %w", err) + return "", fmt.Errorf("failed to parse legacy package-lock.json: %w", err) } - pkg, ok := packageLock.Packages["node_modules/@github/copilot"] if !ok || pkg.Version == "" { - return "", fmt.Errorf("could not find @github/copilot version in package-lock.json") + return "", fmt.Errorf("could not find copilotCliVersion in package.json or @github/copilot in package-lock.json") } - return pkg.Version, nil } diff --git a/java/copilot-native/pom.xml b/java/copilot-native/pom.xml index c03be9909e..b31ba138ba 100644 --- a/java/copilot-native/pom.xml +++ b/java/copilot-native/pom.xml @@ -31,14 +31,13 @@ ${project.basedir}/../.. ${project.build.directory}/native-staging false @@ -60,11 +59,10 @@ org.codehaus.mojo diff --git a/java/copilot-native/scripts/fetch-native.mjs b/java/copilot-native/scripts/fetch-native.mjs index 4ee91b6aa6..f34ece61e0 100644 --- a/java/copilot-native/scripts/fetch-native.mjs +++ b/java/copilot-native/scripts/fetch-native.mjs @@ -6,10 +6,10 @@ * Downloads the native runtime artifacts for one platform classifier. * * Steps: - * 1. Read the pinned version and the SHA-512 `integrity` value for - * `@github/copilot-` from `nodejs/package-lock.json`. - * 2. `npm pack` that exact version into the staging directory. - * 3. Verify the downloaded tarball against the `integrity` value. + * 1. Read the pinned version from `nodejs/package.json`. + * 2. Download the platform npm tarball and checksums from the matching + * `github/copilot-cli` release. + * 3. Verify the downloaded tarball against the release SHA-256. * 4. Stage the hostless runtime tree, flattening the selected prebuild directory * beside the package's retained top-level runtime assets. * 5. Write an inventory consumed by the SDK's generic classpath extractor. @@ -52,19 +52,12 @@ if (!repoRoot || !stagingDir || !classifier) { process.exit(1); } -const lockPath = path.join(repoRoot, 'nodejs', 'package-lock.json'); +const packagePath = path.join(repoRoot, 'nodejs', 'package.json'); const packageName = `@github/copilot-${classifier}`; -const lock = JSON.parse(fs.readFileSync(lockPath, 'utf8')); -const entry = lock.packages?.[`node_modules/${packageName}`]; - -if (!entry?.version || !entry?.integrity) { - console.error(`Could not find version/integrity for ${packageName} in ${lockPath}`); - process.exit(1); -} - -const { version, integrity } = entry; -if (!integrity.startsWith('sha512-')) { - console.error(`Unsupported integrity algorithm for ${packageName}: ${integrity}`); +const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8')); +const version = packageJson.copilotCliVersion; +if (!version) { + console.error(`Could not find copilotCliVersion in ${packagePath}`); process.exit(1); } @@ -92,14 +85,12 @@ if ( const stampLines = fs.readFileSync(stampPath, 'utf8').trim().split('\n'); const stampSchema = stampLines[0] || ''; const stampVersion = stampLines[1] || ''; - const stampIntegrity = stampLines[2] || ''; const stampTreeDigest = stampLines[3] || ''; const currentTreeDigest = digestTree(resourceDir); const currentPlatformProperties = fs.readFileSync(platformPropertiesPath, 'utf8'); if ( stampSchema === stagingSchema && stampVersion === version && - stampIntegrity === integrity && stampTreeDigest === currentTreeDigest && currentPlatformProperties === expectedPlatformProperties ) { @@ -112,21 +103,39 @@ fs.rmSync(outDir, { recursive: true, force: true }); fs.mkdirSync(resourceDir, { recursive: true }); console.log(`Downloading ${packageName}@${version} ...`); -const packOutput = execFileSync('npm', ['pack', `${packageName}@${version}`, '--pack-destination', outDir], { - encoding: 'utf8', - shell: process.platform === 'win32', -}); -const tarballName = packOutput.trim().split('\n').pop().trim(); +const assetName = `github-copilot-${version}-${classifier}.tgz`; +const tarballName = assetName; const tarballPath = path.join(outDir, tarballName); - -const actual = `sha512-${createHash('sha512').update(fs.readFileSync(tarballPath)).digest('base64')}`; -if (actual !== integrity) { +const releaseBase = ( + process.env.COPILOT_CLI_DOWNLOAD_BASE_URL ?? + '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/github/copilot-cli/releases/download' +).replace(/\/+$/, ''); +let archive; +let expectedHash; +if (process.env.COPILOT_CLI_RELEASE_TARBALL) { + archive = fs.readFileSync(process.env.COPILOT_CLI_RELEASE_TARBALL); + expectedHash = process.env.COPILOT_CLI_RELEASE_SHA256; +} else { + const releaseUrl = `${releaseBase}/v${version}`; + const [checksums, downloadedArchive] = await Promise.all([ + download(`${releaseUrl}/SHA256SUMS.txt`).then((data) => data.toString('utf8')), + download(`${releaseUrl}/${assetName}`), + ]); + expectedHash = checksumForAsset(checksums, assetName); + archive = downloadedArchive; +} +if (!expectedHash || !/^[a-fA-F0-9]{64}$/.test(expectedHash)) { + throw new Error(`Missing or invalid SHA-256 for ${assetName}`); +} +fs.writeFileSync(tarballPath, archive); +const actual = createHash('sha256').update(archive).digest('hex'); +if (actual !== expectedHash.toLowerCase()) { console.error(`Integrity verification failed for ${tarballPath}`); - console.error(` expected: ${integrity}`); + console.error(` expected: ${expectedHash}`); console.error(` actual: ${actual}`); process.exit(1); } -console.log(`Integrity verified (${integrity.slice(0, 20)}...).`); +console.log(`Integrity verified (${expectedHash.slice(0, 20)}...).`); const inventory = []; const members = execFileSync('tar', ['-tzf', tarballPath], { encoding: 'utf8' }) @@ -169,7 +178,7 @@ if (!fs.existsSync(runtimePath) || !fs.existsSync(wrapperPath)) { } fs.writeFileSync(platformPropertiesPath, expectedPlatformProperties); const treeDigest = digestTree(resourceDir); -fs.writeFileSync(stampPath, `${stagingSchema}\n${version}\n${integrity}\n${treeDigest}\n`); +fs.writeFileSync(stampPath, `${stagingSchema}\n${version}\n${expectedHash}\n${treeDigest}\n`); console.log(`Staged ${runtimePath}`); @@ -224,3 +233,36 @@ function digestTree(directory) { } return `sha512-${hash.digest('base64')}`; } + +function checksumForAsset(checksums, assetName) { + for (const line of checksums.split(/\r?\n/)) { + const [hash, name] = line.trim().split(/\s+/, 2); + if (name?.replace(/^\*/, '') === assetName && /^[a-fA-F0-9]{64}$/.test(hash)) { + return hash; + } + } + throw new Error(`SHA256SUMS.txt does not contain ${assetName}`); +} + +async function download(url) { + let lastError; + for (let attempt = 0; attempt < 3; attempt++) { + try { + const response = await fetch(url); + if (response.ok) { + return Buffer.from(await response.arrayBuffer()); + } + await response.body?.cancel(); + lastError = new Error(`${response.status} ${response.statusText}`); + if (response.status >= 400 && response.status < 500 && response.status !== 408 && response.status !== 429) { + break; + } + } catch (error) { + lastError = error; + } + if (attempt < 2) { + await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1000)); + } + } + throw new Error(`Failed to download ${url}: ${lastError}`); +} diff --git a/java/copilot-native/scripts/fetch-native.test.mjs b/java/copilot-native/scripts/fetch-native.test.mjs index 582ffa397f..83c484e08e 100644 --- a/java/copilot-native/scripts/fetch-native.test.mjs +++ b/java/copilot-native/scripts/fetch-native.test.mjs @@ -12,7 +12,7 @@ import { fileURLToPath } from 'node:url'; import test from 'node:test'; const version = '1.0.79'; -const integrity = 'sha512-test-integrity'; +const checksum = '0'.repeat(64); const runtimeContent = 'runtime content'; const wrapperContent = 'wrapper content'; const stagingSchema = 'hostless-runtime-v2'; @@ -26,7 +26,6 @@ for (const classifier of ['linux-x64', 'linux-arm64', 'win32-x64', 'win32-arm64' assert.equal(result.status, 0, result.stderr); assert.match(result.stdout, /already staged/); - assert.equal(fs.existsSync(fixture.npmMarkerPath), false); }); test(`${classifier}: missing runtime wrapper does not use incremental fast path`, (t) => { @@ -73,7 +72,6 @@ for (const classifier of ['linux-x64', 'linux-arm64', 'win32-x64', 'win32-arm64' assert.equal(result.status, 0, result.stderr); assert.match(result.stdout, /already staged/); - assert.equal(fs.existsSync(fixture.npmMarkerPath), false); }); } @@ -95,25 +93,17 @@ test('stages retained package assets and excludes CLI-only content', (t) => { fs.writeFileSync(path.join(packageRoot, 'README.md'), 'excluded'); const tarball = path.join(fixture.repoRoot, 'fixture.tgz'); execFileSync('tar', ['-czf', tarball, '-C', path.dirname(packageRoot), 'package']); - const packageIntegrity = digest(fs.readFileSync(tarball)); + const packageChecksum = createHash('sha256').update(fs.readFileSync(tarball)).digest('hex'); fs.writeFileSync( - path.join(fixture.repoRoot, 'nodejs', 'package-lock.json'), - JSON.stringify({ - packages: { - [`node_modules/@github/copilot-${classifier}`]: { version, integrity: packageIntegrity }, - }, - }), + path.join(fixture.repoRoot, 'nodejs', 'package.json'), + JSON.stringify({ copilotCliVersion: version }), ); - const fakeNpmPath = path.join(fixture.fakeBinDir, process.platform === 'win32' ? 'npm.cmd' : 'npm'); - const fakeNpm = - process.platform === 'win32' - ? '@copy "%FETCH_NATIVE_TARBALL%" "%4\\fixture.tgz" >nul\r\n@echo fixture.tgz\r\n' - : '#!/bin/sh\ncp "$FETCH_NATIVE_TARBALL" "$4/fixture.tgz"\nprintf "fixture.tgz\\n"\n'; - fs.writeFileSync(fakeNpmPath, fakeNpm); - fs.chmodSync(fakeNpmPath, 0o755); fs.rmSync(path.join(fixture.stagingDir, classifier), { recursive: true, force: true }); - const result = runScript(fixture, { FETCH_NATIVE_TARBALL: tarball }); + const result = runScript(fixture, { + COPILOT_CLI_RELEASE_TARBALL: tarball, + COPILOT_CLI_RELEASE_SHA256: packageChecksum, + }); assert.equal(result.status, 0, result.stderr); const resourceDir = path.join(fixture.stagingDir, classifier, 'native', classifier); @@ -133,19 +123,12 @@ function createFixture(t, classifier) { const repoRoot = path.join(root, 'repo'); const stagingDir = path.join(root, 'staging'); const resourceDir = path.join(stagingDir, classifier, 'native', classifier); - const fakeBinDir = path.join(root, 'bin'); - const npmMarkerPath = path.join(root, 'npm-invoked'); fs.mkdirSync(path.join(repoRoot, 'nodejs'), { recursive: true }); fs.mkdirSync(resourceDir, { recursive: true }); - fs.mkdirSync(fakeBinDir); fs.writeFileSync( - path.join(repoRoot, 'nodejs', 'package-lock.json'), - JSON.stringify({ - packages: { - [`node_modules/@github/copilot-${classifier}`]: { version, integrity }, - }, - }), + path.join(repoRoot, 'nodejs', 'package.json'), + JSON.stringify({ copilotCliVersion: version }), ); const runtimePath = path.join(resourceDir, 'runtime.node'); @@ -167,23 +150,14 @@ function createFixture(t, classifier) { fs.writeFileSync(platformPropertiesPath, `classifier=${classifier}\nversion=${version}\n`); fs.writeFileSync( path.join(stagingDir, classifier, '.version'), - `${stagingSchema}\n${version}\n${integrity}\n${digestTree(resourceDir)}\n`, + `${stagingSchema}\n${version}\n${checksum}\n${digestTree(resourceDir)}\n`, ); - const fakeNpmPath = path.join(fakeBinDir, process.platform === 'win32' ? 'npm.cmd' : 'npm'); - if (process.platform === 'win32') { - fs.writeFileSync(fakeNpmPath, '@echo off\r\n> "%FETCH_NATIVE_NPM_MARKER%" echo invoked\r\nexit /b 42\r\n'); - } else { - fs.writeFileSync(fakeNpmPath, '#!/bin/sh\nprintf invoked > "$FETCH_NATIVE_NPM_MARKER"\nexit 42\n'); - fs.chmodSync(fakeNpmPath, 0o755); - } - return { + root, classifier, repoRoot, stagingDir, - fakeBinDir, - npmMarkerPath, runtimePath, wrapperPath, ripgrepPath, @@ -196,16 +170,15 @@ function runScript(fixture, extraEnv = {}) { encoding: 'utf8', env: { ...process.env, - PATH: `${fixture.fakeBinDir}${path.delimiter}${process.env.PATH}`, - FETCH_NATIVE_NPM_MARKER: fixture.npmMarkerPath, + COPILOT_CLI_RELEASE_TARBALL: path.join(fixture.root, 'missing.tgz'), + COPILOT_CLI_RELEASE_SHA256: checksum, ...extraEnv, }, }); } function assertRestagingAttempted(fixture, result) { - assert.notEqual(result.status, 0, 'The fake npm command should make restaging fail'); - assert.equal(fs.readFileSync(fixture.npmMarkerPath, 'utf8').trim(), 'invoked'); + assert.notEqual(result.status, 0, 'The unavailable release should make restaging fail'); } function digestTree(directory) { diff --git a/java/copilot-native/scripts/validate-native-artifact.mjs b/java/copilot-native/scripts/validate-native-artifact.mjs index 9af4ffd772..466fa260a4 100644 --- a/java/copilot-native/scripts/validate-native-artifact.mjs +++ b/java/copilot-native/scripts/validate-native-artifact.mjs @@ -119,22 +119,19 @@ export function validateSha256Manifest({ } export function readPinnedNativeVersion(repoRoot, classifier) { - const packageName = `@github/copilot-${classifier}`; - const lockPath = path.join(repoRoot, "nodejs", "package-lock.json"); - let lock; + const packagePath = path.join(repoRoot, "nodejs", "package.json"); + let packageJson; try { - lock = JSON.parse(fs.readFileSync(lockPath, "utf8")); + packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8")); } catch (error) { throw new Error( - `Could not read pinned ${packageName} version from ${lockPath}: ${error.message}`, + `Could not read pinned Copilot CLI version from ${packagePath}: ${error.message}`, ); } - const version = lock.packages?.[`node_modules/${packageName}`]?.version; + const version = packageJson.copilotCliVersion; if (!version) { - throw new Error( - `Could not find pinned ${packageName} version in ${lockPath}`, - ); + throw new Error(`Could not find copilotCliVersion in ${packagePath}`); } return version; } diff --git a/java/copilot-native/scripts/validate-native-artifact.test.mjs b/java/copilot-native/scripts/validate-native-artifact.test.mjs index 25851bb575..c07695a484 100644 --- a/java/copilot-native/scripts/validate-native-artifact.test.mjs +++ b/java/copilot-native/scripts/validate-native-artifact.test.mjs @@ -67,7 +67,7 @@ test("accepts a matching, complete Windows ARM64 classifier", (t) => { }), { classifier: windowsArm64Classifier, - nativeVersion: "9.8.10", + nativeVersion: "9.8.7", sha256: undefined, }, ); @@ -94,7 +94,7 @@ test("accepts a matching, complete Darwin classifier", (t) => { }), { classifier: darwinClassifier, - nativeVersion: "9.8.8", + nativeVersion: "9.8.7", sha256: undefined, }, ); @@ -121,7 +121,7 @@ test("accepts a matching, complete Linux ARM64 classifier", (t) => { }), { classifier: linuxArm64Classifier, - nativeVersion: "9.8.9", + nativeVersion: "9.8.7", sha256: undefined, }, ); @@ -248,7 +248,7 @@ test("rejects Windows resources in a Linux classifier", (t) => { ["native/linux-x64/copilot-runtime", "runtime wrapper"], [ "native/linux-x64/platform.properties", - "classifier=linux-x64\nversion=9.8.6\n", + "classifier=linux-x64\nversion=9.8.7\n", ], ["native/win32-x64/runtime.node", "wrong platform"], ]); @@ -462,7 +462,7 @@ test("local publication validation rejects cross-classifier contamination", (t) ["native/linux-x64/copilot-runtime", "runtime wrapper"], [ "native/linux-x64/platform.properties", - "classifier=linux-x64\nversion=9.8.6\n", + "classifier=linux-x64\nversion=9.8.7\n", ], ["native/win32-x64/runtime.node", "wrong platform"], ], @@ -537,16 +537,8 @@ function createFixture(t) { const repoRoot = path.join(root, "repo"); fs.mkdirSync(path.join(repoRoot, "nodejs"), { recursive: true }); fs.writeFileSync( - path.join(repoRoot, "nodejs", "package-lock.json"), - JSON.stringify({ - packages: { - "node_modules/@github/copilot-win32-x64": { version: "9.8.7" }, - "node_modules/@github/copilot-win32-arm64": { version: "9.8.10" }, - "node_modules/@github/copilot-linux-x64": { version: "9.8.6" }, - "node_modules/@github/copilot-linux-arm64": { version: "9.8.9" }, - "node_modules/@github/copilot-darwin-arm64": { version: "9.8.8" }, - }, - }), + path.join(repoRoot, "nodejs", "package.json"), + JSON.stringify({ copilotCliVersion: "9.8.7" }), ); return { diff --git a/java/docs/adr/adr-007-native-bundling-strategy.md b/java/docs/adr/adr-007-native-bundling-strategy.md index 1540829366..3c13d451cf 100644 --- a/java/docs/adr/adr-007-native-bundling-strategy.md +++ b/java/docs/adr/adr-007-native-bundling-strategy.md @@ -361,7 +361,7 @@ The pattern follows DJL's `LibUtils.loadLibrary()` approach: detect the platform 2. Locates the matching `runtime.node` binary on the classpath (via `getResourceAsStream` from the classifier JAR). 3. Extracts `runtime.node` and the transitional CLI entrypoint into `~/.copilot/runtime-cache/` if valid cached files are not already present. 4. Loads it via [JNA](#references) using the C ABI entry points, per the [binding technology decision](#binding-technology-jna-over-panama-ffm) above. The JNA-specific code is confined behind an internal binding interface to preserve a future FFM migration path. -* A validated supported-host profile fetches the pinned matching `@github/copilot-` npm package, verifies its SHA-512 integrity from `nodejs/package-lock.json`, and packages the version-matched runtime and CLI files. +* A validated supported-host profile fetches the matching platform tarball from the pinned `github/copilot-cli` release, verifies its release SHA-256, and packages the version-matched runtime files. * The current release work publishes the `linux-x64`, `linux-arm64`, `win32-x64`, `win32-arm64`, and `darwin-arm64` classifiers. The planned classifier set expands to the other detected platforms. * Adding an implemented platform requires validated host activation, a profile that supplies the classifier and platform CLI filename, and lifecycle bindings for the shared host validation, fetch, script test, package, and verification executions. * `cli-native.node` is not bundled. It provides terminal UI features that are irrelevant to the Java SDK's programmatic API surface. diff --git a/java/sdk/pom.xml b/java/sdk/pom.xml index dd22744f48..4f6ae9771e 100644 --- a/java/sdk/pom.xml +++ b/java/sdk/pom.xml @@ -42,20 +42,18 @@ ${project.basedir}/../.. ${copilot.sdk.root}/test - ${copilot.sdk.root}/nodejs/node_modules/@github/copilot/npm-loader.js + false + install-nodejs-cli-dependencies generate-test-resources @@ -249,9 +242,8 @@ ${copilot.cli.path} @@ -281,10 +273,9 @@ ${copilot.cli.path} diff --git a/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java b/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java index cb302a8cd2..d8e7d8d9b9 100644 --- a/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java +++ b/java/sdk/src/test/java/com/github/copilot/E2ETestContext.java @@ -596,53 +596,17 @@ private static String getCliPath(Path repoRoot) throws IOException { return envPath; } - // Try test harness platform-specific binary (preferred as it has correct - // version) - String os = System.getProperty("os.name").toLowerCase(); - String arch = System.getProperty("os.arch").toLowerCase(); - String platform = os.contains("mac") ? "darwin" : os.contains("win") ? "win32" : "linux"; - String cpuArch = arch.contains("aarch64") || arch.contains("arm64") ? "arm64" : "x64"; - Path platformBinary = repoRoot - .resolve("test/harness/node_modules/@github/copilot-" + platform + "-" + cpuArch + "/copilot"); - if (os.contains("win")) { - platformBinary = repoRoot - .resolve("test/harness/node_modules/@github/copilot-" + platform + "-" + cpuArch + "/copilot.exe"); - } - if (Files.exists(platformBinary)) { - return platformBinary.toString(); - } - - // Try test harness npm-loader.js - Path harnessCliPath = repoRoot.resolve("test/harness/node_modules/@github/copilot/npm-loader.js"); - if (Files.exists(harnessCliPath)) { - return harnessCliPath.toString(); - } - - // Try nodejs installation. As of CLI 1.0.64-1 the @github/copilot package - // is a thin loader; the runnable index.js ships in the installed - // platform-specific package (e.g. @github/copilot-linux-x64). Exactly one - // is installed. Running index.js under Node.js is the documented preferred - // entry point and matches the Go, Python, Rust, and .NET test harnesses. - Path githubModules = repoRoot.resolve("nodejs/node_modules/@github"); - if (Files.isDirectory(githubModules)) { - try (var modules = Files.newDirectoryStream(githubModules, "copilot-*")) { - for (Path module : modules) { - Path indexJs = module.resolve("index.js"); - if (Files.exists(indexJs)) { - return indexJs.toString(); - } - } - } - } - // Fallback: try to find 'copilot' in PATH String copilotInPath = findCopilotInPath(); if (copilotInPath != null) { return copilotInPath; } - throw new IOException("CLI not found. Either install 'copilot' globally, set COPILOT_CLI_PATH, " - + "or run 'npm install' in the nodejs directory or test/harness directory."); + try { + return TestUtil.preparePinnedCli(repoRoot); + } catch (Exception e) { + throw new IOException("CLI not found and the pinned release could not be prepared.", e); + } } private static String findCopilotInPath() { diff --git a/java/sdk/src/test/java/com/github/copilot/TestUtil.java b/java/sdk/src/test/java/com/github/copilot/TestUtil.java index 23bb53e493..59f1a252ed 100644 --- a/java/sdk/src/test/java/com/github/copilot/TestUtil.java +++ b/java/sdk/src/test/java/com/github/copilot/TestUtil.java @@ -39,8 +39,7 @@ public static String tempPath(String filename) { *
  • Use the {@code COPILOT_CLI_PATH} environment variable when set.
  • *
  • Otherwise search the system PATH using {@code where.exe} (Windows) or * {@code which} (Linux/macOS).
  • - *
  • Walk parent directories looking for - * {@code nodejs/node_modules/@github/copilot/npm-loader.js}.
  • + *
  • Prepare the release pinned by {@code nodejs/package.json}.
  • * * *

    @@ -65,34 +64,14 @@ static String findCliPath() { return copilotInPath; } - // Walk parent directories looking for the CLI in the test harness or nodejs - // installation. Mirrors the resolution order in E2ETestContext.getCliPath(). - String os = System.getProperty("os.name").toLowerCase(); - String arch = System.getProperty("os.arch").toLowerCase(); - String platform = os.contains("mac") ? "darwin" : os.contains("win") ? "win32" : "linux"; - String cpuArch = arch.contains("aarch64") || arch.contains("arm64") ? "arm64" : "x64"; - String binaryName = os.contains("win") ? "copilot.exe" : "copilot"; - Path current = Paths.get(System.getProperty("user.dir")); while (current != null) { - // Test harness platform-specific binary - Path platformBinary = current.resolve( - "test/harness/node_modules/@github/copilot-" + platform + "-" + cpuArch + "/" + binaryName); - if (platformBinary.toFile().exists()) { - return platformBinary.toString(); - } - - // Test harness npm-loader.js - Path npmLoader = current.resolve("test/harness/node_modules/@github/copilot/npm-loader.js"); - if (npmLoader.toFile().exists()) { - return npmLoader.toString(); - } - - // nodejs installation (thin loader; resolves the platform-specific - // CLI package internally) - Path cliPath = current.resolve("nodejs/node_modules/@github/copilot/npm-loader.js"); - if (cliPath.toFile().exists()) { - return cliPath.toString(); + if (current.resolve("nodejs/package.json").toFile().exists()) { + try { + return preparePinnedCli(current); + } catch (Exception preparationFailed) { + return null; + } } current = current.getParent(); } @@ -100,6 +79,21 @@ static String findCliPath() { return null; } + static String preparePinnedCli(Path repoRoot) throws Exception { + boolean isWindows = System.getProperty("os.name").toLowerCase().contains("win"); + var process = new ProcessBuilder(isWindows ? "npm.cmd" : "npm", "run", "--silent", "prepare:runtime", "--", + "--print-path").directory(repoRoot.resolve("nodejs").toFile()).redirectErrorStream(true).start(); + String output; + try (var reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + output = reader.lines().reduce((first, second) -> second).orElse("").trim(); + } + int exitCode = process.waitFor(); + if (exitCode != 0 || output.isEmpty()) { + throw new IllegalStateException("Failed to prepare the pinned Copilot CLI: " + output); + } + return output; + } + /** * Searches the system PATH for a launchable {@code copilot} executable. *

    diff --git a/nodejs/README.md b/nodejs/README.md index 57a8bf484b..5985f491ba 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -8,6 +8,16 @@ To use the SDK, you'll need: - Node.js ^20.19.0 or >=22.12.0 +The SDK downloads its pinned Copilot CLI runtime from the corresponding +`github/copilot-cli` GitHub Release on first use and caches it in the operating +system's user cache directory. Set `COPILOT_CLI_PATH` to use an existing +installation instead, or `COPILOT_CLI_DOWNLOAD_BASE_URL` to use a release +mirror. + +The checked-in release pin is `copilotCliVersion` in `package.json`. Run +`npm run set:cli-version -- ` to update it and regenerate the +platform SHA-256 map in `src/cliVersion.ts`. + ## Installation ```bash diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index bfa5c8a0c6..a22d9cb7b5 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -9,8 +9,8 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.83-3", "koffi": "^3.1.0", + "tar": "^7.5.22", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -57,7 +57,7 @@ }, "node_modules/@emnapi/core": { "version": "1.10.0", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "integrity": "sha1-OAzMjyQS6iLR2XLff47iOjucdGc=", "dev": true, "license": "MIT", "optional": true, @@ -68,7 +68,7 @@ }, "node_modules/@emnapi/runtime": { "version": "1.10.0", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "integrity": "sha1-SyYMDTU0IE6YxhELjbGph9JuyHw=", "dev": true, "license": "MIT", "optional": true, @@ -78,7 +78,7 @@ }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "integrity": "sha1-KP7SGhuhznl8RKBwq8lNQvOuhUg=", "dev": true, "license": "MIT", "optional": true, @@ -88,7 +88,7 @@ }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "integrity": "sha1-egGo0uwvuy2seK2tCbD6eB5Agr4=", "cpu": [ "ppc64" ], @@ -104,7 +104,7 @@ }, "node_modules/@esbuild/android-arm": { "version": "0.28.1", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "integrity": "sha1-cEvSl95tdi3lTqu+r79V9nVqvi8=", "cpu": [ "arm" ], @@ -120,7 +120,7 @@ }, "node_modules/@esbuild/android-arm64": { "version": "0.28.1", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "integrity": "sha1-tUCifRTkr9BYSWpNvsTT9BTbEQo=", "cpu": [ "arm64" ], @@ -136,7 +136,7 @@ }, "node_modules/@esbuild/android-x64": { "version": "0.28.1", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "integrity": "sha1-0csWbTSw+/D+irRgpVlPJKN4cB4=", "cpu": [ "x64" ], @@ -168,7 +168,7 @@ }, "node_modules/@esbuild/darwin-x64": { "version": "0.28.1", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "integrity": "sha1-ZVVqQyoeTXIDLYIYwZMvzKGkl3I=", "cpu": [ "x64" ], @@ -184,7 +184,7 @@ }, "node_modules/@esbuild/freebsd-arm64": { "version": "0.28.1", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "integrity": "sha1-LmHgWS+QMNfj2uGO4l68U1kYrvY=", "cpu": [ "arm64" ], @@ -200,7 +200,7 @@ }, "node_modules/@esbuild/freebsd-x64": { "version": "0.28.1", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "integrity": "sha1-yV7CiZWe+AecTcqBeh4sS+Zrm9M=", "cpu": [ "x64" ], @@ -216,7 +216,7 @@ }, "node_modules/@esbuild/linux-arm": { "version": "0.28.1", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "integrity": "sha1-wJoPZ5F1kqwN6JKpvk04FN69Kmw=", "cpu": [ "arm" ], @@ -232,7 +232,7 @@ }, "node_modules/@esbuild/linux-arm64": { "version": "0.28.1", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "integrity": "sha1-QLIhdd2gYYLz7oFBGGxf8wTEpxc=", "cpu": [ "arm64" ], @@ -248,7 +248,7 @@ }, "node_modules/@esbuild/linux-ia32": { "version": "0.28.1", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "integrity": "sha1-pYD5xnZ5eDOJHlGfx6EzfIr9jbM=", "cpu": [ "ia32" ], @@ -264,7 +264,7 @@ }, "node_modules/@esbuild/linux-loong64": { "version": "0.28.1", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "integrity": "sha1-RkUs8yHcf56Rwvp4Cla7Vuec1os=", "cpu": [ "loong64" ], @@ -280,7 +280,7 @@ }, "node_modules/@esbuild/linux-mips64el": { "version": "0.28.1", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "integrity": "sha1-QhGzGE3WYI9T3LIuOfXTTuCIUsg=", "cpu": [ "mips64el" ], @@ -296,7 +296,7 @@ }, "node_modules/@esbuild/linux-ppc64": { "version": "0.28.1", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "integrity": "sha1-aXhXwqYcubC2u2ZS5AwdxeHKjl0=", "cpu": [ "ppc64" ], @@ -312,7 +312,7 @@ }, "node_modules/@esbuild/linux-riscv64": { "version": "0.28.1", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "integrity": "sha1-0ZKUPrFGpArExkl9DPe+NbmGvwg=", "cpu": [ "riscv64" ], @@ -328,7 +328,7 @@ }, "node_modules/@esbuild/linux-s390x": { "version": "0.28.1", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "integrity": "sha1-rOoDVtoODrwI+Xz3ucLkAeHmSNw=", "cpu": [ "s390x" ], @@ -344,7 +344,7 @@ }, "node_modules/@esbuild/linux-x64": { "version": "0.28.1", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "integrity": "sha1-bww84MtkxTS3DExF7LLBbTTjXf0=", "cpu": [ "x64" ], @@ -360,7 +360,7 @@ }, "node_modules/@esbuild/netbsd-arm64": { "version": "0.28.1", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "integrity": "sha1-i813B3oNzjN4tXT+2ybSolO3PTY=", "cpu": [ "arm64" ], @@ -376,7 +376,7 @@ }, "node_modules/@esbuild/netbsd-x64": { "version": "0.28.1", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "integrity": "sha1-5/sqAemcgwyU5mI82f77TI+1g0c=", "cpu": [ "x64" ], @@ -392,7 +392,7 @@ }, "node_modules/@esbuild/openbsd-arm64": { "version": "0.28.1", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "integrity": "sha1-xSkJNy24uG4sVeBaiUADO1Zgo7I=", "cpu": [ "arm64" ], @@ -408,7 +408,7 @@ }, "node_modules/@esbuild/openbsd-x64": { "version": "0.28.1", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "integrity": "sha1-xCe5vlpkwmL/mn63C1+7qt9EbGw=", "cpu": [ "x64" ], @@ -424,7 +424,7 @@ }, "node_modules/@esbuild/openharmony-arm64": { "version": "0.28.1", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "integrity": "sha1-3JsUe6yi5sSzyFVxdB70hgpIkJc=", "cpu": [ "arm64" ], @@ -440,7 +440,7 @@ }, "node_modules/@esbuild/sunos-x64": { "version": "0.28.1", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "integrity": "sha1-zoZtEt8TwV5MmfBzo9Rm9uBkmzo=", "cpu": [ "x64" ], @@ -456,7 +456,7 @@ }, "node_modules/@esbuild/win32-arm64": { "version": "0.28.1", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "integrity": "sha1-dGjjaS0B1inVlB5dg4F7uA+eObQ=", "cpu": [ "arm64" ], @@ -472,7 +472,7 @@ }, "node_modules/@esbuild/win32-ia32": { "version": "0.28.1", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "integrity": "sha1-pbwAY/sryrbQ7WPyoVN5WLwmnsY=", "cpu": [ "ia32" ], @@ -488,7 +488,7 @@ }, "node_modules/@esbuild/win32-x64": { "version": "0.28.1", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "integrity": "sha1-EAZO5E9DR7kMmgK0Rrv4CpFjKxI=", "cpu": [ "x64" ], @@ -657,147 +657,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@github/copilot": { - "version": "1.0.83-3", - "integrity": "sha512-4+5wVGC2IvLYog3kdfmY6rg+NIGJesjENVrTONZr6uic6zR+8Ksgy+sCWO86n6AARs09MXktAZNHbbrXz+hl7A==", - "license": "SEE LICENSE IN LICENSE.md", - "dependencies": { - "detect-libc": "^2.1.2" - }, - "bin": { - "copilot": "npm-loader.js" - }, - "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.83-3", - "@github/copilot-darwin-x64": "1.0.83-3", - "@github/copilot-linux-arm64": "1.0.83-3", - "@github/copilot-linux-x64": "1.0.83-3", - "@github/copilot-linuxmusl-arm64": "1.0.83-3", - "@github/copilot-linuxmusl-x64": "1.0.83-3", - "@github/copilot-win32-arm64": "1.0.83-3", - "@github/copilot-win32-x64": "1.0.83-3" - } - }, - "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.83-3", - "integrity": "sha512-pNI71CRL2WR6Wp+Nm+HOsSBcUIOoybcSZtMHqm2zwJGdzAjzv6MU2lLOFFeqhBh8UNQGltD4KtPU/pr+t6t4Uw==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "copilot-darwin-arm64": "copilot" - } - }, - "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.83-3", - "integrity": "sha512-9LKUwR7em12mz76s2ytWl/xkHyF13t0TLScAUcnNNj171/Kvg0lWNemwsmPK4m0QbbcmRUs7FyFFF79TmKBAmA==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "copilot-darwin-x64": "copilot" - } - }, - "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.83-3", - "integrity": "sha512-ouGA46t6flyUqUdutQL+94bnD+IwcCurR+5KS2JPHozbkeiR2BW4ed0ZZ5KT/6I13mTsjO9uu9LvWwfO5+PjiQ==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linux-arm64": "copilot" - } - }, - "node_modules/@github/copilot-linux-x64": { - "version": "1.0.83-3", - "integrity": "sha512-AiAf2yVrnP+Dw0M8RpacpOoK89sMFizPMuQfFPxAJUWS9hIw5mq4o4invKtUfiz0F7cjxaDJZz1JLUSuGEAQhw==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linux-x64": "copilot" - } - }, - "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.83-3", - "integrity": "sha512-TmXPXi65OX/Wfd7JnU8RZjZxzc5kFZU/3Gvr/N1Y+G+cJJyB0NBmWk2PP+yD381ASYOOgeNgWitlYMw8tU7Ddg==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linuxmusl-arm64": "copilot" - } - }, - "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.83-3", - "integrity": "sha512-Zlbya4anjkbI8LcbenwuBhxUUeVIrGJqeYh/6JUWwnisOiuuimqQ4zb2UU2pX3vxE03f2PbTcueOo/GkF6AS8A==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linuxmusl-x64": "copilot" - } - }, - "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.83-3", - "integrity": "sha512-zNmVj3ZDmI3dFmBigfEMzEvMxyjBjL5+nTVxrt9fvTA+29jI0C6A+cdCqrad3fJ1RKgn2RbsZyhnpyViPNhNDw==", - "cpu": [ - "arm64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ], - "bin": { - "copilot-win32-arm64": "copilot.exe" - } - }, - "node_modules/@github/copilot-win32-x64": { - "version": "1.0.83-3", - "integrity": "sha512-pbw739Jdwjr4ovsjwpMI1hguZyOPwTy/fdVnrgBv1nazXxIFrwE3tq0FgzF0NnNcs4r5LXdbIBjKQP+HKFZagA==", - "cpu": [ - "x64" - ], - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ], - "bin": { - "copilot-win32-x64": "copilot.exe" - } - }, "node_modules/@glideapps/ts-necessities": { "version": "2.2.3", "integrity": "sha512-gXi0awOZLHk3TbW55GZLCPP6O+y/b5X1pBXKBVckFONSwF1z1E5ND2BGJsghQFah+pW7pkkyFb2VhUQI2qhL5w==", @@ -852,6 +711,17 @@ "url": "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/sponsors/nzakas" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "integrity": "sha1-LVmuOrSzj7QnC/oj0w+OLobH/jI=", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", @@ -881,7 +751,7 @@ }, "node_modules/@koromix/koffi-darwin-x64": { "version": "3.1.0", - "integrity": "sha512-n/tVRB9xIzdXT5H3zZt8ueThgWTSDL+yU7PWnU8wbZPBSawP/otx3swQyd6nMOqj1bmHgSHopiKSBXRS9pllmg==", + "integrity": "sha1-r1z9mNiPAynDUcq8q6sPqKFY+xs=", "cpu": [ "x64" ], @@ -896,7 +766,7 @@ }, "node_modules/@koromix/koffi-freebsd-arm64": { "version": "3.1.0", - "integrity": "sha512-vazoPYIhOAlXZksVIqDRMIID4VeUZKx8F3dR90hOobT2ATyOkqNS5dv5UCV7Q7DSq22lQTrdbvENBAhROzCp0w==", + "integrity": "sha1-rOu888/eXKu5jj4KgZb0gCzt+xE=", "cpu": [ "arm64" ], @@ -911,7 +781,7 @@ }, "node_modules/@koromix/koffi-freebsd-ia32": { "version": "3.1.0", - "integrity": "sha512-Vm7Uc97ru6RTSVmae2zCZZQeaizqVZ8WoU4+gG4H03Qe+WOj7kbKt/MxT7VBzdbPYIU5ZJeG/ZED1YlZyab6eQ==", + "integrity": "sha1-0mky/r//V0JaXd+q9IRmdsZrkPI=", "cpu": [ "ia32" ], @@ -926,7 +796,7 @@ }, "node_modules/@koromix/koffi-freebsd-x64": { "version": "3.1.0", - "integrity": "sha512-N+VuVWjoiYPy1Go5mRadZ3B6RM5Qz+eCLhj2LXrMlefbUJ+O4gg7teCUGvPGfBEHDgmSN4yYUrfQmdJC10vOYw==", + "integrity": "sha1-2INR65Jz3bqyIPXHyqZ+PRGVkCs=", "cpu": [ "x64" ], @@ -941,7 +811,7 @@ }, "node_modules/@koromix/koffi-linux-arm64": { "version": "3.1.0", - "integrity": "sha512-Wx5iOkeALe2ympLdiYwRpIg5qUkyQIv8N2foZ9rRker0uE7ZtXew2RRkbEgMir4b0yDYR1zyXd6B62GUzLtZ/g==", + "integrity": "sha1-9h8pjdDbtKufG4JXoxDFRNzAbnI=", "cpu": [ "arm64" ], @@ -956,7 +826,7 @@ }, "node_modules/@koromix/koffi-linux-ia32": { "version": "3.1.0", - "integrity": "sha512-1DjYm1QehXU0dgn0uE+FGYOb3Of7GiTMqLS+ZI2gbl1b+h76sz4LRBvDVrQyAmSMVVU8/7696S21YgE/iBhBVg==", + "integrity": "sha1-8AbgHpYQoyx0uSoDgGpHwc1sGvM=", "cpu": [ "ia32" ], @@ -971,7 +841,7 @@ }, "node_modules/@koromix/koffi-linux-loong64": { "version": "3.1.0", - "integrity": "sha512-NOa0LdyltdESz3oeTqUH6MErHVoJOHoeXIsEp6xIMTUh4eKXEtlDQeoK6EYqo0DnBt83Xud95qLvi4Aw12pG4Q==", + "integrity": "sha1-bQvI/dvGGdARiMeDkZg0R+POkPI=", "cpu": [ "loong64" ], @@ -986,7 +856,7 @@ }, "node_modules/@koromix/koffi-linux-riscv64": { "version": "3.1.0", - "integrity": "sha512-Ye6kiXZCGxGtAIXSly6XuOP5tJZNYOZ2eVg33k1MilKrzimAy9Mpw4d6e9+Sfsc1jesgeNYs1sb5iaI8HS3ncA==", + "integrity": "sha1-V5aXxWe4DH2j2r1dnJcMH9w44co=", "cpu": [ "riscv64" ], @@ -1001,7 +871,7 @@ }, "node_modules/@koromix/koffi-linux-x64": { "version": "3.1.0", - "integrity": "sha512-3yQTOkQrMna4VX+yeyfYImBjLlGrItMpsWyfaW1uSiz/A6GRydqdwYH7DWnp4Z+RSGYZpsewkf7byMc8pOOQKA==", + "integrity": "sha1-eJX/wAVe0HJASLRTgubjJrHppuo=", "cpu": [ "x64" ], @@ -1016,7 +886,7 @@ }, "node_modules/@koromix/koffi-openbsd-ia32": { "version": "3.1.0", - "integrity": "sha512-/cDoFHb9yx4+yoT3GUpnKnfi3W2drG+/Ewo0TTZaQHb4PsxnYYyT6V8+t4cL5XXbQcTTcOsZxpmBRrn0NBa3dA==", + "integrity": "sha1-LwhKcVohSKoVCgY945O/4/gyoSk=", "cpu": [ "ia32" ], @@ -1031,7 +901,7 @@ }, "node_modules/@koromix/koffi-openbsd-x64": { "version": "3.1.0", - "integrity": "sha512-CoQdqgnKvWgTXXZlUst8cBRQEov7QsxlTN2WAsu9wez01Xe6gEcH/zYePANualzzCbnaELfe5P0rA80QkoDuPA==", + "integrity": "sha1-wWm472ijVY45okrdH/8EIAVtzAo=", "cpu": [ "x64" ], @@ -1046,7 +916,7 @@ }, "node_modules/@koromix/koffi-win32-ia32": { "version": "3.1.0", - "integrity": "sha512-WjrA+DEkpy0xEHu48+NSOboHhTnzkIfsFuq3d/WrSs+T9WflWRng3jC7mdJxmR4eHb6i6BqjW3k/U0mNUTjFPA==", + "integrity": "sha1-nDF+wS4sK934tD6A6e4I036PANM=", "cpu": [ "ia32" ], @@ -1061,7 +931,7 @@ }, "node_modules/@koromix/koffi-win32-x64": { "version": "3.1.0", - "integrity": "sha512-tnK5+IkzQBauQAQSzuyjso8OOIQRlaTZS39xIWpfqVYDLVDIuLDQk/WwHcOrR5yxlDrZq9ygiebBTOfcJFia7w==", + "integrity": "sha1-FyJ7F9SaNAIYddhe6gJ533eKOI4=", "cpu": [ "x64" ], @@ -1075,21 +945,24 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.2.3", + "integrity": "sha1-l+PUXXQk3F2h1OMvO/OykvbBtEw=", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" }, "funding": { "type": "github", "url": "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/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "node_modules/@oxc-project/types": { @@ -1112,7 +985,7 @@ }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.3", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "integrity": "sha1-VM6Pg4IhP0oxSgwve6g/gf/q5ZI=", "cpu": [ "arm64" ], @@ -1144,7 +1017,7 @@ }, "node_modules/@rolldown/binding-darwin-x64": { "version": "1.0.3", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "integrity": "sha1-U/V94fWZ7PHbE4I8/IjBj7gJVK0=", "cpu": [ "x64" ], @@ -1160,7 +1033,7 @@ }, "node_modules/@rolldown/binding-freebsd-x64": { "version": "1.0.3", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "integrity": "sha1-bz/dobeuqsnSaKUmgEtPuW5ONfE=", "cpu": [ "x64" ], @@ -1176,7 +1049,7 @@ }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { "version": "1.0.3", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "integrity": "sha1-2HpFS/WFzJZ2hJN36R1uN1KXMm8=", "cpu": [ "arm" ], @@ -1192,7 +1065,7 @@ }, "node_modules/@rolldown/binding-linux-arm64-gnu": { "version": "1.0.3", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "integrity": "sha1-QZ/Wv2Es80jxBSjLzZTrq5YH2NE=", "cpu": [ "arm64" ], @@ -1208,7 +1081,7 @@ }, "node_modules/@rolldown/binding-linux-arm64-musl": { "version": "1.0.3", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "integrity": "sha1-/MaRhpa7doRId+HkkwoY/Q03QGk=", "cpu": [ "arm64" ], @@ -1224,7 +1097,7 @@ }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { "version": "1.0.3", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "integrity": "sha1-Mq7LfI2uXU8qjN5XoFjshpkVQvg=", "cpu": [ "ppc64" ], @@ -1240,7 +1113,7 @@ }, "node_modules/@rolldown/binding-linux-s390x-gnu": { "version": "1.0.3", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "integrity": "sha1-vtk0bqgea7i5PPEfXYi3fbiQt2M=", "cpu": [ "s390x" ], @@ -1256,7 +1129,7 @@ }, "node_modules/@rolldown/binding-linux-x64-gnu": { "version": "1.0.3", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "integrity": "sha1-ZMLSb3Xf/ZtaH5dVegCudyUMjLc=", "cpu": [ "x64" ], @@ -1272,7 +1145,7 @@ }, "node_modules/@rolldown/binding-linux-x64-musl": { "version": "1.0.3", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "integrity": "sha1-WkUTLopHZZ7qrztUDClUqXyGD/M=", "cpu": [ "x64" ], @@ -1288,7 +1161,7 @@ }, "node_modules/@rolldown/binding-openharmony-arm64": { "version": "1.0.3", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "integrity": "sha1-KQUTBoxV6EnchFejKv7h17Csswk=", "cpu": [ "arm64" ], @@ -1304,7 +1177,7 @@ }, "node_modules/@rolldown/binding-wasm32-wasi": { "version": "1.0.3", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "integrity": "sha1-PZly2/GpU9PHr6pKDyDvKy458xs=", "cpu": [ "wasm32" ], @@ -1322,7 +1195,7 @@ }, "node_modules/@rolldown/binding-win32-arm64-msvc": { "version": "1.0.3", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "integrity": "sha1-oASrYHoW1vA7y1VXKP+IivdXc60=", "cpu": [ "arm64" ], @@ -1338,7 +1211,7 @@ }, "node_modules/@rolldown/binding-win32-x64-msvc": { "version": "1.0.3", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "integrity": "sha1-4qJbNGkaHMihIJ195wkGMCbdDNs=", "cpu": [ "x64" ], @@ -1365,8 +1238,8 @@ "license": "MIT" }, "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "version": "0.10.3", + "integrity": "sha1-AVy6np3UfOFNA9KoxdVHv7FpZl0=", "dev": true, "license": "MIT", "optional": true, @@ -1933,6 +1806,14 @@ "url": "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/chalk/ansi-styles?sponsor=1" } }, + "node_modules/chownr": { + "version": "3.0.0", + "integrity": "sha1-mFXmTs0kCpzEJnzopKpdJKHaFeQ=", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/collection-utils": { "version": "1.0.1", "integrity": "sha512-LA2YTIlR7biSpXkKYwwuzGjwL5rjWEZVOSnvdUc7gObvWe4WkjxOpfrdhoP7Hs09YWDVfg0Mal9BpAqLfVEzQg==", @@ -2018,6 +1899,7 @@ "node_modules/detect-libc": { "version": "2.1.2", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -2695,7 +2577,7 @@ }, "node_modules/lightningcss-android-arm64": { "version": "1.32.0", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "integrity": "sha1-8DOIURbf79nG9UeHUj41FLYeGWg=", "cpu": [ "arm64" ], @@ -2735,7 +2617,7 @@ }, "node_modules/lightningcss-darwin-x64": { "version": "1.32.0", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "integrity": "sha1-NfPpczLRMLnKGB4RtWje1q68bV4=", "cpu": [ "x64" ], @@ -2755,7 +2637,7 @@ }, "node_modules/lightningcss-freebsd-x64": { "version": "1.32.0", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "integrity": "sha1-l3enZHK2Ttb/lDQq1kx7r9eUpXU=", "cpu": [ "x64" ], @@ -2775,7 +2657,7 @@ }, "node_modules/lightningcss-linux-arm-gnueabihf": { "version": "1.32.0", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "integrity": "sha1-E65lLhq3O5E117faFy9mbEEK1T0=", "cpu": [ "arm" ], @@ -2795,7 +2677,7 @@ }, "node_modules/lightningcss-linux-arm64-gnu": { "version": "1.32.0", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "integrity": "sha1-QXhYeVqUWS9oASOhsfnaig4e8zU=", "cpu": [ "arm64" ], @@ -2815,7 +2697,7 @@ }, "node_modules/lightningcss-linux-arm64-musl": { "version": "1.32.0", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "integrity": "sha1-a+NmkugQtxgECAL9gJYjz/5zITM=", "cpu": [ "arm64" ], @@ -2835,7 +2717,7 @@ }, "node_modules/lightningcss-linux-x64-gnu": { "version": "1.32.0", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "integrity": "sha1-C3gDr06yHP043Tn+Kru1PH3QkfY=", "cpu": [ "x64" ], @@ -2855,7 +2737,7 @@ }, "node_modules/lightningcss-linux-x64-musl": { "version": "1.32.0", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "integrity": "sha1-iNyLqGXd3bGsXvBLDxYYBEGMFjs=", "cpu": [ "x64" ], @@ -2875,7 +2757,7 @@ }, "node_modules/lightningcss-win32-arm64-msvc": { "version": "1.32.0", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "integrity": "sha1-TzC6P6XpJfW3n5RejMDRdsOxqzg=", "cpu": [ "arm64" ], @@ -2895,7 +2777,7 @@ }, "node_modules/lightningcss-win32-x64-msvc": { "version": "1.32.0", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "integrity": "sha1-FBqlYFZFBkkokCu0rwRfp9n0Igo=", "cpu": [ "x64" ], @@ -3006,12 +2888,22 @@ "node_modules/minipass": { "version": "7.1.3", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "3.1.0", + "integrity": "sha1-atdsOo8QInybUdHJrI4wsn9aJRw=", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/ms": { "version": "2.1.3", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", @@ -3492,6 +3384,21 @@ "node": ">=8" } }, + "node_modules/tar": { + "version": "7.5.22", + "integrity": "sha1-ppb5mBNucUh9w/hpqFu6LGeXG6k=", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tiny-inflate": { "version": "1.0.3", "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", @@ -3558,7 +3465,7 @@ }, "node_modules/tslib": { "version": "2.8.1", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "integrity": "sha1-YS7+TtI11Wfoq6Xypfq3AoCt6D8=", "dev": true, "license": "0BSD", "optional": true @@ -3910,6 +3817,14 @@ } } }, + "node_modules/yallist": { + "version": "5.0.0", + "integrity": "sha1-AOLeRDY57Q14/YfeDSdGn7z/tTM=", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/yaml": { "version": "2.9.0", "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", diff --git a/nodejs/package.json b/nodejs/package.json index adbc639cb0..076da46ca3 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -5,6 +5,7 @@ "url": "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/github/copilot-sdk.git" }, "version": "0.0.0-dev", + "copilotCliVersion": "1.0.83-3", "description": "TypeScript SDK for programmatic control of GitHub Copilot CLI via JSON-RPC", "main": "./dist/cjs/index.js", "types": "./dist/index.d.ts", @@ -34,6 +35,7 @@ "scripts": { "clean": "rimraf --glob dist *.tgz", "build": "tsx esbuild-copilotsdk-nodejs.ts", + "prepare:runtime": "tsx scripts/prepare-runtime.ts", "test": "vitest run", "test:watch": "vitest", "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" --ignore-path .prettierignore", @@ -42,6 +44,7 @@ "lint:fix": "eslint --fix \"src/**/*.ts\" \"test/**/*.ts\"", "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.test.json", "generate": "cd ../scripts/codegen && npm run generate", + "set:cli-version": "node scripts/set-cli-version.js", "update:protocol-version": "tsx scripts/update-protocol-version.ts", "prepublishOnly": "npm run build", "package": "npm run clean && npm run build && node scripts/set-version.js && npm pack && npm version 0.0.0-dev --no-git-tag-version --allow-same-version" @@ -56,8 +59,8 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.83-3", "koffi": "^3.1.0", + "tar": "^7.5.22", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index 09df5b1ff1..e6a09a1b64 100644 --- a/nodejs/samples/package-lock.json +++ b/nodejs/samples/package-lock.json @@ -18,8 +18,8 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.83-3", "koffi": "^3.1.0", + "tar": "^7.5.22", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -47,10 +47,2529 @@ "node": "^20.19.0 || >=22.12.0" } }, + "../node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "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/sponsors/philsturgeon" + } + }, + "../node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "../node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "../node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "../node_modules/@eslint/config-array": { + "version": "0.21.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "../node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "../node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "../node_modules/@eslint/core": { + "version": "0.17.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "../node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "../node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "../node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "../node_modules/@eslint/js": { + "version": "9.39.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "../node_modules/@eslint/object-schema": { + "version": "2.1.7", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "../node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "../node_modules/@glideapps/ts-necessities": { + "version": "2.2.3", + "dev": true, + "license": "MIT" + }, + "../node_modules/@humanfs/core": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "../node_modules/@humanfs/node": { + "version": "0.16.7", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "../node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "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/sponsors/nzakas" + } + }, + "../node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "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/sponsors/nzakas" + } + }, + "../node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "../node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "dev": true, + "license": "MIT" + }, + "../node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "dev": true, + "license": "MIT" + }, + "../node_modules/@koromix/koffi-darwin-arm64": { + "version": "3.1.0", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://liberapay.com/Koromix" + } + }, + "../node_modules/@oxc-project/types": { + "version": "0.133.0", + "dev": true, + "license": "MIT", + "funding": { + "url": "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/sponsors/Boshen" + } + }, + "../node_modules/@platformatic/vfs": { + "version": "0.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 22" + } + }, + "../node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.3", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "../node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "../node_modules/@standard-schema/spec": { + "version": "1.1.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/@types/chai": { + "version": "5.2.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "../node_modules/@types/deep-eql": { + "version": "4.0.2", + "dev": true, + "license": "MIT" + }, + "../node_modules/@types/estree": { + "version": "1.0.8", + "dev": true, + "license": "MIT" + }, + "../node_modules/@types/json-schema": { + "version": "7.0.15", + "dev": true, + "license": "MIT" + }, + "../node_modules/@types/lodash": { + "version": "4.17.21", + "dev": true, + "license": "MIT" + }, + "../node_modules/@types/node": { + "version": "25.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "../node_modules/@types/ws": { + "version": "8.18.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "../node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/type-utils": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.56.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "../node_modules/@typescript-eslint/parser": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "../node_modules/@typescript-eslint/project-service": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.56.1", + "@typescript-eslint/types": "^8.56.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "../node_modules/@typescript-eslint/scope-manager": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "../node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "../node_modules/@typescript-eslint/type-utils": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1", + "@typescript-eslint/utils": "8.56.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "../node_modules/@typescript-eslint/types": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "../node_modules/@typescript-eslint/typescript-estree": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.56.1", + "@typescript-eslint/tsconfig-utils": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/visitor-keys": "8.56.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "../node_modules/@typescript-eslint/utils": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.56.1", + "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/typescript-estree": "8.56.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "../node_modules/@typescript-eslint/visitor-keys": { + "version": "8.56.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.56.1", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "../node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "../node_modules/@vitest/expect": { + "version": "4.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "../node_modules/@vitest/mocker": { + "version": "4.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "../node_modules/@vitest/pretty-format": { + "version": "4.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "../node_modules/@vitest/runner": { + "version": "4.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.8", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "../node_modules/@vitest/snapshot": { + "version": "4.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "@vitest/utils": "4.1.8", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "../node_modules/@vitest/spy": { + "version": "4.1.8", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "../node_modules/@vitest/utils": { + "version": "4.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.8", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "../node_modules/abort-controller": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "../node_modules/acorn": { + "version": "8.15.0", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "../node_modules/acorn-jsx": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "../node_modules/ajv": { + "version": "6.15.0", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "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/sponsors/epoberezkin" + } + }, + "../node_modules/argparse": { + "version": "2.0.1", + "dev": true, + "license": "Python-2.0" + }, + "../node_modules/assertion-error": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "../node_modules/balanced-match": { + "version": "1.0.2", + "dev": true, + "license": "MIT" + }, + "../node_modules/base64-js": { + "version": "1.5.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "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/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "../node_modules/brace-expansion": { + "version": "1.1.16", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "../node_modules/browser-or-node": { + "version": "3.0.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/buffer": { + "version": "6.0.3", + "dev": true, + "funding": [ + { + "type": "github", + "url": "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/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "../node_modules/callsites": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "../node_modules/chai": { + "version": "6.2.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "../node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "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/chalk/chalk?sponsor=1" + } + }, + "../node_modules/chalk/node_modules/ansi-styles": { + "version": "4.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "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/chalk/ansi-styles?sponsor=1" + } + }, + "../node_modules/chownr": { + "version": "3.0.0", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "../node_modules/collection-utils": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0" + }, + "../node_modules/color-convert": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "../node_modules/color-name": { + "version": "1.1.4", + "dev": true, + "license": "MIT" + }, + "../node_modules/concat-map": { + "version": "0.0.1", + "dev": true, + "license": "MIT" + }, + "../node_modules/convert-source-map": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/cross-fetch": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "../node_modules/cross-spawn": { + "version": "7.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "../node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "../node_modules/deep-is": { + "version": "0.1.4", + "dev": true, + "license": "MIT" + }, + "../node_modules/detect-libc": { + "version": "2.1.2", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "../node_modules/es-module-lexer": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/esbuild": { + "version": "0.28.1", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "../node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "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/sponsors/sindresorhus" + } + }, + "../node_modules/eslint": { + "version": "9.39.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "../node_modules/eslint-scope": { + "version": "8.4.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "../node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "../node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "../node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "../node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "../node_modules/espree": { + "version": "10.4.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "../node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "../node_modules/esquery": { + "version": "1.6.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "../node_modules/esrecurse": { + "version": "4.3.0", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "../node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "../node_modules/estree-walker": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "../node_modules/esutils": { + "version": "2.0.3", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "../node_modules/event-target-shim": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "../node_modules/events": { + "version": "3.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "../node_modules/expect-type": { + "version": "1.3.0", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "../node_modules/fast-deep-equal": { + "version": "3.1.3", + "dev": true, + "license": "MIT" + }, + "../node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/fast-levenshtein": { + "version": "2.0.6", + "dev": true, + "license": "MIT" + }, + "../node_modules/fdir": { + "version": "6.5.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "../node_modules/file-entry-cache": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "../node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "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/sponsors/sindresorhus" + } + }, + "../node_modules/flat-cache": { + "version": "4.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "../node_modules/flatted": { + "version": "3.4.2", + "dev": true, + "license": "ISC" + }, + "../node_modules/fsevents": { + "version": "2.3.3", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "../node_modules/glob": { + "version": "13.0.6", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "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/sponsors/isaacs" + } + }, + "../node_modules/glob-parent": { + "version": "6.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "../node_modules/globals": { + "version": "14.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "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/sponsors/sindresorhus" + } + }, + "../node_modules/has-flag": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "../node_modules/ieee754": { + "version": "1.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "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/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "../node_modules/ignore": { + "version": "7.0.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "../node_modules/import-fresh": { + "version": "3.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "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/sponsors/sindresorhus" + } + }, + "../node_modules/imurmurhash": { + "version": "0.1.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "../node_modules/is-extglob": { + "version": "2.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "../node_modules/is-glob": { + "version": "4.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "../node_modules/is-url": { + "version": "1.2.4", + "dev": true, + "license": "MIT" + }, + "../node_modules/isexe": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "../node_modules/js-base64": { + "version": "3.7.8", + "dev": true, + "license": "BSD-3-Clause" + }, + "../node_modules/js-yaml": { + "version": "4.2.0", + "dev": true, + "funding": [ + { + "type": "github", + "url": "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/sponsors/puzrin" + }, + { + "type": "github", + "url": "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/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "../node_modules/json-buffer": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "../node_modules/json-schema": { + "version": "0.4.0", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "../node_modules/json-schema-to-typescript": { + "version": "15.0.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^11.5.5", + "@types/json-schema": "^7.0.15", + "@types/lodash": "^4.17.7", + "is-glob": "^4.0.3", + "js-yaml": "^4.1.0", + "lodash": "^4.17.21", + "minimist": "^1.2.8", + "prettier": "^3.2.5", + "tinyglobby": "^0.2.9" + }, + "bin": { + "json2ts": "dist/src/cli.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "../node_modules/json-schema-traverse": { + "version": "0.4.1", + "dev": true, + "license": "MIT" + }, + "../node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "../node_modules/keyv": { + "version": "4.5.4", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "../node_modules/koffi": { + "version": "3.1.0", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "url": "https://liberapay.com/Koromix" + }, + "optionalDependencies": { + "@koromix/koffi-darwin-arm64": "3.1.0", + "@koromix/koffi-darwin-x64": "3.1.0", + "@koromix/koffi-freebsd-arm64": "3.1.0", + "@koromix/koffi-freebsd-ia32": "3.1.0", + "@koromix/koffi-freebsd-x64": "3.1.0", + "@koromix/koffi-linux-arm64": "3.1.0", + "@koromix/koffi-linux-ia32": "3.1.0", + "@koromix/koffi-linux-loong64": "3.1.0", + "@koromix/koffi-linux-riscv64": "3.1.0", + "@koromix/koffi-linux-x64": "3.1.0", + "@koromix/koffi-openbsd-ia32": "3.1.0", + "@koromix/koffi-openbsd-x64": "3.1.0", + "@koromix/koffi-win32-ia32": "3.1.0", + "@koromix/koffi-win32-x64": "3.1.0" + } + }, + "../node_modules/levn": { + "version": "0.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "../node_modules/lightningcss": { + "version": "1.32.0", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "../node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "../node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "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/sponsors/sindresorhus" + } + }, + "../node_modules/lodash": { + "version": "4.18.1", + "dev": true, + "license": "MIT" + }, + "../node_modules/lodash.merge": { + "version": "4.6.2", + "dev": true, + "license": "MIT" + }, + "../node_modules/lru-cache": { + "version": "11.2.6", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "../node_modules/magic-string": { + "version": "0.30.21", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "../node_modules/minimatch": { + "version": "10.2.4", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "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/sponsors/isaacs" + } + }, + "../node_modules/minimatch/node_modules/balanced-match": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "../node_modules/minimatch/node_modules/brace-expansion": { + "version": "5.0.8", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "../node_modules/minimist": { + "version": "1.2.8", + "dev": true, + "license": "MIT", + "funding": { + "url": "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/sponsors/ljharb" + } + }, + "../node_modules/minipass": { + "version": "7.1.3", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "../node_modules/minizlib": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "../node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "../node_modules/nanoid": { + "version": "3.3.17", + "dev": true, + "funding": [ + { + "type": "github", + "url": "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/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "../node_modules/natural-compare": { + "version": "1.4.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/node-fetch": { + "version": "2.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "../node_modules/obug": { + "version": "2.1.1", + "dev": true, + "funding": [ + "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/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "../node_modules/optionator": { + "version": "0.9.4", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "../node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "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/sponsors/sindresorhus" + } + }, + "../node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "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/sponsors/sindresorhus" + } + }, + "../node_modules/package-json-from-dist": { + "version": "1.0.1", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "../node_modules/pako": { + "version": "1.0.11", + "dev": true, + "license": "(MIT AND Zlib)" + }, + "../node_modules/parent-module": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "../node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "../node_modules/path-key": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "../node_modules/path-scurry": { + "version": "2.0.2", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "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/sponsors/isaacs" + } + }, + "../node_modules/pathe": { + "version": "2.0.3", + "dev": true, + "license": "MIT" + }, + "../node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "../node_modules/picomatch": { + "version": "4.0.4", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "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/sponsors/jonschlinkert" + } + }, + "../node_modules/pluralize": { + "version": "8.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "../node_modules/postcss": { + "version": "8.5.25", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "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/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "../node_modules/prelude-ls": { + "version": "1.2.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "../node_modules/prettier": { + "version": "3.8.1", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "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/prettier/prettier?sponsor=1" + } + }, + "../node_modules/process": { + "version": "0.11.10", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, + "../node_modules/punycode": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "../node_modules/quicktype-core": { + "version": "23.2.6", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@glideapps/ts-necessities": "2.2.3", + "browser-or-node": "^3.0.0", + "collection-utils": "^1.0.1", + "cross-fetch": "^4.0.0", + "is-url": "^1.2.4", + "js-base64": "^3.7.7", + "lodash": "^4.17.21", + "pako": "^1.0.6", + "pluralize": "^8.0.0", + "readable-stream": "4.5.2", + "unicode-properties": "^1.4.1", + "urijs": "^1.19.1", + "wordwrap": "^1.0.0", + "yaml": "^2.4.1" + } + }, + "../node_modules/readable-stream": { + "version": "4.5.2", + "dev": true, + "license": "MIT", + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "../node_modules/resolve-from": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "../node_modules/rimraf": { + "version": "6.1.3", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.3", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "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/sponsors/isaacs" + } + }, + "../node_modules/rolldown": { + "version": "1.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.133.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.3", + "@rolldown/binding-darwin-arm64": "1.0.3", + "@rolldown/binding-darwin-x64": "1.0.3", + "@rolldown/binding-freebsd-x64": "1.0.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.3", + "@rolldown/binding-linux-arm64-gnu": "1.0.3", + "@rolldown/binding-linux-arm64-musl": "1.0.3", + "@rolldown/binding-linux-ppc64-gnu": "1.0.3", + "@rolldown/binding-linux-s390x-gnu": "1.0.3", + "@rolldown/binding-linux-x64-gnu": "1.0.3", + "@rolldown/binding-linux-x64-musl": "1.0.3", + "@rolldown/binding-openharmony-arm64": "1.0.3", + "@rolldown/binding-wasm32-wasi": "1.0.3", + "@rolldown/binding-win32-arm64-msvc": "1.0.3", + "@rolldown/binding-win32-x64-msvc": "1.0.3" + } + }, + "../node_modules/safe-buffer": { + "version": "5.2.1", + "dev": true, + "funding": [ + { + "type": "github", + "url": "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/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "../node_modules/semver": { + "version": "7.7.3", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "../node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "../node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "../node_modules/siginfo": { + "version": "2.0.0", + "dev": true, + "license": "ISC" + }, + "../node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "../node_modules/stackback": { + "version": "0.0.2", + "dev": true, + "license": "MIT" + }, + "../node_modules/std-env": { + "version": "4.1.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/string_decoder": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "../node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "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/sponsors/sindresorhus" + } + }, + "../node_modules/supports-color": { + "version": "7.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "../node_modules/tar": { + "version": "7.5.22", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "../node_modules/tiny-inflate": { + "version": "1.0.3", + "dev": true, + "license": "MIT" + }, + "../node_modules/tinybench": { + "version": "2.9.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/tinyexec": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "../node_modules/tinyglobby": { + "version": "0.2.17", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "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/sponsors/SuperchupuDev" + } + }, + "../node_modules/tinyrainbow": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "../node_modules/tr46": { + "version": "0.0.3", + "dev": true, + "license": "MIT" + }, + "../node_modules/ts-api-utils": { + "version": "2.4.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "../node_modules/tsx": { + "version": "4.22.4", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "../node_modules/type-check": { + "version": "0.4.0", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "../node_modules/typescript": { + "version": "5.9.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "../node_modules/undici-types": { + "version": "7.18.2", + "dev": true, + "license": "MIT" + }, + "../node_modules/unicode-properties": { + "version": "1.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "../node_modules/unicode-trie": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "../node_modules/unicode-trie/node_modules/pako": { + "version": "0.2.9", + "dev": true, + "license": "MIT" + }, + "../node_modules/uri-js": { + "version": "4.4.1", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "../node_modules/urijs": { + "version": "1.19.11", + "dev": true, + "license": "MIT" + }, + "../node_modules/vite": { + "version": "8.0.16", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "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/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "../node_modules/vitest": { + "version": "4.1.8", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.8", + "@vitest/mocker": "4.1.8", + "@vitest/pretty-format": "4.1.8", + "@vitest/runner": "4.1.8", + "@vitest/snapshot": "4.1.8", + "@vitest/spy": "4.1.8", + "@vitest/utils": "4.1.8", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.8", + "@vitest/browser-preview": "4.1.8", + "@vitest/browser-webdriverio": "4.1.8", + "@vitest/coverage-istanbul": "4.1.8", + "@vitest/coverage-v8": "4.1.8", + "@vitest/ui": "4.1.8", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "../node_modules/vscode-jsonrpc": { + "version": "8.2.1", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "../node_modules/webidl-conversions": { + "version": "3.0.1", + "dev": true, + "license": "BSD-2-Clause" + }, + "../node_modules/whatwg-url": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "../node_modules/which": { + "version": "2.0.2", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "../node_modules/why-is-node-running": { + "version": "2.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "../node_modules/word-wrap": { + "version": "1.2.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "../node_modules/wordwrap": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, + "../node_modules/ws": { + "version": "8.21.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "../node_modules/yallist": { + "version": "5.0.0", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "../node_modules/yaml": { + "version": "2.9.0", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "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/sponsors/eemeli" + } + }, + "../node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "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/sponsors/sindresorhus" + } + }, + "../node_modules/zod": { + "version": "4.3.6", + "license": "MIT", + "funding": { + "url": "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/sponsors/colinhacks" + } + }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha1-v24QMDvPLnxoaXX6Uvk37Cco2Lw=", "cpu": [ "ppc64" ], @@ -65,9 +2584,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha1-LYTs5qTiaE2SvibuE9QnV9gxw4E=", "cpu": [ "arm" ], @@ -82,9 +2601,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha1-DGJGvI0sTRcqrC2z+xGQ1yvWVQQ=", "cpu": [ "arm64" ], @@ -99,9 +2618,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha1-/DjU1jWNjcHPU/CfdYn+Q262SAE=", "cpu": [ "x64" ], @@ -116,9 +2635,7 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "version": "0.28.2", "cpu": [ "arm64" ], @@ -133,9 +2650,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-12.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha1-UQFHwFWnlViNu+FP1rG4rQovMN4=", "cpu": [ "x64" ], @@ -150,9 +2667,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "version": "0.28.2", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha1-CTuSAOzwsRW6Tl4kinSFycX4vV4=", "cpu": [ "arm64" ], @@ -167,9 +2684,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha1-C+Irbfkl0hPoQeqHEjr134Cw+vc=", "cpu": [ "x64" ], @@ -184,9 +2701,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha1-vrEq1yuE9y0oSIzBuO6ffrFB11M=", "cpu": [ "arm" ], @@ -201,9 +2718,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha1-G9vGUc2pupmVxT7ZxxzqplCUdi0=", "cpu": [ "arm64" ], @@ -218,9 +2735,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha1-uB+dVVKbRcIGpGoTghSxqmh5aWs=", "cpu": [ "ia32" ], @@ -235,9 +2752,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "version": "0.28.2", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha1-WYZnJBoEyZt27W75QKxQA4xBn5g=", "cpu": [ "loong64" ], @@ -252,9 +2769,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha1-HFHrnOqQP1PZe1rzsYQdtw9Vlso=", "cpu": [ "mips64el" ], @@ -269,9 +2786,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha1-Y91h8XzrMagSJ/QT/qyKcbwsUfI=", "cpu": [ "ppc64" ], @@ -286,9 +2803,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha1-N2Owj95c8lqx+suOd1Lt/kX7/Cc=", "cpu": [ "riscv64" ], @@ -303,9 +2820,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha1-GhN/8pOoKQbrMXY4W9fo4OXPt8s=", "cpu": [ "s390x" ], @@ -320,9 +2837,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha1-Jos2IRwUbKVPj+EsV4qNbviXlIU=", "cpu": [ "x64" ], @@ -337,9 +2854,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha1-Ilca2VHWK7aszILY0frVyMGsC6E=", "cpu": [ "arm64" ], @@ -354,9 +2871,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha1-QvzFcpfrCgyj9fxHUpH0waP3wN4=", "cpu": [ "x64" ], @@ -371,9 +2888,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "version": "0.28.2", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha1-nrMq8QSsPaz07coB9ZZmSqsMc+8=", "cpu": [ "arm64" ], @@ -388,9 +2905,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha1-/r7SQC1giCJekfIPtM4lIq0KTv0=", "cpu": [ "x64" ], @@ -405,9 +2922,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha1-hWQcPUZkKL+8zqXyHCaDZmP+9c4=", "cpu": [ "arm64" ], @@ -422,9 +2939,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "version": "0.28.2", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha1-pzb52JYkgQRfxMPlT1R58iyHD7Q=", "cpu": [ "x64" ], @@ -439,9 +2956,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha1-7lq0D60YYgG2UqM/il6xSenkJTI=", "cpu": [ "arm64" ], @@ -456,9 +2973,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "version": "0.28.2", + "resolved": "https://ms-feed-17.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha1-xA0optmaEn2mcR8q/XSxHLY7Bqc=", "cpu": [ "ia32" ], @@ -473,9 +2990,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "version": "0.28.2", + "resolved": "https://ms-feed-2.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha1-shr/uATMFnwTPZX0WzodwTI7moc=", "cpu": [ "x64" ], @@ -494,9 +3011,7 @@ "link": true }, "node_modules/@types/node": { - "version": "22.19.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.11.tgz", - "integrity": "sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==", + "version": "22.20.1", "dev": true, "license": "MIT", "dependencies": { @@ -504,9 +3019,7 @@ } }, "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "version": "0.28.2", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -517,40 +3030,37 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" } }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -561,9 +3071,7 @@ } }, "node_modules/tsx": { - "version": "4.22.4", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", - "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", + "version": "4.23.12", "dev": true, "license": "MIT", "dependencies": { @@ -581,8 +3089,6 @@ }, "node_modules/undici-types": { "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" } diff --git a/nodejs/scripts/prepare-runtime.ts b/nodejs/scripts/prepare-runtime.ts new file mode 100644 index 0000000000..4b499cb6f3 --- /dev/null +++ b/nodejs/scripts/prepare-runtime.ts @@ -0,0 +1,14 @@ +import { join } from "node:path"; +import { ensureCopilotPackage, ensureRuntimeBundle } from "../src/runtimeArtifacts.js"; +import { COPILOT_CLI_VERSION } from "../src/cliVersion.js"; + +const [option] = process.argv.slice(2); +if (option === "--print-legacy-path") { + const packageRoot = await ensureCopilotPackage(COPILOT_CLI_VERSION); + process.stdout.write(`${join(packageRoot, "app.js")}\n`); +} else if (option === "--print-path" || option === undefined) { + const runtimePath = await ensureRuntimeBundle(COPILOT_CLI_VERSION); + process.stdout.write(`${runtimePath}\n`); +} else { + throw new Error(`Unknown option: ${option}`); +} diff --git a/nodejs/scripts/set-cli-version.js b/nodejs/scripts/set-cli-version.js new file mode 100644 index 0000000000..5976231fcb --- /dev/null +++ b/nodejs/scripts/set-cli-version.js @@ -0,0 +1,57 @@ +import { readFileSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +const version = process.argv[2]; +if (!version || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z._-]+)?$/.test(version)) { + throw new Error("Usage: set-cli-version.js "); +} + +const nodeRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); +const platforms = [ + "darwin-arm64", + "darwin-x64", + "linux-arm64", + "linux-x64", + "linuxmusl-arm64", + "linuxmusl-x64", + "win32-arm64", + "win32-x64", +]; +const checksumsUrl = `https://github.com/github/copilot-cli/releases/download/v${version}/SHA256SUMS.txt`; +const response = await fetch(checksumsUrl); +if (!response.ok) { + throw new Error(`Failed to download ${checksumsUrl}: ${response.status} ${response.statusText}`); +} +const checksums = new Map( + (await response.text()) + .split(/\r?\n/) + .map((line) => line.trim().split(/\s+/, 2)) + .filter(([hash, name]) => /^[a-fA-F0-9]{64}$/.test(hash) && name) + .map(([hash, name]) => [name.replace(/^\*/, ""), hash.toLowerCase()]) +); +const hashes = Object.fromEntries( + platforms.map((platform) => { + const assetName = `github-copilot-${version}-${platform}.tgz`; + const hash = checksums.get(assetName); + if (!hash) { + throw new Error(`SHA256SUMS.txt does not contain ${assetName}`); + } + return [platform, hash]; + }) +); +const packagePath = join(nodeRoot, "package.json"); +const packageJson = JSON.parse(readFileSync(packagePath, "utf8")); +packageJson.copilotCliVersion = version; +writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 4)}\n`); + +const sourcePath = join(nodeRoot, "src", "cliVersion.ts"); +writeFileSync( + sourcePath, + [ + `export const COPILOT_CLI_VERSION = ${JSON.stringify(version)};`, + "", + `export const COPILOT_CLI_HASHES: Readonly> = ${JSON.stringify(hashes, null, 4)};`, + "", + ].join("\n") +); diff --git a/nodejs/src/cliVersion.ts b/nodejs/src/cliVersion.ts new file mode 100644 index 0000000000..360b3ea58d --- /dev/null +++ b/nodejs/src/cliVersion.ts @@ -0,0 +1,12 @@ +export const COPILOT_CLI_VERSION = "1.0.83-1"; + +export const COPILOT_CLI_HASHES: Readonly> = { + "darwin-arm64": "d8bd725ed870fe1ad4c55070857a4efa03dc3327e07e3aacc62a9b67379bc2d0", + "darwin-x64": "6ff4e3b42f74106392e3a8b8709bf8506bc1a9b9459cdc3ac01dffd4c464186f", + "linux-arm64": "11ad54a68c6f585d32aed0de97569e0c503bcde6bb90a90fffbf33c8b29f1236", + "linux-x64": "c0ff7b87e81367ddfad2a93c6d8402b43066f6a06a9ff24c6ac3aabd126fcd67", + "linuxmusl-arm64": "b1b766d55772038053c195c9ba3e8739d97a487f6d0f8472c190f0d1c313c7b6", + "linuxmusl-x64": "a4e59a686743cbc031bd91e3640e2ba788b06da2bb61b69537ff8bf3c65ce106", + "win32-arm64": "e064594c5c2f9d2e7758fa8758f2322557888fe1e11240ae4ed2acb8afdfea47", + "win32-x64": "f5003673d621971acffca7b05ff3442c86de686f389d65cc8791c03ea3ae6d9b", +}; diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 83b0a5f483..d4c9a34ed1 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -14,10 +14,8 @@ import { spawn, type ChildProcess } from "node:child_process"; import { randomUUID } from "node:crypto"; import { existsSync } from "node:fs"; -import { createRequire } from "node:module"; import { Socket } from "node:net"; import { dirname, isAbsolute, join, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; import { createMessageConnection, ErrorCodes, @@ -44,7 +42,8 @@ import type { import { getSdkProtocolVersion } from "./sdkProtocolVersion.js"; import { CopilotSession } from "./session.js"; import type { FfiRuntimeHost } from "./ffiRuntimeHost.js"; -import { materializeRuntimeBundle } from "./runtimeArtifacts.js"; +import { ensureRuntimeBundle } from "./runtimeArtifacts.js"; +import { COPILOT_CLI_VERSION } from "./cliVersion.js"; import { createSessionFsAdapter, type SessionFsProvider } from "./sessionFsProvider.js"; import { createCopilotRequestAdapter } from "./copilotRequestHandler.js"; import type { CopilotRequestHandler } from "./copilotRequestHandler.js"; @@ -371,83 +370,8 @@ function getNodeExecPath(): string { return process.execPath; } -/** - * Computes the candidate platform-specific CLI package names for the current - * platform/arch, mirroring @github/copilot's npm-loader. As of CLI 1.0.64-1 the - * @github/copilot package is a thin loader and the actual CLI ships in a - * platform package (e.g. @github/copilot-darwin-arm64). For Linux we try both - * the glibc and musl variants since only the matching one is installed. - */ -function getCliPlatformPackageNames(): string[] { - const arch = process.arch; - const variants = process.platform === "linux" ? ["linux", "linuxmusl"] : [process.platform]; - return variants.map((variant) => `@github/copilot-${variant}-${arch}`); -} - -interface BundledCliPackage { - root: string; - platform: string; -} - -/** - * Resolves the current platform package and its npm prebuilds folder. - * - * In ESM, uses import.meta.resolve directly. In CJS (e.g., VS Code extensions - * bundled with esbuild format:"cjs"), import.meta is empty so we fall back to - * walking node_modules to find the package. - */ -function getBundledCliPackage(): BundledCliPackage { - const packageNames = getCliPlatformPackageNames(); - - if (typeof import.meta.resolve === "function") { - // ESM: resolve via import.meta.resolve - for (const packageName of packageNames) { - try { - const packageEntryUrl = import.meta.resolve(packageName); - const packageEntryPath = fileURLToPath(packageEntryUrl); - return { - root: dirname(packageEntryPath), - platform: packageName.slice("@github/copilot-".length), - }; - } catch { - // Try the next candidate platform package. - } - } - throw new Error( - `Could not resolve a @github/copilot platform package (tried ${packageNames.join(", ")}). ` + - `Ensure @github/copilot is installed, or pass cliPath/cliUrl to CopilotClient.` - ); - } - - // CJS fallback: the platform packages have ESM-only exports so - // require.resolve cannot reach them. Walk the module search paths instead. - const req = createRequire(__filename); - const searchPaths = req.resolve.paths("@github/copilot") ?? []; - for (const base of searchPaths) { - for (const packageName of packageNames) { - const root = join(base, ...packageName.split("/")); - const candidate = join(root, "index.js"); - if (existsSync(candidate)) { - return { - root, - platform: packageName.slice("@github/copilot-".length), - }; - } - } - } - throw new Error( - `Could not find a @github/copilot platform package (tried ${packageNames.join(", ")}). ` + - `Searched ${searchPaths.length} paths. ` + - `Ensure @github/copilot is installed, or pass cliPath/cliUrl to CopilotClient.` - ); -} - -function getBundledRuntimePath(): string { - const bundled = getBundledCliPackage(); - return materializeRuntimeBundle({ - packageRoot: bundled.root, - platform: bundled.platform, - }); +function getBundledRuntimePath(environment: NodeJS.ProcessEnv = process.env): Promise { + return ensureRuntimeBundle(COPILOT_CLI_VERSION, { environment }); } /** @@ -776,8 +700,6 @@ export class CopilotClient { const explicitCliPath = conn.path ?? effectiveEnv.COPILOT_CLI_PATH; if (explicitCliPath) { this.resolvedCliPath = explicitCliPath; - } else { - this.resolvedCliPath = getBundledRuntimePath(); } } @@ -2639,6 +2561,7 @@ export class CopilotClient { * Start the CLI server process */ private async startCLIServer(): Promise { + this.resolvedCliPath ??= await getBundledRuntimePath(this.resolvedEnv); return new Promise((resolve, reject) => { // Clear stderr buffer for fresh capture this.stderrBuffer = ""; @@ -2698,7 +2621,7 @@ export class CopilotClient { // Verify CLI exists before attempting to spawn if (!existsSync(this.resolvedCliPath)) { throw new Error( - `Copilot CLI not found at ${this.resolvedCliPath}. Ensure @github/copilot is installed.` + `Copilot CLI not found at ${this.resolvedCliPath}. Set COPILOT_CLI_PATH to use a custom installation.` ); } @@ -2849,7 +2772,7 @@ export class CopilotClient { CopilotClient.getNapiPrebuildsFolder(explicitEntrypoint), "runtime.node" ) - : join(dirname(getBundledRuntimePath()), "runtime.node"); + : join(dirname(await getBundledRuntimePath(this.resolvedEnv)), "runtime.node"); // Load the FFI host lazily so the native `koffi` addon (and its // platform-specific `koffi.node`) is only loaded on the in-process path; // out-of-process (stdio/tcp) consumers never touch the native dependency. diff --git a/nodejs/src/runtimeArtifacts.ts b/nodejs/src/runtimeArtifacts.ts index 19d9926250..cba39d940d 100644 --- a/nodejs/src/runtimeArtifacts.ts +++ b/nodejs/src/runtimeArtifacts.ts @@ -10,15 +10,27 @@ import { renameSync, rmSync, statSync, + writeFileSync, } from "node:fs"; import { homedir } from "node:os"; import { dirname, join, relative, sep } from "node:path"; +import { x as extractTar } from "tar"; +import { COPILOT_CLI_HASHES, COPILOT_CLI_VERSION } from "./cliVersion.js"; export interface RuntimeArtifactSources { packageRoot: string; platform: string; } +export interface EnsureRuntimeBundleOptions { + cacheRoot?: string; + environment?: NodeJS.ProcessEnv; + fetch?: typeof globalThis.fetch; + platform?: string; +} + +const runtimeDownloads = new Map>(); + const EXCLUDED_TOP_LEVEL = new Set([ "app.js", "assets", @@ -60,6 +72,10 @@ function validateRuntimeBundle(wrapper: string, runtimeNode: string): void { validateFile(runtimeNode, "Copilot runtime.node"); } +function sanitizeCacheSegment(value: string): string { + return value.replace(/[^a-zA-Z0-9._-]/g, "_"); +} + function isExcluded(relativePath: string): boolean { const parts = relativePath.split(sep); const topLevel = parts[0]; @@ -142,15 +158,18 @@ export function defaultRuntimeCacheRoot( export function materializeRuntimeBundle( sources: RuntimeArtifactSources, - cacheRoot = defaultRuntimeCacheRoot() + cacheRoot = defaultRuntimeCacheRoot(), + cacheKey = `${sources.platform}-${sourceFingerprint(collectRuntimeAssets(sources))}` ): string { const assets = collectRuntimeAssets(sources); - const wrapperName = process.platform === "win32" ? "copilot-runtime.exe" : "copilot-runtime"; + const wrapperName = sources.platform.startsWith("win32") + ? "copilot-runtime.exe" + : "copilot-runtime"; const sourceWrapper = assets.find((asset) => asset.relativePath === wrapperName)?.source; const sourceRuntimeNode = assets.find((asset) => asset.relativePath === "runtime.node")?.source; validateRuntimeBundle(sourceWrapper ?? "", sourceRuntimeNode ?? ""); - const installDir = join(cacheRoot, `${sources.platform}-${sourceFingerprint(assets)}`); + const installDir = join(cacheRoot, cacheKey); const installedWrapper = join(installDir, wrapperName); const installedRuntimeNode = join(installDir, "runtime.node"); if (existsSync(installDir)) { @@ -181,3 +200,193 @@ export function materializeRuntimeBundle( return installedWrapper; } + +function isMusl(): boolean { + if (process.platform !== "linux") { + return false; + } + const report = process.report?.getReport() as + | { header?: { glibcVersionRuntime?: string } } + | undefined; + return report?.header?.glibcVersionRuntime === undefined; +} + +export function getRuntimePlatform( + platform = process.platform, + arch = process.arch, + musl = isMusl() +): string { + if (arch !== "x64" && arch !== "arm64") { + throw new Error(`Unsupported Copilot CLI architecture: ${arch}.`); + } + if (platform === "linux") { + return `${musl ? "linuxmusl" : "linux"}-${arch}`; + } + if (platform === "darwin" || platform === "win32") { + return `${platform}-${arch}`; + } + throw new Error(`Unsupported Copilot CLI platform: ${platform}-${arch}.`); +} + +export function getRuntimeReleaseAssetName(version: string, platform: string): string { + return `github-copilot-${version}-${platform}.tgz`; +} + +async function fetchWithRetry(fetcher: typeof globalThis.fetch, url: string): Promise { + let lastError: unknown; + for (let attempt = 0; attempt < 3; attempt++) { + try { + const response = await fetcher(url); + if (response.ok) { + return response; + } + await response.body?.cancel(); + lastError = new Error(`${response.status} ${response.statusText}`); + if ( + response.status >= 400 && + response.status < 500 && + response.status !== 408 && + response.status !== 429 + ) { + break; + } + } catch (error) { + lastError = error; + } + if (attempt < 2) { + await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1000)); + } + } + throw new Error(`Failed to download ${url}: ${String(lastError)}`); +} + +function checksumForAsset(checksums: string, assetName: string): string { + for (const line of checksums.split(/\r?\n/)) { + const [digest, name] = line.trim().split(/\s+/, 2); + if (name?.replace(/^\*/, "") === assetName && /^[a-f0-9]{64}$/i.test(digest)) { + return digest.toLowerCase(); + } + } + throw new Error(`SHA256SUMS.txt does not contain ${assetName}.`); +} + +export async function ensureRuntimeBundle( + version: string, + options: EnsureRuntimeBundleOptions = {} +): Promise { + const platform = options.platform ?? getRuntimePlatform(); + const cacheRoot = options.cacheRoot ?? defaultRuntimeCacheRoot(); + const baseUrl = ( + (options.environment ?? process.env).COPILOT_CLI_DOWNLOAD_BASE_URL ?? + "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/github/copilot-cli/releases/download" + ).replace(/\/+$/, ""); + const downloadKey = `${cacheRoot}\0${version}\0${platform}\0${baseUrl}`; + if (!options.fetch) { + const existing = runtimeDownloads.get(downloadKey); + if (existing) { + return existing; + } + const download = ensureRuntimeBundleUncached(version, options); + runtimeDownloads.set(downloadKey, download); + try { + return await download; + } finally { + runtimeDownloads.delete(downloadKey); + } + } + return ensureRuntimeBundleUncached(version, options); +} + +async function ensureRuntimeBundleUncached( + version: string, + options: EnsureRuntimeBundleOptions +): Promise { + const platform = options.platform ?? getRuntimePlatform(); + const cacheRoot = options.cacheRoot ?? defaultRuntimeCacheRoot(); + const versionRoot = join(cacheRoot, sanitizeCacheSegment(version)); + const wrapperName = platform.startsWith("win32") ? "copilot-runtime.exe" : "copilot-runtime"; + const installedWrapper = join(versionRoot, platform, wrapperName); + const installedRuntimeNode = join(versionRoot, platform, "runtime.node"); + if (existsSync(installedWrapper) && existsSync(installedRuntimeNode)) { + validateRuntimeBundle(installedWrapper, installedRuntimeNode); + makeExecutable(installedWrapper); + return installedWrapper; + } + + const packageRoot = await ensureCopilotPackage(version, options); + return materializeRuntimeBundle({ packageRoot, platform }, versionRoot, platform); +} + +export async function ensureCopilotPackage( + version: string, + options: EnsureRuntimeBundleOptions = {} +): Promise { + const environment = options.environment ?? process.env; + const platform = options.platform ?? getRuntimePlatform(); + const cacheRoot = options.cacheRoot ?? defaultRuntimeCacheRoot(); + const versionRoot = join(cacheRoot, sanitizeCacheSegment(version)); + const cachedPackageRoot = join(versionRoot, "packages", platform); + const cachedRuntimeNode = join(cachedPackageRoot, "prebuilds", platform, "runtime.node"); + if (existsSync(cachedRuntimeNode)) { + validateFile(cachedRuntimeNode, "Copilot runtime.node"); + return cachedPackageRoot; + } + + const fetcher = options.fetch ?? globalThis.fetch; + if (!fetcher) { + throw new Error("This Node.js runtime does not provide fetch()."); + } + mkdirSync(cacheRoot, { recursive: true }); + const baseUrl = ( + environment.COPILOT_CLI_DOWNLOAD_BASE_URL ?? + "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/github/copilot-cli/releases/download" + ).replace(/\/+$/, ""); + const releaseUrl = `${baseUrl}/v${version}`; + const assetName = getRuntimeReleaseAssetName(version, platform); + const pinnedChecksum = + version === COPILOT_CLI_VERSION ? COPILOT_CLI_HASHES[platform] : undefined; + const [checksumsResponse, assetResponse] = await Promise.all([ + pinnedChecksum + ? Promise.resolve(undefined) + : fetchWithRetry(fetcher, `${releaseUrl}/SHA256SUMS.txt`), + fetchWithRetry(fetcher, `${releaseUrl}/${assetName}`), + ]); + const archive = Buffer.from(await assetResponse.arrayBuffer()); + const expectedChecksum = + pinnedChecksum ?? checksumForAsset(await checksumsResponse!.text(), assetName); + const actualChecksum = createHash("sha256").update(archive).digest("hex"); + if (actualChecksum !== expectedChecksum) { + throw new Error( + `Checksum mismatch for ${assetName}: expected ${expectedChecksum}, got ${actualChecksum}.` + ); + } + + const stagingRoot = mkdtempSync(join(cacheRoot, ".download-")); + const archivePath = join(stagingRoot, assetName); + const packageRoot = join(stagingRoot, "package"); + writeFileSync(archivePath, archive); + try { + await extractTar({ + cwd: stagingRoot, + file: archivePath, + gzip: true, + preservePaths: false, + strict: true, + }); + validateFile( + join(packageRoot, "prebuilds", platform, "runtime.node"), + "Copilot runtime.node" + ); + mkdirSync(dirname(cachedPackageRoot), { recursive: true }); + try { + renameSync(packageRoot, cachedPackageRoot); + } catch (error) { + if (!existsSync(cachedRuntimeNode)) { + throw error; + } + } + return cachedPackageRoot; + } finally { + rmSync(stagingRoot, { recursive: true, force: true }); + } +} diff --git a/nodejs/test/e2e/extension_env_access.e2e.test.ts b/nodejs/test/e2e/extension_env_access.e2e.test.ts index 38210a22fb..1aa4875dd4 100644 --- a/nodejs/test/e2e/extension_env_access.e2e.test.ts +++ b/nodejs/test/e2e/extension_env_access.e2e.test.ts @@ -39,7 +39,7 @@ interface ExtensionRun { * stdio, so a stub host observes exactly what the CLI observes. That is the only * way to cover this feature end to end today: the released CLI predates the host * half (github/copilot-agent-runtime#15144), so it ignores the request and grants - * nothing. Once the `@github/copilot` dependency carries the host half, the + * nothing. Once the pinned CLI release carries the host half, the * real-CLI case below can assert the grant instead. */ async function runExtensionAgainstStubHost(options: { @@ -188,7 +188,7 @@ const cliObservations = mkdtempSync(join(tmpdir(), "copilot-env-access-cli-")); const cliResultFile = join(cliObservations, "result"); const cliContext = await createSdkTestContext({ copilotClientOptions: { - connection: RuntimeConnection.forStdio({ path: getLegacyCliPathForTests() }), + connection: RuntimeConnection.forStdio({ path: await getLegacyCliPathForTests() }), env: { COPILOT_CLI_ENABLED_FEATURE_FLAGS: "EXTENSIONS", EXTENSION_ENV_REQUEST: "E2E_SDK_TOKEN", @@ -201,7 +201,7 @@ const cliContext = await createSdkTestContext({ // The released CLI ignores `requestedEnvironmentVariables`, so this covers the // half a real CLI can prove today: asking for variables does not break the join. -// It becomes the grant test once `@github/copilot` carries the host half. +// It becomes the grant test once the pinned CLI release carries the host half. it("joins a real CLI that does not support environment requests", async () => { const { workDir, copilotClient } = cliContext; const extensionDir = join(workDir, ".github", "extensions", "env-access"); diff --git a/nodejs/test/e2e/factory.e2e.test.ts b/nodejs/test/e2e/factory.e2e.test.ts index 2bf3ff17fb..98004406f9 100644 --- a/nodejs/test/e2e/factory.e2e.test.ts +++ b/nodejs/test/e2e/factory.e2e.test.ts @@ -15,7 +15,7 @@ import { retry } from "./harness/sdkTestHelper.js"; const __dirname = dirname(fileURLToPath(import.meta.url)); const factoryTestContext = await createSdkTestContext({ copilotClientOptions: { - connection: RuntimeConnection.forStdio({ path: getLegacyCliPathForTests() }), + connection: RuntimeConnection.forStdio({ path: await getLegacyCliPathForTests() }), env: { COPILOT_CLI_ENABLED_FEATURE_FLAGS: "EXTENSIONS,AGENT_FACTORIES", }, diff --git a/nodejs/test/e2e/harness/sdkTestContext.ts b/nodejs/test/e2e/harness/sdkTestContext.ts index 58c275c800..4eb937b5c4 100644 --- a/nodejs/test/e2e/harness/sdkTestContext.ts +++ b/nodejs/test/e2e/harness/sdkTestContext.ts @@ -12,6 +12,8 @@ import { afterAll, afterEach, beforeEach, onTestFailed, TestContext } from "vite import { CopilotClient, CopilotClientOptions, RuntimeConnection } from "../../../src"; import { CapiProxy } from "./CapiProxy"; import { formatError, retry } from "./sdkTestHelper"; +import { ensureCopilotPackage } from "../../../src/runtimeArtifacts"; +import { COPILOT_CLI_VERSION } from "../../../src/cliVersion"; export const isCI = process.env.GITHUB_ACTIONS === "true"; export const DEFAULT_GITHUB_TOKEN = "fake-token-for-e2e-tests"; @@ -50,27 +52,10 @@ function getCliPathForTests(): string | undefined { return undefined; } -function getCliPlatformPackageNames(): string[] { - const variants = - process.platform === "linux" - ? process.report?.getReport().header.glibcVersionRuntime - ? ["linux", "linuxmusl"] - : ["linuxmusl", "linux"] - : [process.platform]; - return variants.map((variant) => `@github/copilot-${variant}-${process.arch}`); -} - /** Resolves the legacy SEA only for tests that explicitly exercise Node-hosted features. */ -export function getLegacyCliPathForTests(): string { - const cliName = process.platform === "win32" ? "copilot.exe" : "copilot"; - const githubModules = resolve(__dirname, "../../../node_modules/@github"); - for (const packageName of getCliPlatformPackageNames()) { - const cliPath = join(githubModules, packageName.slice("@github/".length), cliName); - if (fs.existsSync(cliPath)) { - return cliPath; - } - } - throw new Error("Legacy Copilot CLI binary not found in the installed platform package."); +export async function getLegacyCliPathForTests(): Promise { + const packageRoot = await ensureCopilotPackage(COPILOT_CLI_VERSION); + return join(packageRoot, "app.js"); } export async function createSdkTestContext({ diff --git a/nodejs/test/e2e/ui_elicitation.e2e.test.ts b/nodejs/test/e2e/ui_elicitation.e2e.test.ts index 6366db36cb..51195206aa 100644 --- a/nodejs/test/e2e/ui_elicitation.e2e.test.ts +++ b/nodejs/test/e2e/ui_elicitation.e2e.test.ts @@ -48,7 +48,9 @@ describe("UI Elicitation Callback", async () => { { timeout: 60_000 }, async () => { const legacyClient = ctx.createClient({ - connection: RuntimeConnection.forStdio({ path: getLegacyCliPathForTests() }), + connection: RuntimeConnection.forStdio({ + path: await getLegacyCliPathForTests(), + }), }); try { const session = await legacyClient.createSession({ diff --git a/nodejs/test/runtimeArtifacts.test.ts b/nodejs/test/runtimeArtifacts.test.ts index 4a58789e6e..3a347799f5 100644 --- a/nodejs/test/runtimeArtifacts.test.ts +++ b/nodejs/test/runtimeArtifacts.test.ts @@ -1,9 +1,18 @@ import { existsSync, mkdtempSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; +import { createHash } from "node:crypto"; +import { c as createTar } from "tar"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { defaultRuntimeCacheRoot, materializeRuntimeBundle } from "../src/runtimeArtifacts.js"; +import { + defaultRuntimeCacheRoot, + ensureRuntimeBundle, + getRuntimePlatform, + getRuntimeReleaseAssetName, + materializeRuntimeBundle, +} from "../src/runtimeArtifacts.js"; +import { COPILOT_CLI_HASHES, COPILOT_CLI_VERSION } from "../src/cliVersion.js"; describe("defaultRuntimeCacheRoot", () => { it.each([ @@ -31,6 +40,33 @@ describe("defaultRuntimeCacheRoot", () => { }); }); +describe("release runtime selection", () => { + it("keeps the compiled CLI version aligned with package metadata", () => { + const packageJson = JSON.parse( + readFileSync(join(import.meta.dirname, "../package.json"), "utf8") + ); + expect(COPILOT_CLI_VERSION).toBe(packageJson.copilotCliVersion); + expect(Object.keys(COPILOT_CLI_HASHES)).toHaveLength(8); + expect(packageJson.dependencies).not.toHaveProperty("@github/copilot"); + }); + + it.each([ + ["darwin", "arm64", false, "darwin-arm64"], + ["darwin", "x64", false, "darwin-x64"], + ["linux", "arm64", false, "linux-arm64"], + ["linux", "x64", true, "linuxmusl-x64"], + ["win32", "arm64", false, "win32-arm64"], + ])("maps %s/%s to %s", (platform, arch, musl, expected) => { + expect(getRuntimePlatform(platform, arch, musl)).toBe(expected); + }); + + it("uses the platform npm tarball published in the CLI release", () => { + expect(getRuntimeReleaseAssetName("1.2.3-4", "linux-x64")).toBe( + "github-copilot-1.2.3-4-linux-x64.tgz" + ); + }); +}); + describe("materializeRuntimeBundle", () => { afterEach(() => vi.unstubAllEnvs()); @@ -107,3 +143,63 @@ describe("materializeRuntimeBundle", () => { ).toThrow(/Copilot runtime\.node not found/); }); }); + +describe("ensureRuntimeBundle", () => { + it("downloads, verifies, and caches a release runtime", async () => { + const sourceRoot = mkdtempSync(join(tmpdir(), "copilot-release-source-")); + const packageRoot = join(sourceRoot, "package"); + const platform = "linux-x64"; + const prebuilds = join(packageRoot, "prebuilds", platform); + mkdirSync(prebuilds, { recursive: true }); + writeFileSync(join(prebuilds, "copilot-runtime"), "wrapper"); + writeFileSync(join(prebuilds, "runtime.node"), "runtime"); + mkdirSync(join(packageRoot, "schemas"), { recursive: true }); + writeFileSync(join(packageRoot, "schemas", "api.schema.json"), "{}"); + + const archivePath = join(sourceRoot, "runtime.tgz"); + await createTar({ cwd: sourceRoot, file: archivePath, gzip: true }, ["package"]); + const archive = readFileSync(archivePath); + const version = "1.2.3-4"; + const assetName = getRuntimeReleaseAssetName(version, platform); + const checksum = createHash("sha256").update(archive).digest("hex"); + const fetcher = vi.fn(async (url: string | URL | Request) => { + const value = String(url); + return value.endsWith("SHA256SUMS.txt") + ? new Response(`${checksum} ${assetName}\n`) + : new Response(archive); + }); + const cacheRoot = join(sourceRoot, "cache"); + + const runtimePath = await ensureRuntimeBundle(version, { + cacheRoot, + fetch: fetcher, + platform, + }); + expect(readFileSync(runtimePath, "utf8")).toBe("wrapper"); + expect(readFileSync(join(dirname(runtimePath), "runtime.node"), "utf8")).toBe("runtime"); + expect(readFileSync(join(dirname(runtimePath), "schemas", "api.schema.json"), "utf8")).toBe( + "{}" + ); + + await expect( + ensureRuntimeBundle(version, { cacheRoot, fetch: fetcher, platform }) + ).resolves.toBe(runtimePath); + expect(fetcher).toHaveBeenCalledTimes(2); + }); + + it("rejects a release archive that does not match the manifest", async () => { + const cacheRoot = mkdtempSync(join(tmpdir(), "copilot-release-mismatch-")); + const fetcher = vi.fn(async (url: string | URL | Request) => + String(url).endsWith("SHA256SUMS.txt") + ? new Response( + `${"0".repeat(64)} ${getRuntimeReleaseAssetName("1.2.3", "linux-x64")}\n` + ) + : new Response("corrupt archive") + ); + + await expect( + ensureRuntimeBundle("1.2.3", { cacheRoot, fetch: fetcher, platform: "linux-x64" }) + ).rejects.toThrow("Checksum mismatch"); + expect(existsSync(join(cacheRoot, "1.2.3", "linux-x64"))).toBe(false); + }); +}); diff --git a/python/e2e/conftest.py b/python/e2e/conftest.py index a789b2b567..dfe6f30917 100644 --- a/python/e2e/conftest.py +++ b/python/e2e/conftest.py @@ -20,10 +20,10 @@ # Out-of-process children resolve auth in their own process where the token already # outranks HMAC. See https://github.com/github/copilot-sdk/issues/1934. if not cli_download.CLI_VERSION: - package_lock = json.loads( - (Path(__file__).parents[2] / "nodejs" / "package-lock.json").read_text() + package_json = json.loads( + (Path(__file__).parents[2] / "nodejs" / "package.json").read_text() ) - cli_download.CLI_VERSION = package_lock["packages"]["node_modules/@github/copilot"]["version"] + cli_download.CLI_VERSION = package_json["copilotCliVersion"] if is_inprocess_transport(): os.environ.pop("COPILOT_HMAC_KEY", None) diff --git a/python/scripts/inject-cli-version.mjs b/python/scripts/inject-cli-version.mjs index 359e7f680b..fef0f967b8 100644 --- a/python/scripts/inject-cli-version.mjs +++ b/python/scripts/inject-cli-version.mjs @@ -2,7 +2,7 @@ /** * inject-cli-version.mjs * - * Reads the pinned @github/copilot version from nodejs/package-lock.json and + * Reads the pinned Copilot CLI version from nodejs/package.json and * writes it into python/copilot/_cli_version.py, replacing the `CLI_VERSION = None` * sentinel with the concrete version string. * @@ -17,19 +17,13 @@ import { fileURLToPath } from "url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const repoRoot = join(__dirname, "..", ".."); -// Read version from nodejs/package-lock.json -const lockPath = join(repoRoot, "nodejs", "package-lock.json"); -const lock = JSON.parse(readFileSync(lockPath, "utf-8")); - -// The version is in packages["node_modules/@github/copilot"].version -const copilotPkg = lock.packages?.["node_modules/@github/copilot"]; -if (!copilotPkg?.version) { - console.error( - "Error: Could not find @github/copilot version in nodejs/package-lock.json" - ); +const packagePath = join(repoRoot, "nodejs", "package.json"); +const packageJson = JSON.parse(readFileSync(packagePath, "utf-8")); +const version = packageJson.copilotCliVersion; +if (!version) { + console.error("Error: Could not find copilotCliVersion in nodejs/package.json"); process.exit(1); } -const version = copilotPkg.version; console.log(`Injecting CLI_VERSION = "${version}"`); // Patch _cli_version.py diff --git a/rust/Cargo.toml b/rust/Cargo.toml index c7a704fd50..c66480d511 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -119,7 +119,6 @@ test = false bench = false [build-dependencies] -base64 = "0.22" dirs = "5" flate2 = "1" serde_json = "1" diff --git a/rust/README.md b/rust/README.md index b9a1f5b1bc..62d75e2f19 100644 --- a/rust/README.md +++ b/rust/README.md @@ -961,7 +961,7 @@ github-copilot-sdk = { version = "0.1", default-features = false } 1. **Version pin.** `build.rs` reads the CLI version from one of two sources: - `cli-version.txt` at the crate root (present in published crate tarballs and vendored slots). - - Otherwise, `../nodejs/package-lock.json` (contributor build inside the github/copilot-sdk repo — matches the .NET and Go SDK conventions here). + - Otherwise, `../nodejs/package.json` (contributor build inside the github/copilot-sdk repo — matches the .NET and Go SDK conventions here). The resolved version is baked into the crate via `cargo:rustc-env=COPILOT_SDK_CLI_VERSION` regardless of mode. The runtime resolver consumes it to recompute the on-disk path by convention, so no absolute paths leak into the rlib. diff --git a/rust/build/in_process.rs b/rust/build/in_process.rs index e6edcf1b90..5e07ce43df 100644 --- a/rust/build/in_process.rs +++ b/rust/build/in_process.rs @@ -2,7 +2,6 @@ use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use std::time::Duration; -use base64::Engine; use sha2::Digest; pub(crate) fn main() { @@ -14,20 +13,20 @@ pub(crate) fn main() { println!("cargo::rustc-check-cfg=cfg(has_extracted_cli)"); println!("cargo:rerun-if-changed=cli-version-in-process.txt"); - // Only declare the lockfile rerun when the lockfile actually exists. + // Only declare the package metadata rerun when it actually exists. // Cargo treats `rerun-if-changed` for a missing path as "always rerun" // — so unconditionally declaring this on consumers without a sibling // `nodejs/` (vendored slots, published crates) would force build.rs // to re-run on every `cargo build` even when nothing has changed. - // The lockfile path is only the source-of-truth in this repo's + // The package file is only the source-of-truth in this repo's // contributor builds; everywhere else `cli-version-in-process.txt` is canonical. let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set"); - let lockfile = Path::new(&manifest_dir) + let package_file = Path::new(&manifest_dir) .join("..") .join("nodejs") - .join("package-lock.json"); - if lockfile.is_file() { - println!("cargo:rerun-if-changed={}", lockfile.display()); + .join("package.json"); + if package_file.is_file() { + println!("cargo:rerun-if-changed={}", package_file.display()); } // Hard opt-out: disable the entire download / bundle / cache mechanism @@ -60,17 +59,16 @@ pub(crate) fn main() { let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR is always set by cargo"); let out = Path::new(&out_dir); - // Resolve version + npm integrity from one of two sources, in order: + // Resolve version + release SHA-256 from one of two sources, in order: // 1. `cli-version-in-process.txt` snapshot at the crate root (published-crate // consumer; generated by the publish workflow). Combined format: - // `version=X` line + per-package integrity lines. Committing these + // `version=X` line + per-package hash lines. Committing these // makes the publish workflow the trust boundary — an attacker who // later re-points the release tag can't silently poison consumer // builds. - // 2. Sibling `../nodejs/package-lock.json` (contributor build inside - // the github/copilot-sdk repo), whose platform-package integrity is - // the same trust source npm uses. - let (version, expected_integrity) = resolve_version_and_integrity(platform.package_name); + // 2. Sibling `../nodejs/package.json` (contributor build inside + // the github/copilot-sdk repo), combined with the release checksums. + let (version, expected_hash) = resolve_version_and_hash(platform.package_name); // Bake the version into the crate regardless of mode. This is the // single source of truth for "what CLI version did build.rs target", @@ -81,10 +79,13 @@ pub(crate) fn main() { // `target/` reuse stays cache-coherent. println!("cargo:rustc-env=COPILOT_SDK_CLI_VERSION={version}"); - let archive_name = format!("{}-{version}.tgz", platform.package_name); + let asset_platform = platform + .package_name + .strip_prefix("copilot-") + .expect("platform package names start with copilot-"); + let archive_name = format!("github-copilot-{version}-{asset_platform}.tgz"); let download_url = format!( - "https://registry.npmjs.org/@github/{}/-/{}", - platform.package_name, archive_name + "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/github/copilot-cli/releases/download/v{version}/{archive_name}" ); let cache_dir = std::env::var("BUNDLED_CLI_CACHE_DIR") .ok() @@ -94,7 +95,7 @@ pub(crate) fn main() { let include_runtime = std::env::var_os("CARGO_FEATURE_BUNDLED_IN_PROCESS").is_some(); if std::env::var_os("CARGO_FEATURE_BUNDLED_CLI").is_some() { - let archive = cached_download(&download_url, &cache_key, &expected_integrity, &cache_dir); + let archive = cached_download(&download_url, &cache_key, &expected_hash, &cache_dir); verify_runtime_package(&archive, platform, &archive_name); emit_embedded(out, &archive, platform, include_runtime); println!("cargo:rustc-cfg=has_bundled_cli"); @@ -112,7 +113,7 @@ pub(crate) fn main() { install_dir.join("runtime.node"), install_dir.join(".hostless-runtime-assets-v1"), ]; - let expected_marker = format!("{version}\n{expected_integrity}\n"); + let expected_marker = format!("{version}\n{expected_hash}\n"); // Invalidate build.rs whenever either cached artifact disappears (cache // GC, manual rm, OS reset, switching extract dir). Without this, cargo @@ -135,8 +136,7 @@ pub(crate) fn main() { ) }); } - let archive = - cached_download(&download_url, &cache_key, &expected_integrity, &cache_dir); + let archive = cached_download(&download_url, &cache_key, &expected_hash, &cache_dir); verify_runtime_package(&archive, platform, &archive_name); extract_to_cache( &archive, @@ -331,10 +331,10 @@ fn append_archive_file( .unwrap_or_else(|e| panic!("failed to add `{path}` to embedded CLI archive: {e}")); } -/// Resolve the CLI version and npm integrity for the current target's +/// Resolve the CLI version and release hash for the current target's /// platform package. Picks one of two sources in order. Panics with a clear /// error if neither is available. -fn resolve_version_and_integrity(package_name: &str) -> (String, String) { +fn resolve_version_and_hash(package_name: &str) -> (String, String) { let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set"); // 1. Snapshot file at the crate root (published-crate consumer, @@ -347,13 +347,19 @@ fn resolve_version_and_integrity(package_name: &str) -> (String, String) { .unwrap_or_else(|e| panic!("invalid {}: {e}", snapshot.display())); } - // 2. Lockfile fallback (contributor build inside github/copilot-sdk). - let lockfile = Path::new(&manifest_dir) + // 2. Package metadata fallback (contributor build inside github/copilot-sdk). + let package_file = Path::new(&manifest_dir) .join("..") .join("nodejs") - .join("package-lock.json"); - if lockfile.is_file() { - return read_version_and_integrity_from_package_lock(&lockfile, package_name); + .join("package.json"); + if package_file.is_file() { + let version = read_version_from_package_json(&package_file); + let platform = package_name + .strip_prefix("copilot-") + .expect("platform package names start with copilot-"); + let asset_name = format!("github-copilot-{version}-{platform}.tgz"); + let hash = fetch_live_sha256(&version, &asset_name); + return (version, hash); } panic!( @@ -362,19 +368,19 @@ fn resolve_version_and_integrity(package_name: &str) -> (String, String) { - {} (missing)\n\ - {} (missing)\n\ In a published crate or vendored slot, `cli-version-in-process.txt` should be present.\n\ - Inside the github/copilot-sdk repo, `../nodejs/package-lock.json` is the source.", + Inside the github/copilot-sdk repo, `../nodejs/package.json` is the source.", snapshot.display(), - lockfile.display(), + package_file.display(), ); } /// Parse the `cli-version-in-process.txt` snapshot file. Format is one `key=value` per /// line. The first non-comment line is `version=X.Y.Z`; subsequent lines map -/// platform package name to npm integrity. Blank lines and lines starting with `#` +/// platform package name to SHA-256. Blank lines and lines starting with `#` /// are skipped. fn parse_snapshot(contents: &str, package_name: &str) -> Result<(String, String), String> { let mut version: Option = None; - let mut integrity: Option = None; + let mut hash: Option = None; for (line_no, raw) in contents.lines().enumerate() { let line = raw.trim(); if line.is_empty() || line.starts_with('#') { @@ -388,33 +394,40 @@ fn parse_snapshot(contents: &str, package_name: &str) -> Result<(String, String) }; match key.trim() { "version" => version = Some(value.trim().to_string()), - k if k == package_name => integrity = Some(value.trim().to_string()), + k if k == package_name => hash = Some(value.trim().to_string()), _ => {} } } let version = version.ok_or("missing `version=` line")?; - let integrity = - integrity.ok_or_else(|| format!("missing integrity for package `{package_name}`"))?; - Ok((version, integrity)) + let hash = hash.ok_or_else(|| format!("missing hash for package `{package_name}`"))?; + Ok((version, hash)) } -fn read_version_and_integrity_from_package_lock( - path: &Path, - package_name: &str, -) -> (String, String) { +fn read_version_from_package_json(path: &Path) -> String { let contents = std::fs::read_to_string(path) .unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display())); - let lock: serde_json::Value = serde_json::from_str(&contents) + let package: serde_json::Value = serde_json::from_str(&contents) .unwrap_or_else(|e| panic!("failed to parse {}: {e}", path.display())); - let cli_key = "node_modules/@github/copilot"; - let version = lock["packages"][cli_key]["version"] - .as_str() - .unwrap_or_else(|| panic!("{cli_key} has no version in {}", path.display())); - let platform_key = format!("node_modules/@github/{package_name}"); - let integrity = lock["packages"][&platform_key]["integrity"] + package["copilotCliVersion"] .as_str() - .unwrap_or_else(|| panic!("{platform_key} has no integrity in {}", path.display())); - (version.to_string(), integrity.to_string()) + .unwrap_or_else(|| panic!("copilotCliVersion is missing in {}", path.display())) + .to_string() +} + +fn fetch_live_sha256(version: &str, asset_name: &str) -> String { + let checksums_url = format!( + "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/github/copilot-cli/releases/download/v{version}/SHA256SUMS.txt" + ); + let checksums = download_with_retry(&checksums_url); + let checksums_text = + std::str::from_utf8(&checksums).expect("checksums file is not valid UTF-8"); + checksums_text + .lines() + .find_map(|line| { + let (hash, name) = line.split_once(char::is_whitespace)?; + (name.trim_start().trim_start_matches('*') == asset_name).then(|| hash.to_string()) + }) + .unwrap_or_else(|| panic!("SHA256SUMS.txt has no entry for {asset_name}")) } #[derive(Clone, Copy)] @@ -695,20 +708,20 @@ fn sanitize_version(version: &str) -> String { } /// Read a file from the download cache, or download it (with retries) and save -/// to cache. Verifies npm integrity on every path. Evicts stale/corrupt cache entries +/// to cache. Verifies SHA-256 on every path. Evicts stale/corrupt cache entries /// automatically. Cache I/O failures are treated as cache misses — they never /// break the build. fn cached_download( url: &str, cache_key: &str, - expected_integrity: &str, + expected_hash: &str, cache_dir: &Option, ) -> Vec { if let Some(dir) = cache_dir { let cached_path = dir.join(cache_key); if cached_path.is_file() { match std::fs::read(&cached_path) { - Ok(data) if verify_integrity(&data, expected_integrity) => { + Ok(data) if verify_hash(&data, expected_hash) => { // Silent cache hit — nothing to surface. return data; } @@ -728,9 +741,9 @@ fn cached_download( println!("cargo:warning=Downloading {url}"); let data = download_with_retry(url); - if !verify_integrity(&data, expected_integrity) { + if !verify_hash(&data, expected_hash) { panic!( - "Archive integrity check failed for {url}!\n expected: {expected_integrity}\n \ + "Archive integrity check failed for {url}!\n expected: {expected_hash}\n \ This could indicate a corrupted download or a supply-chain attack." ); } @@ -832,11 +845,7 @@ fn try_download(url: &str) -> Result, DownloadError> { } fn verify_runtime_package(archive: &[u8], platform: Platform, package_name: &str) { - for file_name in [ - platform.binary_name, - "runtime.node", - platform.runtime_wrapper_name(), - ] { + for file_name in ["runtime.node", platform.runtime_wrapper_name()] { if archive_contains_tar_entry(archive, file_name) { continue; } @@ -864,14 +873,8 @@ fn archive_contains_tar_entry(targz: &[u8], binary_name: &str) -> bool { false } -fn verify_integrity(data: &[u8], integrity: &str) -> bool { - let Some(encoded) = integrity.strip_prefix("sha512-") else { - return false; - }; - let Ok(expected) = base64::engine::general_purpose::STANDARD.decode(encoded) else { - return false; - }; - let mut hasher = sha2::Sha512::new(); +fn verify_hash(data: &[u8], expected: &str) -> bool { + let mut hasher = sha2::Sha256::new(); hasher.update(data); - hasher.finalize().as_slice() == expected + format!("{:x}", hasher.finalize()) == expected } diff --git a/rust/build/out_of_process.rs b/rust/build/out_of_process.rs index b8cd3acc3f..72c0bd48cc 100644 --- a/rust/build/out_of_process.rs +++ b/rust/build/out_of_process.rs @@ -13,20 +13,20 @@ pub(crate) fn main() { println!("cargo::rustc-check-cfg=cfg(has_extracted_cli)"); println!("cargo:rerun-if-changed=cli-version.txt"); - // Only declare the lockfile rerun when the lockfile actually exists. + // Only declare the package metadata rerun when it actually exists. // Cargo treats `rerun-if-changed` for a missing path as "always rerun" // — so unconditionally declaring this on consumers without a sibling // `nodejs/` (vendored slots, published crates) would force build.rs // to re-run on every `cargo build` even when nothing has changed. - // The lockfile path is only the source-of-truth in this repo's + // The package file is only the source-of-truth in this repo's // contributor builds; everywhere else `cli-version.txt` is canonical. let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set"); - let lockfile = Path::new(&manifest_dir) + let package_file = Path::new(&manifest_dir) .join("..") .join("nodejs") - .join("package-lock.json"); - if lockfile.is_file() { - println!("cargo:rerun-if-changed={}", lockfile.display()); + .join("package.json"); + if package_file.is_file() { + println!("cargo:rerun-if-changed={}", package_file.display()); } // Hard opt-out: disable the entire download / bundle / cache mechanism @@ -66,7 +66,7 @@ pub(crate) fn main() { // makes the publish workflow the trust boundary — an attacker who // later re-points the release tag can't silently poison consumer // builds. - // 2. Sibling `../nodejs/package-lock.json` (contributor build inside + // 2. Sibling `../nodejs/package.json` (contributor build inside // the github/copilot-sdk repo; live SHA256SUMS.txt fetch). Matches // the .NET `_GetCopilotCliVersion` MSBuild target and the Go // `cmd/bundler` tool. @@ -194,14 +194,14 @@ fn resolve_version_and_hash(asset_name: &str) -> (String, String) { .unwrap_or_else(|e| panic!("invalid {}: {e}", snapshot.display())); } - // 2. Lockfile fallback (contributor build inside github/copilot-sdk) — + // 2. Package metadata fallback (contributor build inside github/copilot-sdk) — // read version, fetch live SHA256SUMS. - let lockfile = Path::new(&manifest_dir) + let package_file = Path::new(&manifest_dir) .join("..") .join("nodejs") - .join("package-lock.json"); - if lockfile.is_file() { - let version = read_version_from_package_lock(&lockfile); + .join("package.json"); + if package_file.is_file() { + let version = read_version_from_package_json(&package_file); let hash = fetch_live_sha256(&version, asset_name); return (version, hash); } @@ -212,9 +212,9 @@ fn resolve_version_and_hash(asset_name: &str) -> (String, String) { - {} (missing)\n\ - {} (missing)\n\ In a published crate or vendored slot, `cli-version.txt` should be present.\n\ - Inside the github/copilot-sdk repo, `../nodejs/package-lock.json` is the source.", + Inside the github/copilot-sdk repo, `../nodejs/package.json` is the source.", snapshot.display(), - lockfile.display(), + package_file.display(), ); } @@ -247,27 +247,18 @@ fn parse_snapshot(contents: &str, asset_name: &str) -> Result<(String, String), Ok((version, hash)) } -/// Read the `@github/copilot` version from `nodejs/package-lock.json`. -fn read_version_from_package_lock(path: &Path) -> String { +/// Read the pinned Copilot CLI version from `nodejs/package.json`. +fn read_version_from_package_json(path: &Path) -> String { let contents = std::fs::read_to_string(path) .unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display())); - // Minimal JSON walk: find `"node_modules/@github/copilot"` object and - // its `"version"` field. Full JSON parsing keeps build.rs dep-light by - // using a regex; the file is generated by npm and we're matching an - // exact key path. - let key = "\"node_modules/@github/copilot\""; + let key = "\"copilotCliVersion\""; let key_pos = contents .find(key) .unwrap_or_else(|| panic!("{} does not contain {key}", path.display())); let after_key = &contents[key_pos + key.len()..]; - let version_key = "\"version\""; - let v_pos = after_key - .find(version_key) - .unwrap_or_else(|| panic!("no `version` field found near {key} in {}", path.display())); - let after_v = &after_key[v_pos + version_key.len()..]; - let q1 = after_v.find('"').expect("malformed version"); - let after_q1 = &after_v[q1 + 1..]; - let q2 = after_q1.find('"').expect("malformed version"); + let q1 = after_key.find('"').expect("malformed copilotCliVersion"); + let after_q1 = &after_key[q1 + 1..]; + let q2 = after_q1.find('"').expect("malformed copilotCliVersion"); after_q1[..q2].to_string() } diff --git a/rust/scripts/snapshot-bundled-cli-version.sh b/rust/scripts/snapshot-bundled-cli-version.sh index 7f78d529b0..808853141c 100755 --- a/rust/scripts/snapshot-bundled-cli-version.sh +++ b/rust/scripts/snapshot-bundled-cli-version.sh @@ -6,7 +6,7 @@ # GitHub.Copilot.SDK.props before NuGet packing. # # Inputs: -# - ../nodejs/package-lock.json (sibling) - source of the pinned version. +# - ../nodejs/package.json (sibling) - source of the pinned version. # - https://github.com/github/copilot-cli/releases/v{version}/SHA256SUMS.txt - # authoritative per-platform hashes. # @@ -18,17 +18,17 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RUST_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" REPO_ROOT="$(cd "${RUST_DIR}/.." && pwd)" -LOCKFILE="${REPO_ROOT}/nodejs/package-lock.json" +PACKAGE_FILE="${REPO_ROOT}/nodejs/package.json" OUTPUT="${RUST_DIR}/cli-version.txt" -if [[ ! -f "${LOCKFILE}" ]]; then - echo "error: ${LOCKFILE} not found" >&2 +if [[ ! -f "${PACKAGE_FILE}" ]]; then + echo "error: ${PACKAGE_FILE} not found" >&2 exit 1 fi -VERSION="$(node -e "console.log(require('${LOCKFILE}').packages['node_modules/@github/copilot'].version)")" +VERSION="$(node -e "console.log(require('${PACKAGE_FILE}').copilotCliVersion)")" if [[ -z "${VERSION}" ]]; then - echo "error: could not read @github/copilot version from ${LOCKFILE}" >&2 + echo "error: could not read copilotCliVersion from ${PACKAGE_FILE}" >&2 exit 1 fi @@ -45,23 +45,22 @@ ASSETS=( "copilot-win32-x64.zip" ) -declare -A HASHES -for asset in "${ASSETS[@]}"; do - hash="$(printf '%s\n' "${SHA256SUMS}" | awk -v a="${asset}" '$2 == a { print $1 }')" - if [[ -z "${hash}" ]]; then - echo "error: SHA256SUMS.txt missing entry for ${asset}" >&2 - exit 1 - fi - HASHES[$asset]="${hash}" -done - +TEMP_OUTPUT="${OUTPUT}.tmp.$$" +trap 'rm -f "${TEMP_OUTPUT}"' EXIT { echo "# Auto-generated by rust/scripts/snapshot-bundled-cli-version.sh" echo "# Do not edit. Regenerated by the publish workflow on every release." echo "version=${VERSION}" for asset in "${ASSETS[@]}"; do - echo "${asset}=${HASHES[$asset]}" + hash="$(printf '%s\n' "${SHA256SUMS}" | awk -v a="${asset}" '$2 == a { print $1 }')" + if [[ -z "${hash}" ]]; then + echo "error: SHA256SUMS.txt missing entry for ${asset}" >&2 + exit 1 + fi + echo "${asset}=${hash}" done -} > "${OUTPUT}" +} > "${TEMP_OUTPUT}" +mv "${TEMP_OUTPUT}" "${OUTPUT}" +trap - EXIT echo "Wrote ${OUTPUT} (version=${VERSION}, ${#ASSETS[@]} hashes)" \ No newline at end of file diff --git a/rust/scripts/snapshot-bundled-in-process-version.sh b/rust/scripts/snapshot-bundled-in-process-version.sh index 8743f9d17a..09e7917464 100755 --- a/rust/scripts/snapshot-bundled-in-process-version.sh +++ b/rust/scripts/snapshot-bundled-in-process-version.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # -# Snapshot the Copilot CLI version + per-platform npm integrity values for the +# Snapshot the Copilot CLI version + per-platform release hashes for the # rust crate's bundled-in-process build path. set -euo pipefail @@ -8,20 +8,23 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RUST_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" REPO_ROOT="$(cd "${RUST_DIR}/.." && pwd)" -LOCKFILE="${REPO_ROOT}/nodejs/package-lock.json" +PACKAGE_FILE="${REPO_ROOT}/nodejs/package.json" OUTPUT="${RUST_DIR}/cli-version-in-process.txt" -if [[ ! -f "${LOCKFILE}" ]]; then - echo "error: ${LOCKFILE} not found" >&2 +if [[ ! -f "${PACKAGE_FILE}" ]]; then + echo "error: ${PACKAGE_FILE} not found" >&2 exit 1 fi -VERSION="$(node -e "console.log(require('${LOCKFILE}').packages['node_modules/@github/copilot'].version)")" +VERSION="$(node -e "console.log(require('${PACKAGE_FILE}').copilotCliVersion)")" if [[ -z "${VERSION}" ]]; then - echo "error: could not read @github/copilot version from ${LOCKFILE}" >&2 + echo "error: could not read copilotCliVersion from ${PACKAGE_FILE}" >&2 exit 1 fi +CHECKSUMS_URL="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/github/copilot-cli/releases/download/v${VERSION}/SHA256SUMS.txt" +SHA256SUMS="$(curl -fsSL --retry 3 --retry-delay 2 "${CHECKSUMS_URL}")" + PACKAGES=( "copilot-darwin-arm64" "copilot-darwin-x64" @@ -33,23 +36,24 @@ PACKAGES=( "copilot-win32-x64" ) -declare -A INTEGRITIES -for package in "${PACKAGES[@]}"; do - integrity="$(node -e "console.log(require('${LOCKFILE}').packages['node_modules/@github/${package}'].integrity)")" - if [[ -z "${integrity}" ]]; then - echo "error: package-lock.json missing integrity for @github/${package}" >&2 - exit 1 - fi - INTEGRITIES[$package]="${integrity}" -done - +TEMP_OUTPUT="${OUTPUT}.tmp.$$" +trap 'rm -f "${TEMP_OUTPUT}"' EXIT { echo "# Auto-generated by rust/scripts/snapshot-bundled-in-process-version.sh" echo "# Do not edit. Regenerated by the publish workflow on every release." echo "version=${VERSION}" for package in "${PACKAGES[@]}"; do - echo "${package}=${INTEGRITIES[$package]}" + platform="${package#copilot-}" + asset="github-copilot-${VERSION}-${platform}.tgz" + hash="$(printf '%s\n' "${SHA256SUMS}" | awk -v a="${asset}" '$2 == a { print $1 }')" + if [[ -z "${hash}" ]]; then + echo "error: SHA256SUMS.txt missing entry for ${asset}" >&2 + exit 1 + fi + echo "${package}=${hash}" done -} > "${OUTPUT}" +} > "${TEMP_OUTPUT}" +mv "${TEMP_OUTPUT}" "${OUTPUT}" +trap - EXIT -echo "Wrote ${OUTPUT} (version=${VERSION}, ${#PACKAGES[@]} integrity values)" +echo "Wrote ${OUTPUT} (version=${VERSION}, ${#PACKAGES[@]} hashes)" diff --git a/rust/tests/cli_resolution_test.rs b/rust/tests/cli_resolution_test.rs index 75773a0c0d..0cb72510b1 100644 --- a/rust/tests/cli_resolution_test.rs +++ b/rust/tests/cli_resolution_test.rs @@ -199,7 +199,7 @@ async fn extract_dir_runtime_override_is_honored() { /// Build-time version pins, when present, must match the selected bundling /// implementation's checksum format. -/// When absent, build.rs falls through to `../nodejs/package-lock.json` — +/// When absent, build.rs falls through to `../nodejs/package.json` — /// both are accepted, this test only checks the pin file's format if it's /// there. #[test] diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index 590213d72b..cab25ff0e6 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -12,6 +12,8 @@ import type { JSONSchema7, JSONSchema7Definition } from "json-schema"; import path from "path"; import { fileURLToPath } from "url"; import { promisify } from "util"; +import { COPILOT_CLI_VERSION } from "../../nodejs/src/cliVersion.js"; +import { ensureRuntimeBundle } from "../../nodejs/src/runtimeArtifacts.js"; export const execFileAsync = promisify(execFile); @@ -45,59 +47,23 @@ export type SchemaWithSharedDefinitions = T }; // ── Schema paths ──────────────────────────────────────────────────────────── -const SDK_NODE_MODULES = path.join(REPO_ROOT, "nodejs/node_modules"); - /** - * Resolve a JSON schema shipped by the `@github/copilot` CLI package. - * - * The CLI package layout changed in 1.0.64-1: the umbrella `@github/copilot` - * package became a thin loader and its bundled assets (including the JSON - * schemas) moved into the platform-specific packages installed as optional - * dependencies, e.g. `@github/copilot-linux-x64` or `@github/copilot-win32-x64`. - * - * To support both layouts we look in the umbrella package first (older - * versions) and then in whichever platform package was installed for the - * current host. + * Resolve a JSON schema from the pinned Copilot CLI GitHub Release. */ -async function resolveCopilotSchemaPath(nodeModulesDir: string, fileName: string): Promise { - const candidates = [path.join(nodeModulesDir, "@github/copilot/schemas", fileName)]; - - const githubScopeDir = path.join(nodeModulesDir, "@github"); - try { - for (const entry of await fs.readdir(githubScopeDir)) { - if (entry.startsWith("copilot-")) { - candidates.push(path.join(githubScopeDir, entry, "schemas", fileName)); - } - } - } catch (err) { - const code = (err as NodeJS.ErrnoException).code; - if (code !== "ENOENT" && code !== "ENOTDIR") { - throw err; - } - // @github scope directory may not exist yet; fall through to the error below. - } - - for (const candidate of candidates) { - try { - await fs.access(candidate); - return candidate; - } catch { - // Try the next candidate. - } - } - - throw new Error( - `${fileName} not found under ${githubScopeDir}. Run 'npm ci' in nodejs/ first.` - ); +async function resolveCopilotSchemaPath(fileName: string): Promise { + const runtimePath = await ensureRuntimeBundle(COPILOT_CLI_VERSION); + const schemaPath = path.join(path.dirname(runtimePath), "schemas", fileName); + await fs.access(schemaPath); + return schemaPath; } export async function getSessionEventsSchemaPath(): Promise { - return resolveCopilotSchemaPath(SDK_NODE_MODULES, "session-events.schema.json"); + return resolveCopilotSchemaPath("session-events.schema.json"); } export async function getApiSchemaPath(cliArg?: string): Promise { if (cliArg) return cliArg; - return resolveCopilotSchemaPath(SDK_NODE_MODULES, "api.schema.json"); + return resolveCopilotSchemaPath("api.schema.json"); } // ── Brand casing normalization ────────────────────────────────────────────── diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 288ec1db3c..2488aa2c01 100644 --- a/test/harness/package-lock.json +++ b/test/harness/package-lock.json @@ -9,7 +9,6 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "@github/copilot": "^1.0.83-3", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", @@ -26,7 +25,7 @@ }, "node_modules/@emnapi/core": { "version": "1.10.0", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "integrity": "sha1-OAzMjyQS6iLR2XLff47iOjucdGc=", "dev": true, "license": "MIT", "optional": true, @@ -37,7 +36,7 @@ }, "node_modules/@emnapi/runtime": { "version": "1.10.0", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "integrity": "sha1-SyYMDTU0IE6YxhELjbGph9JuyHw=", "dev": true, "license": "MIT", "optional": true, @@ -47,7 +46,7 @@ }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "integrity": "sha1-KP7SGhuhznl8RKBwq8lNQvOuhUg=", "dev": true, "license": "MIT", "optional": true, @@ -57,7 +56,7 @@ }, "node_modules/@esbuild/aix-ppc64": { "version": "0.28.1", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "integrity": "sha1-egGo0uwvuy2seK2tCbD6eB5Agr4=", "cpu": [ "ppc64" ], @@ -73,7 +72,7 @@ }, "node_modules/@esbuild/android-arm": { "version": "0.28.1", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "integrity": "sha1-cEvSl95tdi3lTqu+r79V9nVqvi8=", "cpu": [ "arm" ], @@ -89,7 +88,7 @@ }, "node_modules/@esbuild/android-arm64": { "version": "0.28.1", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "integrity": "sha1-tUCifRTkr9BYSWpNvsTT9BTbEQo=", "cpu": [ "arm64" ], @@ -105,7 +104,7 @@ }, "node_modules/@esbuild/android-x64": { "version": "0.28.1", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "integrity": "sha1-0csWbTSw+/D+irRgpVlPJKN4cB4=", "cpu": [ "x64" ], @@ -121,7 +120,6 @@ }, "node_modules/@esbuild/darwin-arm64": { "version": "0.28.1", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -137,7 +135,7 @@ }, "node_modules/@esbuild/darwin-x64": { "version": "0.28.1", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "integrity": "sha1-ZVVqQyoeTXIDLYIYwZMvzKGkl3I=", "cpu": [ "x64" ], @@ -153,7 +151,7 @@ }, "node_modules/@esbuild/freebsd-arm64": { "version": "0.28.1", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "integrity": "sha1-LmHgWS+QMNfj2uGO4l68U1kYrvY=", "cpu": [ "arm64" ], @@ -169,7 +167,7 @@ }, "node_modules/@esbuild/freebsd-x64": { "version": "0.28.1", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "integrity": "sha1-yV7CiZWe+AecTcqBeh4sS+Zrm9M=", "cpu": [ "x64" ], @@ -185,7 +183,7 @@ }, "node_modules/@esbuild/linux-arm": { "version": "0.28.1", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "integrity": "sha1-wJoPZ5F1kqwN6JKpvk04FN69Kmw=", "cpu": [ "arm" ], @@ -201,7 +199,7 @@ }, "node_modules/@esbuild/linux-arm64": { "version": "0.28.1", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "integrity": "sha1-QLIhdd2gYYLz7oFBGGxf8wTEpxc=", "cpu": [ "arm64" ], @@ -217,7 +215,7 @@ }, "node_modules/@esbuild/linux-ia32": { "version": "0.28.1", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "integrity": "sha1-pYD5xnZ5eDOJHlGfx6EzfIr9jbM=", "cpu": [ "ia32" ], @@ -233,7 +231,7 @@ }, "node_modules/@esbuild/linux-loong64": { "version": "0.28.1", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "integrity": "sha1-RkUs8yHcf56Rwvp4Cla7Vuec1os=", "cpu": [ "loong64" ], @@ -249,7 +247,7 @@ }, "node_modules/@esbuild/linux-mips64el": { "version": "0.28.1", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "integrity": "sha1-QhGzGE3WYI9T3LIuOfXTTuCIUsg=", "cpu": [ "mips64el" ], @@ -265,7 +263,7 @@ }, "node_modules/@esbuild/linux-ppc64": { "version": "0.28.1", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "integrity": "sha1-aXhXwqYcubC2u2ZS5AwdxeHKjl0=", "cpu": [ "ppc64" ], @@ -281,7 +279,7 @@ }, "node_modules/@esbuild/linux-riscv64": { "version": "0.28.1", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "integrity": "sha1-0ZKUPrFGpArExkl9DPe+NbmGvwg=", "cpu": [ "riscv64" ], @@ -297,7 +295,7 @@ }, "node_modules/@esbuild/linux-s390x": { "version": "0.28.1", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "integrity": "sha1-rOoDVtoODrwI+Xz3ucLkAeHmSNw=", "cpu": [ "s390x" ], @@ -313,7 +311,7 @@ }, "node_modules/@esbuild/linux-x64": { "version": "0.28.1", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "integrity": "sha1-bww84MtkxTS3DExF7LLBbTTjXf0=", "cpu": [ "x64" ], @@ -329,7 +327,7 @@ }, "node_modules/@esbuild/netbsd-arm64": { "version": "0.28.1", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "integrity": "sha1-i813B3oNzjN4tXT+2ybSolO3PTY=", "cpu": [ "arm64" ], @@ -345,7 +343,7 @@ }, "node_modules/@esbuild/netbsd-x64": { "version": "0.28.1", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "integrity": "sha1-5/sqAemcgwyU5mI82f77TI+1g0c=", "cpu": [ "x64" ], @@ -361,7 +359,7 @@ }, "node_modules/@esbuild/openbsd-arm64": { "version": "0.28.1", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "integrity": "sha1-xSkJNy24uG4sVeBaiUADO1Zgo7I=", "cpu": [ "arm64" ], @@ -377,7 +375,7 @@ }, "node_modules/@esbuild/openbsd-x64": { "version": "0.28.1", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "integrity": "sha1-xCe5vlpkwmL/mn63C1+7qt9EbGw=", "cpu": [ "x64" ], @@ -393,7 +391,7 @@ }, "node_modules/@esbuild/openharmony-arm64": { "version": "0.28.1", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "integrity": "sha1-3JsUe6yi5sSzyFVxdB70hgpIkJc=", "cpu": [ "arm64" ], @@ -409,7 +407,7 @@ }, "node_modules/@esbuild/sunos-x64": { "version": "0.28.1", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "integrity": "sha1-zoZtEt8TwV5MmfBzo9Rm9uBkmzo=", "cpu": [ "x64" ], @@ -425,7 +423,7 @@ }, "node_modules/@esbuild/win32-arm64": { "version": "0.28.1", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "integrity": "sha1-dGjjaS0B1inVlB5dg4F7uA+eObQ=", "cpu": [ "arm64" ], @@ -441,7 +439,7 @@ }, "node_modules/@esbuild/win32-ia32": { "version": "0.28.1", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "integrity": "sha1-pbwAY/sryrbQ7WPyoVN5WLwmnsY=", "cpu": [ "ia32" ], @@ -457,7 +455,7 @@ }, "node_modules/@esbuild/win32-x64": { "version": "0.28.1", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "integrity": "sha1-EAZO5E9DR7kMmgK0Rrv4CpFjKxI=", "cpu": [ "x64" ], @@ -471,159 +469,8 @@ "node": ">=18" } }, - "node_modules/@github/copilot": { - "version": "1.0.83-3", - "integrity": "sha512-4+5wVGC2IvLYog3kdfmY6rg+NIGJesjENVrTONZr6uic6zR+8Ksgy+sCWO86n6AARs09MXktAZNHbbrXz+hl7A==", - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "dependencies": { - "detect-libc": "^2.1.2" - }, - "bin": { - "copilot": "npm-loader.js" - }, - "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.83-3", - "@github/copilot-darwin-x64": "1.0.83-3", - "@github/copilot-linux-arm64": "1.0.83-3", - "@github/copilot-linux-x64": "1.0.83-3", - "@github/copilot-linuxmusl-arm64": "1.0.83-3", - "@github/copilot-linuxmusl-x64": "1.0.83-3", - "@github/copilot-win32-arm64": "1.0.83-3", - "@github/copilot-win32-x64": "1.0.83-3" - } - }, - "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.83-3", - "integrity": "sha512-pNI71CRL2WR6Wp+Nm+HOsSBcUIOoybcSZtMHqm2zwJGdzAjzv6MU2lLOFFeqhBh8UNQGltD4KtPU/pr+t6t4Uw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "copilot-darwin-arm64": "copilot" - } - }, - "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.83-3", - "integrity": "sha512-9LKUwR7em12mz76s2ytWl/xkHyF13t0TLScAUcnNNj171/Kvg0lWNemwsmPK4m0QbbcmRUs7FyFFF79TmKBAmA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "darwin" - ], - "bin": { - "copilot-darwin-x64": "copilot" - } - }, - "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.83-3", - "integrity": "sha512-ouGA46t6flyUqUdutQL+94bnD+IwcCurR+5KS2JPHozbkeiR2BW4ed0ZZ5KT/6I13mTsjO9uu9LvWwfO5+PjiQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linux-arm64": "copilot" - } - }, - "node_modules/@github/copilot-linux-x64": { - "version": "1.0.83-3", - "integrity": "sha512-AiAf2yVrnP+Dw0M8RpacpOoK89sMFizPMuQfFPxAJUWS9hIw5mq4o4invKtUfiz0F7cjxaDJZz1JLUSuGEAQhw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linux-x64": "copilot" - } - }, - "node_modules/@github/copilot-linuxmusl-arm64": { - "version": "1.0.83-3", - "integrity": "sha512-TmXPXi65OX/Wfd7JnU8RZjZxzc5kFZU/3Gvr/N1Y+G+cJJyB0NBmWk2PP+yD381ASYOOgeNgWitlYMw8tU7Ddg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linuxmusl-arm64": "copilot" - } - }, - "node_modules/@github/copilot-linuxmusl-x64": { - "version": "1.0.83-3", - "integrity": "sha512-Zlbya4anjkbI8LcbenwuBhxUUeVIrGJqeYh/6JUWwnisOiuuimqQ4zb2UU2pX3vxE03f2PbTcueOo/GkF6AS8A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "linux" - ], - "bin": { - "copilot-linuxmusl-x64": "copilot" - } - }, - "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.83-3", - "integrity": "sha512-zNmVj3ZDmI3dFmBigfEMzEvMxyjBjL5+nTVxrt9fvTA+29jI0C6A+cdCqrad3fJ1RKgn2RbsZyhnpyViPNhNDw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ], - "bin": { - "copilot-win32-arm64": "copilot.exe" - } - }, - "node_modules/@github/copilot-win32-x64": { - "version": "1.0.83-3", - "integrity": "sha512-pbw739Jdwjr4ovsjwpMI1hguZyOPwTy/fdVnrgBv1nazXxIFrwE3tq0FgzF0NnNcs4r5LXdbIBjKQP+HKFZagA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "SEE LICENSE IN LICENSE.md", - "optional": true, - "os": [ - "win32" - ], - "bin": { - "copilot-win32-x64": "copilot.exe" - } - }, "node_modules/@hono/node-server": { "version": "1.19.14", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "dev": true, "license": "MIT", "engines": { @@ -635,13 +482,11 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.26.0", - "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", "dev": true, "license": "MIT", "dependencies": { @@ -680,26 +525,28 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.2.3", + "integrity": "sha1-l+PUXXQk3F2h1OMvO/OykvbBtEw=", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" }, "funding": { "type": "github", "url": "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/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "node_modules/@oxc-project/types": { "version": "0.133.0", - "integrity": "sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==", "dev": true, "license": "MIT", "funding": { @@ -708,7 +555,7 @@ }, "node_modules/@rolldown/binding-android-arm64": { "version": "1.0.3", - "integrity": "sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==", + "integrity": "sha1-VM6Pg4IhP0oxSgwve6g/gf/q5ZI=", "cpu": [ "arm64" ], @@ -724,7 +571,6 @@ }, "node_modules/@rolldown/binding-darwin-arm64": { "version": "1.0.3", - "integrity": "sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==", "cpu": [ "arm64" ], @@ -740,7 +586,7 @@ }, "node_modules/@rolldown/binding-darwin-x64": { "version": "1.0.3", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "integrity": "sha1-U/V94fWZ7PHbE4I8/IjBj7gJVK0=", "cpu": [ "x64" ], @@ -756,7 +602,7 @@ }, "node_modules/@rolldown/binding-freebsd-x64": { "version": "1.0.3", - "integrity": "sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==", + "integrity": "sha1-bz/dobeuqsnSaKUmgEtPuW5ONfE=", "cpu": [ "x64" ], @@ -772,7 +618,7 @@ }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { "version": "1.0.3", - "integrity": "sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==", + "integrity": "sha1-2HpFS/WFzJZ2hJN36R1uN1KXMm8=", "cpu": [ "arm" ], @@ -788,7 +634,7 @@ }, "node_modules/@rolldown/binding-linux-arm64-gnu": { "version": "1.0.3", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "integrity": "sha1-QZ/Wv2Es80jxBSjLzZTrq5YH2NE=", "cpu": [ "arm64" ], @@ -804,7 +650,7 @@ }, "node_modules/@rolldown/binding-linux-arm64-musl": { "version": "1.0.3", - "integrity": "sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==", + "integrity": "sha1-/MaRhpa7doRId+HkkwoY/Q03QGk=", "cpu": [ "arm64" ], @@ -820,7 +666,7 @@ }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { "version": "1.0.3", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "integrity": "sha1-Mq7LfI2uXU8qjN5XoFjshpkVQvg=", "cpu": [ "ppc64" ], @@ -836,7 +682,7 @@ }, "node_modules/@rolldown/binding-linux-s390x-gnu": { "version": "1.0.3", - "integrity": "sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==", + "integrity": "sha1-vtk0bqgea7i5PPEfXYi3fbiQt2M=", "cpu": [ "s390x" ], @@ -852,7 +698,7 @@ }, "node_modules/@rolldown/binding-linux-x64-gnu": { "version": "1.0.3", - "integrity": "sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==", + "integrity": "sha1-ZMLSb3Xf/ZtaH5dVegCudyUMjLc=", "cpu": [ "x64" ], @@ -868,7 +714,7 @@ }, "node_modules/@rolldown/binding-linux-x64-musl": { "version": "1.0.3", - "integrity": "sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==", + "integrity": "sha1-WkUTLopHZZ7qrztUDClUqXyGD/M=", "cpu": [ "x64" ], @@ -884,7 +730,7 @@ }, "node_modules/@rolldown/binding-openharmony-arm64": { "version": "1.0.3", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "integrity": "sha1-KQUTBoxV6EnchFejKv7h17Csswk=", "cpu": [ "arm64" ], @@ -900,7 +746,7 @@ }, "node_modules/@rolldown/binding-wasm32-wasi": { "version": "1.0.3", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "integrity": "sha1-PZly2/GpU9PHr6pKDyDvKy458xs=", "cpu": [ "wasm32" ], @@ -918,7 +764,7 @@ }, "node_modules/@rolldown/binding-win32-arm64-msvc": { "version": "1.0.3", - "integrity": "sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==", + "integrity": "sha1-oASrYHoW1vA7y1VXKP+IivdXc60=", "cpu": [ "arm64" ], @@ -934,7 +780,7 @@ }, "node_modules/@rolldown/binding-win32-x64-msvc": { "version": "1.0.3", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "integrity": "sha1-4qJbNGkaHMihIJ195wkGMCbdDNs=", "cpu": [ "x64" ], @@ -950,19 +796,17 @@ }, "node_modules/@rolldown/pluginutils": { "version": "1.0.1", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, "node_modules/@standard-schema/spec": { "version": "1.1.0", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", "dev": true, "license": "MIT" }, "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "version": "0.10.3", + "integrity": "sha1-AVy6np3UfOFNA9KoxdVHv7FpZl0=", "dev": true, "license": "MIT", "optional": true, @@ -972,7 +816,6 @@ }, "node_modules/@types/chai": { "version": "5.2.3", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { @@ -982,19 +825,16 @@ }, "node_modules/@types/deep-eql": { "version": "4.0.2", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.9", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "25.3.3", - "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1003,7 +843,6 @@ }, "node_modules/@types/node-forge": { "version": "1.3.14", - "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", "dev": true, "license": "MIT", "dependencies": { @@ -1012,7 +851,6 @@ }, "node_modules/@vitest/expect": { "version": "4.1.8", - "integrity": "sha512-h3nDO677RDLEGlBxyQ5CW8RlMThSKSRLUePLOx09gNIWRL40edgA1GCZSZgf1W55MFAG6/Sw14KeaAnqv0NKdQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1029,7 +867,6 @@ }, "node_modules/@vitest/mocker": { "version": "4.1.8", - "integrity": "sha512-LEiN/xe4OSIbKe9HQIp5OC24agGD9J5CnmMgsLohVVoOPWL9a2sBoR6VBx43jQZb7Kr1l4RCuyCJzcAa0+dojw==", "dev": true, "license": "MIT", "dependencies": { @@ -1055,7 +892,6 @@ }, "node_modules/@vitest/pretty-format": { "version": "4.1.8", - "integrity": "sha512-9GasEBxpZ1VYIpqHf/0+YGg121uSNwCKOJqIrTwWP/TB7DmFCiaBpNl3aPZzoLWfWkuqhbH8vJIVobZkvdo2cA==", "dev": true, "license": "MIT", "dependencies": { @@ -1067,7 +903,6 @@ }, "node_modules/@vitest/runner": { "version": "4.1.8", - "integrity": "sha512-EmVxeBAfMJvycdjd6Hm+RbFBbA9fKvo0Kx37hNpBYoYeavH3RNsBXWDooR1mgD52dCrxIIuP7UotpfiwOikvcg==", "dev": true, "license": "MIT", "dependencies": { @@ -1080,7 +915,6 @@ }, "node_modules/@vitest/snapshot": { "version": "4.1.8", - "integrity": "sha512-acfZboRmAIf05DEKcBQy33VXojFJjtUdLyo7oOmV9kebb2xdU01UknNiPuPZoJZQyO7DF0gZdTGTpeAzET9QPQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1095,7 +929,6 @@ }, "node_modules/@vitest/spy": { "version": "4.1.8", - "integrity": "sha512-6EevtBp6OZOPF7bmz36HrGMeP3txgVSrgebWxHOafDXGkhIzfXK14f8KF6MuFfgXXUeHxmpD3BQxkV00/3s5mA==", "dev": true, "license": "MIT", "funding": { @@ -1104,7 +937,6 @@ }, "node_modules/@vitest/utils": { "version": "4.1.8", - "integrity": "sha512-uOJamYALNhfJ6iolExyQM40yIQwDqYnkKtQ5VCiSe17E33H0aQ/u+1GlRuz4LZBk6Mm3sg90G9hEbmEt37C1Zg==", "dev": true, "license": "MIT", "dependencies": { @@ -1118,7 +950,6 @@ }, "node_modules/accepts": { "version": "2.0.0", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "dev": true, "license": "MIT", "dependencies": { @@ -1131,7 +962,6 @@ }, "node_modules/ajv": { "version": "8.18.0", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -1147,7 +977,6 @@ }, "node_modules/ajv-formats": { "version": "3.0.1", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1164,7 +993,6 @@ }, "node_modules/assertion-error": { "version": "2.0.1", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { @@ -1173,7 +1001,6 @@ }, "node_modules/body-parser": { "version": "2.2.2", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "dev": true, "license": "MIT", "dependencies": { @@ -1197,7 +1024,6 @@ }, "node_modules/bytes": { "version": "3.1.2", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true, "license": "MIT", "engines": { @@ -1206,7 +1032,6 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1219,7 +1044,6 @@ }, "node_modules/call-bound": { "version": "1.0.4", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, "license": "MIT", "dependencies": { @@ -1235,7 +1059,6 @@ }, "node_modules/chai": { "version": "6.2.2", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", "engines": { @@ -1244,7 +1067,6 @@ }, "node_modules/content-disposition": { "version": "1.0.1", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "dev": true, "license": "MIT", "engines": { @@ -1257,7 +1079,6 @@ }, "node_modules/content-type": { "version": "1.0.5", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "dev": true, "license": "MIT", "engines": { @@ -1266,13 +1087,11 @@ }, "node_modules/convert-source-map": { "version": "2.0.0", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, "node_modules/cookie": { "version": "0.7.2", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, "license": "MIT", "engines": { @@ -1281,7 +1100,6 @@ }, "node_modules/cookie-signature": { "version": "1.2.2", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "dev": true, "license": "MIT", "engines": { @@ -1290,7 +1108,6 @@ }, "node_modules/cors": { "version": "2.8.6", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "dev": true, "license": "MIT", "dependencies": { @@ -1307,7 +1124,6 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { @@ -1321,7 +1137,6 @@ }, "node_modules/debug": { "version": "4.4.3", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -1338,7 +1153,6 @@ }, "node_modules/depd": { "version": "2.0.0", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "dev": true, "license": "MIT", "engines": { @@ -1347,7 +1161,6 @@ }, "node_modules/detect-libc": { "version": "2.1.2", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1356,7 +1169,6 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, "license": "MIT", "dependencies": { @@ -1370,13 +1182,11 @@ }, "node_modules/ee-first": { "version": "1.1.1", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "dev": true, "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "dev": true, "license": "MIT", "engines": { @@ -1385,7 +1195,6 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, "license": "MIT", "engines": { @@ -1394,7 +1203,6 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, "license": "MIT", "engines": { @@ -1403,13 +1211,11 @@ }, "node_modules/es-module-lexer": { "version": "2.1.0", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "dev": true, "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, "license": "MIT", "dependencies": { @@ -1421,7 +1227,6 @@ }, "node_modules/esbuild": { "version": "0.28.1", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1462,13 +1267,11 @@ }, "node_modules/escape-html": { "version": "1.0.3", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "dev": true, "license": "MIT" }, "node_modules/estree-walker": { "version": "3.0.3", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", "dev": true, "license": "MIT", "dependencies": { @@ -1477,7 +1280,6 @@ }, "node_modules/etag": { "version": "1.8.1", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "dev": true, "license": "MIT", "engines": { @@ -1486,7 +1288,6 @@ }, "node_modules/eventsource": { "version": "3.0.7", - "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", "dev": true, "license": "MIT", "dependencies": { @@ -1498,7 +1299,6 @@ }, "node_modules/eventsource-parser": { "version": "3.0.6", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", "dev": true, "license": "MIT", "engines": { @@ -1507,7 +1307,6 @@ }, "node_modules/expect-type": { "version": "1.3.0", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1516,7 +1315,6 @@ }, "node_modules/express": { "version": "5.2.1", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "dev": true, "license": "MIT", "dependencies": { @@ -1559,7 +1357,6 @@ }, "node_modules/express-rate-limit": { "version": "8.5.2", - "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", "dev": true, "license": "MIT", "dependencies": { @@ -1577,13 +1374,11 @@ }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, "license": "MIT" }, "node_modules/fast-uri": { "version": "3.1.5", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "dev": true, "funding": [ { @@ -1599,7 +1394,6 @@ }, "node_modules/fdir": { "version": "6.5.0", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", "engines": { @@ -1616,7 +1410,6 @@ }, "node_modules/finalhandler": { "version": "2.1.1", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "dev": true, "license": "MIT", "dependencies": { @@ -1637,7 +1430,6 @@ }, "node_modules/forwarded": { "version": "0.2.0", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "dev": true, "license": "MIT", "engines": { @@ -1646,7 +1438,6 @@ }, "node_modules/fresh": { "version": "2.0.0", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "dev": true, "license": "MIT", "engines": { @@ -1655,9 +1446,7 @@ }, "node_modules/fsevents": { "version": "2.3.3", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -1669,7 +1458,6 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, "license": "MIT", "funding": { @@ -1678,7 +1466,6 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1702,7 +1489,6 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, "license": "MIT", "dependencies": { @@ -1715,7 +1501,6 @@ }, "node_modules/gopd": { "version": "1.2.0", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, "license": "MIT", "engines": { @@ -1727,7 +1512,6 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, "license": "MIT", "engines": { @@ -1739,7 +1523,6 @@ }, "node_modules/hasown": { "version": "2.0.2", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1751,7 +1534,6 @@ }, "node_modules/hono": { "version": "4.13.1", - "integrity": "sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==", "dev": true, "license": "MIT", "engines": { @@ -1760,7 +1542,6 @@ }, "node_modules/http-errors": { "version": "2.0.1", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1780,7 +1561,6 @@ }, "node_modules/iconv-lite": { "version": "0.7.2", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "dev": true, "license": "MIT", "dependencies": { @@ -1796,13 +1576,11 @@ }, "node_modules/inherits": { "version": "2.0.4", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, "license": "ISC" }, "node_modules/ip-address": { "version": "10.4.0", - "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==", "dev": true, "license": "MIT", "engines": { @@ -1811,7 +1589,6 @@ }, "node_modules/ipaddr.js": { "version": "1.9.1", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "dev": true, "license": "MIT", "engines": { @@ -1820,19 +1597,16 @@ }, "node_modules/is-promise": { "version": "4.0.0", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "dev": true, "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, "node_modules/jose": { "version": "6.1.3", - "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", "dev": true, "license": "MIT", "funding": { @@ -1841,19 +1615,16 @@ }, "node_modules/json-schema-traverse": { "version": "1.0.0", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, "license": "MIT" }, "node_modules/json-schema-typed": { "version": "8.0.2", - "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", "dev": true, "license": "BSD-2-Clause" }, "node_modules/lightningcss": { "version": "1.32.0", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -1882,7 +1653,7 @@ }, "node_modules/lightningcss-android-arm64": { "version": "1.32.0", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "integrity": "sha1-8DOIURbf79nG9UeHUj41FLYeGWg=", "cpu": [ "arm64" ], @@ -1902,7 +1673,6 @@ }, "node_modules/lightningcss-darwin-arm64": { "version": "1.32.0", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -1922,7 +1692,7 @@ }, "node_modules/lightningcss-darwin-x64": { "version": "1.32.0", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "integrity": "sha1-NfPpczLRMLnKGB4RtWje1q68bV4=", "cpu": [ "x64" ], @@ -1942,7 +1712,7 @@ }, "node_modules/lightningcss-freebsd-x64": { "version": "1.32.0", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "integrity": "sha1-l3enZHK2Ttb/lDQq1kx7r9eUpXU=", "cpu": [ "x64" ], @@ -1962,7 +1732,7 @@ }, "node_modules/lightningcss-linux-arm-gnueabihf": { "version": "1.32.0", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "integrity": "sha1-E65lLhq3O5E117faFy9mbEEK1T0=", "cpu": [ "arm" ], @@ -1982,7 +1752,7 @@ }, "node_modules/lightningcss-linux-arm64-gnu": { "version": "1.32.0", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "integrity": "sha1-QXhYeVqUWS9oASOhsfnaig4e8zU=", "cpu": [ "arm64" ], @@ -2002,7 +1772,7 @@ }, "node_modules/lightningcss-linux-arm64-musl": { "version": "1.32.0", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "integrity": "sha1-a+NmkugQtxgECAL9gJYjz/5zITM=", "cpu": [ "arm64" ], @@ -2022,7 +1792,7 @@ }, "node_modules/lightningcss-linux-x64-gnu": { "version": "1.32.0", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "integrity": "sha1-C3gDr06yHP043Tn+Kru1PH3QkfY=", "cpu": [ "x64" ], @@ -2042,7 +1812,7 @@ }, "node_modules/lightningcss-linux-x64-musl": { "version": "1.32.0", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "integrity": "sha1-iNyLqGXd3bGsXvBLDxYYBEGMFjs=", "cpu": [ "x64" ], @@ -2062,7 +1832,7 @@ }, "node_modules/lightningcss-win32-arm64-msvc": { "version": "1.32.0", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "integrity": "sha1-TzC6P6XpJfW3n5RejMDRdsOxqzg=", "cpu": [ "arm64" ], @@ -2082,7 +1852,7 @@ }, "node_modules/lightningcss-win32-x64-msvc": { "version": "1.32.0", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "integrity": "sha1-FBqlYFZFBkkokCu0rwRfp9n0Igo=", "cpu": [ "x64" ], @@ -2102,7 +1872,6 @@ }, "node_modules/magic-string": { "version": "0.30.21", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2111,7 +1880,6 @@ }, "node_modules/math-intrinsics": { "version": "1.1.0", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, "license": "MIT", "engines": { @@ -2120,7 +1888,6 @@ }, "node_modules/media-typer": { "version": "1.1.0", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "dev": true, "license": "MIT", "engines": { @@ -2129,7 +1896,6 @@ }, "node_modules/merge-descriptors": { "version": "2.0.0", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "dev": true, "license": "MIT", "engines": { @@ -2141,7 +1907,6 @@ }, "node_modules/mime-db": { "version": "1.54.0", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "dev": true, "license": "MIT", "engines": { @@ -2150,7 +1915,6 @@ }, "node_modules/mime-types": { "version": "3.0.2", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "dev": true, "license": "MIT", "dependencies": { @@ -2166,13 +1930,11 @@ }, "node_modules/ms": { "version": "2.1.3", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true, "license": "MIT" }, "node_modules/nanoid": { "version": "3.3.17", - "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", "dev": true, "funding": [ { @@ -2190,7 +1952,6 @@ }, "node_modules/negotiator": { "version": "1.0.0", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "dev": true, "license": "MIT", "engines": { @@ -2199,7 +1960,6 @@ }, "node_modules/node-forge": { "version": "1.4.0", - "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==", "dev": true, "license": "(BSD-3-Clause OR GPL-2.0)", "engines": { @@ -2208,7 +1968,6 @@ }, "node_modules/object-assign": { "version": "4.1.1", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, "license": "MIT", "engines": { @@ -2217,7 +1976,6 @@ }, "node_modules/object-inspect": { "version": "1.13.4", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, "license": "MIT", "engines": { @@ -2229,7 +1987,6 @@ }, "node_modules/obug": { "version": "2.1.1", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", "dev": true, "funding": [ "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/sponsors/sxzz", @@ -2239,7 +1996,6 @@ }, "node_modules/on-finished": { "version": "2.4.1", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dev": true, "license": "MIT", "dependencies": { @@ -2251,7 +2007,6 @@ }, "node_modules/once": { "version": "1.4.0", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "license": "ISC", "dependencies": { @@ -2260,7 +2015,6 @@ }, "node_modules/openai": { "version": "6.17.0", - "integrity": "sha512-NHRpPEUPzAvFOAFs9+9pC6+HCw/iWsYsKCMPXH5Kw7BpMxqd8g/A07/1o7Gx2TWtCnzevVRyKMRFqyiHyAlqcA==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2281,7 +2035,6 @@ }, "node_modules/parseurl": { "version": "1.3.3", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true, "license": "MIT", "engines": { @@ -2290,7 +2043,6 @@ }, "node_modules/path-key": { "version": "3.1.1", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "license": "MIT", "engines": { @@ -2299,7 +2051,6 @@ }, "node_modules/path-to-regexp": { "version": "8.4.2", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "dev": true, "license": "MIT", "funding": { @@ -2309,19 +2060,16 @@ }, "node_modules/pathe": { "version": "2.0.3", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.4", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -2333,7 +2081,6 @@ }, "node_modules/pkce-challenge": { "version": "5.0.1", - "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", "dev": true, "license": "MIT", "engines": { @@ -2342,7 +2089,6 @@ }, "node_modules/postcss": { "version": "8.5.25", - "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", "dev": true, "funding": [ { @@ -2370,7 +2116,6 @@ }, "node_modules/proxy-addr": { "version": "2.0.7", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "dev": true, "license": "MIT", "dependencies": { @@ -2383,7 +2128,6 @@ }, "node_modules/qs": { "version": "6.15.2", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2398,7 +2142,6 @@ }, "node_modules/range-parser": { "version": "1.2.1", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "dev": true, "license": "MIT", "engines": { @@ -2407,7 +2150,6 @@ }, "node_modules/raw-body": { "version": "3.0.2", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "dev": true, "license": "MIT", "dependencies": { @@ -2422,7 +2164,6 @@ }, "node_modules/require-from-string": { "version": "2.0.2", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, "license": "MIT", "engines": { @@ -2431,7 +2172,6 @@ }, "node_modules/rolldown": { "version": "1.0.3", - "integrity": "sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==", "dev": true, "license": "MIT", "dependencies": { @@ -2464,7 +2204,6 @@ }, "node_modules/router": { "version": "2.2.0", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2480,13 +2219,11 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "license": "MIT" }, "node_modules/send": { "version": "1.2.1", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2512,7 +2249,6 @@ }, "node_modules/serve-static": { "version": "2.2.1", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "dev": true, "license": "MIT", "dependencies": { @@ -2531,13 +2267,11 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "dev": true, "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "license": "MIT", "dependencies": { @@ -2549,7 +2283,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "license": "MIT", "engines": { @@ -2558,7 +2291,6 @@ }, "node_modules/side-channel": { "version": "1.1.0", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, "license": "MIT", "dependencies": { @@ -2577,7 +2309,6 @@ }, "node_modules/side-channel-list": { "version": "1.0.0", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, "license": "MIT", "dependencies": { @@ -2593,7 +2324,6 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, "license": "MIT", "dependencies": { @@ -2611,7 +2341,6 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, "license": "MIT", "dependencies": { @@ -2630,13 +2359,11 @@ }, "node_modules/siginfo": { "version": "2.0.0", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, "node_modules/source-map-js": { "version": "1.2.1", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -2645,13 +2372,11 @@ }, "node_modules/stackback": { "version": "0.0.2", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, "node_modules/statuses": { "version": "2.0.2", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "dev": true, "license": "MIT", "engines": { @@ -2660,19 +2385,16 @@ }, "node_modules/std-env": { "version": "4.1.0", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", "dev": true, "license": "MIT" }, "node_modules/tinybench": { "version": "2.9.0", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, "node_modules/tinyexec": { "version": "1.0.2", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", "dev": true, "license": "MIT", "engines": { @@ -2681,7 +2403,6 @@ }, "node_modules/tinyglobby": { "version": "0.2.17", - "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -2697,7 +2418,6 @@ }, "node_modules/tinyrainbow": { "version": "3.1.0", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", "dev": true, "license": "MIT", "engines": { @@ -2706,7 +2426,6 @@ }, "node_modules/toidentifier": { "version": "1.0.1", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "dev": true, "license": "MIT", "engines": { @@ -2715,14 +2434,13 @@ }, "node_modules/tslib": { "version": "2.8.1", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "integrity": "sha1-YS7+TtI11Wfoq6Xypfq3AoCt6D8=", "dev": true, "license": "0BSD", "optional": true }, "node_modules/tsx": { "version": "4.22.4", - "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", "dev": true, "license": "MIT", "dependencies": { @@ -2740,7 +2458,6 @@ }, "node_modules/type-is": { "version": "2.0.1", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "dev": true, "license": "MIT", "dependencies": { @@ -2754,7 +2471,6 @@ }, "node_modules/typescript": { "version": "5.9.3", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2767,13 +2483,11 @@ }, "node_modules/undici-types": { "version": "7.18.2", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "dev": true, "license": "MIT" }, "node_modules/unpipe": { "version": "1.0.0", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "dev": true, "license": "MIT", "engines": { @@ -2782,7 +2496,6 @@ }, "node_modules/vary": { "version": "1.1.2", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "dev": true, "license": "MIT", "engines": { @@ -2791,7 +2504,6 @@ }, "node_modules/vite": { "version": "8.0.16", - "integrity": "sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==", "dev": true, "license": "MIT", "dependencies": { @@ -2868,7 +2580,6 @@ }, "node_modules/vitest": { "version": "4.1.8", - "integrity": "sha512-flY6ScbCIt9HThs+C5HS7jvGOB560DJtk/Z15IQROTA6zEy49Nh8T/dofWTQL+n3vswqn87sbJNiuqw1SDp5Ig==", "dev": true, "license": "MIT", "dependencies": { @@ -2957,7 +2668,6 @@ }, "node_modules/which": { "version": "2.0.2", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -2972,7 +2682,6 @@ }, "node_modules/why-is-node-running": { "version": "2.3.0", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { @@ -2988,13 +2697,11 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, "license": "ISC" }, "node_modules/yaml": { "version": "2.9.0", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "dev": true, "license": "ISC", "bin": { @@ -3009,7 +2716,6 @@ }, "node_modules/zod": { "version": "4.3.6", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", "funding": { @@ -3018,7 +2724,6 @@ }, "node_modules/zod-to-json-schema": { "version": "3.25.1", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", "dev": true, "license": "ISC", "peerDependencies": { diff --git a/test/harness/package.json b/test/harness/package.json index 7b04900614..9b37dfb9d0 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -14,7 +14,6 @@ "node": "^20.19.0 || >=22.12.0" }, "devDependencies": { - "@github/copilot": "^1.0.83-3", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", From 6ba8a5fc709803bce56a392f665dccd0c434e6d8 Mon Sep 17 00:00:00 2001 From: Devraj Mehta Date: Tue, 1 Sep 2026 14:25:51 -0400 Subject: [PATCH 02/30] Address CLI release runtime review findings Prepare the pinned runtime from Python, .NET, Go, and Rust E2E harnesses; preserve internal npm canaries; and centralize trusted release hashes for Node, Java, and Rust packaging. Update Rust pin validation and bundled runtime contents accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3043824d-becf-4b5d-b62b-4754511894f7 --- .github/workflows/required-checks.yml | 10 +- .github/workflows/sdk-canary.yml | 23 ++- .../workflows/update-copilot-dependency.yml | 9 +- dotnet/test/Harness/E2ETestContext.cs | 59 +++----- go/internal/e2e/testharness/context.go | 22 +-- go/test.sh | 9 +- java/copilot-native/scripts/fetch-native.mjs | 33 ++-- .../scripts/fetch-native.test.mjs | 8 +- nodejs/README.md | 3 +- nodejs/copilot-cli.json | 22 +++ nodejs/scripts/set-cli-version.js | 78 +++++++--- nodejs/src/cliVersion.ts | 2 + nodejs/src/runtimeArtifacts.ts | 22 ++- nodejs/test/runtimeArtifacts.test.ts | 55 ++++++- python/e2e/testharness/context.py | 86 +++-------- python/test_e2e_harness_cli_path.py | 143 +++--------------- rust/README.md | 6 +- rust/build/in_process.rs | 93 ++++++------ rust/build/out_of_process.rs | 75 +++------ rust/scripts/snapshot-bundled-cli-version.sh | 24 ++- .../snapshot-bundled-in-process-version.sh | 18 +-- rust/tests/cli_resolution_test.rs | 35 ++--- rust/tests/e2e/support.rs | 28 ++-- 23 files changed, 390 insertions(+), 473 deletions(-) create mode 100644 nodejs/copilot-cli.json diff --git a/.github/workflows/required-checks.yml b/.github/workflows/required-checks.yml index f4083dd307..33848d53a9 100644 --- a/.github/workflows/required-checks.yml +++ b/.github/workflows/required-checks.yml @@ -44,35 +44,35 @@ jobs: - '!**/.editorconfig' - '!**/*.{png,jpg,jpeg,gif,svg}' python: - - '{python/**,test/**,nodejs/package.json,.github/workflows/python-sdk-tests.yml}' + - '{python/**,test/**,nodejs/package.json,nodejs/copilot-cli.json,.github/workflows/python-sdk-tests.yml}' - '!**/*.md' - '!**/LICENSE*' - '!**/.gitignore' - '!**/.editorconfig' - '!**/*.{png,jpg,jpeg,gif,svg}' go: - - '{go/**,test/**,nodejs/package.json,.github/workflows/go-sdk-tests.yml,.github/actions/setup-copilot/**}' + - '{go/**,test/**,nodejs/package.json,nodejs/copilot-cli.json,.github/workflows/go-sdk-tests.yml,.github/actions/setup-copilot/**}' - '!**/*.md' - '!**/LICENSE*' - '!**/.gitignore' - '!**/.editorconfig' - '!**/*.{png,jpg,jpeg,gif,svg}' dotnet: - - '{dotnet/**,test/**,nodejs/package.json,.github/workflows/dotnet-sdk-tests.yml}' + - '{dotnet/**,test/**,nodejs/package.json,nodejs/copilot-cli.json,.github/workflows/dotnet-sdk-tests.yml}' - '!**/*.md' - '!**/LICENSE*' - '!**/.gitignore' - '!**/.editorconfig' - '!**/*.{png,jpg,jpeg,gif,svg}' java: - - '{java/**,test/**,.github/workflows/java-sdk-tests.yml,.github/actions/setup-copilot/**,.github/actions/java-test-report/**}' + - '{java/**,test/**,nodejs/copilot-cli.json,.github/workflows/java-sdk-tests.yml,.github/actions/setup-copilot/**,.github/actions/java-test-report/**}' - '!**/*.md' - '!**/LICENSE*' - '!**/.gitignore' - '!**/.editorconfig' - '!**/*.{png,jpg,jpeg,gif,svg}' rust: - - '{rust/**,test/**,nodejs/package.json,.github/workflows/rust-sdk-tests.yml,.github/actions/setup-copilot/**}' + - '{rust/**,test/**,nodejs/package.json,nodejs/copilot-cli.json,.github/workflows/rust-sdk-tests.yml,.github/actions/setup-copilot/**}' - '!**/*.md' - '!**/LICENSE*' - '!**/.gitignore' diff --git a/.github/workflows/sdk-canary.yml b/.github/workflows/sdk-canary.yml index a05bb9bcfa..894dbc1c74 100644 --- a/.github/workflows/sdk-canary.yml +++ b/.github/workflows/sdk-canary.yml @@ -165,7 +165,7 @@ jobs: tenant-id: "${{ vars.CPD_ID_TENANT_ID }}" allow-no-subscriptions: true - # Route ONLY @github/* (the runtime + its 8 platform packages) to the + # Route ONLY @github/* (the runtime + its platform packages) to the # internal feed via a scoped registry. All other deps (e.g. detect-libc) # still resolve from public npm. A global --registry would break because # detect-libc is not on the feed. @@ -189,9 +189,15 @@ jobs: - name: Override runtime version run: | set -euo pipefail - echo "Pinning github/copilot-cli release ${RUNTIME_VERSION} (source: ${RUNTIME_SOURCE})" - node scripts/set-cli-version.js "$RUNTIME_VERSION" - npm install --ignore-scripts + if [ "$RUNTIME_SOURCE" = "internal" ]; then + echo "Installing internal @github/copilot@${RUNTIME_VERSION}" + npm install "@github/copilot@${RUNTIME_VERSION}" --save-exact --ignore-scripts + node scripts/set-cli-version.js "$RUNTIME_VERSION" --npm-package + else + echo "Pinning github/copilot-cli release ${RUNTIME_VERSION}" + node scripts/set-cli-version.js "$RUNTIME_VERSION" + npm install --ignore-scripts + fi - name: Verify release runtime run: | @@ -239,6 +245,7 @@ jobs: id-token: write env: RUNTIME_VERSION: ${{ needs.resolve.outputs.RUNTIME_VERSION }} + RUNTIME_SOURCE: ${{ needs.resolve.outputs.RUNTIME_SOURCE }} defaults: run: shell: bash @@ -306,8 +313,12 @@ jobs: run: | set -euo pipefail npm version "$SDK_VERSION" --no-git-tag-version --allow-same-version - node scripts/set-cli-version.js "$RUNTIME_VERSION" - npm install --package-lock-only --ignore-scripts + if [ "$RUNTIME_SOURCE" = "internal" ]; then + npm pkg set "dependencies.@github/copilot=$RUNTIME_VERSION" + node scripts/set-cli-version.js "$RUNTIME_VERSION" --npm-package + else + node scripts/set-cli-version.js "$RUNTIME_VERSION" + fi echo "Pinned github/copilot-cli release to $(npm pkg get copilotCliVersion)" - name: Build SDK diff --git a/.github/workflows/update-copilot-dependency.yml b/.github/workflows/update-copilot-dependency.yml index 8dc550edbe..43a6c618b2 100644 --- a/.github/workflows/update-copilot-dependency.yml +++ b/.github/workflows/update-copilot-dependency.yml @@ -161,19 +161,20 @@ jobs: exit 0 fi - git commit -m "Update @github/copilot to $VERSION + git commit -m "Update Copilot CLI to $VERSION - - Updated the Node.js CLI release pin + - Updated the Node.js CLI release pin and trusted hashes - Re-ran code generators - Formatted generated code" git push origin "$BRANCH" --force-with-lease PR_BODY=$(cat <<'BODY_EOF' - Automated update of `@github/copilot` to version `PLACEHOLDER_VERSION`. + Automated update of the Copilot CLI release to version `PLACEHOLDER_VERSION`. ### Changes - - Updated the Copilot CLI release pin in `nodejs/package.json` + - Updated the release pin in `nodejs/package.json` + - Updated trusted release hashes in `nodejs/copilot-cli.json` - Re-ran all code generators (`scripts/codegen`) - Formatted generated output - Updated Java codegen dependency, POM property, and regenerated Java types diff --git a/dotnet/test/Harness/E2ETestContext.cs b/dotnet/test/Harness/E2ETestContext.cs index 0080cbc609..008fd08eb8 100644 --- a/dotnet/test/Harness/E2ETestContext.cs +++ b/dotnet/test/Harness/E2ETestContext.cs @@ -5,7 +5,6 @@ using Microsoft.Extensions.Logging; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text.RegularExpressions; namespace GitHub.Copilot.Test.Harness; @@ -149,41 +148,33 @@ private static string GetCliPath(string repoRoot) var envPath = Environment.GetEnvironmentVariable("COPILOT_CLI_PATH"); if (!string.IsNullOrEmpty(envPath)) return envPath; - // As of CLI 1.0.64-1 the @github/copilot package is a thin loader; the - // runnable index.js ships in the installed platform package. - var githubModules = Path.Join(repoRoot, "nodejs", "node_modules", "@github"); - var packagePrefix = GetCliPackagePrefix(); - var candidates = Directory.Exists(githubModules) - ? Directory.EnumerateDirectories(githubModules, $"{packagePrefix}-*", SearchOption.TopDirectoryOnly) - .Select(directory => Path.Join(directory, "index.js")) - .Where(File.Exists) - .ToArray() - : []; - - return candidates.Length switch + var startInfo = new ProcessStartInfo { - 1 => candidates[0], - 0 => throw new InvalidOperationException( - $"CLI package matching '{packagePrefix}-*' not found under {githubModules}. " + - "Run 'npm install' in the nodejs directory first."), - _ => throw new InvalidOperationException( - $"Multiple CLI packages matching '{packagePrefix}-*' found under {githubModules}: " + - string.Join(", ", candidates.Select(Path.GetDirectoryName))), + FileName = OperatingSystem.IsWindows() ? "npm.cmd" : "npm", + WorkingDirectory = Path.Join(repoRoot, "nodejs"), + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, }; - } - - private static string GetCliPackagePrefix() - { - var platform = OperatingSystem.IsWindows() - ? "win32" - : OperatingSystem.IsMacOS() - ? "darwin" - : OperatingSystem.IsLinux() - ? RuntimeInformation.RuntimeIdentifier.StartsWith("linux-musl-", StringComparison.Ordinal) - ? "linuxmusl" - : "linux" - : throw new PlatformNotSupportedException("Unsupported operating system for Copilot CLI E2E tests."); - return $"copilot-{platform}"; + foreach (var argument in new[] { "run", "--silent", "prepare:runtime", "--", "--print-path" }) + startInfo.ArgumentList.Add(argument); + + using var process = Process.Start(startInfo) + ?? throw new InvalidOperationException("Failed to start Node.js runtime preparation."); + var stdout = process.StandardOutput.ReadToEndAsync(); + var stderr = process.StandardError.ReadToEndAsync(); + process.WaitForExit(); + var output = stdout.GetAwaiter().GetResult(); + var lines = output.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries); + var cliPath = lines.Length == 0 ? string.Empty : lines[^1].Trim(); + var error = stderr.GetAwaiter().GetResult().Trim(); + if (process.ExitCode != 0 || string.IsNullOrEmpty(cliPath)) + throw new InvalidOperationException( + $"Failed to prepare the pinned Copilot CLI: {error}"); + if (!File.Exists(cliPath)) + throw new InvalidOperationException( + $"Pinned Copilot CLI was not created at {cliPath}."); + return cliPath; } public async Task ConfigureForTestAsync(string testFile, [CallerMemberName] string? testName = null) diff --git a/go/internal/e2e/testharness/context.go b/go/internal/e2e/testharness/context.go index 037265f9de..0374cc5130 100644 --- a/go/internal/e2e/testharness/context.go +++ b/go/internal/e2e/testharness/context.go @@ -2,6 +2,7 @@ package testharness import ( "os" + "os/exec" "path/filepath" "regexp" "runtime" @@ -29,15 +30,18 @@ func CLIPath() string { return } - // Look for CLI in sibling nodejs directory's node_modules. As of CLI - // 1.0.64-1 the @github/copilot package is a thin loader; the runnable - // index.js ships in the installed platform package - // (e.g. @github/copilot-linux-x64). - base := RepoPath("nodejs", "node_modules", "@github") - matches, _ := filepath.Glob(filepath.Join(base, "copilot-*", "index.js")) - if len(matches) > 0 { - cliPath = matches[0] - return + npm := "npm" + if runtime.GOOS == "windows" { + npm = "npm.cmd" + } + command := exec.Command(npm, "run", "--silent", "prepare:runtime", "--", "--print-path") + command.Dir = RepoPath("nodejs") + output, err := command.Output() + if err == nil { + candidate := strings.TrimSpace(string(output)) + if info, statErr := os.Stat(candidate); statErr == nil && !info.IsDir() { + cliPath = candidate + } } }) return cliPath diff --git a/go/test.sh b/go/test.sh index dfb7bac1dd..f5924b3e8d 100755 --- a/go/test.sh +++ b/go/test.sh @@ -15,17 +15,14 @@ fi # Determine COPILOT_CLI_PATH if [ -z "$COPILOT_CLI_PATH" ]; then - # Try to find it relative to the SDK. As of CLI 1.0.64-1 the @github/copilot - # package is a thin loader; the runnable index.js ships in the installed - # platform package (e.g. @github/copilot-linux-x64). Exactly one is installed. SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" - POTENTIAL_PATH="$(ls "$SCRIPT_DIR"/../nodejs/node_modules/@github/copilot-*/index.js 2>/dev/null | head -n1)" - if [ -n "$POTENTIAL_PATH" ] && [ -f "$POTENTIAL_PATH" ]; then + if POTENTIAL_PATH="$(cd "$SCRIPT_DIR/../nodejs" && npm run --silent prepare:runtime -- --print-path)" && + [ -n "$POTENTIAL_PATH" ] && [ -f "$POTENTIAL_PATH" ]; then export COPILOT_CLI_PATH="$POTENTIAL_PATH" echo "📍 Auto-detected CLI path: $COPILOT_CLI_PATH" else echo "❌ COPILOT_CLI_PATH environment variable not set" - echo " Run: export COPILOT_CLI_PATH=/path/to/dist-cli/index.js" + echo " Run: export COPILOT_CLI_PATH=/path/to/copilot" exit 1 fi fi diff --git a/java/copilot-native/scripts/fetch-native.mjs b/java/copilot-native/scripts/fetch-native.mjs index f34ece61e0..2495e1c8ea 100644 --- a/java/copilot-native/scripts/fetch-native.mjs +++ b/java/copilot-native/scripts/fetch-native.mjs @@ -6,10 +6,9 @@ * Downloads the native runtime artifacts for one platform classifier. * * Steps: - * 1. Read the pinned version from `nodejs/package.json`. - * 2. Download the platform npm tarball and checksums from the matching - * `github/copilot-cli` release. - * 3. Verify the downloaded tarball against the release SHA-256. + * 1. Read the pinned version and trusted hash from `nodejs/copilot-cli.json`. + * 2. Download the platform npm tarball from the matching release. + * 3. Verify the downloaded tarball against the checked-in SHA-256. * 4. Stage the hostless runtime tree, flattening the selected prebuild directory * beside the package's retained top-level runtime assets. * 5. Write an inventory consumed by the SDK's generic classpath extractor. @@ -52,12 +51,12 @@ if (!repoRoot || !stagingDir || !classifier) { process.exit(1); } -const packagePath = path.join(repoRoot, 'nodejs', 'package.json'); +const manifestPath = path.join(repoRoot, 'nodejs', 'copilot-cli.json'); const packageName = `@github/copilot-${classifier}`; -const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8')); -const version = packageJson.copilotCliVersion; +const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); +const version = manifest.version; if (!version) { - console.error(`Could not find copilotCliVersion in ${packagePath}`); + console.error(`Could not find version in ${manifestPath}`); process.exit(1); } @@ -117,12 +116,8 @@ if (process.env.COPILOT_CLI_RELEASE_TARBALL) { expectedHash = process.env.COPILOT_CLI_RELEASE_SHA256; } else { const releaseUrl = `${releaseBase}/v${version}`; - const [checksums, downloadedArchive] = await Promise.all([ - download(`${releaseUrl}/SHA256SUMS.txt`).then((data) => data.toString('utf8')), - download(`${releaseUrl}/${assetName}`), - ]); - expectedHash = checksumForAsset(checksums, assetName); - archive = downloadedArchive; + expectedHash = manifest.runtimeHashes?.[classifier]; + archive = await download(`${releaseUrl}/${assetName}`); } if (!expectedHash || !/^[a-fA-F0-9]{64}$/.test(expectedHash)) { throw new Error(`Missing or invalid SHA-256 for ${assetName}`); @@ -234,16 +229,6 @@ function digestTree(directory) { return `sha512-${hash.digest('base64')}`; } -function checksumForAsset(checksums, assetName) { - for (const line of checksums.split(/\r?\n/)) { - const [hash, name] = line.trim().split(/\s+/, 2); - if (name?.replace(/^\*/, '') === assetName && /^[a-fA-F0-9]{64}$/.test(hash)) { - return hash; - } - } - throw new Error(`SHA256SUMS.txt does not contain ${assetName}`); -} - async function download(url) { let lastError; for (let attempt = 0; attempt < 3; attempt++) { diff --git a/java/copilot-native/scripts/fetch-native.test.mjs b/java/copilot-native/scripts/fetch-native.test.mjs index 83c484e08e..bac77fd6bf 100644 --- a/java/copilot-native/scripts/fetch-native.test.mjs +++ b/java/copilot-native/scripts/fetch-native.test.mjs @@ -95,8 +95,8 @@ test('stages retained package assets and excludes CLI-only content', (t) => { execFileSync('tar', ['-czf', tarball, '-C', path.dirname(packageRoot), 'package']); const packageChecksum = createHash('sha256').update(fs.readFileSync(tarball)).digest('hex'); fs.writeFileSync( - path.join(fixture.repoRoot, 'nodejs', 'package.json'), - JSON.stringify({ copilotCliVersion: version }), + path.join(fixture.repoRoot, 'nodejs', 'copilot-cli.json'), + JSON.stringify({ version, runtimeHashes: { [classifier]: packageChecksum } }), ); fs.rmSync(path.join(fixture.stagingDir, classifier), { recursive: true, force: true }); @@ -127,8 +127,8 @@ function createFixture(t, classifier) { fs.mkdirSync(resourceDir, { recursive: true }); fs.writeFileSync( - path.join(repoRoot, 'nodejs', 'package.json'), - JSON.stringify({ copilotCliVersion: version }), + path.join(repoRoot, 'nodejs', 'copilot-cli.json'), + JSON.stringify({ version, runtimeHashes: { [classifier]: checksum } }), ); const runtimePath = path.join(resourceDir, 'runtime.node'); diff --git a/nodejs/README.md b/nodejs/README.md index 5985f491ba..465f7c9ce8 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -16,7 +16,8 @@ mirror. The checked-in release pin is `copilotCliVersion` in `package.json`. Run `npm run set:cli-version -- ` to update it and regenerate the -platform SHA-256 map in `src/cliVersion.ts`. +trusted platform SHA-256 manifest in `copilot-cli.json` and the compiled +metadata in `src/cliVersion.ts`. ## Installation diff --git a/nodejs/copilot-cli.json b/nodejs/copilot-cli.json new file mode 100644 index 0000000000..7bec267c2e --- /dev/null +++ b/nodejs/copilot-cli.json @@ -0,0 +1,22 @@ +{ + "version": "1.0.83-0", + "source": "github-release", + "runtimeHashes": { + "darwin-arm64": "d8bd725ed870fe1ad4c55070857a4efa03dc3327e07e3aacc62a9b67379bc2d0", + "darwin-x64": "6ff4e3b42f74106392e3a8b8709bf8506bc1a9b9459cdc3ac01dffd4c464186f", + "linux-arm64": "11ad54a68c6f585d32aed0de97569e0c503bcde6bb90a90fffbf33c8b29f1236", + "linux-x64": "c0ff7b87e81367ddfad2a93c6d8402b43066f6a06a9ff24c6ac3aabd126fcd67", + "linuxmusl-arm64": "b1b766d55772038053c195c9ba3e8739d97a487f6d0f8472c190f0d1c313c7b6", + "linuxmusl-x64": "a4e59a686743cbc031bd91e3640e2ba788b06da2bb61b69537ff8bf3c65ce106", + "win32-arm64": "e064594c5c2f9d2e7758fa8758f2322557888fe1e11240ae4ed2acb8afdfea47", + "win32-x64": "f5003673d621971acffca7b05ff3442c86de686f389d65cc8791c03ea3ae6d9b" + }, + "cliHashes": { + "copilot-darwin-arm64.tar.gz": "c98b91348b3e3a5406bd1fb870addf038e0be029d7c818842a3b6023453d1eec", + "copilot-darwin-x64.tar.gz": "c5ba16fde484f3921cd62596d3c49b6d15cdf1cdba68b53781ffcd880439ef14", + "copilot-linux-arm64.tar.gz": "99f2a0b77c9558067aa7e93be9bf55248364486fe2461a85f74c3d2dc40e47a0", + "copilot-linux-x64.tar.gz": "5dc7b71233c09259508bbfc542894b2918c026d1751d5f3bd4ec177de0b7f0bb", + "copilot-win32-arm64.zip": "29097ca03a7ee452f713c3157702ecb562727df450e179eb6d97da1b08138500", + "copilot-win32-x64.zip": "cab6b3aca6003f9851c7afd88f301cc0c3cb111c2bdad88416b901c43b6656ee" + } +} diff --git a/nodejs/scripts/set-cli-version.js b/nodejs/scripts/set-cli-version.js index 5976231fcb..ec63186a46 100644 --- a/nodejs/scripts/set-cli-version.js +++ b/nodejs/scripts/set-cli-version.js @@ -2,13 +2,16 @@ import { readFileSync, writeFileSync } from "node:fs"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -const version = process.argv[2]; +const [version, mode] = process.argv.slice(2); if (!version || !/^\d+\.\d+\.\d+(?:-[0-9A-Za-z._-]+)?$/.test(version)) { - throw new Error("Usage: set-cli-version.js "); + throw new Error("Usage: set-cli-version.js [--npm-package]"); +} +if (mode !== undefined && mode !== "--npm-package") { + throw new Error(`Unknown option: ${mode}`); } const nodeRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); -const platforms = [ +const runtimePlatforms = [ "darwin-arm64", "darwin-x64", "linux-arm64", @@ -18,40 +21,71 @@ const platforms = [ "win32-arm64", "win32-x64", ]; -const checksumsUrl = `https://github.com/github/copilot-cli/releases/download/v${version}/SHA256SUMS.txt`; -const response = await fetch(checksumsUrl); -if (!response.ok) { - throw new Error(`Failed to download ${checksumsUrl}: ${response.status} ${response.statusText}`); -} -const checksums = new Map( - (await response.text()) - .split(/\r?\n/) - .map((line) => line.trim().split(/\s+/, 2)) - .filter(([hash, name]) => /^[a-fA-F0-9]{64}$/.test(hash) && name) - .map(([hash, name]) => [name.replace(/^\*/, ""), hash.toLowerCase()]) -); -const hashes = Object.fromEntries( - platforms.map((platform) => { - const assetName = `github-copilot-${version}-${platform}.tgz`; +const cliAssets = [ + "copilot-darwin-arm64.tar.gz", + "copilot-darwin-x64.tar.gz", + "copilot-linux-arm64.tar.gz", + "copilot-linux-x64.tar.gz", + "copilot-win32-arm64.zip", + "copilot-win32-x64.zip", +]; +const useNpmPackage = mode === "--npm-package"; +let runtimeHashes = {}; +let cliHashes = {}; +if (!useNpmPackage) { + const checksumsUrl = `https://github.com/github/copilot-cli/releases/download/v${version}/SHA256SUMS.txt`; + const response = await fetch(checksumsUrl); + if (!response.ok) { + throw new Error( + `Failed to download ${checksumsUrl}: ${response.status} ${response.statusText}` + ); + } + const checksums = new Map( + (await response.text()) + .split(/\r?\n/) + .map((line) => line.trim().split(/\s+/, 2)) + .filter(([hash, name]) => /^[a-fA-F0-9]{64}$/.test(hash) && name) + .map(([hash, name]) => [name.replace(/^\*/, ""), hash.toLowerCase()]) + ); + const hashForAsset = (assetName) => { const hash = checksums.get(assetName); if (!hash) { throw new Error(`SHA256SUMS.txt does not contain ${assetName}`); } - return [platform, hash]; - }) -); + return hash; + }; + runtimeHashes = Object.fromEntries( + runtimePlatforms.map((platform) => [ + platform, + hashForAsset(`github-copilot-${version}-${platform}.tgz`), + ]) + ); + cliHashes = Object.fromEntries( + cliAssets.map((assetName) => [assetName, hashForAsset(assetName)]) + ); +} const packagePath = join(nodeRoot, "package.json"); const packageJson = JSON.parse(readFileSync(packagePath, "utf8")); packageJson.copilotCliVersion = version; writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 4)}\n`); +const manifest = { + version, + source: useNpmPackage ? "npm-package" : "github-release", + runtimeHashes, + cliHashes, +}; +writeFileSync(join(nodeRoot, "copilot-cli.json"), `${JSON.stringify(manifest, null, 4)}\n`); + const sourcePath = join(nodeRoot, "src", "cliVersion.ts"); writeFileSync( sourcePath, [ `export const COPILOT_CLI_VERSION = ${JSON.stringify(version)};`, "", - `export const COPILOT_CLI_HASHES: Readonly> = ${JSON.stringify(hashes, null, 4)};`, + `export const COPILOT_CLI_USE_NPM_PACKAGE = ${useNpmPackage};`, + "", + `export const COPILOT_CLI_HASHES: Readonly> = ${JSON.stringify(runtimeHashes, null, 4)};`, "", ].join("\n") ); diff --git a/nodejs/src/cliVersion.ts b/nodejs/src/cliVersion.ts index 360b3ea58d..6055d2ee94 100644 --- a/nodejs/src/cliVersion.ts +++ b/nodejs/src/cliVersion.ts @@ -1,5 +1,7 @@ export const COPILOT_CLI_VERSION = "1.0.83-1"; +export const COPILOT_CLI_USE_NPM_PACKAGE = false; + export const COPILOT_CLI_HASHES: Readonly> = { "darwin-arm64": "d8bd725ed870fe1ad4c55070857a4efa03dc3327e07e3aacc62a9b67379bc2d0", "darwin-x64": "6ff4e3b42f74106392e3a8b8709bf8506bc1a9b9459cdc3ac01dffd4c464186f", diff --git a/nodejs/src/runtimeArtifacts.ts b/nodejs/src/runtimeArtifacts.ts index cba39d940d..8dd0500f37 100644 --- a/nodejs/src/runtimeArtifacts.ts +++ b/nodejs/src/runtimeArtifacts.ts @@ -12,10 +12,15 @@ import { statSync, writeFileSync, } from "node:fs"; +import { createRequire } from "node:module"; import { homedir } from "node:os"; import { dirname, join, relative, sep } from "node:path"; import { x as extractTar } from "tar"; -import { COPILOT_CLI_HASHES, COPILOT_CLI_VERSION } from "./cliVersion.js"; +import { + COPILOT_CLI_HASHES, + COPILOT_CLI_USE_NPM_PACKAGE, + COPILOT_CLI_VERSION, +} from "./cliVersion.js"; export interface RuntimeArtifactSources { packageRoot: string; @@ -30,6 +35,7 @@ export interface EnsureRuntimeBundleOptions { } const runtimeDownloads = new Map>(); +const require = createRequire(typeof __filename === "string" ? __filename : import.meta.url); const EXCLUDED_TOP_LEVEL = new Set([ "app.js", @@ -323,6 +329,20 @@ export async function ensureCopilotPackage( ): Promise { const environment = options.environment ?? process.env; const platform = options.platform ?? getRuntimePlatform(); + if (version === COPILOT_CLI_VERSION && COPILOT_CLI_USE_NPM_PACKAGE) { + const packageName = `@github/copilot-${platform}`; + const packageRoot = (require.resolve.paths(packageName) ?? []) + .map((base) => join(base, ...packageName.split("/"))) + .find((candidate) => existsSync(join(candidate, "index.js"))); + if (!packageRoot) { + throw new Error(`Could not resolve ${packageName} for Copilot CLI ${version}.`); + } + validateFile( + join(packageRoot, "prebuilds", platform, "runtime.node"), + "Copilot runtime.node" + ); + return packageRoot; + } const cacheRoot = options.cacheRoot ?? defaultRuntimeCacheRoot(); const versionRoot = join(cacheRoot, sanitizeCacheSegment(version)); const cachedPackageRoot = join(versionRoot, "packages", platform); diff --git a/nodejs/test/runtimeArtifacts.test.ts b/nodejs/test/runtimeArtifacts.test.ts index 3a347799f5..fbe83471ea 100644 --- a/nodejs/test/runtimeArtifacts.test.ts +++ b/nodejs/test/runtimeArtifacts.test.ts @@ -2,6 +2,7 @@ import { existsSync, mkdtempSync, mkdirSync, readFileSync, statSync, writeFileSy import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { createHash } from "node:crypto"; +import { spawnSync } from "node:child_process"; import { c as createTar } from "tar"; import { afterEach, describe, expect, it, vi } from "vitest"; @@ -12,7 +13,11 @@ import { getRuntimeReleaseAssetName, materializeRuntimeBundle, } from "../src/runtimeArtifacts.js"; -import { COPILOT_CLI_HASHES, COPILOT_CLI_VERSION } from "../src/cliVersion.js"; +import { + COPILOT_CLI_HASHES, + COPILOT_CLI_USE_NPM_PACKAGE, + COPILOT_CLI_VERSION, +} from "../src/cliVersion.js"; describe("defaultRuntimeCacheRoot", () => { it.each([ @@ -45,9 +50,53 @@ describe("release runtime selection", () => { const packageJson = JSON.parse( readFileSync(join(import.meta.dirname, "../package.json"), "utf8") ); + const manifest = JSON.parse( + readFileSync(join(import.meta.dirname, "../copilot-cli.json"), "utf8") + ); expect(COPILOT_CLI_VERSION).toBe(packageJson.copilotCliVersion); - expect(Object.keys(COPILOT_CLI_HASHES)).toHaveLength(8); - expect(packageJson.dependencies).not.toHaveProperty("@github/copilot"); + expect(manifest.version).toBe(COPILOT_CLI_VERSION); + expect(manifest.runtimeHashes).toEqual(COPILOT_CLI_HASHES); + expect(COPILOT_CLI_USE_NPM_PACKAGE).toBe(manifest.source === "npm-package"); + if (COPILOT_CLI_USE_NPM_PACKAGE) { + expect(COPILOT_CLI_HASHES).toEqual({}); + expect(manifest.cliHashes).toEqual({}); + expect(packageJson.dependencies["@github/copilot"]).toBe(COPILOT_CLI_VERSION); + } else { + expect(manifest.source).toBe("github-release"); + expect(Object.keys(COPILOT_CLI_HASHES)).toHaveLength(8); + expect(Object.keys(manifest.cliHashes)).toHaveLength(6); + expect(packageJson.dependencies).not.toHaveProperty("@github/copilot"); + } + }); + + it("can pin an internal npm package without contacting GitHub Releases", () => { + const root = mkdtempSync(join(tmpdir(), "copilot-cli-version-")); + mkdirSync(join(root, "scripts"), { recursive: true }); + mkdirSync(join(root, "src"), { recursive: true }); + writeFileSync(join(root, "package.json"), "{}\n"); + writeFileSync( + join(root, "scripts", "set-cli-version.js"), + readFileSync(join(import.meta.dirname, "../scripts/set-cli-version.js")) + ); + + const result = spawnSync( + process.execPath, + [join(root, "scripts", "set-cli-version.js"), "9.9.9-canary.test", "--npm-package"], + { encoding: "utf8" } + ); + expect(result.status, result.stderr).toBe(0); + expect(JSON.parse(readFileSync(join(root, "package.json"), "utf8"))).toMatchObject({ + copilotCliVersion: "9.9.9-canary.test", + }); + expect(JSON.parse(readFileSync(join(root, "copilot-cli.json"), "utf8"))).toEqual({ + version: "9.9.9-canary.test", + source: "npm-package", + runtimeHashes: {}, + cliHashes: {}, + }); + expect(readFileSync(join(root, "src", "cliVersion.ts"), "utf8")).toContain( + "COPILOT_CLI_USE_NPM_PACKAGE = true" + ); }); it.each([ diff --git a/python/e2e/testharness/context.py b/python/e2e/testharness/context.py index 8eaecf6244..616fa9bb0a 100644 --- a/python/e2e/testharness/context.py +++ b/python/e2e/testharness/context.py @@ -9,91 +9,47 @@ import os import re import shutil +import subprocess import tempfile import time -from collections.abc import Sequence from pathlib import Path from typing import Any from copilot import CopilotClient, RuntimeConnection -from copilot._cli_version import get_npm_platform from .proxy import CapiProxy -def _cli_platform_package_names(npm_platform: str | None = None) -> list[str]: - """Return candidate ``@github/copilot-*`` directory names, best match first. - - Mirrors ``getCliPlatformPackageNames()`` in ``nodejs/src/client.ts``: as of CLI - 1.0.64-1 the runnable ``index.js`` ships in a platform package such as - ``copilot-darwin-arm64``. On Linux both libc variants are listed (the detected - one first) because npm installs exactly one of them and musl probing can come up - empty in minimal containers. - """ - primary = npm_platform or get_npm_platform() - names = [f"copilot-{primary}"] - if primary.startswith("linux"): - arch = primary.rsplit("-", 1)[-1] - for variant in (f"linux-{arch}", f"linuxmusl-{arch}"): - name = f"copilot-{variant}" - if name not in names: - names.append(name) - return names - - -def _find_cli_in_node_modules(github_modules: Path, package_names: Sequence[str]) -> str | None: - """Return the resolved ``index.js`` of the first installed candidate package. - - Only exact package names are probed, so unrelated ``copilot-*`` directories - (e.g. ``copilot-language-server``) can never be mistaken for the CLI. - """ - for name in package_names: - candidate = github_modules / name / "index.js" - if candidate.exists(): - return str(candidate.resolve()) - return None - - -def _installed_cli_package_names(github_modules: Path) -> list[str]: - """Return the ``copilot-*`` directory names present, for error messages only. - - Selection never globs — that was the #2103 bug. This exists so a failure can - say what *is* installed, which is the difference between a dead-end "run npm - install" and a message that diagnoses itself on a mixed-architecture host. - """ - if not github_modules.is_dir(): - return [] - return sorted(path.name for path in github_modules.glob("copilot-*") if path.is_dir()) +def _prepare_pinned_cli(repo_root: Path) -> str: + npm = "npm.cmd" if os.name == "nt" else "npm" + result = subprocess.run( + [npm, "run", "--silent", "prepare:runtime", "--", "--print-path"], + cwd=repo_root / "nodejs", + capture_output=True, + text=True, + check=False, + ) + output = result.stdout.strip() + if result.returncode != 0 or not output: + detail = result.stderr.strip() or output or f"exit code {result.returncode}" + raise RuntimeError(f"Failed to prepare the pinned Copilot CLI: {detail}") + cli_path = Path(output.splitlines()[-1]) + if not cli_path.is_file(): + raise RuntimeError(f"Pinned Copilot CLI was not created at {cli_path}") + return str(cli_path.resolve()) def get_cli_path_for_tests() -> str: """Get CLI path for E2E tests. - Uses COPILOT_CLI_PATH env var if set, otherwise the platform-specific CLI - package in the sibling nodejs directory's node_modules. + Uses COPILOT_CLI_PATH env var if set, otherwise prepares the release pinned + by the sibling Node.js SDK. """ env_path = os.environ.get("COPILOT_CLI_PATH") if env_path and Path(env_path).exists(): return str(Path(env_path).resolve()) - # Look for CLI in sibling nodejs directory's node_modules. As of CLI 1.0.64-1 - # the @github/copilot package is a thin loader; the runnable index.js ships in - # the installed platform package (e.g. @github/copilot-linux-x64), so pick the - # one built for this host rather than whichever sorts first (#2103). - base_path = Path(__file__).parents[3] - github_modules = base_path / "nodejs" / "node_modules" / "@github" - package_names = _cli_platform_package_names() - found = _find_cli_in_node_modules(github_modules, package_names) - if found is not None: - return found - - installed = _installed_cli_package_names(github_modules) - raise RuntimeError( - f"CLI not found for tests under {github_modules} " - f"(tried: {', '.join(package_names)}; " - f"present: {', '.join(installed) or 'none'}). " - "Run 'npm install' in the nodejs directory, or set COPILOT_CLI_PATH." - ) + return _prepare_pinned_cli(Path(__file__).parents[3]) CLI_PATH = get_cli_path_for_tests() diff --git a/python/test_e2e_harness_cli_path.py b/python/test_e2e_harness_cli_path.py index 8a50ba7a51..83167ffd33 100644 --- a/python/test_e2e_harness_cli_path.py +++ b/python/test_e2e_harness_cli_path.py @@ -1,99 +1,12 @@ -"""Unit tests for the E2E harness's Copilot CLI platform-package resolution. - -Regression coverage for github/copilot-sdk#2103: the harness used to return the -first ``@github/copilot-*`` directory in alphabetical order instead of the package -built for the current platform. -""" +"""Unit tests for the E2E harness's pinned CLI preparation.""" from __future__ import annotations -from pathlib import Path - import pytest -from copilot._cli_version import get_npm_platform from e2e.testharness import context -def _make_package(github_modules: Path, name: str) -> Path: - """Create ``//index.js`` and return the entrypoint path.""" - package_dir = github_modules / name - package_dir.mkdir(parents=True, exist_ok=True) - index = package_dir / "index.js" - index.write_text("// fake CLI entrypoint\n") - return index - - -class TestCliPlatformPackageNames: - def test_non_linux_platform_yields_single_candidate(self): - assert context._cli_platform_package_names("darwin-arm64") == ["copilot-darwin-arm64"] - - def test_windows_platform_yields_single_candidate(self): - assert context._cli_platform_package_names("win32-x64") == ["copilot-win32-x64"] - - def test_glibc_linux_also_considers_musl_variant(self): - assert context._cli_platform_package_names("linux-x64") == [ - "copilot-linux-x64", - "copilot-linuxmusl-x64", - ] - - def test_musl_linux_prefers_musl_then_falls_back_to_glibc(self): - assert context._cli_platform_package_names("linuxmusl-arm64") == [ - "copilot-linuxmusl-arm64", - "copilot-linux-arm64", - ] - - def test_defaults_to_current_host_platform(self): - assert context._cli_platform_package_names()[0] == f"copilot-{get_npm_platform()}" - - -class TestFindCliInNodeModules: - def test_skips_alphabetically_earlier_foreign_package(self, tmp_path): - # The #2103 regression: "aardvark" sorts before every real platform name. - _make_package(tmp_path, "copilot-aardvark-x64") - expected = _make_package(tmp_path, "copilot-darwin-arm64") - found = context._find_cli_in_node_modules(tmp_path, ["copilot-darwin-arm64"]) - assert found == str(expected.resolve()) - - def test_returns_none_when_no_candidate_is_installed(self, tmp_path): - _make_package(tmp_path, "copilot-win32-x64") - assert context._find_cli_in_node_modules(tmp_path, ["copilot-darwin-arm64"]) is None - - def test_ignores_non_platform_copilot_packages(self, tmp_path): - _make_package(tmp_path, "copilot-language-server") - assert context._find_cli_in_node_modules(tmp_path, ["copilot-linux-x64"]) is None - - def test_prefers_earlier_candidate_when_both_libc_variants_exist(self, tmp_path): - expected = _make_package(tmp_path, "copilot-linuxmusl-x64") - _make_package(tmp_path, "copilot-linux-x64") - found = context._find_cli_in_node_modules( - tmp_path, ["copilot-linuxmusl-x64", "copilot-linux-x64"] - ) - assert found == str(expected.resolve()) - - def test_returns_none_when_package_dir_has_no_index_js(self, tmp_path): - (tmp_path / "copilot-linux-x64").mkdir() - assert context._find_cli_in_node_modules(tmp_path, ["copilot-linux-x64"]) is None - - def test_returns_none_when_github_modules_is_absent(self, tmp_path): - missing = tmp_path / "missing" - assert context._find_cli_in_node_modules(missing, ["copilot-linux-x64"]) is None - - -class TestInstalledCliPackageNames: - def test_lists_platform_directories_sorted(self, tmp_path): - _make_package(tmp_path, "copilot-win32-x64") - _make_package(tmp_path, "copilot-darwin-arm64") - (tmp_path / "not-copilot").mkdir() - assert context._installed_cli_package_names(tmp_path) == [ - "copilot-darwin-arm64", - "copilot-win32-x64", - ] - - def test_returns_empty_when_directory_is_absent(self, tmp_path): - assert context._installed_cli_package_names(tmp_path / "missing") == [] - - class TestGetCliPathForTests: def test_env_var_takes_precedence(self, tmp_path, monkeypatch): cli = tmp_path / "custom-cli.js" @@ -101,46 +14,26 @@ def test_env_var_takes_precedence(self, tmp_path, monkeypatch): monkeypatch.setenv("COPILOT_CLI_PATH", str(cli)) assert context.get_cli_path_for_tests() == str(cli.resolve()) - def test_error_names_the_packages_tried_and_the_remedy(self, monkeypatch): + def test_prepares_the_pinned_runtime(self, tmp_path, monkeypatch): monkeypatch.delenv("COPILOT_CLI_PATH", raising=False) - monkeypatch.setattr( - context, "_cli_platform_package_names", lambda *_: ["copilot-linux-x64"] - ) - monkeypatch.setattr(context, "_find_cli_in_node_modules", lambda *_: None) - with pytest.raises(RuntimeError) as excinfo: - context.get_cli_path_for_tests() - message = str(excinfo.value) - assert "copilot-linux-x64" in message - assert "npm install" in message - assert "COPILOT_CLI_PATH" in message + cli = tmp_path / "copilot" + cli.write_text("runtime\n") - def test_error_names_the_searched_directory(self, monkeypatch): - monkeypatch.delenv("COPILOT_CLI_PATH", raising=False) - seen: list[Path] = [] + class Result: + returncode = 0 + stdout = f"{cli}\n" + stderr = "" - def fake_find(github_modules, package_names): - seen.append(github_modules) - return None + monkeypatch.setattr(context.subprocess, "run", lambda *args, **kwargs: Result()) + assert context._prepare_pinned_cli(tmp_path) == str(cli.resolve()) - monkeypatch.setattr(context, "_cli_platform_package_names", lambda *_: ["copilot-nope-x64"]) - monkeypatch.setattr(context, "_find_cli_in_node_modules", fake_find) - with pytest.raises(RuntimeError) as excinfo: - context.get_cli_path_for_tests() - assert seen, "get_cli_path_for_tests must consult _find_cli_in_node_modules" - assert seen[0].name == "@github" - assert seen[0].parent.name == "node_modules" - assert seen[0].parent.parent.name == "nodejs" - assert str(seen[0]) in str(excinfo.value) + def test_preparation_failure_includes_command_error(self, tmp_path, monkeypatch): + class Result: + returncode = 1 + stdout = "" + stderr = "download failed" - def test_error_lists_the_packages_actually_installed(self, monkeypatch): - monkeypatch.delenv("COPILOT_CLI_PATH", raising=False) - monkeypatch.setattr(context, "_cli_platform_package_names", lambda *_: ["copilot-nope-x64"]) - monkeypatch.setattr(context, "_find_cli_in_node_modules", lambda *_: None) - monkeypatch.setattr( - context, "_installed_cli_package_names", lambda *_: ["copilot-darwin-arm64"] - ) + monkeypatch.setattr(context.subprocess, "run", lambda *args, **kwargs: Result()) with pytest.raises(RuntimeError) as excinfo: - context.get_cli_path_for_tests() - message = str(excinfo.value) - assert "present: copilot-darwin-arm64" in message - assert "copilot-nope-x64" in message + context._prepare_pinned_cli(tmp_path) + assert "download failed" in str(excinfo.value) diff --git a/rust/README.md b/rust/README.md index 62d75e2f19..4862ae3eb3 100644 --- a/rust/README.md +++ b/rust/README.md @@ -961,12 +961,12 @@ github-copilot-sdk = { version = "0.1", default-features = false } 1. **Version pin.** `build.rs` reads the CLI version from one of two sources: - `cli-version.txt` at the crate root (present in published crate tarballs and vendored slots). - - Otherwise, `../nodejs/package.json` (contributor build inside the github/copilot-sdk repo — matches the .NET and Go SDK conventions here). + - Otherwise, `../nodejs/copilot-cli.json` (contributor build inside the github/copilot-sdk repo). The resolved version is baked into the crate via `cargo:rustc-env=COPILOT_SDK_CLI_VERSION` regardless of mode. The runtime resolver consumes it to recompute the on-disk path by convention, so no absolute paths leak into the rlib. -2. **Build time:** `build.rs` downloads the platform-specific npm package and - verifies its `sha512` integrity against the lockfile or publish snapshot. +2. **Build time:** `build.rs` downloads the platform-specific release archive and + verifies its SHA-256 against the checked-in manifest or publish snapshot. Then: - **`bundled-cli` on (default):** creates and embeds a minimal archive containing the CLI executable, `copilot-runtime[.exe]`, and `runtime.node`. - **`bundled-in-process` on:** the archive additionally contains the platform-native runtime library (`.dll`, `.so`, or `.dylib`). diff --git a/rust/build/in_process.rs b/rust/build/in_process.rs index 5e07ce43df..d147b5b68b 100644 --- a/rust/build/in_process.rs +++ b/rust/build/in_process.rs @@ -21,12 +21,12 @@ pub(crate) fn main() { // The package file is only the source-of-truth in this repo's // contributor builds; everywhere else `cli-version-in-process.txt` is canonical. let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set"); - let package_file = Path::new(&manifest_dir) + let release_manifest = Path::new(&manifest_dir) .join("..") .join("nodejs") - .join("package.json"); - if package_file.is_file() { - println!("cargo:rerun-if-changed={}", package_file.display()); + .join("copilot-cli.json"); + if release_manifest.is_file() { + println!("cargo:rerun-if-changed={}", release_manifest.display()); } // Hard opt-out: disable the entire download / bundle / cache mechanism @@ -66,8 +66,8 @@ pub(crate) fn main() { // makes the publish workflow the trust boundary — an attacker who // later re-points the release tag can't silently poison consumer // builds. - // 2. Sibling `../nodejs/package.json` (contributor build inside - // the github/copilot-sdk repo), combined with the release checksums. + // 2. Sibling `../nodejs/copilot-cli.json` (contributor build inside + // the github/copilot-sdk repo). let (version, expected_hash) = resolve_version_and_hash(platform.package_name); // Bake the version into the crate regardless of mode. This is the @@ -199,7 +199,8 @@ fn build_embedded_archive(package: &[u8], platform: Platform, include_runtime: b .mtime(0) .write(Vec::new(), flate2::Compression::default()); let mut archive = tar::Builder::new(encoder); - let runtime = append_hostless_runtime_tree(&mut archive, package, platform); + let (runtime, wrapper) = append_hostless_runtime_tree(&mut archive, package, platform); + append_archive_file(&mut archive, platform.binary_name, &wrapper, 0o755); if include_runtime { append_archive_file( &mut archive, @@ -220,10 +221,11 @@ fn append_hostless_runtime_tree( archive: &mut tar::Builder, package: &[u8], platform: Platform, -) -> Vec { +) -> (Vec, Vec) { let decoder = flate2::read::GzDecoder::new(package); let mut source = tar::Archive::new(decoder); let mut runtime = None; + let mut wrapper = None; for entry in source .entries() .unwrap_or_else(|e| panic!("failed to read npm package entries: {e}")) @@ -247,6 +249,9 @@ fn append_hostless_runtime_tree( if destination == Path::new("runtime.node") { runtime = Some(bytes.clone()); } + if destination == Path::new(platform.runtime_wrapper_name()) { + wrapper = Some(bytes.clone()); + } append_archive_file( archive, destination @@ -256,12 +261,21 @@ fn append_hostless_runtime_tree( mode, ); } - runtime.unwrap_or_else(|| { - panic!( - "package `{}` does not contain prebuilds//runtime.node", - platform.package_name - ) - }) + ( + runtime.unwrap_or_else(|| { + panic!( + "package `{}` does not contain prebuilds//runtime.node", + platform.package_name + ) + }), + wrapper.unwrap_or_else(|| { + panic!( + "package `{}` does not contain prebuilds//{}", + platform.package_name, + platform.runtime_wrapper_name() + ) + }), + ) } fn hostless_runtime_path(source: &str, platform: Platform) -> Option { @@ -347,19 +361,16 @@ fn resolve_version_and_hash(package_name: &str) -> (String, String) { .unwrap_or_else(|e| panic!("invalid {}: {e}", snapshot.display())); } - // 2. Package metadata fallback (contributor build inside github/copilot-sdk). - let package_file = Path::new(&manifest_dir) + // 2. Checked-in release manifest (contributor build inside github/copilot-sdk). + let release_manifest = Path::new(&manifest_dir) .join("..") .join("nodejs") - .join("package.json"); - if package_file.is_file() { - let version = read_version_from_package_json(&package_file); + .join("copilot-cli.json"); + if release_manifest.is_file() { let platform = package_name .strip_prefix("copilot-") .expect("platform package names start with copilot-"); - let asset_name = format!("github-copilot-{version}-{platform}.tgz"); - let hash = fetch_live_sha256(&version, &asset_name); - return (version, hash); + return read_version_and_hash_from_manifest(&release_manifest, platform); } panic!( @@ -368,9 +379,9 @@ fn resolve_version_and_hash(package_name: &str) -> (String, String) { - {} (missing)\n\ - {} (missing)\n\ In a published crate or vendored slot, `cli-version-in-process.txt` should be present.\n\ - Inside the github/copilot-sdk repo, `../nodejs/package.json` is the source.", + Inside the github/copilot-sdk repo, `../nodejs/copilot-cli.json` is the source.", snapshot.display(), - package_file.display(), + release_manifest.display(), ); } @@ -403,31 +414,23 @@ fn parse_snapshot(contents: &str, package_name: &str) -> Result<(String, String) Ok((version, hash)) } -fn read_version_from_package_json(path: &Path) -> String { +fn read_version_and_hash_from_manifest(path: &Path, platform: &str) -> (String, String) { let contents = std::fs::read_to_string(path) .unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display())); - let package: serde_json::Value = serde_json::from_str(&contents) + let manifest: serde_json::Value = serde_json::from_str(&contents) .unwrap_or_else(|e| panic!("failed to parse {}: {e}", path.display())); - package["copilotCliVersion"] + let version = manifest["version"] .as_str() - .unwrap_or_else(|| panic!("copilotCliVersion is missing in {}", path.display())) - .to_string() -} - -fn fetch_live_sha256(version: &str, asset_name: &str) -> String { - let checksums_url = format!( - "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/github/copilot-cli/releases/download/v{version}/SHA256SUMS.txt" - ); - let checksums = download_with_retry(&checksums_url); - let checksums_text = - std::str::from_utf8(&checksums).expect("checksums file is not valid UTF-8"); - checksums_text - .lines() - .find_map(|line| { - let (hash, name) = line.split_once(char::is_whitespace)?; - (name.trim_start().trim_start_matches('*') == asset_name).then(|| hash.to_string()) - }) - .unwrap_or_else(|| panic!("SHA256SUMS.txt has no entry for {asset_name}")) + .unwrap_or_else(|| panic!("version is missing in {}", path.display())); + let hash = manifest["runtimeHashes"][platform] + .as_str() + .unwrap_or_else(|| { + panic!( + "trusted hash for {platform} is missing in {}", + path.display() + ) + }); + (version.to_string(), hash.to_string()) } #[derive(Clone, Copy)] diff --git a/rust/build/out_of_process.rs b/rust/build/out_of_process.rs index 72c0bd48cc..67712316e2 100644 --- a/rust/build/out_of_process.rs +++ b/rust/build/out_of_process.rs @@ -21,12 +21,12 @@ pub(crate) fn main() { // The package file is only the source-of-truth in this repo's // contributor builds; everywhere else `cli-version.txt` is canonical. let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is set"); - let package_file = Path::new(&manifest_dir) + let release_manifest = Path::new(&manifest_dir) .join("..") .join("nodejs") - .join("package.json"); - if package_file.is_file() { - println!("cargo:rerun-if-changed={}", package_file.display()); + .join("copilot-cli.json"); + if release_manifest.is_file() { + println!("cargo:rerun-if-changed={}", release_manifest.display()); } // Hard opt-out: disable the entire download / bundle / cache mechanism @@ -66,10 +66,8 @@ pub(crate) fn main() { // makes the publish workflow the trust boundary — an attacker who // later re-points the release tag can't silently poison consumer // builds. - // 2. Sibling `../nodejs/package.json` (contributor build inside - // the github/copilot-sdk repo; live SHA256SUMS.txt fetch). Matches - // the .NET `_GetCopilotCliVersion` MSBuild target and the Go - // `cmd/bundler` tool. + // 2. Sibling `../nodejs/copilot-cli.json` (contributor build inside + // the github/copilot-sdk repo). let (version, expected_hash) = resolve_version_and_hash(platform.asset_name); // Bake the version into the crate regardless of mode. This is the @@ -194,16 +192,13 @@ fn resolve_version_and_hash(asset_name: &str) -> (String, String) { .unwrap_or_else(|e| panic!("invalid {}: {e}", snapshot.display())); } - // 2. Package metadata fallback (contributor build inside github/copilot-sdk) — - // read version, fetch live SHA256SUMS. - let package_file = Path::new(&manifest_dir) + // 2. Checked-in release manifest (contributor build inside github/copilot-sdk). + let release_manifest = Path::new(&manifest_dir) .join("..") .join("nodejs") - .join("package.json"); - if package_file.is_file() { - let version = read_version_from_package_json(&package_file); - let hash = fetch_live_sha256(&version, asset_name); - return (version, hash); + .join("copilot-cli.json"); + if release_manifest.is_file() { + return read_version_and_hash_from_manifest(&release_manifest, asset_name); } panic!( @@ -212,9 +207,9 @@ fn resolve_version_and_hash(asset_name: &str) -> (String, String) { - {} (missing)\n\ - {} (missing)\n\ In a published crate or vendored slot, `cli-version.txt` should be present.\n\ - Inside the github/copilot-sdk repo, `../nodejs/package.json` is the source.", + Inside the github/copilot-sdk repo, `../nodejs/copilot-cli.json` is the source.", snapshot.display(), - package_file.display(), + release_manifest.display(), ); } @@ -247,30 +242,18 @@ fn parse_snapshot(contents: &str, asset_name: &str) -> Result<(String, String), Ok((version, hash)) } -/// Read the pinned Copilot CLI version from `nodejs/package.json`. -fn read_version_from_package_json(path: &Path) -> String { +fn read_version_and_hash_from_manifest(path: &Path, asset_name: &str) -> (String, String) { let contents = std::fs::read_to_string(path) .unwrap_or_else(|e| panic!("failed to read {}: {e}", path.display())); - let key = "\"copilotCliVersion\""; - let key_pos = contents - .find(key) - .unwrap_or_else(|| panic!("{} does not contain {key}", path.display())); - let after_key = &contents[key_pos + key.len()..]; - let q1 = after_key.find('"').expect("malformed copilotCliVersion"); - let after_q1 = &after_key[q1 + 1..]; - let q2 = after_q1.find('"').expect("malformed copilotCliVersion"); - after_q1[..q2].to_string() -} - -/// Fetch the live `SHA256SUMS.txt` for the given version from GitHub Releases -/// and pluck out the entry for `asset_name`. -fn fetch_live_sha256(version: &str, asset_name: &str) -> String { - let base_url = format!("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/github/copilot-cli/releases/download/v{version}"); - let checksums_url = format!("{base_url}/SHA256SUMS.txt"); - let checksums = download_with_retry(&checksums_url); - let checksums_text = - std::str::from_utf8(&checksums).expect("checksums file is not valid UTF-8"); - find_sha256_for_asset(checksums_text, asset_name) + let manifest: serde_json::Value = serde_json::from_str(&contents) + .unwrap_or_else(|e| panic!("failed to parse {}: {e}", path.display())); + let version = manifest["version"] + .as_str() + .unwrap_or_else(|| panic!("version is missing in {}", path.display())); + let hash = manifest["cliHashes"][asset_name] + .as_str() + .unwrap_or_else(|| panic!("trusted hash for {asset_name} is missing in {}", path.display())); + (version.to_string(), hash.to_string()) } #[derive(Clone, Copy)] @@ -632,18 +615,6 @@ fn try_download(url: &str) -> Result, DownloadError> { } } -fn find_sha256_for_asset(sums: &str, asset_name: &str) -> String { - for line in sums.lines() { - // Format: " " (two spaces) - if let Some((hash, name)) = line.split_once(" ") - && name.trim() == asset_name - { - return hash.trim().to_string(); - } - } - panic!("SHA256SUMS.txt does not contain an entry for {asset_name}"); -} - fn sha256(data: &[u8]) -> [u8; 32] { let mut hasher = sha2::Sha256::new(); hasher.update(data); diff --git a/rust/scripts/snapshot-bundled-cli-version.sh b/rust/scripts/snapshot-bundled-cli-version.sh index 808853141c..16c4a2c9d4 100755 --- a/rust/scripts/snapshot-bundled-cli-version.sh +++ b/rust/scripts/snapshot-bundled-cli-version.sh @@ -5,10 +5,8 @@ # how .NET's _GenerateVersionProps BeforeTargets="Pack" target writes # GitHub.Copilot.SDK.props before NuGet packing. # -# Inputs: -# - ../nodejs/package.json (sibling) - source of the pinned version. -# - https://github.com/github/copilot-cli/releases/v{version}/SHA256SUMS.txt - -# authoritative per-platform hashes. +# Input: +# - ../nodejs/copilot-cli.json - checked-in version and trusted hashes. # # Output: # - cli-version.txt (in the rust crate root). Gitignored. @@ -18,24 +16,20 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RUST_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" REPO_ROOT="$(cd "${RUST_DIR}/.." && pwd)" -PACKAGE_FILE="${REPO_ROOT}/nodejs/package.json" +MANIFEST_FILE="${REPO_ROOT}/nodejs/copilot-cli.json" OUTPUT="${RUST_DIR}/cli-version.txt" -if [[ ! -f "${PACKAGE_FILE}" ]]; then - echo "error: ${PACKAGE_FILE} not found" >&2 +if [[ ! -f "${MANIFEST_FILE}" ]]; then + echo "error: ${MANIFEST_FILE} not found" >&2 exit 1 fi -VERSION="$(node -e "console.log(require('${PACKAGE_FILE}').copilotCliVersion)")" +VERSION="$(node -e "console.log(require('${MANIFEST_FILE}').version)")" if [[ -z "${VERSION}" ]]; then - echo "error: could not read copilotCliVersion from ${PACKAGE_FILE}" >&2 + echo "error: could not read version from ${MANIFEST_FILE}" >&2 exit 1 fi -CHECKSUMS_URL="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/github/copilot-cli/releases/download/v${VERSION}/SHA256SUMS.txt" -echo "Fetching ${CHECKSUMS_URL}" -SHA256SUMS="$(curl -fsSL --retry 3 --retry-delay 2 "${CHECKSUMS_URL}")" - ASSETS=( "copilot-darwin-arm64.tar.gz" "copilot-darwin-x64.tar.gz" @@ -52,9 +46,9 @@ trap 'rm -f "${TEMP_OUTPUT}"' EXIT echo "# Do not edit. Regenerated by the publish workflow on every release." echo "version=${VERSION}" for asset in "${ASSETS[@]}"; do - hash="$(printf '%s\n' "${SHA256SUMS}" | awk -v a="${asset}" '$2 == a { print $1 }')" + hash="$(node -e "console.log(require(process.argv[1]).cliHashes[process.argv[2]] || '')" "${MANIFEST_FILE}" "${asset}")" if [[ -z "${hash}" ]]; then - echo "error: SHA256SUMS.txt missing entry for ${asset}" >&2 + echo "error: ${MANIFEST_FILE} missing trusted hash for ${asset}" >&2 exit 1 fi echo "${asset}=${hash}" diff --git a/rust/scripts/snapshot-bundled-in-process-version.sh b/rust/scripts/snapshot-bundled-in-process-version.sh index 09e7917464..69440b905b 100755 --- a/rust/scripts/snapshot-bundled-in-process-version.sh +++ b/rust/scripts/snapshot-bundled-in-process-version.sh @@ -8,23 +8,20 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" RUST_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" REPO_ROOT="$(cd "${RUST_DIR}/.." && pwd)" -PACKAGE_FILE="${REPO_ROOT}/nodejs/package.json" +MANIFEST_FILE="${REPO_ROOT}/nodejs/copilot-cli.json" OUTPUT="${RUST_DIR}/cli-version-in-process.txt" -if [[ ! -f "${PACKAGE_FILE}" ]]; then - echo "error: ${PACKAGE_FILE} not found" >&2 +if [[ ! -f "${MANIFEST_FILE}" ]]; then + echo "error: ${MANIFEST_FILE} not found" >&2 exit 1 fi -VERSION="$(node -e "console.log(require('${PACKAGE_FILE}').copilotCliVersion)")" +VERSION="$(node -e "console.log(require('${MANIFEST_FILE}').version)")" if [[ -z "${VERSION}" ]]; then - echo "error: could not read copilotCliVersion from ${PACKAGE_FILE}" >&2 + echo "error: could not read version from ${MANIFEST_FILE}" >&2 exit 1 fi -CHECKSUMS_URL="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/github/copilot-cli/releases/download/v${VERSION}/SHA256SUMS.txt" -SHA256SUMS="$(curl -fsSL --retry 3 --retry-delay 2 "${CHECKSUMS_URL}")" - PACKAGES=( "copilot-darwin-arm64" "copilot-darwin-x64" @@ -44,10 +41,9 @@ trap 'rm -f "${TEMP_OUTPUT}"' EXIT echo "version=${VERSION}" for package in "${PACKAGES[@]}"; do platform="${package#copilot-}" - asset="github-copilot-${VERSION}-${platform}.tgz" - hash="$(printf '%s\n' "${SHA256SUMS}" | awk -v a="${asset}" '$2 == a { print $1 }')" + hash="$(node -e "console.log(require(process.argv[1]).runtimeHashes[process.argv[2]] || '')" "${MANIFEST_FILE}" "${platform}")" if [[ -z "${hash}" ]]; then - echo "error: SHA256SUMS.txt missing entry for ${asset}" >&2 + echo "error: ${MANIFEST_FILE} missing trusted hash for ${platform}" >&2 exit 1 fi echo "${package}=${hash}" diff --git a/rust/tests/cli_resolution_test.rs b/rust/tests/cli_resolution_test.rs index 0cb72510b1..6f6b0f8edd 100644 --- a/rust/tests/cli_resolution_test.rs +++ b/rust/tests/cli_resolution_test.rs @@ -199,16 +199,16 @@ async fn extract_dir_runtime_override_is_honored() { /// Build-time version pins, when present, must match the selected bundling /// implementation's checksum format. -/// When absent, build.rs falls through to `../nodejs/package.json` — +/// When absent, build.rs falls through to `../nodejs/copilot-cli.json` — /// both are accepted, this test only checks the pin file's format if it's /// there. #[test] fn pin_file_when_present_is_well_formed() { let manifest_dir = env!("CARGO_MANIFEST_DIR"); - let (filename, value_prefix) = if cfg!(feature = "bundled-in-process") { - ("cli-version-in-process.txt", Some("sha512-")) + let (filename, expected_package_count) = if cfg!(feature = "bundled-in-process") { + ("cli-version-in-process.txt", 8) } else { - ("cli-version.txt", None) + ("cli-version.txt", 6) }; let pin = PathBuf::from(manifest_dir).join(filename); if !pin.is_file() { @@ -230,27 +230,20 @@ fn pin_file_when_present_is_well_formed() { if key.trim() == "version" { saw_version = true; } else { - if let Some(prefix) = value_prefix { - assert!( - value.trim().starts_with(prefix), - "invalid npm integrity for key {key:?}" - ); - } else { - assert_eq!( - value.trim().len(), - 64, - "invalid SHA-256 hash for key {key:?}" - ); - assert!( - value.trim().bytes().all(|byte| byte.is_ascii_hexdigit()), - "invalid SHA-256 hash for key {key:?}" - ); - } + assert_eq!( + value.trim().len(), + 64, + "invalid SHA-256 hash for key {key:?}" + ); + assert!( + value.trim().bytes().all(|byte| byte.is_ascii_hexdigit()), + "invalid SHA-256 hash for key {key:?}" + ); package_count += 1; } } assert!(saw_version, "{filename} missing `version=` line"); - assert_eq!(package_count, 6); + assert_eq!(package_count, expected_package_count); } /// With `bundled-cli` on AND a supported target, `install_bundled_cli` diff --git a/rust/tests/e2e/support.rs b/rust/tests/e2e/support.rs index 42accf2ec7..0fd95dc562 100644 --- a/rust/tests/e2e/support.rs +++ b/rust/tests/e2e/support.rs @@ -1175,29 +1175,23 @@ fn cli_path(repo_root: &Path) -> std::io::Result { } } - // The `@github/copilot` package is a thin loader; the runnable `index.js` - // ships in a platform-specific `@github/copilot--` package, - // exactly one of which is installed. Resolve whichever one is present. - let github_dir = repo_root - .join("nodejs") - .join("node_modules") - .join("@github"); - if let Ok(entries) = std::fs::read_dir(&github_dir) { - for entry in entries.flatten() { - if entry.file_name().to_string_lossy().starts_with("copilot-") { - let candidate = entry.path().join("index.js"); - if candidate.exists() { - return Ok(candidate); - } - } + let npm = if cfg!(windows) { "npm.cmd" } else { "npm" }; + let output = std::process::Command::new(npm) + .args(["run", "--silent", "prepare:runtime", "--", "--print-path"]) + .current_dir(repo_root.join("nodejs")) + .output()?; + if output.status.success() { + let path = PathBuf::from(String::from_utf8_lossy(&output.stdout).trim()); + if path.is_file() { + return Ok(path); } } Err(std::io::Error::new( std::io::ErrorKind::NotFound, format!( - "CLI not found under {}; run npm install in nodejs first", - github_dir.display() + "failed to prepare the pinned Copilot CLI: {}", + String::from_utf8_lossy(&output.stderr).trim() ), )) } From 2b3a4484e7eca0d0415c87619f7fe1f8e1bee31a Mon Sep 17 00:00:00 2001 From: Devraj Mehta Date: Tue, 1 Sep 2026 14:48:23 -0400 Subject: [PATCH 03/30] Package CLI runtimes by platform Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3043824d-becf-4b5d-b62b-4754511894f7 --- .github/workflows/nodejs-sdk-tests.yml | 5 + .github/workflows/publish.yml | 57 ++++-- .github/workflows/sdk-canary.yml | 32 +++- nodejs/README.md | 31 ++-- nodejs/package-lock.json | 8 +- nodejs/package.json | 5 +- nodejs/scripts/package-sdk.ts | 66 +++++++ nodejs/scripts/prepare-runtime.ts | 8 +- nodejs/scripts/releaseArtifacts.ts | 174 ++++++++++++++++++ nodejs/src/client.ts | 29 +-- nodejs/src/runtimeArtifacts.ts | 205 +++++----------------- nodejs/test/e2e/harness/sdkTestContext.ts | 2 +- nodejs/test/runtimeArtifacts.test.ts | 93 +++++++--- scripts/codegen/utils.ts | 6 +- 14 files changed, 486 insertions(+), 235 deletions(-) create mode 100644 nodejs/scripts/package-sdk.ts create mode 100644 nodejs/scripts/releaseArtifacts.ts diff --git a/.github/workflows/nodejs-sdk-tests.yml b/.github/workflows/nodejs-sdk-tests.yml index 1738828e6b..afd0b9b20a 100644 --- a/.github/workflows/nodejs-sdk-tests.yml +++ b/.github/workflows/nodejs-sdk-tests.yml @@ -58,6 +58,11 @@ jobs: working-directory: ./test/harness run: npm test + - name: Prepare Copilot CLI runtime + run: | + runtime_path=$(npm run --silent prepare:runtime -- --print-path) + echo "COPILOT_CLI_PATH=$runtime_path" >> "$GITHUB_ENV" + - name: Warm up PowerShell if: runner.os == 'Windows' run: pwsh.exe -Command "Write-Host 'PowerShell ready'" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 486e59fa09..d1961d4a97 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -107,19 +107,18 @@ jobs: - name: Build run: npm run build - name: Pack - id: pack run: | - TARBALL="$(npm pack . --json | jq -r '.[0].filename')" - if [ -z "$TARBALL" ] || [ ! -f "$TARBALL" ]; then - echo "::error::npm pack did not produce a tarball." + npm run pack:release + TARBALL_COUNT="$(find . -maxdepth 1 -name 'github-copilot-sdk-*.tgz' | wc -l | tr -d ' ')" + if [ "$TARBALL_COUNT" -ne 9 ]; then + echo "::error::Expected nine Node.js package tarballs, found $TARBALL_COUNT." exit 1 fi - echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT" - name: Upload artifact uses: actions/upload-artifact@v7.0.0 with: name: nodejs-package - path: nodejs/${{ steps.pack.outputs.tarball }} + path: nodejs/github-copilot-sdk-*.tgz if-no-files-found: error publish-nodejs: @@ -150,12 +149,29 @@ jobs: set -euo pipefail shopt -s nullglob TARBALLS=(./dist/*.tgz) - if [ "${#TARBALLS[@]}" -ne 1 ]; then - echo "::error::Expected exactly one Node.js package tarball, found ${#TARBALLS[@]}." + if [ "${#TARBALLS[@]}" -ne 9 ]; then + echo "::error::Expected nine Node.js package tarballs, found ${#TARBALLS[@]}." + exit 1 + fi + MAIN_TARBALL="" + for TARBALL in "${TARBALLS[@]}"; do + PACKAGE_NAME="$(tar -xOf "$TARBALL" package/package.json | jq -r .name)" + if [ "$PACKAGE_NAME" = "@github/copilot-sdk" ]; then + MAIN_TARBALL="$TARBALL" + continue + fi + node nodejs/scripts/npm-release.js publish \ + "$TARBALL" \ + "$DIST_TAG" \ + https://registry.npmjs.org \ + public + done + if [ -z "$MAIN_TARBALL" ]; then + echo "::error::Main @github/copilot-sdk tarball not found." exit 1 fi node nodejs/scripts/npm-release.js publish \ - "${TARBALLS[0]}" \ + "$MAIN_TARBALL" \ "$DIST_TAG" \ https://registry.npmjs.org \ public @@ -209,12 +225,29 @@ jobs: fi shopt -s nullglob TARBALLS=(./dist/*.tgz) - if [ "${#TARBALLS[@]}" -ne 1 ]; then - echo "::error::Expected exactly one Node.js package tarball, found ${#TARBALLS[@]}." + if [ "${#TARBALLS[@]}" -ne 9 ]; then + echo "::error::Expected nine Node.js package tarballs, found ${#TARBALLS[@]}." + exit 1 + fi + MAIN_TARBALL="" + for TARBALL in "${TARBALLS[@]}"; do + PACKAGE_NAME="$(tar -xOf "$TARBALL" package/package.json | jq -r .name)" + if [ "$PACKAGE_NAME" = "@github/copilot-sdk" ]; then + MAIN_TARBALL="$TARBALL" + continue + fi + node nodejs/scripts/npm-release.js publish \ + "$TARBALL" \ + "$DIST_TAG" \ + "$FEED_URL" \ + azure + done + if [ -z "$MAIN_TARBALL" ]; then + echo "::error::Main @github/copilot-sdk tarball not found." exit 1 fi node nodejs/scripts/npm-release.js publish \ - "${TARBALLS[0]}" \ + "$MAIN_TARBALL" \ "$DIST_TAG" \ "$FEED_URL" \ azure diff --git a/.github/workflows/sdk-canary.yml b/.github/workflows/sdk-canary.yml index 894dbc1c74..5d860140c8 100644 --- a/.github/workflows/sdk-canary.yml +++ b/.github/workflows/sdk-canary.yml @@ -207,6 +207,7 @@ jobs: test -s "$(dirname "$runtime_path")/runtime.node" legacy_path=$(npm run --silent prepare:runtime -- --print-legacy-path) node "$legacy_path" --version | grep -F "$RUNTIME_VERSION" + echo "COPILOT_CLI_PATH=$runtime_path" >> "$GITHUB_ENV" - name: Build SDK run: npm run build @@ -324,6 +325,10 @@ jobs: - name: Build SDK run: npm run build + - name: Package public release runtimes + if: env.RUNTIME_SOURCE == 'public' + run: npm run pack:release + - name: Azure Login (OIDC -> id-cpd-ci) uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0 with: @@ -365,7 +370,32 @@ jobs: fi - name: Publish SDK canary to internal feed - run: npm publish --registry "$FEED_URL" + run: | + set -euo pipefail + if [ "$RUNTIME_SOURCE" = "internal" ]; then + npm publish --registry "$FEED_URL" + exit + fi + shopt -s nullglob + TARBALLS=(./github-copilot-sdk-*.tgz) + if [ "${#TARBALLS[@]}" -ne 9 ]; then + echo "::error::Expected nine Node.js package tarballs, found ${#TARBALLS[@]}." + exit 1 + fi + MAIN_TARBALL="" + for TARBALL in "${TARBALLS[@]}"; do + PACKAGE_NAME="$(tar -xOf "$TARBALL" package/package.json | jq -r .name)" + if [ "$PACKAGE_NAME" = "@github/copilot-sdk" ]; then + MAIN_TARBALL="$TARBALL" + else + npm publish "$TARBALL" --registry "$FEED_URL" + fi + done + if [ -z "$MAIN_TARBALL" ]; then + echo "::error::Main @github/copilot-sdk tarball not found." + exit 1 + fi + npm publish "$MAIN_TARBALL" --registry "$FEED_URL" - name: Summarize published canary env: diff --git a/nodejs/README.md b/nodejs/README.md index 465f7c9ce8..3a011eb57c 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -8,17 +8,20 @@ To use the SDK, you'll need: - Node.js ^20.19.0 or >=22.12.0 -The SDK downloads its pinned Copilot CLI runtime from the corresponding -`github/copilot-cli` GitHub Release on first use and caches it in the operating -system's user cache directory. Set `COPILOT_CLI_PATH` to use an existing -installation instead, or `COPILOT_CLI_DOWNLOAD_BASE_URL` to use a release -mirror. +The SDK uses an optional `@github/copilot-sdk-` package containing the +Copilot CLI runtime for the host platform. These packages are built from +verified `github/copilot-cli` release assets when the SDK is published, so +starting the SDK performs no runtime download. Set `COPILOT_CLI_PATH` to use an +existing installation instead. The checked-in release pin is `copilotCliVersion` in `package.json`. Run `npm run set:cli-version -- ` to update it and regenerate the trusted platform SHA-256 manifest in `copilot-cli.json` and the compiled metadata in `src/cliVersion.ts`. +`npm run pack:release` builds the main package and all platform packages. Set +`COPILOT_CLI_DOWNLOAD_BASE_URL` to use a release mirror while packaging. + ## Installation ```bash @@ -947,15 +950,15 @@ const session = await client.createSession({ The handler must return one of the `PermissionDecision` shapes (or `{ kind: "no-result" }`). Approval scopes are present-tense — they describe the decision to apply, not the outcome reported back on session events: -| Kind | Meaning | Extra fields | -| ------------------------ | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | -| `"approve-once"` | Allow this single request | — | -| `"approve-for-session"` | Allow this request and remember the approval for the rest of the session | `approval?` (rule to remember), `domain?` (for URL approvals) | -| `"approve-for-location"` | Allow this request and persist the approval for this project location (git root or cwd) | `approval` (rule to persist), `locationKey` (location to persist under) | -| `"approve-permanently"` | Allow this request and persist the approval across sessions (currently used for URL domains) | `domain` (URL domain to approve) | -| `"reject"` | Deny the request | `feedback?` (optional string surfaced to the agent) | -| `"user-not-available"` | Deny the request because no user is available to confirm it | — | -| `"no-result"` | Suppress this SDK client's response so another connected client can answer the pending request | — | +| Kind | Meaning | Extra fields | +| ------------------------ | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | +| `"approve-once"` | Allow this single request | — | +| `"approve-for-session"` | Allow this request and remember the approval for the rest of the session | `approval?` (rule to remember), `domain?` (for URL approvals) | +| `"approve-for-location"` | Allow this request and persist the approval for this project location (git root or cwd) | `approval` (rule to persist), `locationKey` (location to persist under) | +| `"approve-permanently"` | Allow this request and persist the approval across sessions (currently used for URL domains) | `domain` (URL domain to approve) | +| `"reject"` | Deny the request | `feedback?` (optional string surfaced to the agent) | +| `"user-not-available"` | Deny the request because no user is available to confirm it | — | +| `"no-result"` | Suppress this SDK client's response so another connected client can answer the pending request | — | ### Resuming Sessions diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index a22d9cb7b5..9119db7e3e 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -10,7 +10,6 @@ "license": "MIT", "dependencies": { "koffi": "^3.1.0", - "tar": "^7.5.22", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -29,6 +28,7 @@ "quicktype-core": "^23.2.6", "rimraf": "^6.1.2", "semver": "^7.7.3", + "tar": "^7.5.22", "tsx": "^4.20.6", "typescript": "^5.0.0", "vitest": "^4.0.18", @@ -714,6 +714,7 @@ "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "integrity": "sha1-LVmuOrSzj7QnC/oj0w+OLobH/jI=", + "dev": true, "license": "ISC", "dependencies": { "minipass": "^7.0.4" @@ -1809,6 +1810,7 @@ "node_modules/chownr": { "version": "3.0.0", "integrity": "sha1-mFXmTs0kCpzEJnzopKpdJKHaFeQ=", + "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" @@ -2888,6 +2890,7 @@ "node_modules/minipass": { "version": "7.1.3", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" @@ -2896,6 +2899,7 @@ "node_modules/minizlib": { "version": "3.1.0", "integrity": "sha1-atdsOo8QInybUdHJrI4wsn9aJRw=", + "dev": true, "license": "MIT", "dependencies": { "minipass": "^7.1.2" @@ -3387,6 +3391,7 @@ "node_modules/tar": { "version": "7.5.22", "integrity": "sha1-ppb5mBNucUh9w/hpqFu6LGeXG6k=", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -3820,6 +3825,7 @@ "node_modules/yallist": { "version": "5.0.0", "integrity": "sha1-AOLeRDY57Q14/YfeDSdGn7z/tTM=", + "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=18" diff --git a/nodejs/package.json b/nodejs/package.json index 076da46ca3..b8f575e3aa 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -35,6 +35,7 @@ "scripts": { "clean": "rimraf --glob dist *.tgz", "build": "tsx esbuild-copilotsdk-nodejs.ts", + "pack:release": "tsx scripts/package-sdk.ts", "prepare:runtime": "tsx scripts/prepare-runtime.ts", "test": "vitest run", "test:watch": "vitest", @@ -47,7 +48,7 @@ "set:cli-version": "node scripts/set-cli-version.js", "update:protocol-version": "tsx scripts/update-protocol-version.ts", "prepublishOnly": "npm run build", - "package": "npm run clean && npm run build && node scripts/set-version.js && npm pack && npm version 0.0.0-dev --no-git-tag-version --allow-same-version" + "package": "npm run clean && npm run build && node scripts/set-version.js && npm run pack:release && npm version 0.0.0-dev --no-git-tag-version --allow-same-version" }, "keywords": [ "github", @@ -60,7 +61,6 @@ "license": "MIT", "dependencies": { "koffi": "^3.1.0", - "tar": "^7.5.22", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -79,6 +79,7 @@ "quicktype-core": "^23.2.6", "rimraf": "^6.1.2", "semver": "^7.7.3", + "tar": "^7.5.22", "tsx": "^4.20.6", "typescript": "^5.0.0", "vitest": "^4.0.18", diff --git a/nodejs/scripts/package-sdk.ts b/nodejs/scripts/package-sdk.ts new file mode 100644 index 0000000000..9a44c97611 --- /dev/null +++ b/nodejs/scripts/package-sdk.ts @@ -0,0 +1,66 @@ +import { execFileSync } from "node:child_process"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { COPILOT_CLI_VERSION } from "../src/cliVersion.js"; +import { + getRuntimePackageName, + materializeRuntimeBundle, + RUNTIME_PLATFORMS, +} from "../src/runtimeArtifacts.js"; +import { ensureCopilotPackage } from "./releaseArtifacts.js"; + +const nodeRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); +const packagePath = join(nodeRoot, "package.json"); +const originalPackage = readFileSync(packagePath, "utf8"); +const packageJson = JSON.parse(originalPackage); +const sdkVersion = packageJson.version; +const requestedPlatforms = process.env.COPILOT_SDK_RUNTIME_PLATFORMS?.split(",").filter(Boolean); +const platforms = requestedPlatforms ?? [...RUNTIME_PLATFORMS]; +const stagingRoot = mkdtempSync(join(tmpdir(), "copilot-sdk-platform-packages-")); + +try { + const optionalDependencies: Record = {}; + for (const platform of platforms) { + if (!(RUNTIME_PLATFORMS as readonly string[]).includes(platform)) { + throw new Error(`Unsupported runtime platform: ${platform}`); + } + const releasePackage = await ensureCopilotPackage(COPILOT_CLI_VERSION, { platform }); + const runtimeRoot = dirname( + materializeRuntimeBundle( + { packageRoot: releasePackage, platform }, + stagingRoot, + platform + ) + ); + const packageName = getRuntimePackageName(platform); + const [osName, cpu] = platform.replace("linuxmusl", "linux").split("-"); + const runtimePackage = { + name: packageName, + version: sdkVersion, + description: `Platform runtime for @github/copilot-sdk (${platform})`, + license: "MIT", + os: [osName], + cpu: [cpu], + ...(platform.startsWith("linux") + ? { libc: [platform.startsWith("linuxmusl") ? "musl" : "glibc"] } + : {}), + }; + writeFileSync( + join(runtimeRoot, "package.json"), + `${JSON.stringify(runtimePackage, null, 4)}\n` + ); + execFileSync("npm", ["pack", runtimeRoot, "--pack-destination", nodeRoot], { + stdio: "inherit", + }); + optionalDependencies[packageName] = sdkVersion; + } + + packageJson.optionalDependencies = optionalDependencies; + writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 4)}\n`); + execFileSync("npm", ["pack", nodeRoot, "--pack-destination", nodeRoot], { stdio: "inherit" }); +} finally { + writeFileSync(packagePath, originalPackage); + rmSync(stagingRoot, { recursive: true, force: true }); +} diff --git a/nodejs/scripts/prepare-runtime.ts b/nodejs/scripts/prepare-runtime.ts index 4b499cb6f3..1cdfc0a6e7 100644 --- a/nodejs/scripts/prepare-runtime.ts +++ b/nodejs/scripts/prepare-runtime.ts @@ -1,13 +1,15 @@ import { join } from "node:path"; -import { ensureCopilotPackage, ensureRuntimeBundle } from "../src/runtimeArtifacts.js"; +import { getRuntimePlatform, materializeRuntimeBundle } from "../src/runtimeArtifacts.js"; import { COPILOT_CLI_VERSION } from "../src/cliVersion.js"; +import { ensureCopilotPackage } from "./releaseArtifacts.js"; const [option] = process.argv.slice(2); +const platform = getRuntimePlatform(); +const packageRoot = await ensureCopilotPackage(COPILOT_CLI_VERSION, { platform }); if (option === "--print-legacy-path") { - const packageRoot = await ensureCopilotPackage(COPILOT_CLI_VERSION); process.stdout.write(`${join(packageRoot, "app.js")}\n`); } else if (option === "--print-path" || option === undefined) { - const runtimePath = await ensureRuntimeBundle(COPILOT_CLI_VERSION); + const runtimePath = materializeRuntimeBundle({ packageRoot, platform }); process.stdout.write(`${runtimePath}\n`); } else { throw new Error(`Unknown option: ${option}`); diff --git a/nodejs/scripts/releaseArtifacts.ts b/nodejs/scripts/releaseArtifacts.ts new file mode 100644 index 0000000000..a7019fd5d1 --- /dev/null +++ b/nodejs/scripts/releaseArtifacts.ts @@ -0,0 +1,174 @@ +import { createHash } from "node:crypto"; +import { existsSync, mkdirSync, mkdtempSync, renameSync, rmSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { x as extractTar } from "tar"; +import { + defaultRuntimeCacheRoot, + getRuntimePlatform, + getRuntimeReleaseAssetName, + resolvePackageRoot, + validateFile, +} from "../src/runtimeArtifacts.js"; +import { + COPILOT_CLI_HASHES, + COPILOT_CLI_USE_NPM_PACKAGE, + COPILOT_CLI_VERSION, +} from "../src/cliVersion.js"; + +export interface EnsureCopilotPackageOptions { + cacheRoot?: string; + environment?: NodeJS.ProcessEnv; + expectedChecksum?: string; + fetch?: typeof globalThis.fetch; + platform?: string; +} + +const packageDownloads = new Map>(); + +async function fetchWithRetry(fetcher: typeof globalThis.fetch, url: string): Promise { + let lastError: unknown; + for (let attempt = 0; attempt < 3; attempt++) { + try { + const response = await fetcher(url); + if (response.ok) { + return response; + } + await response.body?.cancel(); + lastError = new Error(`${response.status} ${response.statusText}`); + if ( + response.status >= 400 && + response.status < 500 && + response.status !== 408 && + response.status !== 429 + ) { + break; + } + } catch (error) { + lastError = error; + } + if (attempt < 2) { + await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1000)); + } + } + throw new Error(`Failed to download ${url}: ${String(lastError)}`); +} + +export async function ensureCopilotPackage( + version = COPILOT_CLI_VERSION, + options: EnsureCopilotPackageOptions = {} +): Promise { + const platform = options.platform ?? getRuntimePlatform(); + if (version === COPILOT_CLI_VERSION && COPILOT_CLI_USE_NPM_PACKAGE) { + const packageName = `@github/copilot-${platform}`; + const packageRoot = resolvePackageRoot(packageName); + if (!packageRoot) { + throw new Error(`Could not resolve ${packageName} for Copilot CLI ${version}.`); + } + validateFile( + join(packageRoot, "prebuilds", platform, "runtime.node"), + "Copilot runtime.node" + ); + return packageRoot; + } + + const cacheRoot = options.cacheRoot ?? defaultRuntimeCacheRoot(); + const cachedPackageRoot = join(cacheRoot, version, "packages", platform); + const cachedRuntimeNode = join(cachedPackageRoot, "prebuilds", platform, "runtime.node"); + if (existsSync(cachedRuntimeNode)) { + validateFile(cachedRuntimeNode, "Copilot runtime.node"); + return cachedPackageRoot; + } + + const baseUrl = ( + (options.environment ?? process.env).COPILOT_CLI_DOWNLOAD_BASE_URL ?? + "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/github/copilot-cli/releases/download" + ).replace(/\/+$/, ""); + const key = `${cacheRoot}\0${version}\0${platform}\0${baseUrl}`; + if (!options.fetch) { + const existing = packageDownloads.get(key); + if (existing) { + return existing; + } + const download = downloadCopilotPackage( + version, + platform, + cacheRoot, + baseUrl, + globalThis.fetch, + options.expectedChecksum + ); + packageDownloads.set(key, download); + try { + return await download; + } finally { + packageDownloads.delete(key); + } + } + return downloadCopilotPackage( + version, + platform, + cacheRoot, + baseUrl, + options.fetch, + options.expectedChecksum + ); +} + +async function downloadCopilotPackage( + version: string, + platform: string, + cacheRoot: string, + baseUrl: string, + fetcher: typeof globalThis.fetch, + checksumOverride?: string +): Promise { + if (!fetcher) { + throw new Error("This Node.js runtime does not provide fetch()."); + } + const assetName = getRuntimeReleaseAssetName(version, platform); + const expectedChecksum = + checksumOverride ?? + (version === COPILOT_CLI_VERSION ? COPILOT_CLI_HASHES[platform] : undefined); + if (!expectedChecksum) { + throw new Error(`No trusted SHA-256 is pinned for ${assetName}.`); + } + const response = await fetchWithRetry(fetcher, `${baseUrl}/v${version}/${assetName}`); + const archive = Buffer.from(await response.arrayBuffer()); + const actualChecksum = createHash("sha256").update(archive).digest("hex"); + if (actualChecksum !== expectedChecksum) { + throw new Error( + `Checksum mismatch for ${assetName}: expected ${expectedChecksum}, got ${actualChecksum}.` + ); + } + + mkdirSync(cacheRoot, { recursive: true }); + const stagingRoot = mkdtempSync(join(cacheRoot, ".download-")); + const archivePath = join(stagingRoot, assetName); + const packageRoot = join(stagingRoot, "package"); + const cachedPackageRoot = join(cacheRoot, version, "packages", platform); + writeFileSync(archivePath, archive); + try { + await extractTar({ + cwd: stagingRoot, + file: archivePath, + gzip: true, + preservePaths: false, + strict: true, + }); + validateFile( + join(packageRoot, "prebuilds", platform, "runtime.node"), + "Copilot runtime.node" + ); + mkdirSync(dirname(cachedPackageRoot), { recursive: true }); + try { + renameSync(packageRoot, cachedPackageRoot); + } catch (error) { + if (!existsSync(cachedPackageRoot)) { + throw error; + } + } + return cachedPackageRoot; + } finally { + rmSync(stagingRoot, { recursive: true, force: true }); + } +} diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index d4c9a34ed1..51c3e6650d 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -370,8 +370,8 @@ function getNodeExecPath(): string { return process.execPath; } -function getBundledRuntimePath(environment: NodeJS.ProcessEnv = process.env): Promise { - return ensureRuntimeBundle(COPILOT_CLI_VERSION, { environment }); +function getBundledRuntimePath(): Promise { + return ensureRuntimeBundle(COPILOT_CLI_VERSION); } /** @@ -2561,7 +2561,7 @@ export class CopilotClient { * Start the CLI server process */ private async startCLIServer(): Promise { - this.resolvedCliPath ??= await getBundledRuntimePath(this.resolvedEnv); + this.resolvedCliPath ??= await getBundledRuntimePath(); return new Promise((resolve, reject) => { // Clear stderr buffer for fresh capture this.stderrBuffer = ""; @@ -2765,14 +2765,21 @@ export class CopilotClient { /** Starts the in-process FFI runtime with SDK-managed typed options. */ private async startInProcessFfi(): Promise { const explicitEntrypoint = this.resolvedEnv.COPILOT_CLI_PATH; - const runtimeLibrary = explicitEntrypoint - ? join( - dirname(resolve(explicitEntrypoint)), - "prebuilds", - CopilotClient.getNapiPrebuildsFolder(explicitEntrypoint), - "runtime.node" - ) - : join(dirname(await getBundledRuntimePath(this.resolvedEnv)), "runtime.node"); + let runtimeLibrary: string; + if (explicitEntrypoint) { + const entrypointDirectory = dirname(resolve(explicitEntrypoint)); + const adjacentRuntime = join(entrypointDirectory, "runtime.node"); + runtimeLibrary = existsSync(adjacentRuntime) + ? adjacentRuntime + : join( + entrypointDirectory, + "prebuilds", + CopilotClient.getNapiPrebuildsFolder(explicitEntrypoint), + "runtime.node" + ); + } else { + runtimeLibrary = join(dirname(await getBundledRuntimePath()), "runtime.node"); + } // Load the FFI host lazily so the native `koffi` addon (and its // platform-specific `koffi.node`) is only loaded on the in-process path; // out-of-process (stdio/tcp) consumers never touch the native dependency. diff --git a/nodejs/src/runtimeArtifacts.ts b/nodejs/src/runtimeArtifacts.ts index 8dd0500f37..37550a2a74 100644 --- a/nodejs/src/runtimeArtifacts.ts +++ b/nodejs/src/runtimeArtifacts.ts @@ -1,4 +1,3 @@ -import { createHash } from "node:crypto"; import { chmodSync, copyFileSync, @@ -10,17 +9,12 @@ import { renameSync, rmSync, statSync, - writeFileSync, } from "node:fs"; +import { createHash } from "node:crypto"; import { createRequire } from "node:module"; import { homedir } from "node:os"; import { dirname, join, relative, sep } from "node:path"; -import { x as extractTar } from "tar"; -import { - COPILOT_CLI_HASHES, - COPILOT_CLI_USE_NPM_PACKAGE, - COPILOT_CLI_VERSION, -} from "./cliVersion.js"; +import { COPILOT_CLI_USE_NPM_PACKAGE } from "./cliVersion.js"; export interface RuntimeArtifactSources { packageRoot: string; @@ -29,13 +23,21 @@ export interface RuntimeArtifactSources { export interface EnsureRuntimeBundleOptions { cacheRoot?: string; - environment?: NodeJS.ProcessEnv; - fetch?: typeof globalThis.fetch; + packageSearchPaths?: string[]; platform?: string; } -const runtimeDownloads = new Map>(); const require = createRequire(typeof __filename === "string" ? __filename : import.meta.url); +export const RUNTIME_PLATFORMS = [ + "darwin-arm64", + "darwin-x64", + "linux-arm64", + "linux-x64", + "linuxmusl-arm64", + "linuxmusl-x64", + "win32-arm64", + "win32-x64", +] as const; const EXCLUDED_TOP_LEVEL = new Set([ "app.js", @@ -64,7 +66,7 @@ interface RuntimeAsset { relativePath: string; } -function validateFile(path: string, label: string): void { +export function validateFile(path: string, label: string): void { if (!existsSync(path)) { throw new Error(`${label} not found at ${path}.`); } @@ -78,10 +80,6 @@ function validateRuntimeBundle(wrapper: string, runtimeNode: string): void { validateFile(runtimeNode, "Copilot runtime.node"); } -function sanitizeCacheSegment(value: string): string { - return value.replace(/[^a-zA-Z0-9._-]/g, "_"); -} - function isExcluded(relativePath: string): boolean { const parts = relativePath.split(sep); const topLevel = parts[0]; @@ -238,42 +236,17 @@ export function getRuntimeReleaseAssetName(version: string, platform: string): s return `github-copilot-${version}-${platform}.tgz`; } -async function fetchWithRetry(fetcher: typeof globalThis.fetch, url: string): Promise { - let lastError: unknown; - for (let attempt = 0; attempt < 3; attempt++) { - try { - const response = await fetcher(url); - if (response.ok) { - return response; - } - await response.body?.cancel(); - lastError = new Error(`${response.status} ${response.statusText}`); - if ( - response.status >= 400 && - response.status < 500 && - response.status !== 408 && - response.status !== 429 - ) { - break; - } - } catch (error) { - lastError = error; - } - if (attempt < 2) { - await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 1000)); - } - } - throw new Error(`Failed to download ${url}: ${String(lastError)}`); +export function getRuntimePackageName(platform: string): string { + return `@github/copilot-sdk-${platform}`; } -function checksumForAsset(checksums: string, assetName: string): string { - for (const line of checksums.split(/\r?\n/)) { - const [digest, name] = line.trim().split(/\s+/, 2); - if (name?.replace(/^\*/, "") === assetName && /^[a-f0-9]{64}$/i.test(digest)) { - return digest.toLowerCase(); - } - } - throw new Error(`SHA256SUMS.txt does not contain ${assetName}.`); +export function resolvePackageRoot( + packageName: string, + searchPaths = require.resolve.paths(packageName) ?? [] +): string | undefined { + return searchPaths + .map((base) => join(base, ...packageName.split("/"))) + .find((candidate) => existsSync(join(candidate, "package.json"))); } export async function ensureRuntimeBundle( @@ -281,59 +254,9 @@ export async function ensureRuntimeBundle( options: EnsureRuntimeBundleOptions = {} ): Promise { const platform = options.platform ?? getRuntimePlatform(); - const cacheRoot = options.cacheRoot ?? defaultRuntimeCacheRoot(); - const baseUrl = ( - (options.environment ?? process.env).COPILOT_CLI_DOWNLOAD_BASE_URL ?? - "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/github/copilot-cli/releases/download" - ).replace(/\/+$/, ""); - const downloadKey = `${cacheRoot}\0${version}\0${platform}\0${baseUrl}`; - if (!options.fetch) { - const existing = runtimeDownloads.get(downloadKey); - if (existing) { - return existing; - } - const download = ensureRuntimeBundleUncached(version, options); - runtimeDownloads.set(downloadKey, download); - try { - return await download; - } finally { - runtimeDownloads.delete(downloadKey); - } - } - return ensureRuntimeBundleUncached(version, options); -} - -async function ensureRuntimeBundleUncached( - version: string, - options: EnsureRuntimeBundleOptions -): Promise { - const platform = options.platform ?? getRuntimePlatform(); - const cacheRoot = options.cacheRoot ?? defaultRuntimeCacheRoot(); - const versionRoot = join(cacheRoot, sanitizeCacheSegment(version)); - const wrapperName = platform.startsWith("win32") ? "copilot-runtime.exe" : "copilot-runtime"; - const installedWrapper = join(versionRoot, platform, wrapperName); - const installedRuntimeNode = join(versionRoot, platform, "runtime.node"); - if (existsSync(installedWrapper) && existsSync(installedRuntimeNode)) { - validateRuntimeBundle(installedWrapper, installedRuntimeNode); - makeExecutable(installedWrapper); - return installedWrapper; - } - - const packageRoot = await ensureCopilotPackage(version, options); - return materializeRuntimeBundle({ packageRoot, platform }, versionRoot, platform); -} - -export async function ensureCopilotPackage( - version: string, - options: EnsureRuntimeBundleOptions = {} -): Promise { - const environment = options.environment ?? process.env; - const platform = options.platform ?? getRuntimePlatform(); - if (version === COPILOT_CLI_VERSION && COPILOT_CLI_USE_NPM_PACKAGE) { + if (COPILOT_CLI_USE_NPM_PACKAGE) { const packageName = `@github/copilot-${platform}`; - const packageRoot = (require.resolve.paths(packageName) ?? []) - .map((base) => join(base, ...packageName.split("/"))) - .find((candidate) => existsSync(join(candidate, "index.js"))); + const packageRoot = resolvePackageRoot(packageName, options.packageSearchPaths); if (!packageRoot) { throw new Error(`Could not resolve ${packageName} for Copilot CLI ${version}.`); } @@ -341,72 +264,30 @@ export async function ensureCopilotPackage( join(packageRoot, "prebuilds", platform, "runtime.node"), "Copilot runtime.node" ); - return packageRoot; - } - const cacheRoot = options.cacheRoot ?? defaultRuntimeCacheRoot(); - const versionRoot = join(cacheRoot, sanitizeCacheSegment(version)); - const cachedPackageRoot = join(versionRoot, "packages", platform); - const cachedRuntimeNode = join(cachedPackageRoot, "prebuilds", platform, "runtime.node"); - if (existsSync(cachedRuntimeNode)) { - validateFile(cachedRuntimeNode, "Copilot runtime.node"); - return cachedPackageRoot; + return materializeRuntimeBundle( + { packageRoot, platform }, + options.cacheRoot, + `${version}-${platform}` + ); } - const fetcher = options.fetch ?? globalThis.fetch; - if (!fetcher) { - throw new Error("This Node.js runtime does not provide fetch()."); - } - mkdirSync(cacheRoot, { recursive: true }); - const baseUrl = ( - environment.COPILOT_CLI_DOWNLOAD_BASE_URL ?? - "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/github/copilot-cli/releases/download" - ).replace(/\/+$/, ""); - const releaseUrl = `${baseUrl}/v${version}`; - const assetName = getRuntimeReleaseAssetName(version, platform); - const pinnedChecksum = - version === COPILOT_CLI_VERSION ? COPILOT_CLI_HASHES[platform] : undefined; - const [checksumsResponse, assetResponse] = await Promise.all([ - pinnedChecksum - ? Promise.resolve(undefined) - : fetchWithRetry(fetcher, `${releaseUrl}/SHA256SUMS.txt`), - fetchWithRetry(fetcher, `${releaseUrl}/${assetName}`), - ]); - const archive = Buffer.from(await assetResponse.arrayBuffer()); - const expectedChecksum = - pinnedChecksum ?? checksumForAsset(await checksumsResponse!.text(), assetName); - const actualChecksum = createHash("sha256").update(archive).digest("hex"); - if (actualChecksum !== expectedChecksum) { + const packageName = getRuntimePackageName(platform); + const packageRoot = resolvePackageRoot(packageName, options.packageSearchPaths); + if (!packageRoot) { throw new Error( - `Checksum mismatch for ${assetName}: expected ${expectedChecksum}, got ${actualChecksum}.` + `Could not resolve ${packageName}. Reinstall @github/copilot-sdk so its platform package is installed.` ); } - - const stagingRoot = mkdtempSync(join(cacheRoot, ".download-")); - const archivePath = join(stagingRoot, assetName); - const packageRoot = join(stagingRoot, "package"); - writeFileSync(archivePath, archive); + const wrapperName = platform.startsWith("win32") ? "copilot-runtime.exe" : "copilot-runtime"; + const wrapper = join(packageRoot, wrapperName); try { - await extractTar({ - cwd: stagingRoot, - file: archivePath, - gzip: true, - preservePaths: false, - strict: true, - }); - validateFile( - join(packageRoot, "prebuilds", platform, "runtime.node"), - "Copilot runtime.node" + validateRuntimeBundle(wrapper, join(packageRoot, "runtime.node")); + } catch (error) { + throw new Error( + `${packageName} is missing required Copilot CLI runtime files. Reinstall @github/copilot-sdk.`, + { cause: error } ); - mkdirSync(dirname(cachedPackageRoot), { recursive: true }); - try { - renameSync(packageRoot, cachedPackageRoot); - } catch (error) { - if (!existsSync(cachedRuntimeNode)) { - throw error; - } - } - return cachedPackageRoot; - } finally { - rmSync(stagingRoot, { recursive: true, force: true }); } + makeExecutable(wrapper); + return wrapper; } diff --git a/nodejs/test/e2e/harness/sdkTestContext.ts b/nodejs/test/e2e/harness/sdkTestContext.ts index 4eb937b5c4..c4befb148e 100644 --- a/nodejs/test/e2e/harness/sdkTestContext.ts +++ b/nodejs/test/e2e/harness/sdkTestContext.ts @@ -12,7 +12,7 @@ import { afterAll, afterEach, beforeEach, onTestFailed, TestContext } from "vite import { CopilotClient, CopilotClientOptions, RuntimeConnection } from "../../../src"; import { CapiProxy } from "./CapiProxy"; import { formatError, retry } from "./sdkTestHelper"; -import { ensureCopilotPackage } from "../../../src/runtimeArtifacts"; +import { ensureCopilotPackage } from "../../../scripts/releaseArtifacts"; import { COPILOT_CLI_VERSION } from "../../../src/cliVersion"; export const isCI = process.env.GITHUB_ACTIONS === "true"; diff --git a/nodejs/test/runtimeArtifacts.test.ts b/nodejs/test/runtimeArtifacts.test.ts index fbe83471ea..c534e8e2e3 100644 --- a/nodejs/test/runtimeArtifacts.test.ts +++ b/nodejs/test/runtimeArtifacts.test.ts @@ -9,6 +9,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { defaultRuntimeCacheRoot, ensureRuntimeBundle, + getRuntimePackageName, getRuntimePlatform, getRuntimeReleaseAssetName, materializeRuntimeBundle, @@ -18,6 +19,7 @@ import { COPILOT_CLI_USE_NPM_PACKAGE, COPILOT_CLI_VERSION, } from "../src/cliVersion.js"; +import { ensureCopilotPackage } from "../scripts/releaseArtifacts.js"; describe("defaultRuntimeCacheRoot", () => { it.each([ @@ -114,6 +116,10 @@ describe("release runtime selection", () => { "github-copilot-1.2.3-4-linux-x64.tgz" ); }); + + it("uses the SDK platform package namespace", () => { + expect(getRuntimePackageName("linux-x64")).toBe("@github/copilot-sdk-linux-x64"); + }); }); describe("materializeRuntimeBundle", () => { @@ -194,7 +200,47 @@ describe("materializeRuntimeBundle", () => { }); describe("ensureRuntimeBundle", () => { - it("downloads, verifies, and caches a release runtime", async () => { + it("resolves the installed platform runtime without network access", async () => { + const root = mkdtempSync(join(tmpdir(), "copilot-packaged-runtime-")); + const nodeModules = join(root, "node_modules"); + const platform = "linux-x64"; + const packageRoot = join(nodeModules, ...getRuntimePackageName(platform).split("/")); + mkdirSync(packageRoot, { recursive: true }); + writeFileSync(join(packageRoot, "package.json"), "{}"); + writeFileSync(join(packageRoot, "copilot-runtime"), "wrapper"); + writeFileSync(join(packageRoot, "runtime.node"), "runtime"); + mkdirSync(join(packageRoot, "schemas")); + writeFileSync(join(packageRoot, "schemas", "api.schema.json"), "{}"); + const fetcher = vi.fn(() => { + throw new Error("runtime resolution must not fetch"); + }); + vi.stubGlobal("fetch", fetcher); + + const runtimePath = await ensureRuntimeBundle(COPILOT_CLI_VERSION, { + packageSearchPaths: [nodeModules], + platform, + }); + + expect(runtimePath).toBe(join(packageRoot, "copilot-runtime")); + expect(readFileSync(join(dirname(runtimePath), "runtime.node"), "utf8")).toBe("runtime"); + expect(fetcher).not.toHaveBeenCalled(); + vi.unstubAllGlobals(); + }); + + it("fails clearly when the platform package is not installed", async () => { + await expect( + ensureRuntimeBundle(COPILOT_CLI_VERSION, { + packageSearchPaths: [], + platform: "linux-x64", + }) + ).rejects.toThrow( + "Could not resolve @github/copilot-sdk-linux-x64. Reinstall @github/copilot-sdk" + ); + }); +}); + +describe("release package acquisition", () => { + it("downloads, verifies, and caches a release package for packaging", async () => { const sourceRoot = mkdtempSync(join(tmpdir(), "copilot-release-source-")); const packageRoot = join(sourceRoot, "package"); const platform = "linux-x64"; @@ -209,46 +255,43 @@ describe("ensureRuntimeBundle", () => { await createTar({ cwd: sourceRoot, file: archivePath, gzip: true }, ["package"]); const archive = readFileSync(archivePath); const version = "1.2.3-4"; - const assetName = getRuntimeReleaseAssetName(version, platform); const checksum = createHash("sha256").update(archive).digest("hex"); - const fetcher = vi.fn(async (url: string | URL | Request) => { - const value = String(url); - return value.endsWith("SHA256SUMS.txt") - ? new Response(`${checksum} ${assetName}\n`) - : new Response(archive); - }); + const fetcher = vi.fn(async () => new Response(archive)); const cacheRoot = join(sourceRoot, "cache"); - const runtimePath = await ensureRuntimeBundle(version, { + const downloadedPackage = await ensureCopilotPackage(version, { cacheRoot, + expectedChecksum: checksum, fetch: fetcher, platform, }); - expect(readFileSync(runtimePath, "utf8")).toBe("wrapper"); - expect(readFileSync(join(dirname(runtimePath), "runtime.node"), "utf8")).toBe("runtime"); - expect(readFileSync(join(dirname(runtimePath), "schemas", "api.schema.json"), "utf8")).toBe( + expect(readFileSync(join(downloadedPackage, "schemas", "api.schema.json"), "utf8")).toBe( "{}" ); await expect( - ensureRuntimeBundle(version, { cacheRoot, fetch: fetcher, platform }) - ).resolves.toBe(runtimePath); - expect(fetcher).toHaveBeenCalledTimes(2); + ensureCopilotPackage(version, { + cacheRoot, + expectedChecksum: checksum, + fetch: fetcher, + platform, + }) + ).resolves.toBe(downloadedPackage); + expect(fetcher).toHaveBeenCalledTimes(1); }); - it("rejects a release archive that does not match the manifest", async () => { + it("rejects a release package that does not match the trusted hash", async () => { const cacheRoot = mkdtempSync(join(tmpdir(), "copilot-release-mismatch-")); - const fetcher = vi.fn(async (url: string | URL | Request) => - String(url).endsWith("SHA256SUMS.txt") - ? new Response( - `${"0".repeat(64)} ${getRuntimeReleaseAssetName("1.2.3", "linux-x64")}\n` - ) - : new Response("corrupt archive") - ); + const fetcher = vi.fn(async () => new Response("corrupt archive")); await expect( - ensureRuntimeBundle("1.2.3", { cacheRoot, fetch: fetcher, platform: "linux-x64" }) + ensureCopilotPackage("1.2.3", { + cacheRoot, + expectedChecksum: "0".repeat(64), + fetch: fetcher, + platform: "linux-x64", + }) ).rejects.toThrow("Checksum mismatch"); - expect(existsSync(join(cacheRoot, "1.2.3", "linux-x64"))).toBe(false); + expect(existsSync(join(cacheRoot, "1.2.3", "packages", "linux-x64"))).toBe(false); }); }); diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index cab25ff0e6..bad31a7675 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -13,7 +13,7 @@ import path from "path"; import { fileURLToPath } from "url"; import { promisify } from "util"; import { COPILOT_CLI_VERSION } from "../../nodejs/src/cliVersion.js"; -import { ensureRuntimeBundle } from "../../nodejs/src/runtimeArtifacts.js"; +import { ensureCopilotPackage } from "../../nodejs/scripts/releaseArtifacts.js"; export const execFileAsync = promisify(execFile); @@ -51,8 +51,8 @@ export type SchemaWithSharedDefinitions = T * Resolve a JSON schema from the pinned Copilot CLI GitHub Release. */ async function resolveCopilotSchemaPath(fileName: string): Promise { - const runtimePath = await ensureRuntimeBundle(COPILOT_CLI_VERSION); - const schemaPath = path.join(path.dirname(runtimePath), "schemas", fileName); + const packageRoot = await ensureCopilotPackage(COPILOT_CLI_VERSION); + const schemaPath = path.join(packageRoot, "schemas", fileName); await fs.access(schemaPath); return schemaPath; } From 70abcaea23d9eecee2daae811c1ebe2666eab7fc Mon Sep 17 00:00:00 2001 From: Devraj Mehta Date: Tue, 1 Sep 2026 15:18:54 -0400 Subject: [PATCH 04/30] Use release checksum manifests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3043824d-becf-4b5d-b62b-4754511894f7 --- .github/workflows/required-checks.yml | 10 +-- .../workflows/update-copilot-dependency.yml | 4 +- java/README.md | 4 +- java/copilot-native/pom.xml | 2 +- java/copilot-native/scripts/fetch-native.mjs | 27 ++++--- .../scripts/fetch-native.test.mjs | 8 +-- nodejs/README.md | 6 +- nodejs/copilot-cli.json | 22 ------ nodejs/scripts/releaseArtifacts.ts | 66 +++++++++++------ nodejs/scripts/set-cli-version.js | 32 ++------- nodejs/src/cliVersion.ts | 11 --- nodejs/test/runtimeArtifacts.test.ts | 45 +++++------- rust/README.md | 4 +- rust/build/in_process.rs | 71 +++++++++++-------- rust/build/out_of_process.rs | 65 ++++++++++------- rust/scripts/snapshot-bundled-cli-version.sh | 19 ++--- .../snapshot-bundled-in-process-version.sh | 17 +++-- rust/tests/cli_resolution_test.rs | 3 +- 18 files changed, 206 insertions(+), 210 deletions(-) delete mode 100644 nodejs/copilot-cli.json diff --git a/.github/workflows/required-checks.yml b/.github/workflows/required-checks.yml index 33848d53a9..ef9d666f05 100644 --- a/.github/workflows/required-checks.yml +++ b/.github/workflows/required-checks.yml @@ -44,35 +44,35 @@ jobs: - '!**/.editorconfig' - '!**/*.{png,jpg,jpeg,gif,svg}' python: - - '{python/**,test/**,nodejs/package.json,nodejs/copilot-cli.json,.github/workflows/python-sdk-tests.yml}' + - '{python/**,test/**,nodejs/package.json,.github/workflows/python-sdk-tests.yml}' - '!**/*.md' - '!**/LICENSE*' - '!**/.gitignore' - '!**/.editorconfig' - '!**/*.{png,jpg,jpeg,gif,svg}' go: - - '{go/**,test/**,nodejs/package.json,nodejs/copilot-cli.json,.github/workflows/go-sdk-tests.yml,.github/actions/setup-copilot/**}' + - '{go/**,test/**,nodejs/package.json,.github/workflows/go-sdk-tests.yml,.github/actions/setup-copilot/**}' - '!**/*.md' - '!**/LICENSE*' - '!**/.gitignore' - '!**/.editorconfig' - '!**/*.{png,jpg,jpeg,gif,svg}' dotnet: - - '{dotnet/**,test/**,nodejs/package.json,nodejs/copilot-cli.json,.github/workflows/dotnet-sdk-tests.yml}' + - '{dotnet/**,test/**,nodejs/package.json,.github/workflows/dotnet-sdk-tests.yml}' - '!**/*.md' - '!**/LICENSE*' - '!**/.gitignore' - '!**/.editorconfig' - '!**/*.{png,jpg,jpeg,gif,svg}' java: - - '{java/**,test/**,nodejs/copilot-cli.json,.github/workflows/java-sdk-tests.yml,.github/actions/setup-copilot/**,.github/actions/java-test-report/**}' + - '{java/**,test/**,nodejs/package.json,.github/workflows/java-sdk-tests.yml,.github/actions/setup-copilot/**,.github/actions/java-test-report/**}' - '!**/*.md' - '!**/LICENSE*' - '!**/.gitignore' - '!**/.editorconfig' - '!**/*.{png,jpg,jpeg,gif,svg}' rust: - - '{rust/**,test/**,nodejs/package.json,nodejs/copilot-cli.json,.github/workflows/rust-sdk-tests.yml,.github/actions/setup-copilot/**}' + - '{rust/**,test/**,nodejs/package.json,.github/workflows/rust-sdk-tests.yml,.github/actions/setup-copilot/**}' - '!**/*.md' - '!**/LICENSE*' - '!**/.gitignore' diff --git a/.github/workflows/update-copilot-dependency.yml b/.github/workflows/update-copilot-dependency.yml index 43a6c618b2..2870ad27f7 100644 --- a/.github/workflows/update-copilot-dependency.yml +++ b/.github/workflows/update-copilot-dependency.yml @@ -163,7 +163,7 @@ jobs: git commit -m "Update Copilot CLI to $VERSION - - Updated the Node.js CLI release pin and trusted hashes + - Updated the Node.js CLI release pin - Re-ran code generators - Formatted generated code" @@ -174,7 +174,7 @@ jobs: ### Changes - Updated the release pin in `nodejs/package.json` - - Updated trusted release hashes in `nodejs/copilot-cli.json` + - Validated the release assets listed in `SHA256SUMS.txt` - Re-ran all code generators (`scripts/codegen`) - Formatted generated output - Updated Java codegen dependency, POM property, and regenerated Java types diff --git a/java/README.md b/java/README.md index f01911c159..356c1c5c1d 100644 --- a/java/README.md +++ b/java/README.md @@ -548,9 +548,9 @@ mvn jacoco:prepare-agent@wire-up-coverage-instrumentation antrun:run@print-test- #### Development Setup for native embedding -Run native-runtime Maven commands from the `java` directory. Native packaging requires Node.js and npm in addition to JDK 25 and Maven because `copilot-native/scripts/fetch-native.mjs` retrieves the pinned npm runtime package. +Run native-runtime Maven commands from the `java` directory. Native packaging requires Node.js in addition to JDK 25 and Maven because `copilot-native/scripts/fetch-native.mjs` retrieves the pinned runtime package from the corresponding GitHub release. -On a native Linux glibc host, Maven activates `native-linux-x64` or `native-linux-arm64` for the matching architecture when `copilot.native.libc=glibc` is set. On Windows x64, Windows ARM64, and Apple Silicon macOS, Maven activates `native-win32-x64`, `native-win32-arm64`, or `native-darwin-arm64` automatically. The matching profile validates the host, runs the native script tests, fetches the pinned `@github/copilot-` package during `generate-resources`, packages the classifier JAR during `package`, and verifies its native contents. Ensure npm can authenticate to the package registry before running the build. +On a native Linux glibc host, Maven activates `native-linux-x64` or `native-linux-arm64` for the matching architecture when `copilot.native.libc=glibc` is set. On Windows x64, Windows ARM64, and Apple Silicon macOS, Maven activates `native-win32-x64`, `native-win32-arm64`, or `native-darwin-arm64` automatically. The matching profile validates the host, runs the native script tests, fetches the pinned platform package from the corresponding `github/copilot-cli` release during `generate-resources`, packages the classifier JAR during `package`, and verifies its native contents. Before opting in, validate that Node.js reports glibc for the build host: diff --git a/java/copilot-native/pom.xml b/java/copilot-native/pom.xml index b31ba138ba..8dff6c4ad6 100644 --- a/java/copilot-native/pom.xml +++ b/java/copilot-native/pom.xml @@ -59,7 +59,7 @@