1
+ /* eslint-disable no-console */
1
2
import type { DashboardData , PackageFile , PullRequest } from '../types'
2
3
3
4
export class DashboardGenerator {
@@ -42,10 +43,10 @@ export class DashboardGenerator {
42
43
/**
43
44
* Generate the default header section
44
45
*/
45
- private generateDefaultHeader ( data : DashboardData ) : string {
46
- const portalUrl = `https://developer.mend.io/github/${ data . repository . owner } /${ data . repository . name } `
46
+ private generateDefaultHeader ( _data : DashboardData ) : string {
47
+ // const portalUrl = `https://developer.mend.io/github/${data.repository.owner}/${data.repository.name}`
47
48
48
- return `This issue lists Buddy Bot updates and detected dependencies. Read the [Dependency Dashboard](https://buddy-bot.sh/features/dependency-dashboard) docs to learn more.<br/>[View this repository on the Mend.io Web Portal]( ${ portalUrl } ).
49
+ return `This issue lists Buddy Bot updates and detected dependencies. Read the [Dependency Dashboard](https://buddy-bot.sh/features/dependency-dashboard) docs to learn more.
49
50
50
51
`
51
52
}
@@ -72,6 +73,7 @@ The following updates have all been created. To force a retry/rebase of any, cli
72
73
73
74
section += ` - [ ] <!-- rebase-branch=${ rebaseBranch } -->[${ pr . title } ](${ relativeUrl } )`
74
75
76
+ // Show clean package names like Renovate does (without version info)
75
77
if ( packageInfo . length > 0 ) {
76
78
section += ` (\`${ packageInfo . join ( '`, `' ) } \`)`
77
79
}
@@ -247,58 +249,219 @@ The following updates have all been created. To force a retry/rebase of any, cli
247
249
}
248
250
249
251
/**
250
- * Extract package names from PR title or body
252
+ * Extract package names from PR title or body (like Renovate does)
251
253
*/
252
254
private extractPackageInfo ( pr : PullRequest ) : string [ ] {
253
255
const packages : string [ ] = [ ]
254
256
255
- // Try to extract from title patterns like "update dependency react to v18"
256
- const titleMatch = pr . title . match ( / u p d a t e .* ?d e p e n d e n c y \s + ( \w + ) / i)
257
- if ( titleMatch ) {
258
- packages . push ( titleMatch [ 1 ] )
257
+ // Add logging for debugging the real issue
258
+ const isTargetPR = pr . title . includes ( 'update all non-major dependencies' )
259
+ if ( isTargetPR ) {
260
+ console . log ( `\n🔍 DEBUG: Extracting from PR #${ pr . number } ` )
261
+ console . log ( `📝 Title: "${ pr . title } "` )
262
+ console . log ( `📄 Body preview: "${ pr . body . substring ( 0 , 200 ) } ..."\n` )
259
263
}
260
264
261
- // Extract from the enhanced PR body format
262
- // Look for table entries like: | [package-name](url) | version change | badges |
263
- const tableMatches = pr . body . match ( / \| \s * \[ ( [ ^ \] ] + ) \] / g)
264
- if ( tableMatches ) {
265
- for ( const match of tableMatches ) {
266
- const packageMatch = match . match ( / \| \s * \[ ( [ ^ \] ] + ) \] / )
267
- if ( packageMatch ) {
268
- const packageName = packageMatch [ 1 ]
269
- // Skip if it's a URL, badge, or contains special characters that indicate it's not a package name
270
- if ( ! packageName . includes ( '://' )
271
- && ! packageName . includes ( 'Compare Source' )
272
- && ! packageName . includes ( 'badge' )
273
- && ! packageName . includes ( '!' )
274
- && ! packageName . startsWith ( '[![' )
275
- && ! packages . includes ( packageName ) ) {
276
- packages . push ( packageName )
265
+ // Pattern 1: Extract from common dependency update titles
266
+ // Examples: "chore(deps): update dependency react to v18"
267
+ // "chore(deps): update all non-major dependencies"
268
+ // "update @types/node to v20"
269
+ const titlePatterns = [
270
+ / u p d a t e .* ?d e p e n d e n c y \s + ( \S + ) / i,
271
+ / u p d a t e \s + ( \S + ) \s + t o \s + v ? \d + / i,
272
+ / b u m p \s + ( \S + ) \s + f r o m / i,
273
+ ]
274
+
275
+ for ( const pattern of titlePatterns ) {
276
+ const match = pr . title . match ( pattern )
277
+ if ( match && match [ 1 ] && ! packages . includes ( match [ 1 ] ) ) {
278
+ packages . push ( match [ 1 ] )
279
+ if ( isTargetPR )
280
+ console . log ( `✅ Title match: "${ match [ 1 ] } "` )
281
+ }
282
+ }
283
+
284
+ // Pattern 2: Extract from table format in PR body - handle all table sections
285
+ // Look for different table types: npm Dependencies, Launchpad/pkgx Dependencies, GitHub Actions
286
+
287
+ // Split the body into sections and process each table
288
+ const tableSections = [
289
+ // npm Dependencies table
290
+ { name : 'npm' , pattern : / # # # n p m D e p e n d e n c i e s [ \s \S ] * ?(? = # # # | \n \n - - - | z ) / i } ,
291
+ // Launchpad/pkgx Dependencies table
292
+ { name : 'pkgx' , pattern : / # # # L a u n c h p a d \/ p k g x D e p e n d e n c i e s [ \s \S ] * ?(? = # # # | \n \n - - - | z ) / i } ,
293
+ // GitHub Actions table
294
+ { name : 'actions' , pattern : / # # # G i t H u b A c t i o n s [ \s \S ] * ?(? = # # # | \n \n - - - | z ) / i } ,
295
+ ]
296
+
297
+ for ( const section of tableSections ) {
298
+ const sectionMatch = pr . body . match ( section . pattern )
299
+ if ( sectionMatch ) {
300
+ const sectionContent = sectionMatch [ 0 ]
301
+ if ( isTargetPR )
302
+ console . log ( `📊 Found ${ section . name } section` )
303
+
304
+ // Extract package names from this section's table
305
+ const tableRowMatches = sectionContent . match ( / \| \s * \[ ( [ ^ \] ] + ) \] \( [ ^ ) ] + \) \s * \| / g)
306
+ if ( tableRowMatches ) {
307
+ if ( isTargetPR ) {
308
+ console . log ( `📊 ${ section . name } table matches: ${ tableRowMatches . length } ` )
309
+ console . log ( `📋 Raw ${ section . name } matches:` , JSON . stringify ( tableRowMatches ) )
310
+ }
311
+
312
+ for ( const match of tableRowMatches ) {
313
+ const packageMatch = match . match ( / \| \s * \[ ( [ ^ \] ] + ) \] / )
314
+ if ( packageMatch && packageMatch [ 1 ] ) {
315
+ const packageName = packageMatch [ 1 ] . trim ( )
316
+
317
+ if ( isTargetPR ) {
318
+ console . log ( `🔍 Checking ${ section . name } : "${ packageName } "` )
319
+ }
320
+
321
+ // Check if this looks like a version string - if so, try to extract package name from URL
322
+ if ( packageName . includes ( '`' ) && packageName . includes ( '->' ) ) {
323
+ // This is a version string like "`1.2.17` -> `1.2.19`"
324
+ // Try to extract the package name from the URL
325
+ const urlMatch = match . match ( / \] \( ( [ ^ ) ] + ) \) / )
326
+ if ( urlMatch && urlMatch [ 1 ] ) {
327
+ const url = urlMatch [ 1 ]
328
+ if ( isTargetPR ) {
329
+ console . log ( `🔗 Extracting from URL: "${ url } "` )
330
+ }
331
+
332
+ // Extract package name from Renovate diff URLs like:
333
+ // https://renovatebot.com/diffs/npm/%40types%2Fbun/1.2.17/1.2.19
334
+ // https://renovatebot.com/diffs/npm/cac/6.7.13/6.7.14
335
+ const diffUrlMatch = url . match ( / \/ d i f f s \/ n p m \/ ( [ ^ / ] + ) \/ / )
336
+ if ( diffUrlMatch && diffUrlMatch [ 1 ] ) {
337
+ // Decode URL encoding like %40types%2Fbun -> @types/bun
338
+ const extractedPackage = decodeURIComponent ( diffUrlMatch [ 1 ] )
339
+ if ( isTargetPR ) {
340
+ console . log ( `📦 Extracted from URL: "${ extractedPackage } "` )
341
+ }
342
+
343
+ if ( extractedPackage && extractedPackage . length > 1 && ! packages . includes ( extractedPackage ) ) {
344
+ packages . push ( extractedPackage )
345
+ if ( isTargetPR )
346
+ console . log ( `✅ ${ section . name } (from URL): "${ extractedPackage } "` )
347
+ }
348
+ }
349
+ else if ( isTargetPR ) {
350
+ console . log ( `❌ Could not extract package from URL: "${ url } "` )
351
+ }
352
+ }
353
+ continue // Skip the normal processing for version strings
354
+ }
355
+
356
+ // Normal processing for direct package names
357
+ // Skip if it's a URL, badge, or contains special characters
358
+ // CRITICAL: Skip version strings like "`1.2.17` -> `1.2.19`"
359
+ if ( ! packageName . includes ( '://' )
360
+ && ! packageName . includes ( 'Compare Source' )
361
+ && ! packageName . includes ( 'badge' )
362
+ && ! packageName . includes ( '!' )
363
+ && ! packageName . startsWith ( '[![' )
364
+ && ! packageName . includes ( '`' ) // Skip anything with backticks (version strings)
365
+ && ! packageName . includes ( '->' ) // Skip version arrows
366
+ && ! packageName . includes ( ' -> ' ) // Skip spaced version arrows
367
+ && ! packageName . match ( / ^ \d + \. \d + / ) // Skip version numbers
368
+ && ! packageName . includes ( ' ' ) // Package names shouldn't have spaces
369
+ && packageName . length > 0
370
+ && ! packages . includes ( packageName ) ) {
371
+ packages . push ( packageName )
372
+ if ( isTargetPR )
373
+ console . log ( `✅ ${ section . name } : "${ packageName } "` )
374
+ }
375
+ else if ( isTargetPR ) {
376
+ console . log ( `❌ ${ section . name } skipped: "${ packageName } " (likely version string)` )
377
+ }
378
+ }
379
+ }
380
+ }
381
+ else if ( isTargetPR ) {
382
+ console . log ( `❌ No table matches in ${ section . name } section` )
383
+ // Let's also try a simpler approach - look for table rows with package names
384
+ const simpleRowMatches = sectionContent . match ( / ^ \| \s * \[ ( [ ^ \] ] + ) \] / gm)
385
+ if ( simpleRowMatches ) {
386
+ console . log ( `🔄 Trying simpler pattern, found ${ simpleRowMatches . length } rows:` , JSON . stringify ( simpleRowMatches ) )
277
387
}
278
388
}
279
389
}
390
+ else if ( isTargetPR ) {
391
+ console . log ( `❌ No ${ section . name } section found` )
392
+ }
280
393
}
281
394
282
- // Fallback: try to extract from simple backtick patterns
283
- if ( packages . length === 0 ) {
395
+ // Pattern 3: Extract from PR body - look for package names in backticks (avoid table content)
396
+ // This handles cases where the title doesn't contain specific package names
397
+ // but the body lists them like: `@types/bun`, `cac`, `ts-pkgx`
398
+ if ( packages . length < 3 ) { // Allow backtick extraction to supplement table extraction
284
399
const bodyMatches = pr . body . match ( / ` ( [ ^ ` ] + ) ` / g)
285
400
if ( bodyMatches ) {
401
+ if ( isTargetPR ) {
402
+ console . log ( `🔍 Backtick matches (${ bodyMatches . length } ):` )
403
+ console . log ( JSON . stringify ( bodyMatches . slice ( 0 , 10 ) ) ) // Show first 10
404
+ }
405
+
286
406
for ( const match of bodyMatches ) {
287
- const content = match . replace ( / ` / g, '' )
288
- // Only extract if it looks like a package name (no version arrows, URLs, or special chars)
289
- if ( ! content . includes ( '->' )
290
- && ! content . includes ( '://' )
291
- && ! content . includes ( ' ' )
292
- && ! content . includes ( 'Compare Source' )
293
- && content . length > 0
294
- && ! packages . includes ( content ) ) {
295
- packages . push ( content )
407
+ let packageName = match . replace ( / ` / g, '' ) . trim ( )
408
+
409
+ // Skip anything that looks like version information
410
+ if ( packageName . includes ( '->' )
411
+ || packageName . includes ( ' -> ' )
412
+ || packageName . includes ( '` -> `' )
413
+ || packageName . match ( / ^ \d + \. \d + / ) // Starts with version number
414
+ || packageName . match ( / ^ v \d + / ) // Version tags
415
+ || packageName . match ( / [ \d . ] + \s * - > \s * [ \d . ] + / ) // Version arrows
416
+ || packageName . match ( / ^ [ \d . ] + $ / ) // Pure version numbers like "1.2.17"
417
+ || packageName . match ( / ^ \d + \. \d + \. \d + / ) // Semver patterns
418
+ || packageName . match ( / ^ \d + \. \d + \. \d + \. / ) // Longer version patterns
419
+ || packageName . match ( / ^ \^ ? \d + \. \d + / ) // Version ranges like "^1.2.3"
420
+ || packageName . match ( / ^ ~ \d + \. \d + / ) // Tilde version ranges
421
+ || packageName . includes ( '://' ) // URLs with protocol
422
+ || packageName . includes ( 'Compare Source' )
423
+ || packageName . includes ( 'badge' )
424
+ || packageName . includes ( ' ' ) ) { // Package names shouldn't have spaces
425
+ if ( isTargetPR )
426
+ console . log ( `❌ Skipped: "${ packageName } "` )
427
+ continue
428
+ }
429
+
430
+ // Clean up the package name - take first part only
431
+ packageName = packageName . split ( ',' ) [ 0 ] . trim ( )
432
+
433
+ // Only include if it looks like a valid package/dependency name
434
+ if ( packageName
435
+ && packageName . length > 1
436
+ && ! packages . includes ( packageName )
437
+ && (
438
+ // Must match one of these patterns for valid package names:
439
+ packageName . startsWith ( '@' ) // Scoped packages like @types /node
440
+ || packageName . includes ( '/' ) // GitHub actions like actions/checkout
441
+ || packageName . match ( / ^ [ a - z ] [ a - z 0 - 9 . - ] * $ / i) // Simple package names like lodash, ts-pkgx, bun.com
442
+ ) ) {
443
+ packages . push ( packageName )
444
+ if ( isTargetPR )
445
+ console . log ( `✅ Added: "${ packageName } "` )
446
+ }
447
+ else if ( isTargetPR ) {
448
+ console . log ( `❌ Failed validation: "${ packageName } "` )
296
449
}
297
450
}
298
451
}
452
+ else if ( isTargetPR ) {
453
+ console . log ( `❌ No backtick matches found` )
454
+ }
455
+ }
456
+ else if ( isTargetPR ) {
457
+ console . log ( `⏭️ Skipping backticks (have ${ packages . length } packages)` )
299
458
}
300
459
301
- return packages . slice ( 0 , 5 ) // Limit to first 5 packages to keep it clean
460
+ // NO LIMIT - return all packages like Renovate does
461
+ if ( isTargetPR ) {
462
+ console . log ( `\n📋 FINAL RESULT: ${ JSON . stringify ( packages ) } (${ packages . length } packages)\n` )
463
+ }
464
+ return packages
302
465
}
303
466
304
467
/**
0 commit comments