From 2f5b6a9b4884ebf2a8170eb26289dabaef6e34e4 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 5 Aug 2026 22:22:30 -0400 Subject: [PATCH] fix(#3001): indent continuation lines in serializeChangelog (#3101) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(#3001): indent continuation lines in serializeChangelog serializeChangelog interpolated bullet bodies verbatim into a single - ${body} (#${pr}) line. Any embedded newline became a column-0 line; parseChangelog's continuation-fold (/^\s+/) didn't pick it up, so flushBullet terminated the bullet early — dropping the continuation text and the (#NNNN) PR trailer (recorded as pr: null). The round-trip property serialize(IR) → parse(text) === IR was false for any body containing \n. Fix: indent continuation lines (body.replace(/\n/g, '\n ')) so the parser folds them correctly. Round-trip test asserts both paragraphs' content AND the PR number survive. * chore(#3001): backfill changeset PR number 3101 --------- Co-authored-by: sim --- .changeset/nimble-hawks-hop.md | 5 +++++ scripts/changeset/serialize.cjs | 6 +++++- tests/changeset-serialize.test.cjs | 29 +++++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 .changeset/nimble-hawks-hop.md diff --git a/.changeset/nimble-hawks-hop.md b/.changeset/nimble-hawks-hop.md new file mode 100644 index 000000000..2b32ed999 --- /dev/null +++ b/.changeset/nimble-hawks-hop.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 3101 +--- +**Multi-paragraph changeset bodies no longer truncate and lose their PR trailer** — `serializeChangelog` wrote bullet bodies verbatim, so an embedded newline became a column-0 line that `parseChangelog` treated as the end of the bullet, silently dropping the continuation and the `(#NNNN)` trailer. Continuation lines are now indented so the round-trip preserves content and attribution. (#3001) diff --git a/scripts/changeset/serialize.cjs b/scripts/changeset/serialize.cjs index 418a59ba2..54db655a0 100644 --- a/scripts/changeset/serialize.cjs +++ b/scripts/changeset/serialize.cjs @@ -26,7 +26,11 @@ function serializeChangelog(ir) { lines.push(`### ${section.type}`); lines.push(''); for (const b of section.bullets) { - lines.push(`- ${b.body} (#${b.pr})`); + // #3001: indent continuation lines so parseChangelog's continuation-fold + // (/^\s+/) picks them up instead of terminating the bullet at the first + // blank line. A multi-paragraph body round-trips with content preserved. + const body = b.body.replace(/\n/g, '\n '); + lines.push(`- ${body} (#${b.pr})`); } lines.push(''); } diff --git a/tests/changeset-serialize.test.cjs b/tests/changeset-serialize.test.cjs index a3a64bab0..b9428ff23 100644 --- a/tests/changeset-serialize.test.cjs +++ b/tests/changeset-serialize.test.cjs @@ -152,3 +152,32 @@ describe('changeset serialize: multi-section + prior content (#2975)', () => { assert.equal(back.releases[1].sections[0].bullets[0].pr, 100); }); }); + +// ─── #3001: multi-paragraph body round-trip ──────────────────────────────── + +describe('#3001: multi-paragraph changeset body round-trips without truncation', () => { + test('a body containing \\n\\n retains content from both paragraphs AND its PR number', () => { + const ir = { + releaseHeader: { version: '1.42.0', date: '2026-08-05' }, + sections: [{ + type: 'Fixed', + bullets: [{ + pr: 2595, + body: 'First paragraph, load-bearing.\n\nSecond paragraph, also load-bearing.', + }], + }], + priorChangelog: null, + }; + const text = serializeChangelog(ir); + const back = parseChangelog(text); + const bullet = back.releases[0].sections[0].bullets[0]; + // PR trailer must survive (was lost pre-fix because the trailer was on the dropped side). + assert.equal(bullet.pr, 2595, + `PR number must survive round-trip; got: ${bullet.pr}`); + // Content from BOTH paragraphs must be present (was truncated pre-fix). + assert.ok(bullet.body.includes('First paragraph'), + `first paragraph must survive; got: ${JSON.stringify(bullet.body)}`); + assert.ok(bullet.body.includes('Second paragraph'), + `second paragraph must survive; got: ${JSON.stringify(bullet.body)}`); + }); +});