11name : Release
2- # Cuts a FrankenPHP release end-to-end: refreshes the PGO profile, bumps the
3- # Caddy module's frankenphp dependency, commits the result as
4- # github-actions[bot], tags v<version> and caddy/v<version>, drafts a GitHub
5- # release, dispatches the downstream binary builds, and opens a Homebrew
6- # formula bump PR. Dispatched by release.sh.
7- #
8- # The workflow is idempotent: re-dispatching after a partial failure (flaky
9- # test, network blip, registry hiccup) detects which steps already completed
10- # and skips them, so the release can be resumed without manual cleanup.
2+ # Refreshes PGO, bumps caddy/go.mod, commits as github-actions[bot],
3+ # tags v<version> and caddy/v<version>, drafts the GitHub release,
4+ # dispatches the binary build workflows, and bumps the Homebrew formula.
5+ # Idempotent: a re-dispatch after a partial failure resumes by tag.
116on :
127 workflow_dispatch :
138 inputs :
1813 type : string
1914permissions :
2015 contents : write
21- # Needed to dispatch the downstream binary build workflows from this run.
22- actions : write
16+ actions : write # to dispatch downstream binary build workflows
2317concurrency :
2418 group : ${{ github.workflow }}
2519 cancel-in-progress : false
@@ -35,13 +29,10 @@ jobs:
3529 BENCH_SEC : " 30"
3630 steps :
3731 - name : Validate inputs
38- # UI-triggered dispatches bypass release.sh's preflight regex, so
39- # re-validate the version string here. Any payload that would not
40- # produce a clean semver tag is rejected before it propagates into
41- # go get / sed / tag refs and breaks the release in mid-flight.
32+ # Reject non-main refs and non-semver versions before they reach
33+ # go get / sed / tag refs. https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
4234 env :
4335 VERSION : ${{ inputs.version }}
44- # Adapted from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
4536 run : |
4637 if [[ "${GITHUB_REF}" != "refs/heads/main" ]]; then
4738 echo "::error::release.yaml must be dispatched against refs/heads/main, got ${GITHUB_REF}"
5950 id : classify
6051 env :
6152 VERSION : ${{ inputs.version }}
62- # Pre-release versions (those carrying a "-" suffix per SemVer) must
63- # not be marked --latest nor bump the stable Homebrew formula.
53+ # Pre-releases (SemVer "-" suffix) must not bump --latest or Homebrew.
6454 run : |
6555 if [[ "${VERSION}" == *-* ]]; then
6656 echo "prerelease=true" >> "${GITHUB_OUTPUT}"
@@ -72,17 +62,12 @@ jobs:
7262 env :
7363 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
7464 VERSION : ${{ inputs.version }}
75- # Tag existence is the source of truth for "release in progress":
76- # main HEAD may have moved past the release commit (a follow-up fix
77- # merged on top), so the commit-message check on HEAD is too narrow.
78- # If v<version> exists, resume from the commit it points at;
79- # otherwise it's a fresh attempt and tags must not exist.
65+ # Tag existence is the resume signal — main HEAD may have moved
66+ # past the release commit, so a HEAD message check is too narrow.
8067 run : |
8168 set -euo pipefail
82- # matching-refs returns an empty array (still HTTP 200) when the ref
83- # is absent — no need to parse error wording to distinguish 404
84- # from rate-limit/5xx/auth failures, which `gh` still surfaces as a
85- # non-zero exit through `set -e`.
69+ # matching-refs returns [] (HTTP 200) for absent tags; real
70+ # failures still trip set -e.
8671 lookup_tag() {
8772 gh api "repos/${GITHUB_REPOSITORY}/git/matching-refs/tags/$1" \
8873 --jq ".[] | select(.ref == \"refs/tags/$1\") | {sha: .object.sha, type: .object.type}"
@@ -102,16 +87,12 @@ jobs:
10287 caddy_entry=$(lookup_tag "caddy/v${VERSION}")
10388 if [[ -n "${main_entry}" ]]; then
10489 sha=$(resolve_commit "${main_entry}")
105- # Refuse to resume against a tag that isn't reachable from main:
106- # protects against an orphan tag created on a side branch.
90+ # Reject orphan tags created on a side branch.
10791 if ! git merge-base --is-ancestor "${sha}" HEAD; then
10892 echo "::error::Tag v${VERSION} (${sha}) is not reachable from main; refusing to resume."
10993 exit 1
11094 fi
111- # Symmetric split-state guard: the create_tag step further down
112- # also fails if caddy/v${VERSION} points to a different commit,
113- # but flagging the inconsistency here surfaces it before any
114- # writes happen.
95+ # Catch a mismatched caddy/v${VERSION} before any writes.
11596 if [[ -n "${caddy_entry}" ]]; then
11697 caddy_sha=$(resolve_commit "${caddy_entry}")
11798 if [[ "${caddy_sha}" != "${sha}" ]]; then
@@ -149,8 +130,7 @@ jobs:
149130 run : ./profiles/build-pgo.sh
150131 - if : steps.state.outputs.resume != 'true'
151132 name : Sanity-check PGO profile
152- # Catch the degenerate case where wrk silently failed to drive load
153- # and we ended up shipping a near-empty profile.
133+ # Guard against wrk silently failing and producing a near-empty profile.
154134 run : |
155135 size=$(wc -c <caddy/frankenphp/default.pgo)
156136 echo "PGO profile: ${size} bytes"
@@ -167,8 +147,8 @@ jobs:
167147 go get "github.com/dunglas/frankenphp@v${VERSION}"
168148 go mod tidy
169149 - name : Commit and tag via GitHub API
170- # API-created commits/tags are signed server-side with GitHub's key
171- # and show as " Verified" under the github-actions[bot] identity .
150+ # API-created commits/tags are signed server-side and show as
151+ # Verified under github-actions[bot].
172152 env :
173153 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
174154 REPO : ${{ github.repository }}
@@ -182,10 +162,8 @@ jobs:
182162 commit_sha="${RELEASE_COMMIT}"
183163 echo "Reusing existing release commit ${commit_sha}"
184164 else
185- # Stage the base64 in a temp file and feed it to jq via
186- # --rawfile: passing big blobs (the PGO profile is ~2 MB encoded)
187- # through --arg exceeds ARG_MAX on the runner. Subshell body +
188- # EXIT trap clean up the tmpfile even if base64/jq/gh aborts.
165+ # Use --rawfile: the PGO blob (~2 MB encoded) exceeds ARG_MAX
166+ # via --arg.
189167 make_blob() (
190168 local tmp
191169 tmp=$(mktemp)
@@ -199,10 +177,8 @@ jobs:
199177 parent_sha=$(gh api "repos/${REPO}/git/refs/heads/main" -q .object.sha)
200178 base_tree=$(gh api "repos/${REPO}/git/commits/${parent_sha}" -q .tree.sha)
201179
202- # Collect every tracked file modified by the PGO/bump steps so
203- # that, e.g., a transitive update to a go.sum entry doesn't get
204- # silently dropped from the release commit. Matches the
205- # `git commit -a` behavior the script used to rely on.
180+ # Capture every touched file so transitive go.sum or PGO
181+ # side effects aren't dropped from the release commit.
206182 mapfile -t changed < <(git diff --name-only HEAD)
207183 if [[ ${#changed[@]} -eq 0 ]]; then
208184 echo "::error::No file changes detected after PGO/bump steps."
@@ -224,7 +200,7 @@ jobs:
224200 '{base_tree: $base_tree, tree: $entries}' \
225201 | gh api "repos/${REPO}/git/trees" --input - -q .sha)
226202
227- # [skip ci] keeps push-triggered workflows from firing on top of
203+ # [skip ci] avoids push-triggered workflows firing alongside
228204 # the explicit downstream dispatches below.
229205 commit_sha=$(jq -nc \
230206 --arg message "chore: prepare release ${VERSION} [skip ci]" \
@@ -236,8 +212,8 @@ jobs:
236212 gh api "repos/${REPO}/git/refs/heads/main" -X PATCH -f sha="$commit_sha" --silent
237213 fi
238214
239- # Idempotent tag creation: if the tag already exists and points at
240- # the release commit, leave it alone; if it points elsewhere, fail .
215+ # Idempotent: skip if tag already points at the release commit,
216+ # fail if it points elsewhere.
241217 create_tag() {
242218 local tag="$1"
243219 local existing
@@ -268,15 +244,12 @@ jobs:
268244 create_tag "v${VERSION}"
269245 create_tag "caddy/v${VERSION}"
270246
271- # Pull the new commit + tags so the release-draft step's git
272- # describe can resolve v${VERSION}^.
247+ # So the release-draft step's `git describe v${VERSION}^` resolves.
273248 git fetch origin main --tags
274249 - name : Draft GitHub release
275- # `gh release create` validates the tag through GraphQL, which can
276- # lag minutes behind the Git Data API we just used to create the
277- # tag — leading to "no matches found" or `untagged-*` placeholder
278- # releases. Use the REST releases endpoints directly: they see the
279- # tag immediately and behave deterministically.
250+ # `gh release create` goes through GraphQL which can lag minutes
251+ # behind the Git Data API and yield "no matches" or `untagged-*`
252+ # placeholder releases; the REST releases endpoint is consistent.
280253 env :
281254 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
282255 REPO : ${{ github.repository }}
@@ -302,11 +275,9 @@ jobs:
302275 fi
303276 gh api "repos/${REPO}/releases" "${create_args[@]}" --silent
304277 - name : Trigger downstream release builds
305- # GITHUB_TOKEN-driven API writes don't trigger workflows that listen
306- # on tag/push events, so dispatch each downstream explicitly. Keep
307- # going on partial failure so the operator only needs to re-run the
308- # specific dispatches that didn't go through. Re-dispatch on resume
309- # is harmless: it just queues another build run.
278+ # GITHUB_TOKEN tag writes don't fire push triggers, so dispatch
279+ # each downstream explicitly. Keep going on partial failure;
280+ # re-dispatch on resume just queues another idempotent build.
310281 env :
311282 GH_TOKEN : ${{ secrets.GITHUB_TOKEN }}
312283 REPO : ${{ github.repository }}
0 commit comments