Skip to content

Commit b65dd17

Browse files
committed
chore: several dashboard improvements
chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip chore: wip
1 parent 0c7b00a commit b65dd17

File tree

6 files changed

+212
-91
lines changed

6 files changed

+212
-91
lines changed

src/buddy.ts

Lines changed: 17 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -760,15 +760,11 @@ export class Buddy {
760760
assignees: dashboardConfig.assignees,
761761
})
762762

763-
// Pin the issue if configured (even if it's an existing issue)
763+
// Note: GitHub REST API does not support pinning issues programmatically
764+
// Users need to pin the dashboard issue manually through the GitHub UI
764765
if (dashboardConfig.pin) {
765-
try {
766-
await gitProvider.pinIssue(issue.number)
767-
this.logger.info(`📌 Pinned existing dashboard issue #${issue.number}`)
768-
}
769-
catch (error) {
770-
this.logger.warn(`Failed to pin dashboard issue: ${error}`)
771-
}
766+
this.logger.info(`💡 To pin dashboard issue #${issue.number}, please do so manually in the GitHub UI`)
767+
this.logger.info(`📌 Go to: ${issue.url} and click "Pin issue" in the right sidebar`)
772768
}
773769
}
774770
else {
@@ -782,15 +778,11 @@ export class Buddy {
782778
assignees: dashboardConfig.assignees,
783779
})
784780

785-
// Pin the issue if configured
781+
// Note: GitHub REST API does not support pinning issues programmatically
782+
// Users need to pin the dashboard issue manually through the GitHub UI
786783
if (dashboardConfig.pin) {
787-
try {
788-
await gitProvider.pinIssue(issue.number)
789-
this.logger.info(`📌 Pinned new dashboard issue #${issue.number}`)
790-
}
791-
catch (error) {
792-
this.logger.warn(`Failed to pin dashboard issue: ${error}`)
793-
}
784+
this.logger.info(`💡 To pin dashboard issue #${issue.number}, please do so manually in the GitHub UI`)
785+
this.logger.info(`📌 Go to: ${issue.url} and click "Pin issue" in the right sidebar`)
794786
}
795787
}
796788

@@ -814,9 +806,15 @@ export class Buddy {
814806

815807
// Filter PRs to only include dependency updates (likely created by buddy-bot)
816808
const dependencyPRs = openPRs.filter(pr =>
817-
pr.labels.includes('dependencies')
818-
|| pr.title.toLowerCase().includes('update')
819-
|| pr.title.toLowerCase().includes('chore(deps)'),
809+
// Exclude Renovate PRs
810+
!pr.author.toLowerCase().includes('renovate')
811+
&& !pr.head.includes('renovate/')
812+
&& !pr.title.toLowerCase().includes('renovate')
813+
&& (
814+
pr.labels.includes('dependencies')
815+
|| pr.title.toLowerCase().includes('update')
816+
|| pr.title.toLowerCase().includes('chore(deps)')
817+
),
820818
)
821819

822820
// Categorize package files

src/dashboard/dashboard-generator.ts

Lines changed: 129 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,10 @@ export class DashboardGenerator {
4242
/**
4343
* Generate the default header section
4444
*/
45-
private generateDefaultHeader(data: DashboardData): string {
46-
const portalUrl = `https://developer.mend.io/github/${data.repository.owner}/${data.repository.name}`
45+
private generateDefaultHeader(_data: DashboardData): string {
46+
// const portalUrl = `https://developer.mend.io/github/${data.repository.owner}/${data.repository.name}`
4747

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}).
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.
4949
5050
`
5151
}
@@ -72,6 +72,7 @@ The following updates have all been created. To force a retry/rebase of any, cli
7272

7373
section += ` - [ ] <!-- rebase-branch=${rebaseBranch} -->[${pr.title}](${relativeUrl})`
7474

75+
// Show clean package names like Renovate does (without version info)
7576
if (packageInfo.length > 0) {
7677
section += ` (\`${packageInfo.join('`, `')}\`)`
7778
}
@@ -247,58 +248,150 @@ The following updates have all been created. To force a retry/rebase of any, cli
247248
}
248249

249250
/**
250-
* Extract package names from PR title or body
251+
* Extract package names from PR title or body (like Renovate does)
251252
*/
252253
private extractPackageInfo(pr: PullRequest): string[] {
253254
const packages: string[] = []
254255

255-
// Try to extract from title patterns like "update dependency react to v18"
256-
const titleMatch = pr.title.match(/update.*?dependency\s+(\w+)/i)
257-
if (titleMatch) {
258-
packages.push(titleMatch[1])
256+
// Pattern 1: Extract from common dependency update titles
257+
// Examples: "chore(deps): update dependency react to v18"
258+
// "chore(deps): update all non-major dependencies"
259+
// "update @types/node to v20"
260+
const titlePatterns = [
261+
/update.*?dependency\s+(\S+)/i,
262+
/update\s+(\S+)\s+to\s+v?\d+/i,
263+
/bump\s+(\S+)\s+from/i,
264+
]
265+
266+
for (const pattern of titlePatterns) {
267+
const match = pr.title.match(pattern)
268+
if (match && match[1] && !packages.includes(match[1])) {
269+
packages.push(match[1])
270+
}
259271
}
260272

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)
273+
// Pattern 2: Extract from table format in PR body - handle all table sections
274+
// Look for different table types: npm Dependencies, Launchpad/pkgx Dependencies, GitHub Actions
275+
276+
// Split the body into sections and process each table
277+
const tableSections = [
278+
// npm Dependencies table
279+
{ name: 'npm', pattern: /### npm Dependencies[\s\S]*?(?=###|\n\n---|z)/i },
280+
// Launchpad/pkgx Dependencies table
281+
{ name: 'pkgx', pattern: /### Launchpad\/pkgx Dependencies[\s\S]*?(?=###|\n\n---|z)/i },
282+
// GitHub Actions table
283+
{ name: 'actions', pattern: /### GitHub Actions[\s\S]*?(?=###|\n\n---|z)/i },
284+
]
285+
286+
for (const section of tableSections) {
287+
const sectionMatch = pr.body.match(section.pattern)
288+
if (sectionMatch) {
289+
const sectionContent = sectionMatch[0]
290+
291+
// Extract package names from this section's table
292+
const tableRowMatches = sectionContent.match(/\|\s*\[([^\]]+)\]\([^)]+\)\s*\|/g)
293+
if (tableRowMatches) {
294+
for (const match of tableRowMatches) {
295+
const packageMatch = match.match(/\|\s*\[([^\]]+)\]/)
296+
if (packageMatch && packageMatch[1]) {
297+
const packageName = packageMatch[1].trim()
298+
299+
// Check if this looks like a version string - if so, try to extract package name from URL
300+
if (packageName.includes('`') && packageName.includes('->')) {
301+
// This is a version string like "`1.2.17` -> `1.2.19`"
302+
// Try to extract the package name from the URL
303+
const urlMatch = match.match(/\]\(([^)]+)\)/)
304+
if (urlMatch && urlMatch[1]) {
305+
const url = urlMatch[1]
306+
307+
// Extract package name from Renovate diff URLs like:
308+
// https://renovatebot.com/diffs/npm/%40types%2Fbun/1.2.17/1.2.19
309+
// https://renovatebot.com/diffs/npm/cac/6.7.13/6.7.14
310+
const diffUrlMatch = url.match(/\/diffs\/npm\/([^/]+)\//)
311+
if (diffUrlMatch && diffUrlMatch[1]) {
312+
// Decode URL encoding like %40types%2Fbun -> @types/bun
313+
const extractedPackage = decodeURIComponent(diffUrlMatch[1])
314+
315+
if (extractedPackage && extractedPackage.length > 1 && !packages.includes(extractedPackage)) {
316+
packages.push(extractedPackage)
317+
}
318+
}
319+
}
320+
continue // Skip the normal processing for version strings
321+
}
322+
323+
// Normal processing for direct package names
324+
// Skip if it's a URL, badge, or contains special characters
325+
// CRITICAL: Skip version strings like "`1.2.17` -> `1.2.19`"
326+
if (!packageName.includes('://')
327+
&& !packageName.includes('Compare Source')
328+
&& !packageName.includes('badge')
329+
&& !packageName.includes('!')
330+
&& !packageName.startsWith('[![')
331+
&& !packageName.includes('`') // Skip anything with backticks (version strings)
332+
&& !packageName.includes('->') // Skip version arrows
333+
&& !packageName.includes(' -> ') // Skip spaced version arrows
334+
&& !packageName.match(/^\d+\.\d+/) // Skip version numbers
335+
&& !packageName.includes(' ') // Package names shouldn't have spaces
336+
&& packageName.length > 0
337+
&& !packages.includes(packageName)) {
338+
packages.push(packageName)
339+
}
340+
}
277341
}
278342
}
279343
}
280344
}
281345

282-
// Fallback: try to extract from simple backtick patterns
283-
if (packages.length === 0) {
346+
// Pattern 3: Extract from PR body - look for package names in backticks (avoid table content)
347+
// This handles cases where the title doesn't contain specific package names
348+
// but the body lists them like: `@types/bun`, `cac`, `ts-pkgx`
349+
if (packages.length < 3) { // Allow backtick extraction to supplement table extraction
284350
const bodyMatches = pr.body.match(/`([^`]+)`/g)
285351
if (bodyMatches) {
286352
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)
353+
let packageName = match.replace(/`/g, '').trim()
354+
355+
// Skip anything that looks like version information
356+
if (packageName.includes('->')
357+
|| packageName.includes(' -> ')
358+
|| packageName.includes('` -> `')
359+
|| packageName.match(/^\d+\.\d+/) // Starts with version number
360+
|| packageName.match(/^v\d+/) // Version tags
361+
|| packageName.match(/[\d.]+\s*->\s*[\d.]+/) // Version arrows
362+
|| packageName.match(/^[\d.]+$/) // Pure version numbers like "1.2.17"
363+
|| packageName.match(/^\d+\.\d+\.\d+/) // Semver patterns
364+
|| packageName.match(/^\d+\.\d+\.\d+\./) // Longer version patterns
365+
|| packageName.match(/^\^?\d+\.\d+/) // Version ranges like "^1.2.3"
366+
|| packageName.match(/^~\d+\.\d+/) // Tilde version ranges
367+
|| packageName.includes('://') // URLs with protocol
368+
|| packageName.includes('Compare Source')
369+
|| packageName.includes('badge')
370+
|| packageName.includes(' ')) { // Package names shouldn't have spaces
371+
continue
372+
}
373+
374+
// Clean up the package name - take first part only
375+
packageName = packageName.split(',')[0].trim()
376+
377+
// Only include if it looks like a valid package/dependency name
378+
if (packageName
379+
&& packageName.length > 1
380+
&& !packages.includes(packageName)
381+
&& (
382+
// Must match one of these patterns for valid package names:
383+
packageName.startsWith('@') // Scoped packages like @types/node
384+
|| packageName.includes('/') // GitHub actions like actions/checkout
385+
|| packageName.match(/^[a-z][a-z0-9.-]*$/i) // Simple package names like lodash, ts-pkgx, bun.com
386+
)) {
387+
packages.push(packageName)
296388
}
297389
}
298390
}
299391
}
300392

301-
return packages.slice(0, 5) // Limit to first 5 packages to keep it clean
393+
// NO LIMIT - return all packages like Renovate does
394+
return packages
302395
}
303396

304397
/**

src/git/github-provider.ts

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -658,29 +658,17 @@ export class GitHubProvider implements GitProvider {
658658
}
659659
}
660660

661-
async pinIssue(issueNumber: number): Promise<void> {
662-
try {
663-
// GitHub's pin/unpin issue API requires special headers and is relatively new
664-
await this.apiRequest(`PUT /repos/${this.owner}/${this.repo}/issues/${issueNumber}/pin`, undefined)
665-
666-
console.log(`✅ Pinned issue #${issueNumber}`)
667-
}
668-
catch (error) {
669-
console.warn(`⚠️ Failed to pin issue #${issueNumber}:`, error)
670-
// Don't throw error for pinning failures as it's not critical
671-
}
672-
}
673-
674661
async unpinIssue(issueNumber: number): Promise<void> {
675662
try {
676-
// GitHub's pin/unpin issue API requires special headers and is relatively new
677663
await this.apiRequest(`DELETE /repos/${this.owner}/${this.repo}/issues/${issueNumber}/pin`, undefined)
678-
679-
console.log(`✅ Unpinned issue #${issueNumber}`)
680664
}
681-
catch (error) {
682-
console.warn(`⚠️ Failed to unpin issue #${issueNumber}:`, error)
683-
// Don't throw error for unpinning failures as it's not critical
665+
catch (error: any) {
666+
console.log(`⚠️ Failed to unpin issue #${issueNumber}:`, error)
667+
// Don't throw error for pinning failures as it's not critical
684668
}
685669
}
670+
671+
// Note: GitHub REST API does not support pinning issues programmatically
672+
// Pinning can only be done manually through the GitHub web interface
673+
// See: https://docs.github.com/en/issues/tracking-your-work-with-issues/administering-issues/pinning-an-issue-to-your-repository
686674
}

src/git/gitlab-provider.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,8 @@ export class GitLabProvider implements GitProvider {
9797
console.log(`Would close issue #${issueNumber}`)
9898
}
9999

100-
async pinIssue(issueNumber: number): Promise<void> {
101-
// TODO: Implement GitLab API call to pin issue
102-
console.log(`Would pin issue #${issueNumber}`)
103-
}
104-
105-
async unpinIssue(issueNumber: number): Promise<void> {
106-
// TODO: Implement GitLab API call to unpin issue
107-
console.log(`Would unpin issue #${issueNumber}`)
100+
async unpinIssue(_issueNumber: number): Promise<void> {
101+
// GitLab doesn't have issue pinning functionality
102+
console.log(`ℹ️ GitLab does not support issue pinning`)
108103
}
109104
}

src/types.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -256,10 +256,7 @@ export interface GitProvider {
256256
/** Close issue */
257257
closeIssue: (issueNumber: number) => Promise<void>
258258

259-
/** Pin issue */
260-
pinIssue: (issueNumber: number) => Promise<void>
261-
262-
/** Unpin issue */
259+
/** Unpin issue - Note: GitHub REST API does not support pinning issues programmatically */
263260
unpinIssue: (issueNumber: number) => Promise<void>
264261
}
265262

0 commit comments

Comments
 (0)