Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,11 @@ jobs:

echo "Cross-platform CI passed for ${GITHUB_SHA}: ${ci_url}"

# Notes / service baseline:
# Service baseline (lineage-relative): merged tags only, so the
# changed-files gate compares against the last release actually
# reachable from this commit. The release-notes baseline below uses the
# full tag set instead, so a stable on another lineage can anchor the
# changelog range.
# - Preview: newest prior release of either channel (stable or preview). A
# preview→preview-only baseline skips a shipped stable and restates it.
# - Stable: newest prior stable only (matching preview carry adjusts the
Expand Down Expand Up @@ -310,8 +314,14 @@ jobs:

# Channel previous tag for Full Changelog + default notes baseline.
# Preview baselines any prior release; stable baselines prior stable only.
# Read the FULL tag set: stable tags live on main's lineage, which the
# preview branch does not carry, and a trailing same-core preview
# (vX.Y.Z-preview.* shipped after vX.Y.Z) must not hide the stable from
# the compare range. The helper's semver ordering already ranks the
# stable above its own trailing preview, so the full tag list yields
# v2.9.1 → v2.10.0-preview instead of v2.9.1-preview → v2.10.0-preview.
previous_tag="$(
git tag --merged HEAD --list 'v[0-9]*' |
git tag --list 'v[0-9]*' |
bun scripts/release-notes.ts previous-release-tag "$RELEASE_VERSION"
)"
npm_metadata="Published to npm as \`@bitkyc08/opencodex@${RELEASE_VERSION}\` with dist-tag \`${NPM_DIST_TAG}\`."
Expand Down
8 changes: 8 additions & 0 deletions scripts/release-notes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,14 @@ export function matchingPreviewTags(version: string, tags: string[]): string[] {
* that stable's changelog (e.g. 2.7.41-preview → 2.7.43-preview after 2.7.42).
* - Stable releases: newest prior stable only. Matching preview carry adjusts the
* notes range start separately when assembling latest notes.
*
* Callers must pass the FULL repo tag set, not `git tag --merged HEAD`. Stable
* tags live on main's lineage, which the preview branch does not carry, and a
* trailing same-core preview (vX.Y.Z-preview.* shipped after vX.Y.Z) must not
* hide the stable: for `2.10.0-preview.*` after `v2.9.1` + `v2.9.1-preview.*`,
* the baseline must be `v2.9.1`, not the trailing preview. Semver ordering
* already ranks the stable above its own trailing preview, so the full set is
* sufficient; restricting to merged tags is what reintroduces the bug.
*/
export function previousReleaseNotesTag(version: string, tags: string[]): string | null {
if (!version) return null;
Expand Down
12 changes: 12 additions & 0 deletions tests/ci-workflows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,18 @@ describe("GitHub Actions hardening", () => {
expect(createStep.indexOf("gh api")).toBeGreaterThan(-1);
expect(createStep.indexOf('git tag "$release_tag"')).toBeGreaterThan(-1);
expect(createStep.indexOf("gh api")).toBeLessThan(createStep.indexOf('git tag "$release_tag"'));
// The notes baseline must read the FULL tag set, not `--merged HEAD`: stable
// tags live on main's lineage, which the preview branch does not carry, and a
// trailing same-core preview must not hide the stable from the range
// (v2.9.1-preview → v2.10.0-preview is wrong; the range must start at v2.9.1).
expect(createStep).toContain("git tag --list 'v[0-9]*' |");
expect(createStep).not.toContain("--merged HEAD");
// The merged-only restriction remains on the service gate, whose
// changed-files comparison is deliberately lineage-relative.
const ciGateStep = workflow
.split("- name: Require successful Cross-platform CI for this commit")[1]!
.split(/\n {6}- name:/)[0]!;
expect(ciGateStep).toContain("--merged HEAD");
// First-channel releases must not call generate-notes without an explicit baseline
// (GitHub would otherwise pick the newest repo tag, possibly from the other channel).
// Scope to the single if-block that owns generate-notes; createStep has two
Expand Down
13 changes: 13 additions & 0 deletions tests/release-notes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,19 @@ describe("previousReleaseNotesTag", () => {
"v2.7.42",
])).toBe("v2.7.42");
});

test("a trailing same-core preview does not hide the stable (2.9.1 → 2.10.0-preview)", () => {
// v2.9.1-preview.20260802 shipped after the v2.9.1 stable on another lineage;
// the next preview train must still baseline the stable, not the trailing
// preview. This is the workflow's `git tag --list` (full set) contract: the
// same input restricted to `--merged HEAD` would drop v2.9.1 and wrongly
// return v2.9.1-preview.20260802.
expect(previousReleaseNotesTag("2.10.0-preview.20260802", [
"v2.9.1-preview.20260802",
"v2.9.1",
"v2.10.0-preview.20260802",
])).toBe("v2.9.1");
});
});

describe("stripCarriedReleaseNotes", () => {
Expand Down
Loading