@@ -39,29 +39,70 @@ function parsePrBody(body) {
3939 const seen = new Set ( ) ;
4040 const prPattern = / \[ # ( \d + ) \] \( ( [ ^ ) ] + ) \) / ;
4141
42+ // A standalone dependency-bump list item, e.g. "`@trigger.dev/core@4.5.0-rc.7`"
43+ // or "trigger.dev@4.5.0-rc.7". These normally appear nested under
44+ // "Updated dependencies:" (and so get swallowed into that item below), but we
45+ // guard against them showing up on their own too. Crucially this only matches
46+ // a line that is *entirely* a package bump, so a real changeset that merely
47+ // begins with a package name (e.g. "`@trigger.dev/sdk` now bundles ...") is
48+ // kept.
49+ const depBumpPattern = / ^ ` ? (?: @ t r i g g e r \. d e v \/ [ \w - ] + | t r i g g e r \. d e v ) @ [ \w . + - ] + ` ? $ / ;
50+
51+ // Group lines into top-level list items. A top-level item starts with a bullet
52+ // at column 0 ("- " / "* "); every indented or blank line below it (sub-bullets,
53+ // fenced code blocks, continuation paragraphs) belongs to that same item.
54+ const items = [ ] ;
55+ let current = null ;
56+
57+ const flush = ( ) => {
58+ if ( ! current ) return ;
59+ while ( current . length > 1 && current [ current . length - 1 ] . trim ( ) === "" ) {
60+ current . pop ( ) ;
61+ }
62+ items . push ( current ) ;
63+ current = null ;
64+ } ;
65+
4266 for ( const line of body . split ( "\n" ) ) {
43- const trimmed = line . trim ( ) ;
44- if ( ! trimmed . startsWith ( "- " ) && ! trimmed . startsWith ( "* " ) ) continue ;
67+ const isTopLevelBullet = / ^ [ - * ] \s + / . test ( line ) ;
68+ if ( isTopLevelBullet ) {
69+ flush ( ) ;
70+ current = [ line ] ;
71+ } else if ( current ) {
72+ if ( line . trim ( ) === "" || / ^ \s / . test ( line ) ) {
73+ current . push ( line ) ;
74+ } else {
75+ // A non-indented, non-blank, non-bullet line (heading or prose) ends the item
76+ flush ( ) ;
77+ }
78+ }
79+ }
80+ flush ( ) ;
4581
46- let text = trimmed . replace ( / ^ [ - * ] \s + / , "" ) . trim ( ) ;
47- if ( ! text ) continue ;
82+ for ( const itemLines of items ) {
83+ const headLine = itemLines [ 0 ] . replace ( / ^ [ - * ] \s + / , "" ) . trim ( ) ;
84+ if ( ! headLine ) continue ;
4885
49- // Skip dependency-only updates (e.g. "Updated dependencies:" or "@trigger.dev/core@4.4.2")
50- if ( text . startsWith ( "Updated dependencies" ) ) continue ;
51- if ( text . startsWith ( "`@trigger.dev/" ) ) continue ;
52- if ( text . startsWith ( "@trigger.dev/" ) ) continue ;
53- if ( text . startsWith ( "`trigger.dev@" ) ) continue ;
54- if ( text . startsWith ( "trigger.dev@" ) ) continue ;
86+ // Skip dependency-only updates
87+ if ( headLine . startsWith ( "Updated dependencies" ) ) continue ;
88+ if ( depBumpPattern . test ( headLine ) ) continue ;
5589
56- const prMatch = trimmed . match ( prPattern ) ;
90+ // Deduplicate by PR number (the changeset link lives on the head line)
91+ const prMatch = itemLines [ 0 ] . match ( prPattern ) ;
5792 if ( prMatch ) {
5893 const prNumber = prMatch [ 1 ] ;
5994 if ( seen . has ( prNumber ) ) continue ;
6095 seen . add ( prNumber ) ;
6196 }
6297
63- // Categorize
64- const lower = text . toLowerCase ( ) ;
98+ // Reconstruct the full item: head line + dedented continuation lines, so
99+ // code blocks and sub-bullets survive. Continuation under a "- " item is
100+ // indented 4 spaces; strip up to 4 to bring it back to the base level.
101+ const continuation = itemLines . slice ( 1 ) . map ( ( l ) => l . replace ( / ^ { 1 , 4 } / , "" ) ) ;
102+ const text = [ headLine , ...continuation ] . join ( "\n" ) . replace ( / \s + $ / , "" ) ;
103+
104+ // Categorize off the head line
105+ const lower = headLine . toLowerCase ( ) ;
65106 let type = "improvement" ;
66107 if ( lower . startsWith ( "fix" ) || lower . includes ( "bug fix" ) ) {
67108 type = "fix" ;
@@ -206,6 +247,12 @@ function parseFrontmatter(content) {
206247
207248// --- Format the enhanced PR body ---
208249
250+ // Render an entry as a list item, re-indenting continuation lines (code blocks,
251+ // sub-bullets, paragraphs) by 2 spaces so they stay inside the "- " bullet.
252+ function renderEntry ( text ) {
253+ return `- ${ text . replace ( / \n / g, "\n " ) } ` ;
254+ }
255+
209256function formatPrBody ( { version, packageEntries, serverEntries, rawBody } ) {
210257 const lines = [ ] ;
211258
@@ -238,7 +285,7 @@ function formatPrBody({ version, packageEntries, serverEntries, rawBody }) {
238285 // Breaking changes
239286 if ( breaking . length > 0 || serverBreaking . length > 0 ) {
240287 lines . push ( "## Breaking changes" ) ;
241- for ( const entry of [ ...breaking , ...serverBreaking ] ) lines . push ( `- ${ entry . text } ` ) ;
288+ for ( const entry of [ ...breaking , ...serverBreaking ] ) lines . push ( renderEntry ( entry . text ) ) ;
242289 lines . push ( "" ) ;
243290 }
244291
@@ -247,22 +294,22 @@ function formatPrBody({ version, packageEntries, serverEntries, rawBody }) {
247294 lines . push ( "## Highlights" ) ;
248295 lines . push ( "" ) ;
249296 for ( const entry of features ) {
250- lines . push ( `- ${ entry . text } ` ) ;
297+ lines . push ( renderEntry ( entry . text ) ) ;
251298 }
252299 lines . push ( "" ) ;
253300 }
254301
255302 // Improvements
256303 if ( improvements . length > 0 ) {
257304 lines . push ( "## Improvements" ) ;
258- for ( const entry of improvements ) lines . push ( `- ${ entry . text } ` ) ;
305+ for ( const entry of improvements ) lines . push ( renderEntry ( entry . text ) ) ;
259306 lines . push ( "" ) ;
260307 }
261308
262309 // Bug fixes
263310 if ( fixes . length > 0 ) {
264311 lines . push ( "## Bug fixes" ) ;
265- for ( const entry of fixes ) lines . push ( `- ${ entry . text } ` ) ;
312+ for ( const entry of fixes ) lines . push ( renderEntry ( entry . text ) ) ;
266313 lines . push ( "" ) ;
267314 }
268315
@@ -274,9 +321,7 @@ function formatPrBody({ version, packageEntries, serverEntries, rawBody }) {
274321 lines . push ( "These changes affect the self-hosted Docker image and Trigger.dev Cloud:" ) ;
275322 lines . push ( "" ) ;
276323 for ( const entry of allServer ) {
277- // Indent continuation lines so multi-line entries stay inside the list item
278- const indented = entry . text . replace ( / \n / g, "\n " ) ;
279- lines . push ( `- ${ indented } ` ) ;
324+ lines . push ( renderEntry ( entry . text ) ) ;
280325 }
281326 lines . push ( "" ) ;
282327 }
0 commit comments