foresthouse 1.0.0-dev.9 → 1.0.0
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 +111 -42
- package/dist/cli.mjs +126 -9
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +102 -4
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/react-Fqrly1Yw.mjs +2500 -0
- package/dist/react-Fqrly1Yw.mjs.map +1 -0
- package/package.json +7 -2
- package/dist/react-BHPy_fw5.mjs +0 -1130
- package/dist/react-BHPy_fw5.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,35 +1,65 @@
|
|
|
1
|
-
# foresthouse
|
|
1
|
+
# foresthouse 🌲🏠
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/foresthouse)
|
|
4
|
+
[](https://codecov.io/gh/async3619/foresthouse)
|
|
5
|
+
[](https://www.npmjs.com/package/foresthouse)
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
`foresthouse` is a modern TypeScript-first Node.js CLI that can print source import trees, React usage trees, and package-manifest dependency trees.
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g foresthouse
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
You can also run it without a global install:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx foresthouse --help
|
|
19
|
+
pnpm dlx foresthouse --help
|
|
20
|
+
yarn dlx foresthouse --help
|
|
21
|
+
bunx foresthouse --help
|
|
22
|
+
```
|
|
15
23
|
|
|
16
24
|
## What it does
|
|
17
25
|
|
|
18
|
-
-
|
|
26
|
+
- Analyzes source import trees from JavaScript and TypeScript entry files (`.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs`, `.mts`, `.cts`)
|
|
19
27
|
- Resolves local imports, re-exports, `require()`, and string-literal dynamic `import()`
|
|
20
28
|
- Honors the nearest `tsconfig.json` or `jsconfig.json`, including `baseUrl` and `paths`
|
|
21
|
-
-
|
|
29
|
+
- Expands sibling workspace packages by default, including their own `tsconfig` alias rules
|
|
30
|
+
- Analyzes React component and hook usage trees from explicit entry files or inferred Next.js app and pages routes
|
|
31
|
+
- Reads `package.json` manifests to show package-level dependency trees for single packages and monorepos
|
|
32
|
+
- Shows dependency-tree changes relative to a Git ref or range with `foresthouse deps --diff`
|
|
33
|
+
- Prints an ASCII tree by default, or JSON with `--json`
|
|
22
34
|
- Colorizes ASCII output automatically when the terminal supports ANSI colors
|
|
23
35
|
|
|
24
36
|
## Usage
|
|
25
37
|
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
38
|
+
```text
|
|
39
|
+
Usage:
|
|
40
|
+
$ foresthouse <command> [options]
|
|
41
|
+
|
|
42
|
+
Commands:
|
|
43
|
+
deps <directory> Analyze package.json dependencies from a package directory.
|
|
44
|
+
import [entry-file] Analyze an entry file and print its dependency tree.
|
|
45
|
+
react [entry-file] Analyze React usage from an entry file.
|
|
46
|
+
|
|
47
|
+
For more info, run any command with the `--help` flag:
|
|
48
|
+
$ foresthouse deps --help
|
|
49
|
+
$ foresthouse import --help
|
|
50
|
+
$ foresthouse react --help
|
|
51
|
+
|
|
52
|
+
Options:
|
|
53
|
+
-h, --help Display this message
|
|
54
|
+
-v, --version Display version number
|
|
31
55
|
```
|
|
32
56
|
|
|
57
|
+
### Commands
|
|
58
|
+
|
|
59
|
+
- `foresthouse import <entry-file>`: analyzes a JavaScript or TypeScript entry file and prints a dependency tree by following imports, re-exports, `require()`, and dynamic `import()`.
|
|
60
|
+
- `foresthouse react [entry-file]`: analyzes React component, hook, and render relationships and prints a React usage tree. It also supports automatic Next.js entry discovery with `--nextjs`.
|
|
61
|
+
- `foresthouse deps <directory>`: reads `package.json` manifests and workspace structure from a package directory and prints a package dependency tree. Use `--diff` to show only changes relative to a Git ref or range.
|
|
62
|
+
|
|
33
63
|
### Example
|
|
34
64
|
|
|
35
65
|
```bash
|
|
@@ -47,40 +77,79 @@ src/main.ts
|
|
|
47
77
|
│ └─ [dynamic] src/widgets/chart.ts
|
|
48
78
|
```
|
|
49
79
|
|
|
50
|
-
###
|
|
80
|
+
### `foresthouse import --help`
|
|
51
81
|
|
|
52
|
-
`
|
|
82
|
+
Analyzes a source dependency tree from a single entry file. You can provide the entry with either a positional argument or `--entry`, and use `--json` for machine-readable output.
|
|
53
83
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
84
|
+
```text
|
|
85
|
+
Usage:
|
|
86
|
+
$ foresthouse import <entry-file> [options]
|
|
87
|
+
|
|
88
|
+
Options:
|
|
89
|
+
--entry <path> Entry file to analyze.
|
|
90
|
+
--cwd <path> Working directory used for relative paths.
|
|
91
|
+
--config <path> Explicit tsconfig.json or jsconfig.json path.
|
|
92
|
+
--include-externals Include packages and Node built-ins in the tree.
|
|
93
|
+
--no-workspaces Do not expand sibling workspace packages into source subtrees. (default: true)
|
|
94
|
+
--project-only Restrict traversal to the active tsconfig.json or jsconfig.json project.
|
|
95
|
+
--no-unused Omit imports that are never referenced. (default: true)
|
|
96
|
+
--json Print the dependency tree as JSON.
|
|
97
|
+
-h, --help Display this message
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### `foresthouse react --help`
|
|
61
101
|
|
|
62
|
-
`
|
|
102
|
+
Analyzes React component and hook usage relationships. You can provide an entry file directly, or use `--nextjs` to discover Next.js route entries automatically.
|
|
63
103
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
104
|
+
```text
|
|
105
|
+
Usage:
|
|
106
|
+
$ foresthouse react [entry-file] [options]
|
|
107
|
+
|
|
108
|
+
Options:
|
|
109
|
+
--cwd <path> Working directory used for relative paths.
|
|
110
|
+
--config <path> Explicit tsconfig.json or jsconfig.json path.
|
|
111
|
+
--nextjs Infer Next.js page entries from app/ and pages/ when no entry is provided.
|
|
112
|
+
--filter <mode> Limit output to `component`, `hook`, or `builtin` usages.
|
|
113
|
+
--builtin Include built-in HTML nodes in the React tree.
|
|
114
|
+
--no-workspaces Do not expand sibling workspace packages into source subtrees. (default: true)
|
|
115
|
+
--project-only Restrict traversal to the active tsconfig.json or jsconfig.json project.
|
|
116
|
+
--json Print the React usage tree as JSON.
|
|
117
|
+
-h, --help Display this message
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### `foresthouse deps --help`
|
|
121
|
+
|
|
122
|
+
Analyzes package-level dependency trees for both single-package projects and monorepos. Use `--diff` to show only dependency changes relative to a Git ref or range.
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
Usage:
|
|
126
|
+
$ foresthouse deps <directory> [options]
|
|
127
|
+
|
|
128
|
+
Options:
|
|
129
|
+
--diff <git-ref-or-range> Show only dependency-tree changes relative to a Git revision or range.
|
|
130
|
+
--json Print the package tree as JSON.
|
|
131
|
+
-h, --help Display this message
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Common examples:
|
|
135
|
+
|
|
136
|
+
- `foresthouse import src/main.ts`
|
|
137
|
+
- `foresthouse import --entry src/main.ts --cwd test/fixtures/basic`
|
|
138
|
+
- `foresthouse react src/App.tsx`
|
|
139
|
+
- `foresthouse react --nextjs --cwd .`
|
|
140
|
+
- `foresthouse deps .`
|
|
141
|
+
- `foresthouse deps ./packages/a`
|
|
142
|
+
- `foresthouse deps . --diff origin/dev...HEAD`
|
|
69
143
|
|
|
70
144
|
## Development
|
|
71
145
|
|
|
72
146
|
```bash
|
|
73
147
|
corepack enable
|
|
74
148
|
pnpm install
|
|
149
|
+
pnpm run test:unit
|
|
150
|
+
pnpm run test:e2e
|
|
75
151
|
pnpm run check
|
|
76
152
|
```
|
|
77
153
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
- `dev` is the pre-release branch and publishes `-dev.N` builds through `semantic-release`
|
|
81
|
-
- `main` is the stable release branch
|
|
82
|
-
- every code change starts from a GitHub issue and lands through a pull request
|
|
83
|
-
- all commits must follow Conventional Commits
|
|
84
|
-
- Biome is the only formatter and linter in this repository
|
|
85
|
-
- CI runs lint, typecheck, build, and release automation
|
|
86
|
-
- npm publishing is configured through `semantic-release` with `NPM_TOKEN` and GitHub Actions provenance
|
|
154
|
+
- Unit tests live next to source files as `src/**/*.spec.ts`.
|
|
155
|
+
- End-to-end command tests live under `e2e/`.
|
package/dist/cli.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { c as analyzeReactUsage, i as
|
|
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-Fqrly1Yw.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
4
6
|
import process$1 from "node:process";
|
|
5
7
|
import { cac } from "cac";
|
|
6
8
|
//#region src/commands/base.ts
|
|
@@ -22,11 +24,32 @@ var BaseCommand = class {
|
|
|
22
24
|
getAnalyzeOptions() {
|
|
23
25
|
return {
|
|
24
26
|
...this.options.cwd === void 0 ? {} : { cwd: this.options.cwd },
|
|
25
|
-
...this.options.configPath === void 0 ? {} : { configPath: this.options.configPath }
|
|
27
|
+
...this.options.configPath === void 0 ? {} : { configPath: this.options.configPath },
|
|
28
|
+
expandWorkspaces: this.options.expandWorkspaces,
|
|
29
|
+
projectOnly: this.options.projectOnly
|
|
26
30
|
};
|
|
27
31
|
}
|
|
28
32
|
};
|
|
29
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
|
|
30
53
|
//#region src/commands/import.ts
|
|
31
54
|
var ImportCommand = class extends BaseCommand {
|
|
32
55
|
analyze() {
|
|
@@ -44,10 +67,71 @@ var ImportCommand = class extends BaseCommand {
|
|
|
44
67
|
}
|
|
45
68
|
};
|
|
46
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);
|
|
81
|
+
}
|
|
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);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!entry.isFile()) continue;
|
|
126
|
+
visitor(entryPath, path.relative(rootDirectory, entryPath));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
47
131
|
//#region src/commands/react.ts
|
|
48
132
|
var ReactCommand = class extends BaseCommand {
|
|
49
133
|
analyze() {
|
|
50
|
-
return analyzeReactUsage(this.options
|
|
134
|
+
return analyzeReactUsage(resolveReactEntryFiles(this.options), this.getAnalyzeOptions());
|
|
51
135
|
}
|
|
52
136
|
serialize(graph) {
|
|
53
137
|
return graphToSerializableReactTree(graph, { filter: this.getFilter() });
|
|
@@ -58,6 +142,12 @@ var ReactCommand = class extends BaseCommand {
|
|
|
58
142
|
filter: this.getFilter()
|
|
59
143
|
});
|
|
60
144
|
}
|
|
145
|
+
getAnalyzeOptions() {
|
|
146
|
+
return {
|
|
147
|
+
...super.getAnalyzeOptions(),
|
|
148
|
+
includeBuiltins: this.options.includeBuiltins
|
|
149
|
+
};
|
|
150
|
+
}
|
|
61
151
|
getFilter() {
|
|
62
152
|
return this.options.filter;
|
|
63
153
|
}
|
|
@@ -75,6 +165,7 @@ var CliApplication = class {
|
|
|
75
165
|
this.createCommand().run();
|
|
76
166
|
}
|
|
77
167
|
createCommand() {
|
|
168
|
+
if (this.options.command === "deps") return new DepsCommand(this.options);
|
|
78
169
|
if (this.options.command === "react") return new ReactCommand(this.options);
|
|
79
170
|
return new ImportCommand(this.options);
|
|
80
171
|
}
|
|
@@ -106,10 +197,13 @@ var CliMain = class {
|
|
|
106
197
|
}
|
|
107
198
|
createCli() {
|
|
108
199
|
const cli = cac("foresthouse");
|
|
109
|
-
cli.command("
|
|
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) => {
|
|
110
204
|
runCli(normalizeImportCliOptions(entryFile, rawOptions));
|
|
111
205
|
});
|
|
112
|
-
cli.command("react
|
|
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) => {
|
|
113
207
|
runCli(normalizeReactCliOptions(entryFile, rawOptions));
|
|
114
208
|
});
|
|
115
209
|
cli.help();
|
|
@@ -122,7 +216,7 @@ var CliMain = class {
|
|
|
122
216
|
if (firstArgument === void 0) return;
|
|
123
217
|
if (firstArgument === "--react" || firstArgument.startsWith("--react=")) throw new Error("Unknown option `--react`");
|
|
124
218
|
if (firstArgument.startsWith("-")) return;
|
|
125
|
-
if (firstArgument === "import" || firstArgument === "react") return;
|
|
219
|
+
if (firstArgument === "deps" || firstArgument === "import" || firstArgument === "react") return;
|
|
126
220
|
throw new Error(`Unknown command \`${firstArgument}\``);
|
|
127
221
|
}
|
|
128
222
|
toProcessArgv() {
|
|
@@ -133,12 +227,26 @@ var CliMain = class {
|
|
|
133
227
|
];
|
|
134
228
|
}
|
|
135
229
|
};
|
|
230
|
+
function normalizeDepsCliOptions(directory, options) {
|
|
231
|
+
return {
|
|
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
|
|
240
|
+
};
|
|
241
|
+
}
|
|
136
242
|
function normalizeImportCliOptions(entryFile, options) {
|
|
137
243
|
return {
|
|
138
244
|
command: "import",
|
|
139
245
|
entryFile: resolveImportEntryFile(entryFile, options.entry),
|
|
140
246
|
cwd: options.cwd,
|
|
141
247
|
configPath: options.config,
|
|
248
|
+
expandWorkspaces: options.workspaces !== false,
|
|
249
|
+
projectOnly: options.projectOnly === true,
|
|
142
250
|
includeExternals: options.includeExternals === true,
|
|
143
251
|
omitUnused: options.unused === false,
|
|
144
252
|
json: options.json === true
|
|
@@ -147,11 +255,15 @@ function normalizeImportCliOptions(entryFile, options) {
|
|
|
147
255
|
function normalizeReactCliOptions(entryFile, options) {
|
|
148
256
|
return {
|
|
149
257
|
command: "react",
|
|
150
|
-
entryFile,
|
|
258
|
+
entryFile: resolveReactEntryFile(entryFile, options.nextjs),
|
|
151
259
|
cwd: options.cwd,
|
|
152
260
|
configPath: options.config,
|
|
261
|
+
expandWorkspaces: options.workspaces !== false,
|
|
262
|
+
projectOnly: options.projectOnly === true,
|
|
153
263
|
json: options.json === true,
|
|
154
|
-
filter: normalizeReactFilter(options.filter)
|
|
264
|
+
filter: normalizeReactFilter(options.filter),
|
|
265
|
+
nextjs: options.nextjs === true,
|
|
266
|
+
includeBuiltins: options.builtin === true
|
|
155
267
|
};
|
|
156
268
|
}
|
|
157
269
|
function resolveImportEntryFile(positionalEntryFile, optionEntryFile) {
|
|
@@ -162,9 +274,14 @@ function resolveImportEntryFile(positionalEntryFile, optionEntryFile) {
|
|
|
162
274
|
}
|
|
163
275
|
function normalizeReactFilter(filter) {
|
|
164
276
|
if (filter === void 0) return "all";
|
|
165
|
-
if (filter === "component" || filter === "hook") return filter;
|
|
277
|
+
if (filter === "component" || filter === "hook" || filter === "builtin") return filter;
|
|
166
278
|
throw new Error(`Unknown React filter: ${filter}`);
|
|
167
279
|
}
|
|
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
|
+
}
|
|
168
285
|
//#endregion
|
|
169
286
|
//#region src/cli.ts
|
|
170
287
|
const { version } = createRequire(import.meta.url)("../package.json");
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":["process"],"sources":["../src/commands/base.ts","../src/commands/import.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 }\n }\n\n protected abstract analyze(): TGraph\n protected abstract serialize(graph: TGraph): object\n protected abstract render(graph: TGraph): string\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 { analyzeReactUsage } from '../analyzers/react/index.js'\nimport type { ReactCliOptions } from '../app/args.js'\nimport { printReactUsageTree } from '../output/ascii/react.js'\nimport { graphToSerializableReactTree } from '../output/json/react.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(this.options.entryFile, this.getAnalyzeOptions())\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 private getFilter(): ReactUsageFilter {\n return this.options.filter\n }\n}\n","import { 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 === '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 { ImportCliOptions, ReactCliOptions } 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 '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('--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 '--filter <mode>',\n 'Limit output to `component` or `hook` usages.',\n )\n .option('--json', 'Print the React usage tree as JSON.')\n .action((entryFile: string, rawOptions: ParsedReactCliOptions) => {\n runCli(normalizeReactCliOptions(entryFile, rawOptions))\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 (firstArgument === 'import' || firstArgument === 'react') {\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 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}\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 includeExternals: options.includeExternals === true,\n omitUnused: options.unused === false,\n json: options.json === true,\n }\n}\n\nfunction normalizeReactCliOptions(\n entryFile: string,\n options: ParsedReactCliOptions,\n): ReactCliOptions {\n return {\n command: 'react',\n entryFile,\n cwd: options.cwd,\n configPath: options.config,\n json: options.json === true,\n filter: normalizeReactFilter(options.filter),\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') {\n return filter\n }\n\n throw new Error(`Unknown React filter: ${filter}`)\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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"}
|
|
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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"}
|