Repository navigation
fix(hopper): stop Linux demo children when the REA owner exits (#2499) #1953
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: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| branches: [main] | |
| types: [closed] | |
| 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 || github.event.pull_request.merge_commit_sha || github.ref_name }} | |
| cancel-in-progress: false | |
| jobs: | |
| release-proposal: | |
| name: Update release proposal | |
| if: github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Respect finalized release notes | |
| id: editorial | |
| env: | |
| GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| run: | | |
| finalized=$(gh api --paginate "repos/${GH_REPO}/pulls?state=open&base=main&per_page=100" \ | |
| --jq '.[] | select(.head.repo.full_name == .base.repo.full_name) | select(.head.ref | startswith("release-please--branches--main--")) | select(any(.labels[]; .name == "rea:release-notes-finalized")) | .number') | |
| if [[ -n "${finalized}" ]]; then | |
| echo "Finalized release notes on PR ${finalized}; automatic proposal refresh is paused." >> "$GITHUB_STEP_SUMMARY" | |
| echo "frozen=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| - uses: googleapis/release-please-action@v4 | |
| if: steps.editorial.outputs.frozen != 'true' | |
| with: | |
| token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }} | |
| target-branch: main | |
| skip-github-release: true | |
| merged-release: | |
| name: Release reviewed main merge | |
| if: >- | |
| github.event_name == 'pull_request' && github.event.pull_request.merged == true && | |
| github.event.pull_request.head.repo.full_name == github.repository && | |
| startsWith(github.event.pull_request.head.ref, 'release-please--branches--main--') | |
| 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: Check out reviewed release merge | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.pull_request.merge_commit_sha }} | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: .nvmrc | |
| package-manager-cache: false | |
| - run: npm ci | |
| - name: Validate reviewed merge before release creation | |
| env: | |
| SOURCE_SHA: ${{ github.event.pull_request.merge_commit_sha }} | |
| run: | | |
| if [[ "${SOURCE_SHA}" != "${GITHUB_SHA}" ]]; then | |
| echo "The reviewed merge differs from the workflow source; refusing mismatched publication provenance." >&2 | |
| exit 1 | |
| fi | |
| node scripts/verify-release-checkpoint.mjs \ | |
| --phase publish --release-branch main --source-sha "$SOURCE_SHA" | |
| - name: Validate finalized notes before release creation | |
| env: | |
| GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| RELEASE_PR: ${{ github.event.pull_request.number }} | |
| SOURCE_SHA: ${{ github.event.pull_request.merge_commit_sha }} | |
| run: | | |
| node scripts/release-notes.mjs publication --repository "$GH_REPO" \ | |
| --source "$SOURCE_SHA" --pr "$RELEASE_PR" | |
| - uses: googleapis/release-please-action@v4 | |
| id: release | |
| with: | |
| token: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }} | |
| target-branch: main | |
| skip-github-release: false | |
| skip-github-pull-request: true | |
| - name: Bind publication to the reviewed merge | |
| env: | |
| RELEASE_CREATED: ${{ steps.release.outputs.release_created }} | |
| RELEASE_SHA: ${{ steps.release.outputs.sha }} | |
| SOURCE_SHA: ${{ github.event.pull_request.merge_commit_sha }} | |
| run: | | |
| if [[ "${RELEASE_CREATED}" != true ]]; then | |
| echo "Release Please did not create a release for the reviewed merge. Check its pending label and release metadata; for a partial publication, re-run failed jobs in the original run." >&2 | |
| exit 1 | |
| fi | |
| if [[ "${RELEASE_SHA}" != "${SOURCE_SHA}" ]]; then | |
| echo "The tagged release differs from the reviewed merge; refusing mismatched publication provenance. The GitHub release already exists; do not create another release as a retry." >&2 | |
| exit 1 | |
| fi | |
| release-please: | |
| name: Release Please | |
| if: github.event_name == 'workflow_dispatch' | |
| 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" | |
| - name: Validate manual finalized notes before release creation | |
| if: inputs.phase == 'publish' | |
| env: | |
| GH_TOKEN: ${{ secrets.RELEASE_PLEASE_TOKEN || secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| SOURCE_SHA: ${{ steps.branch.outputs.sha }} | |
| run: | | |
| node scripts/release-notes.mjs publication --repository "$GH_REPO" \ | |
| --source "$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: Validate generated release documentation | |
| if: inputs.phase == 'prepare' && steps.release.outputs.prs_created == 'true' | |
| run: npm run docs:check && git diff --exit-code | |
| publish: | |
| name: Publish to npm | |
| needs: [merged-release, release-please] | |
| if: >- | |
| always() && !cancelled() && | |
| ((needs.merged-release.result == 'success' && needs.merged-release.outputs.release_created == 'true') || | |
| (needs.release-please.result == 'success' && 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.merged-release.outputs.sha || 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 | |
| tag="$(node scripts/release-npm-tag.mjs "${version}")" | |
| npm publish --access public --tag "${tag}" | |
| 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: [merged-release, release-please, publish] | |
| if: >- | |
| always() && !cancelled() && needs.publish.result == 'success' && | |
| ((needs.merged-release.result == 'success' && needs.merged-release.outputs.release_created == 'true') || | |
| (needs.release-please.result == 'success' && 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.merged-release.outputs.sha || 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 |