Skip to content

Release

Release #191

Workflow file for this run

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
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: 20
- os: macos-latest
node: 20
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
run: node scripts/release-dry-run.js
release:
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
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: 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: Recheck main immediately before publication
if: steps.candidate.outputs.current == 'true'
shell: bash
run: |
set -euo pipefail
current_sha="$(git rev-parse HEAD)"
remote_sha="$(git ls-remote origin refs/heads/main | cut -f1)"
if [[ "$current_sha" != "$remote_sha" ]]; then
echo "::error::main moved from $current_sha to $remote_sha after validation"
exit 1
fi
- name: Run semantic-release
if: steps.candidate.outputs.current == 'true'
env:
GITHUB_TOKEN: ${{ github.token }}
run: npx semantic-release
- name: Assert release state
if: steps.candidate.outputs.current == 'true'
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