Release #287
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 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 | |
| - recover-rust-distribution | |
| 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: | |
| install-matrix: | |
| if: | | |
| (github.event_name == 'workflow_dispatch' && inputs.action == 'dry-run') || | |
| ( | |
| github.event_name == 'workflow_run' && | |
| github.event.workflow_run.event == 'push' && | |
| github.event.workflow_run.conclusion == 'success' && | |
| 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: | |
| outputs: | |
| tag: ${{ steps.rust-version.outputs.tag }} | |
| 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 | |
| - name: Resolve dry-run Rust version | |
| id: rust-version | |
| shell: bash | |
| run: | | |
| version="${{ steps.semantic-dry-run.outputs.version }}" | |
| if [[ -z "$version" ]]; then | |
| version="$(node scripts/rust-distribution.js print-version)" | |
| fi | |
| echo "tag=v$version" >> "$GITHUB_OUTPUT" | |
| 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 | |
| rust-recovery-plan: | |
| outputs: | |
| tag: ${{ steps.immutable.outputs.tag }} | |
| if: | | |
| github.event_name == 'workflow_dispatch' && | |
| inputs.action == 'recover-rust-distribution' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Validate Rust 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: Fetch protected release refs | |
| run: | | |
| git fetch origin main | |
| git fetch origin "refs/tags/${{ inputs.release_tag }}:refs/tags/${{ inputs.release_tag }}" | |
| - name: Verify immutable matching release tag | |
| id: immutable | |
| shell: bash | |
| env: | |
| RELEASE_COMMIT: ${{ inputs.release_commit }} | |
| RELEASE_TAG: ${{ inputs.release_tag }} | |
| run: | | |
| set -euo pipefail | |
| tag_commit="$(git rev-parse "${RELEASE_TAG}^{commit}")" | |
| [[ "$tag_commit" == "$RELEASE_COMMIT" ]] || { | |
| echo "::error::release tag $RELEASE_TAG resolves to $tag_commit, not $RELEASE_COMMIT" | |
| exit 1 | |
| } | |
| if ! git merge-base --is-ancestor "$RELEASE_COMMIT" origin/main; then | |
| echo "::error::release commit $RELEASE_COMMIT is not an ancestor of main" | |
| exit 1 | |
| fi | |
| echo "tag=$RELEASE_TAG" >> "$GITHUB_OUTPUT" | |
| rust-binaries: | |
| needs: [dry-run, release-plan, rust-recovery-plan] | |
| if: | | |
| always() && | |
| ( | |
| ( | |
| github.event_name == 'workflow_dispatch' && | |
| inputs.action == 'dry-run' && | |
| needs.dry-run.result == 'success' | |
| ) || | |
| ( | |
| github.event_name == 'workflow_dispatch' && | |
| inputs.action == 'recover-rust-distribution' && | |
| needs.rust-recovery-plan.result == 'success' | |
| ) || | |
| ( | |
| github.event_name == 'workflow_run' && | |
| needs.release-plan.result == 'success' && | |
| needs.release-plan.outputs.version != '' | |
| ) | |
| ) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: x86_64-unknown-linux-gnu | |
| runner: ubuntu-latest | |
| executable: zeroshot-rust | |
| c-compiler: cc | |
| - target: aarch64-unknown-linux-gnu | |
| runner: ubuntu-24.04-arm | |
| executable: zeroshot-rust | |
| c-compiler: cc | |
| - target: x86_64-apple-darwin | |
| runner: macos-15-intel | |
| executable: zeroshot-rust | |
| c-compiler: cc | |
| - target: aarch64-apple-darwin | |
| runner: macos-14 | |
| executable: zeroshot-rust | |
| c-compiler: cc | |
| - target: x86_64-pc-windows-msvc | |
| runner: windows-latest | |
| executable: zeroshot-rust.exe | |
| c-compiler: cl.exe | |
| runs-on: ${{ matrix.runner }} | |
| permissions: | |
| contents: read | |
| env: | |
| RELEASE_TAG: ${{ inputs.action == 'dry-run' && needs.dry-run.outputs.tag || inputs.action == 'recover-rust-distribution' && needs.rust-recovery-plan.outputs.tag || needs.release-plan.outputs.tag }} | |
| BINARY_PATH: target/${{ matrix.target }}/release/${{ matrix.executable }} | |
| steps: | |
| - name: Checkout immutable release source | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| ref: ${{ inputs.action == 'dry-run' && github.sha || inputs.action == 'recover-rust-distribution' && inputs.release_commit || github.event.workflow_run.head_sha }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| - name: Install pinned script dependencies | |
| run: npm ci --ignore-scripts | |
| - name: Setup Rust 1.97.0 target | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| toolchain: 1.97.0 | |
| targets: ${{ matrix.target }} | |
| - name: Verify bundled SQLite C toolchain | |
| if: runner.os != 'Windows' | |
| shell: bash | |
| env: | |
| C_COMPILER: ${{ matrix.c-compiler }} | |
| run: command -v "$C_COMPILER" | |
| - name: Verify bundled SQLite MSVC toolchain | |
| if: runner.os == 'Windows' | |
| shell: pwsh | |
| run: | | |
| $vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe' | |
| $installation = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath | |
| if (-not $installation) { | |
| throw 'MSVC C toolchain required by bundled SQLite is unavailable' | |
| } | |
| - name: Stage planned Rust package version | |
| run: node scripts/rust-distribution.js stage-version --tag "$RELEASE_TAG" | |
| shell: bash | |
| - name: Verify Rust and release tag versions are coupled | |
| run: node scripts/rust-distribution.js check-version --tag "$RELEASE_TAG" | |
| shell: bash | |
| - name: Build standalone Rust release binary | |
| run: cargo build --release --locked -p zeroshot-rust --bin zeroshot-rust --target ${{ matrix.target }} | |
| - name: Run standalone Rust release binary | |
| run: node scripts/rust-distribution.js smoke --binary "$BINARY_PATH" | |
| shell: bash | |
| - name: Package target archive | |
| run: | | |
| node scripts/rust-distribution.js package \ | |
| --target "${{ matrix.target }}" \ | |
| --version "$RELEASE_TAG" \ | |
| --binary "$BINARY_PATH" \ | |
| --out rust-release | |
| shell: bash | |
| - name: Run executable extracted from target archive | |
| run: | | |
| node scripts/rust-distribution.js smoke-archive \ | |
| --target "${{ matrix.target }}" \ | |
| --archive "rust-release/zeroshot-rust-${RELEASE_TAG}-${{ matrix.target }}.tar.gz" | |
| shell: bash | |
| - name: Upload target archive | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: zeroshot-rust-${{ matrix.target }} | |
| path: rust-release/*.tar.gz | |
| if-no-files-found: error | |
| rust-manifest: | |
| needs: [dry-run, release-plan, rust-recovery-plan, rust-binaries] | |
| if: always() && needs.rust-binaries.result == 'success' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| env: | |
| RELEASE_TAG: ${{ inputs.action == 'dry-run' && needs.dry-run.outputs.tag || inputs.action == 'recover-rust-distribution' && needs.rust-recovery-plan.outputs.tag || needs.release-plan.outputs.tag }} | |
| steps: | |
| - name: Checkout release tooling | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| ref: ${{ inputs.action == 'dry-run' && github.sha || inputs.action == 'recover-rust-distribution' && inputs.release_commit || github.event.workflow_run.head_sha }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 | |
| with: | |
| node-version: 24 | |
| cache: npm | |
| - name: Install pinned script dependencies | |
| run: npm ci --ignore-scripts | |
| - name: Download every target archive | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | |
| with: | |
| pattern: zeroshot-rust-* | |
| path: rust-release | |
| merge-multiple: true | |
| - name: Build and verify complete checksum manifest | |
| run: node scripts/rust-distribution.js manifest --version "$RELEASE_TAG" --dir rust-release | |
| shell: bash | |
| - name: Upload complete dry-run distribution | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | |
| with: | |
| name: zeroshot-rust-${{ env.RELEASE_TAG }} | |
| path: | | |
| rust-release/*.tar.gz | |
| rust-release/SHA256SUMS | |
| if-no-files-found: error | |
| release: | |
| outputs: | |
| created: ${{ steps.semantic-release.outputs.created }} | |
| tag: ${{ steps.semantic-release.outputs.tag }} | |
| needs: [install-matrix, release-plan, rust-manifest] | |
| if: | | |
| always() && | |
| needs.install-matrix.result == 'success' && | |
| needs.release-plan.result == 'success' && | |
| needs.release-plan.outputs.version != '' && | |
| needs.rust-manifest.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 | |
| environment: release | |
| permissions: | |
| contents: write | |
| issues: write | |
| pull-requests: write | |
| id-token: write | |
| steps: | |
| - name: Checkout exact artifact-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 native artifacts were validated: 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 native artifacts: expected=$EXPECTED_VERSION actual=$actual_version" | |
| exit 1 | |
| fi | |
| node scripts/rust-distribution.js stage-version --tag "v$EXPECTED_VERSION" | |
| node scripts/rust-distribution.js check-version --tag "v$EXPECTED_VERSION" | |
| - 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 | |
| rust-publish: | |
| needs: [release, rust-manifest, rust-recovery-plan] | |
| if: | | |
| always() && | |
| needs.rust-manifest.result == 'success' && | |
| ( | |
| ( | |
| needs.release.result == 'success' && | |
| needs.release.outputs.created == 'true' | |
| ) || | |
| ( | |
| github.event_name == 'workflow_dispatch' && | |
| inputs.action == 'recover-rust-distribution' && | |
| needs.rust-recovery-plan.result == 'success' | |
| ) | |
| ) | |
| runs-on: ubuntu-latest | |
| environment: release | |
| permissions: | |
| contents: write | |
| id-token: write | |
| env: | |
| RELEASE_TAG: ${{ inputs.action == 'recover-rust-distribution' && needs.rust-recovery-plan.outputs.tag || needs.release.outputs.tag }} | |
| steps: | |
| - name: Checkout immutable release tag | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| ref: ${{ env.RELEASE_TAG }} | |
| - 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 script dependencies | |
| run: npm ci --ignore-scripts | |
| - name: Download complete Rust distribution | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 | |
| with: | |
| name: zeroshot-rust-${{ env.RELEASE_TAG }} | |
| path: rust-release | |
| - name: Verify existing assets and upload only missing names | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: node scripts/rust-distribution.js publish-assets --tag "$RELEASE_TAG" --dir rust-release | |
| - name: Stage coupled shim package version | |
| working-directory: npm/zeroshot-rust | |
| run: npm version "${RELEASE_TAG#v}" --no-git-tag-version | |
| shell: bash | |
| - name: Idempotently publish standalone Rust shim package | |
| working-directory: npm/zeroshot-rust | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| version="${RELEASE_TAG#v}" | |
| package="@the-open-engine/zeroshot-rust@$version" | |
| if published="$(npm view "$package" version 2>/dev/null)"; then | |
| [[ "$published" == "$version" ]] || { | |
| echo "::error::$package resolved to unexpected version $published" | |
| exit 1 | |
| } | |
| echo "$package is already published; recovery is complete" | |
| else | |
| npm publish --provenance --access public | |
| fi | |
| 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 |