buddy-bot
Version:
Automated & optimized dependency updates for JavaScript & TypeScript projects. Like Renovate & Dependabot.
96 lines • 54.3 kB
JavaScript
// @bun
import{f as i,j as $K}from"./chunk-r1rkcs0b.js";import{k as e,l as KK}from"./chunk-j0phfedr.js";import{m as t,n as a}from"./chunk-ma13sjrm.js";import{o as c}from"./chunk-p0vny2x9.js";import{q as XK}from"./chunk-1z74sgbm.js";import{x as o,y as QK}from"./chunk-b5gyp8hn.js";import{A as p,B as s}from"./chunk-3j3vxazw.js";import"./chunk-f5bh0qas.js";import"./chunk-72vxav2c.js";import"./chunk-knkqp939.js";import"./chunk-jah7r3bb.js";import"./chunk-32dgq3f4.js";import"./chunk-8wk4nc61.js";import"./chunk-vd04qkw4.js";import{ua as J,wa as A}from"./chunk-pvk7bcsa.js";import ZK from"fs";import N from"process";class h{generateDashboard(Q,K={}){let{showOpenPRs:X=!0,showDetectedDependencies:$=!0,showDeprecatedDependencies:O=!0,bodyTemplate:q}=K,Y="Dependency Dashboard";if(q)return{title:"Dependency Dashboard",body:this.applyTemplate(q,Q)};let W=this.generateDefaultHeader(Q);if(O&&Q.deprecatedDependencies&&Q.deprecatedDependencies.length>0)W+=this.generateDeprecatedDependenciesSection(Q.deprecatedDependencies);if(X&&Q.openPRs.length>0)W+=this.generateOpenPRsSection(Q.openPRs);if($)W+=this.generateDetectedDependenciesSection(Q.detectedDependencies);return W+=this.generateFooter(),{title:"Dependency Dashboard",body:W}}generateDefaultHeader(Q){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.
`}generateOpenPRsSection(Q){let K=`## Open
The following updates have all been created. To force a retry/rebase of any, click on a checkbox below.
`;for(let X of Q){let $=this.extractPackageInfo(X),O=X.head,q=X.url.includes("/pull/")&&X.url.includes("github.com")?`../pull/${X.number}`:X.url;if(K+=` - [ ] <!-- rebase-branch=${O} -->[${X.title}](${q})`,$.length>0)K+=` (\`${$.join("`, `")}\`)`;K+=`
`}return K+=` - [ ] <!-- rebase-all-open-prs -->**Click on this checkbox to rebase all open PRs at once**
`,K+=`
`,K}generateDetectedDependenciesSection(Q){let K=`## Detected dependencies
`;if(Q.packageJson.length>0)K+=this.generatePackageJsonSection(Q.packageJson);let X=Q.dependencyFiles.filter((Y)=>Y.path==="composer.json"||Y.path.endsWith("/composer.json")),$=X.filter((Y)=>Y.path==="composer.json"),O=X.filter((Y)=>Y.path!=="composer.json");if($.length>0)K+=this.generateComposerSection($);if(Q.githubActions.length>0)K+=this.generateGitHubActionsSection(Q.githubActions);let q=Q.dependencyFiles.filter((Y)=>!Y.path.endsWith("/composer.json")&&Y.path!=="composer.json");if(q.length>0||O.length>0)K+=this.generateDependencyFilesSection([...q,...O]);return K}generatePackageJsonSection(Q){let K=`<details><summary>npm</summary>
<blockquote>
`;for(let X of Q){let $=X.path.split("/").pop()||X.path;K+=`<details><summary>${$}</summary>
`;let O={dependencies:X.dependencies.filter((q)=>q.type==="dependencies"),devDependencies:X.dependencies.filter((q)=>q.type==="devDependencies"),peerDependencies:X.dependencies.filter((q)=>q.type==="peerDependencies"),optionalDependencies:X.dependencies.filter((q)=>q.type==="optionalDependencies")};for(let[q,Y]of Object.entries(O))if(Y.length>0)for(let W of Y)K+=` - \`${W.name} ${W.currentVersion}\`
`;K+=`
</details>
`}return K+=`</blockquote>
</details>
`,K}generateGitHubActionsSection(Q){let K=`<details><summary>github-actions</summary>
<blockquote>
`;for(let X of Q){K+=`<details><summary>${X.path}</summary>
`;let $=new Map;for(let O of X.dependencies){let q=`${O.name}@${O.currentVersion}`;if(!$.has(q))$.set(q,{name:O.name,currentVersion:O.currentVersion})}for(let O of $.values())K+=` - \`${O.name} ${O.currentVersion}\`
`;K+=`
</details>
`}return K+=`</blockquote>
</details>
`,K}generateDependencyFilesSection(Q){let K=`<details><summary>dependency-files</summary>
<blockquote>
`;for(let X of Q){K+=`<details><summary>${X.path}</summary>
`;for(let $ of X.dependencies)K+=` - \`${$.name} ${$.currentVersion}\`
`;K+=`
</details>
`}return K+=`</blockquote>
</details>
`,K}generateComposerSection(Q){let K=`<details><summary>composer</summary>
<blockquote>
`;for(let X of Q){let $=X.path.split("/").pop()||X.path;K+=`<details><summary>${$}</summary>
`;let O={require:X.dependencies.filter((q)=>q.type==="require"),"require-dev":X.dependencies.filter((q)=>q.type==="require-dev")};for(let[q,Y]of Object.entries(O))if(Y.length>0)for(let W of Y)K+=` - \`${W.name} ${W.currentVersion}\`
`;K+=`
</details>
`}return K+=`</blockquote>
</details>
`,K}generateDeprecatedDependenciesSection(Q){let K=`> [!WARNING]
> These dependencies are deprecated and should be updated to avoid potential security risks and compatibility issues.
| Datasource | Name | Replacement PR? |
|------------|------|-----------------|
`;for(let X of Q){let $=`\`${X.name}\``,q=(X.replacementAvailable?"available":"unavailable")==="available"?"":"";K+=`| ${X.datasource} | ${$} | ${q} |
`}return K+=`
`,K}generateFooter(){return`---
- [ ] <!-- manual job -->Check this box to trigger a request for Buddy Bot to run again on this repository
`}extractPackageInfo(Q){let K=[],X=[/update.*?dependency\s+(\S+)/i,/update\s+require(?:-dev)?\s+(\S+)\s+to\s+v?\d+/i,/update\s+(\S+)\s+to\s+v?\d+/i,/bump\s+(\S+)\s+from/i,/chore\(deps\):\s*update\s+dependency\s+(\S+)/i];for(let O of X){let q=Q.title.match(O);if(q&&q[1]&&!K.includes(q[1]))K.push(q[1])}let $=[{name:"npm",pattern:/### npm Dependencies[\s\S]*?(?=###|\n\n---|z)/i},{name:"pkgx",pattern:/### Launchpad\/pkgx Dependencies[\s\S]*?(?=###|\n\n---|z)/i},{name:"actions",pattern:/### GitHub Actions[\s\S]*?(?=###|\n\n---|z)/i}];for(let O of $){let q=Q.body.match(O.pattern);if(q){let W=q[0].match(/\|\s*\[([^\]]+)\]\([^)]+\)\s*\|/g);if(W)for(let H of W){let E=H.match(/\|\s*\[([^\]]+)\]/);if(E&&E[1]){let Z=E[1].trim();if(Z.includes("`")&&Z.includes("->")){let z=H.match(/\]\(([^)]+)\)/);if(z&&z[1]){let j=z[1].match(/\/diffs\/npm\/([^/]+)\//);if(j&&j[1]){let S=decodeURIComponent(j[1]);if(S&&S.length>1&&!K.includes(S))K.push(S)}}continue}if(!Z.includes("://")&&!Z.includes("Compare Source")&&!Z.includes("badge")&&!Z.includes("!")&&!Z.startsWith("[![")&&!Z.includes("`")&&!Z.includes("->")&&!Z.includes(" -> ")&&!Z.match(/^\d+\.\d+/)&&!Z.includes(" ")&&Z.length>0&&!K.includes(Z))K.push(Z)}}}}if(K.length<3){let O=Q.body.match(/`([^`]+)`/g);if(O)for(let q of O){let Y=q.replace(/`/g,"").trim();if(Y.includes("->")||Y.includes(" -> ")||Y.includes("` -> `")||Y.match(/^\d+\.\d+/)||Y.match(/^v\d+/)||Y.match(/[\d.]+\s*->\s*[\d.]+/)||Y.match(/^[\d.]+$/)||Y.match(/^\d+\.\d+\.\d+/)||Y.match(/^\d+\.\d+\.\d+\./)||Y.match(/^\^?\d+\.\d+/)||Y.match(/^~\d+\.\d+/)||Y.includes("://")||Y.includes("Compare Source")||Y.includes("badge")||Y.includes(" "))continue;if(Y=Y.split(",")[0].trim(),Y&&Y.length>1&&!K.includes(Y)&&(Y.startsWith("@")||Y.includes("/")||Y.match(/^[a-z][a-z0-9.-]*$/i)))K.push(Y)}}return K}applyTemplate(Q,K){return Q.replace(/\{\{repository\.owner\}\}/g,K.repository.owner).replace(/\{\{repository\.name\}\}/g,K.repository.name).replace(/\{\{openPRs\.count\}\}/g,K.openPRs.length.toString()).replace(/\{\{lastUpdated\}\}/g,K.lastUpdated.toISOString()).replace(/\{\{detectedDependencies\.packageJson\.count\}\}/g,K.detectedDependencies.packageJson.length.toString()).replace(/\{\{detectedDependencies\.githubActions\.count\}\}/g,K.detectedDependencies.githubActions.length.toString()).replace(/\{\{detectedDependencies\.dependencyFiles\.count\}\}/g,K.detectedDependencies.dependencyFiles.length.toString())}}var{Glob:qK}=globalThis.Bun;import{readdir as f,readFile as U,stat as k}from"fs/promises";import{join as D}from"path";function YK(Q){return(Q.split(/[/\\]/).pop()||"")==="build.zig.zon"}async function r(Q,K){try{if(!YK(Q))return null;let X=[],$=K.match(/\.dependencies\s*=\s*\.?\{/);if($){let O=$.index+$[0].length,q=1,Y=O;while(Y<K.length&&q>0){if(K[Y]==="{")q++;else if(K[Y]==="}")q--;Y++}let W=K.slice(O,Y-1),H=/\.(\w+)\s*=\s*\.?\{/g,E=H.exec(W);while(E!==null){let Z=E[1],z=E.index+E[0].length,_=1,j=z;while(j<W.length&&_>0){if(W[j]==="{")_++;else if(W[j]==="}")_--;j++}let S=W.slice(z,j-1),G=S.match(/\.url\s*=\s*"([^"]+)"/),w=S.match(/\.hash\s*=\s*"([^"]+)"/);if(G){let C=G[1],V=C.match(/\/v?(\d+\.\d+\.\d+(?:-[\w.]+)?)/i),M=V?V[1]:"unknown",B={url:C};if(w)B.hash=w[1];X.push({name:Z,currentVersion:M,type:"zig-dependencies",file:Q,metadata:B})}E=H.exec(W)}}return{path:Q,type:"build.zig.zon",content:K,dependencies:X}}catch(X){return console.warn(`Failed to parse Zig manifest ${Q}:`,X),null}}class u{projectPath;logger;ignoreGlobs=[];constructor(Q,K,X){this.projectPath=Q;this.logger=K;if(X&&X.length>0)this.ignoreGlobs=X.map(($)=>new qK($)),this.logger.info(`Initialized ${this.ignoreGlobs.length} ignore patterns: ${X.join(", ")}`)}async scanProject(){this.logger.info("Scanning project for package files...");let Q=Date.now(),K=[];try{let X=await this.findFiles("package.json");for(let Z of X){if(this.shouldIgnorePath(Z))continue;let z=await this.parsePackageJsonFile(Z);if(z)K.push(z)}let $=await this.findDependencyFiles();for(let Z of $){if(this.shouldIgnorePath(Z))continue;let z=await this.parseDependencyFile(Z);if(z)K.push(z)}let O=await this.findLockFiles();for(let Z of O){if(this.shouldIgnorePath(Z))continue;let z=await this.parseLockFile(Z);if(z)K.push(z)}let q=await this.findComposerFiles();for(let Z of q){if(this.shouldIgnorePath(Z))continue;let z=await this.parseComposerFile(Z);if(z)K.push(z)}let Y=await this.findGitHubActionsFiles();for(let Z of Y){if(this.shouldIgnorePath(Z))continue;let z=await this.parseGitHubActionsFile(Z);if(z)K.push(z)}let W=await this.findDockerfiles();this.logger.info(`\uD83D\uDD0D Found ${W.length} Dockerfile(s): ${W.join(", ")}`);for(let Z of W){if(this.shouldIgnorePath(Z))continue;let z=await this.parseDockerfile(Z);if(z)K.push(z),this.logger.info(`\uD83D\uDCE6 Parsed Dockerfile: ${Z} with ${z.dependencies.length} dependencies`)}let H=await this.findZigManifests();this.logger.info(`\uD83D\uDD0D Found ${H.length} Zig manifest(s): ${H.join(", ")}`);for(let Z of H){if(this.shouldIgnorePath(Z))continue;let z=await this.parseZigManifestFile(Z);if(z)K.push(z),this.logger.info(`\uD83D\uDCE6 Parsed Zig manifest: ${Z} with ${z.dependencies.length} dependencies`)}let E=Date.now()-Q;return this.logger.success(`Found ${K.length} package files in ${E}ms`),K}catch(X){throw this.logger.error("Failed to scan project:",X),new i(`Failed to scan project: ${X instanceof Error?X.message:"Unknown error"}`)}}async parsePackageJsonFile(Q){try{let K=D(this.projectPath,Q),X=await U(K,"utf-8"),$=JSON.parse(X),O=[];return this.extractDependencies($.dependencies,"dependencies",Q,O),this.extractDependencies($.devDependencies,"devDependencies",Q,O),this.extractDependencies($.peerDependencies,"peerDependencies",Q,O),this.extractDependencies($.optionalDependencies,"optionalDependencies",Q,O),{path:Q,type:"package.json",content:X,dependencies:O}}catch(K){return this.logger.warn(`Failed to parse package.json file ${Q}:`,K),null}}async parseLockFile(Q){try{let K=D(this.projectPath,Q),X=await U(K,"utf-8"),$=Q.split("/").pop()||"",O;if($==="bun.lockb")O="bun.lockb";else if($==="package-lock.json")O="package-lock.json";else if($==="yarn.lock")O="yarn.lock";else if($==="pnpm-lock.yaml")O="pnpm-lock.yaml";else return null;let q=await this.extractLockFileDependencies(X,O,Q);return{path:Q,type:O,content:X,dependencies:q}}catch(K){return this.logger.warn(`Failed to parse lock file ${Q}:`,K),null}}async parseDependencyFile(Q){try{let K=D(this.projectPath,Q),X=await U(K,"utf-8");return await s(Q,X)}catch(K){return this.logger.warn(`Failed to parse dependency file ${Q}:`,K),null}}async findDependencyFiles(){let Q=[],K=["deps.yaml","deps.yml","dependencies.yaml","dependencies.yml","pkgx.yaml","pkgx.yml",".deps.yaml",".deps.yml"];for(let O of K){let q=await this.findFiles(O);Q.push(...q)}let X=await this.findFilesByPattern("*.yaml"),$=await this.findFilesByPattern("*.yml");for(let O of[...X,...$])if(p(O)&&!Q.includes(O))Q.push(O);return Q}async findGitHubActionsFiles(){let Q=[];try{let X=D(this.projectPath,".github","workflows");if((await k(X).catch(()=>null))?.isDirectory()){let O=await this.findFilesByPatternInDir("*.yaml",X),q=await this.findFilesByPatternInDir("*.yml",X);for(let Y of[...O,...q])if(e(Y))Q.push(Y)}}catch{}return Q}async findDockerfiles(){let Q=[];try{let K=["Dockerfile","dockerfile","Dockerfile.dev","Dockerfile.prod","Dockerfile.production","Dockerfile.development","Dockerfile.test","Dockerfile.staging"];for(let $ of K){let O=await this.findFiles($);Q.push(...O)}let X=await this.findFilesByPattern("Dockerfile*");for(let $ of X)if(!Q.includes($)&&t($))Q.push($)}catch{}return Q}async parseGitHubActionsFile(Q){try{let K=D(this.projectPath,Q),X=await U(K,"utf-8");return await KK(Q,X)}catch(K){return this.logger.warn(`Failed to parse GitHub Actions file ${Q}:`,K),null}}async parseDockerfile(Q){try{let K=D(this.projectPath,Q),X=await U(K,"utf-8");return await a(Q,X)}catch(K){return this.logger.warn(`Failed to parse Dockerfile ${Q}:`,K),null}}async findZigManifests(){let Q=[];try{let K=await this.findFiles("build.zig.zon");Q.push(...K)}catch{}return Q}async parseZigManifestFile(Q){try{let K=D(this.projectPath,Q),X=await U(K,"utf-8");return await r(Q,X)}catch(K){return this.logger.warn(`Failed to parse Zig manifest ${Q}:`,K),null}}async findComposerFiles(){let Q=[];try{let K=await this.findFiles("composer.json");Q.push(...K);let X=await this.findFiles("composer.lock");Q.push(...X)}catch{}return Q}async parseComposerFile(Q){try{let K=D(this.projectPath,Q),X=await U(K,"utf-8"),{parseComposerFile:$}=await import("./chunk-a3fv5e8a.js");return await $(Q,X)}catch(K){return this.logger.warn(`Failed to parse Composer file ${Q}:`,K),null}}extractDependencies(Q,K,X,$){if(!Q)return;for(let[O,q]of Object.entries(Q))$.push({name:O,currentVersion:q,type:K,file:this.getRelativePath(X)})}async extractLockFileDependencies(Q,K,X){let $=[];if(K==="package-lock.json")try{let O=JSON.parse(Q);if(O.packages){for(let[q,Y]of Object.entries(O.packages))if(q&&q!==""&&Y&&typeof Y==="object"){let W=Y;if(W.version){let H=q.startsWith("node_modules/")?q.replace("node_modules/",""):q;$.push({name:H,currentVersion:W.version,type:W.dev?"devDependencies":"dependencies",file:this.getRelativePath(X)})}}}}catch(O){this.logger.warn("Failed to parse package-lock.json:",O)}return $}async findFiles(Q,K=this.projectPath){let X=[];try{let $=await f(K);for(let O of $){let q=D(K,O);try{let Y=await k(q);if(Y.isDirectory()){if(!this.shouldSkipDirectory(O)){let W=await this.findFiles(Q,q);X.push(...W)}}else if(Y.isFile()&&O===Q){let W=A("path"),H=W.relative(this.projectPath,q);X.push(H.split(W.sep).join("/"))}}catch{continue}}}catch{}return X}async findLockFiles(){let Q=["bun.lockb","package-lock.json","yarn.lock","pnpm-lock.yaml"],K=[];for(let X of Q){let $=await this.findFiles(X);K.push(...$)}return K}async findFilesByPattern(Q){let K=[],X=Q.replace("*.","");try{let $=await f(this.projectPath);for(let O of $){let q=D(this.projectPath,O),Y=await k(q);if(Y.isFile()&&O.endsWith(`.${X}`)){let W=A("path"),H=W.relative(this.projectPath,q);K.push(H.split(W.sep).join("/"))}else if(Y.isDirectory()&&!this.shouldSkipDirectory(O)){let W=await this.findFilesByPatternInDir(Q,q);K.push(...W)}}}catch{}return K}async findFilesByPatternInDir(Q,K){let X=[],$=Q.replace("*.","");try{let O=await f(K);for(let q of O){let Y=D(K,q),W=await k(Y);if(W.isFile()&&q.endsWith(`.${$}`)){let H=A("path"),E=H.relative(this.projectPath,Y);X.push(E.split(H.sep).join("/"))}else if(W.isDirectory()&&!this.shouldSkipDirectory(q)){let H=await this.findFilesByPatternInDir(Q,Y);X.push(...H)}}}catch{}return X}shouldIgnorePath(Q){if(this.ignoreGlobs.length===0)return!1;let K=Q;if(Q.startsWith("/")||Q.includes(":"))K=this.getRelativePath(Q);for(let X of this.ignoreGlobs)if(X.match(K))return this.logger.debug(`Ignoring path: ${K} (matched pattern)`),!0;return!1}shouldSkipDirectory(Q){return["node_modules",".git",".next",".nuxt","dist","build","coverage",".nyc_output","tmp","temp",".cache",".vscode",".idea"].includes(Q)||Q.startsWith(".")}getRelativePath(Q){let K=A("path");return K.relative(this.projectPath,Q).split(K.sep).join("/")}async getDependencyCount(){return(await this.scanProject()).reduce((K,X)=>K+X.dependencies.length,0)}async getUniqueDependencies(){return(await this.scanProject()).flatMap(($)=>$.dependencies).filter(($,O,q)=>O===q.findIndex((Y)=>Y.name===$.name))}}class g{async checkDeprecatedDependencies(Q){let K=[];for(let X of Q){let $=await this.checkFileForDeprecatedDependencies(X);K.push(...$)}return K}async checkFileForDeprecatedDependencies(Q){let K=[];for(let X of Q.dependencies){let $=await this.checkDependencyDeprecation(X,Q.type);if($.deprecated)K.push({name:X.name,currentVersion:X.currentVersion,datasource:this.getDatasourceFromFileType(Q.type),file:Q.path,type:X.type,replacementAvailable:!1,deprecationMessage:$.message,suggestedReplacement:$.suggestedReplacement})}return K}async checkDependencyDeprecation(Q,K){try{if(K==="package.json"||K.includes("lock"))return await this.checkNpmDeprecation(Q);else if(K==="composer.json"||K==="composer.lock")return await this.checkComposerDeprecation(Q);else if(K==="deps.yaml"||K==="deps.yml"||K.includes("deps"))return await this.checkBunDeprecation(Q);return{deprecated:!1}}catch(X){return console.warn(`Failed to check deprecation for ${Q.name}:`,X),{deprecated:!1}}}async checkNpmDeprecation(Q){try{let K=await fetch(`https://registry.npmjs.org/${Q.name}`);if(!K.ok)return{deprecated:!1};let X=await K.json();if(X.deprecated)return{deprecated:!0,message:X.deprecated,suggestedReplacement:this.extractSuggestedReplacement(X.deprecated)};let $=X.versions?.[Q.currentVersion];if($?.deprecated)return{deprecated:!0,message:$.deprecated,suggestedReplacement:this.extractSuggestedReplacement($.deprecated)};return{deprecated:!1}}catch(K){return console.warn(`Failed to check npm deprecation for ${Q.name}:`,K),{deprecated:!1}}}async checkComposerDeprecation(Q){try{let K=await fetch(`https://packagist.org/packages/${Q.name}.json`);if(!K.ok)return{deprecated:!1};let X=await K.json();if(X.package?.abandoned){let $=typeof X.package.abandoned==="string"?X.package.abandoned:null;return{deprecated:!0,message:`Package is abandoned${$?`, use ${$} instead`:""}`,suggestedReplacement:$||void 0}}return{deprecated:!1}}catch(K){return console.warn(`Failed to check Composer deprecation for ${Q.name}:`,K),{deprecated:!1}}}async checkBunDeprecation(Q){return await this.checkNpmDeprecation(Q)}extractSuggestedReplacement(Q){let K=[/use\s+([a-z0-9@/-]+)\s+instead/i,/replaced\s+by\s+([a-z0-9@/-]+)/i,/migrate\s+to\s+([a-z0-9@/-]+)/i,/switch\s+to\s+([a-z0-9@/-]+)/i];for(let X of K){let $=Q.match(X);if($)return $[1]}return}getDatasourceFromFileType(Q){if(Q==="package.json"||Q.includes("lock"))return"npm";else if(Q==="composer.json"||Q==="composer.lock")return"composer";else if(Q==="deps.yaml"||Q==="deps.yml"||Q.includes("deps"))return"bun";else if(Q==="github-actions")return"github-actions";return"unknown"}}class F{verbose;constructor(Q=!1){this.verbose=Q}info(Q,...K){console.log(Q,...K)}warn(Q,...K){console.warn(Q,...K)}error(Q,...K){console.error(Q,...K)}debug(Q,...K){if(this.verbose)console.log(`\x1B[90m\uD83D\uDC1B\x1B[0m ${Q}`,...K)}success(Q,...K){console.log(`\x1B[32m\u2713\x1B[0m ${Q}`,...K)}static verbose(){return new F(!0)}static quiet(){return new F(!1)}}class HK{config;projectPath;logger;scanner;registryClient;dashboardGenerator;constructor(Q,K=N.cwd()){this.config=Q;this.projectPath=K;this.logger=new F(Q.verbose??!1),this.scanner=new u(this.projectPath,this.logger,this.config.packages?.ignorePaths),this.registryClient=new $K(this.projectPath,this.logger,this.config),this.dashboardGenerator=new h}async scanForUpdates(){let Q=Date.now();this.logger.info("Starting dependency update scan...");try{let K=Date.now(),X=await this.scanner.scanProject();this.logger.info(`\u23F1\uFE0F Package file scan took ${Date.now()-K}ms`);let $=X.reduce((B,v)=>B+v.dependencies.length,0),O=[],q=Date.now();if(this.config.packages?.ignore&&this.config.packages.ignore.length>0)O=(await this.registryClient.getOutdatedPackages()).filter((v)=>!this.config.packages.ignore.includes(v.name));else O=await this.registryClient.getOutdatedPackages();this.logger.info(`\u23F1\uFE0F bun outdated check took ${Date.now()-q}ms (found ${O.length} updates)`);let Y=Date.now(),W=await this.checkDependencyFilesForUpdates(X);this.logger.info(`\u23F1\uFE0F Dependency file checks took ${Date.now()-Y}ms (found ${W.length} updates)`);let H=Date.now(),E=await this.checkGitHubActionsForUpdates(X);this.logger.info(`\u23F1\uFE0F GitHub Actions checks took ${Date.now()-H}ms (found ${E.length} updates)`);let Z=Date.now(),z=await this.checkDockerfilesForUpdates(X);this.logger.info(`\u23F1\uFE0F Docker image checks took ${Date.now()-Z}ms (found ${z.length} updates)`);let _=[...O,...W,...E,...z];if(this.config.packages?.ignore&&this.config.packages.ignore.length>0)_=_.filter((B)=>!this.config.packages.ignore.includes(B.name));let j=Date.now();if(this.config.packages?.strategy)_=this.filterUpdatesByStrategy(_,this.config.packages.strategy);this.logger.info(`\u23F1\uFE0F Strategy filtering took ${Date.now()-j}ms`);let S=Date.now();_=await this.filterUpdatesByMinimumReleaseAge(_),this.logger.info(`\u23F1\uFE0F Minimum release age filtering took ${Date.now()-S}ms`);let G=Date.now();_=QK(_),this.logger.info(`\u23F1\uFE0F Sorting took ${Date.now()-G}ms`);let w=Date.now(),C=this.config.packages?.groups?this.groupUpdatesByConfig(_):o(_);this.logger.info(`\u23F1\uFE0F Grouping took ${Date.now()-w}ms`);let V=Date.now()-Q,M={totalPackages:$,updates:_,groups:C,scannedAt:new Date,duration:V};return this.logger.success(`Scan completed in ${V}ms. Found ${_.length} updates.`),M}catch(K){throw this.logger.error("Failed to scan for updates:",K),K}}async createPullRequests(Q){let K=Date.now();this.logger.info("Creating pull requests for updates...");try{if(!this.config.repository){this.logger.error("\u274C Repository configuration required for PR creation"),this.logger.info("Configure repository.provider, repository.owner, repository.name in buddy-bot.config.ts");return}let X=N.env.BUDDY_BOT_TOKEN||N.env.GITHUB_TOKEN;if(!X){this.logger.error("\u274C GITHUB_TOKEN or BUDDY_BOT_TOKEN environment variable required for PR creation");return}let $=!!N.env.BUDDY_BOT_TOKEN;if(N.env.BUDDY_BOT_TOKEN)console.log("\u2705 BUDDY_BOT_TOKEN detected - workflow permissions enabled"),console.log(`\uD83D\uDD11 Token length: ${N.env.BUDDY_BOT_TOKEN.length} characters`);else console.log("\u26A0\uFE0F BUDDY_BOT_TOKEN not found - workflow permissions disabled"),console.log("\uD83D\uDCA1 Ensure BUDDY_BOT_TOKEN is properly configured in GitHub secrets"),console.log("\uD83D\uDCA1 The workflow should set: env: BUDDY_BOT_TOKEN: ${{ secrets.BUDDY_BOT_TOKEN }}");let O=new c(X,this.config.repository.owner,this.config.repository.name,$),q=new XK(this.config);for(let W of Q.groups)try{let H=Date.now();this.logger.info(`Creating PR for group: ${W.name} (${W.updates.length} updates)`);let E=W.title,Z=Date.now(),z=await q.generateBody(W),_=Date.now()-Z;this.logger.info(`\u23F1\uFE0F PR body generation took ${_}ms`);let j=await O.getPullRequests("open"),S=`buddy-bot/update-${W.name.toLowerCase().replace(/\s+/g,"-")}-`,G=j.find((L)=>(L.title===E||L.head.startsWith(S)||this.isSimilarPRTitle(L.title,E))&&(L.author==="github-actions[bot]"||L.author.includes("buddy")||L.head.startsWith("buddy-bot/"))&&!L.head.includes("renovate/")&&!L.head.includes("dependabot/")&&!L.author.toLowerCase().includes("renovate")&&!L.author.toLowerCase().includes("dependabot"));if(G){if(this.logger.info(`\uD83D\uDD04 Found existing PR #${G.number}: ${G.title}`),this.shouldAutoClosePR(G,W.updates)){this.logger.info(`\uD83D\uDD12 Auto-closing PR #${G.number} due to respectLatest config change`);try{await O.closePullRequest(G.number),await O.deleteBranch(G.head),this.logger.success(`\u2705 Auto-closed PR #${G.number} and deleted branch ${G.head}`);continue}catch(I){this.logger.error(`\u274C Failed to auto-close PR #${G.number}:`,I)}}if(this.checkIfUpdatesMatch(G.body,W.updates)){this.logger.info("\u2705 Existing PR has the same updates, skipping creation");continue}else{this.logger.info("\uD83D\uDD04 Updates differ, will update existing PR with new content");let I=G.head;try{let{spawn:T}=await import("child_process"),x=(y,WK)=>{return new Promise((OK,l)=>{let n=T(y,WK,{stdio:"pipe"});n.on("close",(d)=>{if(d===0)OK();else l(Error(`Git command failed with code ${d}`))}),n.on("error",l)})};await x("git",["checkout","main"]),await x("git",["reset","--hard","HEAD"]),await x("git",["clean","-fd"]),console.log(`\uD83E\uDDF9 Reset to clean main state before updating existing PR ${G.number}`)}catch(T){console.warn("\u26A0\uFE0F Failed to reset to clean state, continuing anyway:",T)}let b=await this.generateAllFileUpdates(W.updates);if(b.length===0)this.logger.warn(`\u2139\uFE0F No file changes generated for existing PR ${G.number}, updating metadata only`);else{this.logger.info(`\uD83D\uDCDD Regenerated ${b.length} file changes for existing PR ${G.number}`);try{let{hasBranchDifferences:T}=await import("./chunk-rn0e8brz.js");if(!await T(b,I))this.logger.info(`\u2139\uFE0F No content differences for ${I}; will merge main to keep branch up-to-date`),await O.commitChanges(I,"chore: merge main to keep branch up-to-date",[]),this.logger.success(`\u2705 Merged main into ${I} to keep it up-to-date`);else await O.commitChanges(I,`${W.title} (updated)`,b),this.logger.success(`\u2705 Updated files in branch ${I} with latest dependency versions`)}catch(T){this.logger.warn("\u26A0\uFE0F Failed to compare branch content, proceeding with commit:",T),await O.commitChanges(I,`${W.title} (updated)`,b),this.logger.success(`\u2705 Updated files in branch ${I} with latest dependency versions`)}}let P=q.generateLabels(W);await O.updatePullRequest(G.number,{title:E,body:z,labels:P,reviewers:this.config.pullRequest?.reviewers,assignees:this.config.pullRequest?.assignees}),this.logger.success(`\u2705 Updated existing PR #${G.number}: ${E}`),this.logger.info(`\uD83D\uDD17 ${G.url}`);continue}}let w=Date.now(),C=`buddy-bot/update-${W.name.toLowerCase().replace(/\s+/g,"-")}-${w}`;await O.createBranch(C,this.config.repository.baseBranch||"main");try{let{spawn:L}=await import("child_process"),R=(I,b)=>{return new Promise((P,T)=>{let x=L(I,b,{stdio:"pipe"});x.on("close",(y)=>{if(y===0)P();else T(Error(`Git command failed with code ${y}`))}),x.on("error",T)})};await R("git",["checkout","main"]),await R("git",["reset","--hard","HEAD"]),await R("git",["clean","-fd"]),console.log(`\uD83E\uDDF9 Reset to clean main state before generating updates for ${W.name}`)}catch(L){console.warn("\u26A0\uFE0F Failed to reset to clean state, continuing anyway:",L)}let V=await this.generateAllFileUpdates(W.updates);if(V.length===0){this.logger.warn(`\u2139\uFE0F No file changes generated for group ${W.name}, skipping PR creation`);continue}let M=!1;for(let L of V)try{let R=await import("fs");if(R.existsSync(L.path)){if(R.readFileSync(L.path,"utf-8")!==L.content){M=!0;break}}else{M=!0;break}}catch{M=!0;break}if(!M){this.logger.warn(`\u2139\uFE0F No actual content changes for group ${W.name}, skipping PR creation`);continue}this.logger.info(`\uD83D\uDCDD Generated ${V.length} file changes for ${W.name}`),await O.commitChanges(C,W.title,V);let B=q.generateLabels(W),v=await O.createPullRequest({title:E,body:z,head:C,base:this.config.repository.baseBranch||"main",draft:!1,reviewers:this.config.pullRequest?.reviewers,assignees:this.config.pullRequest?.assignees,labels:B});this.logger.success(`\u2705 Created PR #${v.number}: ${v.title}`),this.logger.info(`\uD83D\uDD17 ${v.url}`);let m=Date.now()-H;this.logger.info(`\u23F1\uFE0F Total group processing took ${m}ms`)}catch(H){this.logger.error(`\u274C Failed to create PR for group ${W.name}:`,H)}let Y=Date.now()-K;this.logger.success(`\u2705 Completed PR creation for ${Q.groups.length} group(s) in ${Y}ms`)}catch(X){throw this.logger.error("Failed to create pull requests:",X),X}}shouldRespectVersion(Q){if(!(this.config.packages?.respectLatest??!0))return!1;let X=["latest","*","main","master","develop","dev"],$=Q.toLowerCase().trim();return X.includes($)}async checkDependencyFilesForUpdates(Q){let{isDependencyFile:K}=await import("./chunk-3j3vxazw.js"),{resolveDependencyFile:X}=await import("./chunk-f5bh0qas.js"),$=[],O=Q.filter((W)=>K(W.path));this.logger.info(`\u26A1 Checking ${O.length} dependency files in parallel...`);let q=O.map(async(W)=>{try{this.logger.info(`Checking dependency file: ${W.path}`);let H=await X(W.path),E=[];for(let Z of H.allDependencies||[]){if(this.shouldRespectVersion(Z.constraint)){this.logger.debug(`Skipping ${Z.name} - version "${Z.constraint}" should be respected`);continue}if(Z.constraint!==Z.version&&Z.version){let z=Z.constraint.replace(/^[\^~>=<]+/,"");if(!this.isNewerVersion(z,Z.version)){this.logger.debug(`Skipping ${Z.name} - latest (${Z.version}) is not newer than constraint (${z})`);continue}let _=this.getUpdateType(z,Z.version),j=Z.version;E.push({name:Z.name,currentVersion:Z.constraint,newVersion:j,updateType:_,dependencyType:"dependencies",file:W.path,metadata:void 0,releaseNotesUrl:void 0,changelogUrl:void 0,homepage:void 0})}}return E}catch(H){return this.logger.error(`Failed to check dependency file ${W.path}:`,H),[]}}),Y=await Promise.all(q);return $.push(...Y.flat()),$}async checkGitHubActionsForUpdates(Q){let{isGitHubActionsFile:K}=await import("./chunk-j0phfedr.js"),{fetchLatestActionVersion:X}=await import("./chunk-j0phfedr.js"),$=[],O=Q.filter((Z)=>K(Z.path));this.logger.info(`\uD83D\uDD0D Found ${O.length} GitHub Actions workflow files`);let q=[];for(let Z of O){let z=Z.dependencies.filter((_)=>_.type==="github-actions");this.logger.info(`Found ${z.length} GitHub Actions in ${Z.path}`),z.forEach((_)=>q.push({file:Z,dep:_}))}this.logger.info(`\u26A1 Checking ${q.length} GitHub Actions in parallel...`);let Y=q.map(async({file:Z,dep:z})=>{try{this.logger.info(`Checking action: ${z.name}@${z.currentVersion}`);let _=await X(z.name);if(_)if(this.logger.info(`Latest version for ${z.name}: ${_}`),_!==z.currentVersion){let j=this.getUpdateType(z.currentVersion,_);return this.logger.info(`Update available: ${z.name} ${z.currentVersion} \u2192 ${_} (${j})`),{name:z.name,currentVersion:z.currentVersion,newVersion:_,updateType:j,dependencyType:"github-actions",file:Z.path,metadata:void 0,releaseNotesUrl:`https://github.com/${z.name}/releases`,changelogUrl:void 0,homepage:`https://github.com/${z.name}`}}else this.logger.info(`No update needed for ${z.name}: already at ${_}`);else this.logger.warn(`Could not fetch latest version for ${z.name}`);return null}catch(_){return this.logger.warn(`Failed to check version for action ${z.name}:`,_),null}}),H=(await Promise.all(Y)).filter((Z)=>Z!==null);$.push(...H),this.logger.info(`Generated ${$.length} GitHub Actions updates`);let E=$.reduce((Z,z)=>{if(!Z.find((j)=>j.name===z.name&&j.currentVersion===z.currentVersion&&j.newVersion===z.newVersion&&j.file===z.file))Z.push(z);return Z},[]);return this.logger.info(`After deduplication: ${E.length} unique GitHub Actions updates`),E}async checkDockerfilesForUpdates(Q){let{isDockerfile:K}=await import("./chunk-ma13sjrm.js"),{fetchLatestDockerImageVersion:X}=await import("./chunk-ma13sjrm.js"),$=[],O=Q.filter((Z)=>K(Z.path));this.logger.info(`\uD83D\uDD0D Found ${O.length} Dockerfile(s)`);let q=[];for(let Z of O){let z=Z.dependencies.filter((_)=>_.type==="docker-image");this.logger.info(`Found ${z.length} Docker images in ${Z.path}`),z.forEach((_)=>q.push({file:Z,dep:_}))}this.logger.info(`\u26A1 Checking ${q.length} Docker images in parallel...`);let Y=q.map(async({file:Z,dep:z})=>{try{if(this.logger.info(`Checking Docker image: ${z.name}:${z.currentVersion}`),this.shouldRespectVersion(z.currentVersion))return this.logger.debug(`Skipping ${z.name} - version "${z.currentVersion}" should be respected`),null;let _=await X(z.name);if(_)if(this.logger.info(`Latest version for ${z.name}: ${_}`),_!==z.currentVersion){let j=this.getUpdateType(z.currentVersion,_);return this.logger.info(`Update available: ${z.name} ${z.currentVersion} \u2192 ${_} (${j})`),{name:z.name,currentVersion:z.currentVersion,newVersion:_,updateType:j,dependencyType:"docker-image",file:Z.path,metadata:void 0,releaseNotesUrl:`https://hub.docker.com/r/${z.name}/tags`,changelogUrl:void 0,homepage:`https://hub.docker.com/r/${z.name}`}}else this.logger.info(`No update needed for ${z.name}: already at ${_}`);else this.logger.warn(`Could not fetch latest version for Docker image ${z.name}`);return null}catch(_){return this.logger.warn(`Failed to check version for Docker image ${z.name}:`,_),null}}),H=(await Promise.all(Y)).filter((Z)=>Z!==null);$.push(...H),this.logger.info(`Generated ${$.length} Docker image updates`);let E=$.reduce((Z,z)=>{if(!Z.find((j)=>j.name===z.name&&j.currentVersion===z.currentVersion&&j.newVersion===z.newVersion&&j.file===z.file))Z.push(z);return Z},[]);return this.logger.info(`After deduplication: ${E.length} unique Docker image updates`),E}getUpdateType(Q,K){try{let X=Q.replace(/^[v^~>=<@]+/,""),$=K.replace(/^[v^~>=<@]+/,""),O=(W)=>{let H=W.split(".");while(H.length<3)H.push("0");return H.join(".")};if(X=O(X),$=O($),Bun.semver.order($,X)<=0)return"patch";let q=X.split(".").map(Number),Y=$.split(".").map(Number);if(Y[0]>q[0])return"major";if(Y[0]===q[0]&&Y[1]>q[1])return"minor";return"patch"}catch{return"patch"}}isNewerVersion(Q,K){try{return Bun.semver.order(K.replace(/^[v^~>=<@]+/,""),Q.replace(/^[v^~>=<@]+/,""))>0}catch{return!1}}async generateAllFileUpdates(Q){let K=[],X=Q.filter((H)=>H.file.endsWith("package.json")&&!H.file.includes(".yaml")&&!H.file.includes(".yml")&&!H.file.includes(".github/workflows/")),$=new Map;for(let H of X){if(!$.has(H.file))$.set(H.file,[]);$.get(H.file).push(H)}for(let[H,E]of $)try{let Z=ZK.readFileSync(H,"utf-8"),z=JSON.parse(Z);for(let _ of E){let j=!1,S=_.name.replace(/\s*\(dev\)$/,"").replace(/\s*\(peer\)$/,"").replace(/\s*\(optional\)$/,""),G=["dependencies","devDependencies","peerDependencies","optionalDependencies"];for(let w of G)if(z[w]&&z[w][S]){let V=z[w][S].match(/^(\D*)/),M=V?V[1]:"",B=S.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),v=w.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),m=new RegExp(`("${v}"\\s*:\\s*\\{[^}]*?)("${B}"\\s*:\\s*")([^"]+)(")([^}]*?\\})`,"gs"),L=`${M}${_.newVersion}`;Z=Z.replace(m,`$1$2${L}$4$5`),j=!0;break}if(!j)console.warn(`Package ${S} not found in ${H}`)}K.push({path:H,content:Z,type:"update"})}catch(Z){console.warn(`Failed to update ${H}:`,Z)}let O=Q.filter((H)=>{let E=H.file.toLowerCase();return(E.endsWith("deps.yaml")||E.endsWith("deps.yml")||E.endsWith("dependencies.yaml")||E.endsWith("dependencies.yml"))&&!H.file.includes(".github/workflows/")});if(O.length>0)try{let{generateDependencyFileUpdates:H}=await import("./chunk-3j3vxazw.js"),E=await H(O);K.push(...E)}catch(H){this.logger.error("Failed to generate dependency file updates:",H)}let q=Q.filter((H)=>H.file.endsWith("composer.json")||H.file.endsWith("composer.lock"));if(q.length>0)try{let{generateComposerUpdates:H}=await import("./chunk-a3fv5e8a.js"),E=await H(q);K.push(...E)}catch(H){this.logger.error("Failed to generate Composer updates:",H)}let Y=Q.filter((H)=>H.file.includes(".github/workflows/"));if(Y.length>0)try{let{generateGitHubActionsUpdates:H}=await import("./chunk-j0phfedr.js"),E=await H(Y);K.push(...E)}catch(H){this.logger.error("Failed to generate GitHub Actions updates:",H)}let W=Q.filter((H)=>H.dependencyType==="docker-image");if(W.length>0)try{let{generateDockerfileUpdates:H}=await import("./chunk-ma13sjrm.js"),E=await H(W);K.push(...E)}catch(H){this.logger.error("Failed to generate Dockerfile updates:",H)}return K}async run(){let Q=await this.scanForUpdates();if(Q.updates.length===0)return this.logger.info("No updates available!"),Q;if(this.config.pullRequest)await this.createPullRequests(Q);return Q}async checkPackages(Q){return this.logger.info(`Checking specific packages: ${Q.join(", ")}`),this.registryClient.getUpdatesForPackages(Q)}async checkPackagesWithPattern(Q){return this.logger.info(`Checking packages with pattern: ${Q}`),this.registryClient.getUpdatesWithPattern(Q)}filterUpdatesByStrategy(Q,K){if(K==="all")return Q;return Q.filter((X)=>{switch(K){case"major":return X.updateType==="major";case"minor":return X.updateType==="major"||X.updateType==="minor";case"patch":return!0;default:return!0}})}async filterUpdatesByMinimumReleaseAge(Q){let K=this.config.packages?.minimumReleaseAge??0;if(K===0)return Q;this.logger.info(`Applying minimum release age filter (${K} minutes) in parallel...`);let X=Q.map(async(Y)=>{try{if(await this.registryClient.meetsMinimumReleaseAge(Y.name,Y.newVersion,Y.dependencyType))return Y;else return this.logger.debug(`Filtered out ${Y.name}@${Y.newVersion} (${Y.dependencyType}) due to minimum release age requirement`),null}catch(W){return this.logger.warn(`Error checking release age for ${Y.name}@${Y.newVersion} (${Y.dependencyType}), including update:`,W),Y}}),O=(await Promise.all(X)).filter((Y)=>Y!==null),q=Q.length-O.length;if(q>0)this.logger.info(`Filtered out ${q} updates due to minimum release age requirement`);return O}groupUpdatesByConfig(Q){let K=[],X=[...Q];if(this.config.packages?.groups)for(let $ of this.config.packages.groups){let O=[];for(let q of $.patterns){let Y=new RegExp(q.replace("*",".*")),W=X.filter((H)=>Y.test(H.name));O.push(...W),W.forEach((H)=>{let E=X.indexOf(H);if(E>-1)X.splice(E,1)})}if(O.length>0){let q=O;if($.strategy)q=this.filterUpdatesByStrategy(O,$.strategy);K.push({name:$.name,updates:q,updateType:this.getHighestUpdateType(q),title:`chore(deps): update ${$.name}`,body:`Update ${q.length} packages in ${$.name} group`})}}if(X.length>0){let $=o(X);K.push(...$)}return K}getHighestUpdateType(Q){if(Q.some((K)=>K.updateType==="major"))return"major";if(Q.some((K)=>K.updateType==="minor"))return"minor";return"patch"}isSimilarPRTitle(Q,K){if(Q.toLowerCase()===K.toLowerCase())return!0;if(K.toLowerCase().includes("update dependency ")){let O=K.match(/update dependency (\S+)/i),q=Q.match(/update dependency (\S+)/i);if(O&&q)return O[1]===q[1]}let X=Q.toLowerCase(),$=K.toLowerCase();if(X.includes("all non-major")&&$.includes("dependency ")||$.includes("all non-major")&&X.includes("dependency "))return!1;if(X.includes("dependency ")&&$.includes("dependency "))return!1;return!1}checkIfUpdatesMatch(Q,K){if(!Q)return!1;let X=/([\w@\-./]+):\s*(\d+\.\d+\.\d\S*)\s*\u2192\s*(\d+\.\d+\.\d\S*)/g,$=new Map,O;while((O=X.exec(Q))!==null){let[,q,Y,W]=O;$.set(q,{from:Y,to:W})}for(let q of K){let Y=$.get(q.name);if(!Y||Y.to!==q.newVersion)return!1}return $.size===K.length}generatePRLabels(Q){let K=new Set;K.add("dependencies");let X=Q.updates.map((Y)=>Y.updateType),$={major:X.includes("major"),minor:X.includes("minor"),patch:X.includes("patch")};if($.major)K.add("major");if($.minor)K.add("minor");if($.patch)K.add("patch");if(Q.updates.length>5)K.add("dependencies");let O=["helmet","express-rate-limit","cors","bcrypt","jsonwebtoken"];if(Q.updates.some((Y)=>O.some((W)=>Y.name.includes(W))))K.add("security");if(this.config.pullRequest?.labels)this.config.pullRequest.labels.forEach((Y)=>{if(Y!=="dependencies")K.add(Y)});return Array.from(K)}shouldAutoClosePR(Q,K){if(this.shouldAutoCloseForRespectLatest(Q))return!0;if(this.shouldAutoCloseForIgnorePaths(Q))return!0;if(this.shouldAutoCloseForRemovedFiles(Q))return!0;return!1}shouldAutoCloseForRespectLatest(Q){if(!(this.config.packages?.respectLatest??!0))return!1;let X=["latest","*","main","master","develop","dev"],$=Q.body.toLowerCase();if(!X.some((W)=>$.includes(W.toLowerCase())))return!1;return this.extractPackagesFromPRBody(Q.body).filter((W)=>{let H=new RegExp(`\\|\\s*\\[${W.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}\\]\\([^)]+\\)\\s*\\|\\s*([^|]+)\\s*\\|`,"i"),E=Q.body.match(H);if(!E){let j=new RegExp(`${W.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}[^\\w]*[:=]\\s*["']?([^"'
]+)["']?`,"i"),S=Q.body.match(j);if(!S)return!1;let G=S[1].toLowerCase().trim();return X.includes(G)}let z=E[1].trim().match(/^([^\u2192]+)\u2192/);if(!z)return!1;let _=z[1].trim().toLowerCase();return X.includes(_)}).length>0}shouldAutoCloseForIgnorePaths(Q){let K=this.config.packages?.ignorePaths;if(!K||K.length===0)return!1;let X=this.extractFilePathsFromPRBody(Q.body);if(X.length===0)return!1;let{Glob:$}=globalThis.Bun,O=X.filter((q)=>{let Y=q.replace(/^\.\//,"");return K.some((W)=>{try{return new $(W).match(Y)}catch(H){return this.logger.debug(`Failed to match path ${Y} against pattern ${W}: ${H}`),!1}})});if(O.length>0)return this.logger.debug(`PR #${Q.number} contains files now in ignorePaths: ${O.join(", ")}`),!0;return!1}shouldAutoCloseForRemovedFiles(Q){try{let K=this.extractFilePathsFromPRBody(Q.body);if(K.length===0)return!1;let X=A("fs"),$=A("path"),O=K.filter((q)=>{let Y=$.join(this.projectPath,q);return!X.existsSync(Y)});if(O.length>0){if(this.logger.info(`PR #${Q.number} references removed files: ${O.join(", ")}`),this.config.packages?.ignorePaths&&this.config.packages.ignorePaths.length>0){let{Glob:Z}=globalThis.Bun;if(O.filter((_)=>{return!this.config.packages.ignorePaths.some((j)=>{try{return new Z(j).match(_)}catch(S){return this.logger.warn(`Invalid glob pattern '${j}':`,S),!1}})}).length>0)return this.logger.info(`Some removed files are outside ignored paths - not auto-closing PR #${Q.number}`),!1}if(O.some((Z)=>Z.endsWith("composer.json")))return this.logger.info(`composer.json was removed - PR #${Q.number} should be auto-closed`),!0;let Y=Q.body.toLowerCase(),W=Y.includes("composer")||O.some((Z)=>Z.includes("composer")),H=Y.includes("package.json")||O.some((Z)=>Z.includes("package.json")),E=O.some((Z)=>Z.endsWith("deps.yaml")||Z.endsWith("deps.yml")||Z.endsWith("dependencies.yaml")||Z.endsWith("dependencies.yml"));if(W||H||E)return this.logger.info(`PR #${Q.number} is about removed dependency system - should be auto-closed`),!0}return!1}catch(K){return this.logger.debug(`Failed to check for removed files in PR #${Q.number}: ${K}`),!1}}extractFilePathsFromPRBody(Q){let K=[],X=/\|\s*\[[^\]]+\]\([^)]*\)\s*\|[^|]*\|\s*\*\*([^*]+)\*\*\s*\|/g,$;while(($=X.exec(Q))!==null){let W=$[1].trim();if(W&&!K.includes(W))K.push(W)}let O=/\*\*([^*]+\.(?:json|yaml|yml|lock))\*\*/g;while(($=O.exec(Q))!==null){let W=$[1].trim();if(W&&!K.includes(W))K.push(W)}let q=/\|[^|]+\|[^|]+\|([^|]+)\|[^|]*\|/g;while(($=q.exec(Q))!==null){let W=$[1].trim();if(W&&(W.includes("/")||/\.(?:json|yaml|yml|lock)$/.test(W))&&!K.includes(W))K.push(W)}let Y=/(?:^|\s)([\w-]+(?:\/[\w.-]+)*\/[\w.-]+\.(?:json|yaml|yml|lock))(?:\s|$)/gm;while(($=Y.exec(Q))!==null){let W=$[1].trim();if(W&&!K.includes(W))K.push(W)}return K}extractPackagesFromPRBody(Q){let K=[],X=Q.match(/\|[^|]*\|[^|]*\|[^|]*\|[^|]*\|/g);if(X)for(let O of X){let q=O.match(/\[([^\]]+)\]\([^)]*\)/);if(q)K.push(q[1])}let $=Q.match(/<summary>([^<]+)<\/summary>/g);if($)for(let O of $){let q=O.replace(/<summary>/,"").replace(/<\/summary>/,"").trim();if(q&&!K.includes(q))K.push(q)}return K}extractPackageUpdatesFromPRBody(Q){let K=[],X=/\|\s*\[([^\]]+)\][^|]*\|\s*\[?`\^?([^`]+)`\s*->\s*`\^?([^`]+)`\]?/g,$;while(($=X.exec(Q))!==null){let[,O,q,Y]=$;K.push({name:O,currentVersion:q,newVersion:Y})}return K}getConfig(){return this.config}getProjectPath(){return this.projectPath}async checkAndCloseObsoletePRs(Q,K=!1){try{this.logger.info("\uD83D\uDD0D Scanning for obsolete PRs due to removed dependency files...");let $=(await Q.getPullRequests("open")).filter((q)=>q.head.startsWith("buddy-bot/")||q.author==="github-actions[bot]"||q.labels.includes("dependencies")||q.labels.includes("dependency")||q.title.toLowerCase().includes("update")||q.title.toLowerCase().includes("chore(deps)")||q.title.toLowerCase().includes("composer"));this.logger.info(`Found ${$.length} dependency-related PRs to check`);let O=0;for(let q of $)try{if(this.shouldAutoCloseForRemovedFiles(q))if(this.logger.info(`\uD83D\uDD12 PR #${q.number} should be auto-closed: ${q.title}`),K)this.logger.info(`\uD83D\uDD0D [DRY RUN] Would auto-close PR #${q.number}`),O++;else try{let W=this.generateCloseReason(q);try{await Q.createComment(q.number,W)}catch(H){this.logger.warn(`\u26A0\uFE0F Could not add close reason comment to PR #${q.number}:`,H)}if(await Q.closePullRequest(q.number),q.head.startsWith("buddy-bot/"))try{await Q.deleteBranch(q.head),this.logger.success(`\u2705 Auto-closed PR #${q.number} and deleted branch ${q.head}`)}catch(H){this.logger.warn(`\u26A0\uFE0F Auto-closed PR #${q.number} but failed to delete branch: ${H}`)}else this.logger.success(`\u2705 Auto-closed PR #${q.number}`);O++}catch(W){this.logger.error(`\u274C Failed to auto-close PR #${q.number}:`,W)}}catch(Y){this.logger.warn(`\u26A0\uFE0F Error checking PR #${q.number}:`,Y)}if(O>0)this.logger.success(`\u2705 ${K?"Would auto-close":"Auto-closed"} ${O} obsolete PR(s)`);else this.logger.info("\uD83D\uDCCB No obsolete PRs found")}catch(X){throw this.logger.error("Failed to check for obsolete PRs:",X),X}}async checkAndCloseSatisfiedPRs(Q,K=!1){try{this.logger.info("\uD83D\uDD0D Checking for PRs where dependencies are already at target version...");let $=(await Q.getPullRequests("open")).filter((W)=>W.head.startsWith("buddy-bot/")||W.author==="github-actions[bot]"||W.labels.includes("dependencies")||W.labels.includes("dependency"));this.logger.info(`Found ${$.length} dependency PRs to validate`);let O=0,q=await this.scanForUpdates(),Y=new Map;for(let W of q.updates)Y.set(W.name,W);for(let W of $)try{let H=this.extractPackageUpdatesFromPRBody(W.body);if(H.length===0){this.logger.debug(`PR #${W.number}: Could not extract package updates, skipping`);continue}if(H.every((Z)=>{let z=Y.get(Z.name);if(!z)return this.logger.debug(`PR #${W.number}: ${Z.name} not in current scan (satisfied - no longer needs direct update)`),!0;if(z.newVersion===Z.newVersion)return this.logger.debug(`PR #${W.number}: ${Z.name} still needs update to ${Z.newVersion}`),!1;try{if(!this.isNewerVersion(z.currentVersion,Z.newVersion))return this.logger.debug(`PR #${W.number}: ${Z.name} already at or beyond ${Z.newVersion}`),!0}catch{return!1}return!1}))if(this.logger.info(`\u2705 PR #${W.number} is satisfied (dependencies at target version): ${W.title}`),K)this.logger.info(`\uD83D\uDD0D [DRY RUN] Would close PR #${W.number}`),O++;else try{let Z=H.filter((j)=>!Y.has(j.name)),z=H.filter((j)=>{let S=Y.get(j.name);if(!S)return!1;try{return!this.isNewerVersion(S.currentVersion,j.newVersion)}catch{return!1}}),_=`\uD83E\uDD16 **Auto-closing satisfied PR**
`;if(Z.length>0)_+=`This PR was automatically closed because the following packages are no longer direct dependencies (possibly moved to peer dependencies, transitive dependencies, or removed):
`,Z.forEach((j)=>{_+=`- **${j.name}**: ${j.currentVersion} \u2192 ${j.newVersion}
`}),_+=`
`;if(z.length>0)_+=`The following packages are already at the target version or newer:
`,z.forEach((j)=>{_+=`- **${j.name}**: ${j.currentVersion} \u2192 ${j.newVersion}
`}),_+=`
`;_+="If this was closed in error, please reopen and add a comment explaining why.";try{await Q.createComment(W.number,_)}catch(j){this.logger.warn(`\u26A0\uFE0F Could not add comment to PR #${W.number}:`,j)}if(await Q.closePullRequest(W.number),W.head.startsWith("buddy-bot/"))try{await Q.deleteBranch(W.head),this.logger.success(`\u2705 Closed PR #${W.number} and deleted branch ${W.head}`)}catch(j){this.logger.warn(`\u26A0\uFE0F Closed PR #${W.number} but failed to delete branch: ${j}`)}else this.logger.success(`\u2705 Closed PR #${W.number}`);O++}catch(Z){this.logger.error(`\u274C Failed to close PR #${W.number}:`,Z)}}catch(H){this.logger.warn(`\u26A0\uFE0F Error validating PR #${W.number}:`,H)}if(O>0)this.logger.success(`\u2705 ${K?"Would close":"Closed"} ${O} satisfied PR(s)`);else this.logger.info("\uD83D\uDCCB No satisfied PRs found")}catch(X){throw this.logger.error("Failed to check for satisfied PRs:",X),X}}generateCloseReason(Q){let X=this.extractFilePathsFromPRBody(Q.body).filter((W)=>{let H=A("fs"),Z=A("path").join(this.projectPath,W);return!H.existsSync(Z)}),$=X.some((W)=>W.includes("composer")),O=X.some((W)=>W.includes("package.json")),q=X.some((W)=>W.endsWith("deps.yaml")||W.endsWith("deps.yml")||W.endsWith("dependencies.yaml")||W.endsWith("dependencies.yml")),Y=`\uD83E\uDD16 **Auto-closing obsolete PR**
`;if($)Y+="This PR was automatically closed because `composer.json` has been removed from the project, indicating that Composer is no longer used for dependency management.\n\n";else if(O)Y+="This PR was automatically closed because `package.json` has been removed from the project, indicating that npm/yarn/pnpm is no longer used for dependency management.\n\n";else if(q)Y+=`This PR was automatically closed because the dependency files it references have been removed from the project.
`;else Y+=`This PR was automatically closed because the dependency files it references are no longer present in the project.
`;if(X.length>0)Y+=`**Removed files:**
${X.map((W)=>`- \`${W}\``).join(`
`)}
`;return Y+="If this was closed in error, please reopen the PR and update the dependency files accordingly.",Y}async createOrUpdateDashboard(){try{if(this.logger.info("Creating or updating dependency dashboard..."),!this.config.repository)throw Error("Repository configuration is required for dashboard");if(this.config.repository.provider!=="github")throw Error("Dashboard is currently only supported for GitHub repositories");let Q=this.config.repository.token||N.env.BUDDY_BOT_TOKEN||N.env.GITHUB_TOKEN||"",K=!!N.env.BUDDY_BOT_TOKEN,X=new c(Q,this.config.repository.owner,this.config.repository.name,K),$=await this.collectDashboardData(X),O=this.config.dashboard||{},{title:q,body:Y}=this.dashboardGenerator.generateDashboard($,{showOpenPRs:O.showOpenPRs??!0,showDetectedDependencies:O.showDetectedDependencies??!0,showDeprecatedDependencies:O.showDeprecatedDependencies??!0,bodyTemplate:O.bodyTemplate}),W=await this.findExistingDashboard(X,O.issueNumber),H;if(W)this.logger.info(`Updating existing dashboard issue #${W.number}`),H=await X.updateIssue(W.number,{title:O.title||q,body:Y,labels:O.labels||["dependencies","dashboard"],assignees:O.assignees}),this.logger.success(`\u2705 Successfully updated dashboard issue #${H.number}`);else{this.logger.info("Creating new dashboard issue"),this.logger.info("Performing final check for existing dashboards before creation...");let E=await this.findExistingDashboard(X,O.issueNumber);if(E)this.logger.info(`Race condition detected! Found existing dashboard #${E.number} during final check`),H=await X.updateIssue(E.number,{title:O.title||q,body:Y,labels:O.labels||["dependencies","dashboard"],assignees:O.assignees}),this.logger.success(`\u2705 Updated existing dashboard issue #${H.number} (race condition avoided)`);else H=await X.createIssue({title:O.title||q,body:Y,labels:O.labels||["dependencies","dashboard"],assignees:O.assignees}),this.logger.success(`\u2705 Successfully created new dashboard issue #${H.number}`)}return this.logger.success(`\u2705 Dashboard updated: ${H.url}`),H}catch(Q){throw this.logger.error("Failed to create or update dashboard:",Q),Q}}async collectDashboardData(Q){let[K,X]=await Promise.all([this.scanner.scanProject(),Q.getPullRequests("open")]),$=X.filter((E)=>E.labels.includes("dependencies")||E.labels.includes("dependency")||E.labels.includes("deps")||E.title.toLowerCase().includes("update")||E.title.toLowerCase().includes("chore(deps)")||E.title.toLowerCase().includes("bump")||E.title.toLowerCase().includes("upgrade")||E.title.toLowerCase().includes("renovate")||E.head.includes("renovate/")||E.head.includes("dependabot/")||E.head.includes("buddy-bot/")||E.head.includes("update-")||E.head.includes("bump-")),O=K.filter((E)=>E.type==="package.json"),q=K.filter((E)=>E.path.includes(".github/workflows/")&&(E.path.endsWith(".yml")||E.path.endsWith(".yaml"))),Y=K.filter((E)=>!E.path.includes(".github/workflows/")&&E.type!=="package.json"),H=await new g().checkDeprecatedDependencies(K);return{openPRs:$,detectedDependencies:{packageJson:O,dependencyFiles:Y,githubActions:q},deprecatedDependencies:H,repository:{owner:this.config.repository.owner,name:this.config.repository.name,provider:this.config.repository.provider},lastUpdated:new Date}}async findExistingDashboard(Q,K){try{if(this.logger.info("Searching for existing dashboard issue..."),K){this.logger.info(`Looking for specific dashboard issue #${K}`);let W=(await Q.getIssues("open")).find((H)=>H.number===K);if(W)return this.logger.info(`Found specified dashboard issue #${W.number}: ${W.title}`),W;else return this.logger.warn(`Specified dashboard issue #${K} not found`),null}let X=await Q.getIssues("open");this.logger.info(`Found ${X.length} open issues to search through`);for(let Y of X){let W=Y.labels.includes("dashboard")&&Y.labels.includes("dependencies"),H=Y.title.toLowerCase().includes("dependency dashboard"),E=Y.body.includes("This issue lists Buddy Bot updates and detected dependencies");if(W&&(H||E))return this.logger.info(`Found existing dashboard issue #${Y.number}: ${Y.title}`),this.logger.info(` - Labels: ${Y.labels.join(", ")}`),this.logger.info(` - Title matches: ${H}`),this.logger.info(` - Body has marker: ${E}`),Y}let $=X.filter((Y)=>Y.labels.includes("dashboard")),O=X.filter((Y)=>Y.labels.includes("dependencies")),q=X.filter((Y)=>Y.title.toLowerCase().includes("dependency dashboard"));if(this.logger.info("Dashboard search results:"),this.logger.info(` - Issues with 'dashboard' label: ${$.length}`),this.logger.info(` - Issues with 'dependencies' label: ${O.length}`),this.logger.info(` - Issues with 'dependency dashboard' in title: ${q.length}`),$.length>0){this.logger.info("Issues with 'dashboard' label:");for(let Y of $)this.logger.info(` - #${Y.number}: ${Y.title} (labels: ${Y.labels.join(", ")})`)}return this.logger.info("No existing dashboard issue found"),null}catch(X){return this.logger.warn(`Failed to search for existing dashboard: ${X}`),null}}}export{HK as Buddy};
export{u as c,F as d,HK as e};
//# debugId=03BC36B0006DE49764756E2164756E21