agent-orchestrator/.github/workflows/release.yml

153 lines
6.6 KiB
YAML

name: Release
# Two-stage release pipeline.
#
# This public repo is responsible for version bumps, tagging, and creating
# the GitHub release. npm publishing is handled by a private cron job (AO)
# that polls GitHub releases and publishes when a new tag is ahead of the
# current npm version.
#
# No NPM_TOKEN or publisher dispatch secrets are needed in this repo.
# The only secret used is GITHUB_TOKEN (automatic).
#
# See CONTRIBUTING.md → "Release architecture" for the full picture.
on:
workflow_run:
# Depends on the workflow named "CI" in .github/workflows/ci.yml — if
# you rename that file or change its `name:` field, update this string
# too. GitHub matches by name (not filename) and silently no-ops on
# mismatch — so a rename here will mean releases never trigger again.
workflows: [CI]
types: [completed]
branches: [main]
concurrency:
group: release
cancel-in-progress: false
permissions:
contents: write
pull-requests: write
jobs:
release:
name: Release
runs-on: ubuntu-latest
if: >-
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
ref: ${{ github.event.workflow_run.head_sha }}
fetch-depth: 0
- uses: pnpm/action-setup@7088e561eb65bb68695d245aa206f005ef30921d
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: 20
cache: pnpm
# No `registry-url`: this workflow does not publish to npm.
- run: echo "HUSKY=0" >> $GITHUB_ENV
- run: pnpm install --frozen-lockfile
- run: pnpm -r build
# Pre-publish guard: catches the case where a publishable package has a
# workspace:* runtime dep on a `private: true` package — pnpm would
# rewrite the dep on publish to a version that doesn't exist on npm,
# breaking `npm install -g @aoagents/ao`. Still runs here so the
# public repo blocks a malformed version bump before it reaches npm.
- run: node scripts/check-publishable-deps.mjs
# changesets/action manages the "Version Packages" PR — when there
# are pending changesets it opens/updates the PR; when there are
# none (i.e. that PR was just merged) it would normally invoke the
# `publish:` command. We deliberately omit `publish:` so the action
# never runs `changeset publish`. npm publishing is handled by a
# private cron that detects the GitHub release.
- uses: changesets/action@63a615b9cd06ba9a3e6d13796c7fbcb080a60a0b
id: changesets
with:
version: pnpm changeset version
title: "chore: version packages"
commit: "chore: version packages"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Determine release state. Each downstream step (tag push, GH
# release creation) is gated on its own piece of state so the
# workflow is idempotent and recovers cleanly on re-run after a
# partial failure.
#
# `is_release_commit` is the crucial signal: it filters out
# regular commits to main (which also have `hasChangesets ==
# 'false'` but should NOT trigger a publish). We detect a
# version-bump commit by comparing the umbrella package's
# version against its value in the parent commit — a Version
# Packages merge changes that version, a regular commit does not.
# The umbrella `@aoagents/ao` is the canonical version source for
# the `vX.Y.Z` tag scheme and is part of the linked group, so
# any release that bumps the cohort will bump this file.
- name: Determine release state
id: state
if: steps.changesets.outputs.hasChangesets == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
version=$(node -p "require('./packages/ao/package.json').version")
echo "version=$version" >> "$GITHUB_OUTPUT"
prev_version=""
if git rev-parse HEAD^ >/dev/null 2>&1; then
prev_version=$(git show HEAD^:packages/ao/package.json 2>/dev/null | jq -r .version 2>/dev/null || echo "")
fi
if [ -n "$prev_version" ] && [ "$prev_version" != "$version" ]; then
echo "is_release_commit=true" >> "$GITHUB_OUTPUT"
else
echo "is_release_commit=false" >> "$GITHUB_OUTPUT"
fi
if git ls-remote --tags --exit-code origin "refs/tags/v$version" >/dev/null 2>&1; then
echo "tag_on_remote=true" >> "$GITHUB_OUTPUT"
else
echo "tag_on_remote=false" >> "$GITHUB_OUTPUT"
fi
if gh release view "v$version" --json tagName >/dev/null 2>&1; then
echo "release_exists=true" >> "$GITHUB_OUTPUT"
else
echo "release_exists=false" >> "$GITHUB_OUTPUT"
fi
# Push the umbrella `vX.Y.Z` tag only if this is a fresh
# version-bump commit AND the tag isn't already on the remote.
# Skipped on re-runs where the tag was pushed on a prior run
# (idempotent recovery).
#
# We deliberately skip `pnpm changeset tag`: it would create one
# tag per publishable package (~27 here) on every release, which
# creates partial-recovery conflicts on re-run when `git push --tags`
# tries to re-push existing per-package tags. The npm publisher
# only consumes the umbrella tag, so the per-package tags add no
# value.
- name: Tag versioned packages
if: steps.state.outputs.is_release_commit == 'true' && steps.state.outputs.tag_on_remote == 'false'
run: |
git tag "v${{ steps.state.outputs.version }}"
git push origin "v${{ steps.state.outputs.version }}"
# Public-facing GitHub release. Stable channel — not a prerelease.
# No `--target`: the `vX.Y.Z` tag is on the remote at the
# version-bump commit, and `gh release create` resolves the
# commitish from the existing tag. Avoids the race where another
# commit lands on main between the tag push and this step,
# pulling unrelated commits into the auto-generated release notes.
# Skipped if a release for this version already exists.
- name: Create GitHub release
if: steps.state.outputs.is_release_commit == 'true' && steps.state.outputs.release_exists == 'false'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "v${{ steps.state.outputs.version }}" \
--generate-notes