TEMP: re-add branch-push validation trigger #9
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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.<sha>)" | |
| 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 | |
| # TEMPORARY: branch-push trigger so we can validate the pipeline before the | |
| # workflow_dispatch entrypoint exists on main. Push events carry no dispatch | |
| # inputs, so the resolve job falls back to the currently pinned runtime from | |
| # public npm. Remove this trigger once validation is done (coupled with the | |
| # resolve `push)` case and the publish.if `event==push` clause). | |
| push: # TEMP: remove after on-branch validation | |
| branches: [mackinnonbuck-sdk-canary-compat-gate] | |
| 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. Adding a `repository_dispatch: types: [runtime-canary]` | |
| # trigger later is purely additive: add one more case that reads | |
| # client_payload, defaults source to 'internal', and mode to 'publish'. No | |
| # downstream rework required. | |
| - 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 }} | |
| run: | | |
| set -euo pipefail | |
| case "$EVENT_NAME" in | |
| workflow_dispatch) | |
| VERSION="$INPUT_VERSION" | |
| SOURCE="$INPUT_SOURCE" | |
| # Only a human dispatch may pick a non-default publish mode. | |
| MODE="$INPUT_MODE" | |
| ;; | |
| push) | |
| # TEMP (remove with the push trigger): no dispatch inputs on push, | |
| # so use the currently pinned public runtime. A green run proves the | |
| # pipeline mechanics end to end on-branch. Hardcoded (not read from | |
| # package.json) because the resolve job has no checkout. | |
| SOURCE="public" | |
| VERSION="1.0.69" | |
| 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-9]+\.[0-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 (human dispatch only) | |
| # governs behavior: | |
| # - tests-only: never publish (skips this job entirely). | |
| # - publish: publish only when the e2e gate is green (the default; the only | |
| # mode automated triggers ever get). | |
| # - 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. | |
| # TEMP: the `github.event_name == 'push'` clause below is a branch-validation | |
| # bypass coupled with the temporary push trigger; remove all three together. | |
| 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' || | |
| github.event_name == 'push') | |
| 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-9]+\.[0-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" |