foresthouse 1.0.0-dev.16 → 1.0.0-dev.17

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,260 @@
1
1
  import { builtinModules } from "node:module";
2
+ import fs from "node:fs";
2
3
  import path from "node:path";
3
4
  import ts from "typescript";
4
- import fs from "node:fs";
5
5
  import { ResolverFactory } from "oxc-resolver";
6
6
  import { parseSync, visitorKeys } from "oxc-parser";
7
7
  import process$1 from "node:process";
8
+ //#region src/utils/to-display-path.ts
9
+ function toDisplayPath(filePath, cwd) {
10
+ const relativePath = path.relative(cwd, filePath);
11
+ if (relativePath === "") return ".";
12
+ const normalizedPath = relativePath.split(path.sep).join("/");
13
+ return normalizedPath.startsWith("..") ? filePath : normalizedPath;
14
+ }
15
+ //#endregion
16
+ //#region src/analyzers/base.ts
17
+ var BaseAnalyzer = class {
18
+ constructor(entryFile, options) {
19
+ this.entryFile = entryFile;
20
+ this.options = options;
21
+ }
22
+ analyze() {
23
+ return this.doAnalyze();
24
+ }
25
+ };
26
+ //#endregion
27
+ //#region src/analyzers/deps/index.ts
28
+ const PNPM_WORKSPACE_FILE = "pnpm-workspace.yaml";
29
+ function analyzePackageDependencies(directory) {
30
+ return new PackageDependencyAnalyzer(directory).analyze();
31
+ }
32
+ var PackageDependencyAnalyzer = class extends BaseAnalyzer {
33
+ inputPath;
34
+ constructor(directory) {
35
+ super(directory, {});
36
+ this.inputPath = path.resolve(process.cwd(), directory);
37
+ }
38
+ doAnalyze() {
39
+ const rootPackageDir = findNearestPackageDirectory(resolveInputDirectory(this.inputPath));
40
+ const workspaceRootDir = findWorkspaceRoot(rootPackageDir) ?? rootPackageDir;
41
+ const workspacePackages = discoverWorkspacePackages(workspaceRootDir);
42
+ const nodes = /* @__PURE__ */ new Map();
43
+ visitPackage(rootPackageDir, workspaceRootDir, workspacePackages, nodes);
44
+ return {
45
+ repositoryRoot: workspaceRootDir,
46
+ rootId: rootPackageDir,
47
+ nodes
48
+ };
49
+ }
50
+ };
51
+ function visitPackage(packageDir, repositoryRoot, workspacePackages, nodes) {
52
+ if (nodes.has(packageDir)) return;
53
+ const manifest = readPackageManifest(packageDir);
54
+ const packageName = resolvePackageName(manifest, packageDir);
55
+ const dependencies = packageDir === repositoryRoot && workspacePackages.size > 0 ? collectRepositoryRootDependencies(manifest, workspacePackages, repositoryRoot) : collectManifestDependencies(manifest, workspacePackages, repositoryRoot);
56
+ nodes.set(packageDir, {
57
+ packageDir,
58
+ packageName,
59
+ dependencies
60
+ });
61
+ dependencies.forEach((dependency) => {
62
+ if (dependency.kind === "workspace") visitPackage(dependency.target, repositoryRoot, workspacePackages, nodes);
63
+ });
64
+ }
65
+ function resolveInputDirectory(resolvedPath) {
66
+ const stats = fs.existsSync(resolvedPath) ? fs.statSync(resolvedPath) : null;
67
+ if (stats === null) throw new Error(`Directory does not exist: ${resolvedPath}`);
68
+ if (stats.isDirectory()) return resolvedPath;
69
+ if (stats.isFile() && path.basename(resolvedPath) === "package.json") return path.dirname(resolvedPath);
70
+ throw new Error(`Expected a package directory: ${resolvedPath}`);
71
+ }
72
+ function findNearestPackageDirectory(startDirectory) {
73
+ let currentDirectory = startDirectory;
74
+ while (true) {
75
+ const packageJsonPath = path.join(currentDirectory, "package.json");
76
+ if (fs.existsSync(packageJsonPath)) return currentDirectory;
77
+ const parentDirectory = path.dirname(currentDirectory);
78
+ if (parentDirectory === currentDirectory) break;
79
+ currentDirectory = parentDirectory;
80
+ }
81
+ throw new Error(`No package.json found from ${startDirectory}`);
82
+ }
83
+ function findWorkspaceRoot(packageDir) {
84
+ let currentDirectory = packageDir;
85
+ while (true) {
86
+ const packageJsonPath = path.join(currentDirectory, "package.json");
87
+ if (fs.existsSync(packageJsonPath)) {
88
+ const manifest = readPackageManifest(currentDirectory);
89
+ const workspacePatterns = resolveWorkspacePatterns(currentDirectory, manifest);
90
+ if (workspacePatterns.length > 0 && currentDirectory === packageDir) return currentDirectory;
91
+ if (workspacePatterns.length > 0 && isWorkspaceMatch(currentDirectory, workspacePatterns, packageDir)) return currentDirectory;
92
+ }
93
+ const parentDirectory = path.dirname(currentDirectory);
94
+ if (parentDirectory === currentDirectory) return;
95
+ currentDirectory = parentDirectory;
96
+ }
97
+ }
98
+ function discoverWorkspacePackages(repositoryRoot) {
99
+ const workspacePatterns = resolveWorkspacePatterns(repositoryRoot, readPackageManifest(repositoryRoot));
100
+ if (workspacePatterns.length === 0) return /* @__PURE__ */ new Map();
101
+ const workspacePackageDirs = /* @__PURE__ */ new Set();
102
+ workspacePatterns.forEach((pattern) => {
103
+ expandWorkspacePattern(repositoryRoot, pattern).forEach((packageDir) => {
104
+ workspacePackageDirs.add(packageDir);
105
+ });
106
+ });
107
+ const workspacePackages = /* @__PURE__ */ new Map();
108
+ Array.from(workspacePackageDirs).sort((left, right) => left.localeCompare(right)).forEach((packageDir) => {
109
+ const workspaceName = resolvePackageName(readPackageManifest(packageDir), packageDir);
110
+ if (workspacePackages.has(workspaceName)) throw new Error(`Duplicate workspace package name: ${workspaceName}`);
111
+ workspacePackages.set(workspaceName, {
112
+ name: workspaceName,
113
+ packageDir,
114
+ label: toDisplayPath(packageDir, repositoryRoot)
115
+ });
116
+ });
117
+ return workspacePackages;
118
+ }
119
+ function collectManifestDependencies(manifest, workspacePackages, repositoryRoot) {
120
+ const dependencyEntries = Object.entries(manifest.dependencies ?? {});
121
+ const workspaceDependencies = [];
122
+ const externalDependencies = [];
123
+ dependencyEntries.forEach(([dependencyName, specifier]) => {
124
+ const workspacePackage = workspacePackages.get(dependencyName);
125
+ if (workspacePackage !== void 0) {
126
+ workspaceDependencies.push({
127
+ kind: "workspace",
128
+ name: dependencyName,
129
+ specifier,
130
+ target: workspacePackage.packageDir
131
+ });
132
+ return;
133
+ }
134
+ externalDependencies.push({
135
+ kind: "external",
136
+ name: dependencyName,
137
+ specifier
138
+ });
139
+ });
140
+ workspaceDependencies.sort((left, right) => {
141
+ const leftLabel = toDisplayPath(left.target, repositoryRoot);
142
+ const rightLabel = toDisplayPath(right.target, repositoryRoot);
143
+ return leftLabel.localeCompare(rightLabel);
144
+ });
145
+ externalDependencies.sort((left, right) => left.name.localeCompare(right.name));
146
+ return [...workspaceDependencies, ...externalDependencies];
147
+ }
148
+ function collectRepositoryRootDependencies(manifest, workspacePackages, _repositoryRoot) {
149
+ const topLevelWorkspaceDependencies = Array.from(workspacePackages.values()).sort((left, right) => left.label.localeCompare(right.label)).map((workspacePackage) => ({
150
+ kind: "workspace",
151
+ name: workspacePackage.name,
152
+ target: workspacePackage.packageDir
153
+ }));
154
+ const externalDependencies = Object.entries(manifest.dependencies ?? {}).filter(([dependencyName]) => !workspacePackages.has(dependencyName)).map(([dependencyName, specifier]) => ({
155
+ kind: "external",
156
+ name: dependencyName,
157
+ specifier
158
+ })).sort((left, right) => left.name.localeCompare(right.name));
159
+ return [...topLevelWorkspaceDependencies, ...externalDependencies];
160
+ }
161
+ function readPackageManifest(packageDir) {
162
+ const packageJsonPath = path.join(packageDir, "package.json");
163
+ if (!fs.existsSync(packageJsonPath)) throw new Error(`Missing package.json in ${packageDir}`);
164
+ const manifestText = fs.readFileSync(packageJsonPath, "utf8");
165
+ try {
166
+ const parsed = JSON.parse(manifestText);
167
+ if (!isRecord(parsed)) throw new Error("package.json must contain an object");
168
+ return parsed;
169
+ } catch (error) {
170
+ const message = error instanceof Error ? error.message : "Unknown JSON parse error";
171
+ throw new Error(`Failed to read ${packageJsonPath}: ${message}`);
172
+ }
173
+ }
174
+ function resolvePackageName(manifest, packageDir) {
175
+ if (typeof manifest.name === "string" && manifest.name.trim().length > 0) return manifest.name;
176
+ throw new Error(`Package at ${packageDir} is missing a valid name`);
177
+ }
178
+ function getWorkspacePatterns(manifest) {
179
+ if (Array.isArray(manifest.workspaces)) return manifest.workspaces.filter((pattern) => typeof pattern === "string" && pattern.length > 0);
180
+ if (isRecord(manifest.workspaces) && Array.isArray(manifest.workspaces.packages)) return manifest.workspaces.packages.filter((pattern) => typeof pattern === "string" && pattern.length > 0);
181
+ return [];
182
+ }
183
+ function resolveWorkspacePatterns(repositoryRoot, manifest) {
184
+ const manifestPatterns = getWorkspacePatterns(manifest);
185
+ if (manifestPatterns.length > 0) return manifestPatterns;
186
+ return readPnpmWorkspacePatterns(repositoryRoot);
187
+ }
188
+ function readPnpmWorkspacePatterns(repositoryRoot) {
189
+ const pnpmWorkspacePath = path.join(repositoryRoot, PNPM_WORKSPACE_FILE);
190
+ if (!fs.existsSync(pnpmWorkspacePath)) return [];
191
+ const lines = fs.readFileSync(pnpmWorkspacePath, "utf8").split(/\r?\n/);
192
+ const patterns = [];
193
+ let inPackagesBlock = false;
194
+ for (const line of lines) {
195
+ const trimmedLine = line.trim();
196
+ if (trimmedLine.length === 0 || trimmedLine.startsWith("#")) continue;
197
+ if (!inPackagesBlock) {
198
+ if (trimmedLine === "packages:") inPackagesBlock = true;
199
+ continue;
200
+ }
201
+ if (!line.startsWith(" ") && !line.startsWith(" ")) break;
202
+ if (!trimmedLine.startsWith("- ")) continue;
203
+ const pattern = unquoteWorkspacePattern(trimmedLine.slice(2).trim());
204
+ if (pattern.length > 0) patterns.push(pattern);
205
+ }
206
+ return patterns;
207
+ }
208
+ function unquoteWorkspacePattern(pattern) {
209
+ if (pattern.startsWith("\"") && pattern.endsWith("\"") || pattern.startsWith("'") && pattern.endsWith("'")) return pattern.slice(1, -1);
210
+ return pattern;
211
+ }
212
+ function isWorkspaceMatch(repositoryRoot, patterns, packageDir) {
213
+ return patterns.some((pattern) => expandWorkspacePattern(repositoryRoot, pattern).includes(packageDir));
214
+ }
215
+ function expandWorkspacePattern(repositoryRoot, pattern) {
216
+ return matchWorkspaceSegments(repositoryRoot, normalizeWorkspacePattern(pattern), 0).filter((packageDir) => fs.existsSync(path.join(packageDir, "package.json")));
217
+ }
218
+ function normalizeWorkspacePattern(pattern) {
219
+ return pattern.split(/[\\/]+/).filter((segment) => segment.length > 0 && segment !== ".");
220
+ }
221
+ function matchWorkspaceSegments(currentDirectory, segments, index) {
222
+ if (index >= segments.length) return [currentDirectory];
223
+ const segment = segments[index];
224
+ if (segment === void 0) return [currentDirectory];
225
+ if (segment === "**") {
226
+ const results = new Set(matchWorkspaceSegments(currentDirectory, segments, index + 1));
227
+ listSubdirectories(currentDirectory).forEach((subdirectory) => {
228
+ matchWorkspaceSegments(subdirectory, segments, index).forEach((match) => {
229
+ results.add(match);
230
+ });
231
+ });
232
+ return [...results];
233
+ }
234
+ const matcher = createWorkspaceSegmentMatcher(segment);
235
+ const results = [];
236
+ listSubdirectories(currentDirectory).forEach((subdirectory) => {
237
+ if (!matcher(path.basename(subdirectory))) return;
238
+ results.push(...matchWorkspaceSegments(subdirectory, segments, index + 1));
239
+ });
240
+ return results;
241
+ }
242
+ function listSubdirectories(directory) {
243
+ return fs.readdirSync(directory, { withFileTypes: true }).filter((entry) => entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".git").map((entry) => path.join(directory, entry.name));
244
+ }
245
+ function createWorkspaceSegmentMatcher(segment) {
246
+ if (!segment.includes("*")) return (name) => name === segment;
247
+ const escapedSegment = escapeRegExp(segment).replaceAll("*", "[^/]*");
248
+ const pattern = new RegExp(`^${escapedSegment}$`);
249
+ return (name) => pattern.test(name);
250
+ }
251
+ function escapeRegExp(value) {
252
+ return value.replaceAll(/[|\\{}()[\]^$+?.]/g, "\\$&");
253
+ }
254
+ function isRecord(value) {
255
+ return typeof value === "object" && value !== null;
256
+ }
257
+ //#endregion
8
258
  //#region src/typescript/config.ts
9
259
  function loadCompilerOptions(searchFrom, explicitConfigPath) {
10
260
  const configPath = explicitConfigPath === void 0 ? findNearestConfig(searchFrom) : path.resolve(searchFrom, explicitConfigPath);
@@ -54,17 +304,6 @@ function isInsideNodeModules$1(filePath) {
54
304
  return filePath.includes(`${path.sep}node_modules${path.sep}`);
55
305
  }
56
306
  //#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
307
  //#region src/utils/is-source-code-file.ts
69
308
  const SOURCE_EXTENSIONS = new Set([
70
309
  ".js",
@@ -1251,6 +1490,40 @@ function matchesReactFilter(node, filter) {
1251
1490
  return filter === "all" || node.kind === filter;
1252
1491
  }
1253
1492
  //#endregion
1493
+ //#region src/output/ascii/deps.ts
1494
+ function printPackageDependencyTree(graph, _options = {}) {
1495
+ const rootNode = graph.nodes.get(graph.rootId);
1496
+ if (rootNode === void 0) return toDisplayPath(graph.rootId, graph.repositoryRoot);
1497
+ const lines = [rootNode.packageName];
1498
+ const visited = new Set([graph.rootId]);
1499
+ rootNode.dependencies.forEach((dependency, index) => {
1500
+ lines.push(...renderDependency$1(dependency, graph, visited, "", index === rootNode.dependencies.length - 1));
1501
+ });
1502
+ return lines.join("\n");
1503
+ }
1504
+ function renderDependency$1(dependency, graph, visited, prefix, isLast) {
1505
+ const branch = `${prefix}${isLast ? "└─ " : "├─ "}`;
1506
+ const label = formatDependencyLabel$1(dependency, graph.repositoryRoot);
1507
+ if (dependency.kind === "external") return [`${branch}${label}`];
1508
+ if (visited.has(dependency.target)) return [`${branch}${label} (circular)`];
1509
+ const childNode = graph.nodes.get(dependency.target);
1510
+ if (childNode === void 0) return [`${branch}${label}`];
1511
+ const childLines = [`${branch}${label}`];
1512
+ const nextPrefix = `${prefix}${isLast ? " " : "│ "}`;
1513
+ const nextVisited = new Set(visited);
1514
+ nextVisited.add(dependency.target);
1515
+ childNode.dependencies.forEach((childDependency, index) => {
1516
+ childLines.push(...renderDependency$1(childDependency, graph, nextVisited, nextPrefix, index === childNode.dependencies.length - 1));
1517
+ });
1518
+ return childLines;
1519
+ }
1520
+ function formatDependencyLabel$1(dependency, repositoryRoot) {
1521
+ if (dependency.kind === "external") return `${dependency.name}@${dependency.specifier}`;
1522
+ const workspaceLabel = toDisplayPath(dependency.target, repositoryRoot);
1523
+ if (dependency.specifier === void 0) return workspaceLabel;
1524
+ return `${workspaceLabel} (${dependency.specifier})`;
1525
+ }
1526
+ //#endregion
1254
1527
  //#region src/color.ts
1255
1528
  const ANSI_RESET = "\x1B[0m";
1256
1529
  const ANSI_COMPONENT = "\x1B[36m";
@@ -1294,14 +1567,6 @@ function getReactSymbolColor(kind) {
1294
1567
  return ANSI_BUILTIN;
1295
1568
  }
1296
1569
  //#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;
1303
- }
1304
- //#endregion
1305
1570
  //#region src/output/ascii/import.ts
1306
1571
  function printDependencyTree(graph, options = {}) {
1307
1572
  const cwd = options.cwd ?? graph.cwd;
@@ -1422,6 +1687,51 @@ function formatReactNodeFilePath$1(node, cwd) {
1422
1687
  return node.kind === "builtin" ? "html" : toDisplayPath(node.filePath, cwd);
1423
1688
  }
1424
1689
  //#endregion
1690
+ //#region src/output/json/deps.ts
1691
+ function graphToSerializablePackageTree(graph) {
1692
+ return serializePackageNode(graph.rootId, graph, /* @__PURE__ */ new Set());
1693
+ }
1694
+ function serializePackageNode(packageDir, graph, visited) {
1695
+ const packageNode = graph.nodes.get(packageDir);
1696
+ const packagePath = toDisplayPath(packageDir, graph.repositoryRoot);
1697
+ if (packageNode === void 0) return {
1698
+ kind: "missing",
1699
+ label: packagePath,
1700
+ path: packagePath,
1701
+ dependencies: []
1702
+ };
1703
+ if (visited.has(packageDir)) return {
1704
+ kind: "circular",
1705
+ label: packageNode.packageName,
1706
+ packageName: packageNode.packageName,
1707
+ path: packagePath,
1708
+ dependencies: []
1709
+ };
1710
+ visited.add(packageDir);
1711
+ return {
1712
+ kind: packageDir === graph.rootId ? "root" : "workspace",
1713
+ label: packageDir === graph.rootId ? packageNode.packageName : packagePath,
1714
+ packageName: packageNode.packageName,
1715
+ path: packagePath,
1716
+ dependencies: packageNode.dependencies.map((dependency) => serializeDependency(dependency, graph, new Set(visited)))
1717
+ };
1718
+ }
1719
+ function serializeDependency(dependency, graph, visited) {
1720
+ if (dependency.kind === "external") return {
1721
+ kind: "external",
1722
+ name: dependency.name,
1723
+ specifier: dependency.specifier,
1724
+ target: `${dependency.name}@${dependency.specifier}`
1725
+ };
1726
+ return {
1727
+ kind: "workspace",
1728
+ name: dependency.name,
1729
+ ...dependency.specifier === void 0 ? {} : { specifier: dependency.specifier },
1730
+ target: toDisplayPath(dependency.target, graph.repositoryRoot),
1731
+ node: serializePackageNode(dependency.target, graph, visited)
1732
+ };
1733
+ }
1734
+ //#endregion
1425
1735
  //#region src/output/json/import.ts
1426
1736
  function graphToSerializableTree(graph, options = {}) {
1427
1737
  const visited = /* @__PURE__ */ new Set();
@@ -1532,6 +1842,6 @@ function formatReactNodeFilePath(filePath, kind, cwd) {
1532
1842
  return kind === "builtin" ? "html" : toDisplayPath(filePath, cwd);
1533
1843
  }
1534
1844
  //#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 };
1845
+ export { printDependencyTree as a, getReactUsageEntries as c, analyzeDependencies as d, isSourceCodeFile as f, printReactUsageTree as i, getReactUsageRoots as l, graphToSerializableTree as n, printPackageDependencyTree as o, analyzePackageDependencies as p, graphToSerializablePackageTree as r, getFilteredUsages as s, graphToSerializableReactTree as t, analyzeReactUsage as u };
1536
1846
 
1537
- //# sourceMappingURL=react-GNHqx2A-.mjs.map
1847
+ //# sourceMappingURL=react-Ba7anDr7.mjs.map