foresthouse 1.0.0-dev.16 → 1.0.0-dev.18

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,10 +1,579 @@
1
1
  import { builtinModules } from "node:module";
2
+ import { execFileSync } from "node:child_process";
3
+ import fs from "node:fs";
4
+ import os from "node:os";
2
5
  import path from "node:path";
3
6
  import ts from "typescript";
4
- import fs from "node:fs";
5
7
  import { ResolverFactory } from "oxc-resolver";
6
8
  import { parseSync, visitorKeys } from "oxc-parser";
7
9
  import process$1 from "node:process";
10
+ //#region src/utils/to-display-path.ts
11
+ function toDisplayPath(filePath, cwd) {
12
+ const relativePath = path.relative(cwd, filePath);
13
+ if (relativePath === "") return ".";
14
+ const normalizedPath = relativePath.split(path.sep).join("/");
15
+ return normalizedPath.startsWith("..") ? filePath : normalizedPath;
16
+ }
17
+ //#endregion
18
+ //#region src/analyzers/base.ts
19
+ var BaseAnalyzer = class {
20
+ constructor(entryFile, options) {
21
+ this.entryFile = entryFile;
22
+ this.options = options;
23
+ }
24
+ analyze() {
25
+ return this.doAnalyze();
26
+ }
27
+ };
28
+ //#endregion
29
+ //#region src/analyzers/deps/index.ts
30
+ const PNPM_WORKSPACE_FILE$1 = "pnpm-workspace.yaml";
31
+ function analyzePackageDependencies(directory) {
32
+ return new PackageDependencyAnalyzer(directory).analyze();
33
+ }
34
+ var PackageDependencyAnalyzer = class extends BaseAnalyzer {
35
+ inputPath;
36
+ constructor(directory) {
37
+ super(directory, {});
38
+ this.inputPath = path.resolve(process.cwd(), directory);
39
+ }
40
+ doAnalyze() {
41
+ const rootPackageDir = findNearestPackageDirectory(resolveInputDirectory(this.inputPath));
42
+ const workspaceRootDir = findWorkspaceRoot(rootPackageDir) ?? rootPackageDir;
43
+ const workspacePackages = discoverWorkspacePackages(workspaceRootDir);
44
+ const nodes = /* @__PURE__ */ new Map();
45
+ visitPackage(rootPackageDir, workspaceRootDir, workspacePackages, nodes);
46
+ return {
47
+ repositoryRoot: workspaceRootDir,
48
+ rootId: rootPackageDir,
49
+ nodes
50
+ };
51
+ }
52
+ };
53
+ function visitPackage(packageDir, repositoryRoot, workspacePackages, nodes) {
54
+ if (nodes.has(packageDir)) return;
55
+ const manifest = readPackageManifest(packageDir);
56
+ const packageName = resolvePackageName(manifest, packageDir);
57
+ const dependencies = packageDir === repositoryRoot && workspacePackages.size > 0 ? collectRepositoryRootDependencies(manifest, workspacePackages, repositoryRoot) : collectManifestDependencies(manifest, workspacePackages, repositoryRoot);
58
+ nodes.set(packageDir, {
59
+ packageDir,
60
+ packageName,
61
+ dependencies
62
+ });
63
+ dependencies.forEach((dependency) => {
64
+ if (dependency.kind === "workspace") visitPackage(dependency.target, repositoryRoot, workspacePackages, nodes);
65
+ });
66
+ }
67
+ function resolveInputDirectory(resolvedPath) {
68
+ const stats = fs.existsSync(resolvedPath) ? fs.statSync(resolvedPath) : null;
69
+ if (stats === null) throw new Error(`Directory does not exist: ${resolvedPath}`);
70
+ if (stats.isDirectory()) return resolvedPath;
71
+ if (stats.isFile() && path.basename(resolvedPath) === "package.json") return path.dirname(resolvedPath);
72
+ throw new Error(`Expected a package directory: ${resolvedPath}`);
73
+ }
74
+ function findNearestPackageDirectory(startDirectory) {
75
+ let currentDirectory = startDirectory;
76
+ while (true) {
77
+ const packageJsonPath = path.join(currentDirectory, "package.json");
78
+ if (fs.existsSync(packageJsonPath)) return currentDirectory;
79
+ const parentDirectory = path.dirname(currentDirectory);
80
+ if (parentDirectory === currentDirectory) break;
81
+ currentDirectory = parentDirectory;
82
+ }
83
+ throw new Error(`No package.json found from ${startDirectory}`);
84
+ }
85
+ function findWorkspaceRoot(packageDir) {
86
+ let currentDirectory = packageDir;
87
+ while (true) {
88
+ const packageJsonPath = path.join(currentDirectory, "package.json");
89
+ if (fs.existsSync(packageJsonPath)) {
90
+ const manifest = readPackageManifest(currentDirectory);
91
+ const workspacePatterns = resolveWorkspacePatterns(currentDirectory, manifest);
92
+ if (workspacePatterns.length > 0 && currentDirectory === packageDir) return currentDirectory;
93
+ if (workspacePatterns.length > 0 && isWorkspaceMatch(currentDirectory, workspacePatterns, packageDir)) return currentDirectory;
94
+ }
95
+ const parentDirectory = path.dirname(currentDirectory);
96
+ if (parentDirectory === currentDirectory) return;
97
+ currentDirectory = parentDirectory;
98
+ }
99
+ }
100
+ function discoverWorkspacePackages(repositoryRoot) {
101
+ const workspacePatterns = resolveWorkspacePatterns(repositoryRoot, readPackageManifest(repositoryRoot));
102
+ if (workspacePatterns.length === 0) return /* @__PURE__ */ new Map();
103
+ const workspacePackageDirs = /* @__PURE__ */ new Set();
104
+ workspacePatterns.forEach((pattern) => {
105
+ expandWorkspacePattern(repositoryRoot, pattern).forEach((packageDir) => {
106
+ workspacePackageDirs.add(packageDir);
107
+ });
108
+ });
109
+ const workspacePackages = /* @__PURE__ */ new Map();
110
+ Array.from(workspacePackageDirs).sort((left, right) => left.localeCompare(right)).forEach((packageDir) => {
111
+ const workspaceName = resolvePackageName(readPackageManifest(packageDir), packageDir);
112
+ if (workspacePackages.has(workspaceName)) throw new Error(`Duplicate workspace package name: ${workspaceName}`);
113
+ workspacePackages.set(workspaceName, {
114
+ name: workspaceName,
115
+ packageDir,
116
+ label: toDisplayPath(packageDir, repositoryRoot)
117
+ });
118
+ });
119
+ return workspacePackages;
120
+ }
121
+ function collectManifestDependencies(manifest, workspacePackages, repositoryRoot) {
122
+ const dependencyEntries = Object.entries(manifest.dependencies ?? {});
123
+ const workspaceDependencies = [];
124
+ const externalDependencies = [];
125
+ dependencyEntries.forEach(([dependencyName, specifier]) => {
126
+ const workspacePackage = workspacePackages.get(dependencyName);
127
+ if (workspacePackage !== void 0) {
128
+ workspaceDependencies.push({
129
+ kind: "workspace",
130
+ name: dependencyName,
131
+ specifier,
132
+ target: workspacePackage.packageDir
133
+ });
134
+ return;
135
+ }
136
+ externalDependencies.push({
137
+ kind: "external",
138
+ name: dependencyName,
139
+ specifier
140
+ });
141
+ });
142
+ workspaceDependencies.sort((left, right) => {
143
+ const leftLabel = toDisplayPath(left.target, repositoryRoot);
144
+ const rightLabel = toDisplayPath(right.target, repositoryRoot);
145
+ return leftLabel.localeCompare(rightLabel);
146
+ });
147
+ externalDependencies.sort((left, right) => left.name.localeCompare(right.name));
148
+ return [...workspaceDependencies, ...externalDependencies];
149
+ }
150
+ function collectRepositoryRootDependencies(manifest, workspacePackages, _repositoryRoot) {
151
+ const topLevelWorkspaceDependencies = Array.from(workspacePackages.values()).sort((left, right) => left.label.localeCompare(right.label)).map((workspacePackage) => ({
152
+ kind: "workspace",
153
+ name: workspacePackage.name,
154
+ target: workspacePackage.packageDir
155
+ }));
156
+ const externalDependencies = Object.entries(manifest.dependencies ?? {}).filter(([dependencyName]) => !workspacePackages.has(dependencyName)).map(([dependencyName, specifier]) => ({
157
+ kind: "external",
158
+ name: dependencyName,
159
+ specifier
160
+ })).sort((left, right) => left.name.localeCompare(right.name));
161
+ return [...topLevelWorkspaceDependencies, ...externalDependencies];
162
+ }
163
+ function readPackageManifest(packageDir) {
164
+ const packageJsonPath = path.join(packageDir, "package.json");
165
+ if (!fs.existsSync(packageJsonPath)) throw new Error(`Missing package.json in ${packageDir}`);
166
+ const manifestText = fs.readFileSync(packageJsonPath, "utf8");
167
+ try {
168
+ const parsed = JSON.parse(manifestText);
169
+ if (!isRecord(parsed)) throw new Error("package.json must contain an object");
170
+ return parsed;
171
+ } catch (error) {
172
+ const message = error instanceof Error ? error.message : "Unknown JSON parse error";
173
+ throw new Error(`Failed to read ${packageJsonPath}: ${message}`);
174
+ }
175
+ }
176
+ function resolvePackageName(manifest, packageDir) {
177
+ if (typeof manifest.name === "string" && manifest.name.trim().length > 0) return manifest.name;
178
+ throw new Error(`Package at ${packageDir} is missing a valid name`);
179
+ }
180
+ function getWorkspacePatterns(manifest) {
181
+ if (Array.isArray(manifest.workspaces)) return manifest.workspaces.filter((pattern) => typeof pattern === "string" && pattern.length > 0);
182
+ if (isRecord(manifest.workspaces) && Array.isArray(manifest.workspaces.packages)) return manifest.workspaces.packages.filter((pattern) => typeof pattern === "string" && pattern.length > 0);
183
+ return [];
184
+ }
185
+ function resolveWorkspacePatterns(repositoryRoot, manifest) {
186
+ const manifestPatterns = getWorkspacePatterns(manifest);
187
+ if (manifestPatterns.length > 0) return manifestPatterns;
188
+ return readPnpmWorkspacePatterns(repositoryRoot);
189
+ }
190
+ function readPnpmWorkspacePatterns(repositoryRoot) {
191
+ const pnpmWorkspacePath = path.join(repositoryRoot, PNPM_WORKSPACE_FILE$1);
192
+ if (!fs.existsSync(pnpmWorkspacePath)) return [];
193
+ const lines = fs.readFileSync(pnpmWorkspacePath, "utf8").split(/\r?\n/);
194
+ const patterns = [];
195
+ let inPackagesBlock = false;
196
+ for (const line of lines) {
197
+ const trimmedLine = line.trim();
198
+ if (trimmedLine.length === 0 || trimmedLine.startsWith("#")) continue;
199
+ if (!inPackagesBlock) {
200
+ if (trimmedLine === "packages:") inPackagesBlock = true;
201
+ continue;
202
+ }
203
+ if (!line.startsWith(" ") && !line.startsWith(" ")) break;
204
+ if (!trimmedLine.startsWith("- ")) continue;
205
+ const pattern = unquoteWorkspacePattern(trimmedLine.slice(2).trim());
206
+ if (pattern.length > 0) patterns.push(pattern);
207
+ }
208
+ return patterns;
209
+ }
210
+ function unquoteWorkspacePattern(pattern) {
211
+ if (pattern.startsWith("\"") && pattern.endsWith("\"") || pattern.startsWith("'") && pattern.endsWith("'")) return pattern.slice(1, -1);
212
+ return pattern;
213
+ }
214
+ function isWorkspaceMatch(repositoryRoot, patterns, packageDir) {
215
+ return patterns.some((pattern) => expandWorkspacePattern(repositoryRoot, pattern).includes(packageDir));
216
+ }
217
+ function expandWorkspacePattern(repositoryRoot, pattern) {
218
+ return matchWorkspaceSegments(repositoryRoot, normalizeWorkspacePattern(pattern), 0).filter((packageDir) => fs.existsSync(path.join(packageDir, "package.json")));
219
+ }
220
+ function normalizeWorkspacePattern(pattern) {
221
+ return pattern.split(/[\\/]+/).filter((segment) => segment.length > 0 && segment !== ".");
222
+ }
223
+ function matchWorkspaceSegments(currentDirectory, segments, index) {
224
+ if (index >= segments.length) return [currentDirectory];
225
+ const segment = segments[index];
226
+ if (segment === void 0) return [currentDirectory];
227
+ if (segment === "**") {
228
+ const results = new Set(matchWorkspaceSegments(currentDirectory, segments, index + 1));
229
+ listSubdirectories(currentDirectory).forEach((subdirectory) => {
230
+ matchWorkspaceSegments(subdirectory, segments, index).forEach((match) => {
231
+ results.add(match);
232
+ });
233
+ });
234
+ return [...results];
235
+ }
236
+ const matcher = createWorkspaceSegmentMatcher(segment);
237
+ const results = [];
238
+ listSubdirectories(currentDirectory).forEach((subdirectory) => {
239
+ if (!matcher(path.basename(subdirectory))) return;
240
+ results.push(...matchWorkspaceSegments(subdirectory, segments, index + 1));
241
+ });
242
+ return results;
243
+ }
244
+ function listSubdirectories(directory) {
245
+ return fs.readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".git").map((entry) => path.join(directory, entry.name));
246
+ }
247
+ function createWorkspaceSegmentMatcher(segment) {
248
+ if (!segment.includes("*")) return (name) => name === segment;
249
+ const escapedSegment = escapeRegExp(segment).replaceAll("*", "[^/]*");
250
+ const pattern = new RegExp(`^${escapedSegment}$`);
251
+ return (name) => pattern.test(name);
252
+ }
253
+ function escapeRegExp(value) {
254
+ return value.replaceAll(/[|\\{}()[\]^$+?.]/g, "\\$&");
255
+ }
256
+ function isRecord(value) {
257
+ return typeof value === "object" && value !== null;
258
+ }
259
+ //#endregion
260
+ //#region src/analyzers/deps/diff.ts
261
+ const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
262
+ function analyzePackageDependencyDiff(directory, diff) {
263
+ const resolvedInputPath = path.resolve(process.cwd(), directory);
264
+ const repositoryRoot = findGitRepositoryRoot(resolvedInputPath);
265
+ const inputPathWithinRepository = resolveRepositoryInputPath(resolvedInputPath, repositoryRoot);
266
+ const comparison = resolveGitDiffComparison(repositoryRoot, diff);
267
+ const beforeGraph = loadGitTreeGraph(repositoryRoot, comparison.beforeTree, inputPathWithinRepository);
268
+ const afterGraph = comparison.afterTree === void 0 ? loadWorkingTreeGraph(repositoryRoot, inputPathWithinRepository) : loadGitTreeGraph(repositoryRoot, comparison.afterTree, inputPathWithinRepository);
269
+ if (beforeGraph === void 0 && afterGraph === void 0) throw new Error(`No package.json found from ${resolvedInputPath}`);
270
+ return {
271
+ repositoryRoot,
272
+ root: diffPackageNode({
273
+ beforePath: beforeGraph?.rootPath,
274
+ afterPath: afterGraph?.rootPath,
275
+ kind: "root"
276
+ }, beforeGraph, afterGraph, /* @__PURE__ */ new Set())
277
+ };
278
+ }
279
+ function resolveRepositoryInputPath(resolvedInputPath, repositoryRoot) {
280
+ const existingPath = findNearestExistingPath(resolvedInputPath);
281
+ const existingRealPath = fs.realpathSync.native(existingPath);
282
+ const repositoryRealPath = fs.realpathSync.native(repositoryRoot);
283
+ const relativeFromExistingPath = path.relative(existingPath, resolvedInputPath);
284
+ const relativeToRepository = path.relative(repositoryRealPath, existingRealPath);
285
+ const normalizedPath = path.normalize(path.join(relativeToRepository, relativeFromExistingPath));
286
+ return normalizedPath === "." ? "" : normalizedPath;
287
+ }
288
+ function findGitRepositoryRoot(resolvedInputPath) {
289
+ const searchPath = findNearestExistingPath(resolvedInputPath);
290
+ try {
291
+ return runGit(searchPath, ["rev-parse", "--show-toplevel"]).trim();
292
+ } catch (error) {
293
+ throw new Error(`Git diff mode requires a Git repository: ${getCommandErrorMessage(error)}`);
294
+ }
295
+ }
296
+ function findNearestExistingPath(resolvedInputPath) {
297
+ let currentPath = resolvedInputPath;
298
+ while (!fs.existsSync(currentPath)) {
299
+ const parentPath = path.dirname(currentPath);
300
+ if (parentPath === currentPath) return resolvedInputPath;
301
+ currentPath = parentPath;
302
+ }
303
+ if (fs.statSync(currentPath).isFile()) return path.dirname(currentPath);
304
+ return currentPath;
305
+ }
306
+ function resolveGitDiffComparison(repositoryRoot, diff) {
307
+ if (diff.includes("...")) {
308
+ const [baseRef, headRef] = splitDiffRange(diff, "...");
309
+ try {
310
+ return {
311
+ beforeTree: resolveGitTree(repositoryRoot, runGit(repositoryRoot, [
312
+ "merge-base",
313
+ baseRef,
314
+ headRef
315
+ ]).trim()),
316
+ afterTree: resolveGitTree(repositoryRoot, headRef)
317
+ };
318
+ } catch (error) {
319
+ throw new Error(`Failed to resolve Git diff spec \`${diff}\`: ${getCommandErrorMessage(error)}`);
320
+ }
321
+ }
322
+ if (diff.includes("..")) {
323
+ const [beforeRef, afterRef] = splitDiffRange(diff, "..");
324
+ try {
325
+ return {
326
+ beforeTree: resolveGitTree(repositoryRoot, beforeRef),
327
+ afterTree: resolveGitTree(repositoryRoot, afterRef)
328
+ };
329
+ } catch (error) {
330
+ throw new Error(`Failed to resolve Git diff spec \`${diff}\`: ${getCommandErrorMessage(error)}`);
331
+ }
332
+ }
333
+ try {
334
+ return {
335
+ beforeTree: resolveGitTree(repositoryRoot, diff),
336
+ afterTree: void 0
337
+ };
338
+ } catch (error) {
339
+ throw new Error(`Failed to resolve Git diff spec \`${diff}\`: ${getCommandErrorMessage(error)}`);
340
+ }
341
+ }
342
+ function splitDiffRange(diff, separator) {
343
+ const [left, right, ...extra] = diff.split(separator);
344
+ if (left === void 0 || right === void 0 || left.length === 0 || right.length === 0 || extra.length > 0) throw new Error(`Invalid Git diff spec \`${diff}\``);
345
+ return [left, right];
346
+ }
347
+ function resolveGitTree(repositoryRoot, reference) {
348
+ return runGit(repositoryRoot, [
349
+ "rev-parse",
350
+ "--verify",
351
+ `${reference}^{tree}`
352
+ ]).trim();
353
+ }
354
+ function loadWorkingTreeGraph(repositoryRoot, inputPathWithinRepository) {
355
+ const packageGraph = tryAnalyzePackageGraph(resolveSnapshotInputPath(repositoryRoot, inputPathWithinRepository));
356
+ return packageGraph === void 0 ? void 0 : toComparableGraph(packageGraph);
357
+ }
358
+ function loadGitTreeGraph(repositoryRoot, tree, inputPathWithinRepository) {
359
+ const snapshotRoot = materializeGitTreeSnapshot(repositoryRoot, tree);
360
+ try {
361
+ const packageGraph = tryAnalyzePackageGraph(resolveSnapshotInputPath(snapshotRoot, inputPathWithinRepository));
362
+ return packageGraph === void 0 ? void 0 : toComparableGraph(packageGraph);
363
+ } finally {
364
+ fs.rmSync(snapshotRoot, {
365
+ recursive: true,
366
+ force: true
367
+ });
368
+ }
369
+ }
370
+ function materializeGitTreeSnapshot(repositoryRoot, tree) {
371
+ const snapshotRoot = fs.mkdtempSync(path.join(os.tmpdir(), "foresthouse-deps-diff-"));
372
+ const trackedFiles = runGit(repositoryRoot, [
373
+ "ls-tree",
374
+ "-r",
375
+ "-z",
376
+ "--name-only",
377
+ tree
378
+ ]).split("\0").filter((filePath) => filePath.length > 0);
379
+ trackedFiles.forEach((filePath) => {
380
+ ensureSnapshotDirectory(snapshotRoot, filePath);
381
+ });
382
+ trackedFiles.filter(isManifestSnapshotFile).forEach((filePath) => {
383
+ const fileContent = runGit(repositoryRoot, [
384
+ "cat-file",
385
+ "-p",
386
+ `${tree}:${filePath}`
387
+ ], { trim: false });
388
+ const absolutePath = path.join(snapshotRoot, ...filePath.split("/"));
389
+ fs.writeFileSync(absolutePath, fileContent);
390
+ });
391
+ return snapshotRoot;
392
+ }
393
+ function ensureSnapshotDirectory(snapshotRoot, filePath) {
394
+ const parentDirectory = path.dirname(filePath);
395
+ if (parentDirectory === ".") return;
396
+ fs.mkdirSync(path.join(snapshotRoot, ...parentDirectory.split("/")), { recursive: true });
397
+ }
398
+ function isManifestSnapshotFile(filePath) {
399
+ const fileName = path.posix.basename(filePath);
400
+ return fileName === "package.json" || fileName === PNPM_WORKSPACE_FILE;
401
+ }
402
+ function resolveSnapshotInputPath(snapshotRoot, inputPathWithinRepository) {
403
+ if (inputPathWithinRepository === "") return snapshotRoot;
404
+ return path.join(snapshotRoot, inputPathWithinRepository);
405
+ }
406
+ function tryAnalyzePackageGraph(inputPath) {
407
+ if (!fs.existsSync(inputPath)) return;
408
+ try {
409
+ return analyzePackageDependencies(inputPath);
410
+ } catch (error) {
411
+ if (error instanceof Error && error.message.startsWith("No package.json found from ")) return;
412
+ throw error;
413
+ }
414
+ }
415
+ function toComparableGraph(graph) {
416
+ const nodes = /* @__PURE__ */ new Map();
417
+ graph.nodes.forEach((node, packageDir) => {
418
+ const packagePath = toDisplayPath(packageDir, graph.repositoryRoot);
419
+ const dependenciesByKey = /* @__PURE__ */ new Map();
420
+ node.dependencies.forEach((dependency) => {
421
+ dependenciesByKey.set(createDependencyKey(dependency), toComparableDependency(dependency, graph.repositoryRoot));
422
+ });
423
+ nodes.set(packagePath, {
424
+ path: packagePath,
425
+ packageName: node.packageName,
426
+ dependenciesByKey
427
+ });
428
+ });
429
+ return {
430
+ rootPath: toDisplayPath(graph.rootId, graph.repositoryRoot),
431
+ nodes
432
+ };
433
+ }
434
+ function createDependencyKey(dependency) {
435
+ return `${dependency.kind}:${dependency.name}`;
436
+ }
437
+ function toComparableDependency(dependency, repositoryRoot) {
438
+ if (dependency.kind === "external") return {
439
+ kind: "external",
440
+ key: createDependencyKey(dependency),
441
+ name: dependency.name,
442
+ specifier: dependency.specifier
443
+ };
444
+ return {
445
+ kind: "workspace",
446
+ key: createDependencyKey(dependency),
447
+ name: dependency.name,
448
+ specifier: dependency.specifier,
449
+ targetPath: toDisplayPath(dependency.target, repositoryRoot)
450
+ };
451
+ }
452
+ function diffPackageNode(location, beforeGraph, afterGraph, ancestry) {
453
+ const nodeKey = `${location.beforePath ?? ""}->${location.afterPath ?? ""}`;
454
+ const beforeNode = location.beforePath === void 0 ? void 0 : beforeGraph?.nodes.get(location.beforePath);
455
+ const afterNode = location.afterPath === void 0 ? void 0 : afterGraph?.nodes.get(location.afterPath);
456
+ if (beforeNode === void 0 && afterNode === void 0) throw new Error("Unable to resolve package diff node.");
457
+ const packageName = afterNode?.packageName ?? beforeNode?.packageName ?? location.afterPath ?? location.beforePath ?? ".";
458
+ const packagePath = location.afterPath ?? location.beforePath ?? ".";
459
+ if (ancestry.has(nodeKey)) return {
460
+ kind: "circular",
461
+ label: location.kind === "root" ? packageName : packagePath,
462
+ packageName,
463
+ path: packagePath,
464
+ change: "unchanged",
465
+ dependencies: []
466
+ };
467
+ const nextAncestry = new Set(ancestry);
468
+ nextAncestry.add(nodeKey);
469
+ const dependencies = collectDependencyDiffs(beforeNode, afterNode, beforeGraph, afterGraph, nextAncestry);
470
+ const change = resolveNodeChange(location, beforeNode, afterNode, dependencies);
471
+ return {
472
+ kind: location.kind,
473
+ label: location.kind === "root" ? packageName : packagePath,
474
+ packageName,
475
+ path: packagePath,
476
+ change,
477
+ ...beforeNode !== void 0 && afterNode !== void 0 && beforeNode.packageName !== afterNode.packageName ? {
478
+ beforePackageName: beforeNode.packageName,
479
+ afterPackageName: afterNode.packageName
480
+ } : {},
481
+ dependencies
482
+ };
483
+ }
484
+ function collectDependencyDiffs(beforeNode, afterNode, beforeGraph, afterGraph, ancestry) {
485
+ const dependencyKeys = new Set([...Array.from(beforeNode?.dependenciesByKey.keys() ?? []), ...Array.from(afterNode?.dependenciesByKey.keys() ?? [])]);
486
+ return Array.from(dependencyKeys).sort((left, right) => compareDependencyLabels(beforeNode?.dependenciesByKey.get(left) ?? afterNode?.dependenciesByKey.get(left), beforeNode?.dependenciesByKey.get(right) ?? afterNode?.dependenciesByKey.get(right))).flatMap((dependencyKey) => {
487
+ const dependency = diffDependency(beforeNode?.dependenciesByKey.get(dependencyKey), afterNode?.dependenciesByKey.get(dependencyKey), beforeGraph, afterGraph, ancestry);
488
+ return dependency === void 0 ? [] : [dependency];
489
+ });
490
+ }
491
+ function compareDependencyLabels(left, right) {
492
+ const leftLabel = left === void 0 ? "" : getDependencySortLabel(left);
493
+ const rightLabel = right === void 0 ? "" : getDependencySortLabel(right);
494
+ return leftLabel.localeCompare(rightLabel);
495
+ }
496
+ function getDependencySortLabel(dependency) {
497
+ if (dependency.kind === "external") return `${dependency.name}@${dependency.specifier}`;
498
+ return dependency.targetPath;
499
+ }
500
+ function diffDependency(beforeDependency, afterDependency, beforeGraph, afterGraph, ancestry) {
501
+ const change = resolveDependencyChange(beforeDependency, afterDependency);
502
+ if (beforeDependency?.kind === "workspace" || afterDependency?.kind === "workspace") {
503
+ const workspaceDependency = afterDependency?.kind === "workspace" ? afterDependency : beforeDependency?.kind === "workspace" ? beforeDependency : void 0;
504
+ if (workspaceDependency === void 0) return;
505
+ const node = diffPackageNode({
506
+ beforePath: beforeDependency?.kind === "workspace" ? beforeDependency.targetPath : void 0,
507
+ afterPath: afterDependency?.kind === "workspace" ? afterDependency.targetPath : void 0,
508
+ kind: "workspace"
509
+ }, beforeGraph, afterGraph, ancestry);
510
+ if (change === "unchanged" && !hasVisibleChanges(node)) return;
511
+ return {
512
+ kind: "workspace",
513
+ name: workspaceDependency.name,
514
+ change,
515
+ ...beforeDependency === void 0 ? {} : { before: toDiffState(beforeDependency) },
516
+ ...afterDependency === void 0 ? {} : { after: toDiffState(afterDependency) },
517
+ node
518
+ };
519
+ }
520
+ if (change === "unchanged") return;
521
+ const dependency = afterDependency ?? beforeDependency;
522
+ if (dependency === void 0 || dependency.kind !== "external") return;
523
+ return {
524
+ kind: "external",
525
+ name: dependency.name,
526
+ change,
527
+ ...beforeDependency === void 0 ? {} : { before: toDiffState(beforeDependency) },
528
+ ...afterDependency === void 0 ? {} : { after: toDiffState(afterDependency) }
529
+ };
530
+ }
531
+ function resolveDependencyChange(beforeDependency, afterDependency) {
532
+ if (beforeDependency === void 0 && afterDependency !== void 0) return "added";
533
+ if (beforeDependency !== void 0 && afterDependency === void 0) return "removed";
534
+ if (beforeDependency === void 0 || afterDependency === void 0) return "unchanged";
535
+ if (beforeDependency.kind !== afterDependency.kind) return "changed";
536
+ if (beforeDependency.kind === "external" && afterDependency.kind === "external") return beforeDependency.specifier === afterDependency.specifier ? "unchanged" : "changed";
537
+ if (beforeDependency.kind === "workspace" && afterDependency.kind === "workspace") return beforeDependency.specifier === afterDependency.specifier && beforeDependency.targetPath === afterDependency.targetPath ? "unchanged" : "changed";
538
+ return "changed";
539
+ }
540
+ function toDiffState(dependency) {
541
+ if (dependency.kind === "external") return {
542
+ target: `${dependency.name}@${dependency.specifier}`,
543
+ specifier: dependency.specifier
544
+ };
545
+ return dependency.specifier === void 0 ? { target: dependency.targetPath } : {
546
+ target: dependency.targetPath,
547
+ specifier: dependency.specifier
548
+ };
549
+ }
550
+ function hasVisibleChanges(node) {
551
+ return node.change !== "unchanged" || node.dependencies.length > 0;
552
+ }
553
+ function resolveNodeChange(location, beforeNode, afterNode, dependencies) {
554
+ if (beforeNode === void 0 && afterNode !== void 0) return "added";
555
+ if (beforeNode !== void 0 && afterNode === void 0) return "removed";
556
+ if (beforeNode === void 0 || afterNode === void 0) return "unchanged";
557
+ if (location.beforePath !== location.afterPath) return "changed";
558
+ if (beforeNode.packageName !== afterNode.packageName) return "changed";
559
+ return dependencies.some((dependency) => dependency.change !== "unchanged") ? "changed" : "unchanged";
560
+ }
561
+ function runGit(repositoryRoot, args, options = {}) {
562
+ const output = execFileSync("git", [
563
+ "-C",
564
+ repositoryRoot,
565
+ ...args
566
+ ], { encoding: "utf8" });
567
+ return options.trim === false ? output : output.trimEnd();
568
+ }
569
+ function getCommandErrorMessage(error) {
570
+ if (!(error instanceof Error)) return "Unknown Git error";
571
+ const stderr = Reflect.get(error, "stderr");
572
+ if (typeof stderr === "string" && stderr.trim().length > 0) return stderr.trim();
573
+ if (Buffer.isBuffer(stderr) && stderr.byteLength > 0) return stderr.toString("utf8").trim();
574
+ return error.message;
575
+ }
576
+ //#endregion
8
577
  //#region src/typescript/config.ts
9
578
  function loadCompilerOptions(searchFrom, explicitConfigPath) {
10
579
  const configPath = explicitConfigPath === void 0 ? findNearestConfig(searchFrom) : path.resolve(searchFrom, explicitConfigPath);
@@ -54,17 +623,6 @@ function isInsideNodeModules$1(filePath) {
54
623
  return filePath.includes(`${path.sep}node_modules${path.sep}`);
55
624
  }
56
625
  //#endregion
57
- //#region src/analyzers/base.ts
58
- var BaseAnalyzer = class {
59
- constructor(entryFile, options) {
60
- this.entryFile = entryFile;
61
- this.options = options;
62
- }
63
- analyze() {
64
- return this.doAnalyze();
65
- }
66
- };
67
- //#endregion
68
626
  //#region src/utils/is-source-code-file.ts
69
627
  const SOURCE_EXTENSIONS = new Set([
70
628
  ".js",
@@ -1258,6 +1816,9 @@ const ANSI_HOOK = "\x1B[35m";
1258
1816
  const ANSI_BUILTIN = "\x1B[34m";
1259
1817
  const ANSI_MUTED = "\x1B[38;5;244m";
1260
1818
  const ANSI_UNUSED = "\x1B[38;5;214m";
1819
+ const ANSI_DIFF_ADDED = "\x1B[32m";
1820
+ const ANSI_DIFF_REMOVED = "\x1B[31m";
1821
+ const ANSI_DIFF_CHANGED = "\x1B[33m";
1261
1822
  function resolveColorSupport(mode = "auto", options = {}) {
1262
1823
  if (mode === true) return true;
1263
1824
  if (mode === false) return false;
@@ -1288,18 +1849,128 @@ function colorizeMuted(text, enabled) {
1288
1849
  if (!enabled) return text;
1289
1850
  return `${ANSI_MUTED}${text}${ANSI_RESET}`;
1290
1851
  }
1852
+ function colorizePackageDiff(text, change, enabled) {
1853
+ if (!enabled) return text;
1854
+ return `${getPackageDiffColor(change)}${text}${ANSI_RESET}`;
1855
+ }
1291
1856
  function getReactSymbolColor(kind) {
1292
1857
  if (kind === "component") return ANSI_COMPONENT;
1293
1858
  if (kind === "hook") return ANSI_HOOK;
1294
1859
  return ANSI_BUILTIN;
1295
1860
  }
1861
+ function getPackageDiffColor(change) {
1862
+ if (change === "added") return ANSI_DIFF_ADDED;
1863
+ if (change === "removed") return ANSI_DIFF_REMOVED;
1864
+ return ANSI_DIFF_CHANGED;
1865
+ }
1296
1866
  //#endregion
1297
- //#region src/utils/to-display-path.ts
1298
- function toDisplayPath(filePath, cwd) {
1299
- const relativePath = path.relative(cwd, filePath);
1300
- if (relativePath === "") return ".";
1301
- const normalizedPath = relativePath.split(path.sep).join("/");
1302
- return normalizedPath.startsWith("..") ? filePath : normalizedPath;
1867
+ //#region src/output/ascii/deps.ts
1868
+ function printPackageDependencyTree(graph, _options = {}) {
1869
+ const rootNode = graph.nodes.get(graph.rootId);
1870
+ if (rootNode === void 0) return toDisplayPath(graph.rootId, graph.repositoryRoot);
1871
+ const lines = [rootNode.packageName];
1872
+ const visited = new Set([graph.rootId]);
1873
+ rootNode.dependencies.forEach((dependency, index) => {
1874
+ lines.push(...renderDependency$1(dependency, graph, visited, "", index === rootNode.dependencies.length - 1));
1875
+ });
1876
+ return lines.join("\n");
1877
+ }
1878
+ function printPackageDependencyDiffTree(graph, options = {}) {
1879
+ const color = resolveColorSupport(options.color);
1880
+ const lines = [formatDiffRootLabel(graph.root, color)];
1881
+ graph.root.dependencies.forEach((dependency, index) => {
1882
+ lines.push(...renderDiffDependency(dependency, "", index === graph.root.dependencies.length - 1, color));
1883
+ });
1884
+ return lines.join("\n");
1885
+ }
1886
+ function renderDependency$1(dependency, graph, visited, prefix, isLast) {
1887
+ const branch = `${prefix}${isLast ? "└─ " : "├─ "}`;
1888
+ const label = formatDependencyLabel$1(dependency, graph.repositoryRoot);
1889
+ if (dependency.kind === "external") return [`${branch}${label}`];
1890
+ if (visited.has(dependency.target)) return [`${branch}${label} (circular)`];
1891
+ const childNode = graph.nodes.get(dependency.target);
1892
+ if (childNode === void 0) return [`${branch}${label}`];
1893
+ const childLines = [`${branch}${label}`];
1894
+ const nextPrefix = `${prefix}${isLast ? " " : "│ "}`;
1895
+ const nextVisited = new Set(visited);
1896
+ nextVisited.add(dependency.target);
1897
+ childNode.dependencies.forEach((childDependency, index) => {
1898
+ childLines.push(...renderDependency$1(childDependency, graph, nextVisited, nextPrefix, index === childNode.dependencies.length - 1));
1899
+ });
1900
+ return childLines;
1901
+ }
1902
+ function formatDependencyLabel$1(dependency, repositoryRoot) {
1903
+ if (dependency.kind === "external") return `${dependency.name}@${dependency.specifier}`;
1904
+ const workspaceLabel = toDisplayPath(dependency.target, repositoryRoot);
1905
+ if (dependency.specifier === void 0) return workspaceLabel;
1906
+ return `${workspaceLabel} (${dependency.specifier})`;
1907
+ }
1908
+ function renderDiffDependency(dependency, prefix, isLast, color) {
1909
+ const line = `${`${prefix}${isLast ? "└─ " : "├─ "}`}${formatDiffDependencyLine(dependency, color)}`;
1910
+ if (dependency.kind === "external") return [line];
1911
+ if (dependency.node.kind === "circular") return [`${line} (circular)`];
1912
+ const childLines = [line];
1913
+ const nextPrefix = `${prefix}${isLast ? " " : "│ "}`;
1914
+ dependency.node.dependencies.forEach((childDependency, index) => {
1915
+ childLines.push(...renderDiffDependency(childDependency, nextPrefix, index === dependency.node.dependencies.length - 1, color));
1916
+ });
1917
+ return childLines;
1918
+ }
1919
+ function formatDiffRootLabel(node, color) {
1920
+ if (node.change === "changed" && node.beforePackageName !== void 0 && node.afterPackageName !== void 0) return colorizeDiffText(`${toMarker(node.change)} ${node.beforePackageName} -> ${node.afterPackageName}`, node.change, color);
1921
+ if (node.change === "unchanged") return node.packageName;
1922
+ return colorizeDiffText(`${toMarker(node.change)} ${node.packageName}`, node.change, color);
1923
+ }
1924
+ function formatDiffDependencyLine(dependency, color) {
1925
+ const marker = resolveVisibleDependencyMarker(dependency);
1926
+ const label = formatDiffDependencyLabel(dependency);
1927
+ if (marker === "unchanged") return label;
1928
+ return colorizeDiffText(`${toMarker(marker)} ${label}`, marker, color);
1929
+ }
1930
+ function resolveVisibleDependencyMarker(dependency) {
1931
+ if (dependency.change !== "unchanged") return dependency.change;
1932
+ if (dependency.kind === "workspace" && dependency.node.change !== "unchanged") return dependency.node.change;
1933
+ return "unchanged";
1934
+ }
1935
+ function formatDiffDependencyLabel(dependency) {
1936
+ if (dependency.kind === "external") return formatExternalDiffLabel(dependency);
1937
+ return formatWorkspaceDiffLabel(dependency);
1938
+ }
1939
+ function formatExternalDiffLabel(dependency) {
1940
+ if (dependency.change !== "changed") return dependency.after?.target ?? dependency.before?.target ?? dependency.name;
1941
+ const previousSpecifier = dependency.before?.specifier ?? "none";
1942
+ const nextSpecifier = dependency.after?.specifier ?? "none";
1943
+ return `${dependency.name}@${previousSpecifier} -> ${nextSpecifier}`;
1944
+ }
1945
+ function formatWorkspaceDiffLabel(dependency) {
1946
+ if (dependency.change !== "changed") return formatWorkspaceState(dependency.after ?? dependency.before, dependency.node.path);
1947
+ const previousTarget = dependency.before?.target ?? dependency.node.path;
1948
+ const nextTarget = dependency.after?.target ?? dependency.node.path;
1949
+ const targetLabel = previousTarget === nextTarget ? nextTarget : `${previousTarget} -> ${nextTarget}`;
1950
+ const previousSpecifier = dependency.before?.specifier;
1951
+ const nextSpecifier = dependency.after?.specifier;
1952
+ if (previousSpecifier === nextSpecifier) return formatWorkspaceState({
1953
+ target: targetLabel,
1954
+ ...nextSpecifier === void 0 ? {} : { specifier: nextSpecifier }
1955
+ }, dependency.node.path);
1956
+ return formatWorkspaceState({
1957
+ target: targetLabel,
1958
+ specifier: `${previousSpecifier ?? "none"} -> ${nextSpecifier ?? "none"}`
1959
+ }, dependency.node.path);
1960
+ }
1961
+ function formatWorkspaceState(state, fallbackTarget) {
1962
+ const target = state?.target ?? fallbackTarget;
1963
+ if (state?.specifier === void 0) return target;
1964
+ return `${target} (${state.specifier})`;
1965
+ }
1966
+ function toMarker(change) {
1967
+ if (change === "added") return "+";
1968
+ if (change === "removed") return "-";
1969
+ return "~";
1970
+ }
1971
+ function colorizeDiffText(text, change, color) {
1972
+ if (change === "unchanged") return text;
1973
+ return colorizePackageDiff(text, change, color);
1303
1974
  }
1304
1975
  //#endregion
1305
1976
  //#region src/output/ascii/import.ts
@@ -1422,6 +2093,77 @@ function formatReactNodeFilePath$1(node, cwd) {
1422
2093
  return node.kind === "builtin" ? "html" : toDisplayPath(node.filePath, cwd);
1423
2094
  }
1424
2095
  //#endregion
2096
+ //#region src/output/json/deps.ts
2097
+ function graphToSerializablePackageTree(graph) {
2098
+ return serializePackageNode(graph.rootId, graph, /* @__PURE__ */ new Set());
2099
+ }
2100
+ function diffGraphToSerializablePackageTree(graph) {
2101
+ return serializePackageDiffNode(graph.root);
2102
+ }
2103
+ function serializePackageNode(packageDir, graph, visited) {
2104
+ const packageNode = graph.nodes.get(packageDir);
2105
+ const packagePath = toDisplayPath(packageDir, graph.repositoryRoot);
2106
+ if (packageNode === void 0) return {
2107
+ kind: "missing",
2108
+ label: packagePath,
2109
+ path: packagePath,
2110
+ dependencies: []
2111
+ };
2112
+ if (visited.has(packageDir)) return {
2113
+ kind: "circular",
2114
+ label: packageNode.packageName,
2115
+ packageName: packageNode.packageName,
2116
+ path: packagePath,
2117
+ dependencies: []
2118
+ };
2119
+ visited.add(packageDir);
2120
+ return {
2121
+ kind: packageDir === graph.rootId ? "root" : "workspace",
2122
+ label: packageDir === graph.rootId ? packageNode.packageName : packagePath,
2123
+ packageName: packageNode.packageName,
2124
+ path: packagePath,
2125
+ dependencies: packageNode.dependencies.map((dependency) => serializeDependency(dependency, graph, new Set(visited)))
2126
+ };
2127
+ }
2128
+ function serializeDependency(dependency, graph, visited) {
2129
+ if (dependency.kind === "external") return {
2130
+ kind: "external",
2131
+ name: dependency.name,
2132
+ specifier: dependency.specifier,
2133
+ target: `${dependency.name}@${dependency.specifier}`
2134
+ };
2135
+ return {
2136
+ kind: "workspace",
2137
+ name: dependency.name,
2138
+ ...dependency.specifier === void 0 ? {} : { specifier: dependency.specifier },
2139
+ target: toDisplayPath(dependency.target, graph.repositoryRoot),
2140
+ node: serializePackageNode(dependency.target, graph, visited)
2141
+ };
2142
+ }
2143
+ function serializePackageDiffNode(node) {
2144
+ return {
2145
+ kind: node.kind,
2146
+ label: node.label,
2147
+ packageName: node.packageName,
2148
+ path: node.path,
2149
+ change: node.change,
2150
+ ...node.beforePackageName === void 0 ? {} : { beforePackageName: node.beforePackageName },
2151
+ ...node.afterPackageName === void 0 ? {} : { afterPackageName: node.afterPackageName },
2152
+ dependencies: node.dependencies.map((dependency) => serializePackageDiffDependency(dependency))
2153
+ };
2154
+ }
2155
+ function serializePackageDiffDependency(dependency) {
2156
+ return {
2157
+ kind: dependency.kind,
2158
+ name: dependency.name,
2159
+ change: dependency.change,
2160
+ ...dependency.before === void 0 ? {} : { before: dependency.before },
2161
+ ...dependency.after === void 0 ? {} : { after: dependency.after },
2162
+ ...dependency.after === void 0 ? dependency.before === void 0 ? {} : { target: dependency.before.target } : { target: dependency.after.target },
2163
+ ...dependency.kind === "workspace" ? { node: serializePackageDiffNode(dependency.node) } : {}
2164
+ };
2165
+ }
2166
+ //#endregion
1425
2167
  //#region src/output/json/import.ts
1426
2168
  function graphToSerializableTree(graph, options = {}) {
1427
2169
  const visited = /* @__PURE__ */ new Set();
@@ -1532,6 +2274,6 @@ function formatReactNodeFilePath(filePath, kind, cwd) {
1532
2274
  return kind === "builtin" ? "html" : toDisplayPath(filePath, cwd);
1533
2275
  }
1534
2276
  //#endregion
1535
- export { getFilteredUsages as a, analyzeReactUsage as c, printDependencyTree as i, analyzeDependencies as l, graphToSerializableTree as n, getReactUsageEntries as o, printReactUsageTree as r, getReactUsageRoots as s, graphToSerializableReactTree as t, isSourceCodeFile as u };
2277
+ 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 };
1536
2278
 
1537
- //# sourceMappingURL=react-GNHqx2A-.mjs.map
2279
+ //# sourceMappingURL=react-B2on-FAy.mjs.map