Release Zeroshot #315
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: Release | |
| on: | |
| workflow_run: | |
| workflows: [CI] | |
| types: [completed] | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| action: | |
| description: Validate the Node release or recover one missing artifact from an immutable tag. | |
| required: true | |
| default: dry-run | |
| type: choice | |
| options: | |
| - dry-run | |
| - recover-npm | |
| - recover-github-release | |
| release_tag: | |
| description: Recovery tag in vX.Y.Z form. | |
| required: false | |
| type: string | |
| release_commit: | |
| description: Full immutable commit SHA referenced by release_tag. | |
| required: false | |
| type: string | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: release-main | |
| cancel-in-progress: false | |
| jobs: | |
| node-relevance: | |
| if: | | |
| github.event_name == 'workflow_run' && | |
| github.event.workflow_run.event == 'push' && | |
| github.event.workflow_run.conclusion == 'success' && | |
| vars.RELEASE_AUTOMATION_ENABLED == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| outputs: | |
| relevant: ${{ steps.classify.outputs.node }} | |
| steps: | |
| - name: Checkout exact CI-tested main commit | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| ref: ${{ github.event.workflow_run.head_sha }} | |
| - name: Classify unreleased Node history | |
| id: classify | |
| shell: bash | |
| env: | |
| TESTED_SHA: ${{ github.event.workflow_run.head_sha }} | |
| run: | | |
| set -euo pipefail | |
| git fetch --tags --force | |
| latest_node_tag="$( | |
| git describe --tags --match 'v[0-9]*' --abbrev=0 "$TESTED_SHA" 2>/dev/null || true | |
| )" | |
| { | |
| if [[ -n "$latest_node_tag" ]]; then | |
| git rev-list --reverse "$latest_node_tag..$TESTED_SHA" | |
| else | |
| git rev-list --reverse "$TESTED_SHA" | |
| fi | |
| } | node scripts/node-release-commits.js >> "$GITHUB_OUTPUT" | |
| install-matrix: | |
| needs: [node-relevance] | |
| if: | | |
| always() && | |
| ( | |
| (github.event_name == 'workflow_dispatch' && inputs.action == 'dry-run') || | |
| ( | |
| github.event_name == 'workflow_run' && | |
| needs.node-relevance.result == 'success' && | |
| needs.node-relevance.outputs.relevant == 'true' | |
| ) | |
| ) && | |
| ( | |
| github.event_name != 'workflow_run' || | |
| vars.RELEASE_AUTOMATION_ENABLED == 'true' | |
| ) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: ubuntu-latest | |
| node: 22 | |
| - os: macos-latest | |
| node: 22 | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - name: Checkout tested candidate | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.sha }} | |
| - name: Setup Node.js ${{ matrix.node }} | |
| uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | |
| with: | |
| node-version: ${{ matrix.node }} | |
| cache: npm | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Link and smoke CLI | |
| run: | | |
| npm link | |
| zeroshot --help | |
| zeroshot --version | |
| zeroshot list | |
| - name: Test platform process metrics | |
| run: | | |
| node - <<'NODE' | |
| const { getProcessMetrics, isPlatformSupported } = require('./src/process-metrics'); | |
| (async () => { | |
| const metrics = await getProcessMetrics(process.pid, { samplePeriodMs: 100 }); | |
| if (!metrics.exists) throw new Error('Process should exist'); | |
| if (typeof metrics.cpuPercent !== 'number') throw new Error('cpuPercent must be numeric'); | |
| if (typeof metrics.memoryMB !== 'number') throw new Error('memoryMB must be numeric'); | |
| console.log({ platform: process.platform, supported: isPlatformSupported(), metrics }); | |
| })().catch((error) => { | |
| console.error(error); | |
| process.exit(1); | |
| }); | |
| NODE | |
| dry-run: | |
| needs: [install-matrix] | |
| if: | | |
| github.event_name == 'workflow_dispatch' && | |
| inputs.action == 'dry-run' && | |
| needs.install-matrix.result == 'success' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout exact dispatched candidate | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| ref: ${{ github.sha }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| registry-url: https://registry.npmjs.org | |
| - name: Install pinned dependencies | |
| run: npm ci | |
| - name: Fetch immutable release history | |
| run: git fetch --tags --force | |
| - name: Release preflight | |
| run: npm run release:preflight | |
| - name: Verify package tarball | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| pack_dir="$(mktemp -d)" | |
| prefix_dir="$(mktemp -d)" | |
| trap 'rm -rf "$pack_dir" "$prefix_dir"' EXIT | |
| npm pack --pack-destination "$pack_dir" | |
| tarball="$(find "$pack_dir" -maxdepth 1 -name '*.tgz' -print -quit)" | |
| npm install --global --prefix "$prefix_dir" "$tarball" | |
| "$prefix_dir/bin/zeroshot" --version | |
| "$prefix_dir/bin/zeroshot" --help | |
| "$prefix_dir/bin/zeroshot" list | |
| - name: Run semantic-release dry run | |
| id: semantic-dry-run | |
| run: node scripts/release-dry-run.js | |
| release-plan: | |
| outputs: | |
| version: ${{ steps.semantic-plan.outputs.version }} | |
| tag: ${{ steps.planned-tag.outputs.tag }} | |
| needs: [install-matrix] | |
| if: | | |
| always() && | |
| needs.install-matrix.result == 'success' && | |
| github.event_name == 'workflow_run' && | |
| github.event.workflow_run.event == 'push' && | |
| github.event.workflow_run.conclusion == 'success' && | |
| vars.RELEASE_AUTOMATION_ENABLED == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Checkout exact CI-tested main commit | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| ref: ${{ github.event.workflow_run.head_sha }} | |
| - name: Verify this is the CI-tested main commit | |
| id: candidate | |
| shell: bash | |
| env: | |
| TESTED_SHA: ${{ github.event.workflow_run.head_sha }} | |
| run: | | |
| set -euo pipefail | |
| current_sha="$(git rev-parse HEAD)" | |
| remote_sha="$(git ls-remote origin refs/heads/main | cut -f1)" | |
| if [[ "$current_sha" != "$TESTED_SHA" || "$remote_sha" != "$TESTED_SHA" ]]; then | |
| echo "Skipping obsolete release: tested=$TESTED_SHA checkout=$current_sha main=$remote_sha" | |
| echo "current=false" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| echo "current=true" >> "$GITHUB_OUTPUT" | |
| - name: Setup Node.js | |
| if: steps.candidate.outputs.current == 'true' | |
| uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| registry-url: https://registry.npmjs.org | |
| - name: Install pinned dependencies | |
| if: steps.candidate.outputs.current == 'true' | |
| run: npm ci | |
| - name: Fetch immutable release history | |
| if: steps.candidate.outputs.current == 'true' | |
| run: git fetch --tags --force | |
| - name: Release preflight | |
| if: steps.candidate.outputs.current == 'true' | |
| run: npm run release:preflight | |
| - name: Verify package tarball | |
| if: steps.candidate.outputs.current == 'true' | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| pack_dir="$(mktemp -d)" | |
| prefix_dir="$(mktemp -d)" | |
| trap 'rm -rf "$pack_dir" "$prefix_dir"' EXIT | |
| npm pack --pack-destination "$pack_dir" | |
| tarball="$(find "$pack_dir" -maxdepth 1 -name '*.tgz' -print -quit)" | |
| npm install --global --prefix "$prefix_dir" "$tarball" | |
| "$prefix_dir/bin/zeroshot" --version | |
| "$prefix_dir/bin/zeroshot" --help | |
| "$prefix_dir/bin/zeroshot" list | |
| - name: Resolve semantic-release version before native builds | |
| id: semantic-plan | |
| if: steps.candidate.outputs.current == 'true' | |
| env: | |
| GITHUB_REF_NAME: main | |
| run: node scripts/release-dry-run.js | |
| - name: Resolve planned immutable release tag | |
| id: planned-tag | |
| if: steps.semantic-plan.outputs.version != '' | |
| env: | |
| RELEASE_VERSION: ${{ steps.semantic-plan.outputs.version }} | |
| run: echo "tag=v$RELEASE_VERSION" >> "$GITHUB_OUTPUT" | |
| shell: bash | |
| release: | |
| outputs: | |
| created: ${{ steps.semantic-release.outputs.created }} | |
| tag: ${{ steps.semantic-release.outputs.tag }} | |
| needs: [install-matrix, release-plan] | |
| if: | | |
| always() && | |
| needs.install-matrix.result == 'success' && | |
| needs.release-plan.result == 'success' && | |
| needs.release-plan.outputs.version != '' && | |
| github.event_name == 'workflow_run' && | |
| github.event.workflow_run.event == 'push' && | |
| github.event.workflow_run.conclusion == 'success' && | |
| vars.RELEASE_AUTOMATION_ENABLED == 'true' | |
| runs-on: ubuntu-latest | |
| environment: release | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| id-token: write | |
| steps: | |
| - name: Checkout exact CI-tested main commit | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| ref: ${{ github.event.workflow_run.head_sha }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| registry-url: https://registry.npmjs.org | |
| - name: Install pinned dependencies | |
| run: npm ci | |
| - name: Fetch immutable release history | |
| run: git fetch --tags --force | |
| - name: Recheck main immediately before publication and confirm planned version | |
| id: final-plan | |
| shell: bash | |
| env: | |
| EXPECTED_VERSION: ${{ needs.release-plan.outputs.version }} | |
| GITHUB_REF_NAME: main | |
| TESTED_SHA: ${{ github.event.workflow_run.head_sha }} | |
| run: | | |
| set -euo pipefail | |
| current_sha="$(git rev-parse HEAD)" | |
| remote_sha="$(git ls-remote origin refs/heads/main | cut -f1)" | |
| if [[ "$current_sha" != "$TESTED_SHA" || "$remote_sha" != "$TESTED_SHA" ]]; then | |
| echo "::error::main moved after release validation: tested=$TESTED_SHA checkout=$current_sha main=$remote_sha" | |
| exit 1 | |
| fi | |
| actual_version="$(node scripts/release-dry-run.js | sed -n 's/^RELEASE_DRY_RUN_RESULT=//p' | tail -1)" | |
| if [[ "$actual_version" != "$EXPECTED_VERSION" ]]; then | |
| echo "::error::semantic-release version changed after planning: expected=$EXPECTED_VERSION actual=$actual_version" | |
| exit 1 | |
| fi | |
| - name: Run semantic-release | |
| id: semantic-release | |
| shell: bash | |
| env: | |
| EXPECTED_TAG: ${{ needs.release-plan.outputs.tag }} | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: | | |
| set -euo pipefail | |
| tags_before="$(git tag --points-at HEAD --list 'v*')" | |
| npx semantic-release | |
| tags_after="$(git tag --points-at HEAD --list 'v*')" | |
| if [[ "$tags_after" != "$EXPECTED_TAG" || "$tags_after" == "$tags_before" ]]; then | |
| echo "::error::semantic-release did not create expected tag $EXPECTED_TAG (before=$tags_before after=$tags_after)" | |
| exit 1 | |
| fi | |
| echo "created=true" >> "$GITHUB_OUTPUT" | |
| echo "tag=$tags_after" >> "$GITHUB_OUTPUT" | |
| - name: Assert release state | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| run: npm run release:assert-published | |
| recover: | |
| if: | | |
| github.event_name == 'workflow_dispatch' && | |
| (inputs.action == 'recover-npm' || inputs.action == 'recover-github-release') | |
| runs-on: ubuntu-latest | |
| environment: release | |
| permissions: | |
| contents: write | |
| id-token: write | |
| steps: | |
| - name: Validate recovery inputs | |
| shell: bash | |
| env: | |
| RELEASE_COMMIT: ${{ inputs.release_commit }} | |
| RELEASE_TAG: ${{ inputs.release_tag }} | |
| run: | | |
| set -euo pipefail | |
| [[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || { | |
| echo "::error::release_tag must match vX.Y.Z" | |
| exit 1 | |
| } | |
| [[ "$RELEASE_COMMIT" =~ ^[0-9a-f]{40}$ ]] || { | |
| echo "::error::release_commit must be a full lowercase SHA" | |
| exit 1 | |
| } | |
| - name: Checkout immutable release commit | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| ref: ${{ inputs.release_commit }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| registry-url: https://registry.npmjs.org | |
| - name: Install pinned dependencies | |
| run: npm ci | |
| - name: Fetch protected refs | |
| run: | | |
| git fetch origin main | |
| git fetch origin "refs/tags/${{ inputs.release_tag }}:refs/tags/${{ inputs.release_tag }}" | |
| - name: Verify recovery package | |
| run: | | |
| npm run release:preflight | |
| npm run lint | |
| npm run typecheck | |
| npm run check:agent-cli-provider:ci | |
| npm pack --dry-run --json | |
| - name: Recover missing artifact | |
| env: | |
| GITHUB_TOKEN: ${{ github.token }} | |
| RECOVERY_ACTION: ${{ inputs.action }} | |
| RELEASE_COMMIT: ${{ inputs.release_commit }} | |
| RELEASE_TAG: ${{ inputs.release_tag }} | |
| run: npm run release:recover |