( owner: string, repo: string, tag: string, )
| 354 | } |
| 355 | |
| 356 | async function cloneRepo( |
| 357 | owner: string, |
| 358 | repo: string, |
| 359 | tag: string, |
| 360 | ): Promise<{ cwd: string; cleanup: () => Promise<void> }> { |
| 361 | const root = await mkdtemp(path.join(tmpdir(), "plugin-scan-")); |
| 362 | const cwd = path.join(root, "repo"); |
| 363 | const archivePath = path.join(root, "repo.tar.gz"); |
| 364 | const cleanup = () => |
| 365 | rm(root, { recursive: true, force: true }).catch(() => {}); |
| 366 | const startedAt = Date.now(); |
| 367 | const archiveUrl = `https://api.github.com/repos/${owner}/${repo}/tarball/HEAD`; |
| 368 | logInfo(tag, "GitHub archive download start", { owner, repo, cwd }); |
| 369 | try { |
| 370 | await mkdir(cwd); |
| 371 | // GITHUB_TOKEN (when configured) lifts the 60 req/hr anonymous rate limit |
| 372 | // shared across all tenants of the function's egress IP. |
| 373 | const response = await fetchWithRateLimit(archiveUrl, { |
| 374 | headers: { |
| 375 | Accept: "application/vnd.github+json", |
| 376 | "User-Agent": "cursor-directory-plugin-scan", |
| 377 | ...githubAuthHeaders(), |
| 378 | }, |
| 379 | maxWaitMs: 30_000, |
| 380 | }); |
| 381 | |
| 382 | if ([404, 410, 451].includes(response.status)) { |
| 383 | throw new UnscannableRepoError( |
| 384 | `GitHub returned ${response.status} for ${owner}/${repo} — the repository does not exist or is not public.`, |
| 385 | ); |
| 386 | } |
| 387 | |
| 388 | if (!response.ok || !response.body) { |
| 389 | throw new Error( |
| 390 | `GitHub archive request failed with ${response.status} ${response.statusText}`, |
| 391 | ); |
| 392 | } |
| 393 | |
| 394 | const contentLength = response.headers.get("content-length"); |
| 395 | if ( |
| 396 | contentLength && |
| 397 | Number.parseInt(contentLength, 10) > REPO_ARCHIVE_MAX_BYTES |
| 398 | ) { |
| 399 | throw new UnscannableRepoError( |
| 400 | `Repository archive advertised ${contentLength} bytes, above the ${REPO_ARCHIVE_MAX_BYTES}-byte scan limit.`, |
| 401 | ); |
| 402 | } |
| 403 | |
| 404 | await pipeline( |
| 405 | Readable.fromWeb(response.body as never), |
| 406 | archiveSizeGuard(tag), |
| 407 | createWriteStream(archivePath), |
| 408 | ); |
| 409 | |
| 410 | await extractTar({ |
| 411 | file: archivePath, |
| 412 | cwd, |
| 413 | strip: 1, |
no test coverage detected