* Collect files from feature directories
(params: {
client: GitHubClient;
owner: string;
repo: string;
basePath: string;
ref: string;
enabledFeatures: Feature[];
semaphore: Semaphore;
logger: Logger;
})
| 413 | * Collect files from feature directories |
| 414 | */ |
| 415 | async function collectFeatureFiles(params: { |
| 416 | client: GitHubClient; |
| 417 | owner: string; |
| 418 | repo: string; |
| 419 | basePath: string; |
| 420 | ref: string; |
| 421 | enabledFeatures: Feature[]; |
| 422 | semaphore: Semaphore; |
| 423 | logger: Logger; |
| 424 | }): Promise<Array<{ remotePath: string; relativePath: string; size: number }>> { |
| 425 | const { client, owner, repo, basePath, ref, enabledFeatures, semaphore, logger } = params; |
| 426 | |
| 427 | // Cache directory listing results to avoid duplicate API calls |
| 428 | // File-based features (ignore, mcp, hooks) all list the same basePath directory |
| 429 | const dirCache = new Map<string, Promise<GitHubFileEntry[]>>(); |
| 430 | |
| 431 | async function getCachedDirectory(path: string): Promise<GitHubFileEntry[]> { |
| 432 | let promise = dirCache.get(path); |
| 433 | if (promise === undefined) { |
| 434 | promise = withSemaphore(semaphore, () => client.listDirectory(owner, repo, path, ref)); |
| 435 | dirCache.set(path, promise); |
| 436 | } |
| 437 | return promise; |
| 438 | } |
| 439 | |
| 440 | const tasks = enabledFeatures.flatMap((feature) => |
| 441 | FEATURE_PATHS[feature].map((featurePath) => ({ feature, featurePath })), |
| 442 | ); |
| 443 | |
| 444 | const results = await Promise.all( |
| 445 | tasks.map(async ({ featurePath }) => { |
| 446 | const fullPath = |
| 447 | basePath === "." || basePath === "" ? featurePath : posix.join(basePath, featurePath); |
| 448 | const collected: Array<{ remotePath: string; relativePath: string; size: number }> = []; |
| 449 | |
| 450 | try { |
| 451 | // Check if it's a file (mcp.json, .aiignore, hooks.json) |
| 452 | if (featurePath.includes(".")) { |
| 453 | // Try to get the file directly |
| 454 | try { |
| 455 | const entries = await getCachedDirectory( |
| 456 | basePath === "." || basePath === "" ? "." : basePath, |
| 457 | ); |
| 458 | const fileEntry = entries.find((e) => e.name === featurePath && e.type === "file"); |
| 459 | if (fileEntry) { |
| 460 | collected.push({ |
| 461 | remotePath: fileEntry.path, |
| 462 | relativePath: featurePath, |
| 463 | size: fileEntry.size, |
| 464 | }); |
| 465 | } |
| 466 | } catch (error) { |
| 467 | // Only skip 404 errors (file not found), re-throw other errors |
| 468 | if (isNotFoundError(error)) { |
| 469 | logger.debug(`File not found: ${fullPath}`); |
| 470 | } else { |
| 471 | throw error; |
| 472 | } |
no test coverage detected
searching dependent graphs…