orb-beta-release #42
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
| # Automated ORB (self-host container image, ghcr.io/jsonbored/loopover-selfhost) beta channel. | |
| # Daily (or on demand via workflow_dispatch), checks whether any image-relevant commit has landed | |
| # since the last orb-v tag (scripts/check-orb-release-due.mjs / scripts/orb-release-core.mjs) and, | |
| # if so, cuts the next `orb-vX.Y.Z-beta.N` tag and dispatches release-selfhost.yml to build + publish | |
| # it -- fully unattended: that workflow's `environment:` routes an actual beta version to | |
| # `release-beta` (no required reviewers), while a stable/rc version still requires the human-gated | |
| # `release` environment. Promoting a beta to a stable release stays a manual `git tag orb-vX.Y.Z` by | |
| # a maintainer -- this workflow never bumps orb-manifest.json's version or cuts a non-beta tag. | |
| # | |
| # Deliberately independent of the MCP package's release automation (mcp-release-watch.yml / | |
| # mcp-release-core.mjs) -- see scripts/orb-release-core.mjs's own header for why. | |
| name: orb-beta-release | |
| on: | |
| workflow_dispatch: | |
| schedule: | |
| - cron: "10 6 * * *" | |
| permissions: | |
| contents: write # create + push the beta tag | |
| actions: write # dispatch release-selfhost.yml for the new tag | |
| concurrency: | |
| group: orb-beta-release | |
| cancel-in-progress: false | |
| jobs: | |
| cut-beta: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7 | |
| with: | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Setup Node | |
| uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 | |
| with: | |
| node-version: 24.18.0 | |
| - name: Check whether an ORB beta is due | |
| id: report | |
| run: | | |
| set -euo pipefail | |
| node scripts/check-orb-release-due.mjs --json --output orb-release-due.json | |
| node <<'NODE' | |
| const fs = require("node:fs"); | |
| const report = JSON.parse(fs.readFileSync("orb-release-due.json", "utf8")); | |
| const version = report.nextTag.replace(/^orb-v/, ""); | |
| fs.appendFileSync(process.env.GITHUB_OUTPUT, `due=${report.due}\n`); | |
| fs.appendFileSync(process.env.GITHUB_OUTPUT, `tag=${report.nextTag}\n`); | |
| fs.appendFileSync(process.env.GITHUB_OUTPUT, `version=${version}\n`); | |
| NODE | |
| # Pushed with the default GITHUB_TOKEN, which does NOT fire release-selfhost.yml's own | |
| # `push: tags:` trigger (GitHub suppresses workflow-triggered-workflow pushes to prevent | |
| # recursion) -- that's why the next step dispatches it explicitly instead of relying on this | |
| # push alone. Mirrors publish-engine.yml / publish-mcp.yml's identical reasoning and tagging | |
| # idiom. | |
| # Exposes created=true/false so the dispatch step below never fires against a tag this run didn't | |
| # actually just create -- a defense-in-depth backstop (independent of orb-release-core.mjs's own | |
| # correctness) against ever re-triggering a build for an already-published version/tag. | |
| - name: Tag the new beta | |
| id: tag | |
| if: steps.report.outputs.due == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| TAG: ${{ steps.report.outputs.tag }} | |
| VERSION: ${{ steps.report.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| if git ls-remote --exit-code --heads origin "$TAG" >/dev/null 2>&1; then | |
| echo "::error::Branch $TAG already exists; refusing to create or dispatch an ambiguous release ref." | |
| exit 1 | |
| fi | |
| if git rev-parse -q --verify "refs/tags/$TAG" >/dev/null; then | |
| echo "Tag $TAG already exists; skipping (a previous run likely already tagged it, or it collides with an already-published version)." | |
| echo "created=false" >> "$GITHUB_OUTPUT" | |
| else | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| git tag -a "$TAG" -m "loopover-orb ${VERSION}" | |
| git remote set-url origin "https://github.com/${GITHUB_REPOSITORY}.git" | |
| gh auth setup-git | |
| git push origin "$TAG" | |
| echo "created=true" >> "$GITHUB_OUTPUT" | |
| fi | |
| # create_github_release=true: the tag above was just created and pushed, so release-selfhost.yml's | |
| # `--verify-tag` GitHub Release step can run safely (see that workflow's own comments). Gated on | |
| # steps.tag.outputs.created (not just due) so a no-op tag step -- for any reason -- never triggers a | |
| # rebuild/republish of an existing GHCR image tag with different content. | |
| # | |
| # Dispatch against the fully qualified TAG ref, not `main`: `main` is a floating ref, and this repo | |
| # merges fast enough that a commit can land in the gap between the tag push above and this dispatch. | |
| # `--ref main` would then resolve `github.sha` inside release-selfhost.yml to that NEWER commit, while | |
| # $TAG (pushed moments ago, immutable) still points at the older one it was actually cut for -- tripping | |
| # that workflow's own TAG_SHA-must-equal-RELEASE_SHA fail-safe and aborting the release. `refs/tags/$TAG` | |
| # preserves that race fix without letting a same-named branch shadow the tag namespace. | |
| - name: Dispatch the ORB release build | |
| if: steps.report.outputs.due == 'true' && steps.tag.outputs.created == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| TAG: ${{ steps.report.outputs.tag }} | |
| VERSION: ${{ steps.report.outputs.version }} | |
| run: gh workflow run release-selfhost.yml --ref "refs/tags/$TAG" -f "version=${VERSION}" -f create_github_release=true | |
| - name: Summarize | |
| if: always() | |
| run: | | |
| node <<'NODE' | |
| const fs = require("node:fs"); | |
| const report = JSON.parse(fs.readFileSync("orb-release-due.json", "utf8")); | |
| const lines = [ | |
| "## ORB Beta Release", | |
| "", | |
| `- Due: \`${report.due}\``, | |
| `- Next tag: \`${report.nextTag}\``, | |
| `- Target version: \`${report.targetVersion}\``, | |
| `- Manifest version: \`${report.manifestVersion ?? "none"}\``, | |
| `- Manifest stale (commits imply a bigger bump than the manifest declares): \`${report.manifestStale}\``, | |
| `- Latest stable tag: \`${report.latestStableTag ?? "none"}\``, | |
| `- Latest tag: \`${report.latestTag ?? "none"}\``, | |
| `- Image-relevant commits since last tag: \`${report.commits.length}\``, | |
| ]; | |
| fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, `${lines.join("\n")}\n`); | |
| NODE |