Skip to content

Commit 67d0df1

Browse files
committed
chore: improve pr generation
chore: wip
1 parent f37ca94 commit 67d0df1

10 files changed

+196
-107
lines changed

.github/workflows/buddy-bot.yml

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -289,37 +289,6 @@ jobs:
289289
echo "Repository: ${{ github.repository }}"
290290
echo "Branch: ${{ github.ref_name }}"
291291
292-
- name: Run Buddy dependency scan
293-
run: |
294-
STRATEGY="${{ github.event.inputs.strategy || 'patch' }}"
295-
PACKAGES="${{ github.event.inputs.packages }}"
296-
VERBOSE="${{ github.event.inputs.verbose || 'true' }}"
297-
298-
echo "🔍 Scanning for dependency updates..."
299-
echo "Strategy: $STRATEGY"
300-
echo "Packages: ${PACKAGES:-all}"
301-
echo "Verbose: $VERBOSE"
302-
echo ""
303-
304-
set -e # Exit on any error
305-
306-
if [ "$PACKAGES" != "" ]; then
307-
if [ "$VERBOSE" = "true" ]; then
308-
bunx buddy-bot scan --packages "$PACKAGES" --verbose
309-
else
310-
bunx buddy-bot scan --packages "$PACKAGES"
311-
fi
312-
else
313-
if [ "$VERBOSE" = "true" ]; then
314-
bunx buddy-bot scan --strategy "$STRATEGY" --verbose
315-
else
316-
bunx buddy-bot scan --strategy "$STRATEGY"
317-
fi
318-
fi
319-
320-
env:
321-
GITHUB_TOKEN: ${{ secrets.BUDDY_BOT_TOKEN || secrets.GITHUB_TOKEN }}
322-
323292
- name: Run Buddy dependency updates
324293
if: ${{ github.event.inputs.dry_run != 'true' }}
325294
run: |

src/buddy.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -430,12 +430,26 @@ export class Buddy {
430430
*/
431431
private getUpdateType(current: string, latest: string): 'major' | 'minor' | 'patch' {
432432
try {
433-
const currentParts = current.split('.').map(Number)
434-
const latestParts = latest.split('.').map(Number)
433+
// Clean version strings, including @ prefix for version ranges
434+
const cleanCurrent = current.replace(/^[\^~>=<@]+/, '')
435+
const cleanLatest = latest.replace(/^[\^~>=<@]+/, '')
436+
437+
const currentParts = cleanCurrent.split('.').map((part) => {
438+
const num = Number(part)
439+
return Number.isNaN(num) ? 0 : num
440+
})
441+
const latestParts = cleanLatest.split('.').map((part) => {
442+
const num = Number(part)
443+
return Number.isNaN(num) ? 0 : num
444+
})
445+
446+
// Ensure we have at least major.minor.patch structure
447+
while (currentParts.length < 3) currentParts.push(0)
448+
while (latestParts.length < 3) latestParts.push(0)
435449

436450
if (latestParts[0] > currentParts[0])
437451
return 'major'
438-
if (latestParts[1] > currentParts[1])
452+
if (latestParts[0] === currentParts[0] && latestParts[1] > currentParts[1])
439453
return 'minor'
440454
return 'patch'
441455
}

src/pr/pr-generator.ts

Lines changed: 144 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,34 @@ export class PullRequestGenerator {
6767
* Generate enhanced PR body with rich formatting, badges, and release notes
6868
*/
6969
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+
7080
let body = `This PR contains the following updates:\n\n`
7181

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+
7298
// Separate updates by type
7399
const packageJsonUpdates = group.updates.filter(update =>
74100
update.file === 'package.json',
@@ -116,7 +142,10 @@ export class PullRequestGenerator {
116142

117143
// Package.json updates table (with full badges)
118144
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+
}
120149
body += `| Package | Change | Age | Adoption | Passing | Confidence |\n`
121150
body += `|---|---|---|---|---|---|\n`
122151

@@ -237,11 +266,17 @@ export class PullRequestGenerator {
237266
body += `\n`
238267
}
239268

240-
// Dependency files table (simplified, without badges)
269+
// Dependency files table (enhanced with more information)
241270
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`
245280

246281
for (const update of dependencyFileUpdates) {
247282
// Handle special case: bun.sh -> bun.com
@@ -253,50 +288,72 @@ export class PullRequestGenerator {
253288
: `https://pkgx.com/pkg/${encodeURIComponent(update.name)}`
254289
const packageCell = `[${displayName}](${packageUrl})`
255290

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}\``
258295

259-
// File reference
296+
// File reference with link to actual file
260297
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+
}
261308

262-
// Status (simple)
263-
const status = '✅ Available'
264-
265-
body += `| ${packageCell} | ${change} | ${fileName} | ${status} |\n`
309+
body += `| ${packageCell} | ${change} | ${typeEmoji} ${updateType} | ${fileCell} | ${linksCell} |\n`
266310
}
267311

268312
body += `\n`
269313
}
270314

271-
// GitHub Actions table (simplified, without badges, deduplicated)
315+
// GitHub Actions table (enhanced with more information)
272316
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`
276325

277326
for (const update of uniqueGithubActionsUpdates) {
278327
// Generate action link
279328
const actionUrl = `https://github.com/${update.name}`
280329
const actionCell = `[${update.name}](${actionUrl})`
281330

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}\``
284335

285-
// File reference with GitHub links (may be multiple files now)
336+
// Enhanced file reference with proper GitHub links
286337
const fileLinks = update.file.includes(', ')
287338
? update.file.split(', ').map((f) => {
288339
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}\``
290343
}).join(', ')
291344
: (() => {
292345
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}\``
294349
})()
295350

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})`
298355

299-
body += `| ${actionCell} | ${change} | ${fileLinks} | ${status} |\n`
356+
body += `| ${actionCell} | ${change} | ${typeEmoji} ${updateType} | ${fileLinks} | ${linksCell} |\n`
300357
}
301358

302359
body += `\n`
@@ -376,7 +433,7 @@ export class PullRequestGenerator {
376433
body += `</details>\n\n`
377434
}
378435

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
380437
const dependencyOnlyUpdates = dependencyFileUpdates.filter(depUpdate =>
381438
!packageJsonUpdates.some(pkgUpdate =>
382439
pkgUpdate.name.replace(/\s*\(dev\)$/, '').replace(/\s*\(peer\)$/, '').replace(/\s*\(optional\)$/, '') === depUpdate.name,
@@ -391,11 +448,24 @@ export class PullRequestGenerator {
391448
body += `<summary>${displayName}</summary>\n\n`
392449
body += `**${update.currentVersion} -> ${update.newVersion}**\n\n`
393450

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
394458
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`
396466
}
397467
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`
399469
}
400470

401471
body += `</details>\n\n`
@@ -678,4 +748,51 @@ export class PullRequestGenerator {
678748

679749
return result
680750
}
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+
}
681798
}

src/setup.ts

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1374,37 +1374,6 @@ ${generateComposerSetupSteps()}
13741374
echo "Repository: \${{ github.repository }}"
13751375
echo "Branch: \${{ github.ref_name }}"
13761376
1377-
- name: Run Buddy dependency scan
1378-
run: |
1379-
STRATEGY="\${{ github.event.inputs.strategy || 'patch' }}"
1380-
PACKAGES="\${{ github.event.inputs.packages }}"
1381-
VERBOSE="\${{ github.event.inputs.verbose || 'true' }}"
1382-
1383-
echo "🔍 Scanning for dependency updates..."
1384-
echo "Strategy: \$STRATEGY"
1385-
echo "Packages: \${PACKAGES:-all}"
1386-
echo "Verbose: \$VERBOSE"
1387-
echo ""
1388-
1389-
set -e # Exit on any error
1390-
1391-
if [ "\$PACKAGES" != "" ]; then
1392-
if [ "\$VERBOSE" = "true" ]; then
1393-
bunx buddy-bot scan --packages "\$PACKAGES" --verbose
1394-
else
1395-
bunx buddy-bot scan --packages "\$PACKAGES"
1396-
fi
1397-
else
1398-
if [ "\$VERBOSE" = "true" ]; then
1399-
bunx buddy-bot scan --strategy "\$STRATEGY" --verbose
1400-
else
1401-
bunx buddy-bot scan --strategy "\$STRATEGY"
1402-
fi
1403-
fi
1404-
1405-
env:
1406-
GITHUB_TOKEN: ${tokenEnv}
1407-
14081377
- name: Run Buddy dependency updates
14091378
if: \${{ github.event.inputs.dry_run != 'true' }}
14101379
run: |

src/utils/helpers.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -300,12 +300,24 @@ export function sortUpdatesByPriority(updates: PackageUpdate[]): PackageUpdate[]
300300
* Parse version string and determine update type using Bun's semver
301301
*/
302302
export function getUpdateType(currentVersion: string, newVersion: string): 'major' | 'minor' | 'patch' {
303-
// Remove any prefixes like ^, ~, >=, v, etc.
304-
const cleanCurrent = currentVersion.replace(/^[v^~>=<]+/, '')
305-
const cleanNew = newVersion.replace(/^[v^~>=<]+/, '')
303+
// Remove any prefixes like ^, ~, >=, v, @, etc.
304+
const cleanCurrent = currentVersion.replace(/^[v^~>=<@]+/, '')
305+
const cleanNew = newVersion.replace(/^[v^~>=<@]+/, '')
306+
307+
// Handle version ranges with @ (like @1.1, @1, etc.)
308+
// For @1.1 -> 3.5.0, current should be interpreted as 1.1
309+
const currentParts = cleanCurrent.split('.').map((part) => {
310+
const num = Number(part)
311+
return Number.isNaN(num) ? 0 : num
312+
})
313+
const newParts = cleanNew.split('.').map((part) => {
314+
const num = Number(part)
315+
return Number.isNaN(num) ? 0 : num
316+
})
306317

307-
const currentParts = cleanCurrent.split('.').map(Number)
308-
const newParts = cleanNew.split('.').map(Number)
318+
// Ensure we have at least major.minor.patch structure
319+
while (currentParts.length < 3) currentParts.push(0)
320+
while (newParts.length < 3) newParts.push(0)
309321

310322
// Compare major version
311323
if (newParts[0] > currentParts[0]) {

0 commit comments

Comments
 (0)