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/dotnet-sdk-tests.yml b/.github/workflows/dotnet-sdk-tests.yml index 0acf807f0a..9b7216ad20 100644 --- a/.github/workflows/dotnet-sdk-tests.yml +++ b/.github/workflows/dotnet-sdk-tests.yml @@ -104,43 +104,65 @@ jobs: - os: macos-latest transport: default backend: capi - shard: "2b1a-permission-provider" + shard: "2b-pending" - os: macos-latest transport: default backend: capi - shard: "2b1a-rewind" + shard: "2b-permission" - os: macos-latest transport: default backend: capi - shard: "2b1a-rpc-a" + shard: "2b-auth" - os: macos-latest transport: default backend: capi - shard: "2b1a-rpc-e" + shard: "2b-hooks" - os: macos-latest transport: default backend: capi - shard: "2b1b-mcp-and-skills" + shard: "2b-unit-p" - os: macos-latest transport: default backend: capi - shard: "2b1b-mcp-config" + shard: "2b-provider" - os: macos-latest transport: default backend: capi - shard: "2b1b-mcp-lifecycle" + shard: "2b-rpc-additional" - os: macos-latest transport: default backend: capi - shard: "2b1b-queue" + shard: "2b-rpc-agent" - os: macos-latest transport: default backend: capi - shard: "2b1b-remote" + shard: "2b-rpc-event-log" - os: macos-latest transport: default backend: capi - shard: "2b2" + shard: "2b-rpc-event-effects" + - os: macos-latest + transport: default + backend: capi + shard: "2b-rpc-mcp-skills" + - os: macos-latest + transport: default + backend: capi + shard: "2b-rpc-mcp-config" + - os: macos-latest + transport: default + backend: capi + shard: "2b-rpc-mcp-lifecycle" + - os: macos-latest + transport: default + backend: capi + shard: "2b-rpc-q-z" + # Standalone extension tests require the legacy JavaScript entrypoint. + # Keep its subprocess tree isolated from the rest of the O/P/R shard. + - os: macos-latest + transport: default + backend: capi + shard: extensions - os: macos-latest transport: default backend: capi @@ -223,7 +245,6 @@ jobs: ) filter="$DOTNET_TEST_FILTER" - individual_filters=() if [[ "$DOTNET_TEST_SHARD" != "full" ]]; then case "$DOTNET_TEST_SHARD" in 1) @@ -240,54 +261,65 @@ jobs: initials=(B E F G) shard_filter="" ;; - 2b1a-permission-provider) + 2b-pending) initials=() - shard_filter="" - individual_filters=( - "FullyQualifiedName~GitHub.Copilot.Test.E2E.PendingWorkResumeE2ETests" - "FullyQualifiedName~GitHub.Copilot.Test.E2E.PerSessionAuthE2ETests" - "FullyQualifiedName~GitHub.Copilot.Test.E2E.PermissionE2ETests" - "FullyQualifiedName~GitHub.Copilot.Test.E2E.PreMcpToolCallHookE2ETests" - "FullyQualifiedName~GitHub.Copilot.Test.E2E.ProviderEndpointE2ETests" - "FullyQualifiedName~GitHub.Copilot.Test.Unit.PermissionHandlerTests" - "FullyQualifiedName~GitHub.Copilot.Test.Unit.PublicDtoTests" - ) + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.PendingWorkResumeE2ETests" ;; - 2b1a-rewind) + 2b-permission) initials=() - shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.Rewind" + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.PermissionE2ETests" ;; - 2b1a-rpc-a) + 2b-auth) initials=() - shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcA" + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.PerSessionAuthE2ETests" ;; - 2b1a-rpc-e) + 2b-hooks) initials=() - shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcE" + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.PreMcpToolCallHookE2ETests" ;; - 2b1b-mcp-and-skills) + 2b-unit-p) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.Unit.P" + ;; + 2b-provider) + initials=(O) + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.ProviderEndpointE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RewindE2ETests|FullyQualifiedName~GitHub.Copilot.Test.Unit.R" + ;; + 2b-rpc-additional) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcAdditionalEdgeCasesE2ETests" + ;; + 2b-rpc-agent) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcAgentE2ETests" + ;; + 2b-rpc-event-log) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcEventLogE2ETests" + ;; + 2b-rpc-event-effects) + initials=() + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcEventSideEffectsE2ETests" + ;; + 2b-rpc-mcp-skills) initials=() shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcMcpAndSkillsE2ETests" ;; - 2b1b-mcp-config) + 2b-rpc-mcp-config) initials=() shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcMcpConfigE2ETests" ;; - 2b1b-mcp-lifecycle) + 2b-rpc-mcp-lifecycle) initials=() shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcMcpLifecycleE2ETests" ;; - 2b1b-queue) + 2b-rpc-q-z) initials=() - shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcQ" + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcQueueE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcRemoteE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcScheduleE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcServerE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcServerMiscE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcServerPluginsE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcServerRemoteControlE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcSessionStateE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcSessionStateExtrasE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcShellAndFleetE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcShellEdgeCaseE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcShellUserRequestedE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcTasksAndHandlersE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcUiEphemeralQueryE2ETests|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcWorkspaceCheckpointsE2ETests" ;; - 2b1b-remote) + extensions) initials=() - shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcR" - ;; - 2b2) - initials=() - shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcS|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcT|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcU|FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcW|FullyQualifiedName~GitHub.Copilot.Test.Unit.R" + shard_filter="FullyQualifiedName~GitHub.Copilot.Test.E2E.RpcExtensionsLoadedE2ETests" ;; 2c) initials=(T V X Z) @@ -295,14 +327,6 @@ jobs: ;; esac - if (( ${#individual_filters[@]} > 0 )); then - for individual_filter in "${individual_filters[@]}"; do - combined_filter="${filter:+(${filter})&}(${individual_filter})" - dotnet test test/GitHub.Copilot.SDK.Test.csproj "${args[@]}" --filter "$combined_filter" - done - exit 0 - fi - for namespace in E2E Unit; do for initial in "${initials[@]}"; do clause="FullyQualifiedName~GitHub.Copilot.Test.${namespace}.${initial}" 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/nodejs-sdk-tests.yml b/.github/workflows/nodejs-sdk-tests.yml index 1738828e6b..894f3b5d0d 100644 --- a/.github/workflows/nodejs-sdk-tests.yml +++ b/.github/workflows/nodejs-sdk-tests.yml @@ -49,6 +49,12 @@ jobs: - name: Build SDK run: npm run build + - name: Build and verify release packages + if: runner.os == 'Linux' && matrix.transport == 'default' + run: | + npm run pack:release + npm run verify:release-packages + - name: Install test harness dependencies working-directory: ./test/harness run: npm ci --ignore-scripts @@ -58,6 +64,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 98bf236900..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 @@ -327,9 +360,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/required-checks.yml b/.github/workflows/required-checks.yml index f4083dd307..0fb8acf70b 100644 --- a/.github/workflows/required-checks.yml +++ b/.github/workflows/required-checks.yml @@ -37,42 +37,41 @@ jobs: - '.github/workflows/required-checks.yml' nodejs: - '{nodejs/**,test/**,.github/workflows/nodejs-sdk-tests.yml}' - - '!nodejs/scripts/**' - '!**/*.md' - '!**/LICENSE*' - '!**/.gitignore' - '!**/.editorconfig' - '!**/*.{png,jpg,jpeg,gif,svg}' python: - - '{python/**,test/**,nodejs/package.json,.github/workflows/python-sdk-tests.yml}' + - '{python/**,test/**,nodejs/package.json,nodejs/scripts/**,.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/scripts/**,.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/scripts/**,.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/package.json,nodejs/scripts/**,.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/scripts/**,.github/workflows/rust-sdk-tests.yml,.github/actions/setup-copilot/**}' - '!**/*.md' - '!**/LICENSE*' - '!**/.gitignore' diff --git a/.github/workflows/rust-sdk-tests.yml b/.github/workflows/rust-sdk-tests.yml index da030ff999..828459126b 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" @@ -119,11 +119,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" @@ -168,11 +168,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..7425f8e720 100644 --- a/.github/workflows/sdk-canary.yml +++ b/.github/workflows/sdk-canary.yml @@ -1,391 +1,428 @@ -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-cli release (public) or @github/copilot package version (internal)" + 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 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 + 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: | + set -euo pipefail + runtime_path=$(npm run --silent prepare:runtime -- --print-path) + node -e " + const fs = require('node:fs'); + const path = require('node:path'); + const runtime = process.argv[1]; + const runtimeStat = fs.statSync(runtime); + if (!runtimeStat.isFile()) throw new Error('Runtime wrapper is not a file'); + if (process.platform !== 'win32' && (runtimeStat.mode & 0o111) === 0) { + throw new Error('Runtime wrapper is not executable'); + } + if (!fs.statSync(path.join(path.dirname(runtime), 'runtime.node')).isFile()) { + throw new Error('runtime.node is not adjacent to the runtime wrapper'); + } + " "$runtime_path" + 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 + + - 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 }} + RUNTIME_SOURCE: ${{ needs.resolve.outputs.RUNTIME_SOURCE }} + 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 + 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 + 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: + 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: | + set -euo pipefail + if [ "$RUNTIME_SOURCE" = "internal" ]; then + node scripts/npm-release.js publish . canary "$FEED_URL" azure + 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 + node scripts/npm-release.js publish "$TARBALL" canary "$FEED_URL" azure + fi + done + if [ -z "$MAIN_TARBALL" ]; then + echo "::error::Main @github/copilot-sdk tarball not found." + exit 1 + fi + node scripts/npm-release.js publish "$MAIN_TARBALL" canary "$FEED_URL" azure + + - name: Summarize published canary + env: + SDK_VERSION: ${{ steps.sdkver.outputs.SDK_VERSION }} + run: | + set -euo pipefail + { + echo "## SDK canary published" + echo "" + echo "| | |" + echo "| --- | --- |" + if [ "$RUNTIME_SOURCE" = "public" ]; then + echo "| Runtime consumed | \`github/copilot-cli@${RUNTIME_VERSION}\` release assets |" + else + echo "| Runtime consumed | \`@github/copilot@${RUNTIME_VERSION}\` |" + fi + 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..2870ad27f7 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 @@ -165,19 +161,20 @@ jobs: exit 0 fi - git commit -m "Update @github/copilot to $VERSION + git commit -m "Update Copilot CLI to $VERSION - - Updated nodejs and test harness dependencies + - Updated the Node.js CLI release pin - 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 `@github/copilot` in `nodejs/package.json` and `test/harness/package.json` + - Updated the release pin in `nodejs/package.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 @@ -208,7 +205,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/Client.cs b/dotnet/src/Client.cs index 92650ee60c..9bd7a5fb54 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -2544,17 +2544,23 @@ private static string ResolveRuntimePathForExplicitCli(string cliPath) var fullEntrypoint = Path.GetFullPath(cliPath); var directory = Path.GetDirectoryName(fullEntrypoint) ?? throw new InvalidOperationException($"Could not determine directory for '{cliPath}'."); - var flatLibraryPath = Path.Combine(directory, FfiRuntimeHost.GetRuntimeLibraryFileName()); + var flatLibraryPath = Path.GetFullPath( + $"{directory}{Path.DirectorySeparatorChar}{FfiRuntimeHost.GetRuntimeLibraryFileName()}"); if (File.Exists(flatLibraryPath)) { return flatLibraryPath; } + var adjacentPrebuildPath = Path.Combine(directory, "runtime.node"); + if (File.Exists(adjacentPrebuildPath)) + { + return adjacentPrebuildPath; + } var prebuildsLibraryPath = Path.Combine( directory, "prebuilds", GetNapiPrebuildsFolderOrThrow(), "runtime.node"); return File.Exists(prebuildsLibraryPath) ? prebuildsLibraryPath : throw new InvalidOperationException( - $"FFI runtime library not found. Looked for '{flatLibraryPath}' and '{prebuildsLibraryPath}'."); + $"FFI runtime library not found. Looked for '{flatLibraryPath}', '{adjacentPrebuildPath}', and '{prebuildsLibraryPath}'."); } /// 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/dotnet/src/build/GitHub.Copilot.SDK.targets b/dotnet/src/build/GitHub.Copilot.SDK.targets index 95770dba8e..aca299dd27 100644 --- a/dotnet/src/build/GitHub.Copilot.SDK.targets +++ b/dotnet/src/build/GitHub.Copilot.SDK.targets @@ -153,7 +153,7 @@ <_CopilotRuntimeRootAsset Include="$(_CopilotCacheDir)\**\*" - Exclude="$(_CopilotCacheDir)\app.js;$(_CopilotCacheDir)\assets\**\*;$(_CopilotCacheDir)\changelog.json;$(_CopilotCacheDir)\copilot;$(_CopilotCacheDir)\copilot.exe;$(_CopilotCacheDir)\copilot-sdk\**\*;$(_CopilotCacheDir)\copilot.tgz;$(_CopilotCacheDir)\foundry-local-sdk\**\*;$(_CopilotCacheDir)\index.js;$(_CopilotCacheDir)\LICENSE.md;$(_CopilotCacheDir)\napi-oop-runtime\**\*;$(_CopilotCacheDir)\npm-loader.js;$(_CopilotCacheDir)\package.json;$(_CopilotCacheDir)\prebuilds\**\*;$(_CopilotCacheDir)\preloads\**\*;$(_CopilotCacheDir)\pvrecorder\**\*;$(_CopilotCacheDir)\queries\**\*;$(_CopilotCacheDir)\README.md;$(_CopilotCacheDir)\sdk\**\*;$(_CopilotCacheDir)\sea-loader.js;$(_CopilotCacheDir)\tree-sitter*.wasm;$(_CopilotCacheDir)\voice-*.js;$(_CopilotCacheDir)\webview\**\*" /> + Exclude="$(_CopilotCacheDir)\app.js;$(_CopilotCacheDir)\assets\**\*;$(_CopilotCacheDir)\changelog.json;$(_CopilotCacheDir)\copilot;$(_CopilotCacheDir)\copilot.exe;$(_CopilotCacheDir)\copilot.tgz;$(_CopilotCacheDir)\foundry-local-sdk\**\*;$(_CopilotCacheDir)\index.js;$(_CopilotCacheDir)\LICENSE.md;$(_CopilotCacheDir)\napi-oop-runtime\**\*;$(_CopilotCacheDir)\npm-loader.js;$(_CopilotCacheDir)\package.json;$(_CopilotCacheDir)\prebuilds\**\*;$(_CopilotCacheDir)\pvrecorder\**\*;$(_CopilotCacheDir)\queries\**\*;$(_CopilotCacheDir)\README.md;$(_CopilotCacheDir)\sea-loader.js;$(_CopilotCacheDir)\tree-sitter*.wasm;$(_CopilotCacheDir)\voice-*.js;$(_CopilotCacheDir)\webview\**\*" /> <_CopilotRuntimePrebuildAsset Include="$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\**\*" Exclude="$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\cli-native.node;$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\mediaremote-adapter\**\*;$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\copilot-runtime-bin*" /> @@ -193,7 +193,7 @@ Condition="'$(CopilotCliBinaryPath)' != ''" /> <_CopilotRuntimeRootAsset Include="$(_CopilotCacheDir)\**\*" - Exclude="$(_CopilotCacheDir)\app.js;$(_CopilotCacheDir)\assets\**\*;$(_CopilotCacheDir)\changelog.json;$(_CopilotCacheDir)\copilot;$(_CopilotCacheDir)\copilot.exe;$(_CopilotCacheDir)\copilot-sdk\**\*;$(_CopilotCacheDir)\copilot.tgz;$(_CopilotCacheDir)\foundry-local-sdk\**\*;$(_CopilotCacheDir)\index.js;$(_CopilotCacheDir)\LICENSE.md;$(_CopilotCacheDir)\napi-oop-runtime\**\*;$(_CopilotCacheDir)\npm-loader.js;$(_CopilotCacheDir)\package.json;$(_CopilotCacheDir)\prebuilds\**\*;$(_CopilotCacheDir)\preloads\**\*;$(_CopilotCacheDir)\pvrecorder\**\*;$(_CopilotCacheDir)\queries\**\*;$(_CopilotCacheDir)\README.md;$(_CopilotCacheDir)\sdk\**\*;$(_CopilotCacheDir)\sea-loader.js;$(_CopilotCacheDir)\tree-sitter*.wasm;$(_CopilotCacheDir)\voice-*.js;$(_CopilotCacheDir)\webview\**\*" + Exclude="$(_CopilotCacheDir)\app.js;$(_CopilotCacheDir)\assets\**\*;$(_CopilotCacheDir)\changelog.json;$(_CopilotCacheDir)\copilot;$(_CopilotCacheDir)\copilot.exe;$(_CopilotCacheDir)\copilot.tgz;$(_CopilotCacheDir)\foundry-local-sdk\**\*;$(_CopilotCacheDir)\index.js;$(_CopilotCacheDir)\LICENSE.md;$(_CopilotCacheDir)\napi-oop-runtime\**\*;$(_CopilotCacheDir)\npm-loader.js;$(_CopilotCacheDir)\package.json;$(_CopilotCacheDir)\prebuilds\**\*;$(_CopilotCacheDir)\pvrecorder\**\*;$(_CopilotCacheDir)\queries\**\*;$(_CopilotCacheDir)\README.md;$(_CopilotCacheDir)\sea-loader.js;$(_CopilotCacheDir)\tree-sitter*.wasm;$(_CopilotCacheDir)\voice-*.js;$(_CopilotCacheDir)\webview\**\*" Condition="Exists('$(_CopilotRuntimeWrapperPath)') And Exists('$(_CopilotRuntimeNodePath)')" /> <_CopilotRuntimePrebuildAsset Include="$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\**\*" Exclude="$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\cli-native.node;$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\mediaremote-adapter\**\*;$(_CopilotCacheDir)\prebuilds\$(_CopilotPlatform)\copilot-runtime-bin*" diff --git a/dotnet/test/E2E/EventFidelityE2ETests.cs b/dotnet/test/E2E/EventFidelityE2ETests.cs index 8882f972d5..da9869757f 100644 --- a/dotnet/test/E2E/EventFidelityE2ETests.cs +++ b/dotnet/test/E2E/EventFidelityE2ETests.cs @@ -12,7 +12,7 @@ namespace GitHub.Copilot.Test.E2E; /// Verifies the shape and ordering of s emitted from the /// runtime: every event has an id and timestamp, user/assistant messages carry /// content, tool execution events carry a toolCallId, and -/// session.idle is the last event of a turn. Mirrors +/// session.idle follows the final assistant message of a turn. Mirrors /// nodejs/test/e2e/event_fidelity.e2e.test.ts. /// public class EventFidelityE2ETests(E2ETestFixture fixture, ITestOutputHelper output) @@ -43,9 +43,12 @@ await session.SendAndWaitAsync(new MessageOptions var assistantIdx = types.LastIndexOf("assistant.message"); Assert.True(userIdx < assistantIdx, $"Expected user.message ({userIdx}) before last assistant.message ({assistantIdx})"); - // session.idle should be the last event we observed + // session.idle should complete the conversational turn. Post-turn + // metadata events may arrive after it on slower target frameworks. var idleIdx = types.LastIndexOf("session.idle"); - Assert.Equal(types.Count - 1, idleIdx); + Assert.True( + assistantIdx < idleIdx, + $"Expected last assistant.message ({assistantIdx}) before session.idle ({idleIdx}): {string.Join(", ", types)}"); await session.DisposeAsync(); } diff --git a/dotnet/test/E2E/MultiClientE2ETests.cs b/dotnet/test/E2E/MultiClientE2ETests.cs index 4dbe7190ad..7597aa580b 100644 --- a/dotnet/test/E2E/MultiClientE2ETests.cs +++ b/dotnet/test/E2E/MultiClientE2ETests.cs @@ -324,6 +324,9 @@ public async Task Disconnecting_Client_Removes_Its_Tools() // Disconnect client 2 await Client2.ForceStopAsync(); + // Give the server time to process the connection close and remove tools. + await Task.Delay(500); + // Recreate client2 for cleanup var port = Client1.RuntimePort!.Value; _client2 = Ctx.CreateClient(options: new CopilotClientOptions diff --git a/dotnet/test/E2E/PendingWorkResumeE2ETests.cs b/dotnet/test/E2E/PendingWorkResumeE2ETests.cs index bf1ed687c1..e48dbbbcbc 100644 --- a/dotnet/test/E2E/PendingWorkResumeE2ETests.cs +++ b/dotnet/test/E2E/PendingWorkResumeE2ETests.cs @@ -140,10 +140,12 @@ await session1.SendAsync(new MessageOptions } [Description("Looks up a value after resumption")] - async Task BlockingExternalTool([Description("Value to look up")] string value) + async Task BlockingExternalTool( + [Description("Value to look up")] string value, + CancellationToken cancellationToken) { originalToolStarted.TrySetResult(value); - return await releaseOriginalTool.Task; + return await releaseOriginalTool.Task.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken); } } @@ -274,11 +276,13 @@ await TestHelper.WaitForConditionAsync( } [Description("Looks up a value after resumption")] - async Task BlockingExternalTool([Description("Value to look up")] string value) + async Task BlockingExternalTool( + [Description("Value to look up")] string value, + CancellationToken cancellationToken) { Interlocked.Increment(ref invocationCount); originalToolStarted.TrySetResult(value); - return await releaseOriginalTool.Task; + return await releaseOriginalTool.Task.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken); } [Description("Looks up a value after resumption")] @@ -356,17 +360,21 @@ await Task.WhenAll( } [Description("Looks up the first value after resumption")] - async Task BlockingToolA([Description("Value to look up")] string value) + async Task BlockingToolA( + [Description("Value to look up")] string value, + CancellationToken cancellationToken) { originalToolAStarted.TrySetResult(value); - return await releaseOriginalToolA.Task; + return await releaseOriginalToolA.Task.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken); } [Description("Looks up the second value after resumption")] - async Task BlockingToolB([Description("Value to look up")] string value) + async Task BlockingToolB( + [Description("Value to look up")] string value, + CancellationToken cancellationToken) { originalToolBStarted.TrySetResult(value); - return await releaseOriginalToolB.Task; + return await releaseOriginalToolB.Task.WaitAsync(Timeout.InfiniteTimeSpan, cancellationToken); } } diff --git a/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs b/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs index 0a3513e4b5..3d017d8165 100644 --- a/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs +++ b/dotnet/test/E2E/RpcExtensionsLoadedE2ETests.cs @@ -57,10 +57,22 @@ private CopilotClient CreateExtensionsClient() { return Ctx.CreateClient(options: new CopilotClientOptions { - Connection = RuntimeConnection.ForStdio(args: ["--yolo"]), + Connection = RuntimeConnection.ForStdio( + path: Ctx.GetLegacyCliPath(), + args: ["--yolo"]), }, environment: ExtensionsEnabledEnvironment()); } + private static SessionConfig CreateExtensionsSessionConfig(string? workingDirectory = null) + { + return new SessionConfig + { + EnableConfigDiscovery = true, + WorkingDirectory = workingDirectory, + OnPermissionRequest = PermissionHandler.ApproveAll, + }; + } + /// /// Writes a minimal user extension into {HomeDir}/extensions/{name}/extension.mjs. /// The body imports @github/copilot-sdk/extension, calls joinSession @@ -189,12 +201,9 @@ public async Task Discovers_Loads_And_Reports_Running_Extension(string sourceVal await using var client = CreateExtensionsClient(); - await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig - { - EnableConfigDiscovery = true, - WorkingDirectory = workingDirectory, - OnPermissionRequest = PermissionHandler.ApproveAll, - }); + await using var session = await Ctx.CreateSessionAsync( + client, + CreateExtensionsSessionConfig(workingDirectory)); var ext = await WaitForExtensionAsync(session, extId, ExtensionStatus.Running); @@ -214,11 +223,9 @@ public async Task Disable_Then_Enable_Cycles_Extension_Status() await using var client = CreateExtensionsClient(); - await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig - { - EnableConfigDiscovery = true, - OnPermissionRequest = PermissionHandler.ApproveAll, - }); + await using var session = await Ctx.CreateSessionAsync( + client, + CreateExtensionsSessionConfig()); // Wait until the initial running state is observed before mutating. await WaitForExtensionAsync(session, extId, ExtensionStatus.Running); @@ -240,11 +247,9 @@ public async Task Reload_Picks_Up_Extension_Added_After_Session_Create() // Start the session BEFORE writing the extension so the initial discovery sees nothing. await using var client = CreateExtensionsClient(); - await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig - { - EnableConfigDiscovery = true, - OnPermissionRequest = PermissionHandler.ApproveAll, - }); + await using var session = await Ctx.CreateSessionAsync( + client, + CreateExtensionsSessionConfig()); // setupExtensionsForSession runs asynchronously; until it completes the // controller isn't installed and ReloadAsync throws "Extensions not @@ -285,11 +290,9 @@ public async Task Failed_Extension_Reports_Failed_Status() await using var client = CreateExtensionsClient(); - await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig - { - EnableConfigDiscovery = true, - OnPermissionRequest = PermissionHandler.ApproveAll, - }); + await using var session = await Ctx.CreateSessionAsync( + client, + CreateExtensionsSessionConfig()); var ext = await WaitForExtensionAsync(session, extId, ExtensionStatus.Failed); Assert.Equal(extId, ext.Id); @@ -306,11 +309,9 @@ public async Task Multiple_Extensions_Are_Discovered_Independently() await using var client = CreateExtensionsClient(); - await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig - { - EnableConfigDiscovery = true, - OnPermissionRequest = PermissionHandler.ApproveAll, - }); + await using var session = await Ctx.CreateSessionAsync( + client, + CreateExtensionsSessionConfig()); await WaitForExtensionAsync(session, ext1Id, ExtensionStatus.Running); await WaitForExtensionAsync(session, ext2Id, ExtensionStatus.Running); @@ -328,11 +329,9 @@ public async Task Reload_Preserves_Disabled_State_Across_Calls() await using var client = CreateExtensionsClient(); - await using var session = await Ctx.CreateSessionAsync(client, new SessionConfig - { - EnableConfigDiscovery = true, - OnPermissionRequest = PermissionHandler.ApproveAll, - }); + await using var session = await Ctx.CreateSessionAsync( + client, + CreateExtensionsSessionConfig()); await WaitForExtensionAsync(session, extId, ExtensionStatus.Running); diff --git a/dotnet/test/Harness/E2ETestContext.cs b/dotnet/test/Harness/E2ETestContext.cs index 0080cbc609..2554c6a4d1 100644 --- a/dotnet/test/Harness/E2ETestContext.cs +++ b/dotnet/test/Harness/E2ETestContext.cs @@ -3,9 +3,9 @@ *--------------------------------------------------------------------------------------------*/ using Microsoft.Extensions.Logging; +using System.Collections.Concurrent; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text.RegularExpressions; namespace GitHub.Copilot.Test.Harness; @@ -14,6 +14,7 @@ public sealed class E2ETestContext : IAsyncDisposable { private const string DefaultGitHubToken = "fake-token-for-e2e-tests"; private static readonly TimeSpan s_gracefulClientStopTimeout = TimeSpan.FromSeconds(30); + private static readonly ConcurrentDictionary> s_preparedCliPaths = new(StringComparer.Ordinal); public string HomeDir { get; } public string WorkDir { get; } @@ -25,6 +26,8 @@ public sealed class E2ETestContext : IAsyncDisposable private readonly ReplayProxy _proxy; private readonly string _repoRoot; + private readonly Lazy _cliPath; + private readonly Lazy _legacyCliPath; private readonly object _clientsLock = new(); private readonly List _persistentClients = []; private readonly List _transientClients = []; @@ -36,6 +39,8 @@ private E2ETestContext(string homeDir, string workDir, string proxyUrl, ReplayPr ProxyUrl = proxyUrl; _proxy = proxy; _repoRoot = repoRoot; + _cliPath = GetCachedCliPath(repoRoot, "--print-path"); + _legacyCliPath = GetCachedCliPath(repoRoot, "--print-legacy-path"); } public static async Task CreateAsync() @@ -144,46 +149,56 @@ private static string FindRepoRoot() throw new InvalidOperationException("Could not find repository root"); } - private static string GetCliPath(string repoRoot) + private string GetCliPath() + => _cliPath.Value; + + public string GetLegacyCliPath() + => _legacyCliPath.Value; + + private static Lazy GetCachedCliPath(string repoRoot, string option) + { + var envPath = option == "--print-path" + ? Environment.GetEnvironmentVariable("COPILOT_CLI_PATH") + : null; + var cacheKey = $"{repoRoot}\0{option}\0{envPath}"; + return s_preparedCliPaths.GetOrAdd( + cacheKey, + _ => new Lazy( + () => PrepareCliPath(repoRoot, option), + LazyThreadSafetyMode.ExecutionAndPublication)); + } + + private static string PrepareCliPath(string repoRoot, string option) { 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 + if (option == "--print-path" && !string.IsNullOrEmpty(envPath)) return envPath; + + 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 = "node", + WorkingDirectory = Path.Join(repoRoot, "nodejs"), + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + Arguments = $"node_modules/tsx/dist/cli.mjs scripts/prepare-runtime.ts {option}", }; - } - 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}"; + 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) @@ -310,21 +325,20 @@ public CopilotClient CreateClient( // CopilotClient honors COPILOT_SDK_DEFAULT_CONNECTION (stdio by default, // or in-process); the CI matrix uses this to run the suite under both. // Tests that need a specific transport set options.Connection directly. - var cliPath = GetCliPath(_repoRoot); switch (options.Connection) { case null when !IsInProcess(null): // No explicit connection and not the in-process default: the // default resolves to stdio, so materialize it here so the // environment can be attached to the connection below. - options.Connection = RuntimeConnection.ForStdio(path: cliPath); + options.Connection = RuntimeConnection.ForStdio(path: GetCliPath()); break; case null: // In-process default: leave Connection unset so CopilotClient's // ResolveDefaultConnection honors COPILOT_SDK_DEFAULT_CONNECTION. break; case ChildProcessRuntimeConnection child when child.Path is null: - child.Path = cliPath; + child.Path = GetCliPath(); break; } diff --git a/dotnet/test/Unit/MSBuildTargetsTests.cs b/dotnet/test/Unit/MSBuildTargetsTests.cs index cab91e568f..0dd3073956 100644 --- a/dotnet/test/Unit/MSBuildTargetsTests.cs +++ b/dotnet/test/Unit/MSBuildTargetsTests.cs @@ -116,6 +116,9 @@ public async Task RuntimePackageAssets_AreFilteredAndCopiedToOutput() OperatingSystem.IsWindows() ? "copilot-runtime.exe" : "copilot-runtime", "wrapper"); sandbox.WriteRuntimeCacheAsset("ripgrep", "bin", GetNpmPlatform(), "rg", "ripgrep"); sandbox.WriteRuntimeCacheAsset("definitions", "future.json", "{}"); + sandbox.WriteRuntimeCacheAsset("copilot-sdk", "extension.js", "extension"); + sandbox.WriteRuntimeCacheAsset("preloads", "extension_bootstrap.mjs", "preload"); + sandbox.WriteRuntimeCacheAsset("sdk", "factory.js", "factory"); sandbox.WriteRuntimeCacheAsset("app.js", "excluded"); sandbox.WriteRuntimeCacheAsset("LICENSE.md", "excluded"); sandbox.WriteRuntimeCacheAsset("README.md", "excluded"); @@ -129,6 +132,9 @@ public async Task RuntimePackageAssets_AreFilteredAndCopiedToOutput() Assert.True(result.Succeeded, result.FailureMessage()); Assert.Equal("ripgrep", File.ReadAllText(sandbox.ExpectedRuntimeAsset("ripgrep", "bin", GetNpmPlatform(), "rg"))); Assert.Equal("{}", File.ReadAllText(sandbox.ExpectedRuntimeAsset("definitions", "future.json"))); + Assert.Equal("extension", File.ReadAllText(sandbox.ExpectedRuntimeAsset("copilot-sdk", "extension.js"))); + Assert.Equal("preload", File.ReadAllText(sandbox.ExpectedRuntimeAsset("preloads", "extension_bootstrap.mjs"))); + Assert.Equal("factory", File.ReadAllText(sandbox.ExpectedRuntimeAsset("sdk", "factory.js"))); Assert.False(File.Exists(sandbox.ExpectedRuntimeAsset("app.js"))); Assert.False(File.Exists(sandbox.ExpectedRuntimeAsset("LICENSE.md"))); Assert.False(File.Exists(sandbox.ExpectedRuntimeAsset("README.md"))); diff --git a/go/README.md b/go/README.md index 6c85f93ae1..801ad8556c 100644 --- a/go/README.md +++ b/go/README.md @@ -20,7 +20,10 @@ go get github.com/github/copilot-sdk/go Try the interactive chat sample (from the repo root): ```bash -cd go/samples +cd nodejs +npm ci +export COPILOT_CLI_PATH="$(npm run --silent prepare:runtime -- --print-path)" +cd ../go/samples go run chat.go ``` diff --git a/go/client.go b/go/client.go index 735e8ae414..5789fca5eb 100644 --- a/go/client.go +++ b/go/client.go @@ -2445,11 +2445,9 @@ func (c *Client) setupNotificationHandler() { } if c.options.RequestHandler != nil { + llmInference := c.RPC.LlmInference handlers.LlmInference = newCopilotRequestAdapter(c.options.RequestHandler, func() *rpc.ServerLlmInferenceAPI { - if c.RPC == nil { - return nil - } - return c.RPC.LlmInference + return llmInference }) } if c.options.OnGitHubTelemetry != nil { diff --git a/go/client_test.go b/go/client_test.go index e74c234637..dffc15d74e 100644 --- a/go/client_test.go +++ b/go/client_test.go @@ -1326,15 +1326,36 @@ func TestClient_SessionIdleTimeoutSeconds(t *testing.T) { }) } -func findCLIPathForTest() string { - base, err := filepath.Abs("../nodejs/node_modules/@github") - if err == nil { - matches, _ := filepath.Glob(filepath.Join(base, "copilot-*", "index.js")) - if len(matches) > 0 { - return matches[0] +func findCLIPathForTest(t *testing.T) string { + t.Helper() + + if cliPath := os.Getenv("COPILOT_CLI_PATH"); cliPath != "" { + if info, err := os.Stat(cliPath); err == nil && !info.IsDir() { + return cliPath } + t.Fatalf("COPILOT_CLI_PATH does not point to a file: %s", cliPath) + } + + nodeDir, err := filepath.Abs("../nodejs") + if err != nil { + t.Fatal(err) } - return "" + command := exec.Command( + "node", + "node_modules/tsx/dist/cli.mjs", + "scripts/prepare-runtime.ts", + "--print-path", + ) + command.Dir = nodeDir + output, err := command.CombinedOutput() + if err != nil { + t.Fatalf("failed to prepare pinned Copilot CLI: %v\n%s", err, output) + } + cliPath := strings.TrimSpace(string(output)) + if info, err := os.Stat(cliPath); err != nil || info.IsDir() { + t.Fatalf("prepared Copilot CLI path is not a file: %q", cliPath) + } + return cliPath } func TestCreateSessionRequest_ClientName(t *testing.T) { @@ -2181,10 +2202,7 @@ func TestListModelsHandlerCachesResults(t *testing.T) { } func TestClient_StartContextCancellationDoesNotKillProcess(t *testing.T) { - cliPath := findCLIPathForTest() - if cliPath == "" { - t.Skip("CLI not found") - } + cliPath := findCLIPathForTest(t) client := NewClient(&ClientOptions{Connection: StdioConnection{Path: cliPath}}) t.Cleanup(func() { client.ForceStop() }) @@ -2207,10 +2225,7 @@ func TestClient_StartContextCancellationDoesNotKillProcess(t *testing.T) { } func TestClient_StartStopRace(t *testing.T) { - cliPath := findCLIPathForTest() - if cliPath == "" { - t.Skip("CLI not found") - } + cliPath := findCLIPathForTest(t) client := NewClient(&ClientOptions{Connection: StdioConnection{Path: cliPath}}) defer client.ForceStop() errChan := make(chan error) diff --git a/go/cmd/bundler/main.go b/go/cmd/bundler/main.go index 89f99daf1b..7ee078eea0 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 } @@ -509,9 +535,9 @@ func runtimeWrapperName(binaryName string) string { var hostlessExcludedTopLevel = map[string]bool{ "app.js": true, "assets": true, "changelog.json": true, "copilot": true, "copilot.exe": true, - "copilot-sdk": true, "foundry-local-sdk": true, "index.js": true, "napi-oop-runtime": true, - "LICENSE.md": true, "npm-loader.js": true, "package.json": true, "preloads": true, "pvrecorder": true, - "queries": true, "README.md": true, "sdk": true, "sea-loader.js": true, "webview": true, + "foundry-local-sdk": true, "index.js": true, "napi-oop-runtime": true, "LICENSE.md": true, + "npm-loader.js": true, "package.json": true, "pvrecorder": true, "queries": true, "README.md": true, + "sea-loader.js": true, "webview": true, } func hostlessRuntimePath(name, npmPlatform, wrapperName string) (string, bool) { diff --git a/go/cmd/bundler/main_test.go b/go/cmd/bundler/main_test.go index f41afe43f5..9c09559b66 100644 --- a/go/cmd/bundler/main_test.go +++ b/go/cmd/bundler/main_test.go @@ -22,6 +22,9 @@ func TestCreateRuntimeAssetsArchiveRetainsUnknownAssetsAndFiltersCLIContent(t *t "package/prebuilds/linux-x64/copilot-runtime": "wrapper", "package/ripgrep/bin/linux-x64/rg": "ripgrep", "package/definitions/future.json": "{}", + "package/copilot-sdk/extension.js": "extension", + "package/preloads/extension_bootstrap.mjs": "preload", + "package/sdk/factory.js": "factory", "package/app.js": "excluded", "package/LICENSE.md": "excluded", "package/README.md": "excluded", @@ -35,7 +38,11 @@ func TestCreateRuntimeAssetsArchiveRetainsUnknownAssetsAndFiltersCLIContent(t *t } files := readTarGz(t, output) - if files["ripgrep/bin/linux-x64/rg"] != "ripgrep" || files["definitions/future.json"] != "{}" { + if files["ripgrep/bin/linux-x64/rg"] != "ripgrep" || + files["definitions/future.json"] != "{}" || + files["copilot-sdk/extension.js"] != "extension" || + files["preloads/extension_bootstrap.mjs"] != "preload" || + files["sdk/factory.js"] != "factory" { t.Fatalf("retained assets = %#v", files) } for _, excluded := range []string{ diff --git a/go/internal/e2e/event_fidelity_e2e_test.go b/go/internal/e2e/event_fidelity_e2e_test.go index e7cc4bfb37..8606da22d9 100644 --- a/go/internal/e2e/event_fidelity_e2e_test.go +++ b/go/internal/e2e/event_fidelity_e2e_test.go @@ -333,8 +333,8 @@ func TestEventFidelityE2E(t *testing.T) { } idleIdx := lastEventFidelityTypeIndex(types, copilot.SessionEventTypeSessionIdle) - if idleIdx != len(types)-1 { - t.Fatalf("Expected session.idle to be last event; idleIdx=%d len=%d types=%v", idleIdx, len(types), types) + if idleIdx < 0 || assistantIdx >= idleIdx { + t.Fatalf("Expected last assistant.message before session.idle; assistantIdx=%d idleIdx=%d types=%v", assistantIdx, idleIdx, types) } }) 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/samples/chat.go b/go/samples/chat.go index 2f34a243c9..85128a1b56 100644 --- a/go/samples/chat.go +++ b/go/samples/chat.go @@ -5,10 +5,10 @@ import ( "context" "fmt" "os" - "path/filepath" "strings" copilot "github.com/github/copilot-sdk/go" + "github.com/github/copilot-sdk/go/samples/internal/sampleutil" ) const blue = "\033[34m" @@ -16,7 +16,10 @@ const reset = "\033[0m" func main() { ctx := context.Background() - cliPath := filepath.Join("..", "..", "nodejs", "node_modules", "@github", "copilot", "index.js") + cliPath, err := sampleutil.CLIPath() + if err != nil { + panic(err) + } client := copilot.NewClient(&copilot.ClientOptions{Connection: copilot.StdioConnection{Path: cliPath}}) if err := client.Start(ctx); err != nil { panic(err) diff --git a/go/samples/internal/sampleutil/runtime.go b/go/samples/internal/sampleutil/runtime.go new file mode 100644 index 0000000000..5c51f0d8db --- /dev/null +++ b/go/samples/internal/sampleutil/runtime.go @@ -0,0 +1,48 @@ +package sampleutil + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" +) + +// CLIPath resolves the pinned source-checkout runtime used by SDK samples. +func CLIPath() (string, error) { + if cliPath := os.Getenv("COPILOT_CLI_PATH"); cliPath != "" { + return cliPath, nil + } + + current, err := os.Getwd() + if err != nil { + return "", err + } + for { + nodeDir := filepath.Join(current, "nodejs") + if _, err := os.Stat(filepath.Join(nodeDir, "package.json")); err == nil { + command := exec.Command( + "node", + "node_modules/tsx/dist/cli.mjs", + "scripts/prepare-runtime.ts", + "--print-path", + ) + command.Dir = nodeDir + output, err := command.CombinedOutput() + if err != nil { + return "", fmt.Errorf("prepare pinned Copilot CLI: %w: %s", err, output) + } + cliPath := strings.TrimSpace(string(output)) + if info, err := os.Stat(cliPath); err != nil || info.IsDir() { + return "", fmt.Errorf("prepared Copilot CLI path is not a file: %q", cliPath) + } + return cliPath, nil + } + + parent := filepath.Dir(current) + if parent == current { + return "", fmt.Errorf("could not find nodejs/package.json; set COPILOT_CLI_PATH") + } + current = parent + } +} diff --git a/go/samples/manual_tool_resume/main.go b/go/samples/manual_tool_resume/main.go index a7391ff405..62806b7a78 100644 --- a/go/samples/manual_tool_resume/main.go +++ b/go/samples/manual_tool_resume/main.go @@ -4,11 +4,11 @@ import ( "context" "fmt" "os" - "path/filepath" "time" copilot "github.com/github/copilot-sdk/go" "github.com/github/copilot-sdk/go/rpc" + "github.com/github/copilot-sdk/go/samples/internal/sampleutil" ) const timeout = 2 * time.Minute @@ -32,7 +32,10 @@ func manualTool() copilot.Tool { } func newClient() *copilot.Client { - cliPath := filepath.Join("..", "..", "nodejs", "node_modules", "@github", "copilot", "index.js") + cliPath, err := sampleutil.CLIPath() + if err != nil { + panic(err) + } return copilot.NewClient(&copilot.ClientOptions{Connection: copilot.StdioConnection{Path: cliPath}}) } diff --git a/go/session.go b/go/session.go index 5d45d19ef2..32fed684d9 100644 --- a/go/session.go +++ b/go/session.go @@ -98,8 +98,10 @@ type Session struct { // eventCh serializes user event handler dispatch. dispatchEvent enqueues; // a single goroutine (processEvents) dequeues and invokes handlers in FIFO order. + // eventDone stops both sides without closing eventCh while a sender may be active. eventCh chan SessionEvent - closeOnce sync.Once // guards eventCh close so Disconnect is safe to call more than once + eventDone chan struct{} + closeOnce sync.Once // RPC provides typed session-scoped RPC methods. RPC *rpc.SessionRPC @@ -385,6 +387,7 @@ func newSession( toolHandlers: make(map[string]ToolHandler), commandHandlers: make(map[string]CommandHandler), eventCh: make(chan SessionEvent, 128), + eventDone: make(chan struct{}), RPC: rpc.NewSessionRPC(client, sessionID), } s.clientSessionAPIs.Canvas = newCanvasClientSessionAdapter(s) @@ -1391,14 +1394,10 @@ func (s *Session) dispatchEvent(event SessionEvent) { s.updateOpenCanvasesFromEvent(event) go s.handleBroadcastEvent(event) - // Send to the event channel in a closure with a recover guard. - // Disconnect closes eventCh, and in Go sending on a closed channel - // panics — there is no non-panicking send primitive. We only want - // to suppress that specific panic; other panics are not expected here. - func() { - defer func() { recover() }() - s.eventCh <- event - }() + select { + case s.eventCh <- event: + case <-s.eventDone: + } } // processEvents is the single consumer goroutine for the event channel. @@ -1406,7 +1405,14 @@ func (s *Session) dispatchEvent(event SessionEvent) { // handlers are recovered so that one misbehaving handler does not prevent // others from receiving the event. func (s *Session) processEvents() { - for event := range s.eventCh { + for { + var event SessionEvent + select { + case event = <-s.eventCh: + case <-s.eventDone: + return + } + s.handlerMutex.RLock() handlers := make([]SessionEventHandler, 0, len(s.handlers)) for _, h := range s.handlers { @@ -1427,6 +1433,10 @@ func (s *Session) processEvents() { } } +func (s *Session) stopEventProcessing() { + s.closeOnce.Do(func() { close(s.eventDone) }) +} + // handleBroadcastEvent handles broadcast request events by executing local handlers // and responding via RPC. This implements the protocol v3 broadcast model where tool // calls and permission requests are broadcast as session events to all clients. @@ -1729,7 +1739,7 @@ func (s *Session) GetEvents(ctx context.Context) ([]SessionEvent, error) { func (s *Session) Disconnect() error { _, err := s.client.Request(context.Background(), "session.destroy", sessionDestroyRequest{SessionID: s.SessionID}) - s.closeOnce.Do(func() { close(s.eventCh) }) + s.stopEventProcessing() s.releaseGitHubTokenProviderRegistration() // Clear handlers diff --git a/go/session_test.go b/go/session_test.go index 74e212c418..8d29cfc88f 100644 --- a/go/session_test.go +++ b/go/session_test.go @@ -18,21 +18,43 @@ import ( ) // newTestSession creates a session with an event channel and starts the consumer goroutine. -// Returns a cleanup function that closes the channel (stopping the consumer). +// Returns a cleanup function that stops the consumer. func newTestSession() (*Session, func()) { s := &Session{ handlers: make([]sessionHandler, 0), commandHandlers: make(map[string]CommandHandler), eventCh: make(chan SessionEvent, 128), + eventDone: make(chan struct{}), } go s.processEvents() - return s, func() { close(s.eventCh) } + return s, s.stopEventProcessing } func newTestEvent() SessionEvent { return SessionEvent{Data: &SessionIdleData{}} } +func TestDispatchEventReturnsAfterEventProcessingStops(t *testing.T) { + session := &Session{ + eventCh: make(chan SessionEvent), + eventDone: make(chan struct{}), + } + + dispatched := make(chan struct{}) + go func() { + session.dispatchEvent(newTestEvent()) + close(dispatched) + }() + + session.stopEventProcessing() + + select { + case <-dispatched: + case <-time.After(time.Second): + t.Fatal("dispatchEvent remained blocked after event processing stopped") + } +} + func ptr[T any](value T) *T { return &value } @@ -423,9 +445,10 @@ func TestSession_SendAndWaitSkipsAutopilotContinuationIdle(t *testing.T) { RPC: rpc.NewSessionRPC(client, "session-1"), handlers: make([]sessionHandler, 0), eventCh: make(chan SessionEvent, 8), + eventDone: make(chan struct{}), } go session.processEvents() - defer close(session.eventCh) + defer session.stopEventProcessing() resultCh := make(chan *SessionEvent, 1) go func() { 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/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 c03be9909e..8dff6c4ad6 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..49d2180a73 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 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 `SHA256SUMS.txt` from the matching release. + * 3. Verify the downloaded tarball against the release checksum. * 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. @@ -29,18 +28,15 @@ const excludedTopLevel = new Set([ 'changelog.json', 'copilot', 'copilot.exe', - 'copilot-sdk', 'foundry-local-sdk', 'index.js', 'LICENSE.md', 'napi-oop-runtime', 'npm-loader.js', 'package.json', - 'preloads', 'pvrecorder', 'queries', 'README.md', - 'sdk', 'sea-loader.js', 'webview', ]); @@ -52,19 +48,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); } @@ -77,7 +66,7 @@ const wrapperPath = path.join(resourceDir, wrapperFilename); const inventoryPath = path.join(resourceDir, 'runtime-assets.list'); const platformPropertiesPath = path.join(resourceDir, 'platform.properties'); const expectedPlatformProperties = `classifier=${classifier}\nversion=${version}\n`; -const stagingSchema = 'hostless-runtime-v2'; +const stagingSchema = 'hostless-runtime-v3'; const stampPath = path.join(outDir, '.version'); // Idempotence: skip the download only when every required staged artifact @@ -92,14 +81,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,24 +99,36 @@ 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 tarballPath = path.join(outDir, tarballName); - -const actual = `sha512-${createHash('sha512').update(fs.readFileSync(tarballPath)).digest('base64')}`; -if (actual !== integrity) { - console.error(`Integrity verification failed for ${tarballPath}`); - console.error(` expected: ${integrity}`); +const assetName = `github-copilot-${version}-${classifier}.tgz`; +const releaseBase = ( + process.env.COPILOT_CLI_DOWNLOAD_BASE_URL ?? + 'https://github.com/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 = (await download(`${releaseUrl}/SHA256SUMS.txt`)).toString('utf8'); + expectedHash = findChecksum(checksums, assetName); + archive = await download(`${releaseUrl}/${assetName}`); +} +if (!expectedHash || !/^[a-fA-F0-9]{64}$/.test(expectedHash)) { + throw new Error(`Missing or invalid SHA-256 for ${assetName}`); +} +const actual = createHash('sha256').update(archive).digest('hex'); +if (actual !== expectedHash.toLowerCase()) { + console.error(`Integrity verification failed for ${assetName}`); + 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' }) +const members = execFileSync('tar', ['-tzf', '-'], { encoding: 'utf8', input: archive }) .split(/\r?\n/) .filter(Boolean); for (const member of members) { @@ -137,15 +136,19 @@ for (const member of members) { if (destinationRelative === null) { continue; } - const listing = execFileSync('tar', ['-tvzf', tarballPath, member], { encoding: 'utf8' }).trim(); + const listing = execFileSync('tar', ['-tvzf', '-', member], { + encoding: 'utf8', + input: archive, + }).trim(); if (listing.startsWith('d')) { continue; } if (!listing.startsWith('-')) { throw new Error(`Unsupported runtime package entry: ${member}`); } - const content = execFileSync('tar', ['-xOzf', tarballPath, member], { + const content = execFileSync('tar', ['-xOzf', '-', member], { encoding: null, + input: archive, maxBuffer: 512 * 1024 * 1024, }); const destination = path.resolve(resourceDir, destinationRelative); @@ -162,14 +165,12 @@ for (const member of members) { inventory.sort(); fs.writeFileSync(inventoryPath, `${inventory.join('\n')}\n`); -fs.rmSync(tarballPath, { force: true }); - if (!fs.existsSync(runtimePath) || !fs.existsSync(wrapperPath)) { throw new Error(`Package ${packageName}@${version} is missing the runtime wrapper pair`); } 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 +225,37 @@ function digestTree(directory) { } return `sha512-${hash.digest('base64')}`; } + +async function download(url) { + let lastError; + for (let attempt = 0; attempt < 3; attempt++) { + try { + // lgtm[js/file-access-to-http] The repository-pinned CLI version intentionally selects the release asset. + 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}`); +} + +function findChecksum(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.toLowerCase(); + } + } + throw new Error(`SHA256SUMS.txt does not contain ${assetName}`); +} diff --git a/java/copilot-native/scripts/fetch-native.test.mjs b/java/copilot-native/scripts/fetch-native.test.mjs index 582ffa397f..3213d416f0 100644 --- a/java/copilot-native/scripts/fetch-native.test.mjs +++ b/java/copilot-native/scripts/fetch-native.test.mjs @@ -12,10 +12,10 @@ 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'; +const stagingSchema = 'hostless-runtime-v3'; const scriptPath = fileURLToPath(new URL('./fetch-native.mjs', import.meta.url)); for (const classifier of ['linux-x64', 'linux-arm64', 'win32-x64', 'win32-arm64', 'darwin-arm64']) { @@ -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) => { @@ -38,10 +37,10 @@ for (const classifier of ['linux-x64', 'linux-arm64', 'win32-x64', 'win32-arm64' assertRestagingAttempted(fixture, result); }); - test(`${classifier}: legacy staging schema does not use incremental fast path`, (t) => { + test(`${classifier}: v2 staging schema does not use incremental fast path`, (t) => { const fixture = createFixture(t, classifier); const stampPath = path.join(fixture.stagingDir, classifier, '.version'); - fs.writeFileSync(stampPath, fs.readFileSync(stampPath, 'utf8').replace(stagingSchema, 'hostless-runtime-v1')); + fs.writeFileSync(stampPath, fs.readFileSync(stampPath, 'utf8').replace(stagingSchema, 'hostless-runtime-v2')); const result = runScript(fixture); @@ -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..a6bec65d2c 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,15 @@ 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(); - } - } + try { + return TestUtil.preparePinnedCli(repoRoot); + } catch (Exception e) { + String copilotInPath = findCopilotInPath(); + if (copilotInPath != null) { + return copilotInPath; } + throw new IOException("The pinned CLI could not be prepared and no CLI was found on PATH.", e); } - - // 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."); } 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..8e90c2f48f 100644 --- a/java/sdk/src/test/java/com/github/copilot/TestUtil.java +++ b/java/sdk/src/test/java/com/github/copilot/TestUtil.java @@ -5,9 +5,12 @@ package com.github.copilot; import java.io.BufferedReader; +import java.io.File; import java.io.InputStreamReader; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.regex.Pattern; /** * Shared test utilities for locating the Copilot CLI binary and other @@ -37,10 +40,9 @@ public static String tempPath(String filename) { * Resolution order: *
    *
  1. Use the {@code COPILOT_CLI_PATH} environment variable when set.
  2. + *
  3. Prepare the release pinned by {@code nodejs/package.json}.
  4. *
  5. Otherwise search the system PATH using {@code where.exe} (Windows) or * {@code which} (Linux/macOS).
  6. - *
  7. Walk parent directories looking for - * {@code nodejs/node_modules/@github/copilot/npm-loader.js}.
  8. *
* *

@@ -60,43 +62,54 @@ static String findCliPath() { return envPath; } - String copilotInPath = findCopilotInPath(); - if (copilotInPath != null) { - 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(); + if (current.resolve("nodejs/package.json").toFile().exists()) { + try { + return preparePinnedCli(current); + } catch (Exception preparationFailed) { + break; + } } + current = current.getParent(); + } - // 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(); - } + return findCopilotInPath(); + } - // 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(); - } - current = current.getParent(); + static String preparePinnedCli(Path repoRoot) throws Exception { + var nodePath = findExecutableInPath("node"); + if (nodePath == null) { + throw new IllegalStateException("Node.js was not found in PATH"); + } + var process = new ProcessBuilder(nodePath, "node_modules/tsx/dist/cli.mjs", "scripts/prepare-runtime.ts", + "--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; + } + + private static String findExecutableInPath(String name) { + var pathValue = System.getenv("PATH"); + if (pathValue == null || pathValue.isEmpty()) { + return null; } + var windows = System.getProperty("os.name").toLowerCase().contains("win"); + var fileName = windows ? name + ".exe" : name; + for (var directory : pathValue.split(Pattern.quote(File.pathSeparator))) { + var unquotedDirectory = directory.replaceAll("^\"|\"$", ""); + var candidate = Paths.get(unquotedDirectory, fileName).toAbsolutePath().normalize(); + if (Files.isRegularFile(candidate) && (windows || Files.isExecutable(candidate))) { + return candidate.toString(); + } + } return null; } diff --git a/nodejs/README.md b/nodejs/README.md index 57a8bf484b..3a2a536e65 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -8,6 +8,20 @@ To use the SDK, you'll need: - Node.js ^20.19.0 or >=22.12.0 +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 compiled +metadata in `src/cliVersion.ts`. Packaging verifies release assets against the +release's `SHA256SUMS.txt`. + +`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 @@ -22,6 +36,7 @@ Try the interactive chat sample (from the repo root): cd nodejs npm ci npm run build +export COPILOT_CLI_PATH="$(npm run --silent prepare:runtime -- --print-path)" cd samples npm install npm start @@ -936,15 +951,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 bfa5c8a0c6..9119db7e3e 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -9,7 +9,6 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.83-3", "koffi": "^3.1.0", "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", @@ -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,18 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "integrity": "sha1-LVmuOrSzj7QnC/oj0w+OLobH/jI=", + "dev": true, + "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 +752,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 +767,7 @@ }, "node_modules/@koromix/koffi-freebsd-arm64": { "version": "3.1.0", - "integrity": "sha512-vazoPYIhOAlXZksVIqDRMIID4VeUZKx8F3dR90hOobT2ATyOkqNS5dv5UCV7Q7DSq22lQTrdbvENBAhROzCp0w==", + "integrity": "sha1-rOu888/eXKu5jj4KgZb0gCzt+xE=", "cpu": [ "arm64" ], @@ -911,7 +782,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 +797,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 +812,7 @@ }, "node_modules/@koromix/koffi-linux-arm64": { "version": "3.1.0", - "integrity": "sha512-Wx5iOkeALe2ympLdiYwRpIg5qUkyQIv8N2foZ9rRker0uE7ZtXew2RRkbEgMir4b0yDYR1zyXd6B62GUzLtZ/g==", + "integrity": "sha1-9h8pjdDbtKufG4JXoxDFRNzAbnI=", "cpu": [ "arm64" ], @@ -956,7 +827,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 +842,7 @@ }, "node_modules/@koromix/koffi-linux-loong64": { "version": "3.1.0", - "integrity": "sha512-NOa0LdyltdESz3oeTqUH6MErHVoJOHoeXIsEp6xIMTUh4eKXEtlDQeoK6EYqo0DnBt83Xud95qLvi4Aw12pG4Q==", + "integrity": "sha1-bQvI/dvGGdARiMeDkZg0R+POkPI=", "cpu": [ "loong64" ], @@ -986,7 +857,7 @@ }, "node_modules/@koromix/koffi-linux-riscv64": { "version": "3.1.0", - "integrity": "sha512-Ye6kiXZCGxGtAIXSly6XuOP5tJZNYOZ2eVg33k1MilKrzimAy9Mpw4d6e9+Sfsc1jesgeNYs1sb5iaI8HS3ncA==", + "integrity": "sha1-V5aXxWe4DH2j2r1dnJcMH9w44co=", "cpu": [ "riscv64" ], @@ -1001,7 +872,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 +887,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 +902,7 @@ }, "node_modules/@koromix/koffi-openbsd-x64": { "version": "3.1.0", - "integrity": "sha512-CoQdqgnKvWgTXXZlUst8cBRQEov7QsxlTN2WAsu9wez01Xe6gEcH/zYePANualzzCbnaELfe5P0rA80QkoDuPA==", + "integrity": "sha1-wWm472ijVY45okrdH/8EIAVtzAo=", "cpu": [ "x64" ], @@ -1046,7 +917,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 +932,7 @@ }, "node_modules/@koromix/koffi-win32-x64": { "version": "3.1.0", - "integrity": "sha512-tnK5+IkzQBauQAQSzuyjso8OOIQRlaTZS39xIWpfqVYDLVDIuLDQk/WwHcOrR5yxlDrZq9ygiebBTOfcJFia7w==", + "integrity": "sha1-FyJ7F9SaNAIYddhe6gJ533eKOI4=", "cpu": [ "x64" ], @@ -1075,21 +946,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": "https://github.com/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 +986,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 +1018,7 @@ }, "node_modules/@rolldown/binding-darwin-x64": { "version": "1.0.3", - "integrity": "sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==", + "integrity": "sha1-U/V94fWZ7PHbE4I8/IjBj7gJVK0=", "cpu": [ "x64" ], @@ -1160,7 +1034,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 +1050,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 +1066,7 @@ }, "node_modules/@rolldown/binding-linux-arm64-gnu": { "version": "1.0.3", - "integrity": "sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==", + "integrity": "sha1-QZ/Wv2Es80jxBSjLzZTrq5YH2NE=", "cpu": [ "arm64" ], @@ -1208,7 +1082,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 +1098,7 @@ }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { "version": "1.0.3", - "integrity": "sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==", + "integrity": "sha1-Mq7LfI2uXU8qjN5XoFjshpkVQvg=", "cpu": [ "ppc64" ], @@ -1240,7 +1114,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 +1130,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 +1146,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 +1162,7 @@ }, "node_modules/@rolldown/binding-openharmony-arm64": { "version": "1.0.3", - "integrity": "sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==", + "integrity": "sha1-KQUTBoxV6EnchFejKv7h17Csswk=", "cpu": [ "arm64" ], @@ -1304,7 +1178,7 @@ }, "node_modules/@rolldown/binding-wasm32-wasi": { "version": "1.0.3", - "integrity": "sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==", + "integrity": "sha1-PZly2/GpU9PHr6pKDyDvKy458xs=", "cpu": [ "wasm32" ], @@ -1322,7 +1196,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 +1212,7 @@ }, "node_modules/@rolldown/binding-win32-x64-msvc": { "version": "1.0.3", - "integrity": "sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==", + "integrity": "sha1-4qJbNGkaHMihIJ195wkGMCbdDNs=", "cpu": [ "x64" ], @@ -1365,8 +1239,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 +1807,15 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/chownr": { + "version": "3.0.0", + "integrity": "sha1-mFXmTs0kCpzEJnzopKpdJKHaFeQ=", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/collection-utils": { "version": "1.0.1", "integrity": "sha512-LA2YTIlR7biSpXkKYwwuzGjwL5rjWEZVOSnvdUc7gObvWe4WkjxOpfrdhoP7Hs09YWDVfg0Mal9BpAqLfVEzQg==", @@ -2018,6 +1901,7 @@ "node_modules/detect-libc": { "version": "2.1.2", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -2695,7 +2579,7 @@ }, "node_modules/lightningcss-android-arm64": { "version": "1.32.0", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "integrity": "sha1-8DOIURbf79nG9UeHUj41FLYeGWg=", "cpu": [ "arm64" ], @@ -2735,7 +2619,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 +2639,7 @@ }, "node_modules/lightningcss-freebsd-x64": { "version": "1.32.0", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "integrity": "sha1-l3enZHK2Ttb/lDQq1kx7r9eUpXU=", "cpu": [ "x64" ], @@ -2775,7 +2659,7 @@ }, "node_modules/lightningcss-linux-arm-gnueabihf": { "version": "1.32.0", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "integrity": "sha1-E65lLhq3O5E117faFy9mbEEK1T0=", "cpu": [ "arm" ], @@ -2795,7 +2679,7 @@ }, "node_modules/lightningcss-linux-arm64-gnu": { "version": "1.32.0", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "integrity": "sha1-QXhYeVqUWS9oASOhsfnaig4e8zU=", "cpu": [ "arm64" ], @@ -2815,7 +2699,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 +2719,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 +2739,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 +2759,7 @@ }, "node_modules/lightningcss-win32-arm64-msvc": { "version": "1.32.0", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "integrity": "sha1-TzC6P6XpJfW3n5RejMDRdsOxqzg=", "cpu": [ "arm64" ], @@ -2895,7 +2779,7 @@ }, "node_modules/lightningcss-win32-x64-msvc": { "version": "1.32.0", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "integrity": "sha1-FBqlYFZFBkkokCu0rwRfp9n0Igo=", "cpu": [ "x64" ], @@ -3012,6 +2896,18 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/minizlib": { + "version": "3.1.0", + "integrity": "sha1-atdsOo8QInybUdHJrI4wsn9aJRw=", + "dev": true, + "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 +3388,22 @@ "node": ">=8" } }, + "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", + "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 +3470,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 +3822,15 @@ } } }, + "node_modules/yallist": { + "version": "5.0.0", + "integrity": "sha1-AOLeRDY57Q14/YfeDSdGn7z/tTM=", + "dev": true, + "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..c937d83bf7 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -5,6 +5,7 @@ "url": "https://github.com/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,9 @@ "scripts": { "clean": "rimraf --glob dist *.tgz", "build": "tsx esbuild-copilotsdk-nodejs.ts", + "pack:release": "tsx scripts/package-sdk.ts", + "verify:release-packages": "tsx scripts/verify-release-packages.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,9 +46,10 @@ "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" + "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", @@ -56,7 +61,6 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.83-3", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" @@ -76,6 +80,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/samples/package-lock.json b/nodejs/samples/package-lock.json index 09df5b1ff1..e2a9ab8a00 100644 --- a/nodejs/samples/package-lock.json +++ b/nodejs/samples/package-lock.json @@ -18,7 +18,6 @@ "version": "0.0.0-dev", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.83-3", "koffi": "^3.1.0", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" @@ -38,6 +37,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", @@ -47,10 +47,2535 @@ "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": "https://github.com/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": "https://github.com/sponsors/nzakas" + } + }, + "../node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "../node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "dev": true, + "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": "https://github.com/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": "https://github.com/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": "https://github.com/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": "https://github.com/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": "https://github.com/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": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "../node_modules/chownr": { + "version": "3.0.0", + "dev": true, + "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": "https://github.com/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": "https://github.com/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": "https://github.com/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": "https://github.com/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": "https://github.com/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": "https://github.com/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": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/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": "https://github.com/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": "https://github.com/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": "https://github.com/sponsors/ljharb" + } + }, + "../node_modules/minipass": { + "version": "7.1.3", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "../node_modules/minizlib": { + "version": "3.1.0", + "dev": true, + "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": "https://github.com/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": [ + "https://github.com/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": "https://github.com/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": "https://github.com/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": "https://github.com/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": "https://github.com/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": "https://github.com/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": "https://github.com/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": "https://github.com/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": "https://github.com/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": "https://github.com/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", + "dev": true, + "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": "https://github.com/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": "https://github.com/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", + "dev": true, + "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": "https://github.com/sponsors/eemeli" + } + }, + "../node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "../node_modules/zod": { + "version": "4.3.6", + "license": "MIT", + "funding": { + "url": "https://github.com/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 +2590,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 +2607,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-17.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 +2624,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-17.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 +2641,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 +2656,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-17.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 +2673,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 +2690,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 +2707,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-12.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 +2724,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 +2741,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-2.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 +2758,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-17.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 +2775,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-12.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 +2792,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-17.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 +2809,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-17.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 +2826,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-17.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 +2843,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-12.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 +2860,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-25.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 +2877,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-2.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 +2894,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-25.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 +2911,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-12.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 +2928,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-25.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 +2945,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-2.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 +2962,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-12.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 +2979,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-2.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 +2996,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-25.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 +3017,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 +3025,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 +3036,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 +3077,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 +3095,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/package-sdk.ts b/nodejs/scripts/package-sdk.ts new file mode 100644 index 0000000000..fbdd7c6092 --- /dev/null +++ b/nodejs/scripts/package-sdk.ts @@ -0,0 +1,73 @@ +import { execFileSync } from "node:child_process"; +import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } 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 npmCliPath = process.env.npm_execpath; +if (!npmCliPath) { + throw new Error("package-sdk.ts must be run through an npm script"); +} +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 runtimeWrapper = materializeRuntimeBundle( + { packageRoot: releasePackage, platform }, + stagingRoot, + platform + ); + const runtimeRoot = resolve(dirname(runtimeWrapper), "..", ".."); + 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(process.execPath, [npmCliPath, "pack", runtimeRoot, "--pack-destination", nodeRoot], { + stdio: "inherit", + }); + optionalDependencies[packageName] = sdkVersion; + } + + packageJson.optionalDependencies = optionalDependencies; + writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 4)}\n`); + execFileSync( + process.execPath, + [npmCliPath, "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 new file mode 100644 index 0000000000..1cdfc0a6e7 --- /dev/null +++ b/nodejs/scripts/prepare-runtime.ts @@ -0,0 +1,16 @@ +import { join } from "node:path"; +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") { + process.stdout.write(`${join(packageRoot, "app.js")}\n`); +} else if (option === "--print-path" || option === undefined) { + 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..2731d878c5 --- /dev/null +++ b/nodejs/scripts/releaseArtifacts.ts @@ -0,0 +1,233 @@ +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_USE_NPM_PACKAGE, COPILOT_CLI_VERSION } from "../src/cliVersion.js"; + +export interface EnsureCopilotPackageOptions { + cacheRoot?: string; + environment?: NodeJS.ProcessEnv; + fetch?: typeof globalThis.fetch; + fetchTimeoutMs?: number; + platform?: string; +} + +const packageDownloads = new Map>(); +const checksumDownloads = new Map>>(); +const DEFAULT_FETCH_TIMEOUT_MS = 60_000; + +async function fetchWithRetry( + fetcher: typeof globalThis.fetch, + url: string, + readResponse: (response: Response) => Promise, + timeoutMs: number +): Promise { + let lastError: unknown; + for (let attempt = 0; attempt < 3; attempt++) { + try { + const response = await fetcher(url, { + signal: AbortSignal.timeout(timeoutMs), + }); + if (response.ok) { + return await readResponse(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)}`); +} + +async function getReleaseChecksum( + version: string, + assetName: string, + baseUrl: string, + fetcher: typeof globalThis.fetch, + fetchTimeoutMs: number +): Promise { + const key = `${baseUrl}\0${version}`; + let checksums = checksumDownloads.get(key); + if (!checksums) { + checksums = downloadReleaseChecksums(version, baseUrl, fetcher, fetchTimeoutMs); + checksumDownloads.set(key, checksums); + try { + return (await checksums).get(assetName); + } catch (error) { + checksumDownloads.delete(key); + throw error; + } + } + return (await checksums).get(assetName); +} + +async function downloadReleaseChecksums( + version: string, + baseUrl: string, + fetcher: typeof globalThis.fetch, + fetchTimeoutMs: number +): Promise> { + const contents = await fetchWithRetry( + fetcher, + `${baseUrl}/v${version}/SHA256SUMS.txt`, + (response) => response.text(), + fetchTimeoutMs + ); + const checksums = new Map(); + for (const line of contents.split(/\r?\n/)) { + const [hash, name] = line.trim().split(/\s+/, 2); + if (/^[a-fA-F0-9]{64}$/.test(hash) && name) { + checksums.set(name.replace(/^\*/, ""), hash.toLowerCase()); + } + } + return checksums; +} + +export async function ensureCopilotPackage( + version = COPILOT_CLI_VERSION, + options: EnsureCopilotPackageOptions = {} +): Promise { + const platform = options.platform ?? getRuntimePlatform(); + // lgtm[js/trivial-conditional] This generated constant is true for internal canary builds. + 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 ?? + "https://github.com/github/copilot-cli/releases/download" + ).replace(/\/+$/, ""); + const fetchTimeoutMs = options.fetchTimeoutMs ?? DEFAULT_FETCH_TIMEOUT_MS; + 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, + fetchTimeoutMs + ); + packageDownloads.set(key, download); + try { + return await download; + } finally { + packageDownloads.delete(key); + } + } + return downloadCopilotPackage( + version, + platform, + cacheRoot, + baseUrl, + options.fetch, + fetchTimeoutMs + ); +} + +async function downloadCopilotPackage( + version: string, + platform: string, + cacheRoot: string, + baseUrl: string, + fetcher: typeof globalThis.fetch, + fetchTimeoutMs: number +): Promise { + if (!fetcher) { + throw new Error("This Node.js runtime does not provide fetch()."); + } + const assetName = getRuntimeReleaseAssetName(version, platform); + const expectedChecksum = await getReleaseChecksum( + version, + assetName, + baseUrl, + fetcher, + fetchTimeoutMs + ); + if (!expectedChecksum) { + throw new Error(`SHA256SUMS.txt does not contain ${assetName}.`); + } + const archive = await fetchWithRetry( + fetcher, + `${baseUrl}/v${version}/${assetName}`, + async (response) => Buffer.from(await response.arrayBuffer()), + fetchTimeoutMs + ); + 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/scripts/set-cli-version.js b/nodejs/scripts/set-cli-version.js new file mode 100644 index 0000000000..ea45f90ada --- /dev/null +++ b/nodejs/scripts/set-cli-version.js @@ -0,0 +1,71 @@ +import { readFileSync, writeFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +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 [--npm-package]"); +} +if (mode !== undefined && mode !== "--npm-package") { + throw new Error(`Unknown option: ${mode}`); +} + +const nodeRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); +const runtimePlatforms = [ + "darwin-arm64", + "darwin-x64", + "linux-arm64", + "linux-x64", + "linuxmusl-arm64", + "linuxmusl-x64", + "win32-arm64", + "win32-x64", +]; +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"; +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()]) + ); + for (const assetName of [ + ...runtimePlatforms.map((platform) => `github-copilot-${version}-${platform}.tgz`), + ...cliAssets, + ]) { + if (!checksums.has(assetName)) { + throw new Error(`SHA256SUMS.txt does not contain ${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 sourcePath = join(nodeRoot, "src", "cliVersion.ts"); +writeFileSync( + sourcePath, + [ + `export const COPILOT_CLI_VERSION = ${JSON.stringify(version)};`, + "", + `export const COPILOT_CLI_USE_NPM_PACKAGE = ${useNpmPackage};`, + "", + ].join("\n") +); diff --git a/nodejs/scripts/verify-release-packages.ts b/nodejs/scripts/verify-release-packages.ts new file mode 100644 index 0000000000..e1b13a3074 --- /dev/null +++ b/nodejs/scripts/verify-release-packages.ts @@ -0,0 +1,100 @@ +import assert from "node:assert/strict"; +import { mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import { globSync } from "glob"; +import { t as listTar, x as extractTar } from "tar"; +import { getRuntimePackageName, RUNTIME_PLATFORMS } from "../src/runtimeArtifacts.js"; + +interface PackedPackage { + manifest: { + name: string; + version: string; + optionalDependencies?: Record; + }; + entries: Set; +} + +const nodeRoot = join(dirname(fileURLToPath(import.meta.url)), ".."); +const sourceManifest = JSON.parse( + readFileSync(join(nodeRoot, "package.json"), "utf8") +) as PackedPackage["manifest"]; +const expectedRuntimePackages = Object.fromEntries( + RUNTIME_PLATFORMS.map((platform) => [getRuntimePackageName(platform), sourceManifest.version]) +); +const expectedPackageNames = new Set([ + sourceManifest.name, + ...Object.keys(expectedRuntimePackages), +]); +const packages = new Map(); + +for (const archive of globSync("*.tgz", { cwd: nodeRoot, absolute: true })) { + const entries = new Set(); + await listTar({ + file: archive, + onReadEntry(entry) { + entries.add(entry.path); + entry.resume(); + }, + }); + + const manifestRoot = mkdtempSync(join(tmpdir(), "copilot-sdk-package-manifest-")); + let manifest: PackedPackage["manifest"]; + try { + await extractTar({ + cwd: manifestRoot, + file: archive, + strict: true, + filter: (entryPath) => entryPath === "package/package.json", + }); + manifest = JSON.parse( + readFileSync(join(manifestRoot, "package", "package.json"), "utf8") + ) as PackedPackage["manifest"]; + } finally { + rmSync(manifestRoot, { recursive: true, force: true }); + } + + if (manifest.version !== sourceManifest.version || !expectedPackageNames.has(manifest.name)) { + continue; + } + assert(!packages.has(manifest.name), `Duplicate tarball for ${manifest.name}`); + packages.set(manifest.name, { manifest, entries }); +} + +assert.deepEqual( + [...packages.keys()].sort(), + [...expectedPackageNames].sort(), + "Release packaging did not produce the expected main and platform packages" +); + +const mainPackage = packages.get(sourceManifest.name); +assert(mainPackage, `Missing ${sourceManifest.name} tarball`); +assert.deepEqual( + mainPackage.manifest.optionalDependencies, + expectedRuntimePackages, + "Main package optional dependencies do not match the platform packages" +); +assert(mainPackage.entries.has("package/dist/index.js"), "Main package is missing dist/index.js"); +assert( + mainPackage.entries.has("package/dist/cjs/index.js"), + "Main package is missing dist/cjs/index.js" +); + +for (const platform of RUNTIME_PLATFORMS) { + const packageName = getRuntimePackageName(platform); + const packed = packages.get(packageName); + assert(packed, `Missing ${packageName} tarball`); + const runtimeName = platform.startsWith("win32") ? "copilot-runtime.exe" : "copilot-runtime"; + for (const requiredPath of [ + `package/prebuilds/${platform}/${runtimeName}`, + `package/prebuilds/${platform}/runtime.node`, + "package/copilot-sdk/extension.js", + "package/preloads/extension_bootstrap.mjs", + "package/sdk/index.js", + ]) { + assert(packed.entries.has(requiredPath), `${packageName} is missing ${requiredPath}`); + } +} + +console.log(`Verified ${packages.size} release package tarballs.`); diff --git a/nodejs/src/cliVersion.ts b/nodejs/src/cliVersion.ts new file mode 100644 index 0000000000..a0c4eb8558 --- /dev/null +++ b/nodejs/src/cliVersion.ts @@ -0,0 +1,3 @@ +export const COPILOT_CLI_VERSION = "1.0.83-3"; + +export const COPILOT_CLI_USE_NPM_PACKAGE = false; diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 83b0a5f483..51c3e6650d 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(): Promise { + return ensureRuntimeBundle(COPILOT_CLI_VERSION); } /** @@ -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(); 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.` ); } @@ -2842,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(getBundledRuntimePath()), "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 19d9926250..d33b72c9da 100644 --- a/nodejs/src/runtimeArtifacts.ts +++ b/nodejs/src/runtimeArtifacts.ts @@ -1,4 +1,3 @@ -import { createHash } from "node:crypto"; import { chmodSync, copyFileSync, @@ -11,32 +10,50 @@ import { rmSync, statSync, } 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 { COPILOT_CLI_USE_NPM_PACKAGE } from "./cliVersion.js"; export interface RuntimeArtifactSources { packageRoot: string; platform: string; } +export interface EnsureRuntimeBundleOptions { + cacheRoot?: string; + packageSearchPaths?: string[]; + platform?: string; +} + +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", "assets", "changelog.json", "copilot", "copilot.exe", - "copilot-sdk", "foundry-local-sdk", "index.js", "LICENSE.md", "napi-oop-runtime", "npm-loader.js", "package.json", - "preloads", "pvrecorder", "queries", "README.md", - "sdk", "sea-loader.js", "webview", ]); @@ -46,7 +63,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}.`); } @@ -92,14 +109,12 @@ function collectRuntimeAssets(sources: RuntimeArtifactSources): RuntimeAsset[] { } const parts = sourceRelative.split(sep); - let relativePath = sourceRelative; if (parts[0] === "prebuilds") { if (parts[1] !== sources.platform || parts.length < 3) { continue; } - relativePath = parts.slice(2).join(sep); } - assets.push({ source, relativePath }); + assets.push({ source, relativePath: sourceRelative }); } }; visit(sources.packageRoot); @@ -142,17 +157,25 @@ 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 sourceWrapper = assets.find((asset) => asset.relativePath === wrapperName)?.source; - const sourceRuntimeNode = assets.find((asset) => asset.relativePath === "runtime.node")?.source; + const wrapperName = sources.platform.startsWith("win32") + ? "copilot-runtime.exe" + : "copilot-runtime"; + const prebuildDir = join("prebuilds", sources.platform); + const wrapperRelative = join(prebuildDir, wrapperName); + const runtimeNodeRelative = join(prebuildDir, "runtime.node"); + const sourceWrapper = assets.find((asset) => asset.relativePath === wrapperRelative)?.source; + const sourceRuntimeNode = assets.find( + (asset) => asset.relativePath === runtimeNodeRelative + )?.source; validateRuntimeBundle(sourceWrapper ?? "", sourceRuntimeNode ?? ""); - const installDir = join(cacheRoot, `${sources.platform}-${sourceFingerprint(assets)}`); - const installedWrapper = join(installDir, wrapperName); - const installedRuntimeNode = join(installDir, "runtime.node"); + const installDir = join(cacheRoot, cacheKey); + const installedWrapper = join(installDir, wrapperRelative); + const installedRuntimeNode = join(installDir, runtimeNodeRelative); if (existsSync(installDir)) { validateRuntimeBundle(installedWrapper, installedRuntimeNode); makeExecutable(installedWrapper); @@ -167,7 +190,7 @@ export function materializeRuntimeBundle( mkdirSync(dirname(destination), { recursive: true }); copyFileSync(asset.source, destination); } - const stagedWrapper = join(stagingDir, wrapperName); + const stagedWrapper = join(stagingDir, wrapperRelative); makeExecutable(stagedWrapper); renameSync(stagingDir, installDir); } catch (error) { @@ -181,3 +204,92 @@ 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`; +} + +export function getRuntimePackageName(platform: string): string { + return `@github/copilot-sdk-${platform}`; +} + +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( + version: string, + options: EnsureRuntimeBundleOptions = {} +): Promise { + const platform = options.platform ?? getRuntimePlatform(); + // lgtm[js/trivial-conditional] This generated constant is true for internal canary builds. + if (COPILOT_CLI_USE_NPM_PACKAGE) { + const packageName = `@github/copilot-${platform}`; + const packageRoot = resolvePackageRoot(packageName, options.packageSearchPaths); + if (!packageRoot) { + throw new Error(`Could not resolve ${packageName} for Copilot CLI ${version}.`); + } + validateFile( + join(packageRoot, "prebuilds", platform, "runtime.node"), + "Copilot runtime.node" + ); + return materializeRuntimeBundle( + { packageRoot, platform }, + options.cacheRoot, + `${version}-${platform}` + ); + } + + const packageName = getRuntimePackageName(platform); + const packageRoot = resolvePackageRoot(packageName, options.packageSearchPaths); + if (!packageRoot) { + throw new Error( + `Could not resolve ${packageName}. Reinstall @github/copilot-sdk so its platform package is installed.` + ); + } + const wrapperName = platform.startsWith("win32") ? "copilot-runtime.exe" : "copilot-runtime"; + const prebuildDir = join(packageRoot, "prebuilds", platform); + const wrapper = join(prebuildDir, wrapperName); + try { + validateRuntimeBundle(wrapper, join(prebuildDir, "runtime.node")); + } catch (error) { + throw new Error( + `${packageName} is missing required Copilot CLI runtime files. Reinstall @github/copilot-sdk.`, + { cause: error } + ); + } + makeExecutable(wrapper); + return wrapper; +} diff --git a/nodejs/test/e2e/event_fidelity.e2e.test.ts b/nodejs/test/e2e/event_fidelity.e2e.test.ts index da4b8105ad..8ae2091b33 100644 --- a/nodejs/test/e2e/event_fidelity.e2e.test.ts +++ b/nodejs/test/e2e/event_fidelity.e2e.test.ts @@ -35,9 +35,9 @@ describe("Event Fidelity", async () => { const assistantIdx = types.lastIndexOf("assistant.message"); expect(userIdx).toBeLessThan(assistantIdx); - // session.idle should be last + // session.idle completes the conversational turn; metadata may follow it. const idleIdx = types.lastIndexOf("session.idle"); - expect(idleIdx).toBe(types.length - 1); + expect(assistantIdx).toBeLessThan(idleIdx); await session.disconnect(); }); 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..c4befb148e 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 "../../../scripts/releaseArtifacts"; +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..ffd22f21ef 100644 --- a/nodejs/test/runtimeArtifacts.test.ts +++ b/nodejs/test/runtimeArtifacts.test.ts @@ -1,9 +1,21 @@ import { existsSync, mkdtempSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; -import { dirname, join } from "node:path"; +import { dirname, join, resolve } 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"; -import { defaultRuntimeCacheRoot, materializeRuntimeBundle } from "../src/runtimeArtifacts.js"; +import { + defaultRuntimeCacheRoot, + ensureRuntimeBundle, + getRuntimePackageName, + getRuntimePlatform, + getRuntimeReleaseAssetName, + materializeRuntimeBundle, +} from "../src/runtimeArtifacts.js"; +import { COPILOT_CLI_USE_NPM_PACKAGE, COPILOT_CLI_VERSION } from "../src/cliVersion.js"; +import { ensureCopilotPackage } from "../scripts/releaseArtifacts.js"; describe("defaultRuntimeCacheRoot", () => { it.each([ @@ -31,6 +43,66 @@ 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); + // lgtm[js/trivial-conditional] This generated constant is true for internal canary builds. + if (COPILOT_CLI_USE_NPM_PACKAGE) { + expect(packageJson.dependencies["@github/copilot"]).toBe(COPILOT_CLI_VERSION); + } else { + 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(existsSync(join(root, "copilot-cli.json"))).toBe(false); + expect(readFileSync(join(root, "src", "cliVersion.ts"), "utf8")).toContain( + "COPILOT_CLI_USE_NPM_PACKAGE = true" + ); + }); + + 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" + ); + }); + + it("uses the SDK platform package namespace", () => { + expect(getRuntimePackageName("linux-x64")).toBe("@github/copilot-sdk-linux-x64"); + }); +}); + describe("materializeRuntimeBundle", () => { afterEach(() => vi.unstubAllEnvs()); @@ -39,18 +111,26 @@ describe("materializeRuntimeBundle", () => { const cacheRoot = join(sourceDir, "absent-cache"); const emptyPath = join(sourceDir, "empty-path"); mkdirSync(emptyPath); - const wrapperName = - process.platform === "win32" ? "copilot-runtime.exe" : "copilot-runtime"; - const prebuilds = join(sourceDir, "prebuilds", "test-platform"); + const platform = process.platform === "win32" ? "win32-x64" : "test-platform"; + const wrapperName = platform.startsWith("win32") + ? "copilot-runtime.exe" + : "copilot-runtime"; + const prebuilds = join(sourceDir, "prebuilds", platform); const wrapper = join(prebuilds, wrapperName); const runtimeNode = join(prebuilds, "runtime.node"); mkdirSync(prebuilds, { recursive: true }); writeFileSync(wrapper, "wrapper"); writeFileSync(runtimeNode, "runtime"); - mkdirSync(join(sourceDir, "ripgrep", "bin", "test-platform"), { recursive: true }); - writeFileSync(join(sourceDir, "ripgrep", "bin", "test-platform", "rg"), "ripgrep"); + mkdirSync(join(sourceDir, "ripgrep", "bin", platform), { recursive: true }); + writeFileSync(join(sourceDir, "ripgrep", "bin", platform, "rg"), "ripgrep"); mkdirSync(join(sourceDir, "definitions"), { recursive: true }); writeFileSync(join(sourceDir, "definitions", "future.json"), "{}"); + mkdirSync(join(sourceDir, "copilot-sdk"), { recursive: true }); + writeFileSync(join(sourceDir, "copilot-sdk", "extension.js"), "extension SDK"); + mkdirSync(join(sourceDir, "preloads"), { recursive: true }); + writeFileSync(join(sourceDir, "preloads", "extension_bootstrap.mjs"), "bootstrap"); + mkdirSync(join(sourceDir, "sdk"), { recursive: true }); + writeFileSync(join(sourceDir, "sdk", "index.js"), "legacy SDK"); writeFileSync(join(sourceDir, "app.js"), "excluded"); writeFileSync(join(sourceDir, "copilot"), "excluded"); writeFileSync(join(sourceDir, "copilot.exe"), "excluded"); @@ -67,16 +147,25 @@ describe("materializeRuntimeBundle", () => { expect(process.env.COPILOT_RUNTIME_PROVIDER_LIB).toBeUndefined(); const installedWrapper = materializeRuntimeBundle( - { packageRoot: sourceDir, platform: "test-platform" }, + { packageRoot: sourceDir, platform }, cacheRoot ); - const installDir = dirname(installedWrapper); + const installDir = resolve(dirname(installedWrapper), "..", ".."); expect(readFileSync(installedWrapper, "utf8")).toBe("wrapper"); - expect(readFileSync(join(installDir, "runtime.node"), "utf8")).toBe("runtime"); - expect( - readFileSync(join(installDir, "ripgrep", "bin", "test-platform", "rg"), "utf8") - ).toBe("ripgrep"); + expect(readFileSync(join(installDir, "prebuilds", platform, "runtime.node"), "utf8")).toBe( + "runtime" + ); + expect(readFileSync(join(installDir, "ripgrep", "bin", platform, "rg"), "utf8")).toBe( + "ripgrep" + ); + expect(readFileSync(join(installDir, "copilot-sdk", "extension.js"), "utf8")).toBe( + "extension SDK" + ); + expect(readFileSync(join(installDir, "preloads", "extension_bootstrap.mjs"), "utf8")).toBe( + "bootstrap" + ); + expect(readFileSync(join(installDir, "sdk", "index.js"), "utf8")).toBe("legacy SDK"); expect(existsSync(join(installDir, "app.js"))).toBe(false); expect(existsSync(join(installDir, "copilot"))).toBe(false); expect(existsSync(join(installDir, "copilot.exe"))).toBe(false); @@ -89,9 +178,11 @@ describe("materializeRuntimeBundle", () => { it("fails clearly when the package has no runtime.node", () => { const sourceDir = mkdtempSync(join(tmpdir(), "copilot-runtime-missing-node-")); - const wrapperName = - process.platform === "win32" ? "copilot-runtime.exe" : "copilot-runtime"; - const prebuilds = join(sourceDir, "prebuilds", "test-platform"); + const platform = process.platform === "win32" ? "win32-x64" : "test-platform"; + const wrapperName = platform.startsWith("win32") + ? "copilot-runtime.exe" + : "copilot-runtime"; + const prebuilds = join(sourceDir, "prebuilds", platform); const wrapper = join(prebuilds, wrapperName); mkdirSync(prebuilds, { recursive: true }); writeFileSync(wrapper, "wrapper"); @@ -100,10 +191,140 @@ describe("materializeRuntimeBundle", () => { materializeRuntimeBundle( { packageRoot: sourceDir, - platform: "test-platform", + platform, }, join(sourceDir, "cache") ) ).toThrow(/Copilot runtime\.node not found/); }); }); + +describe("ensureRuntimeBundle", () => { + 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("/")); + const prebuilds = join(packageRoot, "prebuilds", platform); + mkdirSync(prebuilds, { recursive: true }); + writeFileSync(join(packageRoot, "package.json"), "{}"); + writeFileSync(join(prebuilds, "copilot-runtime"), "wrapper"); + writeFileSync(join(prebuilds, "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(prebuilds, "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"; + 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 (input: string | URL | Request) => + String(input).endsWith("/SHA256SUMS.txt") + ? new Response(`${checksum} ${assetName}\n`) + : new Response(archive) + ); + const cacheRoot = join(sourceRoot, "cache"); + + const downloadedPackage = await ensureCopilotPackage(version, { + cacheRoot, + fetch: fetcher, + platform, + }); + expect(readFileSync(join(downloadedPackage, "schemas", "api.schema.json"), "utf8")).toBe( + "{}" + ); + + await expect( + ensureCopilotPackage(version, { + cacheRoot, + fetch: fetcher, + platform, + }) + ).resolves.toBe(downloadedPackage); + expect(fetcher).toHaveBeenCalledTimes(2); + }); + + it("rejects a release package that does not match SHA256SUMS.txt", async () => { + const cacheRoot = mkdtempSync(join(tmpdir(), "copilot-release-mismatch-")); + const assetName = getRuntimeReleaseAssetName("1.2.3", "linux-x64"); + const fetcher = vi.fn(async (input: string | URL | Request) => + String(input).endsWith("/SHA256SUMS.txt") + ? new Response(`${"0".repeat(64)} ${assetName}\n`) + : new Response("corrupt archive") + ); + + await expect( + ensureCopilotPackage("1.2.3", { + cacheRoot, + fetch: fetcher, + platform: "linux-x64", + }) + ).rejects.toThrow("Checksum mismatch"); + expect(existsSync(join(cacheRoot, "1.2.3", "packages", "linux-x64"))).toBe(false); + }); + + it("times out and retries a stalled release download", async () => { + const cacheRoot = mkdtempSync(join(tmpdir(), "copilot-release-timeout-")); + const fetcher = vi.fn( + (_input: string | URL | Request, init?: RequestInit) => + new Promise((_resolve, reject) => { + const signal = init?.signal; + if (!signal) { + reject(new Error("Expected a request timeout signal.")); + return; + } + signal.addEventListener("abort", () => reject(signal.reason), { once: true }); + }) + ); + + await expect( + ensureCopilotPackage("1.2.3-timeout", { + cacheRoot, + fetch: fetcher, + fetchTimeoutMs: 10, + platform: "linux-x64", + }) + ).rejects.toThrow("Failed to download"); + expect(fetcher).toHaveBeenCalledTimes(3); + }); +}); diff --git a/python/copilot/_ffi_runtime_host.py b/python/copilot/_ffi_runtime_host.py index 98aa776600..5665f8fba7 100644 --- a/python/copilot/_ffi_runtime_host.py +++ b/python/copilot/_ffi_runtime_host.py @@ -121,7 +121,8 @@ def resolve_library_path(runtime_entrypoint: str) -> str | None: 1. The natural platform library name next to the CLI (bundled/flat layout, what the Python download-at-first-use path writes). - 2. ``prebuilds//runtime.node`` next to the CLI (dev/package layout). + 2. ``runtime.node`` next to the CLI (prepared npm runtime layout). + 3. ``prebuilds//runtime.node`` next to the CLI (package-root layout). Returns the absolute path, or ``None`` when neither exists. """ @@ -131,6 +132,10 @@ def resolve_library_path(runtime_entrypoint: str) -> str | None: if flat.is_file(): return str(flat) + adjacent_prebuilt = directory / "runtime.node" + if adjacent_prebuilt.is_file(): + return str(adjacent_prebuilt) + folder = get_prebuilds_folder() if folder is not None: prebuilt = directory / "prebuilds" / folder / "runtime.node" diff --git a/python/copilot/_jsonrpc.py b/python/copilot/_jsonrpc.py index 6427a8007f..11baf0bd10 100644 --- a/python/copilot/_jsonrpc.py +++ b/python/copilot/_jsonrpc.py @@ -296,29 +296,31 @@ def _read_loop(self): def _fail_pending_requests(self): """Fail all pending requests when process exits""" + error_msg = self._get_process_exit_error() + + # Fail all pending requests + with self._pending_lock: + for request_id, future in list(self.pending_requests.items()): + if not future.done(): + exc = ProcessExitedError(error_msg) + loop = future.get_loop() + loop.call_soon_threadsafe(future.set_exception, exc) + + def _get_process_exit_error(self) -> str: + """Build an error message after the process and stderr readers finish.""" if self._stderr_thread and self._stderr_thread is not threading.current_thread(): self._stderr_thread.join(timeout=1.0) - # Build error message with stderr output stderr_output = self.get_stderr_output() return_code = None if hasattr(self.process, "poll"): return_code = self.process.poll() if stderr_output: - error_msg = f"CLI process exited with code {return_code}\nstderr: {stderr_output}" - elif return_code is not None: - error_msg = f"CLI process exited with code {return_code}" - else: - error_msg = "CLI process exited unexpectedly" - - # Fail all pending requests - with self._pending_lock: - for request_id, future in list(self.pending_requests.items()): - if not future.done(): - exc = ProcessExitedError(error_msg) - loop = future.get_loop() - loop.call_soon_threadsafe(future.set_exception, exc) + return f"CLI process exited with code {return_code}\nstderr: {stderr_output}" + if return_code is not None: + return f"CLI process exited with code {return_code}" + return "CLI process exited unexpectedly" def _read_exact(self, num_bytes: int) -> bytes: """ diff --git a/python/copilot/client.py b/python/copilot/client.py index 929194c9b7..f4d7da75a7 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -2012,13 +2012,14 @@ async def start(self) -> None: # Check if process exited and capture any remaining stderr process = self._cli_process if self._cli_process is not None else self._process if process and hasattr(process, "poll"): + if isinstance(e, BrokenPipeError) and process.poll() is None: + try: + await asyncio.to_thread(process.wait, timeout=1.0) + except subprocess.TimeoutExpired: + pass return_code = process.poll() if return_code is not None and self._client: - stderr_output = self._client.get_stderr_output() - if stderr_output: - raise RuntimeError( - f"CLI process exited with code {return_code}\nstderr: {stderr_output}" - ) from e + raise RuntimeError(self._client._get_process_exit_error()) from e raise async def stop(self) -> None: @@ -4792,7 +4793,10 @@ async def _apply_post_create_options_patch( try: await session.disconnect() except BaseException: - pass + logger.debug( + "Error disconnecting session after options update failure", + exc_info=True, + ) raise async def _set_session_fs_provider(self) -> None: diff --git a/python/e2e/conftest.py b/python/e2e/conftest.py index a789b2b567..d61fe0d875 100644 --- a/python/e2e/conftest.py +++ b/python/e2e/conftest.py @@ -20,10 +20,8 @@ # 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() - ) - cli_download.CLI_VERSION = package_lock["packages"]["node_modules/@github/copilot"]["version"] + package_json = json.loads((Path(__file__).parents[2] / "nodejs" / "package.json").read_text()) + cli_download.CLI_VERSION = package_json["copilotCliVersion"] if is_inprocess_transport(): os.environ.pop("COPILOT_HMAC_KEY", None) diff --git a/python/e2e/test_event_fidelity_e2e.py b/python/e2e/test_event_fidelity_e2e.py index 25b18407a0..6b14ab2486 100644 --- a/python/e2e/test_event_fidelity_e2e.py +++ b/python/e2e/test_event_fidelity_e2e.py @@ -46,7 +46,7 @@ async def test_should_emit_events_in_correct_order_for_tool_using_conversation( assert user_idx < assistant_idx idle_idx = len(types) - 1 - types[::-1].index("session.idle") - assert idle_idx == len(types) - 1 + assert assistant_idx < idle_idx finally: unsubscribe() await session.disconnect() diff --git a/python/e2e/test_rpc_server_misc_e2e.py b/python/e2e/test_rpc_server_misc_e2e.py index d5ade1aec1..2b6b7d9514 100644 --- a/python/e2e/test_rpc_server_misc_e2e.py +++ b/python/e2e/test_rpc_server_misc_e2e.py @@ -131,8 +131,11 @@ async def test_should_login_list_get_current_auth_and_logout_account(self, ctx: client, home = await _create_isolated_client(ctx, github_token=None) try: - initial = await client.rpc.account.get_current_auth() - assert initial.auth_info is None + assert client._client is not None + initial_current = await client._client.request("account.getCurrentAuth", {}) + initial_auth_info = initial_current.get("authInfo") + assert initial_auth_info is not None + assert initial_auth_info.get("login") != login login_result = await client.rpc.account.login( AccountLoginRequest(host="https://github.com", login=login, token=token) @@ -152,21 +155,26 @@ async def test_should_login_list_get_current_auth_and_logout_account(self, ctx: ( user for user in users - if user.auth_info.type == "user" - and getattr(user.auth_info, "login", None) == login + if user.get("authInfo", {}).get("type") == "user" + and user.get("authInfo", {}).get("login") == login ), None, ) if account is not None: - assert account.token == token + assert account.get("token") == token logout = await client.rpc.account.logout( AccountLogoutRequest(auth_info=current.auth_info) ) assert logout.has_more_users is False - after_logout = await client.rpc.account.get_current_auth() - assert after_logout.auth_info is None + users_after_logout = await client.rpc.account.get_all_users() + assert all( + user.get("authInfo", {}).get("login") != login for user in users_after_logout + ) + + current_after_logout = await client._client.request("account.getCurrentAuth", {}) + assert current_after_logout.get("authInfo") == initial_auth_info finally: await _dispose_isolated(client, home) 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/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/python/test_cli_download.py b/python/test_cli_download.py index a5a20dce0d..aac652daf8 100644 --- a/python/test_cli_download.py +++ b/python/test_cli_download.py @@ -11,7 +11,7 @@ import pytest -from copilot import _cli_download +from copilot import _cli_download, _ffi_runtime_host def _integrity(data: bytes, algo: str = "sha512") -> str: @@ -78,6 +78,15 @@ def test_raises_when_integrity_unavailable(self, tmp_path): extract.assert_not_called() +def test_resolve_library_path_accepts_adjacent_runtime_node(tmp_path): + wrapper = tmp_path / ("copilot-runtime.exe" if os.name == "nt" else "copilot-runtime") + wrapper.write_bytes(b"wrapper") + runtime_node = tmp_path / "runtime.node" + runtime_node.write_bytes(b"runtime") + + assert _ffi_runtime_host.resolve_library_path(str(wrapper)) == str(runtime_node) + + class TestEnsureRuntimeWrapper: def test_materializes_pair_from_absent_cache_with_stripped_environment( self, tmp_path, monkeypatch 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/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..a561e6da09 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-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). 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 release's `SHA256SUMS.txt` or the 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 e6edcf1b90..c01e7ffc4f 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_json = 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_json.is_file() { + println!("cargo:rerun-if-changed={}", package_json.display()); } // Hard opt-out: disable the entire download / bundle / cache mechanism @@ -60,17 +59,13 @@ 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 and, when available locally, the 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 - // 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); + // consumer; generated by the publish workflow from SHA256SUMS.txt). + // 2. Sibling `../nodejs/package.json` plus the release SHA256SUMS.txt + // (contributor build inside the github/copilot-sdk repo). + let (version, local_expected_hash) = resolve_version_and_optional_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 +76,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 + "https://github.com/github/copilot-cli/releases/download/v{version}/{archive_name}" ); let cache_dir = std::env::var("BUNDLED_CLI_CACHE_DIR") .ok() @@ -94,7 +92,10 @@ 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 expected_hash = local_expected_hash + .clone() + .unwrap_or_else(|| fetch_in_process_release_hash(&version, platform.package_name)); + 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,8 +113,6 @@ 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"); - // Invalidate build.rs whenever either cached artifact disappears (cache // GC, manual rm, OS reset, switching extract dir). Without this, cargo // replays the saved `has_extracted_cli` cfg from its build-script @@ -123,10 +122,20 @@ pub(crate) fn main() { println!("cargo:rerun-if-changed={}", path.display()); } + let marker = std::fs::read_to_string(&required_paths[2]).ok(); let cache_is_current = required_paths.iter().all(|path| path.is_file()) - && std::fs::read_to_string(&required_paths[2]).ok().as_deref() - == Some(expected_marker.as_str()); + && match local_expected_hash.as_deref() { + Some(expected_hash) => { + marker.as_deref() == Some(&format!("{version}\n{expected_hash}\n")) + } + None => marker + .as_deref() + .is_some_and(|contents| marker_matches_version(contents, &version)), + }; if !cache_is_current { + let expected_hash = local_expected_hash + .unwrap_or_else(|| fetch_in_process_release_hash(&version, platform.package_name)); + let expected_marker = format!("{version}\n{expected_hash}\n"); if install_dir.exists() { std::fs::remove_dir_all(&install_dir).unwrap_or_else(|e| { panic!( @@ -135,8 +144,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, @@ -199,7 +207,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 +229,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 +257,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 +269,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 { @@ -276,18 +298,15 @@ fn hostless_runtime_path(source: &str, platform: Platform) -> Option { "app.js", "assets", "changelog.json", - "copilot-sdk", "foundry-local-sdk", "index.js", "LICENSE.md", "napi-oop-runtime", "npm-loader.js", "package.json", - "preloads", "pvrecorder", "queries", "README.md", - "sdk", "sea-loader.js", "webview", ]; @@ -331,10 +350,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 -/// 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) { +/// Resolve the CLI version and any locally snapshotted release hash for the +/// current target's platform package. Contributor builds defer fetching the +/// checksum until a download is actually required. +fn resolve_version_and_optional_hash(package_name: &str) -> (String, Option) { 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, @@ -343,17 +362,19 @@ fn resolve_version_and_integrity(package_name: &str) -> (String, String) { if snapshot.is_file() { let contents = std::fs::read_to_string(&snapshot) .unwrap_or_else(|e| panic!("failed to read {}: {e}", snapshot.display())); - return parse_snapshot(&contents, package_name) + let (version, hash) = parse_snapshot(&contents, package_name) .unwrap_or_else(|e| panic!("invalid {}: {e}", snapshot.display())); + return (version, Some(hash)); } - // 2. Lockfile fallback (contributor build inside github/copilot-sdk). - let lockfile = Path::new(&manifest_dir) + // 2. Package version plus release checksums (contributor build). + let package_json = 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_json.is_file() { + let version = read_version_from_package_json(&package_json); + return (version, None); } panic!( @@ -362,19 +383,36 @@ 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 version source.", snapshot.display(), - lockfile.display(), + package_json.display(), ); } +fn fetch_in_process_release_hash(version: &str, package_name: &str) -> String { + let platform = package_name + .strip_prefix("copilot-") + .expect("platform package names start with copilot-"); + let asset_name = format!("github-copilot-{version}-{platform}.tgz"); + fetch_release_hash(version, &asset_name) +} + +fn marker_matches_version(contents: &str, version: &str) -> bool { + let mut lines = contents.lines(); + lines.next() == Some(version) + && lines.next().is_some_and(|hash| { + hash.len() == 64 && hash.bytes().all(|byte| byte.is_ascii_hexdigit()) + }) + && lines.next().is_none() +} + /// 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 +426,42 @@ 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_json: 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"] + package_json["copilotCliVersion"] .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"] - .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_release_hash(version: &str, asset_name: &str) -> String { + let url = format!( + "https://github.com/github/copilot-cli/releases/download/v{version}/SHA256SUMS.txt" + ); + let checksums = download_with_retry(&url); + let checksums = std::str::from_utf8(&checksums).expect("SHA256SUMS.txt is not valid UTF-8"); + find_sha256_for_asset(checksums, asset_name) +} + +fn find_sha256_for_asset(sums: &str, asset_name: &str) -> String { + sums.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 does not contain {asset_name}")) } #[derive(Clone, Copy)] @@ -695,20 +742,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 +775,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 +879,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 +907,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..c0a9dd3050 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_json = 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_json.is_file() { + println!("cargo:rerun-if-changed={}", package_json.display()); } // Hard opt-out: disable the entire download / bundle / cache mechanism @@ -61,15 +61,9 @@ pub(crate) fn main() { // Resolve version + per-asset SHA-256 from one of two sources, in order: // 1. `cli-version.txt` snapshot at the crate root (published-crate - // consumer; generated by the publish workflow). Combined format: - // `version=X` line + per-asset hash lines. Committing the hashes - // 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; live SHA256SUMS.txt fetch). Matches - // the .NET `_GetCopilotCliVersion` MSBuild target and the Go - // `cmd/bundler` tool. + // consumer; generated by the publish workflow from SHA256SUMS.txt). + // 2. Sibling `../nodejs/package.json` plus the release SHA256SUMS.txt + // (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,15 +188,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) — - // read version, fetch live SHA256SUMS. - let lockfile = Path::new(&manifest_dir) + // 2. Package version plus release checksums (contributor build). + let package_json = Path::new(&manifest_dir) .join("..") .join("nodejs") - .join("package-lock.json"); - if lockfile.is_file() { - let version = read_version_from_package_lock(&lockfile); - let hash = fetch_live_sha256(&version, asset_name); + .join("package.json"); + if package_json.is_file() { + let version = read_version_from_package_json(&package_json); + let hash = fetch_release_hash(&version, asset_name); return (version, hash); } @@ -212,9 +205,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 version source.", snapshot.display(), - lockfile.display(), + package_json.display(), ); } @@ -247,39 +240,33 @@ 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 { +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_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"); - after_q1[..q2].to_string() + let package_json: serde_json::Value = serde_json::from_str(&contents) + .unwrap_or_else(|e| panic!("failed to parse {}: {e}", path.display())); + package_json["copilotCliVersion"] + .as_str() + .unwrap_or_else(|| panic!("copilotCliVersion is missing in {}", path.display())) + .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!("https://github.com/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) +fn fetch_release_hash(version: &str, asset_name: &str) -> String { + let url = + format!("https://github.com/github/copilot-cli/releases/download/v{version}/SHA256SUMS.txt"); + let checksums = download_with_retry(&url); + let checksums = + std::str::from_utf8(&checksums).expect("SHA256SUMS.txt is not valid UTF-8"); + find_sha256_for_asset(checksums, asset_name) +} + +fn find_sha256_for_asset(sums: &str, asset_name: &str) -> String { + sums.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 does not contain {asset_name}")) } #[derive(Clone, Copy)] @@ -641,18 +628,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 7f78d529b0..08b19ebc2b 100755 --- a/rust/scripts/snapshot-bundled-cli-version.sh +++ b/rust/scripts/snapshot-bundled-cli-version.sh @@ -5,10 +5,9 @@ # how .NET's _GenerateVersionProps BeforeTargets="Pack" target writes # GitHub.Copilot.SDK.props before NuGet packing. # -# Inputs: -# - ../nodejs/package-lock.json (sibling) - source of the pinned version. -# - https://github.com/github/copilot-cli/releases/v{version}/SHA256SUMS.txt - -# authoritative per-platform hashes. +# Input: +# - ../nodejs/package.json - pinned release version. +# - The release's SHA256SUMS.txt - per-platform hashes. # # Output: # - cli-version.txt (in the rust crate root). Gitignored. @@ -18,23 +17,21 @@ 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 - CHECKSUMS_URL="https://github.com/github/copilot-cli/releases/download/v${VERSION}/SHA256SUMS.txt" -echo "Fetching ${CHECKSUMS_URL}" -SHA256SUMS="$(curl -fsSL --retry 3 --retry-delay 2 "${CHECKSUMS_URL}")" +SHA256SUMS="$(curl --fail --silent --show-error --location --retry 3 "${CHECKSUMS_URL}")" ASSETS=( "copilot-darwin-arm64.tar.gz" @@ -45,23 +42,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 asset="${asset}" '$2 == asset || $2 == "*" asset { print $1; exit }')" + if [[ -z "${hash}" ]]; then + echo "error: SHA256SUMS.txt does not contain ${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..9fe2298c78 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,19 +8,21 @@ 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="https://github.com/github/copilot-cli/releases/download/v${VERSION}/SHA256SUMS.txt" +SHA256SUMS="$(curl --fail --silent --show-error --location --retry 3 "${CHECKSUMS_URL}")" PACKAGES=( "copilot-darwin-arm64" @@ -33,23 +35,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 asset="${asset}" '$2 == asset || $2 == "*" asset { print $1; exit }')" + if [[ -z "${hash}" ]]; then + echo "error: SHA256SUMS.txt does not contain ${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..847ac7a4d0 100644 --- a/rust/tests/cli_resolution_test.rs +++ b/rust/tests/cli_resolution_test.rs @@ -199,16 +199,17 @@ 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` and +/// the release's `SHA256SUMS.txt` — /// 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 +231,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..0b7bbae7a7 100644 --- a/rust/tests/e2e/support.rs +++ b/rust/tests/e2e/support.rs @@ -674,7 +674,7 @@ impl E2eContext { impl SharedE2eState { async fn prepare_test(&mut self, category: &str, snapshot_name: &str) -> std::io::Result<()> { self.cleanup_sessions().await?; - clear_directory_contents(self.context.work_dir())?; + clear_directory_contents(self.context.work_dir()).await?; self.context.configure(category, snapshot_name)?; self.context.set_default_copilot_user(); Ok(()) @@ -682,7 +682,7 @@ impl SharedE2eState { async fn cleanup_after_test(&mut self) -> std::io::Result<()> { self.cleanup_sessions().await?; - clear_directory_contents(self.context.work_dir()) + clear_directory_contents(self.context.work_dir()).await } async fn cleanup_sessions(&self) -> std::io::Result<()> { @@ -782,19 +782,36 @@ fn is_filtered_test_run() -> bool { }) } -fn clear_directory_contents(directory: &Path) -> std::io::Result<()> { +async fn clear_directory_contents(directory: &Path) -> std::io::Result<()> { for entry in std::fs::read_dir(directory)? { let entry = entry?; let path = entry.path(); - if entry.file_type()?.is_dir() { - std::fs::remove_dir_all(path)?; - } else { - std::fs::remove_file(path)?; + let is_directory = entry.file_type()?.is_dir(); + + for attempt in 1..=20 { + let result = if is_directory { + std::fs::remove_dir_all(&path) + } else { + std::fs::remove_file(&path) + }; + + match result { + Ok(()) => break, + Err(error) if error.kind() == std::io::ErrorKind::NotFound => break, + Err(error) if is_transient_windows_file_lock(&error) && attempt < 20 => { + tokio::time::sleep(Duration::from_millis(50)).await; + } + Err(error) => return Err(error), + } } } Ok(()) } +fn is_transient_windows_file_lock(error: &std::io::Error) -> bool { + cfg!(windows) && matches!(error.raw_os_error(), Some(5 | 32 | 145)) +} + impl Drop for E2eContext { fn drop(&mut self) { if let Some(mut proxy) = self.proxy.take() { @@ -1175,29 +1192,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() ), )) } diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index 590213d72b..bad31a7675 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 { ensureCopilotPackage } from "../../nodejs/scripts/releaseArtifacts.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 packageRoot = await ensureCopilotPackage(COPILOT_CLI_VERSION); + const schemaPath = path.join(packageRoot, "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": "https://github.com/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": [ "https://github.com/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",