fix(ci): authenticate gh release lookup and fail loud on lookup error

Two issues caught in review on #2597:

1. `gh release list` in GitHub Actions requires GH_TOKEN. The release
   notes step in `.github/workflows/ci.yml` had no env block, so gh would
   exit non-zero and the script's silent fallback would re-strand the
   silent-tag entries this change is meant to recover. Pass
   `GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}` to the step.

2. Silently degrading to legacy single-version output on gh failure is
   itself the regression vector — a future token misconfig or gh outage
   would lose data with no signal. `resolvePublishedFloorTag` now throws
   on gh failure with an actionable hint ("pass GH_TOKEN in Actions; set
   OMP_RELEASE_NOTES_FLOOR= locally to opt into legacy mode"). The
   thrown error propagates out of `main` and exits non-zero, failing the
   CI step loudly so the release is rebuilt with the fix.

The legitimate null path is preserved: `OMP_RELEASE_NOTES_FLOOR=`
(empty) still forces single-version mode, and a successful gh call with
no candidate < target still returns null (first-ever publish case).

Verified locally: hiding gh from PATH now exits 1 with the hint;
`OMP_RELEASE_NOTES_FLOOR=` with hidden gh still produces the legacy
84-bullet single-version output.

Refs #2596
This commit is contained in:
roboomp
2026-06-14 23:43:17 +00:00
parent 4d9f9d9edf
commit 77a5befd53
2 changed files with 27 additions and 14 deletions
+20 -14
View File
@@ -188,13 +188,18 @@ async function loadPackageName(pkgDir: string): Promise<string> {
/**
* Resolve the highest published, non-prerelease, non-draft semver tag strictly
* below `targetVersion`. Falls back to `null` on error or no candidate, which
* downgrades the script to legacy single-version extraction.
* below `targetVersion` via `gh release list`.
*
* `OMP_RELEASE_NOTES_FLOOR` env override:
* - unset → query `gh` (CI default)
* - `vX.Y.Z` → use as-is (manual rerun: "I know the floor")
* - empty string → force single-version mode (legacy escape hatch)
* Failure semantics:
* - `OMP_RELEASE_NOTES_FLOOR` set → honored verbatim (`""` forces null).
* - `gh` succeeded, no candidate < target → `null` (legitimate first-ever
* publish; legacy single-version output is correct).
* - `gh` itself failed (missing binary, missing `GH_TOKEN` in Actions,
* network/auth error) → throws. Letting this degrade to single-version
* output silently re-strands silent-tag entries (#2596 review); the CI
* step must die loudly so the release is rebuilt with the token wired.
* Local runs without `gh` should set `OMP_RELEASE_NOTES_FLOOR=` to opt
* into legacy mode explicitly.
*/
async function resolvePublishedFloorTag(targetVersion: string): Promise<string | null> {
const override = process.env.OMP_RELEASE_NOTES_FLOOR;
@@ -207,21 +212,22 @@ async function resolvePublishedFloorTag(targetVersion: string): Promise<string |
.quiet()
.nothrow();
if (res.exitCode !== 0) {
console.warn(
`Warning: gh release list failed (exit ${res.exitCode}); falling back to single-version notes. stderr: ${res.stderr.toString().trim()}`,
const stderr = res.stderr.toString().trim();
throw new Error(
`gh release list exited ${res.exitCode}.\nstderr: ${stderr || "(empty)"}\n` +
`Hint: in GitHub Actions, pass GH_TOKEN: \${{ secrets.GITHUB_TOKEN }} to this step. ` +
`Locally without gh, set OMP_RELEASE_NOTES_FLOOR= to fall back to single-version notes.`,
);
return null;
}
let raw: unknown;
try {
raw = JSON.parse(res.stdout.toString());
} catch (err) {
console.warn(
`Warning: failed to parse gh release list output: ${(err as Error).message}; falling back to single-version notes.`,
);
return null;
throw new Error(`gh release list returned non-JSON output: ${(err as Error).message}`);
}
if (!Array.isArray(raw)) {
throw new Error(`gh release list returned a non-array payload: ${typeof raw}`);
}
if (!Array.isArray(raw)) return null;
const candidates = (raw as Array<{ tagName?: unknown; isDraft?: unknown; isPrerelease?: unknown }>)
.filter(t => t.isDraft !== true && t.isPrerelease !== true)
.map(t => (typeof t.tagName === "string" ? t.tagName : ""))