also looking at this
fix: prevent generated dependency cache PRs
#1366
16 files
+356
-72
| @@ -3,6 +3,7 @@ import type { FileChange, GitProvider, Issue, IssueOptions, PullRequest, PullReq | ||
| 3 | 3 | import { Buffer } from 'node:buffer' |
| 4 | 4 | import { spawn } from 'node:child_process' |
| 5 | 5 | import process from 'node:process' |
| 6 | import { assertUpdateTargetsExist, FileChangeValidationError, normalizeRepositoryPath } from '../utils/file-changes' | |
| 6 | 7 | import { detectRequiredPackageManagers, getAllLockFilePaths, regenerateLockFile } from '../utils/lock-file' |
| 7 | 8 | |
| 8 | 9 | // Match GitHub token formats (ghp_*, gho_*, ghs_*, ghu_*, ghr_*, github_pat_*) |
| @@ -141,7 +142,7 @@ export class GitHubProvider implements GitProvider { | ||
| 141 | 142 | // Lockfile regeneration failures must NOT fall through to the API path — |
| 142 | 143 | // the API path can't regenerate lockfiles either, so it would just produce |
| 143 | 144 | // a PR with an updated manifest and stale lockfile. Bubble the error up. |
| 144 | if (gitError instanceof LockfileRegenerationError) | |
| 145 | if (gitError instanceof LockfileRegenerationError || gitError instanceof FileChangeValidationError) | |
| 145 | 146 | throw gitError |
| 146 | 147 | |
| 147 | 148 | console.warn(`⚠️ Git CLI commit failed, falling back to GitHub API: ${gitError}`) |
| @@ -240,9 +241,19 @@ export class GitHubProvider implements GitProvider { | ||
| 240 | 241 | await this.runCommand('git', ['clean', '-fd']) |
| 241 | 242 | console.log(`✅ Reset ${branchName} to origin/${baseBranch}`) |
| 242 | 243 | |
| 244 | await assertUpdateTargetsExist(files, async (cleanPath) => { | |
| 245 | try { | |
| 246 | await this.runCommand('git', ['cat-file', '-e', `origin/${baseBranch}:${cleanPath}`]) | |
| 247 | return true | |
| 248 | } | |
| 249 | catch { | |
| 250 | return false | |
| 251 | } | |
| 252 | }) | |
| 253 | ||
| 243 | 254 | // Apply file changes |
| 244 | 255 | for (const file of files) { |
| 245 | const cleanPath = file.path.replace(/^\.\//, '').replace(/^\/+/, '') | |
| 256 | const cleanPath = normalizeRepositoryPath(file.path) | |
| 246 | 257 | |
| 247 | 258 | // Safety check: prevent writing to sensitive files during tests |
| 248 | 259 | if (process.env.NODE_ENV === 'test' || process.env.BUN_ENV === 'test') { |
| @@ -395,11 +406,23 @@ export class GitHubProvider implements GitProvider { | ||
| 395 | 406 | const baseCommit = await this.apiRequest(`GET /repos/${this.owner}/${this.repo}/git/commits/${baseSha}`) |
| 396 | 407 | const baseTreeSha = baseCommit.tree.sha |
| 397 | 408 | |
| 409 | await assertUpdateTargetsExist(files, async (cleanPath) => { | |
| 410 | const encodedPath = cleanPath.split('/').map(segment => encodeURIComponent(segment)).join('/') | |
| 411 | try { | |
| 412 | await this.apiRequest(`GET /repos/${this.owner}/${this.repo}/contents/${encodedPath}?ref=${encodeURIComponent(baseBranch)}`) | |
| 413 | return true | |
| 414 | } | |
| 415 | catch (error: any) { | |
| 416 | if (error.message?.includes('404')) | |
| 417 | return false | |
| 418 | throw error | |
| 419 | } | |
| 420 | }) | |
| 421 | ||
| 398 | 422 | // Create new tree with file changes |
| 399 | 423 | const tree = [] |
| 400 | 424 | for (const file of files) { |
| 401 | // Ensure path doesn't start with ./ or have leading slashes (GitHub API requires clean relative paths) | |
| 402 | const cleanPath = file.path.replace(/^\.\//, '').replace(/^\/+/, '') | |
| 425 | const cleanPath = normalizeRepositoryPath(file.path) | |
| 403 | 426 | |
| 404 | 427 | if (file.type === 'delete') { |
| 405 | 428 | tree.push({ |