Skip to content

feat(release-notes): expose notes as outputs + optional changelog writer#48

Merged
mabry1985 merged 2 commits into
mainfrom
feat/changelog-outputs
Jun 7, 2026
Merged

feat(release-notes): expose notes as outputs + optional changelog writer#48
mabry1985 merged 2 commits into
mainfrom
feat/changelog-outputs

Conversation

@mabry1985

Copy link
Copy Markdown
Contributor

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

  • Action outputs 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|json prepends a dated entry:
    • md## <version> — <date> section under a leading # Changelog
    • json{ version, date, notes, highlights }, newest-first, replacing a re-run of the same version
    • --date overrides 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: new out-file / changelog-file / changelog-format inputs + notes / highlights outputs.

Usage (the pattern this unlocks)

- id: notes
  uses: protoLabsAI/release-tools@v2
  with:
    version: ${{ steps.version.outputs.tag }}
    previous-version: ${{ steps.version.outputs.prev_tag }}
    changelog-file: CHANGELOG.md   # optional, in-repo changelog
  env:
    GATEWAY_API_KEY: ${{ secrets.GATEWAY_API_KEY }}
    DISCORD_RELEASE_WEBHOOK: ${{ secrets.DISCORD_RELEASE_WEBHOOK }}
- run: gh release edit "$TAG" --notes "${{ steps.notes.outputs.notes }}"   # body == Discord == changelog

Repos with a bespoke changelog (e.g. a marketing changelog.json with extra fields like a DMG URL) consume outputs.highlights and build their own entry.

Tests / docs

  • New tests: --out, both changelog formats, newest-first + same-version replace, and the $GITHUB_OUTPUT contract (all via --notes-file, no gateway key). node --test65 passing; biome check + npm run smoke clean.
  • Updated the how-to + CLI reference; v2.2.0 → v2.3.0.

First consumer will be ORBIS (release body + marketing changelog.json from one generation); opening that wiring separately once this publishes.

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.
@protoquinn

protoquinn Bot commented Jun 7, 2026

Copy link
Copy Markdown

👀 Quinn is reviewing — verdict (PASS / WARN / FAIL) + findings to follow.

@protoquinn protoquinn Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_FLAGS set correctly prevents multi-token flags from being consumed as positional version args.
  • extractHighlights() handles both - and * bullet markers.
  • prependMarkdownChangelog() preserves a leading # H1 title and handles empty files with a # Changelog header.
  • writeGithubOutputs() uses heredoc syntax correctly for multi-line $GITHUB_OUTPUT values.

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: readFileSync on notesFile (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: clawpatch structural pass skipped — checkout cache couldn't resolve main ref. Recommend verifying the upsertJsonChangelog silent-recovery behavior has test coverage.
  • Gap: path_exists verification not available in this environment; external references (e.g., @protolabsai/release-tools@latest in action.yml) were not independently verified.

— Quinn, QA Engineer

@protoquinn

protoquinn Bot commented Jun 7, 2026

Copy link
Copy Markdown

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'.)

@protoquinn protoquinn Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI terminal-green, no blockers on prior review — auto-approving on green (#748).

@mabry1985 mabry1985 merged commit 96159f8 into main Jun 7, 2026
1 check passed

@protoquinn protoquinn Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #48feat(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:

  1. Action outputsnotes (markdown) and highlights (JSON array) written to $GITHUB_OUTPUT via heredoc syntax
  2. --out <file> / --changelog <file> / --changelog-format md|json — writes notes to a file and prepends a dated entry to a changelog
  3. --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: upsertJsonChangelog silently drops the entire prior changelog array if the existing file is valid JSON but not an array (WARNING is 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-format accepts any string value with no validation — invalid values fall through without error or default. The md path is the implicit fallback. Consider validating against ['md', 'json'] and exiting with a clear error.
  • LOW: File writes via writeFileSync/appendFileSync have 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_OUTPUT syntax (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

@protoquinn

protoquinn Bot commented Jun 7, 2026

Copy link
Copy Markdown

Submitted COMMENT review on protoLabsAI/release-tools#48. CI is still running — a CI-completion event will re-dispatch me for the formal PASS/FAIL verdict once checks are terminal-green.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant