foresthouse 1.0.0-dev.2 → 1.0.0-dev.21

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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # foresthouse
2
2
 
3
- `foresthouse` is a modern TypeScript-first Node.js CLI that starts from an entry file, follows local JavaScript and TypeScript imports, and prints the result as a dependency tree.
3
+ `foresthouse` is a modern TypeScript-first Node.js CLI that can print source import trees, React usage trees, and package-manifest dependency trees.
4
4
 
5
5
  ## Stack
6
6
 
@@ -18,7 +18,10 @@
18
18
  - Reads a JavaScript/TypeScript entry file (`.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`, `.mts`, `.cts`)
19
19
  - Resolves local imports, re-exports, `require()`, and string-literal dynamic `import()`
20
20
  - Honors the nearest `tsconfig.json` or `jsconfig.json`, including `baseUrl` and `paths`
21
+ - Expands sibling workspace packages by default, including their own `tsconfig` alias rules
22
+ - Reads `package.json` manifests to show package-level dependency trees for single packages and monorepos
21
23
  - Prints a tree by default, or JSON with `--json`
24
+ - Colorizes ASCII output automatically when the terminal supports ANSI colors
22
25
 
23
26
  ## Usage
24
27
 
@@ -26,13 +29,14 @@
26
29
  corepack enable
27
30
  pnpm install
28
31
  pnpm run build
29
- node dist/cli.mjs src/index.ts
32
+ node dist/cli.mjs import src/index.ts
33
+ node dist/cli.mjs deps .
30
34
  ```
31
35
 
32
36
  ### Example
33
37
 
34
38
  ```bash
35
- node dist/cli.mjs src/main.ts --cwd test/fixtures/basic
39
+ node dist/cli.mjs import src/main.ts --cwd test/fixtures/basic
36
40
  ```
37
41
 
38
42
  Output:
@@ -48,11 +52,38 @@ src/main.ts
48
52
 
49
53
  ### Options
50
54
 
55
+ `import` command:
56
+
57
+ - `foresthouse import <path>`: analyze an entry file by positional argument
58
+ - `foresthouse import --entry <path>`: analyze an entry file by explicit option
51
59
  - `--cwd <path>`: working directory for resolving the entry file and config
52
60
  - `--config <path>`: use a specific `tsconfig.json` or `jsconfig.json`
53
61
  - `--include-externals`: include packages and Node built-ins in the output
62
+ - `--no-workspaces`: stop at sibling workspace package boundaries instead of expanding them
63
+ - `--project-only`: restrict traversal to the active `tsconfig.json` or `jsconfig.json` project
64
+ - `--no-unused`: omit imports that are never referenced
65
+ - `--json`: print a JSON tree instead of ASCII output
66
+
67
+ `react` command:
68
+
69
+ - `foresthouse react <path>`: print a React usage tree from an entry file
70
+ - `foresthouse react --nextjs`: infer Next.js page entries from `app/`, `pages/`, `src/app/`, and `src/pages/`
71
+ - `--cwd <path>`: working directory for resolving the entry file and config
72
+ - `--config <path>`: use a specific `tsconfig.json` or `jsconfig.json`
73
+ - `--nextjs`: allow omitting the explicit React entry file and discover Next.js page entries relative to `--cwd` or the current directory
74
+ - `--filter <component|hook|builtin>`: limit the output to a specific React symbol kind
75
+ - `--builtin`: include built-in HTML nodes such as `button` and `div`
76
+ - `--no-workspaces`: stop at sibling workspace package boundaries instead of expanding them
77
+ - `--project-only`: restrict traversal to the active `tsconfig.json` or `jsconfig.json` project
54
78
  - `--json`: print a JSON tree instead of ASCII output
55
79
 
80
+ `deps` command:
81
+
82
+ - `foresthouse deps <directory>`: analyze the nearest package rooted at the given directory
83
+ - `foresthouse deps .`: analyze the current package or repository root
84
+ - `foresthouse deps ./packages/a`: analyze a specific workspace package directory
85
+ - `--json`: print a JSON package tree instead of ASCII output
86
+
56
87
  ## Development
57
88
 
58
89
  ```bash
package/dist/cli.mjs CHANGED
@@ -1,129 +1,291 @@
1
1
  #!/usr/bin/env node
2
- import { a as graphToSerializableReactTree, n as printReactUsageTree, o as analyzeDependencies, r as analyzeReactUsage, s as graphToSerializableTree, t as printDependencyTree } from "./tree-Cp8CNmeY.mjs";
2
+ import { a as printReactUsageTree, c as printPackageDependencyTree, f as analyzeReactUsage, g as analyzePackageDependencies, h as analyzePackageDependencyDiff, i as graphToSerializablePackageTree, m as isSourceCodeFile, n as graphToSerializableTree, o as printDependencyTree, p as analyzeDependencies, r as diffGraphToSerializablePackageTree, s as printPackageDependencyDiffTree, t as graphToSerializableReactTree } from "./react-DWxB8qZ6.mjs";
3
3
  import { createRequire } from "node:module";
4
- import process from "node:process";
5
- //#region src/cli.ts
6
- const { version } = createRequire(import.meta.url)("../package.json");
7
- function main() {
8
- try {
9
- const options = parseArgs(process.argv.slice(2));
10
- if (options.react !== void 0) {
11
- const graph = analyzeReactUsage(options.entryFile, {
12
- ...options.cwd === void 0 ? {} : { cwd: options.cwd },
13
- ...options.configPath === void 0 ? {} : { configPath: options.configPath }
14
- });
15
- if (options.json) {
16
- process.stdout.write(`${JSON.stringify(graphToSerializableReactTree(graph, { filter: options.react }), null, 2)}\n`);
17
- return;
18
- }
19
- process.stdout.write(`${printReactUsageTree(graph, {
20
- cwd: options.cwd ?? graph.cwd,
21
- filter: options.react
22
- })}\n`);
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import process$1 from "node:process";
7
+ import { cac } from "cac";
8
+ //#region src/commands/base.ts
9
+ var BaseCommand = class {
10
+ constructor(options) {
11
+ this.options = options;
12
+ }
13
+ run() {
14
+ const graph = this.analyze();
15
+ if (this.isJsonMode()) {
16
+ process.stdout.write(`${JSON.stringify(this.serialize(graph), null, 2)}\n`);
23
17
  return;
24
18
  }
25
- const graph = analyzeDependencies(options.entryFile, {
26
- ...options.cwd === void 0 ? {} : { cwd: options.cwd },
27
- ...options.configPath === void 0 ? {} : { configPath: options.configPath }
19
+ process.stdout.write(`${this.render(graph)}\n`);
20
+ }
21
+ isJsonMode() {
22
+ return this.options.json;
23
+ }
24
+ getAnalyzeOptions() {
25
+ return {
26
+ ...this.options.cwd === void 0 ? {} : { cwd: this.options.cwd },
27
+ ...this.options.configPath === void 0 ? {} : { configPath: this.options.configPath },
28
+ expandWorkspaces: this.options.expandWorkspaces,
29
+ projectOnly: this.options.projectOnly
30
+ };
31
+ }
32
+ };
33
+ //#endregion
34
+ //#region src/commands/deps.ts
35
+ var DepsCommand = class extends BaseCommand {
36
+ analyze() {
37
+ if (this.options.diff !== void 0) return analyzePackageDependencyDiff(this.options.directory, this.options.diff);
38
+ return analyzePackageDependencies(this.options.directory);
39
+ }
40
+ serialize(graph) {
41
+ if (isPackageDependencyDiffGraph(graph)) return diffGraphToSerializablePackageTree(graph);
42
+ return graphToSerializablePackageTree(graph);
43
+ }
44
+ render(graph) {
45
+ if (isPackageDependencyDiffGraph(graph)) return printPackageDependencyDiffTree(graph);
46
+ return printPackageDependencyTree(graph);
47
+ }
48
+ };
49
+ function isPackageDependencyDiffGraph(graph) {
50
+ return "root" in graph;
51
+ }
52
+ //#endregion
53
+ //#region src/commands/import.ts
54
+ var ImportCommand = class extends BaseCommand {
55
+ analyze() {
56
+ return analyzeDependencies(this.options.entryFile, this.getAnalyzeOptions());
57
+ }
58
+ serialize(graph) {
59
+ return graphToSerializableTree(graph, { omitUnused: this.options.omitUnused });
60
+ }
61
+ render(graph) {
62
+ return printDependencyTree(graph, {
63
+ cwd: this.options.cwd ?? graph.cwd,
64
+ includeExternals: this.options.includeExternals,
65
+ omitUnused: this.options.omitUnused
28
66
  });
29
- if (options.json) {
30
- process.stdout.write(`${JSON.stringify(graphToSerializableTree(graph), null, 2)}\n`);
31
- return;
32
- }
33
- process.stdout.write(`${printDependencyTree(graph, {
34
- cwd: options.cwd ?? graph.cwd,
35
- includeExternals: options.includeExternals
36
- })}\n`);
37
- } catch (error) {
38
- const message = error instanceof Error ? error.message : "Unknown error occurred.";
39
- process.stderr.write(`foresthouse: ${message}\n`);
40
- process.exitCode = 1;
41
67
  }
68
+ };
69
+ //#endregion
70
+ //#region src/app/react-entry-files.ts
71
+ const NEXT_JS_ENTRY_ROOTS = [
72
+ "pages",
73
+ "app",
74
+ path.join("src", "pages"),
75
+ path.join("src", "app")
76
+ ];
77
+ function resolveReactEntryFiles(options) {
78
+ if (options.entryFile !== void 0) return [options.entryFile];
79
+ if (!options.nextjs) throw new Error("Missing React entry file. Use `foresthouse react <entry-file>` or `foresthouse react --nextjs`.");
80
+ return discoverNextJsPageEntries(options.cwd);
42
81
  }
43
- function parseArgs(argv) {
44
- if (argv.includes("--version") || argv.includes("-v")) {
45
- process.stdout.write(`${version}\n`);
46
- process.exit(0);
47
- }
48
- if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) {
49
- printHelp();
50
- process.exit(0);
51
- }
52
- let entryFile;
53
- let cwd;
54
- let configPath;
55
- let includeExternals = false;
56
- let json = false;
57
- let react;
58
- for (let index = 0; index < argv.length; index += 1) {
59
- const argument = argv[index];
60
- if (argument === void 0) continue;
61
- if (argument === "--cwd") {
62
- cwd = readOptionValue(argv, index, "--cwd");
63
- index += 1;
64
- continue;
65
- }
66
- if (argument === "--config") {
67
- configPath = readOptionValue(argv, index, "--config");
68
- index += 1;
69
- continue;
70
- }
71
- if (argument === "--include-externals") {
72
- includeExternals = true;
73
- continue;
74
- }
75
- if (argument === "--json") {
76
- json = true;
77
- continue;
78
- }
79
- if (argument === "--react") {
80
- react = "all";
81
- continue;
82
- }
83
- if (argument.startsWith("--react=")) {
84
- const value = argument.slice(8);
85
- if (value === "component" || value === "hook") {
86
- react = value;
82
+ function discoverNextJsPageEntries(cwd) {
83
+ const effectiveCwd = path.resolve(cwd ?? process.cwd());
84
+ const entries = /* @__PURE__ */ new Set();
85
+ collectPagesRouterEntries(path.join(effectiveCwd, "pages"), effectiveCwd, entries);
86
+ collectAppRouterEntries(path.join(effectiveCwd, "app"), effectiveCwd, entries);
87
+ collectPagesRouterEntries(path.join(effectiveCwd, "src", "pages"), effectiveCwd, entries);
88
+ collectAppRouterEntries(path.join(effectiveCwd, "src", "app"), effectiveCwd, entries);
89
+ const resolvedEntries = [...entries].sort();
90
+ if (resolvedEntries.length > 0) return resolvedEntries;
91
+ const searchedRoots = NEXT_JS_ENTRY_ROOTS.map((root) => `\`${root}/\``).join(", ");
92
+ throw new Error(`No Next.js page entries found. Searched ${searchedRoots} relative to ${effectiveCwd}.`);
93
+ }
94
+ function collectPagesRouterEntries(rootDirectory, cwd, entries) {
95
+ walkDirectory(rootDirectory, (filePath, relativePath) => {
96
+ if (!isSourceCodeFile(filePath) || filePath.endsWith(".d.ts")) return;
97
+ if (relativePath.split(path.sep)[0] === "api") return;
98
+ if (path.basename(filePath).startsWith("_")) return;
99
+ entries.add(path.relative(cwd, filePath));
100
+ });
101
+ }
102
+ function collectAppRouterEntries(rootDirectory, cwd, entries) {
103
+ walkDirectory(rootDirectory, (filePath) => {
104
+ if (!isSourceCodeFile(filePath) || filePath.endsWith(".d.ts")) return;
105
+ const extension = path.extname(filePath);
106
+ if (path.basename(filePath, extension) !== "page") return;
107
+ entries.add(path.relative(cwd, filePath));
108
+ });
109
+ }
110
+ function walkDirectory(rootDirectory, visitor) {
111
+ if (!fs.existsSync(rootDirectory)) return;
112
+ const stack = [rootDirectory];
113
+ while (stack.length > 0) {
114
+ const directory = stack.pop();
115
+ if (directory === void 0) continue;
116
+ const directoryEntries = fs.readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
117
+ for (let index = directoryEntries.length - 1; index >= 0; index -= 1) {
118
+ const entry = directoryEntries[index];
119
+ if (entry === void 0) continue;
120
+ const entryPath = path.join(directory, entry.name);
121
+ if (entry.isDirectory()) {
122
+ stack.push(entryPath);
87
123
  continue;
88
124
  }
89
- throw new Error(`Unknown React mode: ${value}`);
125
+ if (!entry.isFile()) continue;
126
+ visitor(entryPath, path.relative(rootDirectory, entryPath));
90
127
  }
91
- if (argument.startsWith("-")) throw new Error(`Unknown option: ${argument}`);
92
- if (entryFile !== void 0) throw new Error("Only one entry file can be provided.");
93
- entryFile = argument;
94
128
  }
95
- if (entryFile === void 0) throw new Error("An entry file is required.");
129
+ }
130
+ //#endregion
131
+ //#region src/commands/react.ts
132
+ var ReactCommand = class extends BaseCommand {
133
+ analyze() {
134
+ return analyzeReactUsage(resolveReactEntryFiles(this.options), this.getAnalyzeOptions());
135
+ }
136
+ serialize(graph) {
137
+ return graphToSerializableReactTree(graph, { filter: this.getFilter() });
138
+ }
139
+ render(graph) {
140
+ return printReactUsageTree(graph, {
141
+ cwd: this.options.cwd ?? graph.cwd,
142
+ filter: this.getFilter()
143
+ });
144
+ }
145
+ getAnalyzeOptions() {
146
+ return {
147
+ ...super.getAnalyzeOptions(),
148
+ includeBuiltins: this.options.includeBuiltins
149
+ };
150
+ }
151
+ getFilter() {
152
+ return this.options.filter;
153
+ }
154
+ };
155
+ //#endregion
156
+ //#region src/app/run.ts
157
+ function runCli(options) {
158
+ new CliApplication(options).run();
159
+ }
160
+ var CliApplication = class {
161
+ constructor(options) {
162
+ this.options = options;
163
+ }
164
+ run() {
165
+ this.createCommand().run();
166
+ }
167
+ createCommand() {
168
+ if (this.options.command === "deps") return new DepsCommand(this.options);
169
+ if (this.options.command === "react") return new ReactCommand(this.options);
170
+ return new ImportCommand(this.options);
171
+ }
172
+ };
173
+ //#endregion
174
+ //#region src/app/cli.ts
175
+ function main(version, argv = process$1.argv.slice(2)) {
176
+ new CliMain(version, argv).run();
177
+ }
178
+ var CliMain = class {
179
+ constructor(version, argv) {
180
+ this.version = version;
181
+ this.argv = argv;
182
+ }
183
+ run() {
184
+ try {
185
+ const cli = this.createCli();
186
+ if (this.argv.length === 0) {
187
+ cli.outputHelp();
188
+ return;
189
+ }
190
+ this.validateCommandSyntax();
191
+ cli.parse(this.toProcessArgv());
192
+ } catch (error) {
193
+ const message = error instanceof Error ? error.message : "Unknown error occurred.";
194
+ process$1.stderr.write(`foresthouse: ${message}\n`);
195
+ process$1.exitCode = 1;
196
+ }
197
+ }
198
+ createCli() {
199
+ const cli = cac("foresthouse");
200
+ cli.command("deps <directory>", "Analyze package.json dependencies from a package directory.").usage("deps <directory> [options]").option("--diff <git-ref-or-range>", "Show only dependency-tree changes relative to a Git revision or range.").option("--json", "Print the package tree as JSON.").action((directory, rawOptions) => {
201
+ runCli(normalizeDepsCliOptions(directory, rawOptions));
202
+ });
203
+ cli.command("import [entry-file]", "Analyze an entry file and print its dependency tree.").usage("import <entry-file> [options]").option("--entry <path>", "Entry file to analyze.").option("--cwd <path>", "Working directory used for relative paths.").option("--config <path>", "Explicit tsconfig.json or jsconfig.json path.").option("--include-externals", "Include packages and Node built-ins in the tree.").option("--no-workspaces", "Do not expand sibling workspace packages into source subtrees.").option("--project-only", "Restrict traversal to the active tsconfig.json or jsconfig.json project.").option("--no-unused", "Omit imports that are never referenced.").option("--json", "Print the dependency tree as JSON.").action((entryFile, rawOptions) => {
204
+ runCli(normalizeImportCliOptions(entryFile, rawOptions));
205
+ });
206
+ cli.command("react [entry-file]", "Analyze React usage from an entry file.").usage("react [entry-file] [options]").option("--cwd <path>", "Working directory used for relative paths.").option("--config <path>", "Explicit tsconfig.json or jsconfig.json path.").option("--nextjs", "Infer Next.js page entries from app/ and pages/ when no entry is provided.").option("--filter <mode>", "Limit output to `component`, `hook`, or `builtin` usages.").option("--builtin", "Include built-in HTML nodes in the React tree.").option("--no-workspaces", "Do not expand sibling workspace packages into source subtrees.").option("--project-only", "Restrict traversal to the active tsconfig.json or jsconfig.json project.").option("--json", "Print the React usage tree as JSON.").action((entryFile, rawOptions) => {
207
+ runCli(normalizeReactCliOptions(entryFile, rawOptions));
208
+ });
209
+ cli.help();
210
+ cli.version(this.version);
211
+ return cli;
212
+ }
213
+ validateCommandSyntax() {
214
+ if (this.argv.length === 0) return;
215
+ const firstArgument = this.argv[0];
216
+ if (firstArgument === void 0) return;
217
+ if (firstArgument === "--react" || firstArgument.startsWith("--react=")) throw new Error("Unknown option `--react`");
218
+ if (firstArgument.startsWith("-")) return;
219
+ if (firstArgument === "deps" || firstArgument === "import" || firstArgument === "react") return;
220
+ throw new Error(`Unknown command \`${firstArgument}\``);
221
+ }
222
+ toProcessArgv() {
223
+ return [
224
+ "node",
225
+ "foresthouse",
226
+ ...this.argv
227
+ ];
228
+ }
229
+ };
230
+ function normalizeDepsCliOptions(directory, options) {
96
231
  return {
97
- entryFile,
98
- cwd,
99
- configPath,
100
- includeExternals,
101
- json,
102
- react
232
+ command: "deps",
233
+ directory,
234
+ diff: options.diff,
235
+ cwd: void 0,
236
+ configPath: void 0,
237
+ expandWorkspaces: true,
238
+ projectOnly: false,
239
+ json: options.json === true
103
240
  };
104
241
  }
105
- function readOptionValue(argv, index, optionName) {
106
- const value = argv[index + 1];
107
- if (value === void 0 || value.startsWith("-")) throw new Error(`Missing value for ${optionName}`);
108
- return value;
242
+ function normalizeImportCliOptions(entryFile, options) {
243
+ return {
244
+ command: "import",
245
+ entryFile: resolveImportEntryFile(entryFile, options.entry),
246
+ cwd: options.cwd,
247
+ configPath: options.config,
248
+ expandWorkspaces: options.workspaces !== false,
249
+ projectOnly: options.projectOnly === true,
250
+ includeExternals: options.includeExternals === true,
251
+ omitUnused: options.unused === false,
252
+ json: options.json === true
253
+ };
109
254
  }
110
- function printHelp() {
111
- process.stdout.write(`foresthouse v${version}
112
-
113
- Usage:
114
- foresthouse <entry-file> [options]
115
-
116
- Options:
117
- --cwd <path> Working directory used for relative paths.
118
- --config <path> Explicit tsconfig.json or jsconfig.json path.
119
- --include-externals Include packages and Node built-ins in the tree.
120
- --react[=component|hook] Print a React usage tree instead of the import tree.
121
- --json Print the dependency tree as JSON.
122
- -v, --version Show the current version.
123
- -h, --help Show this help message.
124
- `);
255
+ function normalizeReactCliOptions(entryFile, options) {
256
+ return {
257
+ command: "react",
258
+ entryFile: resolveReactEntryFile(entryFile, options.nextjs),
259
+ cwd: options.cwd,
260
+ configPath: options.config,
261
+ expandWorkspaces: options.workspaces !== false,
262
+ projectOnly: options.projectOnly === true,
263
+ json: options.json === true,
264
+ filter: normalizeReactFilter(options.filter),
265
+ nextjs: options.nextjs === true,
266
+ includeBuiltins: options.builtin === true
267
+ };
268
+ }
269
+ function resolveImportEntryFile(positionalEntryFile, optionEntryFile) {
270
+ if (positionalEntryFile !== void 0 && optionEntryFile !== void 0 && positionalEntryFile !== optionEntryFile) throw new Error("Provide the import entry only once, either as `foresthouse import <entry-file>` or `foresthouse import --entry <path>`.");
271
+ const resolvedEntryFile = positionalEntryFile ?? optionEntryFile;
272
+ if (resolvedEntryFile === void 0) throw new Error("Missing import entry file. Use `foresthouse import <entry-file>` or `foresthouse import --entry <path>`.");
273
+ return resolvedEntryFile;
274
+ }
275
+ function normalizeReactFilter(filter) {
276
+ if (filter === void 0) return "all";
277
+ if (filter === "component" || filter === "hook" || filter === "builtin") return filter;
278
+ throw new Error(`Unknown React filter: ${filter}`);
125
279
  }
126
- main();
280
+ function resolveReactEntryFile(entryFile, nextjs) {
281
+ if (entryFile !== void 0) return entryFile;
282
+ if (nextjs === true) return;
283
+ throw new Error("Missing React entry file. Use `foresthouse react <entry-file>` or `foresthouse react --nextjs`.");
284
+ }
285
+ //#endregion
286
+ //#region src/cli.ts
287
+ const { version } = createRequire(import.meta.url)("../package.json");
288
+ main(version);
127
289
  //#endregion
128
290
  export {};
129
291
 
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { createRequire } from 'node:module'\nimport process from 'node:process'\n\nimport { analyzeDependencies, graphToSerializableTree } from './analyzer.js'\nimport {\n analyzeReactUsage,\n graphToSerializableReactTree,\n} from './react-analyzer.js'\nimport { printReactUsageTree } from './react-tree.js'\nimport { printDependencyTree } from './tree.js'\nimport type { ReactUsageFilter } from './types.js'\n\nconst require = createRequire(import.meta.url)\nconst { version } = require('../package.json') as { version: string }\n\ninterface CliOptions {\n readonly entryFile: string\n readonly cwd: string | undefined\n readonly configPath: string | undefined\n readonly includeExternals: boolean\n readonly json: boolean\n readonly react: ReactUsageFilter | undefined\n}\n\nfunction main(): void {\n try {\n const options = parseArgs(process.argv.slice(2))\n\n if (options.react !== undefined) {\n const graph = analyzeReactUsage(options.entryFile, {\n ...(options.cwd === undefined ? {} : { cwd: options.cwd }),\n ...(options.configPath === undefined\n ? {}\n : { configPath: options.configPath }),\n })\n\n if (options.json) {\n process.stdout.write(\n `${JSON.stringify(\n graphToSerializableReactTree(graph, {\n filter: options.react,\n }),\n null,\n 2,\n )}\\n`,\n )\n return\n }\n\n process.stdout.write(\n `${printReactUsageTree(graph, {\n cwd: options.cwd ?? graph.cwd,\n filter: options.react,\n })}\\n`,\n )\n return\n }\n\n const graph = analyzeDependencies(options.entryFile, {\n ...(options.cwd === undefined ? {} : { cwd: options.cwd }),\n ...(options.configPath === undefined\n ? {}\n : { configPath: options.configPath }),\n })\n\n if (options.json) {\n process.stdout.write(\n `${JSON.stringify(graphToSerializableTree(graph), null, 2)}\\n`,\n )\n return\n }\n\n process.stdout.write(\n `${printDependencyTree(graph, {\n cwd: options.cwd ?? graph.cwd,\n includeExternals: options.includeExternals,\n })}\\n`,\n )\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Unknown error occurred.'\n process.stderr.write(`foresthouse: ${message}\\n`)\n process.exitCode = 1\n }\n}\n\nfunction parseArgs(argv: string[]): CliOptions {\n if (argv.includes('--version') || argv.includes('-v')) {\n process.stdout.write(`${version}\\n`)\n process.exit(0)\n }\n\n if (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) {\n printHelp()\n process.exit(0)\n }\n\n let entryFile: string | undefined\n let cwd: string | undefined\n let configPath: string | undefined\n let includeExternals = false\n let json = false\n let react: ReactUsageFilter | undefined\n\n for (let index = 0; index < argv.length; index += 1) {\n const argument = argv[index]\n if (argument === undefined) {\n continue\n }\n\n if (argument === '--cwd') {\n cwd = readOptionValue(argv, index, '--cwd')\n index += 1\n continue\n }\n\n if (argument === '--config') {\n configPath = readOptionValue(argv, index, '--config')\n index += 1\n continue\n }\n\n if (argument === '--include-externals') {\n includeExternals = true\n continue\n }\n\n if (argument === '--json') {\n json = true\n continue\n }\n\n if (argument === '--react') {\n react = 'all'\n continue\n }\n\n if (argument.startsWith('--react=')) {\n const value = argument.slice('--react='.length)\n if (value === 'component' || value === 'hook') {\n react = value\n continue\n }\n\n throw new Error(`Unknown React mode: ${value}`)\n }\n\n if (argument.startsWith('-')) {\n throw new Error(`Unknown option: ${argument}`)\n }\n\n if (entryFile !== undefined) {\n throw new Error('Only one entry file can be provided.')\n }\n\n entryFile = argument\n }\n\n if (entryFile === undefined) {\n throw new Error('An entry file is required.')\n }\n\n return {\n entryFile,\n cwd,\n configPath,\n includeExternals,\n json,\n react,\n }\n}\n\nfunction readOptionValue(\n argv: string[],\n index: number,\n optionName: string,\n): string {\n const value = argv[index + 1]\n if (value === undefined || value.startsWith('-')) {\n throw new Error(`Missing value for ${optionName}`)\n }\n\n return value\n}\n\nfunction printHelp(): void {\n process.stdout.write(`foresthouse v${version}\n\nUsage:\n foresthouse <entry-file> [options]\n\nOptions:\n --cwd <path> Working directory used for relative paths.\n --config <path> Explicit tsconfig.json or jsconfig.json path.\n --include-externals Include packages and Node built-ins in the tree.\n --react[=component|hook] Print a React usage tree instead of the import tree.\n --json Print the dependency tree as JSON.\n -v, --version Show the current version.\n -h, --help Show this help message.\n`)\n}\n\nmain()\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":["process"],"sources":["../src/commands/base.ts","../src/commands/deps.ts","../src/commands/import.ts","../src/app/react-entry-files.ts","../src/commands/react.ts","../src/app/run.ts","../src/app/cli.ts","../src/cli.ts"],"sourcesContent":["import type { CliOptions } from '../app/args.js'\nimport type { AnalyzeOptions } from '../types/analyze-options.js'\n\nexport abstract class BaseCommand<\n TGraph,\n TOptions extends CliOptions = CliOptions,\n> {\n constructor(protected readonly options: TOptions) {}\n\n run(): void {\n const graph = this.analyze()\n\n if (this.isJsonMode()) {\n process.stdout.write(\n `${JSON.stringify(this.serialize(graph), null, 2)}\\n`,\n )\n return\n }\n\n process.stdout.write(`${this.render(graph)}\\n`)\n }\n\n protected isJsonMode(): boolean {\n return this.options.json\n }\n\n protected getAnalyzeOptions(): AnalyzeOptions {\n return {\n ...(this.options.cwd === undefined ? {} : { cwd: this.options.cwd }),\n ...(this.options.configPath === undefined\n ? {}\n : { configPath: this.options.configPath }),\n expandWorkspaces: this.options.expandWorkspaces,\n projectOnly: this.options.projectOnly,\n }\n }\n\n protected abstract analyze(): TGraph\n protected abstract serialize(graph: TGraph): object\n protected abstract render(graph: TGraph): string\n}\n","import { analyzePackageDependencyDiff } from '../analyzers/deps/diff.js'\nimport { analyzePackageDependencies } from '../analyzers/deps/index.js'\nimport type { DepsCliOptions } from '../app/args.js'\nimport {\n printPackageDependencyDiffTree,\n printPackageDependencyTree,\n} from '../output/ascii/deps.js'\nimport {\n diffGraphToSerializablePackageTree,\n graphToSerializablePackageTree,\n} from '../output/json/deps.js'\nimport type { PackageDependencyDiffGraph } from '../types/package-dependency-diff-graph.js'\nimport type { PackageDependencyGraph } from '../types/package-dependency-graph.js'\nimport { BaseCommand } from './base.js'\n\nexport class DepsCommand extends BaseCommand<\n PackageDependencyGraph | PackageDependencyDiffGraph,\n DepsCliOptions\n> {\n protected analyze(): PackageDependencyGraph | PackageDependencyDiffGraph {\n if (this.options.diff !== undefined) {\n return analyzePackageDependencyDiff(\n this.options.directory,\n this.options.diff,\n )\n }\n\n return analyzePackageDependencies(this.options.directory)\n }\n\n protected serialize(\n graph: PackageDependencyGraph | PackageDependencyDiffGraph,\n ): object {\n if (isPackageDependencyDiffGraph(graph)) {\n return diffGraphToSerializablePackageTree(graph)\n }\n\n return graphToSerializablePackageTree(graph)\n }\n\n protected render(\n graph: PackageDependencyGraph | PackageDependencyDiffGraph,\n ): string {\n if (isPackageDependencyDiffGraph(graph)) {\n return printPackageDependencyDiffTree(graph)\n }\n\n return printPackageDependencyTree(graph)\n }\n}\n\nfunction isPackageDependencyDiffGraph(\n graph: PackageDependencyGraph | PackageDependencyDiffGraph,\n): graph is PackageDependencyDiffGraph {\n return 'root' in graph\n}\n","import { analyzeDependencies } from '../analyzers/import/index.js'\nimport type { ImportCliOptions } from '../app/args.js'\nimport { printDependencyTree } from '../output/ascii/import.js'\nimport { graphToSerializableTree } from '../output/json/import.js'\nimport type { DependencyGraph } from '../types/dependency-graph.js'\nimport { BaseCommand } from './base.js'\n\nexport class ImportCommand extends BaseCommand<\n DependencyGraph,\n ImportCliOptions\n> {\n protected analyze(): DependencyGraph {\n return analyzeDependencies(this.options.entryFile, this.getAnalyzeOptions())\n }\n\n protected serialize(graph: DependencyGraph): object {\n return graphToSerializableTree(graph, {\n omitUnused: this.options.omitUnused,\n })\n }\n\n protected render(graph: DependencyGraph): string {\n return printDependencyTree(graph, {\n cwd: this.options.cwd ?? graph.cwd,\n includeExternals: this.options.includeExternals,\n omitUnused: this.options.omitUnused,\n })\n }\n}\n","import fs from 'node:fs'\nimport path from 'node:path'\nimport { isSourceCodeFile } from '../utils/is-source-code-file.js'\nimport type { ReactCliOptions } from './args.js'\n\nconst NEXT_JS_ENTRY_ROOTS = [\n 'pages',\n 'app',\n path.join('src', 'pages'),\n path.join('src', 'app'),\n]\n\nexport function resolveReactEntryFiles(options: ReactCliOptions): string[] {\n if (options.entryFile !== undefined) {\n return [options.entryFile]\n }\n\n if (!options.nextjs) {\n throw new Error(\n 'Missing React entry file. Use `foresthouse react <entry-file>` or `foresthouse react --nextjs`.',\n )\n }\n\n return discoverNextJsPageEntries(options.cwd)\n}\n\nexport function discoverNextJsPageEntries(cwd?: string): string[] {\n const effectiveCwd = path.resolve(cwd ?? process.cwd())\n const entries = new Set<string>()\n\n collectPagesRouterEntries(\n path.join(effectiveCwd, 'pages'),\n effectiveCwd,\n entries,\n )\n collectAppRouterEntries(path.join(effectiveCwd, 'app'), effectiveCwd, entries)\n collectPagesRouterEntries(\n path.join(effectiveCwd, 'src', 'pages'),\n effectiveCwd,\n entries,\n )\n collectAppRouterEntries(\n path.join(effectiveCwd, 'src', 'app'),\n effectiveCwd,\n entries,\n )\n\n const resolvedEntries = [...entries].sort()\n if (resolvedEntries.length > 0) {\n return resolvedEntries\n }\n\n const searchedRoots = NEXT_JS_ENTRY_ROOTS.map((root) => `\\`${root}/\\``).join(\n ', ',\n )\n throw new Error(\n `No Next.js page entries found. Searched ${searchedRoots} relative to ${effectiveCwd}.`,\n )\n}\n\nfunction collectPagesRouterEntries(\n rootDirectory: string,\n cwd: string,\n entries: Set<string>,\n): void {\n walkDirectory(rootDirectory, (filePath, relativePath) => {\n if (!isSourceCodeFile(filePath) || filePath.endsWith('.d.ts')) {\n return\n }\n\n const segments = relativePath.split(path.sep)\n if (segments[0] === 'api') {\n return\n }\n\n const baseName = path.basename(filePath)\n if (baseName.startsWith('_')) {\n return\n }\n\n entries.add(path.relative(cwd, filePath))\n })\n}\n\nfunction collectAppRouterEntries(\n rootDirectory: string,\n cwd: string,\n entries: Set<string>,\n): void {\n walkDirectory(rootDirectory, (filePath) => {\n if (!isSourceCodeFile(filePath) || filePath.endsWith('.d.ts')) {\n return\n }\n\n const extension = path.extname(filePath)\n const baseName = path.basename(filePath, extension)\n if (baseName !== 'page') {\n return\n }\n\n entries.add(path.relative(cwd, filePath))\n })\n}\n\nfunction walkDirectory(\n rootDirectory: string,\n visitor: (filePath: string, relativePath: string) => void,\n): void {\n if (!fs.existsSync(rootDirectory)) {\n return\n }\n\n const stack = [rootDirectory]\n\n while (stack.length > 0) {\n const directory = stack.pop()\n if (directory === undefined) {\n continue\n }\n\n const directoryEntries = fs\n .readdirSync(directory, { withFileTypes: true })\n .sort((left, right) => left.name.localeCompare(right.name))\n\n for (let index = directoryEntries.length - 1; index >= 0; index -= 1) {\n const entry = directoryEntries[index]\n if (entry === undefined) {\n continue\n }\n\n const entryPath = path.join(directory, entry.name)\n if (entry.isDirectory()) {\n stack.push(entryPath)\n continue\n }\n\n if (!entry.isFile()) {\n continue\n }\n\n visitor(entryPath, path.relative(rootDirectory, entryPath))\n }\n }\n}\n","import { analyzeReactUsage } from '../analyzers/react/index.js'\nimport type { ReactCliOptions } from '../app/args.js'\nimport { resolveReactEntryFiles } from '../app/react-entry-files.js'\nimport { printReactUsageTree } from '../output/ascii/react.js'\nimport { graphToSerializableReactTree } from '../output/json/react.js'\nimport type { AnalyzeOptions } from '../types/analyze-options.js'\nimport type { ReactUsageFilter } from '../types/react-usage-filter.js'\nimport type { ReactUsageGraph } from '../types/react-usage-graph.js'\nimport { BaseCommand } from './base.js'\n\nexport class ReactCommand extends BaseCommand<\n ReactUsageGraph,\n ReactCliOptions\n> {\n protected analyze(): ReactUsageGraph {\n return analyzeReactUsage(\n resolveReactEntryFiles(this.options),\n this.getAnalyzeOptions(),\n )\n }\n\n protected serialize(graph: ReactUsageGraph): object {\n return graphToSerializableReactTree(graph, {\n filter: this.getFilter(),\n })\n }\n\n protected render(graph: ReactUsageGraph): string {\n return printReactUsageTree(graph, {\n cwd: this.options.cwd ?? graph.cwd,\n filter: this.getFilter(),\n })\n }\n\n protected getAnalyzeOptions(): AnalyzeOptions {\n return {\n ...super.getAnalyzeOptions(),\n includeBuiltins: this.options.includeBuiltins,\n }\n }\n\n private getFilter(): ReactUsageFilter {\n return this.options.filter\n }\n}\n","import { DepsCommand } from '../commands/deps.js'\nimport { ImportCommand } from '../commands/import.js'\nimport { ReactCommand } from '../commands/react.js'\nimport type { CliOptions } from './args.js'\n\nexport function runCli(options: CliOptions): void {\n new CliApplication(options).run()\n}\n\nclass CliApplication {\n constructor(private readonly options: CliOptions) {}\n\n run(): void {\n this.createCommand().run()\n }\n\n private createCommand(): {\n run(): void\n } {\n if (this.options.command === 'deps') {\n return new DepsCommand(this.options)\n }\n\n if (this.options.command === 'react') {\n return new ReactCommand(this.options)\n }\n\n return new ImportCommand(this.options)\n }\n}\n","import process from 'node:process'\n\nimport { cac } from 'cac'\n\nimport type { ReactUsageFilter } from '../types/react-usage-filter.js'\nimport type {\n DepsCliOptions,\n ImportCliOptions,\n ReactCliOptions,\n} from './args.js'\nimport { runCli } from './run.js'\n\nexport function main(version: string, argv = process.argv.slice(2)): void {\n new CliMain(version, argv).run()\n}\n\nclass CliMain {\n constructor(\n private readonly version: string,\n private readonly argv: string[],\n ) {}\n\n run(): void {\n try {\n const cli = this.createCli()\n\n if (this.argv.length === 0) {\n cli.outputHelp()\n return\n }\n\n this.validateCommandSyntax()\n cli.parse(this.toProcessArgv())\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Unknown error occurred.'\n process.stderr.write(`foresthouse: ${message}\\n`)\n process.exitCode = 1\n }\n }\n\n private createCli() {\n const cli = cac('foresthouse')\n\n cli\n .command(\n 'deps <directory>',\n 'Analyze package.json dependencies from a package directory.',\n )\n .usage('deps <directory> [options]')\n .option(\n '--diff <git-ref-or-range>',\n 'Show only dependency-tree changes relative to a Git revision or range.',\n )\n .option('--json', 'Print the package tree as JSON.')\n .action((directory: string, rawOptions: ParsedDepsCliOptions) => {\n runCli(normalizeDepsCliOptions(directory, rawOptions))\n })\n\n cli\n .command(\n 'import [entry-file]',\n 'Analyze an entry file and print its dependency tree.',\n )\n .usage('import <entry-file> [options]')\n .option('--entry <path>', 'Entry file to analyze.')\n .option('--cwd <path>', 'Working directory used for relative paths.')\n .option(\n '--config <path>',\n 'Explicit tsconfig.json or jsconfig.json path.',\n )\n .option(\n '--include-externals',\n 'Include packages and Node built-ins in the tree.',\n )\n .option(\n '--no-workspaces',\n 'Do not expand sibling workspace packages into source subtrees.',\n )\n .option(\n '--project-only',\n 'Restrict traversal to the active tsconfig.json or jsconfig.json project.',\n )\n .option('--no-unused', 'Omit imports that are never referenced.')\n .option('--json', 'Print the dependency tree as JSON.')\n .action(\n (entryFile: string | undefined, rawOptions: ParsedImportCliOptions) => {\n runCli(normalizeImportCliOptions(entryFile, rawOptions))\n },\n )\n\n cli\n .command('react [entry-file]', 'Analyze React usage from an entry file.')\n .usage('react [entry-file] [options]')\n .option('--cwd <path>', 'Working directory used for relative paths.')\n .option(\n '--config <path>',\n 'Explicit tsconfig.json or jsconfig.json path.',\n )\n .option(\n '--nextjs',\n 'Infer Next.js page entries from app/ and pages/ when no entry is provided.',\n )\n .option(\n '--filter <mode>',\n 'Limit output to `component`, `hook`, or `builtin` usages.',\n )\n .option('--builtin', 'Include built-in HTML nodes in the React tree.')\n .option(\n '--no-workspaces',\n 'Do not expand sibling workspace packages into source subtrees.',\n )\n .option(\n '--project-only',\n 'Restrict traversal to the active tsconfig.json or jsconfig.json project.',\n )\n .option('--json', 'Print the React usage tree as JSON.')\n .action(\n (entryFile: string | undefined, rawOptions: ParsedReactCliOptions) => {\n runCli(normalizeReactCliOptions(entryFile, rawOptions))\n },\n )\n\n cli.help()\n cli.version(this.version)\n\n return cli\n }\n\n private validateCommandSyntax(): void {\n if (this.argv.length === 0) {\n return\n }\n\n const firstArgument = this.argv[0]\n\n if (firstArgument === undefined) {\n return\n }\n\n if (firstArgument === '--react' || firstArgument.startsWith('--react=')) {\n throw new Error('Unknown option `--react`')\n }\n\n if (firstArgument.startsWith('-')) {\n return\n }\n\n if (\n firstArgument === 'deps' ||\n firstArgument === 'import' ||\n firstArgument === 'react'\n ) {\n return\n }\n\n throw new Error(`Unknown command \\`${firstArgument}\\``)\n }\n\n private toProcessArgv(): string[] {\n return ['node', 'foresthouse', ...this.argv]\n }\n}\n\ninterface ParsedBaseCliOptions {\n readonly cwd?: string\n readonly config?: string\n readonly workspaces?: boolean\n readonly projectOnly?: boolean\n readonly json?: boolean\n}\n\ninterface ParsedDepsCliOptions {\n readonly diff?: string\n readonly json?: boolean\n}\n\ninterface ParsedImportCliOptions extends ParsedBaseCliOptions {\n readonly entry?: string\n readonly includeExternals?: boolean\n readonly unused?: boolean\n}\n\ninterface ParsedReactCliOptions extends ParsedBaseCliOptions {\n readonly filter?: string\n readonly nextjs?: boolean\n readonly builtin?: boolean\n}\n\nfunction normalizeDepsCliOptions(\n directory: string,\n options: ParsedDepsCliOptions,\n): DepsCliOptions {\n return {\n command: 'deps',\n directory,\n diff: options.diff,\n cwd: undefined,\n configPath: undefined,\n expandWorkspaces: true,\n projectOnly: false,\n json: options.json === true,\n }\n}\n\nfunction normalizeImportCliOptions(\n entryFile: string | undefined,\n options: ParsedImportCliOptions,\n): ImportCliOptions {\n return {\n command: 'import',\n entryFile: resolveImportEntryFile(entryFile, options.entry),\n cwd: options.cwd,\n configPath: options.config,\n expandWorkspaces: options.workspaces !== false,\n projectOnly: options.projectOnly === true,\n includeExternals: options.includeExternals === true,\n omitUnused: options.unused === false,\n json: options.json === true,\n }\n}\n\nfunction normalizeReactCliOptions(\n entryFile: string | undefined,\n options: ParsedReactCliOptions,\n): ReactCliOptions {\n return {\n command: 'react',\n entryFile: resolveReactEntryFile(entryFile, options.nextjs),\n cwd: options.cwd,\n configPath: options.config,\n expandWorkspaces: options.workspaces !== false,\n projectOnly: options.projectOnly === true,\n json: options.json === true,\n filter: normalizeReactFilter(options.filter),\n nextjs: options.nextjs === true,\n includeBuiltins: options.builtin === true,\n }\n}\n\nfunction resolveImportEntryFile(\n positionalEntryFile: string | undefined,\n optionEntryFile: string | undefined,\n): string {\n if (\n positionalEntryFile !== undefined &&\n optionEntryFile !== undefined &&\n positionalEntryFile !== optionEntryFile\n ) {\n throw new Error(\n 'Provide the import entry only once, either as `foresthouse import <entry-file>` or `foresthouse import --entry <path>`.',\n )\n }\n\n const resolvedEntryFile = positionalEntryFile ?? optionEntryFile\n\n if (resolvedEntryFile === undefined) {\n throw new Error(\n 'Missing import entry file. Use `foresthouse import <entry-file>` or `foresthouse import --entry <path>`.',\n )\n }\n\n return resolvedEntryFile\n}\n\nfunction normalizeReactFilter(\n filter: ParsedReactCliOptions['filter'],\n): ReactUsageFilter {\n if (filter === undefined) {\n return 'all'\n }\n\n if (filter === 'component' || filter === 'hook' || filter === 'builtin') {\n return filter\n }\n\n throw new Error(`Unknown React filter: ${filter}`)\n}\n\nfunction resolveReactEntryFile(\n entryFile: string | undefined,\n nextjs: boolean | undefined,\n): string | undefined {\n if (entryFile !== undefined) {\n return entryFile\n }\n\n if (nextjs === true) {\n return undefined\n }\n\n throw new Error(\n 'Missing React entry file. Use `foresthouse react <entry-file>` or `foresthouse react --nextjs`.',\n )\n}\n","#!/usr/bin/env node\n\nimport { createRequire } from 'node:module'\n\nimport { main } from './app/cli.js'\n\nconst require = createRequire(import.meta.url)\nconst { version } = require('../package.json') as { version: string }\n\nmain(version)\n"],"mappings":"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"}