* feat(#2975): adopt changeset-fragment workflow to eliminate CHANGELOG conflicts
Two PRs that both edit `### Fixed` in CHANGELOG.md always conflict on merge.
Recently bit on #2960/#2972 in the same session — fix-the-conflict-and-rebase
tax. Replace the shared-file model with per-PR fragment files that never
share lines.
Implementation built TDD per #2975, vertical slices with structured-IR
assertions throughout:
scripts/changeset/parse.cjs - fragment text → typed record + frozen
FRAGMENT_ERROR enum (8 tests)
scripts/changeset/render.cjs - fragments → structured IR with
Keep-a-Changelog section ordering
(2 tests)
scripts/changeset/serialize.cjs - IR ↔ markdown round-trip pair
(parse(serialize(ir)) === ir,
3 tests)
scripts/changeset/cli.cjs - file-I/O wrapper with --json mode;
reads .changeset/, folds into
CHANGELOG.md, deletes consumed
fragments. Idempotent. (1 test)
scripts/changeset/lint.cjs - pure verdict (changedFiles, labels)
→ { ok, reason } via LINT_REASON
enum. Honors `no-changelog` label.
(5 tests)
scripts/changeset/new.cjs - fragment scaffolder with random
adjective-noun-noun filename. Tests
assert via parseFragment round-trip.
(3 tests)
Total: 22 tests, all assertions on typed structured fields. No regex on
text, no String#includes on file content. Lint clean across 356 test files.
Supporting:
.changeset/README.md - format spec + workflow docs
.changeset/eager-hawks-rally.md - dogfood fragment for THIS PR (will
be the first thing the new release
tool consumes)
.github/workflows/changeset-required.yml
- CI: every PR runs lint.cjs
package.json - npm run changeset, changelog:render,
lint:changeset
CONTRIBUTING.md - new "CHANGELOG Entries — Drop a
Fragment" section between PR
Guidelines and Testing Standards
Closes#2975
* fix(#2975): address CodeRabbit findings on changeset workflow
7 valid findings (4 Major, 3 Minor); all addressed:
scripts/changeset/parse.cjs
- Preserve fragment body verbatim. Previously body.trim() ate
intentional leading whitespace (code blocks, etc.); now trim() is
used only for the emptiness check, and a single trailing newline
is stripped (the editor-added one) so well-formed fragments
round-trip byte-for-byte. Added a regression test asserting a
code-block-leading body is preserved.
scripts/changeset/cli.cjs
- Validate flag values during argument parsing. parseArgs now returns
{ ok, opts | error }; rejects `--repo` etc. with no following value
or with another flag as the value. main() surfaces the error
message before exiting 2.
- Handle post-write fragment-deletion failures. After CHANGELOG.md
is written, any unlink failure is captured into a structured
deleteFailures list with reason 'fail_fragment_delete'; cmdRender
returns exitCode=1 with the partial-failure detail instead of
leaving the changelog updated and fragments behind (which would
cause double-consumption on rerun).
scripts/changeset/lint.cjs
- Treat CHANGELOG.md as a linted user-facing path. Direct edits to
CHANGELOG.md (the bypass route around the new workflow) now fail
the lint with FAIL_MISSING_FRAGMENT. Added a regression test for
that case.
- Use cp.execFileSync instead of cp.execSync for the git diff call.
Eliminates the shell-interpolation surface on GITHUB_BASE_REF;
git's own arg parser remains the validator.
scripts/changeset/new.cjs
- Atomic fragment creation. existsSync() + writeFileSync was racy
under concurrent invocations. Now writeFileSync uses { flag: 'wx' }
which fails EEXIST on collision; the random-name retry loop
catches EEXIST and re-rolls. Throws explicitly after 16 attempts
rather than silently overwriting.
.changeset/README.md
- Add language tag `md` to the format example fence (markdownlint
MD040).
All 25 changeset tests pass; lint clean (356 test files, 0 violations).
* fix(#2975): sanitize --type and validate flag values in new.cjs (CR fixes)
Two CR findings on scripts/changeset/new.cjs:
1. (Minor) `type` was embedded in frontmatter without sanitization. A
newline in the value (e.g. `--type 'Fixed\ntype: Added'`) would
corrupt the fragment. scaffoldFragment now validates `type` against
the Keep-a-Changelog ALLOWED_TYPES set BEFORE writing — same set
parse.cjs uses on consume. Throws with a typed error referencing
the allowed values; tests cover the newline case + 4 other
non-allowed values.
2. (Minor) `--repo` (and other value-taking flags) without a value
silently set opts.repo to undefined, which produced a cryptic
ERR_INVALID_ARG_TYPE deep inside path.join. parseArgs now mirrors
the cli.cjs convention: returns { ok, opts | error }, validates
that the next token exists and is not itself another flag, and
surfaces a precise "missing value for --repo" message before exit.
Added 3 tests: missing-trailing-value, flag-as-value, well-formed.
29 tests pass across the changeset suite (4 new regression tests).