ci(release): Share staging and production workflows#35
Conversation
Move release orchestration out of the wrapper workflows and into a reusable shared pipeline so staging and production exercise the same build, packaging, and Homebrew logic. Add explicit staging publish and staging validation modes so PRs can run the release packaging path without publishing a GitHub release or updating the staging tap. Require an explicit ref for manual staging runs to remove the old silent fallback to main.
Remove the investigation notes file from the branch and finish consolidating shared release helpers so the review fixes stay scoped to the release pipeline work. Also normalize the remaining staging tap references to homebrew-axe-staging so the dry-run fixture and staging notes match the canonical tap repo used by the updater.
WalkthroughThe pull request refactors the release pipeline architecture by introducing reusable GitHub Actions workflows and modularised release scripts. A new shared workflow orchestrates build, signing, notarisation, and release publication steps. The release and staging workflows now delegate to this shared workflow with mode-based parameters. New Bash scripts handle release artefact packaging, GitHub release publishing, and Homebrew tap updates. Existing release scripts are enhanced with additional validation, staging support, and refactored payload handling. The release dry-run workflow is expanded to validate additional release scripts and configuration files. 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (1)
.github/workflows/release-shared.yml (1)
70-72: Consider defensive handling for IDB commit extraction.The sed regex to extract
IDB_GIT_REFis tightly coupled to the exact formatting inbuild.sh. If no match is found,PINNED_IDB_COMMITwill be empty, which could cause cache misses or unexpected behaviour. Consider adding validation:💡 Suggested improvement
set -euo pipefail PINNED_IDB_COMMIT=$(grep '^IDB_GIT_REF=' scripts/build.sh | sed -E 's/^IDB_GIT_REF="\$\{IDB_GIT_REF:-([^}]*)\}"$/\1/') + if [[ -z "$PINNED_IDB_COMMIT" ]]; then + echo "::error::Could not extract IDB_GIT_REF from scripts/build.sh" + exit 1 + fi echo "idb_commit=$PINNED_IDB_COMMIT" >> "$GITHUB_OUTPUT"🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/release-shared.yml around lines 70 - 72, The current extraction of PINNED_IDB_COMMIT via the grep/sed pipeline for IDB_GIT_REF in scripts/build.sh can yield an empty value if the pattern doesn't match; update the workflow to defensively validate the result of the extraction (PINNED_IDB_COMMIT) after running the grep/sed: if it's empty or unset, either fail the job with a clear error message referencing IDB_GIT_REF and scripts/build.sh or provide a documented fallback value, and ensure the error path exits non-zero so downstream steps (caching/outputs) don't proceed with an invalid commit; make this validation adjacent to the existing assignment and echo to GITHUB_OUTPUT so behavior is obvious and traceable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/release-dry-run.yml:
- Around line 5-16: The dry-run workflow currently omits
scripts/release-payload.sh from its guarded paths and the lightweight parsing in
scripts/release-dry-run.sh doesn't detect payload-only changes; add
'scripts/release-payload.sh' to the paths list in
.github/workflows/release-dry-run.yml and update the parsing/guard logic inside
scripts/release-dry-run.sh (the section that inspects changed files and the
payload-helper detection logic) so that payload-helper-only changes are treated
as packaging-related and trigger the dry-run; keep the fixture lightweight by
only adding payload script detection, not the full packaging flow.
In @.github/workflows/release-shared.yml:
- Line 158: The command substitution used in the line starting with "security
list-keychains -d user -s" is unquoted and can cause word splitting for keychain
paths containing spaces; update that invocation to quote the command
substitution output and use a properly quoted sed expression to strip
double-quotes so the entire list is treated as a single argument (i.e., wrap the
$(security list-keychains -d user | sed ...) expansion in double quotes and use
sed with a single-quoted s/"//g pattern).
In @.github/workflows/release-staging.yml:
- Around line 3-8: The PR validation runs on the pull_request trigger but still
has write access and secrets; to make the PR path read-only, add an explicit
workflow-level permissions block (e.g., permissions: contents: read) and ensure
any jobs that require write/secrets are gated off for pull_request (use
job-level if: github.event_name != 'pull_request' or move write steps behind the
existing mode gate), and apply the same change for both pull_request trigger
blocks; reference the pull_request trigger symbol and the publish/write steps
gated by mode so you scope write/secrets only to push/workflow_dispatch runs.
- Around line 32-33: The requested_ref value currently passes
github.event.pull_request.head.sha which will be looked up in the default repo
by the reusable workflow and fails for forked PRs; update the requested_ref
logic used by the workflow dispatch to supply the fork repository reference for
PR events (use github.event.pull_request.head.repo.full_name combined with the
head.sha) or simply fall back to github.sha for PRs so the checkout step in
release-shared.yml can find the commit; adjust the conditional that builds
requested_ref to use github.event.pull_request.head.repo.full_name when
github.event_name == 'pull_request' (or switch that branch to github.sha) so the
checkout in release-shared.yml succeeds for forked PRs.
In `@scripts/generate-github-release-notes.mjs`:
- Around line 241-246: In the "### Homebrew staging tap" release notes block
replace the ambiguous install command 'brew install axe' with the
fully-qualified tap package 'brew install cameroncooke/homebrew-axe-staging/axe'
so Homebrew disambiguates staging vs production; update the text that currently
contains the 'brew install axe' string to use the fully-qualified reference
instead.
In `@scripts/release-dry-run.sh`:
- Around line 105-106: The silent use of `cmp -s` between
"$tmp_dir/generated.rb" and "$tmp_dir/production.rb" / "$tmp_dir/staging.rb"
causes an immediate exit under set -euo pipefail with no diagnostic; change the
comparisons to explicitly test cmp's exit and emit a clear error identifying
which comparison failed (referencing the operands "$tmp_dir/generated.rb",
"$tmp_dir/production.rb", "$tmp_dir/staging.rb" and the TMP_DIR variable), e.g.
run cmp and on non-zero return print a descriptive message and exit with a
non-zero status so the pipeline log shows which file comparison failed.
In `@scripts/update-homebrew-tap.sh`:
- Around line 69-72: The git remote URL is being set with an incomplete auth
string ("https://${TOKEN}@github.com/...") which lacks a username and will store
the token in plain text; update the logic around the git remote set-url origin
step so it uses a valid HTTP basic-auth format (e.g., include a username like
"x-access-token:${TOKEN}@github.com/${TAP_REPO}.git") or, better, avoid
embedding the token entirely by configuring Git credentials (use a credential
helper or `gh auth login`/`gh repo clone` with GH_TOKEN env) before setting the
remote; refer to the variables and command involved: TOKEN, TAP_REPO, WORK_DIR,
TAP_BRANCH and the git remote set-url origin invocation and ensure any change
does not leave the token in .git/config or logs.
---
Nitpick comments:
In @.github/workflows/release-shared.yml:
- Around line 70-72: The current extraction of PINNED_IDB_COMMIT via the
grep/sed pipeline for IDB_GIT_REF in scripts/build.sh can yield an empty value
if the pattern doesn't match; update the workflow to defensively validate the
result of the extraction (PINNED_IDB_COMMIT) after running the grep/sed: if it's
empty or unset, either fail the job with a clear error message referencing
IDB_GIT_REF and scripts/build.sh or provide a documented fallback value, and
ensure the error path exits non-zero so downstream steps (caching/outputs) don't
proceed with an invalid commit; make this validation adjacent to the existing
assignment and echo to GITHUB_OUTPUT so behavior is obvious and traceable.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 743a5737-d2b5-4b02-8b08-b3911dc3658d
📒 Files selected for processing (13)
.github/workflows/release-dry-run.yml.github/workflows/release-shared.yml.github/workflows/release-staging.yml.github/workflows/release.ymlscripts/build.shscripts/generate-github-release-notes.mjsscripts/publish-github-release.shscripts/release-artifacts.shscripts/release-context.mjsscripts/release-dry-run.shscripts/release-payload.shscripts/release.shscripts/update-homebrew-tap.sh
Make the release dry-run validate the shared payload helper and compare generated Homebrew output against a local canonical fixture instead of the live staging tap. This keeps PR CI deterministic and removes the stale external tap dependency while also trimming the redundant stage payload wrapper in release-artifacts.sh.
Split staging validation from publish so pull request runs stay read-only and do not inherit publish secrets. This keeps the shared release pipeline safer while preserving the privileged path for push and manual publish runs. Also fix the staging Homebrew install instructions, stop embedding the GitHub token in the tap remote URL, and update the dry-run contract checks to match the split staging workflow.
Strip wrapped newlines from the base64-encoded Git auth header before pushing Homebrew tap updates. This keeps the authenticated push path robust on macOS where may wrap long output.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Extract the release pipeline into a shared reusable workflow and shared release scripts so production and staging stop drifting.
The main goal here is to make release packaging and Homebrew distribution follow one canonical path.
release.ymlandrelease-staging.ymlnow delegate torelease-shared.yml, and the packaging, release publication, tap updates, and payload assembly live in shared scripts instead of being reimplemented in workflow YAML.This also adds an explicit staging validation mode for PRs. PRs now exercise the same staging build, archive, Homebrew install, and smoke-test flow without publishing a GitHub release or updating
homebrew-axe-staging. Merges tomainstill publish staging, and manual staging runs now require an explicitrefso we no longer silently buildmainby mistake.I considered a separate PR-only staging workflow, but that would have duplicated the same high-risk release and Homebrew logic in another place. Keeping PR validation on the shared release path is simpler and gives us better confidence that pre-release testing matches what we actually ship.
This branch also tightens the release contract around the canonical staged payload (
axe,Frameworks, andAXe_AXe.bundle), centralizes release context resolution, and updates the dry-run checks so the new staging safety rules stay enforced.Note
High Risk
Refactors the production/staging release pipeline, packaging, and publication logic (including code signing, notarization, GitHub releases, and Homebrew tap updates), so misconfiguration could break releases or publish incorrect artifacts. Most risk is in the new reusable workflow + shared scripts that now drive all release paths.
Overview
Unifies production and staging release automation by extracting nearly all build/package/publish logic into a reusable workflow
release-shared.yml, withrelease.ymland the newrelease-staging.ymlnow delegating to it.Adds explicit staging validation for PRs (build + archive + Homebrew install + smoke tests) without publishing a GitHub release or updating the tap, and tightens manual staging runs to require an explicit
refplus anintent(validatevspublish).Introduces shared release scripts to enforce a canonical staged payload (
axe,Frameworks/,AXe_AXe.bundle/) and centralize operations:release-context.mjs(mode/tag/tap decisions),release-artifacts.sh(stage/extract/verify/archive + Homebrew signature stripping),publish-github-release.sh, andupdate-homebrew-tap.sh. Updatesbuild.shandgenerate-github-release-notes.mjsto support this flow, and expandsrelease-dry-run+ fixture checks to prevent drift.Written by Cursor Bugbot for commit cd76bfa. This will update automatically on new commits. Configure here.