buddy-bot
Version:
Automated & optimized dependency updates for JavaScript & TypeScript projects. Like Renovate & Dependabot.
9 lines (7 loc) • 20.5 kB
JavaScript
// @bun
import{z as U}from"./chunk-eywnktd9.js";import"./chunk-956t2kcb.js";import"./chunk-5fv4yjx7.js";import{D as R,H as T}from"./chunk-cmr43ay2.js";import{spawn as f}from"child_process";import I from"fs";import L from"path";import y from"process";class E extends Error{code;details;constructor(z,H,K){super(z);this.code=H;this.details=K;this.name="BuddyError"}}class B extends E{packageName;constructor(z,H){super(z,"REGISTRY_ERROR");this.packageName=H;this.name="PackageRegistryError"}}class w extends E{operation;constructor(z,H){super(z,"GIT_PROVIDER_ERROR");this.operation=H;this.name="GitProviderError"}}class M extends E{configKey;constructor(z,H){super(z,"CONFIG_ERROR");this.configKey=H;this.name="ConfigurationError"}}class P{projectPath;logger;config;constructor(z,H,K=void 0){this.projectPath=z;this.logger=H;this.config=K}shouldRespectVersion(z){if(!(this.config?.packages?.respectLatest??!0))return!1;let K=["latest","*","main","master","develop","dev"],X=z.toLowerCase().trim();return K.includes(X)}async getOutdatedPackages(z){this.logger.info("Checking for outdated packages...");try{let H=await this.runBunOutdated(z),K=await this.getWorkspaceOutdatedPackages(),X=await this.getPackageJsonOutdated(z),Z=new Map;for(let Q of H)Z.set(Q.name,Q);for(let Q of K)if(!Z.has(Q.name))Z.set(Q.name,Q);for(let Q of X)if(!Z.get(Q.name))Z.set(Q.name,Q);let Y=[];for(let Q of Z.values()){if(this.shouldRespectVersion(Q.current)){this.logger.debug(`Skipping ${Q.name} - version "${Q.current}" should be respected`);continue}let $=U(Q.current,Q.latest),x=await this.getPackageMetadata(Q.name),G=await this.findPackageLocation(Q.name);Y.push({name:Q.name,currentVersion:Q.current,newVersion:Q.latest,updateType:$,dependencyType:"dependencies",file:G||"package.json",metadata:x,releaseNotesUrl:this.getReleaseNotesUrl(Q.name,x),changelogUrl:this.getChangelogUrl(Q.name,x),homepage:x?.homepage})}let W=L.join(this.projectPath,"composer.json");if(I.existsSync(W))try{let Q=await this.getComposerOutdatedPackages();Y.push(...Q)}catch(Q){this.logger.warn("Failed to check Composer packages:",Q)}return this.logger.success(`Found ${Y.length} package updates`),Y}catch(H){throw this.logger.error("Failed to check for outdated packages:",H),new B(`Failed to check for outdated packages: ${H instanceof Error?H.message:"Unknown error"}`)}}async getUpdatesForPackages(z){let H=z.join(" ");return this.getOutdatedPackages(H)}async getUpdatesWithPattern(z){return this.getOutdatedPackages(z)}async getPackageMetadata(z){try{let H=await this.runCommand("bun",["info",z,"--json"]),K=JSON.parse(H);return{name:K.name,description:K.description,repository:typeof K.repository==="string"?K.repository:K.repository?.url,homepage:K.homepage,license:K.license,author:typeof K.author==="string"?K.author:K.author?.name,keywords:K.keywords,latestVersion:K.version,versions:K.versions||[K.version],weeklyDownloads:void 0,dependencies:K.dependencies,devDependencies:K.devDependencies,peerDependencies:K.peerDependencies}}catch(H){this.logger.warn(`Failed to get metadata for ${z}:`,H);return}}async packageExists(z){try{return await this.runCommand("bun",["info",z]),!0}catch{return!1}}async getLatestVersion(z){try{let H=await this.getLatestVersionFromNpm(z);if(H)return H;let K=await this.runCommand("bun",["info",z,"--json"]);return JSON.parse(K).version?.trim()||null}catch{return null}}async getLatestVersionFromNpm(z){try{let H=await fetch(`https://registry.npmjs.org/${encodeURIComponent(z)}`);if(!H.ok)return null;let K=await H.json(),X=Object.keys(K.versions||{});if(X.length===0)return null;let Z=this.config?.packages?.includePrerelease??!1,Y=X;if(!Z)Y=X.filter((Q)=>{return!this.isPrerelease(Q)});if(Y.length===0)return null;return Y.sort((Q,$)=>{try{return Bun.semver.order($,Q)}catch{return 0}})[0]||null}catch(H){return this.logger.warn(`Failed to get npm version for ${z}:`,H),null}}isPrerelease(z){return/-(?:alpha|beta|rc|dev|canary|next|experimental|snapshot|nightly)/i.test(z)}async runBunOutdated(z){let H=["outdated"];if(z)H.push(...z.split(" "));try{let K=await this.runCommand("bun",H);return this.parseBunOutdatedOutput(K)}catch(K){throw this.logger.error("Failed to run bun outdated:",K),new B("bun outdated command failed")}}parseBunOutdatedOutput(z){let H=[],K=`${String.fromCharCode(27)}[`,Z=z.replace(new RegExp(`${K}[0-9;]*m`,"g"),"").split(`
`).filter((W)=>W.trim()),Y=!1;for(let W of Z){if(W.includes("Package")&&W.includes("Current")&&W.includes("Update")){Y=!0;continue}if(!Y||!W.trim())continue;if(W.match(/^[\u2502\u251C\u2500\u253C\u2524\u2514\u2534\u2518\u250C\u252C\u2510|\-\s]+$/))continue;let Q;if(W.includes("\u2502"))Q=W.split("\u2502").map(($)=>$.trim());else Q=W.split("|").map(($)=>$.trim());if(Q.length>=4){let $=Q[1]?.trim()||"",x=Q[2]?.trim()||"",G=Q[3]?.trim()||"",_=Q[4]?.trim()||"",S=Q.length>=6?Q[5]?.trim():void 0;if($=$.replace(/\s*\(dev\)$/,"").replace(/\s*\(peer\)$/,"").replace(/\s*\(optional\)$/,""),$&&x&&_&&$!=="Package"){let q={name:$,current:x,update:G,latest:_};if(S&&S!=="Workspace")q.workspace=S;H.push(q)}}}return H}async getPackageJsonOutdated(z){try{let H=L.join(this.projectPath,"package.json");if(!I.existsSync(H))return[];let K=JSON.parse(I.readFileSync(H,"utf-8")),X={...K.dependencies,...K.devDependencies,...K.peerDependencies},Z=[],Y=Object.keys(X),W=z?Y.filter((Q)=>z.split(" ").some(($)=>Q.includes($))):Y;for(let Q of W){let $=X[Q];if(!$)continue;if((this.config?.packages?.ignore||[]).includes(Q))continue;let G=this.cleanVersionRange($);if(!G)continue;let _=await this.getLatestVersion(Q);if(!_)continue;if(Bun.semver.order(G,_)<0){let S=U(G,_);if((this.config?.packages?.excludeMajor??!1)&&S==="major")continue;Z.push({name:Q,current:G,update:_,latest:_})}}return Z}catch(H){return this.logger.warn("Failed to check package.json versions:",H),[]}}cleanVersionRange(z){if(z.startsWith("workspace:"))return null;return z.replace(/^[\^~>=<]/,"").trim()}async runCommand(z,H){return new Promise((K,X)=>{let Z=f(z,H,{cwd:this.projectPath,stdio:"pipe"}),Y="",W="";Z.stdout?.on("data",(Q)=>{Y+=Q.toString()}),Z.stderr?.on("data",(Q)=>{W+=Q.toString()}),Z.on("close",(Q)=>{if(Q===0)K(Y);else X(Error(`Command failed with code ${Q}: ${W}`))}),Z.on("error",(Q)=>{X(Q)})})}getReleaseNotesUrl(z,H){if(!H?.repository)return;let K=H.repository.match(/github\.com[/:]([^/]+\/[^/]+)/);if(K)return`https://github.com/${K[1].replace(".git","")}/releases`;return}getChangelogUrl(z,H){if(!H?.repository)return;let K=H.repository.match(/github\.com[/:]([^/]+\/[^/]+)/);if(K)return`https://github.com/${K[1].replace(".git","")}/blob/main/CHANGELOG.md`;return}async getUpdatesForWorkspace(z){try{let H=["outdated","--filter",z],K=await this.runCommand("bun",H),X=this.parseBunOutdatedOutput(K),Z=[];for(let Y of X){let W=U(Y.current,Y.latest),Q=await this.getPackageMetadata(Y.name);Z.push({name:Y.name,currentVersion:Y.current,newVersion:Y.latest,updateType:W,dependencyType:"dependencies",file:`${z}/package.json`,metadata:Q,releaseNotesUrl:this.getReleaseNotesUrl(Y.name,Q),changelogUrl:this.getChangelogUrl(Y.name,Q),homepage:Q?.homepage})}return Z}catch(H){return this.logger.warn(`Failed to get updates for workspace ${z}:`,H),[]}}async searchPackages(z,H=10){try{let X=`https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(z)}&size=${H}`,Z=await fetch(X);if(!Z.ok)throw Error(`HTTP ${Z.status}: ${Z.statusText}`);return(await Z.json()).objects?.map((W)=>({name:W.package.name,version:W.package.version,description:W.package.description,keywords:W.package.keywords}))||[]}catch(K){return this.logger.warn("Failed to search packages via registry API:",K),[]}}getMajorVersion(z){return z.replace(/^[v^~>=<]+/,"").split(".")[0]||"0"}getMinorVersion(z){return z.replace(/^[v^~>=<]+/,"").split(".")[1]||"0"}async getComposerOutdatedPackages(){this.logger.info("Checking for outdated Composer packages...");let z=[];try{console.log("\uD83D\uDD0D Checking Composer availability...");try{let W=await this.runCommand("composer",["--version"]);console.log("\u2705 Composer version:",W.split(`
`)[0])}catch(W){return console.log("\u274C Composer not available:",W),this.logger.warn("Composer not found, skipping Composer package updates"),[]}let H=L.join(y.cwd(),"composer.json");if(console.log(`\uD83D\uDD0D Reading composer.json from: ${H}`),!I.existsSync(H))return console.log("\u274C composer.json not found"),[];let K=I.readFileSync(H,"utf8"),X=JSON.parse(K);console.log("\uD83D\uDCE6 composer.json require packages:",Object.keys(X.require||{})),console.log("\uD83D\uDCE6 composer.json require-dev packages:",Object.keys(X["require-dev"]||{})),console.log("\uD83D\uDD0D Running composer outdated (including dev dependencies)...");let Z=await this.runCommand("composer",["outdated","--format=json"]);console.log("\uD83D\uDCCA Raw composer outdated output length:",Z.length);let Y;try{Y=JSON.parse(Z),console.log(`\uD83D\uDCCB Composer outdated found ${Y.installed?.length||0} packages`)}catch(W){return console.log("\u274C Failed to parse composer outdated JSON:",W),console.log("Raw output:",Z.substring(0,500)),[]}if(Y.installed){console.log("\uD83D\uDD04 Processing outdated packages...");for(let W of Y.installed)if(console.log(`
\uD83D\uDCE6 Processing package: ${W.name}`),console.log(` Current version: ${W.version}`),console.log(` Latest version: ${W.latest}`),W.name&&W.version&&W.latest){let Q=X.require?.[W.name],$=X["require-dev"]?.[W.name],x=Q||$;if(console.log(` Constraint: ${x||"NOT FOUND"}`),!x){console.log(` \u26A0\uFE0F Skipping ${W.name} - not found in composer.json`);continue}if((this.config?.packages?.ignore||[]).includes(W.name)){console.log(` \u26A0\uFE0F Skipping ${W.name} - in ignore list`);continue}let _="require";if(X["require-dev"]&&X["require-dev"][W.name])_="require-dev";console.log(` Dependency type: ${_}`);let S=await this.getComposerPackageMetadata(W.name),q=this.extractConstraintBaseVersion(x);if(console.log(` Constraint base version: ${q}`),!q){console.warn(`\u274C Could not extract base version from constraint "${x}" for ${W.name}`);continue}let F=q,j=W.latest;console.log(` Using current version: ${F} (from constraint)`),console.log(` Target latest version: ${j}`);let A=[];try{console.log(` \uD83D\uDD0D Getting available versions for ${W.name}...`);let O=await this.runCommand("composer",["show",W.name,"--available","--format=json"]),C=JSON.parse(O);if(C.versions)A=C.versions,console.log(` \uD83D\uDCCB Found ${A.length} available versions`)}catch(O){console.warn(`\u274C Failed to get available versions for ${W.name}, using latest only:`,O),A=[j]}console.log(" \uD83C\uDFAF Finding best constraint updates...");let J=await this.findBestConstraintUpdates(x,A,F);console.log(` \uD83D\uDCCA Found ${J.length} update candidates`);for(let O of J){let C=U(F,O.version);if(console.log(` \uD83D\uDCC8 Update candidate: ${F} \u2192 ${O.version} (${C})`),(this.config?.packages?.excludeMajor??!1)&&C==="major"){console.log(` \u26A0\uFE0F Skipping major update for ${W.name} - excludeMajor is true`);continue}console.log(` \u2705 Adding update: ${W.name} ${F} \u2192 ${O.version}`),z.push({name:W.name,currentVersion:F,newVersion:O.version,updateType:C,dependencyType:_,file:"composer.json",metadata:S,releaseNotesUrl:this.getComposerReleaseNotesUrl(W.name,S),changelogUrl:this.getComposerChangelogUrl(W.name,S),homepage:S?.homepage})}}}return console.log(`\u2705 Final result: Found ${z.length} Composer package updates`),this.logger.success(`Found ${z.length} Composer package updates`),z}catch(H){return this.logger.warn("Failed to check for outdated Composer packages:",H),[]}}async findBestUpdates(z,H,K){let{getUpdateType:X}=await import("./chunk-eywnktd9.js"),Z=[],Y=this.parseVersion(z);if(!Y)return[];let W=null,Q=null,$=null;for(let x of H){if(x.includes("dev")||x.includes("alpha")||x.includes("beta")||x.includes("RC"))continue;let G=this.parseVersion(x);if(!G)continue;if(this.compareVersions(x,z)<=0)continue;let S=X(z,x);if(S==="patch"&&G.major===Y.major&&G.minor===Y.minor){if(!W||this.compareVersions(x,W)>0)W=x}else if(S==="minor"&&G.major===Y.major){if(!Q||this.compareVersions(x,Q)>0)Q=x}else if(S==="major"){if(!$||this.compareVersions(x,$)>0)$=x}}if(W)Z.push({version:W,type:"patch"});if(Q)Z.push({version:Q,type:"minor"});if($)Z.push({version:$,type:"major"});return Z}parseVersion(z){let K=z.replace(/^v/,"").split("-")[0].split("+")[0].split(".").map((X)=>Number.parseInt(X,10));if(K.length<2||K.some((X)=>Number.isNaN(X)))return null;return{major:K[0]||0,minor:K[1]||0,patch:K[2]||0}}compareVersions(z,H){let K=this.parseVersion(z),X=this.parseVersion(H);if(!K||!X)return 0;if(K.major!==X.major)return K.major-X.major;if(K.minor!==X.minor)return K.minor-X.minor;return K.patch-X.patch}async getComposerPackageMetadata(z){try{let H=await fetch(`https://packagist.org/packages/${z}.json`);if(!H.ok)return;let X=(await H.json()).package;if(!X)return;let Z=Object.keys(X.versions||{}),Y=Z.find((Q)=>!Q.includes("dev")&&!Q.includes("alpha")&&!Q.includes("beta"))||Z[0],W=X.versions[Y]||{};return{name:X.name,description:W.description,repository:W.source?.url||W.homepage,homepage:W.homepage,license:Array.isArray(W.license)?W.license.join(", "):W.license,author:W.authors?.[0]?.name,keywords:W.keywords,latestVersion:Y,versions:Z,weeklyDownloads:X.downloads?.monthly,dependencies:W.require,devDependencies:W["require-dev"]}}catch(H){this.logger.warn(`Failed to get Composer metadata for ${z}:`,H);return}}async composerPackageExists(z){try{return(await fetch(`https://packagist.org/packages/${z}.json`)).ok}catch{return!1}}async getComposerLatestVersion(z){try{let H=await fetch(`https://packagist.org/packages/${z}.json`);if(!H.ok)return null;let X=(await H.json()).package;if(!X?.versions)return null;let Z=Object.keys(X.versions),Y=Z.filter((Q)=>!Q.includes("dev")&&!Q.includes("alpha")&&!Q.includes("beta")&&!Q.includes("rc"));if(Y.length===0)return Z[0]||null;let W=Y.sort((Q,$)=>{try{if(typeof Bun<"u"&&Bun.semver)return Bun.semver.order(Q,$);return Q.localeCompare($,void 0,{numeric:!0})}catch{return Q.localeCompare($)}});return W[W.length-1]||null}catch(H){return this.logger.warn(`Failed to get latest Composer version for ${z}:`,H),null}}getComposerReleaseNotesUrl(z,H){if(!H?.repository)return;let K=H.repository.match(/github\.com[/:]([^/]+\/[^/]+)/);if(K)return`https://github.com/${K[1].replace(".git","")}/releases`;return}getComposerChangelogUrl(z,H){if(!H?.repository)return;let K=H.repository.match(/github\.com[/:]([^/]+\/[^/]+)/);if(K)return`https://github.com/${K[1].replace(".git","")}/blob/main/CHANGELOG.md`;return}extractConstraintBaseVersion(z){let H=z.match(/^[\^~>=<]*([\d.]+)/);if(H)return H[1];return null}async findBestConstraintUpdates(z,H,K){let{getUpdateType:X}=await import("./chunk-eywnktd9.js"),Z=[],Y=this.parseVersion(K);if(!Y)return[];let W=null,Q=null,$=null;for(let x of H){if(x.includes("dev")||x.includes("alpha")||x.includes("beta")||x.includes("RC"))continue;let G=this.parseVersion(x);if(!G)continue;if(this.compareVersions(x,K)<=0)continue;let S=X(K,x);if(S==="patch"&&G.major===Y.major&&G.minor===Y.minor){if(!W||this.compareVersions(x,W)>0)W=x}else if(S==="minor"&&G.major===Y.major){if(!Q||this.compareVersions(x,Q)>0)Q=x}else if(S==="major"){if(!$||this.compareVersions(x,$)>0)$=x}}if(W)Z.push({version:W,type:"patch"});if(Q)Z.push({version:Q,type:"minor"});if($)Z.push({version:$,type:"major"});return Z}async getWorkspaceOutdatedPackages(){try{let z=await this.getWorkspaceNames(),H=await this.findPackageJsonFiles(),K=[];this.logger.info(`Found ${z.length} workspace packages to check`);for(let X of z)try{let Z=await this.runBunOutdatedForWorkspace(X);K.push(...Z)}catch(Z){this.logger.warn(`Failed to check workspace ${X}:`,Z)}this.logger.info(`Checking ${H.length} package.json files directly`);for(let X of H){if(X==="package.json")continue;try{let Z=L.join(this.projectPath,X),Y=I.readFileSync(Z,"utf-8"),W=JSON.parse(Y),Q={...W.dependencies,...W.devDependencies,...W.peerDependencies,...W.optionalDependencies};for(let[$,x]of Object.entries(Q)){if(typeof x!=="string"||x.startsWith("workspace:"))continue;if(K.some((G)=>G.name===$))continue;try{let G=await this.getLatestVersion($);if(G){let _=this.cleanVersionRange(x);if(_&&Bun.semver.order(_,G)<0)K.push({name:$,current:_,update:G,latest:G,file:X})}}catch{continue}}}catch(Z){this.logger.warn(`Failed to check packages in ${X}:`,Z)}}return this.logger.info(`Found ${K.length} outdated packages across workspaces`),K}catch(z){return this.logger.warn("Failed to check workspace packages:",z),[]}}async getWorkspaceNames(){let z=[],H=await this.findPackageJsonFiles();for(let K of H)try{let X=L.join(this.projectPath,K),Z=I.readFileSync(X,"utf-8"),Y=JSON.parse(Z);if(Y.name&&K!=="package.json")z.push(Y.name)}catch(X){this.logger.warn(`Failed to parse package.json ${K}:`,X)}return z}async findPackageJsonFiles(){let z=[],H=async(K,X="")=>{try{let Z=await I.promises.readdir(K);for(let Y of Z){let W=L.join(K,Y),Q=X?L.join(X,Y):Y,$=await I.promises.stat(W);if($.isDirectory()){if(!this.shouldSkipDirectory(Y))await H(W,Q)}else if($.isFile()&&Y==="package.json")z.push(Q)}}catch{}};return await H(this.projectPath),z}shouldSkipDirectory(z){return["node_modules",".git",".next",".nuxt","dist","build","coverage",".nyc_output","tmp","temp",".cache",".vscode",".idea"].includes(z)||z.startsWith(".")}async findPackageLocation(z){let H=await this.findPackageJsonFiles();for(let K of H)try{let X=L.join(this.projectPath,K),Z=I.readFileSync(X,"utf-8"),Y=JSON.parse(Z);if({...Y.dependencies,...Y.devDependencies,...Y.peerDependencies,...Y.optionalDependencies}[z])return K}catch{continue}return null}async getPackageVersionReleaseDate(z,H){try{let K=await this.runCommand("bun",["info",`${z}@${H}`,"--json"]),X=JSON.parse(K);if(X.time&&typeof X.time==="string")return new Date(X.time);if(!X.time)try{let Z=await this.runCommand("npm",["view",`${z}@${H}`,"time","--json"]),Y=JSON.parse(Z);if(Y&&typeof Y==="string")return new Date(Y)}catch(Z){this.logger.debug(`npm fallback failed for ${z}@${H}:`,Z)}return null}catch(K){return this.logger.debug(`Failed to get release date for ${z}@${H}:`,K),null}}async getGitHubActionReleaseDate(z,H){try{let[K,X]=z.split("/");if(!K||!X)return null;let Z=`https://api.github.com/repos/${K}/${X}/releases/tags/${H}`,Y=await this.runCommand("curl",["-s","-H","Accept: application/vnd.github.v3+json",Z]),W=JSON.parse(Y);if(W.published_at)return new Date(W.published_at);return null}catch(K){return this.logger.debug(`Failed to get GitHub Action release date for ${z}@${H}:`,K),null}}async getComposerPackageReleaseDate(z,H){try{let K=`https://packagist.org/packages/${z}.json`,X=await this.runCommand("curl",["-s",K]),Z=JSON.parse(X);if(Z.package&&Z.package.versions&&Z.package.versions[H]){let Y=Z.package.versions[H];if(Y.time)return new Date(Y.time)}return null}catch(K){return this.logger.debug(`Failed to get Composer package release date for ${z}@${H}:`,K),null}}async getDockerImageReleaseDate(z,H){try{return this.logger.debug(`Docker image release date checking not fully implemented for ${z}:${H}`),null}catch(K){return this.logger.debug(`Failed to get Docker image release date for ${z}:${H}:`,K),null}}async meetsMinimumReleaseAge(z,H,K){let X=this.config?.packages?.minimumReleaseAge??0,Z=this.config?.packages?.minimumReleaseAgeExclude??[];if(X===0)return!0;if(Z.includes(z))return this.logger.debug(`Package ${z} is excluded from minimum release age requirement`),!0;let Y=null;switch(K){case"github-actions":Y=await this.getGitHubActionReleaseDate(z,H);break;case"require":case"require-dev":Y=await this.getComposerPackageReleaseDate(z,H);break;case"docker-image":Y=await this.getDockerImageReleaseDate(z,H);break;default:Y=await this.getPackageVersionReleaseDate(z,H);break}if(!Y)return this.logger.warn(`Could not determine release date for ${z}@${H} (${K||"unknown type"}), allowing update`),!0;let Q=(new Date().getTime()-Y.getTime())/60000,$=Q>=X;if(!$)this.logger.info(`Package ${z}@${H} (${K||"unknown type"}) is too new (${Math.round(Q)} minutes old, minimum: ${X} minutes)`);return $}async runBunOutdatedForWorkspace(z){try{let H=await this.runCommand("bun",["outdated","--filter",z]);return this.parseBunOutdatedOutput(H).map((X)=>({...X,workspace:z}))}catch(H){return this.logger.warn(`Failed to run bun outdated for workspace ${z}:`,H),[]}}}export{P as RegistryClient};
export{E as m,B as n,w as o,M as p,P as q};
//# debugId=A7B62E966D2CB1F564756E2164756E21