Skip to content

Release Zeroshot

Release Zeroshot #315

Workflow file for this run

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