foresthouse 1.1.0 → 1.2.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  import { builtinModules } from "node:module";
2
- import { execFileSync } from "node:child_process";
2
+ import { execFileSync, execSync } from "node:child_process";
3
3
  import fs from "node:fs";
4
4
  import os from "node:os";
5
5
  import path from "node:path";
@@ -363,11 +363,12 @@ function parsePnpmVersion(value) {
363
363
  //#region src/analyzers/deps/diff.ts
364
364
  const PNPM_LOCKFILE = "pnpm-lock.yaml";
365
365
  const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
366
+ const GIT_EXEC_MAX_BUFFER$1 = 64 * 1024 * 1024;
366
367
  function analyzePackageDependencyDiff(directory, diff) {
367
368
  const resolvedInputPath = path.resolve(process.cwd(), directory);
368
- const repositoryRoot = findGitRepositoryRoot(resolvedInputPath);
369
+ const repositoryRoot = findGitRepositoryRoot$1(resolvedInputPath);
369
370
  const inputPathWithinRepository = resolveRepositoryInputPath(resolvedInputPath, repositoryRoot);
370
- const comparison = resolveGitDiffComparison(repositoryRoot, diff);
371
+ const comparison = resolveGitDiffComparison$1(repositoryRoot, diff);
371
372
  const beforeGraph = loadGitTreeGraph(repositoryRoot, comparison.beforeTree, inputPathWithinRepository);
372
373
  const afterGraph = comparison.afterTree === void 0 ? loadWorkingTreeGraph(repositoryRoot, inputPathWithinRepository) : loadGitTreeGraph(repositoryRoot, comparison.afterTree, inputPathWithinRepository);
373
374
  if (beforeGraph === void 0 && afterGraph === void 0) throw new Error(`No package.json found from ${resolvedInputPath}`);
@@ -382,7 +383,7 @@ function analyzePackageDependencyDiff(directory, diff) {
382
383
  };
383
384
  }
384
385
  function resolveRepositoryInputPath(resolvedInputPath, repositoryRoot) {
385
- const existingPath = findNearestExistingPath(resolvedInputPath);
386
+ const existingPath = findNearestExistingPath$1(resolvedInputPath);
386
387
  const existingRealPath = fs.realpathSync.native(existingPath);
387
388
  const repositoryRealPath = fs.realpathSync.native(repositoryRoot);
388
389
  const relativeFromExistingPath = path.relative(existingPath, resolvedInputPath);
@@ -390,15 +391,15 @@ function resolveRepositoryInputPath(resolvedInputPath, repositoryRoot) {
390
391
  const normalizedPath = path.normalize(path.join(relativeToRepository, relativeFromExistingPath));
391
392
  return normalizedPath === "." ? "" : normalizedPath;
392
393
  }
393
- function findGitRepositoryRoot(resolvedInputPath) {
394
- const searchPath = findNearestExistingPath(resolvedInputPath);
394
+ function findGitRepositoryRoot$1(resolvedInputPath) {
395
+ const searchPath = findNearestExistingPath$1(resolvedInputPath);
395
396
  try {
396
- return runGit(searchPath, ["rev-parse", "--show-toplevel"]).trim();
397
+ return runGit$1(searchPath, ["rev-parse", "--show-toplevel"]).trim();
397
398
  } catch (error) {
398
- throw new Error(`Git diff mode requires a Git repository: ${getCommandErrorMessage(error)}`);
399
+ throw new Error(`Git diff mode requires a Git repository: ${getCommandErrorMessage$1(error)}`);
399
400
  }
400
401
  }
401
- function findNearestExistingPath(resolvedInputPath) {
402
+ function findNearestExistingPath$1(resolvedInputPath) {
402
403
  let currentPath = resolvedInputPath;
403
404
  while (!fs.existsSync(currentPath)) {
404
405
  const parentPath = path.dirname(currentPath);
@@ -408,49 +409,49 @@ function findNearestExistingPath(resolvedInputPath) {
408
409
  if (fs.statSync(currentPath).isFile()) return path.dirname(currentPath);
409
410
  return currentPath;
410
411
  }
411
- function resolveGitDiffComparison(repositoryRoot, diff) {
412
+ function resolveGitDiffComparison$1(repositoryRoot, diff) {
412
413
  if (diff.includes("...")) {
413
- const [baseRef, headRef] = splitDiffRange(diff, "...");
414
+ const [baseRef, headRef] = splitDiffRange$1(diff, "...");
414
415
  try {
415
416
  return {
416
- beforeTree: resolveGitTree(repositoryRoot, runGit(repositoryRoot, [
417
+ beforeTree: resolveGitTree$1(repositoryRoot, runGit$1(repositoryRoot, [
417
418
  "merge-base",
418
419
  baseRef,
419
420
  headRef
420
421
  ]).trim()),
421
- afterTree: resolveGitTree(repositoryRoot, headRef)
422
+ afterTree: resolveGitTree$1(repositoryRoot, headRef)
422
423
  };
423
424
  } catch (error) {
424
- throw new Error(`Failed to resolve Git diff spec \`${diff}\`: ${getCommandErrorMessage(error)}`);
425
+ throw new Error(`Failed to resolve Git diff spec \`${diff}\`: ${getCommandErrorMessage$1(error)}`);
425
426
  }
426
427
  }
427
428
  if (diff.includes("..")) {
428
- const [beforeRef, afterRef] = splitDiffRange(diff, "..");
429
+ const [beforeRef, afterRef] = splitDiffRange$1(diff, "..");
429
430
  try {
430
431
  return {
431
- beforeTree: resolveGitTree(repositoryRoot, beforeRef),
432
- afterTree: resolveGitTree(repositoryRoot, afterRef)
432
+ beforeTree: resolveGitTree$1(repositoryRoot, beforeRef),
433
+ afterTree: resolveGitTree$1(repositoryRoot, afterRef)
433
434
  };
434
435
  } catch (error) {
435
- throw new Error(`Failed to resolve Git diff spec \`${diff}\`: ${getCommandErrorMessage(error)}`);
436
+ throw new Error(`Failed to resolve Git diff spec \`${diff}\`: ${getCommandErrorMessage$1(error)}`);
436
437
  }
437
438
  }
438
439
  try {
439
440
  return {
440
- beforeTree: resolveGitTree(repositoryRoot, diff),
441
+ beforeTree: resolveGitTree$1(repositoryRoot, diff),
441
442
  afterTree: void 0
442
443
  };
443
444
  } catch (error) {
444
- throw new Error(`Failed to resolve Git diff spec \`${diff}\`: ${getCommandErrorMessage(error)}`);
445
+ throw new Error(`Failed to resolve Git diff spec \`${diff}\`: ${getCommandErrorMessage$1(error)}`);
445
446
  }
446
447
  }
447
- function splitDiffRange(diff, separator) {
448
+ function splitDiffRange$1(diff, separator) {
448
449
  const [left, right, ...extra] = diff.split(separator);
449
450
  if (left === void 0 || right === void 0 || left.length === 0 || right.length === 0 || extra.length > 0) throw new Error(`Invalid Git diff spec \`${diff}\``);
450
451
  return [left, right];
451
452
  }
452
- function resolveGitTree(repositoryRoot, reference) {
453
- return runGit(repositoryRoot, [
453
+ function resolveGitTree$1(repositoryRoot, reference) {
454
+ return runGit$1(repositoryRoot, [
454
455
  "rev-parse",
455
456
  "--verify",
456
457
  `${reference}^{tree}`
@@ -458,13 +459,13 @@ function resolveGitTree(repositoryRoot, reference) {
458
459
  }
459
460
  function loadWorkingTreeGraph(repositoryRoot, inputPathWithinRepository) {
460
461
  const packageGraph = tryAnalyzePackageGraph(resolveSnapshotInputPath(repositoryRoot, inputPathWithinRepository));
461
- return packageGraph === void 0 ? void 0 : toComparableGraph(packageGraph);
462
+ return packageGraph === void 0 ? void 0 : toComparableGraph$1(packageGraph);
462
463
  }
463
464
  function loadGitTreeGraph(repositoryRoot, tree, inputPathWithinRepository) {
464
- const snapshotRoot = materializeGitTreeSnapshot(repositoryRoot, tree);
465
+ const snapshotRoot = materializeGitTreeSnapshot$1(repositoryRoot, tree);
465
466
  try {
466
467
  const packageGraph = tryAnalyzePackageGraph(resolveSnapshotInputPath(snapshotRoot, inputPathWithinRepository));
467
- return packageGraph === void 0 ? void 0 : toComparableGraph(packageGraph);
468
+ return packageGraph === void 0 ? void 0 : toComparableGraph$1(packageGraph);
468
469
  } finally {
469
470
  fs.rmSync(snapshotRoot, {
470
471
  recursive: true,
@@ -472,9 +473,9 @@ function loadGitTreeGraph(repositoryRoot, tree, inputPathWithinRepository) {
472
473
  });
473
474
  }
474
475
  }
475
- function materializeGitTreeSnapshot(repositoryRoot, tree) {
476
+ function materializeGitTreeSnapshot$1(repositoryRoot, tree) {
476
477
  const snapshotRoot = fs.mkdtempSync(path.join(os.tmpdir(), "foresthouse-deps-diff-"));
477
- const trackedFiles = runGit(repositoryRoot, [
478
+ const trackedFiles = runGit$1(repositoryRoot, [
478
479
  "ls-tree",
479
480
  "-r",
480
481
  "-z",
@@ -485,7 +486,7 @@ function materializeGitTreeSnapshot(repositoryRoot, tree) {
485
486
  ensureSnapshotDirectory(snapshotRoot, filePath);
486
487
  });
487
488
  trackedFiles.filter(isManifestSnapshotFile).forEach((filePath) => {
488
- const fileContent = runGit(repositoryRoot, [
489
+ const fileContent = runGit$1(repositoryRoot, [
489
490
  "cat-file",
490
491
  "-p",
491
492
  `${tree}:${filePath}`
@@ -517,7 +518,7 @@ function tryAnalyzePackageGraph(inputPath) {
517
518
  throw error;
518
519
  }
519
520
  }
520
- function toComparableGraph(graph) {
521
+ function toComparableGraph$1(graph) {
521
522
  const pnpmLockResolutions = loadPnpmLockImporterResolutions(graph.repositoryRoot);
522
523
  const nodes = /* @__PURE__ */ new Map();
523
524
  graph.nodes.forEach((node, packageDir) => {
@@ -708,13 +709,13 @@ function collectChangedPackagePaths(repositoryRoot, comparison, beforeGraph, aft
708
709
  return changedPackagePaths;
709
710
  }
710
711
  function listChangedRepositoryPaths(repositoryRoot, comparison) {
711
- const trackedChanges = comparison.afterTree === void 0 ? runGit(repositoryRoot, [
712
+ const trackedChanges = comparison.afterTree === void 0 ? runGit$1(repositoryRoot, [
712
713
  "diff",
713
714
  "--name-only",
714
715
  "-z",
715
716
  comparison.beforeTree,
716
717
  "--"
717
- ]) : runGit(repositoryRoot, [
718
+ ]) : runGit$1(repositoryRoot, [
718
719
  "diff",
719
720
  "--name-only",
720
721
  "-z",
@@ -722,7 +723,7 @@ function listChangedRepositoryPaths(repositoryRoot, comparison) {
722
723
  comparison.afterTree
723
724
  ]);
724
725
  const changedPaths = new Set(trackedChanges.split("\0").filter((filePath) => filePath.length > 0));
725
- if (comparison.afterTree === void 0) runGit(repositoryRoot, [
726
+ if (comparison.afterTree === void 0) runGit$1(repositoryRoot, [
726
727
  "ls-files",
727
728
  "--others",
728
729
  "--exclude-standard",
@@ -736,13 +737,14 @@ function isFileInPackage(filePath, packagePath) {
736
737
  if (packagePath === ".") return true;
737
738
  return filePath === packagePath || filePath.startsWith(`${packagePath}/`);
738
739
  }
739
- function runGit(repositoryRoot, args, options = {}) {
740
+ function runGit$1(repositoryRoot, args, options = {}) {
740
741
  const output = execFileSync("git", [
741
742
  "-C",
742
743
  repositoryRoot,
743
744
  ...args
744
745
  ], {
745
746
  encoding: "utf8",
747
+ maxBuffer: GIT_EXEC_MAX_BUFFER$1,
746
748
  stdio: [
747
749
  "ignore",
748
750
  "pipe",
@@ -751,7 +753,7 @@ function runGit(repositoryRoot, args, options = {}) {
751
753
  });
752
754
  return options.trim === false ? output : output.trimEnd();
753
755
  }
754
- function getCommandErrorMessage(error) {
756
+ function getCommandErrorMessage$1(error) {
755
757
  if (!(error instanceof Error)) return "Unknown Git error";
756
758
  const stderr = Reflect.get(error, "stderr");
757
759
  if (typeof stderr === "string" && stderr.trim().length > 0) return stderr.trim();
@@ -1523,6 +1525,67 @@ var MultiEntryImportAnalyzer = class extends BaseAnalyzer {
1523
1525
  }
1524
1526
  };
1525
1527
  //#endregion
1528
+ //#region src/app/react-entry-files.ts
1529
+ const NEXT_JS_ENTRY_ROOTS = [
1530
+ "pages",
1531
+ "app",
1532
+ path.join("src", "pages"),
1533
+ path.join("src", "app")
1534
+ ];
1535
+ function resolveReactEntryFiles(options) {
1536
+ if (options.entryFile !== void 0) return [options.entryFile];
1537
+ if (!options.nextjs) throw new Error("Missing React entry file. Use `foresthouse react <entry-file>` or `foresthouse react --nextjs`.");
1538
+ return discoverNextJsPageEntries(options.cwd);
1539
+ }
1540
+ function discoverNextJsPageEntries(cwd) {
1541
+ const effectiveCwd = path.resolve(cwd ?? process.cwd());
1542
+ const entries = /* @__PURE__ */ new Set();
1543
+ collectPagesRouterEntries(path.join(effectiveCwd, "pages"), effectiveCwd, entries);
1544
+ collectAppRouterEntries(path.join(effectiveCwd, "app"), effectiveCwd, entries);
1545
+ collectPagesRouterEntries(path.join(effectiveCwd, "src", "pages"), effectiveCwd, entries);
1546
+ collectAppRouterEntries(path.join(effectiveCwd, "src", "app"), effectiveCwd, entries);
1547
+ const resolvedEntries = [...entries].sort();
1548
+ if (resolvedEntries.length > 0) return resolvedEntries;
1549
+ const searchedRoots = NEXT_JS_ENTRY_ROOTS.map((root) => `\`${root}/\``).join(", ");
1550
+ throw new Error(`No Next.js page entries found. Searched ${searchedRoots} relative to ${effectiveCwd}.`);
1551
+ }
1552
+ function collectPagesRouterEntries(rootDirectory, cwd, entries) {
1553
+ walkDirectory(rootDirectory, (filePath, relativePath) => {
1554
+ if (!isSourceCodeFile(filePath) || filePath.endsWith(".d.ts")) return;
1555
+ if (relativePath.split(path.sep)[0] === "api") return;
1556
+ if (path.basename(filePath).startsWith("_")) return;
1557
+ entries.add(path.relative(cwd, filePath));
1558
+ });
1559
+ }
1560
+ function collectAppRouterEntries(rootDirectory, cwd, entries) {
1561
+ walkDirectory(rootDirectory, (filePath) => {
1562
+ if (!isSourceCodeFile(filePath) || filePath.endsWith(".d.ts")) return;
1563
+ const extension = path.extname(filePath);
1564
+ if (path.basename(filePath, extension) !== "page") return;
1565
+ entries.add(path.relative(cwd, filePath));
1566
+ });
1567
+ }
1568
+ function walkDirectory(rootDirectory, visitor) {
1569
+ if (!fs.existsSync(rootDirectory)) return;
1570
+ const stack = [rootDirectory];
1571
+ while (stack.length > 0) {
1572
+ const directory = stack.pop();
1573
+ if (directory === void 0) continue;
1574
+ const directoryEntries = fs.readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
1575
+ for (let index = directoryEntries.length - 1; index >= 0; index -= 1) {
1576
+ const entry = directoryEntries[index];
1577
+ if (entry === void 0) continue;
1578
+ const entryPath = path.join(directory, entry.name);
1579
+ if (entry.isDirectory()) {
1580
+ stack.push(entryPath);
1581
+ continue;
1582
+ }
1583
+ if (!entry.isFile()) continue;
1584
+ visitor(entryPath, path.relative(rootDirectory, entryPath));
1585
+ }
1586
+ }
1587
+ }
1588
+ //#endregion
1526
1589
  //#region src/analyzers/react/bindings.ts
1527
1590
  function collectImportsAndExports(statement, sourceDependencies, symbolsByName, importsByLocalName, exportsByName, reExportBindingsByName, exportAllBindings) {
1528
1591
  switch (statement.type) {
@@ -1536,7 +1599,7 @@ function collectImportsAndExports(statement, sourceDependencies, symbolsByName,
1536
1599
  collectExportAllBindings(statement, sourceDependencies, exportAllBindings);
1537
1600
  return;
1538
1601
  case "ExportDefaultDeclaration":
1539
- collectDefaultExport(statement, symbolsByName, exportsByName);
1602
+ collectDefaultExport(statement, symbolsByName, importsByLocalName, exportsByName, reExportBindingsByName);
1540
1603
  return;
1541
1604
  default: return;
1542
1605
  }
@@ -1571,6 +1634,12 @@ function getImportBinding(specifier, sourceSpecifier, sourcePath) {
1571
1634
  sourceSpecifier,
1572
1635
  ...sourcePath === void 0 ? {} : { sourcePath }
1573
1636
  };
1637
+ if (specifier.type === "ImportNamespaceSpecifier") return {
1638
+ localName: specifier.local.name,
1639
+ importedName: "*",
1640
+ sourceSpecifier,
1641
+ ...sourcePath === void 0 ? {} : { sourcePath }
1642
+ };
1574
1643
  }
1575
1644
  function collectNamedExports(declaration, sourceDependencies, symbolsByName, exportsByName, reExportBindingsByName) {
1576
1645
  if (declaration.exportKind === "type") return;
@@ -1611,14 +1680,19 @@ function collectExportAllBindings(declaration, sourceDependencies, exportAllBind
1611
1680
  ...sourcePath === void 0 ? {} : { sourcePath }
1612
1681
  });
1613
1682
  }
1614
- function collectDefaultExport(declaration, symbolsByName, exportsByName) {
1683
+ function collectDefaultExport(declaration, symbolsByName, importsByLocalName, exportsByName, reExportBindingsByName) {
1615
1684
  if (declaration.declaration.type === "FunctionDeclaration" || declaration.declaration.type === "FunctionExpression") {
1616
1685
  const localName = declaration.declaration.id?.name;
1617
1686
  if (localName !== void 0) addExportBinding(localName, "default", symbolsByName, exportsByName);
1618
1687
  return;
1619
1688
  }
1620
1689
  if (declaration.declaration.type === "Identifier") {
1621
- addExportBinding(declaration.declaration.name, "default", symbolsByName, exportsByName);
1690
+ const localName = declaration.declaration.name;
1691
+ addExportBinding(localName, "default", symbolsByName, exportsByName);
1692
+ if (!exportsByName.has("default")) {
1693
+ const importBinding = importsByLocalName.get(localName);
1694
+ if (importBinding !== void 0) reExportBindingsByName.set("default", importBinding);
1695
+ }
1622
1696
  return;
1623
1697
  }
1624
1698
  if (declaration.declaration.type === "ArrowFunctionExpression") addExportBinding("default", "default", symbolsByName, exportsByName);
@@ -1682,6 +1756,14 @@ function getComponentReferenceName(node) {
1682
1756
  const name = getJsxName(node.openingElement.name);
1683
1757
  return name !== void 0 && isComponentName(name) ? name : void 0;
1684
1758
  }
1759
+ function getMemberExpressionComponentReferenceName(node) {
1760
+ const name = node.openingElement.name;
1761
+ if (name.type !== "JSXMemberExpression") return;
1762
+ if (name.object.type !== "JSXIdentifier") return;
1763
+ const objectName = name.object.name;
1764
+ const propertyName = name.property.name;
1765
+ if (isComponentName(objectName)) return `${objectName}.${propertyName}`;
1766
+ }
1685
1767
  function getBuiltinReferenceName(node) {
1686
1768
  const name = getJsxName(node.openingElement.name);
1687
1769
  return name !== void 0 && isIntrinsicElementName(name) ? name : void 0;
@@ -1796,7 +1878,7 @@ function collectNodeEntryUsages(node, filePath, sourceText, entries, includeBuil
1796
1878
  if (FUNCTION_NODE_TYPES.has(node.type)) return;
1797
1879
  let nextHasComponentAncestor = hasComponentAncestor;
1798
1880
  if (node.type === "JSXElement") {
1799
- const referenceName = getComponentReferenceName(node);
1881
+ const referenceName = getComponentReferenceName(node) ?? getMemberExpressionComponentReferenceName(node);
1800
1882
  if (referenceName !== void 0) {
1801
1883
  if (!hasComponentAncestor) addPendingReactUsageEntry(entries, referenceName, "component", createReactUsageLocation(filePath, sourceText, node.start));
1802
1884
  nextHasComponentAncestor = true;
@@ -1982,6 +2064,10 @@ function analyzeSymbolUsages(symbol, includeBuiltins) {
1982
2064
  if (node.type === "JSXElement") {
1983
2065
  const name = getComponentReferenceName(node);
1984
2066
  if (name !== void 0) symbol.componentReferences.add(name);
2067
+ else {
2068
+ const memberName = getMemberExpressionComponentReferenceName(node);
2069
+ if (memberName !== void 0) symbol.componentReferences.add(memberName);
2070
+ }
1985
2071
  if (includeBuiltins) {
1986
2072
  const builtinName = getBuiltinReferenceName(node);
1987
2073
  if (builtinName !== void 0) symbol.builtinReferences.add(builtinName);
@@ -2063,6 +2149,8 @@ function collectComponentDeclarationEntryUsages(symbolsByName, sourceText) {
2063
2149
  //#region src/analyzers/react/references.ts
2064
2150
  function resolveReactReference(fileAnalysis, fileAnalyses, name, kind) {
2065
2151
  if (kind === "builtin") return getBuiltinNodeId(name);
2152
+ const dotIndex = name.indexOf(".");
2153
+ if (dotIndex !== -1) return resolveNamespaceMemberReference(fileAnalysis, fileAnalyses, name.slice(0, dotIndex), name.slice(dotIndex + 1), kind);
2066
2154
  const localSymbol = fileAnalysis.allSymbolsByName.get(name);
2067
2155
  if (localSymbol !== void 0 && localSymbol.kind === kind) return localSymbol.id;
2068
2156
  const importBinding = fileAnalysis.importsByLocalName.get(name);
@@ -2074,6 +2162,13 @@ function resolveReactReference(fileAnalysis, fileAnalyses, name, kind) {
2074
2162
  if (targetId === void 0) return;
2075
2163
  return targetId;
2076
2164
  }
2165
+ function resolveNamespaceMemberReference(fileAnalysis, fileAnalyses, namespaceName, propertyName, kind) {
2166
+ const importBinding = fileAnalysis.importsByLocalName.get(namespaceName);
2167
+ if (importBinding === void 0 || importBinding.importedName !== "*" || importBinding.sourcePath === void 0) return;
2168
+ const sourceFileAnalysis = fileAnalyses.get(importBinding.sourcePath);
2169
+ if (sourceFileAnalysis === void 0) return;
2170
+ return resolveExportedSymbol(sourceFileAnalysis, propertyName, kind, fileAnalyses, /* @__PURE__ */ new Set());
2171
+ }
2077
2172
  function resolveExportedSymbol(fileAnalysis, exportName, kind, fileAnalyses, visited) {
2078
2173
  const visitKey = `${fileAnalysis.filePath}:${exportName}:${kind}`;
2079
2174
  if (visited.has(visitKey)) return;
@@ -2322,6 +2417,513 @@ function matchesReactFilter(node, filter) {
2322
2417
  return filter === "all" || node.kind === filter;
2323
2418
  }
2324
2419
  //#endregion
2420
+ //#region src/analyzers/react/diff.ts
2421
+ function analyzeReactUsageDiff(options) {
2422
+ const originalCwd = resolveOriginalCwd(options.cwd);
2423
+ const repositoryRoot = findGitRepositoryRoot(resolveDiffInputPath(options, originalCwd));
2424
+ const comparison = resolveGitDiffComparison(repositoryRoot, options.diff ?? "HEAD");
2425
+ const [beforeGraph, afterGraph] = loadBothGraphs({
2426
+ repositoryRoot,
2427
+ originalCwd,
2428
+ cwdWithinRepository: resolvePathWithinRepository(originalCwd, repositoryRoot, "Working directory"),
2429
+ entryFile: options.entryFile,
2430
+ nextjs: options.nextjs,
2431
+ filter: options.filter,
2432
+ includeBuiltins: options.includeBuiltins,
2433
+ analyzeOptions: toAnalyzeOptions(options)
2434
+ }, comparison);
2435
+ if (beforeGraph === void 0 && afterGraph === void 0) throw new Error("No React entry files found for the requested diff. Check the entry path or Next.js discovery roots.");
2436
+ const entries = diffEntries(beforeGraph, afterGraph);
2437
+ return {
2438
+ kind: "react-usage-diff",
2439
+ repositoryRoot,
2440
+ cwd: originalCwd,
2441
+ entries,
2442
+ roots: entries.length > 0 ? entries.map((entry) => entry.node) : diffRoots(beforeGraph, afterGraph)
2443
+ };
2444
+ }
2445
+ const GIT_EXEC_MAX_BUFFER = 64 * 1024 * 1024;
2446
+ function resolveOriginalCwd(cwd) {
2447
+ return path.resolve(cwd ?? process.cwd());
2448
+ }
2449
+ function resolveDiffInputPath(options, originalCwd) {
2450
+ if (options.entryFile === void 0) return originalCwd;
2451
+ return path.isAbsolute(options.entryFile) ? path.resolve(options.entryFile) : path.resolve(originalCwd, options.entryFile);
2452
+ }
2453
+ function toAnalyzeOptions(options) {
2454
+ return {
2455
+ ...options.configPath === void 0 ? {} : { configPath: options.configPath },
2456
+ expandWorkspaces: options.expandWorkspaces,
2457
+ projectOnly: options.projectOnly,
2458
+ includeBuiltins: options.includeBuiltins
2459
+ };
2460
+ }
2461
+ function loadBothGraphs(context, comparison) {
2462
+ const snapshotsToCleanup = [];
2463
+ try {
2464
+ let beforeRoot;
2465
+ let afterRoot;
2466
+ if (comparison.afterTree === void 0) {
2467
+ beforeRoot = materializeGitTreeSnapshot(context.repositoryRoot, comparison.beforeTree);
2468
+ snapshotsToCleanup.push(beforeRoot);
2469
+ afterRoot = context.repositoryRoot;
2470
+ } else {
2471
+ [beforeRoot, afterRoot] = materializeTwoGitTreeSnapshots(context.repositoryRoot, comparison.beforeTree, comparison.afterTree);
2472
+ snapshotsToCleanup.push(beforeRoot, afterRoot);
2473
+ }
2474
+ return [tryAnalyzeComparableReactGraph(context, beforeRoot), tryAnalyzeComparableReactGraph(context, afterRoot)];
2475
+ } finally {
2476
+ for (const dir of snapshotsToCleanup) fs.rm(dir, {
2477
+ recursive: true,
2478
+ force: true
2479
+ }, () => {});
2480
+ }
2481
+ }
2482
+ function tryAnalyzeComparableReactGraph(context, workingRoot) {
2483
+ const snapshotCwd = resolveSnapshotCwd(workingRoot, context.cwdWithinRepository);
2484
+ const entryFiles = resolveSnapshotEntryFiles(context, snapshotCwd, workingRoot);
2485
+ if (entryFiles === void 0 || entryFiles.length === 0) return;
2486
+ return toComparableGraph(analyzeReactUsage(entryFiles, {
2487
+ ...context.analyzeOptions,
2488
+ cwd: snapshotCwd,
2489
+ ...context.analyzeOptions.configPath === void 0 ? {} : { configPath: resolveSnapshotConfigPath(context.analyzeOptions.configPath, workingRoot, context.repositoryRoot) }
2490
+ }), context.repositoryRoot, context.originalCwd, context.filter);
2491
+ }
2492
+ function resolveSnapshotCwd(workingRoot, cwdWithinRepository) {
2493
+ return cwdWithinRepository.length === 0 ? workingRoot : path.join(workingRoot, cwdWithinRepository);
2494
+ }
2495
+ function resolveSnapshotEntryFiles(context, snapshotCwd, workingRoot) {
2496
+ if (context.entryFile !== void 0) {
2497
+ const entryFile = resolveSnapshotEntryPath(context.entryFile, workingRoot, context.repositoryRoot);
2498
+ return doesResolvedPathExist(entryFile, snapshotCwd) ? [entryFile] : void 0;
2499
+ }
2500
+ if (!context.nextjs) throw new Error("Missing React entry file. Use `foresthouse react <entry-file>` or `foresthouse react --nextjs`.");
2501
+ try {
2502
+ return resolveReactEntryFiles({
2503
+ command: "react",
2504
+ entryFile: void 0,
2505
+ diff: void 0,
2506
+ cwd: snapshotCwd,
2507
+ configPath: void 0,
2508
+ expandWorkspaces: true,
2509
+ projectOnly: false,
2510
+ json: false,
2511
+ filter: context.filter,
2512
+ nextjs: true,
2513
+ includeBuiltins: context.includeBuiltins
2514
+ });
2515
+ } catch (error) {
2516
+ if (error instanceof Error && error.message.startsWith("No Next.js page entries found.")) return;
2517
+ throw error;
2518
+ }
2519
+ }
2520
+ function resolveSnapshotEntryPath(entryFile, workingRoot, repositoryRoot) {
2521
+ if (!path.isAbsolute(entryFile)) return entryFile;
2522
+ const entryWithinRepository = resolvePathWithinRepository(entryFile, repositoryRoot, "React entry file");
2523
+ return entryWithinRepository.length === 0 ? workingRoot : path.join(workingRoot, entryWithinRepository);
2524
+ }
2525
+ function resolveSnapshotConfigPath(configPath, workingRoot, repositoryRoot) {
2526
+ if (!path.isAbsolute(configPath)) return configPath;
2527
+ const configWithinRepository = resolvePathWithinRepository(configPath, repositoryRoot, "TypeScript config");
2528
+ return configWithinRepository.length === 0 ? workingRoot : path.join(workingRoot, configWithinRepository);
2529
+ }
2530
+ function doesResolvedPathExist(entryFile, cwd) {
2531
+ const resolvedEntryPath = path.isAbsolute(entryFile) ? entryFile : path.resolve(cwd, entryFile);
2532
+ return fs.existsSync(resolvedEntryPath);
2533
+ }
2534
+ function toComparableGraph(graph, _repositoryRoot, originalCwd, filter) {
2535
+ const nodes = /* @__PURE__ */ new Map();
2536
+ graph.nodes.forEach((node) => {
2537
+ const comparableNode = toComparableNode(node, graph, originalCwd, filter);
2538
+ nodes.set(comparableNode.id, comparableNode);
2539
+ });
2540
+ const entries = indexEntries(getReactUsageEntries(graph, filter).map((entry) => toComparableEntry(entry, graph, originalCwd)));
2541
+ return {
2542
+ entriesByKey: new Map(entries.map((entry) => [entry.key, entry])),
2543
+ rootIds: getReactUsageRoots(graph, filter).map((rootId) => {
2544
+ const node = graph.nodes.get(rootId);
2545
+ return node === void 0 ? void 0 : createComparableNodeId(node, graph.cwd);
2546
+ }).filter((rootId) => rootId !== void 0),
2547
+ nodes
2548
+ };
2549
+ }
2550
+ function toComparableNode(node, graph, originalCwd, filter) {
2551
+ const usages = indexUsages(getFilteredUsages(node, graph, filter).map((usage) => toComparableEdge(usage, graph)));
2552
+ return {
2553
+ id: createComparableNodeId(node, graph.cwd),
2554
+ name: node.name,
2555
+ symbolKind: node.kind,
2556
+ filePath: toDisplayFilePath(node.filePath, graph.cwd, originalCwd),
2557
+ exportNames: [...node.exportNames].sort(),
2558
+ usagesByKey: new Map(usages.map((usage) => [usage.key, usage]))
2559
+ };
2560
+ }
2561
+ function toComparableEdge(usage, graph) {
2562
+ const targetNode = graph.nodes.get(usage.target);
2563
+ return {
2564
+ kind: usage.kind,
2565
+ targetId: targetNode === void 0 ? usage.target : createComparableNodeId(targetNode, graph.cwd),
2566
+ referenceName: usage.referenceName
2567
+ };
2568
+ }
2569
+ function toComparableEntry(entry, graph, originalCwd) {
2570
+ const targetNode = graph.nodes.get(entry.target);
2571
+ return {
2572
+ targetId: targetNode === void 0 ? entry.target : createComparableNodeId(targetNode, graph.cwd),
2573
+ referenceName: entry.referenceName,
2574
+ filePath: toDisplayFilePath(entry.location.filePath, graph.cwd, originalCwd),
2575
+ line: entry.location.line,
2576
+ column: entry.location.column
2577
+ };
2578
+ }
2579
+ function indexUsages(usages) {
2580
+ const counts = /* @__PURE__ */ new Map();
2581
+ return [...usages].sort(compareComparableEdgeState).map((usage) => {
2582
+ const signature = `${usage.kind}:${usage.targetId}`;
2583
+ const occurrence = counts.get(signature) ?? 0;
2584
+ counts.set(signature, occurrence + 1);
2585
+ return {
2586
+ ...usage,
2587
+ key: `${signature}#${occurrence}`
2588
+ };
2589
+ });
2590
+ }
2591
+ function indexEntries(entries) {
2592
+ const counts = /* @__PURE__ */ new Map();
2593
+ return [...entries].sort(compareComparableEntryState).map((entry) => {
2594
+ const signature = `${entry.filePath}:${entry.targetId}`;
2595
+ const occurrence = counts.get(signature) ?? 0;
2596
+ counts.set(signature, occurrence + 1);
2597
+ return {
2598
+ ...entry,
2599
+ key: `${signature}#${occurrence}`
2600
+ };
2601
+ });
2602
+ }
2603
+ function createComparableNodeId(node, analysisCwd) {
2604
+ return `${node.kind}:${toComparablePath(node.filePath, analysisCwd)}#${node.name}`;
2605
+ }
2606
+ function toComparablePath(filePath, analysisCwd) {
2607
+ if (!path.isAbsolute(filePath)) return normalizePathSeparators(filePath);
2608
+ const relativePath = path.relative(fs.realpathSync.native(analysisCwd), fs.realpathSync.native(filePath));
2609
+ if (relativePath === "") return ".";
2610
+ return normalizePathSeparators(relativePath);
2611
+ }
2612
+ function toDisplayFilePath(filePath, analysisCwd, originalCwd) {
2613
+ if (!path.isAbsolute(filePath)) return normalizePathSeparators(filePath);
2614
+ const relativePath = path.relative(fs.realpathSync.native(analysisCwd), fs.realpathSync.native(filePath));
2615
+ if (relativePath === "") return ".";
2616
+ return toDisplayPath(path.resolve(originalCwd, relativePath), originalCwd);
2617
+ }
2618
+ function diffEntries(beforeGraph, afterGraph) {
2619
+ const entryKeys = new Set([...Array.from(beforeGraph?.entriesByKey.keys() ?? []), ...Array.from(afterGraph?.entriesByKey.keys() ?? [])]);
2620
+ return Array.from(entryKeys).sort((left, right) => compareComparableEntryState(beforeGraph?.entriesByKey.get(left) ?? afterGraph?.entriesByKey.get(left), beforeGraph?.entriesByKey.get(right) ?? afterGraph?.entriesByKey.get(right))).flatMap((entryKey) => {
2621
+ const beforeEntry = beforeGraph?.entriesByKey.get(entryKey);
2622
+ const afterEntry = afterGraph?.entriesByKey.get(entryKey);
2623
+ const node = diffNode(beforeEntry?.targetId, afterEntry?.targetId, beforeGraph, afterGraph, /* @__PURE__ */ new Set());
2624
+ const change = resolveEntryChange(beforeEntry, afterEntry);
2625
+ if (change === "unchanged" && !hasVisibleNodeChanges(node)) return [];
2626
+ const currentEntry = afterEntry ?? beforeEntry;
2627
+ if (currentEntry === void 0) return [];
2628
+ return [{
2629
+ key: currentEntry.key,
2630
+ change,
2631
+ targetId: node.id,
2632
+ referenceName: afterEntry?.referenceName ?? beforeEntry?.referenceName ?? node.name,
2633
+ ...beforeEntry === void 0 ? {} : {
2634
+ beforeReferenceName: beforeEntry.referenceName,
2635
+ beforeFilePath: beforeEntry.filePath,
2636
+ beforeLine: beforeEntry.line,
2637
+ beforeColumn: beforeEntry.column
2638
+ },
2639
+ ...afterEntry === void 0 ? {} : {
2640
+ afterReferenceName: afterEntry.referenceName,
2641
+ afterFilePath: afterEntry.filePath,
2642
+ afterLine: afterEntry.line,
2643
+ afterColumn: afterEntry.column
2644
+ },
2645
+ node
2646
+ }];
2647
+ });
2648
+ }
2649
+ function diffRoots(beforeGraph, afterGraph) {
2650
+ const rootIds = new Set([...Array.from(beforeGraph?.rootIds ?? []), ...Array.from(afterGraph?.rootIds ?? [])]);
2651
+ return Array.from(rootIds).sort((left, right) => compareComparableNodeState(beforeGraph?.nodes.get(left) ?? afterGraph?.nodes.get(left), beforeGraph?.nodes.get(right) ?? afterGraph?.nodes.get(right))).flatMap((rootId) => {
2652
+ const node = diffNode(beforeGraph?.nodes.has(rootId) === true ? rootId : void 0, afterGraph?.nodes.has(rootId) === true ? rootId : void 0, beforeGraph, afterGraph, /* @__PURE__ */ new Set());
2653
+ return hasVisibleNodeChanges(node) ? [node] : [];
2654
+ });
2655
+ }
2656
+ function diffNode(beforeNodeId, afterNodeId, beforeGraph, afterGraph, ancestry) {
2657
+ const beforeNode = beforeNodeId === void 0 ? void 0 : beforeGraph?.nodes.get(beforeNodeId);
2658
+ const afterNode = afterNodeId === void 0 ? void 0 : afterGraph?.nodes.get(afterNodeId);
2659
+ if (beforeNode === void 0 && afterNode === void 0) throw new Error("Unable to resolve React diff node.");
2660
+ const ancestryKey = `${beforeNodeId ?? ""}->${afterNodeId ?? ""}`;
2661
+ if (ancestry.has(ancestryKey)) {
2662
+ const node = afterNode ?? beforeNode;
2663
+ if (node === void 0) throw new Error("Unable to resolve circular React diff node.");
2664
+ return {
2665
+ id: node.id,
2666
+ name: node.name,
2667
+ symbolKind: node.symbolKind,
2668
+ circular: true,
2669
+ filePath: node.filePath,
2670
+ change: "unchanged",
2671
+ exportNames: node.exportNames,
2672
+ usages: []
2673
+ };
2674
+ }
2675
+ const nextAncestry = new Set(ancestry);
2676
+ nextAncestry.add(ancestryKey);
2677
+ const usages = collectUsageDiffs(beforeNode, afterNode, beforeGraph, afterGraph, nextAncestry);
2678
+ const directChange = resolveNodeDirectChange(beforeNode, afterNode);
2679
+ const node = afterNode ?? beforeNode;
2680
+ if (node === void 0) throw new Error("Unable to resolve React diff node.");
2681
+ return {
2682
+ id: node.id,
2683
+ name: node.name,
2684
+ symbolKind: node.symbolKind,
2685
+ filePath: node.filePath,
2686
+ change: directChange === "unchanged" && usages.length > 0 ? "changed" : directChange,
2687
+ exportNames: node.exportNames,
2688
+ ...beforeNode === void 0 ? {} : { beforeExportNames: beforeNode.exportNames },
2689
+ ...afterNode === void 0 ? {} : { afterExportNames: afterNode.exportNames },
2690
+ usages
2691
+ };
2692
+ }
2693
+ function collectUsageDiffs(beforeNode, afterNode, beforeGraph, afterGraph, ancestry) {
2694
+ const usageKeys = new Set([...Array.from(beforeNode?.usagesByKey.keys() ?? []), ...Array.from(afterNode?.usagesByKey.keys() ?? [])]);
2695
+ return Array.from(usageKeys).sort((left, right) => compareComparableEdgeState(beforeNode?.usagesByKey.get(left) ?? afterNode?.usagesByKey.get(left), beforeNode?.usagesByKey.get(right) ?? afterNode?.usagesByKey.get(right))).flatMap((usageKey) => {
2696
+ const beforeUsage = beforeNode?.usagesByKey.get(usageKey);
2697
+ const afterUsage = afterNode?.usagesByKey.get(usageKey);
2698
+ const node = diffNode(beforeUsage?.targetId, afterUsage?.targetId, beforeGraph, afterGraph, ancestry);
2699
+ const change = resolveEdgeChange(beforeUsage, afterUsage);
2700
+ if (change === "unchanged" && !hasVisibleNodeChanges(node)) return [];
2701
+ return [{
2702
+ key: afterUsage?.key ?? beforeUsage?.key ?? usageKey,
2703
+ kind: afterUsage?.kind ?? beforeUsage?.kind ?? "render",
2704
+ change,
2705
+ targetId: node.id,
2706
+ referenceName: afterUsage?.referenceName ?? beforeUsage?.referenceName ?? node.name,
2707
+ ...beforeUsage === void 0 ? {} : { beforeReferenceName: beforeUsage.referenceName },
2708
+ ...afterUsage === void 0 ? {} : { afterReferenceName: afterUsage.referenceName },
2709
+ node
2710
+ }];
2711
+ });
2712
+ }
2713
+ function resolveEntryChange(beforeEntry, afterEntry) {
2714
+ if (beforeEntry === void 0 && afterEntry !== void 0) return "added";
2715
+ if (beforeEntry !== void 0 && afterEntry === void 0) return "removed";
2716
+ if (beforeEntry === void 0 || afterEntry === void 0) return "unchanged";
2717
+ return beforeEntry.targetId === afterEntry.targetId && beforeEntry.referenceName === afterEntry.referenceName && beforeEntry.filePath === afterEntry.filePath && beforeEntry.line === afterEntry.line && beforeEntry.column === afterEntry.column ? "unchanged" : "changed";
2718
+ }
2719
+ function resolveNodeDirectChange(beforeNode, afterNode) {
2720
+ if (beforeNode === void 0 && afterNode !== void 0) return "added";
2721
+ if (beforeNode !== void 0 && afterNode === void 0) return "removed";
2722
+ if (beforeNode === void 0 || afterNode === void 0) return "unchanged";
2723
+ return beforeNode.name === afterNode.name && beforeNode.symbolKind === afterNode.symbolKind && beforeNode.filePath === afterNode.filePath && areStringArraysEqual(beforeNode.exportNames, afterNode.exportNames) ? "unchanged" : "changed";
2724
+ }
2725
+ function resolveEdgeChange(beforeUsage, afterUsage) {
2726
+ if (beforeUsage === void 0 && afterUsage !== void 0) return "added";
2727
+ if (beforeUsage !== void 0 && afterUsage === void 0) return "removed";
2728
+ if (beforeUsage === void 0 || afterUsage === void 0) return "unchanged";
2729
+ return beforeUsage.kind === afterUsage.kind && beforeUsage.targetId === afterUsage.targetId && beforeUsage.referenceName === afterUsage.referenceName ? "unchanged" : "changed";
2730
+ }
2731
+ function hasVisibleNodeChanges(node) {
2732
+ return node.change !== "unchanged" || node.usages.length > 0;
2733
+ }
2734
+ function compareComparableNodeState(left, right) {
2735
+ return (left?.filePath ?? "").localeCompare(right?.filePath ?? "") || (left?.name ?? "").localeCompare(right?.name ?? "") || (left?.symbolKind ?? "").localeCompare(right?.symbolKind ?? "");
2736
+ }
2737
+ function compareComparableEdgeState(left, right) {
2738
+ return (left?.targetId ?? "").localeCompare(right?.targetId ?? "") || (left?.referenceName ?? "").localeCompare(right?.referenceName ?? "") || (left?.kind ?? "").localeCompare(right?.kind ?? "");
2739
+ }
2740
+ function compareComparableEntryState(left, right) {
2741
+ return (left?.filePath ?? "").localeCompare(right?.filePath ?? "") || (left?.line ?? 0) - (right?.line ?? 0) || (left?.column ?? 0) - (right?.column ?? 0) || (left?.referenceName ?? "").localeCompare(right?.referenceName ?? "") || (left?.targetId ?? "").localeCompare(right?.targetId ?? "");
2742
+ }
2743
+ function areStringArraysEqual(left, right) {
2744
+ return left.length === right.length && left.every((value, index) => value === right[index]);
2745
+ }
2746
+ function resolvePathWithinRepository(resolvedPath, repositoryRoot, label) {
2747
+ const absolutePath = path.resolve(resolvedPath);
2748
+ const existingPath = findNearestExistingPath(absolutePath);
2749
+ const existingRealPath = fs.realpathSync.native(existingPath);
2750
+ const repositoryRealPath = fs.realpathSync.native(repositoryRoot);
2751
+ const relativeFromExistingPath = path.relative(existingPath, absolutePath);
2752
+ const relativePath = path.normalize(path.join(path.relative(repositoryRealPath, existingRealPath), relativeFromExistingPath));
2753
+ if (relativePath === "") return "";
2754
+ if (relativePath.startsWith("..")) throw new Error(`${label} must be inside the Git repository when using --diff.`);
2755
+ return normalizePathSeparators(relativePath);
2756
+ }
2757
+ function findGitRepositoryRoot(resolvedInputPath) {
2758
+ const searchPath = findNearestExistingPath(resolvedInputPath);
2759
+ try {
2760
+ return runGit(searchPath, ["rev-parse", "--show-toplevel"]).trim();
2761
+ } catch (error) {
2762
+ throw new Error(`Git diff mode requires a Git repository: ${getCommandErrorMessage(error)}`);
2763
+ }
2764
+ }
2765
+ function findNearestExistingPath(resolvedInputPath) {
2766
+ let currentPath = resolvedInputPath;
2767
+ while (!fs.existsSync(currentPath)) {
2768
+ const parentPath = path.dirname(currentPath);
2769
+ if (parentPath === currentPath) return resolvedInputPath;
2770
+ currentPath = parentPath;
2771
+ }
2772
+ if (fs.statSync(currentPath).isFile()) return path.dirname(currentPath);
2773
+ return currentPath;
2774
+ }
2775
+ function resolveGitDiffComparison(repositoryRoot, diff) {
2776
+ if (diff.includes("...")) {
2777
+ const [baseRef, headRef] = splitDiffRange(diff, "...");
2778
+ try {
2779
+ return {
2780
+ beforeTree: resolveGitTree(repositoryRoot, runGit(repositoryRoot, [
2781
+ "merge-base",
2782
+ baseRef,
2783
+ headRef
2784
+ ]).trim()),
2785
+ afterTree: resolveGitTree(repositoryRoot, headRef)
2786
+ };
2787
+ } catch (error) {
2788
+ throw new Error(`Failed to resolve Git diff spec \`${diff}\`: ${getCommandErrorMessage(error)}`);
2789
+ }
2790
+ }
2791
+ if (diff.includes("..")) {
2792
+ const [beforeRef, afterRef] = splitDiffRange(diff, "..");
2793
+ try {
2794
+ return {
2795
+ beforeTree: resolveGitTree(repositoryRoot, beforeRef),
2796
+ afterTree: resolveGitTree(repositoryRoot, afterRef)
2797
+ };
2798
+ } catch (error) {
2799
+ throw new Error(`Failed to resolve Git diff spec \`${diff}\`: ${getCommandErrorMessage(error)}`);
2800
+ }
2801
+ }
2802
+ try {
2803
+ return {
2804
+ beforeTree: resolveGitTree(repositoryRoot, diff),
2805
+ afterTree: void 0
2806
+ };
2807
+ } catch (error) {
2808
+ throw new Error(`Failed to resolve Git diff spec \`${diff}\`: ${getCommandErrorMessage(error)}`);
2809
+ }
2810
+ }
2811
+ function splitDiffRange(diff, separator) {
2812
+ const [left, right, ...extra] = diff.split(separator);
2813
+ if (left === void 0 || right === void 0 || left.length === 0 || right.length === 0 || extra.length > 0) throw new Error(`Invalid Git diff spec \`${diff}\``);
2814
+ return [left, right];
2815
+ }
2816
+ function resolveGitTree(repositoryRoot, reference) {
2817
+ return runGit(repositoryRoot, [
2818
+ "rev-parse",
2819
+ "--verify",
2820
+ `${reference}^{tree}`
2821
+ ]).trim();
2822
+ }
2823
+ function materializeGitTreeSnapshot(repositoryRoot, tree) {
2824
+ const snapshotRoot = fs.mkdtempSync(path.join(os.tmpdir(), "foresthouse-react-diff-"));
2825
+ execSync(`git -C ${JSON.stringify(repositoryRoot)} archive --format=tar ${tree} | tar -x -C ${JSON.stringify(snapshotRoot)}`, {
2826
+ maxBuffer: GIT_EXEC_MAX_BUFFER,
2827
+ stdio: [
2828
+ "ignore",
2829
+ "pipe",
2830
+ "pipe"
2831
+ ]
2832
+ });
2833
+ return snapshotRoot;
2834
+ }
2835
+ function materializeTwoGitTreeSnapshots(repositoryRoot, beforeTree, afterTree) {
2836
+ const afterSnap = materializeGitTreeSnapshot(repositoryRoot, afterTree);
2837
+ try {
2838
+ return [cloneSnapshotWithOverlay(repositoryRoot, afterSnap, beforeTree, afterTree), afterSnap];
2839
+ } catch {
2840
+ return [materializeGitTreeSnapshot(repositoryRoot, beforeTree), afterSnap];
2841
+ }
2842
+ }
2843
+ function cloneSnapshotWithOverlay(repositoryRoot, sourceSnap, targetTree, sourceTree) {
2844
+ const targetSnap = fs.mkdtempSync(path.join(os.tmpdir(), "foresthouse-react-diff-"));
2845
+ fs.rmSync(targetSnap, { recursive: true });
2846
+ if (process.platform === "darwin") execFileSync("cp", [
2847
+ "-cR",
2848
+ sourceSnap,
2849
+ targetSnap
2850
+ ], { stdio: "ignore" });
2851
+ else execFileSync("cp", [
2852
+ "-a",
2853
+ "--reflink=auto",
2854
+ sourceSnap,
2855
+ targetSnap
2856
+ ], { stdio: "ignore" });
2857
+ const diffOutput = execFileSync("git", [
2858
+ "-C",
2859
+ repositoryRoot,
2860
+ "diff",
2861
+ "--name-status",
2862
+ "--no-renames",
2863
+ "-z",
2864
+ targetTree,
2865
+ sourceTree
2866
+ ], {
2867
+ maxBuffer: GIT_EXEC_MAX_BUFFER,
2868
+ encoding: "utf8"
2869
+ });
2870
+ if (diffOutput.length === 0) return targetSnap;
2871
+ const parts = diffOutput.split("\0");
2872
+ const filesToDelete = [];
2873
+ const filesToRestore = [];
2874
+ for (let i = 0; i + 1 < parts.length; i += 2) {
2875
+ const status = parts[i];
2876
+ const file = parts[i + 1];
2877
+ if (file === void 0) break;
2878
+ if (status === "A") filesToDelete.push(file);
2879
+ else if (status === "D" || status === "M") filesToRestore.push(file);
2880
+ }
2881
+ for (const file of filesToDelete) fs.rmSync(path.join(targetSnap, file), { force: true });
2882
+ if (filesToRestore.length > 0) {
2883
+ const archive = execFileSync("git", [
2884
+ "-C",
2885
+ repositoryRoot,
2886
+ "archive",
2887
+ "--format=tar",
2888
+ targetTree,
2889
+ "--",
2890
+ ...filesToRestore
2891
+ ], { maxBuffer: GIT_EXEC_MAX_BUFFER });
2892
+ execFileSync("tar", [
2893
+ "-x",
2894
+ "-C",
2895
+ targetSnap
2896
+ ], { input: archive });
2897
+ }
2898
+ return targetSnap;
2899
+ }
2900
+ function runGit(repositoryRoot, args, options = {}) {
2901
+ const output = execFileSync("git", [
2902
+ "-C",
2903
+ repositoryRoot,
2904
+ ...args
2905
+ ], {
2906
+ encoding: "utf8",
2907
+ maxBuffer: GIT_EXEC_MAX_BUFFER,
2908
+ stdio: [
2909
+ "ignore",
2910
+ "pipe",
2911
+ "pipe"
2912
+ ]
2913
+ });
2914
+ return options.trim === false ? output : output.trimEnd();
2915
+ }
2916
+ function getCommandErrorMessage(error) {
2917
+ if (!(error instanceof Error)) return "Unknown Git error";
2918
+ const stderr = Reflect.get(error, "stderr");
2919
+ if (typeof stderr === "string" && stderr.trim().length > 0) return stderr.trim();
2920
+ if (Buffer.isBuffer(stderr) && stderr.byteLength > 0) return stderr.toString("utf8").trim();
2921
+ return error.message;
2922
+ }
2923
+ function normalizePathSeparators(filePath) {
2924
+ return filePath.split(path.sep).join("/");
2925
+ }
2926
+ //#endregion
2325
2927
  //#region src/color.ts
2326
2928
  const ANSI_RESET = "\x1B[0m";
2327
2929
  const ANSI_COMPONENT = "\x1B[36m";
@@ -2579,6 +3181,20 @@ function printReactUsageTree(graph, options = {}) {
2579
3181
  });
2580
3182
  return lines.join("\n");
2581
3183
  }
3184
+ function printReactUsageDiffTree(graph, options = {}) {
3185
+ const color = resolveColorSupport(options.color);
3186
+ if (graph.entries.length > 0) return renderReactDiffEntries(graph.entries, color);
3187
+ if (graph.roots.length === 0) return "No React changes found.";
3188
+ const lines = [];
3189
+ graph.roots.forEach((root, index) => {
3190
+ lines.push(formatDiffLine(root.change, formatReactDiffNodeLabel(root, color), color));
3191
+ root.usages.forEach((usage, usageIndex) => {
3192
+ lines.push(...renderDiffUsage(usage, color, "", usageIndex === root.usages.length - 1));
3193
+ });
3194
+ if (index < graph.roots.length - 1) lines.push("");
3195
+ });
3196
+ return lines.join("\n");
3197
+ }
2582
3198
  function renderReactUsageEntries(graph, entries, cwd, filter, color) {
2583
3199
  const lines = [];
2584
3200
  entries.forEach((entry, index) => {
@@ -2618,6 +3234,63 @@ function formatReactEntryLabel(entry, cwd) {
2618
3234
  function formatReactNodeFilePath$1(node, cwd) {
2619
3235
  return node.kind === "builtin" ? "html" : toDisplayPath(node.filePath, cwd);
2620
3236
  }
3237
+ function renderReactDiffEntries(entries, color) {
3238
+ const lines = [];
3239
+ entries.forEach((entry, index) => {
3240
+ lines.push(formatDiffLine(resolveVisibleEntryChange(entry), formatReactDiffEntryLabel(entry), color));
3241
+ lines.push(formatDiffLine(entry.node.change, formatReactDiffNodeLabel(entry.node, color, entry.beforeReferenceName, entry.afterReferenceName), color));
3242
+ entry.node.usages.forEach((usage, usageIndex) => {
3243
+ lines.push(...renderDiffUsage(usage, color, "", usageIndex === entry.node.usages.length - 1));
3244
+ });
3245
+ if (index < entries.length - 1) lines.push("");
3246
+ });
3247
+ return lines.join("\n");
3248
+ }
3249
+ function renderDiffUsage(usage, color, prefix, isLast) {
3250
+ const line = `${`${prefix}${isLast ? "└─ " : "├─ "}`}${formatDiffLine(resolveVisibleEdgeChange(usage), formatReactDiffNodeLabel(usage.node, color, usage.beforeReferenceName, usage.afterReferenceName), color)}`;
3251
+ if (usage.node.circular === true) return [`${line} (circular)`];
3252
+ const childLines = [line];
3253
+ const nextPrefix = `${prefix}${isLast ? " " : "│ "}`;
3254
+ usage.node.usages.forEach((childUsage, index) => {
3255
+ childLines.push(...renderDiffUsage(childUsage, color, nextPrefix, index === usage.node.usages.length - 1));
3256
+ });
3257
+ return childLines;
3258
+ }
3259
+ function formatReactDiffEntryLabel(entry) {
3260
+ const beforeLocation = entry.beforeFilePath === void 0 || entry.beforeLine === void 0 || entry.beforeColumn === void 0 ? void 0 : `${entry.beforeFilePath}:${entry.beforeLine}:${entry.beforeColumn}`;
3261
+ const afterLocation = entry.afterFilePath === void 0 || entry.afterLine === void 0 || entry.afterColumn === void 0 ? void 0 : `${entry.afterFilePath}:${entry.afterLine}:${entry.afterColumn}`;
3262
+ if (entry.change === "changed" && beforeLocation !== void 0 && afterLocation !== void 0) return `${beforeLocation} -> ${afterLocation}`;
3263
+ return afterLocation ?? beforeLocation ?? entry.referenceName;
3264
+ }
3265
+ function formatReactDiffNodeLabel(node, color, beforeReferenceName, afterReferenceName) {
3266
+ return `${formatReactDiffSymbolLabel(node.name, node.symbolKind, color, beforeReferenceName, afterReferenceName)} (${node.filePath})`;
3267
+ }
3268
+ function formatReactDiffSymbolLabel(name, kind, color, beforeReferenceName, afterReferenceName) {
3269
+ if (![beforeReferenceName, afterReferenceName].filter((referenceName) => referenceName !== void 0).some((referenceName) => referenceName !== name)) return formatReactSymbolLabel(name, kind, color);
3270
+ const aliasText = formatReactDiffAlias(name, beforeReferenceName, afterReferenceName);
3271
+ return `${colorizeReactLabel(formatReactSymbolName(name, kind), kind, color)} ${colorizeMuted(aliasText, color)} ${colorizeReactLabel(`[${kind}]`, kind, color)}`;
3272
+ }
3273
+ function formatReactDiffAlias(name, beforeReferenceName, afterReferenceName) {
3274
+ if (beforeReferenceName !== void 0 && afterReferenceName !== void 0 && beforeReferenceName !== afterReferenceName) return `as ${beforeReferenceName} -> ${afterReferenceName}`;
3275
+ const currentReferenceName = afterReferenceName ?? beforeReferenceName;
3276
+ if (currentReferenceName === void 0 || currentReferenceName === name) return "as self";
3277
+ return `as ${currentReferenceName}`;
3278
+ }
3279
+ function resolveVisibleEntryChange(entry) {
3280
+ return entry.change === "unchanged" ? entry.node.change : entry.change;
3281
+ }
3282
+ function resolveVisibleEdgeChange(usage) {
3283
+ return usage.change === "unchanged" ? usage.node.change : usage.change;
3284
+ }
3285
+ function formatDiffLine(change, text, color) {
3286
+ if (change === "unchanged") return text;
3287
+ return colorizePackageDiff(`${toDiffMarker(change)} ${text}`, change, color);
3288
+ }
3289
+ function toDiffMarker(change) {
3290
+ if (change === "added") return "+";
3291
+ if (change === "removed") return "-";
3292
+ return "~";
3293
+ }
2621
3294
  //#endregion
2622
3295
  //#region src/output/json/deps.ts
2623
3296
  function graphToSerializablePackageTree(graph) {
@@ -2775,6 +3448,13 @@ function graphToSerializableReactTree(graph, options = {}) {
2775
3448
  roots
2776
3449
  };
2777
3450
  }
3451
+ function diffGraphToSerializableReactTree(graph) {
3452
+ return {
3453
+ kind: graph.kind,
3454
+ entries: graph.entries.map((entry) => serializeReactDiffEntry(entry)),
3455
+ roots: graph.roots.map((root) => serializeReactDiffNode(root))
3456
+ };
3457
+ }
2778
3458
  function serializeReactUsageNode(nodeId, graph, filter, visited) {
2779
3459
  const node = graph.nodes.get(nodeId);
2780
3460
  if (node === void 0) return {
@@ -2822,7 +3502,50 @@ function serializeReactUsageEntry(entry, graph, filter) {
2822
3502
  function formatReactNodeFilePath(filePath, kind, cwd) {
2823
3503
  return kind === "builtin" ? "html" : toDisplayPath(filePath, cwd);
2824
3504
  }
3505
+ function serializeReactDiffNode(node) {
3506
+ return {
3507
+ id: node.id,
3508
+ name: node.name,
3509
+ symbolKind: node.symbolKind,
3510
+ ...node.circular === true ? { circular: true } : {},
3511
+ filePath: node.filePath,
3512
+ change: node.change,
3513
+ exportNames: node.exportNames,
3514
+ ...node.beforeExportNames === void 0 ? {} : { beforeExportNames: node.beforeExportNames },
3515
+ ...node.afterExportNames === void 0 ? {} : { afterExportNames: node.afterExportNames },
3516
+ usages: node.usages.map((usage) => serializeReactDiffEdge(usage))
3517
+ };
3518
+ }
3519
+ function serializeReactDiffEdge(usage) {
3520
+ return {
3521
+ key: usage.key,
3522
+ kind: usage.kind,
3523
+ change: usage.change,
3524
+ targetId: usage.targetId,
3525
+ referenceName: usage.referenceName,
3526
+ ...usage.beforeReferenceName === void 0 ? {} : { beforeReferenceName: usage.beforeReferenceName },
3527
+ ...usage.afterReferenceName === void 0 ? {} : { afterReferenceName: usage.afterReferenceName },
3528
+ node: serializeReactDiffNode(usage.node)
3529
+ };
3530
+ }
3531
+ function serializeReactDiffEntry(entry) {
3532
+ return {
3533
+ key: entry.key,
3534
+ change: entry.change,
3535
+ targetId: entry.targetId,
3536
+ referenceName: entry.referenceName,
3537
+ ...entry.beforeReferenceName === void 0 ? {} : { beforeReferenceName: entry.beforeReferenceName },
3538
+ ...entry.afterReferenceName === void 0 ? {} : { afterReferenceName: entry.afterReferenceName },
3539
+ ...entry.beforeFilePath === void 0 ? {} : { beforeFilePath: entry.beforeFilePath },
3540
+ ...entry.beforeLine === void 0 ? {} : { beforeLine: entry.beforeLine },
3541
+ ...entry.beforeColumn === void 0 ? {} : { beforeColumn: entry.beforeColumn },
3542
+ ...entry.afterFilePath === void 0 ? {} : { afterFilePath: entry.afterFilePath },
3543
+ ...entry.afterLine === void 0 ? {} : { afterLine: entry.afterLine },
3544
+ ...entry.afterColumn === void 0 ? {} : { afterColumn: entry.afterColumn },
3545
+ node: serializeReactDiffNode(entry.node)
3546
+ };
3547
+ }
2825
3548
  //#endregion
2826
- export { printReactUsageTree as a, printPackageDependencyTree as c, getReactUsageRoots as d, analyzeReactUsage as f, analyzePackageDependencies as g, analyzePackageDependencyDiff as h, graphToSerializablePackageTree as i, getFilteredUsages as l, isSourceCodeFile as m, graphToSerializableTree as n, printDependencyTree as o, analyzeDependencies as p, diffGraphToSerializablePackageTree as r, printPackageDependencyDiffTree as s, graphToSerializableReactTree as t, getReactUsageEntries as u };
3549
+ export { analyzeDependencies as _, graphToSerializablePackageTree as a, printDependencyTree as c, analyzeReactUsageDiff as d, getFilteredUsages as f, resolveReactEntryFiles as g, analyzeReactUsage as h, diffGraphToSerializablePackageTree as i, printPackageDependencyDiffTree as l, getReactUsageRoots as m, graphToSerializableReactTree as n, printReactUsageDiffTree as o, getReactUsageEntries as p, graphToSerializableTree as r, printReactUsageTree as s, diffGraphToSerializableReactTree as t, printPackageDependencyTree as u, analyzePackageDependencyDiff as v, analyzePackageDependencies as y };
2827
3550
 
2828
- //# sourceMappingURL=react-xFA5HmqF.mjs.map
3551
+ //# sourceMappingURL=react-D0TO9XR2.mjs.map