Files
rea/.github/workflows/release.yml
T
N0zoM1z0 60825fb447 fix(release): validate frozen checkpoint versions and breaking notes
Audit actual first-parent ancestry instead of trusting the release bot
chronological cutoff, and block mismatched versions before tag creation.
Sync the published 6.0.0 baseline without dropping newer main changes.

Fixes #1063

Assisted-by: Sakuya (gensokyo-skills:sakuya-checkpointed-execution)
2026-10-08 10:13:05 +00:00

319 lines
13 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Release
on:
workflow_dispatch:
inputs:
release_branch:
description: Frozen release branch (release/VERSION)
required: true
type: string
phase:
description: Prepare the release PR, or publish its reviewed merge
required: true
default: prepare
type: choice
options: [prepare, publish]
env:
HUSKY: "0"
concurrency:
group: release-${{ inputs.release_branch }}
cancel-in-progress: false
jobs:
release-please:
name: Release Please
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
outputs:
release_created: ${{ steps.release.outputs.release_created }}
sha: ${{ steps.release.outputs.sha }}
steps:
- name: Validate release selection
shell: bash
env:
RELEASE_BRANCH: ${{ inputs.release_branch }}
RELEASE_PHASE: ${{ inputs.phase }}
run: |
if [[ "${RELEASE_PHASE}" != prepare && "${RELEASE_PHASE}" != publish ]]; then
echo "phase must be prepare or publish." >&2
exit 1
fi
if [[ "${RELEASE_PHASE}" == prepare && "${GITHUB_REF}" != "refs/heads/main" ]]; then
echo "Run prepare from main; release_branch selects the frozen source." >&2
exit 1
fi
if [[ "${RELEASE_PHASE}" == publish && "${GITHUB_REF}" != "refs/heads/${RELEASE_BRANCH}" ]]; then
echo "Run publish from the selected frozen release branch so npm provenance records its source." >&2
exit 1
fi
if [[ ! "${RELEASE_BRANCH}" =~ ^release/[^/]+$ ]]; then
echo "release_branch must select a release/ branch; main is not a release checkpoint." >&2
exit 1
fi
if ! git check-ref-format "refs/heads/${RELEASE_BRANCH}"; then
echo "Invalid release_branch: ${RELEASE_BRANCH}" >&2
exit 1
fi
- name: Resolve release branch tip
id: branch
shell: bash
env:
GH_TOKEN: ${{ github.token }}
RELEASE_BRANCH: ${{ inputs.release_branch }}
run: |
sha="$(gh api "repos/${GITHUB_REPOSITORY}/git/ref/heads/${RELEASE_BRANCH}" --jq '.object | select(.type == "commit") | .sha')"
if [[ ! "${sha}" =~ ^[0-9a-f]{40}$ ]]; then
echo "The release branch tip is not a commit." >&2
exit 1
fi
echo "sha=${sha}" >> "${GITHUB_OUTPUT}"
- name: Require the publish dispatch to match the branch tip
if: inputs.phase == 'publish'
shell: bash
env:
BRANCH_SHA: ${{ steps.branch.outputs.sha }}
run: |
if [[ "${BRANCH_SHA}" != "${GITHUB_SHA}" ]]; then
echo "The release branch tip differs from the dispatched checkpoint; refusing to create a release for a moved source." >&2
exit 1
fi
- name: Check out release controller and Git history
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}
fetch-depth: 0
persist-credentials: false
- name: Set up Node.js for checkpoint validation
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
package-manager-cache: false
- name: Install checkpoint validation dependencies
run: npm ci
- name: Validate checkpoint version and ancestry before release creation
env:
RELEASE_PHASE: ${{ inputs.phase }}
RELEASE_BRANCH: ${{ inputs.release_branch }}
SOURCE_SHA: ${{ steps.branch.outputs.sha }}
run: >-
node scripts/verify-release-checkpoint.mjs
--phase "$RELEASE_PHASE" --release-branch "$RELEASE_BRANCH"
--source-sha "$SOURCE_SHA"
- uses: googleapis/release-please-action@v4
id: release
with:
# A dedicated credential starts PR CI automatically; the built-in
# token creates approval-required PR runs for maintainers to approve.
token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
target-branch: ${{ inputs.release_branch }}
skip-github-release: ${{ inputs.phase == 'prepare' }}
skip-github-pull-request: ${{ inputs.phase == 'publish' }}
- name: Require a merged release PR for publication
if: inputs.phase == 'publish' && steps.release.outputs.release_created != 'true'
run: |
echo "No new release was created. Merge the reviewed release PR into its frozen branch before publishing. For a partial publication, re-run failed jobs in the original run." >&2
exit 1
- name: Bind publication to the dispatched checkpoint
if: inputs.phase == 'publish' && steps.release.outputs.release_created == 'true'
shell: bash
env:
RELEASE_SHA: ${{ steps.release.outputs.sha }}
run: |
if [[ "${RELEASE_SHA}" != "${GITHUB_SHA}" ]]; then
echo "The tagged release SHA differs from the dispatched checkpoint; refusing to publish mismatched source provenance. The GitHub release already exists; do not dispatch publish again." >&2
exit 1
fi
- name: Check out release pull request
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
uses: actions/checkout@v6
with:
ref: ${{ fromJSON(steps.release.outputs.pr).headBranchName }}
fetch-depth: 0
persist-credentials: false
- name: Set up Node.js for generated documentation
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
package-manager-cache: false
- name: Install dependencies
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
run: npm ci
- name: Validate prepared release version and migration notes
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
env:
RELEASE_BRANCH: ${{ inputs.release_branch }}
SOURCE_SHA: ${{ steps.branch.outputs.sha }}
run: >-
node scripts/verify-release-checkpoint.mjs
--phase prepare --stage candidate --release-branch "$RELEASE_BRANCH"
--source-sha "$SOURCE_SHA"
- name: Regenerate release documentation
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
run: npm run docs:generate
- name: Commit canonical release catalog
if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true'
shell: bash
env:
# Use the same identity for the follow-up commit so it also triggers CI.
GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }}
RELEASE_PR_JSON: ${{ steps.release.outputs.pr }}
run: |
release_pr_branch="$(node --input-type=module -e '
const value = JSON.parse(process.env.RELEASE_PR_JSON ?? "");
if (
typeof value !== "object" ||
value === null ||
typeof value.headBranchName !== "string" ||
value.headBranchName.length === 0
) {
process.exit(1);
}
process.stdout.write(value.headBranchName);
')"
changed_paths="$(git diff --name-only)"
if [[ -z "${changed_paths}" ]]; then
echo "Release documentation is already canonical."
exit 0
fi
if [[ "${changed_paths}" != "docs/product-catalog.json" ]]; then
echo "docs:generate changed unexpected tracked paths:" >&2
printf '%s\n' "${changed_paths}" >&2
exit 1
fi
git diff --check
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -- docs/product-catalog.json
git commit -m "chore: normalize release product catalog"
gh auth setup-git
git push origin "HEAD:${release_pr_branch}"
publish:
name: Publish to npm
needs: release-please
if: inputs.phase == 'publish' && needs.release-please.outputs.release_created == 'true'
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.release-please.outputs.sha }}
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
package-manager-cache: false
registry-url: https://registry.npmjs.org
- run: npm ci
- name: Prepare Windows native build toolchain
run: sudo apt-get update && sudo apt-get install --no-install-recommends -y g++-mingw-w64-x86-64
- name: Build and verify bundled Windows native controls
run: npm run build:windows-native && npm run verify:windows-native:artifact
- name: Verify packaged release before publishing
run: npm run verify:package
- name: Publish
shell: bash
run: |
version="$(node --input-type=module -e 'import packageJson from "./package.json" with { type: "json" }; process.stdout.write(packageJson.version)')"
published_version="$(npm view "rea-agents@${version}" version --json 2>/dev/null || true)"
if [ "${published_version}" = "\"${version}\"" ]; then
echo "rea-agents@${version} is already published; verifying the existing package."
else
npm publish --access public
fi
- name: Verify published CLI from npm
shell: bash
run: |
version="$(node --input-type=module -e 'import packageJson from "./package.json" with { type: "json" }; process.stdout.write(packageJson.version)')"
verification_root="$(mktemp -d)"
trap 'rm -rf -- "${verification_root}"' EXIT
cd "${verification_root}"
max_attempts=120
sleep_seconds=15
total_window_minutes=$((max_attempts * sleep_seconds / 60))
for attempt in $(seq 1 "${max_attempts}"); do
published_version="$(npm view --prefer-online "rea-agents@${version}" version --json 2>/dev/null || true)"
if [ "${published_version}" = "\"${version}\"" ]; then
echo "rea-agents@${version} is visible on npm after attempt ${attempt}/${max_attempts}; verifying the published CLI."
npm exec --yes --package="rea-agents@${version}" -- rea --help
npm exec --yes --package="rea-agents@${version}" -- rea --llms
node "${GITHUB_WORKSPACE}/scripts/verify-published-package.mjs" "${version}"
exit 0
else
echo "rea-agents@${version} is not visible on npm yet (attempt ${attempt}/${max_attempts}; up to ${total_window_minutes} minutes)" >&2
fi
if [ "${attempt}" -lt "${max_attempts}" ]; then
sleep "${sleep_seconds}"
fi
done
echo "rea-agents@${version} did not become visible on npm within ${total_window_minutes} minutes (${max_attempts} attempts × ${sleep_seconds}s)" >&2
echo "This indicates npm registry propagation delay, not a CLI verification failure." >&2
exit 1
publish-mcp:
name: Publish to MCP Registry
needs: [release-please, publish]
if: inputs.phase == 'publish' && needs.release-please.outputs.release_created == 'true' && needs.publish.result == 'success'
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
with:
ref: ${{ needs.release-please.outputs.sha }}
persist-credentials: false
- name: Verify package and registry metadata versions
run: npm run mcp-registry:check
- name: Install MCP Registry publisher
env:
MCP_PUBLISHER_VERSION: v1.8.0
MCP_PUBLISHER_SHA256_LINUX_AMD64: 1370446bbe74d562608e8005a6ccce02d146a661fbd78674e11cc70b9618d6cf
run: |
archive="mcp-publisher_linux_amd64.tar.gz"
curl -fsSLo "${archive}" \
"https://github.com/modelcontextprotocol/registry/releases/download/${MCP_PUBLISHER_VERSION}/${archive}"
echo "${MCP_PUBLISHER_SHA256_LINUX_AMD64} ${archive}" | sha256sum --check --strict
tar xzf "${archive}" mcp-publisher
chmod +x mcp-publisher
- name: Validate MCP Registry metadata
run: ./mcp-publisher validate
- name: Authenticate to MCP Registry
run: ./mcp-publisher login github-oidc
- name: Publish MCP Registry metadata
run: ./mcp-publisher publish