buddy-bot
Version:
Automated & optimized dependency updates for JavaScript & TypeScript projects. Like Renovate & Dependabot.
13 lines (10 loc) • 25.3 kB
JavaScript
// @bun
import{D as j,H as A}from"./chunk-jcxe2rnh.js";import{Buffer as z}from"buffer";import{spawn as M}from"child_process";import W from"process";class R{token;owner;repo;hasWorkflowPermissions;apiUrl="https://api.github.com";constructor(q,G,H,I=!1){this.token=q;this.owner=G;this.repo=H;this.hasWorkflowPermissions=I}async createBranch(q,G){try{let I=(await this.apiRequest(`GET /repos/${this.owner}/${this.repo}/git/ref/heads/${G}`)).object.sha;await this.apiRequest(`POST /repos/${this.owner}/${this.repo}/git/refs`,{ref:`refs/heads/${q}`,sha:I}),console.log(`\u2705 Created branch ${q} from ${G}`)}catch(H){throw console.error(`\u274C Failed to create branch ${q}:`,H),H}}async commitChanges(q,G,H){try{await this.commitChangesWithGit(q,G,H)}catch(I){console.warn(`\u26A0\uFE0F Git CLI commit failed, falling back to GitHub API: ${I}`),await this.commitChangesWithAPI(q,G,H)}}async commitChangesWithGit(q,G,H){try{let I=H.filter((K)=>K.path.includes(".github/workflows/")),J=H.filter((K)=>!K.path.includes(".github/workflows/"));if(I.length>0&&!this.hasWorkflowPermissions)if(console.warn(`\u26A0\uFE0F Detected ${I.length} workflow file(s). These require elevated permissions.`),console.warn(`\u26A0\uFE0F Workflow files: ${I.map((K)=>K.path).join(", ")}`),console.warn("\u2139\uFE0F Workflow files will be skipped in this commit. BUDDY_BOT_TOKEN not detected or lacks workflow permissions."),J.length>0)console.log(`\uD83D\uDCDD Committing ${J.length} non-workflow files...`),H=J;else{console.warn("\u26A0\uFE0F All files are workflow files. No files will be committed in this PR."),console.warn("\uD83D\uDCA1 To update workflow files, ensure BUDDY_BOT_TOKEN is set with workflow:write permissions."),console.log('\uD83D\uDCDD Creating empty commit to avoid "No commits between branches" error...');try{await this.runCommand("git",["commit","--allow-empty","-m","Workflow files require elevated permissions - no changes committed"]),console.log("\u2705 Created empty commit for workflow-only PR")}catch(K){console.warn(`\u26A0\uFE0F Failed to create empty commit: ${K}`);try{let Q=await import("fs");if(Q.existsSync("README.md")){let $=`${Q.readFileSync("README.md","utf-8")}
<!-- Updated by Buddy Bot -->
`;Q.writeFileSync("README.md",$),await this.runCommand("git",["add","README.md"]),await this.runCommand("git",["commit","-m","Update README for workflow-only PR"]),console.log("\u2705 Created README update for workflow-only PR")}}catch(X){console.error(`\u274C Failed to create any commit: ${X}`)}}return}else if(I.length>0)console.log(`\u2705 Including ${I.length} workflow file(s) with elevated permissions`);try{await this.runCommand("git",["config","user.name","github-actions[bot]"]),await this.runCommand("git",["config","user.email","41898282+github-actions[bot]@users.noreply.github.com"]),console.log("\u2705 Git identity configured for github-actions[bot]")}catch(K){console.warn("\u26A0\uFE0F Failed to configure Git identity:",K)}await this.runCommand("git",["fetch","origin"]);try{await this.runCommand("git",["checkout",q])}catch{try{await this.runCommand("git",["checkout","-b",q,`origin/${q}`])}catch{await this.runCommand("git",["checkout","-b",q])}}try{await this.runCommand("git",["reset","--hard",`origin/${q}`])}catch{await this.runCommand("git",["reset","--hard","HEAD"])}await this.runCommand("git",["clean","-fd"]);for(let K of H){let X=K.path.replace(/^\.\//,"").replace(/^\/+/,"");if(W.env.NODE_ENV==="test"||W.env.BUN_ENV==="test"){if(["package.json","bun.lockb","package-lock.json","yarn.lock"].includes(X)&&K.content==='{"name":"x"}'){console.warn(`\u26A0\uFE0F Skipping test file write to ${X} to prevent overwriting project files`);continue}}if(K.type==="delete")try{await this.runCommand("git",["rm",X])}catch{}else{let Q=await import("fs"),$=(await import("path")).dirname(X);if($!==".")Q.mkdirSync($,{recursive:!0});Q.writeFileSync(X,K.content,"utf8"),await this.runCommand("git",["add",X])}}if((await this.runCommand("git",["status","--porcelain"])).trim()){await this.runCommand("git",["commit","-m",G]);try{await this.runCommand("git",["push","origin",q])}catch{await this.runCommand("git",["push","origin",q,"--force-with-lease"])}console.log(`\u2705 Successfully rebased ${q} with fresh changes: ${G}`)}else console.log(`\u2139\uFE0F No changes to commit for ${q}`)}catch(I){throw console.error(`\u274C Failed to commit changes to ${q} with Git CLI:`,I),I}}async commitChangesWithAPI(q,G,H){try{let I=H.filter((Z)=>Z.path.includes(".github/workflows/")),J=H.filter((Z)=>!Z.path.includes(".github/workflows/"));if(I.length>0&&!this.hasWorkflowPermissions)if(console.warn(`\u26A0\uFE0F Detected ${I.length} workflow file(s). These require elevated permissions.`),console.warn(`\u26A0\uFE0F Workflow files: ${I.map((Z)=>Z.path).join(", ")}`),console.warn("\u2139\uFE0F Workflow files will be skipped in this commit. Consider using a GitHub App with workflow permissions for workflow updates."),J.length>0)console.log(`\uD83D\uDCDD Committing ${J.length} non-workflow files...`),H=J;else{console.warn("\u26A0\uFE0F All files are workflow files. No files will be committed in this PR."),console.warn("\uD83D\uDCA1 To update workflow files, consider using a GitHub App with appropriate permissions.");return}else if(I.length>0)console.log(`\u2705 Including ${I.length} workflow file(s) with elevated permissions`);let K=(await this.apiRequest(`GET /repos/${this.owner}/${this.repo}/git/ref/heads/${q}`)).object.sha,Q=(await this.apiRequest(`GET /repos/${this.owner}/${this.repo}/git/commits/${K}`)).tree.sha,Y=[];for(let Z of H){let _=Z.path.replace(/^\.\//,"").replace(/^\/+/,"");if(Z.type==="delete")Y.push({path:_,mode:"100644",type:"blob",sha:null});else{let U=await this.apiRequest(`POST /repos/${this.owner}/${this.repo}/git/blobs`,{content:z.from(Z.content).toString("base64"),encoding:"base64"});Y.push({path:_,mode:"100644",type:"blob",sha:U.sha})}}let $=await this.apiRequest(`POST /repos/${this.owner}/${this.repo}/git/trees`,{base_tree:Q,tree:Y});if($.sha===Q){console.log(`\u2139\uFE0F No changes detected for ${q} (API path) - skipping commit`);return}let L=await this.apiRequest(`POST /repos/${this.owner}/${this.repo}/git/commits`,{message:G,tree:$.sha,parents:[K],author:{name:"github-actions[bot]",email:"41898282+github-actions[bot]@users.noreply.github.com"},committer:{name:"github-actions[bot]",email:"41898282+github-actions[bot]@users.noreply.github.com"}});await this.apiRequest(`PATCH /repos/${this.owner}/${this.repo}/git/refs/heads/${q}`,{sha:L.sha}),console.log(`\u2705 Committed changes to ${q}: ${G}`)}catch(I){throw console.error(`\u274C Failed to commit changes to ${q}:`,I),I}}async createPullRequest(q){try{return await this.createPullRequestWithCLI(q)}catch(G){return console.warn(`\u26A0\uFE0F GitHub CLI failed, falling back to API: ${G}`),await this.createPullRequestWithAPI(q)}}async createPullRequestWithCLI(q){try{let G=["pr","create","--title",q.title,"--body",q.body,"--head",q.head,"--base",q.base];if(q.draft)G.push("--draft");if(q.reviewers&&q.reviewers.length>0)console.log(`\uD83D\uDD0D Adding reviewers via CLI: ${q.reviewers.join(", ")}`),G.push("--reviewer",q.reviewers.join(","));if(q.assignees&&q.assignees.length>0)G.push("--assignee",q.assignees.join(","));let I=(await this.runCommand("gh",G)).match(/https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/(\d+)/);if(!I)throw new Error("Failed to parse PR number from GitHub CLI output");let J=Number.parseInt(I[1]),V=I[0];if(console.log(`\u2705 Created PR #${J}: ${q.title}`),q.labels&&q.labels.length>0)try{await this.apiRequest(`POST /repos/${this.owner}/${this.repo}/issues/${J}/labels`,{labels:q.labels}),console.log(`\u2705 Added labels to PR #${J}: ${q.labels.join(", ")}`)}catch(K){console.warn(`\u26A0\uFE0F Failed to add labels: ${K}`);for(let X of q.labels)try{await this.apiRequest(`POST /repos/${this.owner}/${this.repo}/issues/${J}/labels`,{labels:[X]})}catch(Q){console.warn(`\u26A0\uFE0F Failed to add label '${X}': ${Q}`)}}return{number:J,title:q.title,body:q.body,head:q.head,base:q.base,state:"open",url:V,createdAt:new Date,updatedAt:new Date,author:"github-actions[bot]",reviewers:q.reviewers||[],assignees:q.assignees||[],labels:q.labels||[],draft:q.draft||!1}}catch(G){throw console.error(`\u274C Failed to create PR with GitHub CLI: ${q.title}`,G),G}}async createPullRequestWithAPI(q){try{let G=await this.apiRequest(`POST /repos/${this.owner}/${this.repo}/pulls`,{title:q.title,body:q.body,head:q.head,base:q.base,draft:q.draft||!1});if(q.reviewers&&q.reviewers.length>0)try{console.log(`\uD83D\uDD0D Adding reviewers to PR #${G.number}: ${q.reviewers.join(", ")}`),await this.apiRequest(`POST /repos/${this.owner}/${this.repo}/pulls/${G.number}/requested_reviewers`,{reviewers:q.reviewers,team_reviewers:q.teamReviewers||[]}),console.log(`\u2705 Successfully added reviewers: ${q.reviewers.join(", ")}`)}catch(H){console.error(`\u274C Failed to add reviewers: ${H}`),console.error(` Reviewers: ${q.reviewers.join(", ")}`),console.error(` Repository: ${this.owner}/${this.repo}`),console.error(` PR: #${G.number}`)}if(q.assignees&&q.assignees.length>0)try{await this.apiRequest(`POST /repos/${this.owner}/${this.repo}/issues/${G.number}/assignees`,{assignees:q.assignees})}catch(H){console.warn(`\u26A0\uFE0F Failed to add assignees: ${H}`)}if(q.labels&&q.labels.length>0)try{await this.apiRequest(`POST /repos/${this.owner}/${this.repo}/issues/${G.number}/labels`,{labels:q.labels})}catch(H){console.warn(`\u26A0\uFE0F Failed to add labels: ${H}`);for(let I of q.labels)try{await this.apiRequest(`POST /repos/${this.owner}/${this.repo}/issues/${G.number}/labels`,{labels:[I]})}catch(J){console.warn(`\u26A0\uFE0F Failed to add label '${I}': ${J}`)}}return console.log(`\u2705 Created PR #${G.number}: ${q.title}`),{number:G.number,title:G.title,body:G.body||"",head:G.head.ref,base:G.base.ref,state:G.state,url:G.html_url,createdAt:new Date(G.created_at),updatedAt:new Date(G.updated_at),author:G.user.login,reviewers:q.reviewers||[],assignees:q.assignees||[],labels:q.labels||[],draft:G.draft}}catch(G){throw console.error(`\u274C Failed to create PR with API: ${q.title}`,G),G}}async runCommand(q,G){return new Promise((H,I)=>{let J=M(q,G,{stdio:"pipe",env:{...W.env,GITHUB_TOKEN:this.token,GH_TOKEN:this.token}}),V="",K="";J.stdout?.on("data",(X)=>{V+=X.toString()}),J.stderr?.on("data",(X)=>{K+=X.toString()}),J.on("close",(X)=>{if(X===0)H(V);else I(new Error(`Command failed with code ${X}: ${K}`))}),J.on("error",(X)=>{I(X)})})}async getPullRequests(q="open"){try{return(await this.apiRequest(`GET /repos/${this.owner}/${this.repo}/pulls?state=${q}`)).map((H)=>({number:H.number,title:H.title,body:H.body||"",head:H.head.ref,base:H.base.ref,state:H.state,url:H.html_url,createdAt:new Date(H.created_at),updatedAt:new Date(H.updated_at),mergedAt:H.merged_at?new Date(H.merged_at):void 0,author:H.user.login,reviewers:H.requested_reviewers?.map((I)=>I.login)||[],assignees:H.assignees?.map((I)=>I.login)||[],labels:H.labels?.map((I)=>I.name)||[],draft:H.draft}))}catch(G){throw console.error("\u274C Failed to get PRs:",G),G}}async updatePullRequest(q,G){try{let H={};if(G.title)H.title=G.title;if(G.body)H.body=G.body;if(G.base)H.base=G.base;if(G.draft!==void 0)H.draft=G.draft;let I=await this.apiRequest(`PATCH /repos/${this.owner}/${this.repo}/pulls/${q}`,H);if(G.labels&&G.labels.length>0)try{await this.apiRequest(`PUT /repos/${this.owner}/${this.repo}/issues/${q}/labels`,{labels:G.labels}),console.log(`\u2705 Updated labels for PR #${q}: ${G.labels.join(", ")}`)}catch(J){console.warn(`\u26A0\uFE0F Failed to update labels for PR #${q}: ${J}`);for(let V of G.labels)try{await this.apiRequest(`POST /repos/${this.owner}/${this.repo}/issues/${q}/labels`,{labels:[V]})}catch(K){console.warn(`\u26A0\uFE0F Failed to add label '${V}' to PR #${q}: ${K}`)}}if(G.reviewers&&G.reviewers.length>0)try{console.log(`\uD83D\uDD0D Adding reviewers to existing PR #${q}: ${G.reviewers.join(", ")}`),await this.apiRequest(`POST /repos/${this.owner}/${this.repo}/pulls/${q}/requested_reviewers`,{reviewers:G.reviewers,team_reviewers:G.teamReviewers||[]}),console.log(`\u2705 Updated reviewers for PR #${q}: ${G.reviewers.join(", ")}`)}catch(J){console.error(`\u274C Failed to update reviewers for PR #${q}: ${J}`),console.error(` Reviewers: ${G.reviewers.join(", ")}`),console.error(` Repository: ${this.owner}/${this.repo}`)}if(G.assignees&&G.assignees.length>0)try{await this.runCommand("gh",["issue","edit",q.toString(),"--add-assignee",G.assignees.join(",")]),console.log(`\u2705 Updated assignees for PR #${q}: ${G.assignees.join(", ")}`)}catch(J){console.warn(`\u26A0\uFE0F Failed to update assignees for PR #${q}: ${J}`)}return console.log(`\u2705 Updated PR #${q}`),{number:I.number,title:I.title,body:I.body||"",head:I.head.ref,base:I.base.ref,state:I.state,url:I.html_url,createdAt:new Date(I.created_at),updatedAt:new Date(I.updated_at),author:I.user.login,reviewers:[],assignees:[],labels:G.labels||[],draft:I.draft}}catch(H){throw console.error(`\u274C Failed to update PR #${q}:`,H),H}}async closePullRequest(q){try{let H=(await this.apiRequest(`GET /repos/${this.owner}/${this.repo}/pulls/${q}`)).head.ref;await this.apiRequest(`PATCH /repos/${this.owner}/${this.repo}/pulls/${q}`,{state:"closed"}),console.log(`\u2705 Closed PR #${q}`);try{await this.deleteBranch(H),console.log(`\uD83E\uDDF9 Cleaned up branch ${H} after close`)}catch(I){console.warn(`\u26A0\uFE0F Failed to clean up branch ${H}:`,I)}}catch(G){throw console.error(`\u274C Failed to close PR #${q}:`,G),G}}async createComment(q,G){try{await this.apiRequest(`POST /repos/${this.owner}/${this.repo}/issues/${q}/comments`,{body:G}),console.log(`\uD83D\uDCAC Added comment to PR #${q}`)}catch(H){throw console.error(`\u274C Failed to add comment to PR #${q}:`,H),H}}async mergePullRequest(q,G="merge"){try{let H=G==="rebase"?"rebase":G==="squash"?"squash":"merge",J=(await this.apiRequest(`GET /repos/${this.owner}/${this.repo}/pulls/${q}`)).head.ref;await this.apiRequest(`PUT /repos/${this.owner}/${this.repo}/pulls/${q}/merge`,{merge_method:H}),console.log(`\u2705 Merged PR #${q} using ${G}`);try{await this.deleteBranch(J),console.log(`\uD83E\uDDF9 Cleaned up branch ${J} after merge`)}catch(V){console.warn(`\u26A0\uFE0F Failed to clean up branch ${J}:`,V)}}catch(H){throw console.error(`\u274C Failed to merge PR #${q}:`,H),H}}async deleteBranch(q){try{await this.runCommand("git",["push","origin","--delete",q]),console.log(`\u2705 Deleted branch ${q} via git`)}catch(G){try{await this.runCommand("git",["branch","-D",q]),console.log(`\u2705 Deleted local branch ${q}`)}catch{}console.warn(`\u26A0\uFE0F Failed to delete remote branch ${q}:`,G)}}async getBuddyBotBranches(){try{let q=await this.runCommand("git",["branch","-r","--format=%(refname:short) %(objectname) %(committerdate:iso8601)"]),G=[];for(let H of q.split(`
`)){let I=H.trim();if(!I)continue;let J=I.split(" ");if(J.length<3)continue;let V=J[0],K=J[1],X=J.slice(2).join(" "),Q=V.replace(/^origin\//,"");if(!Q.startsWith("buddy-bot/"))continue;try{let Y=new Date(X);G.push({name:Q,sha:K,lastCommitDate:Y})}catch{console.warn(`\u26A0\uFE0F Failed to parse date for branch ${Q}: ${X}`),G.push({name:Q,sha:K,lastCommitDate:new Date(0)})}}return console.log(`\uD83D\uDD0D Found ${G.length} buddy-bot branches using local git`),G}catch(q){return console.warn("\u26A0\uFE0F Failed to fetch buddy-bot branches via git, falling back to API:",q),this.getBuddyBotBranchesViaAPI()}}async getBuddyBotBranchesViaAPI(){try{let q=[],G=1,H=100;while(!0){let V=await this.apiRequest(`GET /repos/${this.owner}/${this.repo}/branches?per_page=${H}&page=${G}`);if(!V||V.length===0)break;if(q=q.concat(V),V.length<H)break;G++}console.log(`\uD83D\uDD0D Found ${q.length} total branches in repository`);let I=q.filter((V)=>V.name.startsWith("buddy-bot/"));return console.log(`\uD83E\uDD16 Found ${I.length} buddy-bot branches`),await Promise.all(I.map(async(V)=>{try{let K=await this.apiRequest(`GET /repos/${this.owner}/${this.repo}/commits/${V.commit.sha}`);return{name:V.name,sha:V.commit.sha,lastCommitDate:new Date(K.commit.committer.date)}}catch(K){return console.warn(`\u26A0\uFE0F Failed to get commit info for branch ${V.name}:`,K),{name:V.name,sha:V.commit.sha,lastCommitDate:new Date(0)}}}))}catch(q){return console.warn("\u26A0\uFE0F Failed to fetch buddy-bot branches:",q),[]}}async getOrphanedBuddyBotBranches(){try{let q=await this.getBuddyBotBranches(),G;try{G=await this.getOpenPRBranchesViaGit()}catch(I){console.warn("\u26A0\uFE0F Failed to get PR branches via git, falling back to API:",I);let J=await this.getPullRequests("open");G=new Set(J.map((V)=>V.head))}return q.filter((I)=>!G.has(I.name))}catch(q){return console.warn("\u26A0\uFE0F Failed to identify orphaned branches:",q),[]}}async isPROpen(q){try{let G=`https://github.com/${this.owner}/${this.repo}/pull/${q}`,H=await fetch(G,{method:"GET",headers:{"User-Agent":"buddy-bot/1.0"}});if(!H.ok)return!0;let I=await H.text();return I.includes("State--open")&&!I.includes("State--closed")&&!I.includes("State--merged")}catch(G){return console.warn(`\u26A0\uFE0F Could not check PR #${q} status via HTTP:`,G),!0}}httpDetectionSuccessful=!1;async getOpenPRBranchesViaGit(){try{let q=new Set;this.httpDetectionSuccessful=!1,console.log("\uD83D\uDD0D Using HTTP requests to check actual PR status (no API auth needed)...");try{let G=await this.runCommand("git",["ls-remote","origin","refs/pull/*/head"]),H=[],I=new Map;for(let X of G.split(`
`))if(X.trim()){let Q=X.trim().split("\t");if(Q.length===2){let Y=Q[1],$=Q[0],L=Y.match(/refs\/pull\/(\d+)\/head/);if(L){let Z=Number.parseInt(L[1]);H.push(Z);try{let _=await this.runCommand("git",["branch","-r","--contains",$]),U=[];for(let x of _.split(`
`)){let O=x.trim().replace(/^origin\//,"");if(O.startsWith("buddy-bot/"))U.push(O)}if(U.length>0)I.set(Z,U)}catch{}}}}console.log(`\uD83D\uDCCB Found ${H.length} PR refs, checking their status via HTTP...`);let J=5,V=0,K=0;for(let X=0;X<H.length;X+=J){let Y=H.slice(X,X+J).map(async($,L)=>{await new Promise((_)=>setTimeout(_,L*100));let Z=await this.isPROpen($);if(V++,Z){K++;let _=I.get($)||[];for(let U of _)q.add(U)}return{prNumber:$,isOpen:Z}});if(await Promise.all(Y),X+J<H.length)await new Promise(($)=>setTimeout($,500))}console.log(`\u2705 Checked ${V} PRs via HTTP: ${K} open, ${V-K} closed`),console.log(`\uD83D\uDEE1\uFE0F Protected ${q.size} branches with confirmed open PRs`),console.log("\uD83C\uDFAF HTTP detection successful - no age-based protection needed"),this.httpDetectionSuccessful=!0}catch(G){console.warn("\u26A0\uFE0F Could not check PR status via HTTP, applying conservative fallback:",G);try{let H=await this.getBuddyBotBranches(),I=new Date;I.setDate(I.getDate()-1);let J=0;for(let V of H)if(V.lastCommitDate>I&&!q.has(V.name))q.add(V.name),J++;if(J>0)console.log(`\uD83D\uDEE1\uFE0F Emergency fallback: ${J} very recent branches (< 1 day) protected due to HTTP failure`)}catch{console.log("\u26A0\uFE0F Could not apply emergency fallback protection")}}return console.log(`\uD83C\uDFAF HTTP-based analysis complete: protecting ${q.size} branches total`),q}catch(q){console.warn("\u26A0\uFE0F HTTP-based analysis failed, using conservative fallback:",q);try{let G=await this.getBuddyBotBranches(),H=new Date;H.setDate(H.getDate()-30);let I=new Set;for(let J of G)if(J.lastCommitDate>H)I.add(J.name);return console.log(`\uD83D\uDEE1\uFE0F Conservative fallback: protecting ${I.size} branches newer than 30 days`),I}catch{return console.log("\uD83D\uDEE1\uFE0F Ultimate fallback: protecting all branches"),new Set}}}async cleanupStaleBranches(q=7,G=!1){console.log("\uD83D\uDD0D Looking for buddy-bot branches without open PRs...");let H=await this.getOrphanedBuddyBotBranches();console.log(`\uD83D\uDD0D Found ${H.length} orphaned buddy-bot branches (no associated open PRs)`);let I=H;if(this.httpDetectionSuccessful)console.log(`\uD83C\uDFAF HTTP detection successful - cleaning up ALL ${I.length} orphaned branches (any age)`);else{let Q=new Date;Q.setDate(Q.getDate()-q),I=H.filter((Y)=>Y.lastCommitDate<Q),console.log(`\u26A0\uFE0F HTTP detection failed - only deleting branches older than ${q} days`),console.log(`\uD83D\uDD0D Found ${I.length} stale buddy-bot branches (older than ${q} days)`)}if(I.length>0){if(console.log("\uD83D\uDCCB Sample of branches to delete:"),I.slice(0,5).forEach((Q)=>{let Y=Math.floor((Date.now()-Q.lastCommitDate.getTime())/86400000);console.log(` - ${Q.name} (${Y} days old)`)}),I.length>5)console.log(` ... and ${I.length-5} more`)}if(I.length===0)return console.log("\u2705 No branches to clean up!"),{deleted:[],failed:[]};let J=I;if(G)return console.log("\uD83D\uDD0D [DRY RUN] Would delete the following branches:"),J.forEach((Q)=>{let Y=Math.floor((Date.now()-Q.lastCommitDate.getTime())/86400000);console.log(` - ${Q.name} (${Y} days old, last commit: ${Q.lastCommitDate.toISOString()})`)}),{deleted:J.map((Q)=>Q.name),failed:[]};let V=[],K=[];console.log(`\uD83E\uDDF9 Cleaning up ${J.length} stale branches...`);let X=5;for(let Q=0;Q<J.length;Q+=X){let Y=J.slice(Q,Q+X),$=Math.floor(Q/X)+1,L=Math.ceil(J.length/X);console.log(`\uD83D\uDD04 Processing batch ${$}/${L} (${Y.length} branches)`);for(let Z of Y){try{await this.deleteBranch(Z.name),V.push(Z.name),console.log(`\u2705 Deleted: ${Z.name}`)}catch(_){K.push(Z.name),console.warn(`\u274C Failed to delete ${Z.name}:`,_)}await new Promise((_)=>setTimeout(_,200))}if(Q+X<J.length)console.log(`\u23F3 Waiting ${3} seconds before next batch...`),await new Promise((_)=>setTimeout(_,3000))}if(console.log("\uD83C\uDF89 Cleanup complete!"),console.log(` \u2705 Successfully deleted: ${V.length} branches`),console.log(` \u274C Failed to delete: ${K.length} branches`),K.length>0)console.log("\u274C Failed branches:"),K.forEach((Q)=>console.log(` - ${Q}`));return{deleted:V,failed:K}}async apiRequest(q,G){let[H,I]=q.split(" "),J=`${this.apiUrl}${I}`,V={method:H,headers:{Authorization:`Bearer ${this.token}`,Accept:"application/vnd.github.v3+json","Content-Type":"application/json","User-Agent":"buddy-bot"}};if(G&&(H==="POST"||H==="PATCH"||H==="PUT"))V.body=JSON.stringify(G);let K=await fetch(J,V);if(!K.ok){let X=await K.text();throw new Error(`GitHub API error: ${K.status} ${K.statusText}
${X}`)}if(K.headers.get("content-type")?.includes("application/json"))return K.json();return K.text()}async apiRequestWithRetry(q,G,H=3){for(let I=1;I<=H;I++)try{return await this.apiRequest(q,G)}catch(J){if(J.message?.includes("403")&&J.message?.includes("rate limit")&&I<H){let K=2**I*1000,X=Math.random()*1000,Q=K+X;console.log(`\u23F3 Rate limited, waiting ${Math.round(Q/1000)}s before retry ${I}/${H}...`),await new Promise((Y)=>setTimeout(Y,Q));continue}throw J}}async createIssue(q){try{let G=await this.apiRequest(`POST /repos/${this.owner}/${this.repo}/issues`,{title:q.title,body:q.body,assignees:q.assignees||[],labels:q.labels||[],milestone:q.milestone});return console.log(`\u2705 Created issue #${G.number}: ${q.title}`),{number:G.number,title:G.title,body:G.body,state:G.state,url:G.html_url,createdAt:new Date(G.created_at),updatedAt:new Date(G.updated_at),closedAt:G.closed_at?new Date(G.closed_at):void 0,author:G.user.login,assignees:G.assignees?.map((H)=>H.login)||[],labels:G.labels?.map((H)=>typeof H==="string"?H:H.name)||[],pinned:!1}}catch(G){throw console.error(`\u274C Failed to create issue: ${q.title}`,G),G}}async getIssues(q="open"){try{return(await this.apiRequest(`GET /repos/${this.owner}/${this.repo}/issues?state=${q}&sort=updated&direction=desc`)).filter((H)=>!H.pull_request).map((H)=>({number:H.number,title:H.title,body:H.body||"",state:H.state,url:H.html_url,createdAt:new Date(H.created_at),updatedAt:new Date(H.updated_at),closedAt:H.closed_at?new Date(H.closed_at):void 0,author:H.user.login,assignees:H.assignees?.map((I)=>I.login)||[],labels:H.labels?.map((I)=>typeof I==="string"?I:I.name)||[],pinned:!1}))}catch(G){throw console.error("\u274C Failed to get issues:",G),G}}async updateIssue(q,G){try{let H={};if(G.title!==void 0)H.title=G.title;if(G.body!==void 0)H.body=G.body;if(G.assignees!==void 0)H.assignees=G.assignees;if(G.labels!==void 0)H.labels=G.labels;if(G.milestone!==void 0)H.milestone=G.milestone;let I=await this.apiRequest(`PATCH /repos/${this.owner}/${this.repo}/issues/${q}`,H);return console.log(`\u2705 Updated issue #${q}: ${I.title}`),{number:I.number,title:I.title,body:I.body,state:I.state,url:I.html_url,createdAt:new Date(I.created_at),updatedAt:new Date(I.updated_at),closedAt:I.closed_at?new Date(I.closed_at):void 0,author:I.user.login,assignees:I.assignees?.map((J)=>J.login)||[],labels:I.labels?.map((J)=>typeof J==="string"?J:J.name)||[],pinned:!1}}catch(H){throw console.error(`\u274C Failed to update issue #${q}:`,H),H}}async closeIssue(q){try{await this.apiRequest(`PATCH /repos/${this.owner}/${this.repo}/issues/${q}`,{state:"closed"}),console.log(`\u2705 Closed issue #${q}`)}catch(G){throw console.error(`\u274C Failed to close issue #${q}:`,G),G}}async unpinIssue(q){try{await this.apiRequest(`DELETE /repos/${this.owner}/${this.repo}/issues/${q}/pin`,void 0)}catch(G){console.log(`\u26A0\uFE0F Failed to unpin issue #${q}:`,G)}}}export{R as GitHubProvider};
export{R as j};
//# debugId=9AE18BF134F9740C64756E2164756E21