feat(docs): add changelog release workflow#1164
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
af1ff52 to
95310a7
Compare
miguel-heygen
left a comment
There was a problem hiding this comment.
#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.mdalready-exists guard (existsSync + failunless--force) prevents accidentally nuking a reviewed file. Clean.resolvePreviousTaghandles both "version tag exists" and "unreleased HEAD" cases; the catch →failwith explicit instruction to pass--fromis the right behavior on a repo with no prior tags.DOCS_MARKERas the prepend point inchangelog.mdxis cleaner than line-number arithmetic.rss: trueon 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.mdxusesbun run changelog:draft 0.4.24 --write— that version number looks like a leftover from an older docs template (the rest of the file uses0.4.24from before the 0.6.x era). Should be<version>or matched to the surrounding example.--forcesilently 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.
jrusso1020
left a comment
There was a problem hiding this comment.
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:504—writeReleaseNotesdidexistsSync(path) && !forcethenwriteFileSynca 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 theEEXISTcatch),"w"under--force. Removed the now-unusedexistsSyncimport. -
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}) forexecSync. Even with the semver regex guard, the canonical fix this rule asks for is to drop the shell: switched all git calls toexecFileSync("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 pushedwriteReleaseNotesto cyclomatic 5 / CRAP 30.0 (threshold 30.0). The!forcein the catch was redundant —EEXISTis 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
--writepath end-to-end: fresh write succeeds, second write without--forcecorrectly fails with "already exists" (the newEEXISTbranch),--forceoverwrites.
Notes
- The rest of the diff (
.github/release.ymlcategories incl. the required"*"catch-all; docschangelog.mdx/changelog-process.mdx/release-channels.mdx/docs.json) read cleanly; docs passedmint validate/broken-linksin CI. - Remaining
BLOCKEDstate is purely theREVIEW_REQUIREDreviewer 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)
vanceingalls
left a comment
There was a problem hiding this comment.
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. ✓execFileSyncarray args throughoutset-version.tseliminates shell injection. The CodeQL "failure" on the checks page ran against an older commit (sha95310a7); the current HEAD (eaae48e) is clean. ✓releaseAllowedPathsnow includes plugin manifests ingit add— fixes a pre-existing staging gap. ✓resolvePreviousTagdouble-path logic (handle re-runs cleanly) is correct. ✓release.ymllabel categories with "Internal" catch-all are a good investment for clean user-facing changelogs. ✓
— Vai
- 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>
eaae48e to
ceed471
Compare
|
Valid feedback, addressed in
Verification run locally:
CI is running on the updated head now. |
ceed471 to
2b2bd27
Compare
vanceingalls
left a comment
There was a problem hiding this comment.
Both blockers addressed:
escapeForMdx— applied to commit summary and scope inrenderMdxCommitBullet;<,>,{,}in commit subjects will no longer break the Mintlify docs build. ✓- Unit tests —
scripts/draft-changelog.test.ts+scripts/set-version.test.ts+test:scriptstask wired into CI. ✓
LGTM.
— Vai

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
v0.6.52entry.releases/vX.Y.Z.mdas the reviewed GitHub Release body source of truth.bun run changelog:draft <version>to generate release notes and Mintlify update blocks from conventional commits..github/release.yml.bun run set-version <version>for stable releases, with prerelease/no-tag skips and an emergency--skip-changelog-checkescape hatch.Test plan
Manual checks:
bun run test:scriptsbun run changelog:draft 0.6.53 --date 2026-06-02bun run set-version 9.9.9(verified the stable-release changelog guard fails before version mutation)bun run lintbun run format:checkbunx fallow audit --base origin/main --fail-on-issuesnpx mint validatenpx mint broken-links