* 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 <sim@local>
This commit is contained in:
5
.changeset/nimble-hawks-hop.md
Normal file
5
.changeset/nimble-hawks-hop.md
Normal file
@@ -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)
|
||||
@@ -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('');
|
||||
}
|
||||
|
||||
@@ -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)}`);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user