mirror of
https://github.com/cozystack/cozystack.git
synced 2026-11-09 05:25:14 +09:00
ci(release): carry the finalize fixes onto the 1.6 line (#3530)
## Why `release-1.6` was created at v1.6.0's merge commit on 2026-07-22, and the finalize fixes landed on `main` after that. Workflow files run from the ref they fire on, so a promote PR based on `release-1.6` runs **this** copy of `pull-requests-release.yaml`, not `main`'s. Shipping v1.6.1 from this line today would repeat two defects v1.6.0 already hit. ## What this carries Three upstream commits, after which this file is **byte-identical to `main`'s copy** — so later backports touching it will not conflict. | Upstream | Change | | --- | --- | | `01e1e7188` | `persist-credentials: false` on the checkout | | `f49d54a68` | publish the release with the merged changelog as its body | | `ba67fea7d` | drop `paths-ignore` from the trigger | ### persist-credentials Without it the checkout persists `GITHUB_TOKEN` as an `http.extraheader`, which silently wins over the app token injected by `git remote set-url`. The stable tag then pushes as `GITHUB_TOKEN`, which creates no workflow run, so `tags.yaml` never fires and its `generate-changelog` and `update-website-docs` backstops stay silent. That is exactly what happened to v1.6.0. ### Changelog as release body Without it the release publishes with the draft's own body, `"Promoted from vX.Y.Z-rc.N"`. **These two compound into a permanent defect on a maintenance line.** A patch's changelog is committed to `release-1.6.1`, merged into `release-1.6`, and never reaches `main` — and `update-releasenotes.yaml` only watches `main`, so nothing would ever sync it. The tag-time backstop that would have ported it is dead because of the credential bug. v1.6.1 would ship with placeholder release notes permanently, with no error anywhere. ### paths-ignore Latent rather than active. A promote PR carrying only `docs/changelogs/vX.Y.Z.md` would be dropped by the filter, producing no finalize run, no tag and no error. v1.6.1 will carry tag-string rewrites so it would not have fired, but the filter has no remaining purpose now that the promote PR always carries a changelog. ## On the cherry-pick policy `docs/release.md`'s skip rule says CI-only changes do not belong in a patch. That rule governs release *contents*; backporting release *machinery* so the line is releasable at all is a different thing, and there is precedent already on this branch — #3473, #3474 and #3514 are all CI/release backports. ## Verification `actionlint` clean. `zizmor` clean, no findings. YAML parses. No bats suite references this workflow, and `hack/promote-gate-contract.bats` does not exist on this line, so there is no stale contract test to trip. The diff against `origin/main`'s copy of the file is empty. ## Not included Three known gaps on this line are deliberately out of scope, none of which needs a code change to ship v1.6.1: - `e2e-tag.yaml` is absent and `tags.yaml` has no `rc-e2e` job, so no E2E runs on the rc. Handled by dispatching **E2E Release Tag from `main`** against the published rc tag, which also supplies the promote gate's alternate evidence. - `pull-requests.yaml` has no `labeled` trigger, so adding `full-e2e` to the promote PR starts nothing. Backporting that needs the label-event guards too, and rc-time validation makes it unnecessary here. - finalize is still the pre-#3456 monolith, so a mid-registry failure is not re-runnable and needs the documented hand recovery. ```release-note NONE ```
This commit is contained in:
1 file changed
+53
-3
@@ -3,8 +3,15 @@ name: "Releasing PR"
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
paths-ignore:
|
||||
- 'docs/**/*'
|
||||
# No paths filter. A `paths-ignore: docs/**/*` used to live here as an
|
||||
# optimization, but the promote PR now carries docs/changelogs/vX.Y.Z.md —
|
||||
# and `Prepare stable branch` explicitly tolerates producing no tag-string
|
||||
# changes ("digests already stable"). That combination can yield a docs-only
|
||||
# promote PR, which the filter would drop: no finalize run, so no tag, no
|
||||
# release, and no error anywhere. Filtering is left entirely to the `if:`
|
||||
# gate below (merged + `release` label + release-bot author), which is what
|
||||
# actually decides whether this workflow should do anything. The cost is a
|
||||
# skipped job on unrelated closed PRs.
|
||||
|
||||
# Cancel in‑flight runs for the same PR when a new push arrives.
|
||||
concurrency:
|
||||
@@ -74,6 +81,15 @@ jobs:
|
||||
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Do NOT persist GITHUB_TOKEN as http.extraheader. Every tag/branch push
|
||||
# below re-injects the app token via `git remote set-url`, but a persisted
|
||||
# extraheader silently wins over the URL credential — so those pushes
|
||||
# would authenticate as GITHUB_TOKEN, and a GITHUB_TOKEN push creates no
|
||||
# workflow run (GitHub anti-recursion). That is exactly why v1.6.0's
|
||||
# stable tag triggered no tags.yaml run (the generate-changelog and
|
||||
# update-website-docs backstops never fired). With this false, the set-url
|
||||
# app token authenticates, matching how promote-rc.yaml already pushes.
|
||||
persist-credentials: false
|
||||
|
||||
# Create the release tag at the merge commit — write-once.
|
||||
#
|
||||
@@ -312,6 +328,39 @@ jobs:
|
||||
console.log(`🏷️ ${tag} is the highest version, make_latest: true`);
|
||||
}
|
||||
|
||||
// Release body comes from the changelog that merged with THIS PR
|
||||
// (promote-rc.yaml commits it onto the release-X.Y.Z branch), read
|
||||
// from the merge commit already checked out here.
|
||||
//
|
||||
// Deliberately not left to update-releasenotes.yaml: that workflow
|
||||
// triggers on pushes to main touching docs/changelogs/v*.md, so it
|
||||
// would race this job on the very same push — and if it won, it
|
||||
// would find no published release yet, skip, and never fire again,
|
||||
// leaving the release body permanently empty. It also only watches
|
||||
// main, so a patch release whose promote PR targets release-X.Y
|
||||
// would never be synced at all. Reading the file here is both
|
||||
// race-free and branch-agnostic; update-releasenotes.yaml remains
|
||||
// for later maintainer edits and manual re-syncs.
|
||||
const fs = require('fs');
|
||||
const changelogPath = `docs/changelogs/${tag}.md`;
|
||||
let body;
|
||||
if (fs.existsSync(changelogPath)) {
|
||||
const raw = fs.readFileSync(changelogPath, 'utf8');
|
||||
// Last line of defence before the body becomes public and
|
||||
// immutable-by-convention. promote-rc.yaml validates the changelog
|
||||
// structurally, but it can also arrive by hand-commit, and an
|
||||
// empty or whitespace-only file here would blank the release notes
|
||||
// more thoroughly than publishing the draft's body would.
|
||||
if (raw.trim().length === 0) {
|
||||
console.log(`::warning::${changelogPath} is empty or whitespace-only — refusing to use it as the release body for ${tag}. Publishing with the draft's existing body instead.`);
|
||||
} else {
|
||||
body = raw;
|
||||
console.log(`📝 Release body from ${changelogPath} (${body.length} bytes)`);
|
||||
}
|
||||
} else {
|
||||
console.log(`::warning::${changelogPath} not found in the merge commit — publishing ${tag} with the draft's existing body. Add the changelog and re-run 'Update Release Notes' to backfill it.`);
|
||||
}
|
||||
|
||||
// Publish the release
|
||||
await github.rest.repos.updateRelease({
|
||||
owner: context.repo.owner,
|
||||
@@ -319,7 +368,8 @@ jobs:
|
||||
release_id: draft.id,
|
||||
draft: false,
|
||||
prerelease: isRc,
|
||||
make_latest: makeLatest
|
||||
make_latest: makeLatest,
|
||||
...(body ? { body } : {})
|
||||
});
|
||||
console.log(`🚀 Published release ${tag}`);
|
||||
|
||||
|
||||
Reference in new issue
Block a user