@@ -67,8 +67,34 @@ export class PullRequestGenerator {
67
67
* Generate enhanced PR body with rich formatting, badges, and release notes
68
68
*/
69
69
async generateBody ( group : UpdateGroup ) : Promise < string > {
70
+ // Count different types of updates
71
+ const packageJsonCount = group . updates . filter ( u => u . file === 'package.json' ) . length
72
+ const dependencyFileCount = group . updates . filter ( u =>
73
+ ( u . file . includes ( '.yaml' ) || u . file . includes ( '.yml' ) ) && ! u . file . includes ( '.github/workflows/' ) ,
74
+ ) . length
75
+ const githubActionsCount = group . updates . filter ( u => u . file . includes ( '.github/workflows/' ) ) . length
76
+ const composerCount = group . updates . filter ( u =>
77
+ u . file . endsWith ( 'composer.json' ) || u . file . endsWith ( 'composer.lock' ) ,
78
+ ) . length
79
+
70
80
let body = `This PR contains the following updates:\n\n`
71
81
82
+ // Add summary table
83
+ if ( group . updates . length > 1 ) {
84
+ body += `## 📦 Package Updates Summary\n\n`
85
+ body += `| Type | Count |\n`
86
+ body += `|------|-------|\n`
87
+ if ( packageJsonCount > 0 )
88
+ body += `| 📦 NPM Packages | ${ packageJsonCount } |\n`
89
+ if ( dependencyFileCount > 0 )
90
+ body += `| 🔧 System Dependencies | ${ dependencyFileCount } |\n`
91
+ if ( githubActionsCount > 0 )
92
+ body += `| 🚀 GitHub Actions | ${ githubActionsCount } |\n`
93
+ if ( composerCount > 0 )
94
+ body += `| 🎼 Composer Packages | ${ composerCount } |\n`
95
+ body += `| **Total** | **${ group . updates . length } ** |\n\n`
96
+ }
97
+
72
98
// Separate updates by type
73
99
const packageJsonUpdates = group . updates . filter ( update =>
74
100
update . file === 'package.json' ,
@@ -116,7 +142,10 @@ export class PullRequestGenerator {
116
142
117
143
// Package.json updates table (with full badges)
118
144
if ( packageJsonUpdates . length > 0 ) {
119
- body += `### npm Dependencies\n\n`
145
+ body += `## 📦 npm Dependencies\n\n`
146
+ if ( packageJsonUpdates . length > 1 ) {
147
+ body += `*${ packageJsonUpdates . length } packages will be updated*\n\n`
148
+ }
120
149
body += `| Package | Change | Age | Adoption | Passing | Confidence |\n`
121
150
body += `|---|---|---|---|---|---|\n`
122
151
@@ -237,11 +266,17 @@ export class PullRequestGenerator {
237
266
body += `\n`
238
267
}
239
268
240
- // Dependency files table (simplified, without badges )
269
+ // Dependency files table (enhanced with more information )
241
270
if ( dependencyFileUpdates . length > 0 ) {
242
- body += `### Launchpad/pkgx Dependencies\n\n`
243
- body += `| Package | Change | File | Status |\n`
244
- body += `|---|---|---|---|\n`
271
+ body += `## 🔧 System Dependencies\n\n`
272
+
273
+ const uniqueFiles = [ ...new Set ( dependencyFileUpdates . map ( u => u . file ) ) ]
274
+ if ( dependencyFileUpdates . length > 1 ) {
275
+ body += `*${ dependencyFileUpdates . length } packages will be updated across ${ uniqueFiles . length } file(s): ${ uniqueFiles . map ( f => `\`${ f . split ( '/' ) . pop ( ) } \`` ) . join ( ', ' ) } *\n\n`
276
+ }
277
+
278
+ body += `| Package | Change | Type | File | Links |\n`
279
+ body += `|---|---|---|---|---|\n`
245
280
246
281
for ( const update of dependencyFileUpdates ) {
247
282
// Handle special case: bun.sh -> bun.com
@@ -253,50 +288,72 @@ export class PullRequestGenerator {
253
288
: `https://pkgx.com/pkg/${ encodeURIComponent ( update . name ) } `
254
289
const packageCell = `[${ displayName } ](${ packageUrl } )`
255
290
256
- // Simple version change display
257
- const change = `\`${ update . currentVersion } \` -> \`${ update . newVersion } \``
291
+ // Enhanced version change display with update type
292
+ const updateType = this . getUpdateType ( update . currentVersion , update . newVersion )
293
+ const typeEmoji = updateType === 'major' ? '🔴' : updateType === 'minor' ? '🟡' : '🟢'
294
+ const change = `\`${ update . currentVersion } \` → \`${ update . newVersion } \``
258
295
259
- // File reference
296
+ // File reference with link to actual file
260
297
const fileName = update . file . split ( '/' ) . pop ( ) || update . file
298
+ const fileCell = this . config ?. repository ?. owner && this . config ?. repository ?. name
299
+ ? `[\`${ fileName } \`](https://github.com/${ this . config . repository . owner } /${ this . config . repository . name } /blob/main/${ update . file } )`
300
+ : `\`${ fileName } \``
301
+
302
+ // Enhanced links column
303
+ let linksCell = `📦 [pkgx](${ packageUrl } )`
304
+ if ( update . name . includes ( '.org' ) || update . name . includes ( '.net' ) || update . name . includes ( '.com' ) ) {
305
+ const domain = update . name . split ( '/' ) [ 0 ] || update . name
306
+ linksCell += ` | 🌐 [${ domain } ](https://${ domain } )`
307
+ }
261
308
262
- // Status (simple)
263
- const status = '✅ Available'
264
-
265
- body += `| ${ packageCell } | ${ change } | ${ fileName } | ${ status } |\n`
309
+ body += `| ${ packageCell } | ${ change } | ${ typeEmoji } ${ updateType } | ${ fileCell } | ${ linksCell } |\n`
266
310
}
267
311
268
312
body += `\n`
269
313
}
270
314
271
- // GitHub Actions table (simplified, without badges, deduplicated )
315
+ // GitHub Actions table (enhanced with more information )
272
316
if ( uniqueGithubActionsUpdates . length > 0 ) {
273
- body += `### GitHub Actions\n\n`
274
- body += `| Action | Change | File | Status |\n`
275
- body += `|---|---|---|---|\n`
317
+ body += `## 🚀 GitHub Actions\n\n`
318
+
319
+ if ( uniqueGithubActionsUpdates . length > 1 ) {
320
+ body += `*${ uniqueGithubActionsUpdates . length } actions will be updated*\n\n`
321
+ }
322
+
323
+ body += `| Action | Change | Type | Files | Links |\n`
324
+ body += `|---|---|---|---|---|\n`
276
325
277
326
for ( const update of uniqueGithubActionsUpdates ) {
278
327
// Generate action link
279
328
const actionUrl = `https://github.com/${ update . name } `
280
329
const actionCell = `[${ update . name } ](${ actionUrl } )`
281
330
282
- // Simple version change display
283
- const change = `\`${ update . currentVersion } \` -> \`${ update . newVersion } \``
331
+ // Enhanced version change display with update type
332
+ const updateType = this . getUpdateType ( update . currentVersion , update . newVersion )
333
+ const typeEmoji = updateType === 'major' ? '🔴' : updateType === 'minor' ? '🟡' : '🟢'
334
+ const change = `\`${ update . currentVersion } \` → \`${ update . newVersion } \``
284
335
285
- // File reference with GitHub links (may be multiple files now)
336
+ // Enhanced file reference with proper GitHub links
286
337
const fileLinks = update . file . includes ( ', ' )
287
338
? update . file . split ( ', ' ) . map ( ( f ) => {
288
339
const fileName = f . split ( '/' ) . pop ( ) || f
289
- return `[${ fileName } ](../${ f } )`
340
+ return this . config ?. repository ?. owner && this . config ?. repository ?. name
341
+ ? `[\`${ fileName } \`](https://github.com/${ this . config . repository . owner } /${ this . config . repository . name } /blob/main/${ f } )`
342
+ : `\`${ fileName } \``
290
343
} ) . join ( ', ' )
291
344
: ( ( ) => {
292
345
const fileName = update . file . split ( '/' ) . pop ( ) || update . file
293
- return `[${ fileName } ](../${ update . file } )`
346
+ return this . config ?. repository ?. owner && this . config ?. repository ?. name
347
+ ? `[\`${ fileName } \`](https://github.com/${ this . config . repository . owner } /${ this . config . repository . name } /blob/main/${ update . file } )`
348
+ : `\`${ fileName } \``
294
349
} ) ( )
295
350
296
- // Status (simple)
297
- const status = '✅ Available'
351
+ // Enhanced links column
352
+ const releasesUrl = `https://github.com/${ update . name } /releases`
353
+ const compareUrl = `https://github.com/${ update . name } /compare/${ update . currentVersion } ...${ update . newVersion } `
354
+ const linksCell = `📋 [releases](${ releasesUrl } ) | 📊 [compare](${ compareUrl } )`
298
355
299
- body += `| ${ actionCell } | ${ change } | ${ fileLinks } | ${ status } |\n`
356
+ body += `| ${ actionCell } | ${ change } | ${ typeEmoji } ${ updateType } | ${ fileLinks } | ${ linksCell } |\n`
300
357
}
301
358
302
359
body += `\n`
@@ -376,7 +433,7 @@ export class PullRequestGenerator {
376
433
body += `</details>\n\n`
377
434
}
378
435
379
- // Process dependency file updates with simple release notes (no duplicates with package.json)
436
+ // Process dependency file updates with enhanced release notes and file links
380
437
const dependencyOnlyUpdates = dependencyFileUpdates . filter ( depUpdate =>
381
438
! packageJsonUpdates . some ( pkgUpdate =>
382
439
pkgUpdate . name . replace ( / \s * \( d e v \) $ / , '' ) . replace ( / \s * \( p e e r \) $ / , '' ) . replace ( / \s * \( o p t i o n a l \) $ / , '' ) === depUpdate . name ,
@@ -391,11 +448,24 @@ export class PullRequestGenerator {
391
448
body += `<summary>${ displayName } </summary>\n\n`
392
449
body += `**${ update . currentVersion } -> ${ update . newVersion } **\n\n`
393
450
451
+ // Add file reference with link to the dependency file
452
+ if ( update . file ) {
453
+ const fileName = update . file . split ( '/' ) . pop ( ) || update . file
454
+ body += `📁 **File**: [\`${ fileName } \`](https://github.com/${ this . config . repository . owner } /${ this . config . repository . name } /blob/main/${ update . file } )\n\n`
455
+ }
456
+
457
+ // Add appropriate links based on package type
394
458
if ( update . name === 'bun.sh' ) {
395
- body += `Visit [bun.sh](https://bun.sh) for more information about Bun releases.\n\n`
459
+ body += `🔗 **Release Notes**: [bun.sh](https://bun.sh)\n\n`
460
+ }
461
+ else if ( update . name . includes ( '.org' ) || update . name . includes ( '.net' ) || update . name . includes ( '.com' ) ) {
462
+ // For domain-style packages, link to pkgx and try to provide the official site
463
+ const domain = update . name . split ( '/' ) [ 0 ] || update . name
464
+ body += `🔗 **Package Info**: [pkgx.com](https://pkgx.com/pkg/${ encodeURIComponent ( update . name ) } )\n\n`
465
+ body += `🌐 **Official Site**: [${ domain } ](https://${ domain } )\n\n`
396
466
}
397
467
else {
398
- body += `Visit [pkgx.com](https://pkgx.com/pkg/${ encodeURIComponent ( update . name ) } ) for more information. \n\n`
468
+ body += `🔗 **Package Info**: [pkgx.com](https://pkgx.com/pkg/${ encodeURIComponent ( update . name ) } )\n\n`
399
469
}
400
470
401
471
body += `</details>\n\n`
@@ -678,4 +748,51 @@ export class PullRequestGenerator {
678
748
679
749
return result
680
750
}
751
+
752
+ /**
753
+ * Determine update type between two versions
754
+ */
755
+ private getUpdateType ( currentVersion : string , newVersion : string ) : 'major' | 'minor' | 'patch' {
756
+ // Remove any prefixes like ^, ~, >=, v, @, etc.
757
+ const cleanCurrent = currentVersion . replace ( / ^ [ v ^ ~ > = < @ ] + / , '' )
758
+ const cleanNew = newVersion . replace ( / ^ [ v ^ ~ > = < @ ] + / , '' )
759
+
760
+ const currentParts = cleanCurrent . split ( '.' ) . map ( ( part ) => {
761
+ const num = Number ( part )
762
+ return Number . isNaN ( num ) ? 0 : num
763
+ } )
764
+ const newParts = cleanNew . split ( '.' ) . map ( ( part ) => {
765
+ const num = Number ( part )
766
+ return Number . isNaN ( num ) ? 0 : num
767
+ } )
768
+
769
+ // Ensure we have at least major.minor.patch structure
770
+ while ( currentParts . length < 3 ) currentParts . push ( 0 )
771
+ while ( newParts . length < 3 ) newParts . push ( 0 )
772
+
773
+ // Compare major version
774
+ if ( newParts [ 0 ] > currentParts [ 0 ] ) {
775
+ return 'major'
776
+ }
777
+
778
+ // Compare minor version
779
+ if ( newParts [ 0 ] === currentParts [ 0 ] && newParts [ 1 ] > currentParts [ 1 ] ) {
780
+ return 'minor'
781
+ }
782
+
783
+ // Everything else is patch
784
+ return 'patch'
785
+ }
786
+
787
+ /**
788
+ * Generate a description of the version change
789
+ */
790
+ private getVersionChangeDescription ( currentVersion : string , newVersion : string , updateType : 'major' | 'minor' | 'patch' ) : string {
791
+ const descriptions = {
792
+ major : '🔴 Breaking changes possible' ,
793
+ minor : '🟡 New features added' ,
794
+ patch : '🟢 Bug fixes & patches' ,
795
+ }
796
+ return descriptions [ updateType ]
797
+ }
681
798
}
0 commit comments