Skip to content

feat(docs): add changelog release workflow#1164

Merged
jrusso1020 merged 4 commits into
mainfrom
06-02-changelog-process
Jun 2, 2026
Merged

feat(docs): add changelog release workflow#1164
jrusso1020 merged 4 commits into
mainfrom
06-02-changelog-process

Conversation

@jrusso1020
Copy link
Copy Markdown
Collaborator

@jrusso1020 jrusso1020 commented Jun 2, 2026

What

Adds a proper changelog and release-note workflow for HyperFrames.

Why

HyperFrames currently relies on GitHub-generated release notes and does not have a public docs changelog. This gives maintainers a repeatable process that can draft release notes automatically while preserving a manual rewrite/review step before publishing.

How

  • Added a Mintlify changelog page with RSS enabled and a seed v0.6.52 entry.
  • Added a maintainer-facing changelog process doc and linked it from release-channel docs.
  • Added releases/vX.Y.Z.md as the reviewed GitHub Release body source of truth.
  • Added bun run changelog:draft <version> to generate release notes and Mintlify update blocks from conventional commits.
  • Configured GitHub generated-release-note categories in .github/release.yml.
  • Updated the publish workflow to prefer reviewed release notes and fall back to GitHub-generated notes.
  • Integrated the changelog checkpoint into bun run set-version <version> for stable releases, with prerelease/no-tag skips and an emergency --skip-changelog-check escape hatch.

Test plan

  • Unit tests added/updated
  • Manual testing performed
  • Documentation updated (if applicable)

Manual checks:

  • bun run test:scripts
  • bun run changelog:draft 0.6.53 --date 2026-06-02
  • bun run set-version 9.9.9 (verified the stable-release changelog guard fails before version mutation)
  • bun run lint
  • bun run format:check
  • bunx fallow audit --base origin/main --fail-on-issues
  • npx mint validate
  • npx mint broken-links

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@mintlify
Copy link
Copy Markdown

mintlify Bot commented Jun 2, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
hyperframes 🟢 Ready View Preview Jun 2, 2026, 9:20 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

Comment thread scripts/draft-changelog.ts Fixed
Comment thread scripts/set-version.ts Fixed
Comment thread scripts/set-version.ts Fixed
Comment thread scripts/set-version.ts Fixed
Copy link
Copy Markdown
Collaborator

@miguel-heygen miguel-heygen left a comment

Choose a reason for hiding this comment

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

#1164 — changelog workflow

LGTM. Solid design — draft → human rewrite → releases/vX.Y.Z.md as source of truth → publish picks it up, with --generate-notes as a safe fallback. A few notes:

Solid

  • releases/vX.Y.Z.md already-exists guard (existsSync + fail unless --force) prevents accidentally nuking a reviewed file. Clean.
  • resolvePreviousTag handles both "version tag exists" and "unreleased HEAD" cases; the catch → fail with explicit instruction to pass --from is the right behavior on a repo with no prior tags.
  • DOCS_MARKER as the prepend point in changelog.mdx is cleaner than line-number arithmetic.
  • rss: true on the changelog page is a nice touch — subscribers get notified automatically.
  • CI fallback: [ -f "$NOTES_FILE" ] && --notes-file || --generate-notes — idempotent and safe.

Minor

  • release-channels.mdx uses bun run changelog:draft 0.4.24 --write — that version number looks like a leftover from an older docs template (the rest of the file uses 0.4.24 from before the 0.6.x era). Should be <version> or matched to the surrounding example.
  • --force silently overwrites a reviewed file with no confirmation prompt. Fine for a script, but worth a note in the process doc so maintainers don't accidentally draft over a finished release note the day before tagging.

Neither is blocking.

Copy link
Copy Markdown
Collaborator Author

@jrusso1020 jrusso1020 left a comment

Choose a reason for hiding this comment

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

Reviewed the full diff (11 files, +795/−32). The changelog/release workflow is well-shaped: publish.yml prefers a reviewed releases/v${VERSION}.md and falls back to --generate-notes idempotently, and set-version.ts's releaseAllowedPaths correctly whitelists exactly the files that flow into the release commit, so the two pieces line up.

The only thing gating the PR was CodeQL (sole failing required check; everything else green). It raised 4 alerts, all real and all in the two new scripts. I pushed fixes directly (this is my own PR) — commits 17620a5 and eaae48e, both signed/verified.

Findings (fixed)

  • blocker — js/file-system-race (high), scripts/draft-changelog.ts:504writeReleaseNotes did existsSync(path) && !force then writeFileSync a few lines later: a TOCTOU gap. Per the rule's recommendation (make creation atomic rather than check-then-act), replaced with an exclusive-write flag — "wx" when not forcing (the "already exists" guard now surfaces atomically via the EEXIST catch), "w" under --force. Removed the now-unused existsSync import.

  • blocker — js/indirect-command-line-injection (medium ×3), scripts/set-version.ts:109/110/113 — the release-commit helper built shell strings (git add ${paths}, git commit -m "...${version}", git tag v${version}) for execSync. Even with the semver regex guard, the canonical fix this rule asks for is to drop the shell: switched all git calls to execFileSync("git", [...args]) with argument arrays, so interpolated values can't be shell-interpreted.

  • important — fallow/high-crap-score (minor, surfaced as a required-check failure), scripts/draft-changelog.ts:504 — the exclusive-write fix above pushed writeReleaseNotes to cyclomatic 5 / CRAP 30.0 (threshold 30.0). The !force in the catch was redundant — EEXIST is only reachable under the "wx" flag (force=false), since "w" overwrites without throwing. Dropping it returns the function to cyclomatic 4 / CRAP 20 with identical behavior.

Verification

  • oxlint + oxfmt --check: clean on both files.
  • bunx fallow audit --base origin/main: ✓ No issues in 11 changed files.
  • CodeQL re-ran on the fix head — failure cleared (the 4 alerts no longer surface in the analysis).
  • Exercised the --write path end-to-end: fresh write succeeds, second write without --force correctly fails with "already exists" (the new EEXIST branch), --force overwrites.

Notes

  • The rest of the diff (.github/release.yml categories incl. the required "*" catch-all; docs changelog.mdx / changelog-process.mdx / release-channels.mdx / docs.json) read cleanly; docs passed mint validate / broken-links in CI.
  • Remaining BLOCKED state is purely the REVIEW_REQUIRED reviewer gate — no CI check is failing. Posting as a comment since this is my own PR (GitHub disallows self-approval); needs one approving review to merge.

Verdict: COMMENT (self-PR — can't self-approve; code is merge-ready once a reviewer approves)
Reasoning: All 4 CodeQL/Fallow findings fixed at root and verified green; the rest of the diff is sound and the only remaining gate is a required human approval.

— Claude (pr-review)

Copy link
Copy Markdown
Collaborator

@vanceingalls vanceingalls left a comment

Choose a reason for hiding this comment

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

Good workflow design overall — the --generate-notes fallback, the { flag: "wx" } atomic write, and the execFileSync array-args migration in set-version.ts are all the right calls. Two things need addressing before this ships.


Important — MDX injection from raw commit summaries

renderCommitBullet interpolates commit summaries unescaped into docs/changelog.mdx, which Mintlify parses as MDX (JSX-capable). A summary containing <, >, {, or } — common in a framework codebase referencing component names or TypeScript generics — will cause the docs build to fail or produce garbled output.

Example: fix(core): guard <timeline> calls → becomes - **Core:** Guard <timeline> calls (abc123, #999) in MDX → Mintlify parses <timeline> as a JSX element and errors.

The releases/vX.Y.Z.md output is plain Markdown and not affected. The risk is isolated to the MDX path only.

Fix — a helper applied only in the MDX rendering context:

function escapeForMdx(text: string): string {
  return text.replace(/</g, "\\<").replace(/>/g, "\\>").replace(/\{/g, "\\{").replace(/\}/g, "\\}");
}

Apply it to commit.summary (and scope) in renderCommitBullet when building the MDX output, or factor a separate MDX-rendering variant.

Important — No tests for a 542-line parsing script

The script is almost entirely pure functions — parseConventionalSubject, categorizeCommit, formatScope, renderCommitBullet, shouldSkipCommit, gitStatusPath, the arg parser — all straightforwardly unit-testable. scripts/validate-release-channel.test.mjs is a direct precedent in the same directory. The PR's own "Unit tests added/updated" checkbox is unchecked.

Silent miscategorization (e.g., a breaking change landing in "Other Changes") is the main failure mode — it only shows up months later when reviewing a confusing changelog. Expected coverage: all categorize* paths, the catalog heuristic, breaking-change override, shouldSkipCommit patterns, renderCommitBullet with scope/no-scope/PR-link, and gitStatusPath on quoted vs. unquoted paths.


Nits

Boilerplate body looks production-ready. Both renderReleaseNotes and renderDocsUpdate emit "This release includes the changes below." — it's easy to miss on review and accidentally ship. Consider a more obvious placeholder like <!-- TODO: write a 1–2 sentence release summary here -->.

Two sources of truth. releases/vX.Y.Z.md and the <Update> block in changelog.mdx hold the same content. Worth a line in the process doc reminding maintainers to keep them in sync after the mandatory rewrite step.

Compare URL is a brief forward reference. The compare/${from}...v${version} URL points to a tag that doesn't exist until the release is pushed. Fine for releases/*.md (not published until then), but changelog.mdx could briefly show a broken link between when main is pushed and when the tag resolves. Worth a note in the process doc.


What's solid

  • Publish workflow fallback (reviewed notes → --generate-notes) is the right default-safe design. ✓
  • { flag: force ? "w" : "wx" } is the correct atomic write pattern. ✓
  • execFileSync array args throughout set-version.ts eliminates shell injection. The CodeQL "failure" on the checks page ran against an older commit (sha 95310a7); the current HEAD (eaae48e) is clean. ✓
  • releaseAllowedPaths now includes plugin manifests in git add — fixes a pre-existing staging gap. ✓
  • resolvePreviousTag double-path logic (handle re-runs cleanly) is correct. ✓
  • release.yml label categories with "Internal" catch-all are a good investment for clean user-facing changelogs. ✓

— Vai

jrusso1020 and others added 3 commits June 2, 2026 15:16
- draft-changelog.ts: replace existsSync+writeFileSync check-then-act with
  an atomic exclusive-write flag (flag: wx) to fix the js/file-system-race
  TOCTOU finding; overwrite only under --force (flag: w).
- set-version.ts: switch execSync shell-string git calls to execFileSync with
  argument arrays so the interpolated version/paths can never be interpreted
  by a shell, resolving the js/indirect-command-line-injection findings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The exclusive-write fix pushed writeReleaseNotes to cyclomatic 5 / CRAP 30.0
(fallow/high-crap-score, threshold 30.0). The '!force' guard in the catch is
redundant — EEXIST is only reachable under the 'wx' flag (force=false), since
'w' overwrites without throwing. Dropping it returns the function to cyclomatic
4 / CRAP 20 with identical behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jrusso1020
Copy link
Copy Markdown
Collaborator Author

Valid feedback, addressed in ceed4714.

  • MDX safety: added a separate MDX bullet renderer that escapes <, >, {, and } for Mintlify docs output only. Plain releases/vX.Y.Z.md Markdown remains unescaped.
  • Tests: added scripts/draft-changelog.test.ts and scripts/set-version.test.ts, covering arg parsing, category paths, catalog heuristic, breaking override, skip patterns, scoped/unscoped/PR-linked bullets, MDX escaping, and quoted/unquoted gitStatusPath.
  • CI: wired script tests into the main CI test job with bun run test:scripts.
  • Placeholder copy: replaced the production-looking generated summary with an obvious TODO comment.
  • Process docs: added notes for keeping releases/vX.Y.Z.md and the docs <Update> in sync, --force overwriting drafts, and the temporary future-tag compare URL.
  • Release-flow integration: stable bun run set-version <version> now enforces the changelog checkpoint automatically before mutating versions or creating the tag. It requires both releases/vX.Y.Z.md and a matching docs/changelog.mdx entry. Prereleases and --no-tag bumps skip the check; --skip-changelog-check is documented as an emergency-only bypass.

Verification run locally:

  • bun run test:scripts
  • bun run changelog:draft 0.6.53 --date 2026-06-02
  • bun run set-version 9.9.9 (confirmed the guard fails before version mutation)
  • bun run lint
  • bun run format:check
  • bunx fallow audit --base origin/main --fail-on-issues
  • npx mint validate
  • npx mint broken-links

CI is running on the updated head now.

Comment thread scripts/draft-changelog.ts Fixed
Comment thread scripts/draft-changelog.ts Fixed
Comment thread scripts/draft-changelog.ts Fixed
Comment thread scripts/draft-changelog.ts Fixed
Copy link
Copy Markdown
Collaborator

@vanceingalls vanceingalls left a comment

Choose a reason for hiding this comment

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

Both blockers addressed:

  1. escapeForMdx — applied to commit summary and scope in renderMdxCommitBullet; <, >, {, } in commit subjects will no longer break the Mintlify docs build. ✓
  2. Unit testsscripts/draft-changelog.test.ts + scripts/set-version.test.ts + test:scripts task wired into CI. ✓

LGTM.

— Vai

@jrusso1020 jrusso1020 merged commit 248f640 into main Jun 2, 2026
41 checks passed
@jrusso1020 jrusso1020 deleted the 06-02-changelog-process branch June 2, 2026 22:51
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.

4 participants