Skip to content

ci(release): Share staging and production workflows#35

Merged
cameroncooke merged 5 commits intomainfrom
ci/shared-release-pipeline
Mar 6, 2026
Merged

ci(release): Share staging and production workflows#35
cameroncooke merged 5 commits intomainfrom
ci/shared-release-pipeline

Conversation

@cameroncooke
Copy link
Copy Markdown
Owner

@cameroncooke cameroncooke commented Mar 6, 2026

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.yml and release-staging.yml now delegate to release-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 to main still publish staging, and manual staging runs now require an explicit ref so we no longer silently build main by 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, and AXe_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, with release.yml and the new release-staging.yml now 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 ref plus an intent (validate vs publish).

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, and update-homebrew-tap.sh. Updates build.sh and generate-github-release-notes.mjs to support this flow, and expands release-dry-run + fixture checks to prevent drift.

Written by Cursor Bugbot for commit cd76bfa. This will update automatically on new commits. Configure here.

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.
@cameroncooke cameroncooke marked this pull request as ready for review March 6, 2026 11:12
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.
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 6, 2026

Walkthrough

The 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)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'ci(release): Share staging and production workflows' directly and concisely summarizes the main change: consolidating release pipelines into a shared reusable workflow.
Description check ✅ Passed The PR description comprehensively describes the changeset, explaining the extraction of release workflows into shared components and the addition of staging validation modes.

✏️ 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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_REF is tightly coupled to the exact formatting in build.sh. If no match is found, PINNED_IDB_COMMIT will 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

📥 Commits

Reviewing files that changed from the base of the PR and between 53bf441 and 6945d04.

📒 Files selected for processing (13)
  • .github/workflows/release-dry-run.yml
  • .github/workflows/release-shared.yml
  • .github/workflows/release-staging.yml
  • .github/workflows/release.yml
  • scripts/build.sh
  • scripts/generate-github-release-notes.mjs
  • scripts/publish-github-release.sh
  • scripts/release-artifacts.sh
  • scripts/release-context.mjs
  • scripts/release-dry-run.sh
  • scripts/release-payload.sh
  • scripts/release.sh
  • scripts/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.
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@cameroncooke cameroncooke merged commit d9356b5 into main Mar 6, 2026
4 checks passed
@cameroncooke cameroncooke deleted the ci/shared-release-pipeline branch March 6, 2026 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant