({
mainCheckout,
projectRoot,
files = GITIGNORED_FILES_TO_LINK,
log = console.log
})
| 454 | * @returns {Promise<{linked: string[], alreadyLinked: string[], skippedNoSource: string[], skippedRealFile: string[], mainCheckout: boolean}>} Per-file action map. |
| 455 | */ |
| 456 | export async function symlinkGitignoredFiles({ |
| 457 | mainCheckout, |
| 458 | projectRoot, |
| 459 | files = GITIGNORED_FILES_TO_LINK, |
| 460 | log = console.log |
| 461 | }) { |
| 462 | const result = {linked: [], alreadyLinked: [], skippedNoSource: [], skippedRealFile: [], mainCheckout: false}; |
| 463 | |
| 464 | if (path.resolve(projectRoot) === path.resolve(mainCheckout)) { |
| 465 | log(`file-symlink skip (main checkout): no per-file action`); |
| 466 | result.mainCheckout = true; |
| 467 | return result; |
| 468 | } |
| 469 | |
| 470 | for (const rel of files) { |
| 471 | const src = path.join(mainCheckout, rel); |
| 472 | const dst = path.join(projectRoot, rel); |
| 473 | const lstat = await fs.lstat(dst).catch(() => null); |
| 474 | |
| 475 | if (lstat?.isSymbolicLink()) { |
| 476 | log(`file-symlink skip (already linked): ${rel}`); |
| 477 | result.alreadyLinked.push(rel); |
| 478 | continue; |
| 479 | } |
| 480 | |
| 481 | // Skip if canonical lacks the file — graceful for the pre-Sandman-run state. |
| 482 | const srcExists = await exists(src); |
| 483 | if (!srcExists) { |
| 484 | log(`file-symlink skip (no source in main checkout): ${rel}`); |
| 485 | result.skippedNoSource.push(rel); |
| 486 | continue; |
| 487 | } |
| 488 | |
| 489 | if (lstat) { |
| 490 | // Real file present — preserve local state, surface warning. |
| 491 | log(`file-symlink skip (real file present, preserving local state): ${rel}`); |
| 492 | log(` → manually remove the local file and re-run --link-data to override`); |
| 493 | result.skippedRealFile.push(rel); |
| 494 | continue; |
| 495 | } |
| 496 | |
| 497 | // Ensure parent dir exists; resources/content/ is normally tracked + present, but |
| 498 | // belt-and-suspenders for fresh repos or future allowlist entries in absent dirs. |
| 499 | await fs.mkdir(path.dirname(dst), {recursive: true}); |
| 500 | |
| 501 | await fs.symlink(src, dst, 'file'); |
| 502 | log(`file-symlinked: ${rel} → ${src}`); |
| 503 | result.linked.push(rel); |
| 504 | } |
| 505 | |
| 506 | return result; |
| 507 | } |
| 508 | |
| 509 | /** |
| 510 | * @summary Installs the worktree's `node_modules` and bundles the parse5 test prerequisite. |
no test coverage detected