feat(release-notes): expose notes as outputs + optional changelog writer#48
Conversation
The generator produced themed notes but only printed/Discord'd them, so each
repo re-typed the same text into its GitHub release body and its changelog (and
they drifted). Make one generation drive every changelog surface — opt-in and
backward compatible.
- Action outputs `notes` (markdown) + `highlights` (JSON array of the bullet
lines) via $GITHUB_OUTPUT — wire to the release body / a marketing changelog.
- `--out <file>` writes the notes; `--changelog <file>` + `--changelog-format
md|json` prepends a dated entry (md: "## <version> — <date>" under a leading
"# Changelog"; json: { version, date, notes, highlights }, newest-first,
replacing a re-run of the same version). `--date` overrides the entry date.
- `--notes-file <file>` reuses a prior job's notes instead of calling the LLM —
so the release body + changelog can't disagree, and the file paths run without
a gateway key. Short-circuits before any git work (no range needed).
- action.yml: `out-file` / `changelog-file` / `changelog-format` inputs +
`notes` / `highlights` outputs.
Existing callers are unaffected (no new flags → same behavior). New tests cover
--out, both changelog formats, newest-first/replace, and the $GITHUB_OUTPUT
contract; docs + CLI reference updated. v2.2.0 → v2.3.0.
|
👀 Quinn is reviewing — verdict (PASS / WARN / FAIL) + findings to follow. |
There was a problem hiding this comment.
QA Audit — PR #48 | feat(release-notes): expose notes as outputs + optional changelog writer
VERDICT: PASS (CI pending)
CI Status
- Lint + smoke test: queued (non-terminal)
Diff Review
Feature-rich, well-scoped PR that adds GitHub Actions outputs (notes + highlights), --out file write, and structured --changelog with md/json formats — fully backward-compatible via existing callers passing no new flags. The id: generate addition to action.yml is required and correctly placed for the output references to resolve.
Key correctness points:
VALUE_FLAGSset correctly prevents multi-token flags from being consumed as positional version args.extractHighlights()handles both-and*bullet markers.prependMarkdownChangelog()preserves a leading# H1title and handles empty files with a# Changelogheader.writeGithubOutputs()uses heredoc syntax correctly for multi-line$GITHUB_OUTPUTvalues.
Observations
- MEDIUM/CLAWPATCH-GAP:
upsertJsonChangelog()silently recovers from a malformed JSON changelog by starting an empty array — prior entries are lost with no warning on the next successful run. Consider: log a console warning when falling back so the author sees the silent data loss in CI output. - LOW:
readFileSynconnotesFile(around line 308) uses no explicit encoding — Node defaults to UTF-8 for string results but'utf8'would be safer and match the rest of the file's fs usage. - Gap:
clawpatchstructural pass skipped — checkout cache couldn't resolvemainref. Recommend verifying theupsertJsonChangelogsilent-recovery behavior has test coverage. - Gap:
path_existsverification not available in this environment; external references (e.g.,@protolabsai/release-tools@latestinaction.yml) were not independently verified.
— Quinn, QA Engineer
|
Submitted COMMENT review on #48. CI is still queued — a terminal-green CI pass (or a CI-completion re-dispatch) will allow the merge loop to proceed. The two observations (silent JSON recovery data loss, and missing explicit encoding) are MEDIUM/LOW and non-blocking; the PR is otherwise well-structured and the core logic is correct. |
Quinn (QA) flagged that upsertJsonChangelog() recovered from an unparseable changelog by starting an empty array — losing prior entries with no signal. Keep the don't-fail-the-release behavior but console.warn loudly (parse error or non-array) so the data loss shows up in CI output. Adds a test. (The LOW encoding note is already satisfied — every readFileSync passes 'utf8'.)
There was a problem hiding this comment.
PR #48 — feat(release-notes): expose notes as outputs + optional changelog writer
CI Status
Lint + smoke test: queued (non-terminal — deferring formal verdict)
Diff Review
The PR adds three new capabilities to the release-notes action:
- Action outputs —
notes(markdown) andhighlights(JSON array) written to$GITHUB_OUTPUTvia heredoc syntax --out <file>/--changelog <file>/--changelog-format md|json— writes notes to a file and prepends a dated entry to a changelog--notes-file <file>— short-circuits LLM call by reading pre-generated notes from disk
Changes span action.yml (new inputs/outputs wiring) and bin/rewrite-release-notes.mjs (flag parsing, file I/O, output exposure).
Observations
- MEDIUM:
upsertJsonChangelogsilently drops the entire prior changelog array if the existing file is valid JSON but not an array (WARNINGis printed but execution continues with an empty array). This is documented intentional behavior, but a consumer could lose history if their changelog is malformed. Consider surfacing this as a non-zero exit condition instead of a console.warn. - LOW:
--changelog-formataccepts any string value with no validation — invalid values fall through without error or default. Themdpath is the implicit fallback. Consider validating against['md', 'json']and exiting with a clear error. - LOW: File writes via
writeFileSync/appendFileSynchave no explicit error handling. A disk-full or permission error propagates as an unhandled exception. A try/catch with a user-facing message would be clearer. - INFO: The heredoc
$GITHUB_OUTPUTsyntax (notes<<RELNOTES_EOF\n...\nRELNOTES_EOF) is correctly formatted for GitHub Actions' multi-line output protocol.
No unresolved CodeRabbit threads. No critical path issues identified in the diff. The feature is fully opt-in and backward-compatible with existing callers.
— Quinn, QA Engineer
|
Submitted |
Why
The generator produces nice themed notes — but only prints / Discords them. So every consuming repo re-types the same text into its GitHub release body and its changelog, and the two drift. As the fleet starts releasing a lot, that hand-copying is the bottleneck.
This makes one generation drive every changelog surface. Fully opt-in and backward compatible — existing callers (no new flags) behave exactly as before.
What
notes(markdown) +highlights(JSON array of the bullet lines) via$GITHUB_OUTPUT→ wire to the GitHub release body and/or a marketing changelog.--out <file>writes the notes;--changelog <file>+--changelog-format md|jsonprepends a dated entry:md→## <version> — <date>section under a leading# Changelogjson→{ version, date, notes, highlights }, newest-first, replacing a re-run of the same version--dateoverrides the entry date (default: today UTC)--notes-file <file>reuses a prior job's notes instead of calling the LLM — so the release body + changelog can't disagree, and the file paths run without a gateway key. Short-circuits before any git work.action.yml: newout-file/changelog-file/changelog-formatinputs +notes/highlightsoutputs.Usage (the pattern this unlocks)
Repos with a bespoke changelog (e.g. a marketing
changelog.jsonwith extra fields like a DMG URL) consumeoutputs.highlightsand build their own entry.Tests / docs
--out, both changelog formats, newest-first + same-version replace, and the$GITHUB_OUTPUTcontract (all via--notes-file, no gateway key).node --test→ 65 passing;biome check+npm run smokeclean.v2.2.0 → v2.3.0.First consumer will be ORBIS (release body + marketing
changelog.jsonfrom one generation); opening that wiring separately once this publishes.