Skip to content

Commit 4e929e0

Browse files
committed
chore: more stability
chore: wip
1 parent 63d0157 commit 4e929e0

File tree

4 files changed

+308
-6
lines changed

4 files changed

+308
-6
lines changed

bin/cli.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -801,7 +801,7 @@ cli
801801

802802
// Generate new PR content
803803
const { PullRequestGenerator } = await import('../src/pr/pr-generator')
804-
const prGenerator = new PullRequestGenerator()
804+
const prGenerator = new PullRequestGenerator({ verbose: options.verbose })
805805
const newBody = await prGenerator.generateBody(group)
806806

807807
// Update the PR with new title/body (and uncheck the rebase box)
@@ -958,7 +958,7 @@ cli
958958

959959
// Generate new PR content
960960
const { PullRequestGenerator } = await import('../src/pr/pr-generator')
961-
const prGenerator = new PullRequestGenerator()
961+
const prGenerator = new PullRequestGenerator({ verbose: options.verbose })
962962
const newBody = await prGenerator.generateBody(group)
963963

964964
// Update the PR with new title/body (and uncheck the rebase box)

src/buddy.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export class Buddy {
2727
private readonly config: BuddyBotConfig,
2828
private readonly projectPath: string = process.cwd(),
2929
) {
30-
this.logger = new Logger(false) // Will be configurable
30+
this.logger = new Logger(config.verbose ?? false)
3131
this.scanner = new PackageScanner(this.projectPath, this.logger, this.config.packages?.ignorePaths)
3232
this.registryClient = new RegistryClient(this.projectPath, this.logger, this.config)
3333
this.dashboardGenerator = new DashboardGenerator()

src/pr/pr-generator.ts

Lines changed: 99 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,27 @@
1+
/* eslint-disable no-console */
12
import type { PackageInfo, ReleaseNote } from '../services/release-notes-fetcher'
23
import type { BuddyBotConfig, PullRequest, UpdateGroup } from '../types'
4+
import process from 'node:process'
35
import { ReleaseNotesFetcher } from '../services/release-notes-fetcher'
46

57
export class PullRequestGenerator {
68
private releaseNotesFetcher = new ReleaseNotesFetcher()
9+
private verbose = false
710

8-
constructor(private readonly config?: BuddyBotConfig | undefined) {}
11+
constructor(private readonly config?: BuddyBotConfig | undefined) {
12+
// Enable verbose logging via config or environment variable
13+
this.verbose = config?.verbose || process.env.BUDDY_BOT_VERBOSE === 'true'
14+
}
15+
16+
private log(message: string, data?: any) {
17+
if (this.verbose) {
18+
const timestamp = new Date().toISOString()
19+
console.log(`[${timestamp}] PR-GEN: ${message}`)
20+
if (data) {
21+
console.log(JSON.stringify(data, null, 2))
22+
}
23+
}
24+
}
925

1026
/**
1127
* Generate pull requests for update groups
@@ -69,8 +85,22 @@ export class PullRequestGenerator {
6985
* Generate enhanced PR body with rich formatting, badges, and release notes
7086
*/
7187
async generateBody(group: UpdateGroup): Promise<string> {
88+
this.log('🚀 Starting PR body generation', {
89+
groupName: group.name,
90+
totalUpdates: group.updates.length,
91+
updates: group.updates.map(u => ({
92+
name: u.name,
93+
file: u.file,
94+
dependencyType: u.dependencyType,
95+
currentVersion: u.currentVersion,
96+
newVersion: u.newVersion,
97+
})),
98+
})
99+
72100
// Count different types of updates
73-
const packageJsonCount = group.updates.filter(u => u.file === 'package.json').length
101+
const packageJsonCount = group.updates.filter(u =>
102+
u.file === 'package.json' || u.file.endsWith('/package.json') || u.file.endsWith('\\package.json'),
103+
).length
74104
const dependencyFileCount = group.updates.filter(u =>
75105
(u.file.includes('.yaml') || u.file.includes('.yml')) && !u.file.includes('.github/workflows/'),
76106
).length
@@ -79,6 +109,28 @@ export class PullRequestGenerator {
79109
u.file.endsWith('composer.json') || u.file.endsWith('composer.lock'),
80110
).length
81111

112+
this.log('📊 Package type counts', {
113+
packageJsonCount,
114+
dependencyFileCount,
115+
githubActionsCount,
116+
composerCount,
117+
total: group.updates.length,
118+
})
119+
120+
// Debug file path matching for package.json
121+
if (group.updates.length > 0) {
122+
this.log('🔍 File path analysis', {
123+
files: group.updates.map(u => ({
124+
name: u.name,
125+
file: u.file,
126+
isExactMatch: u.file === 'package.json',
127+
endsWithSlash: u.file.endsWith('/package.json'),
128+
endsWithBackslash: u.file.endsWith('\\package.json'),
129+
matchesAny: u.file === 'package.json' || u.file.endsWith('/package.json') || u.file.endsWith('\\package.json'),
130+
})),
131+
})
132+
}
133+
82134
let body = `This PR contains the following updates:\n\n`
83135

84136
// Add summary table (always show for clarity)
@@ -97,7 +149,7 @@ export class PullRequestGenerator {
97149

98150
// Separate updates by type
99151
const packageJsonUpdates = group.updates.filter(update =>
100-
update.file === 'package.json',
152+
update.file === 'package.json' || update.file.endsWith('/package.json') || update.file.endsWith('\\package.json'),
101153
)
102154
const composerUpdates = group.updates.filter(update =>
103155
update.file.endsWith('composer.json') || update.file.endsWith('composer.lock'),
@@ -109,6 +161,25 @@ export class PullRequestGenerator {
109161
update.file.includes('.github/workflows/'),
110162
)
111163

164+
this.log('📝 Filtered updates by type', {
165+
packageJsonUpdates: {
166+
count: packageJsonUpdates.length,
167+
items: packageJsonUpdates.map(u => ({ name: u.name, file: u.file })),
168+
},
169+
composerUpdates: {
170+
count: composerUpdates.length,
171+
items: composerUpdates.map(u => ({ name: u.name, file: u.file })),
172+
},
173+
dependencyFileUpdates: {
174+
count: dependencyFileUpdates.length,
175+
items: dependencyFileUpdates.map(u => ({ name: u.name, file: u.file })),
176+
},
177+
githubActionsUpdates: {
178+
count: githubActionsUpdates.length,
179+
items: githubActionsUpdates.map(u => ({ name: u.name, file: u.file })),
180+
},
181+
})
182+
112183
// Deduplicate GitHub Actions updates by name (multiple files may reference same action)
113184
const uniqueGithubActionsUpdates = githubActionsUpdates.reduce((acc, update) => {
114185
const existing = acc.find(u => u.name === update.name && u.currentVersion === update.currentVersion && u.newVersion === update.newVersion)
@@ -142,6 +213,10 @@ export class PullRequestGenerator {
142213

143214
// Package.json updates table (with full badges)
144215
if (packageJsonUpdates.length > 0) {
216+
this.log('✅ Generating npm Dependencies section', {
217+
packageCount: packageJsonUpdates.length,
218+
packages: packageJsonUpdates.map(u => u.name),
219+
})
145220
body += `## 📦 npm Dependencies\n\n`
146221
body += `![npm](https://img.shields.io/badge/npm-CB3837?style=flat&logo=npm&logoColor=white)\n\n`
147222
if (packageJsonUpdates.length === 1) {
@@ -580,6 +655,27 @@ export class PullRequestGenerator {
580655
body += `This PR was generated by [Buddy](https://github.com/stacksjs/buddy-bot) 🤖`
581656
}
582657

658+
this.log('✅ PR body generation complete', {
659+
bodyLength: body.length,
660+
hasNpmSection: body.includes('## 📦 npm Dependencies'),
661+
hasComposerSection: body.includes('## 🐘 PHP/Composer Dependencies'),
662+
hasSystemSection: body.includes('## 🔧 System Dependencies'),
663+
hasGitHubActionsSection: body.includes('## 🚀 GitHub Actions'),
664+
hasPackageTable: body.includes('| Package | Change | Age | Adoption | Passing | Confidence |'),
665+
hasReleaseNotes: body.includes('### Release Notes'),
666+
isSparse: body.length < 1000,
667+
sections: {
668+
hasNpmPackagesInSummary: body.includes('📦 NPM Packages'),
669+
hasComposerInSummary: body.includes('🐘 Composer Packages'),
670+
hasSystemInSummary: body.includes('🔧 System Dependencies'),
671+
hasGitHubActionsInSummary: body.includes('🚀 GitHub Actions'),
672+
},
673+
})
674+
675+
if (body.length < 1000) {
676+
this.log('🚨 WARNING: Generated PR body appears sparse (under 1000 chars)!')
677+
}
678+
583679
return body
584680
}
585681

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
/* eslint-disable no-console */
2+
import { describe, expect, it } from 'bun:test'
3+
import { PullRequestGenerator } from '../src/pr/pr-generator'
4+
5+
describe('PR Body Regression - Stripe Issue', () => {
6+
it('should reproduce the exact stripe regression from PR #1453', async () => {
7+
// This test reproduces the exact regression where stripe major update
8+
// generates only summary table without detailed package information
9+
10+
const config = {
11+
repository: {
12+
provider: 'github' as const,
13+
owner: 'stacksjs',
14+
name: 'stacks',
15+
},
16+
}
17+
18+
const generator = new PullRequestGenerator(config)
19+
20+
// Exact same data structure that would generate PR #1453
21+
const stripeUpdate = {
22+
name: 'Major Update - stripe',
23+
title: 'chore(deps): update dependency stripe to 18.4.0',
24+
body: '',
25+
updateType: 'major' as const,
26+
updates: [{
27+
name: 'stripe',
28+
currentVersion: '17.7.0',
29+
newVersion: '18.4.0',
30+
updateType: 'major' as const,
31+
dependencyType: 'dependencies' as const,
32+
file: 'package.json',
33+
}],
34+
}
35+
36+
const body = await generator.generateBody(stripeUpdate)
37+
38+
console.log('🔍 Generated PR body for regression test:')
39+
console.log('='.repeat(60))
40+
console.log(body)
41+
console.log('='.repeat(60))
42+
43+
// The regression: PR body should NOT be sparse like PR #1453
44+
// It should include the detailed NPM table with badges and links
45+
46+
// 1. Should have package summary table
47+
expect(body).toContain('## Package Updates Summary')
48+
expect(body).toContain('| 📦 NPM Packages | 1 |')
49+
expect(body).toContain('| **Total** | **1** |')
50+
51+
// 2. CRITICAL: Should have detailed NPM dependencies section (this was missing in #1453)
52+
expect(body).toContain('## 📦 npm Dependencies')
53+
expect(body).toContain('![npm](https://img.shields.io/badge/npm-CB3837?style=flat&logo=npm&logoColor=white)')
54+
expect(body).toContain('*1 package will be updated*')
55+
56+
// 3. Should have detailed package table with badges (missing in #1453)
57+
expect(body).toContain('| Package | Change | Age | Adoption | Passing | Confidence |')
58+
expect(body).toContain('stripe')
59+
expect(body).toContain('17.7.0')
60+
expect(body).toContain('18.4.0')
61+
expect(body).toContain('developer.mend.io/api/mc/badges') // Confidence badges
62+
expect(body).toContain('renovatebot.com/diffs/npm/stripe') // Diff link
63+
64+
// 4. Should have release notes section (missing in #1453)
65+
expect(body).toContain('### Release Notes')
66+
expect(body).toContain('<details>')
67+
expect(body).toContain('stripe/stripe-node')
68+
69+
// 5. Should have package statistics (missing in #1453)
70+
expect(body).toContain('### 📊 Package Statistics')
71+
expect(body).toContain('weekly downloads')
72+
73+
// 6. Should NOT be the broken minimal version from PR #1453
74+
expect(body).not.toMatch(/^This PR contains the following updates:\s*## Package Updates Summary\s*\| Type \| Count \|\s*\|------\|-------\|\s*\| \*\*Total\*\* \| \*\*1\*\* \|\s*---\s*### Release Notes\s*---/)
75+
76+
// 7. Body should be substantial, not sparse like PR #1453 (which was ~500 chars)
77+
expect(body.length).toBeGreaterThan(2000) // Should have substantial content
78+
79+
console.log(`✅ PR body length: ${body.length} characters (should be > 2000)`)
80+
console.log(`✅ Has detailed package table: ${body.includes('| Package | Change | Age |')}`)
81+
console.log(`✅ Has badges: ${body.includes('developer.mend.io')}`)
82+
console.log(`✅ Has release notes: ${body.includes('<details>')}`)
83+
})
84+
85+
it('should handle different file path formats correctly', async () => {
86+
// This test ensures we handle various file path formats that might come from the runtime
87+
88+
const config = {
89+
repository: {
90+
provider: 'github' as const,
91+
owner: 'stacksjs',
92+
name: 'stacks',
93+
},
94+
}
95+
96+
const generator = new PullRequestGenerator(config)
97+
98+
// Test relative path (likely cause of PR #1453 regression)
99+
const relativePathUpdate = {
100+
name: 'Major Update - stripe',
101+
title: 'chore(deps): update dependency stripe to 18.4.0',
102+
body: '',
103+
updateType: 'major' as const,
104+
updates: [{
105+
name: 'stripe',
106+
currentVersion: '17.7.0',
107+
newVersion: '18.4.0',
108+
updateType: 'major' as const,
109+
dependencyType: 'dependencies' as const,
110+
file: './package.json', // This was likely the issue in PR #1453
111+
}],
112+
}
113+
114+
const body = await generator.generateBody(relativePathUpdate)
115+
116+
// Should work correctly with relative path
117+
expect(body).toContain('| 📦 NPM Packages | 1 |')
118+
expect(body).toContain('## 📦 npm Dependencies')
119+
expect(body).toContain('| Package | Change | Age | Adoption | Passing | Confidence |')
120+
expect(body.length).toBeGreaterThan(2000)
121+
122+
// Test absolute path (common in CI environments)
123+
const absolutePathUpdate = {
124+
name: 'Major Update - stripe',
125+
title: 'chore(deps): update dependency stripe to 18.4.0',
126+
body: '',
127+
updateType: 'major' as const,
128+
updates: [{
129+
name: 'stripe',
130+
currentVersion: '17.7.0',
131+
newVersion: '18.4.0',
132+
updateType: 'major' as const,
133+
dependencyType: 'dependencies' as const,
134+
file: '/home/runner/work/stacks/stacks/package.json', // CI environment path
135+
}],
136+
}
137+
138+
const bodyAbsolute = await generator.generateBody(absolutePathUpdate)
139+
140+
// Should work correctly with absolute path
141+
expect(bodyAbsolute).toContain('| 📦 NPM Packages | 1 |')
142+
expect(bodyAbsolute).toContain('## 📦 npm Dependencies')
143+
expect(bodyAbsolute).toContain('| Package | Change | Age | Adoption | Passing | Confidence |')
144+
expect(bodyAbsolute.length).toBeGreaterThan(2000)
145+
})
146+
147+
it('should verify the minimal broken format does NOT match our output', async () => {
148+
// This test ensures we don't regress back to the broken format from PR #1453
149+
150+
const config = {
151+
repository: {
152+
provider: 'github' as const,
153+
owner: 'stacksjs',
154+
name: 'stacks',
155+
},
156+
}
157+
158+
const generator = new PullRequestGenerator(config)
159+
160+
const stripeUpdate = {
161+
name: 'Major Update - stripe',
162+
title: 'chore(deps): update dependency stripe to 18.4.0',
163+
body: '',
164+
updateType: 'major' as const,
165+
updates: [{
166+
name: 'stripe',
167+
currentVersion: '17.7.0',
168+
newVersion: '18.4.0',
169+
updateType: 'major' as const,
170+
dependencyType: 'dependencies' as const,
171+
file: 'package.json',
172+
}],
173+
}
174+
175+
const body = await generator.generateBody(stripeUpdate)
176+
177+
// The broken pattern from PR #1453 (minimal content without detailed table)
178+
const brokenPattern = `This PR contains the following updates:
179+
180+
## Package Updates Summary
181+
182+
| Type | Count |
183+
|------|-------|
184+
| **Total** | **1** |
185+
186+
187+
---
188+
189+
### Release Notes
190+
191+
---`
192+
193+
// Our output should NOT match this broken minimal pattern
194+
expect(body).not.toContain(brokenPattern)
195+
196+
// Specifically check that we have content between summary and release notes
197+
const summaryToReleaseNotes = body.substring(
198+
body.indexOf('| **Total** | **1** |'),
199+
body.indexOf('### Release Notes'),
200+
)
201+
202+
// Should have substantial content (NPM section) between summary and release notes
203+
expect(summaryToReleaseNotes).toContain('## 📦 npm Dependencies')
204+
expect(summaryToReleaseNotes.length).toBeGreaterThan(500) // Should have substantial content
205+
})
206+
})

0 commit comments

Comments
 (0)