foresthouse 1.0.0-dev.13 → 1.0.0-dev.15

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
@@ -65,9 +65,12 @@ src/main.ts
65
65
  `react` command:
66
66
 
67
67
  - `foresthouse react <path>`: print a React usage tree from an entry file
68
+ - `foresthouse react --nextjs`: infer Next.js page entries from `app/`, `pages/`, `src/app/`, and `src/pages/`
68
69
  - `--cwd <path>`: working directory for resolving the entry file and config
69
70
  - `--config <path>`: use a specific `tsconfig.json` or `jsconfig.json`
70
- - `--filter <component|hook>`: limit the output to a specific React symbol kind
71
+ - `--nextjs`: allow omitting the explicit React entry file and discover Next.js page entries relative to `--cwd` or the current directory
72
+ - `--filter <component|hook|builtin>`: limit the output to a specific React symbol kind
73
+ - `--builtin`: include built-in HTML nodes such as `button` and `div`
71
74
  - `--no-workspaces`: stop at sibling workspace package boundaries instead of expanding them
72
75
  - `--project-only`: restrict traversal to the active `tsconfig.json` or `jsconfig.json` project
73
76
  - `--json`: print a JSON tree instead of ASCII output
package/dist/cli.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { c as analyzeReactUsage, i as printDependencyTree, l as analyzeDependencies, n as graphToSerializableTree, r as printReactUsageTree, t as graphToSerializableReactTree } from "./react-a7ugf8JN.mjs";
2
+ import { c as analyzeReactUsage, i as printDependencyTree, l as analyzeDependencies, n as graphToSerializableTree, r as printReactUsageTree, t as graphToSerializableReactTree, u as isSourceCodeFile } from "./react-B0frvTuH.mjs";
3
3
  import { createRequire } from "node:module";
4
+ import path from "node:path";
5
+ import fs from "node:fs";
4
6
  import process$1 from "node:process";
5
7
  import { cac } from "cac";
6
8
  //#region src/commands/base.ts
@@ -46,10 +48,71 @@ var ImportCommand = class extends BaseCommand {
46
48
  }
47
49
  };
48
50
  //#endregion
51
+ //#region src/app/react-entry-files.ts
52
+ const NEXT_JS_ENTRY_ROOTS = [
53
+ "pages",
54
+ "app",
55
+ path.join("src", "pages"),
56
+ path.join("src", "app")
57
+ ];
58
+ function resolveReactEntryFiles(options) {
59
+ if (options.entryFile !== void 0) return [options.entryFile];
60
+ if (!options.nextjs) throw new Error("Missing React entry file. Use `foresthouse react <entry-file>` or `foresthouse react --nextjs`.");
61
+ return discoverNextJsPageEntries(options.cwd);
62
+ }
63
+ function discoverNextJsPageEntries(cwd) {
64
+ const effectiveCwd = path.resolve(cwd ?? process.cwd());
65
+ const entries = /* @__PURE__ */ new Set();
66
+ collectPagesRouterEntries(path.join(effectiveCwd, "pages"), effectiveCwd, entries);
67
+ collectAppRouterEntries(path.join(effectiveCwd, "app"), effectiveCwd, entries);
68
+ collectPagesRouterEntries(path.join(effectiveCwd, "src", "pages"), effectiveCwd, entries);
69
+ collectAppRouterEntries(path.join(effectiveCwd, "src", "app"), effectiveCwd, entries);
70
+ const resolvedEntries = [...entries].sort();
71
+ if (resolvedEntries.length > 0) return resolvedEntries;
72
+ const searchedRoots = NEXT_JS_ENTRY_ROOTS.map((root) => `\`${root}/\``).join(", ");
73
+ throw new Error(`No Next.js page entries found. Searched ${searchedRoots} relative to ${effectiveCwd}.`);
74
+ }
75
+ function collectPagesRouterEntries(rootDirectory, cwd, entries) {
76
+ walkDirectory(rootDirectory, (filePath, relativePath) => {
77
+ if (!isSourceCodeFile(filePath) || filePath.endsWith(".d.ts")) return;
78
+ if (relativePath.split(path.sep)[0] === "api") return;
79
+ if (path.basename(filePath).startsWith("_")) return;
80
+ entries.add(path.relative(cwd, filePath));
81
+ });
82
+ }
83
+ function collectAppRouterEntries(rootDirectory, cwd, entries) {
84
+ walkDirectory(rootDirectory, (filePath) => {
85
+ if (!isSourceCodeFile(filePath) || filePath.endsWith(".d.ts")) return;
86
+ const extension = path.extname(filePath);
87
+ if (path.basename(filePath, extension) !== "page") return;
88
+ entries.add(path.relative(cwd, filePath));
89
+ });
90
+ }
91
+ function walkDirectory(rootDirectory, visitor) {
92
+ if (!fs.existsSync(rootDirectory)) return;
93
+ const stack = [rootDirectory];
94
+ while (stack.length > 0) {
95
+ const directory = stack.pop();
96
+ if (directory === void 0) continue;
97
+ const directoryEntries = fs.readdirSync(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
98
+ for (let index = directoryEntries.length - 1; index >= 0; index -= 1) {
99
+ const entry = directoryEntries[index];
100
+ if (entry === void 0) continue;
101
+ const entryPath = path.join(directory, entry.name);
102
+ if (entry.isDirectory()) {
103
+ stack.push(entryPath);
104
+ continue;
105
+ }
106
+ if (!entry.isFile()) continue;
107
+ visitor(entryPath, path.relative(rootDirectory, entryPath));
108
+ }
109
+ }
110
+ }
111
+ //#endregion
49
112
  //#region src/commands/react.ts
50
113
  var ReactCommand = class extends BaseCommand {
51
114
  analyze() {
52
- return analyzeReactUsage(this.options.entryFile, this.getAnalyzeOptions());
115
+ return analyzeReactUsage(resolveReactEntryFiles(this.options), this.getAnalyzeOptions());
53
116
  }
54
117
  serialize(graph) {
55
118
  return graphToSerializableReactTree(graph, { filter: this.getFilter() });
@@ -60,6 +123,12 @@ var ReactCommand = class extends BaseCommand {
60
123
  filter: this.getFilter()
61
124
  });
62
125
  }
126
+ getAnalyzeOptions() {
127
+ return {
128
+ ...super.getAnalyzeOptions(),
129
+ includeBuiltins: this.options.includeBuiltins
130
+ };
131
+ }
63
132
  getFilter() {
64
133
  return this.options.filter;
65
134
  }
@@ -111,7 +180,7 @@ var CliMain = class {
111
180
  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) => {
112
181
  runCli(normalizeImportCliOptions(entryFile, rawOptions));
113
182
  });
114
- 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("--filter <mode>", "Limit output to `component` or `hook` usages.").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) => {
183
+ 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) => {
115
184
  runCli(normalizeReactCliOptions(entryFile, rawOptions));
116
185
  });
117
186
  cli.help();
@@ -151,13 +220,15 @@ function normalizeImportCliOptions(entryFile, options) {
151
220
  function normalizeReactCliOptions(entryFile, options) {
152
221
  return {
153
222
  command: "react",
154
- entryFile,
223
+ entryFile: resolveReactEntryFile(entryFile, options.nextjs),
155
224
  cwd: options.cwd,
156
225
  configPath: options.config,
157
226
  expandWorkspaces: options.workspaces !== false,
158
227
  projectOnly: options.projectOnly === true,
159
228
  json: options.json === true,
160
- filter: normalizeReactFilter(options.filter)
229
+ filter: normalizeReactFilter(options.filter),
230
+ nextjs: options.nextjs === true,
231
+ includeBuiltins: options.builtin === true
161
232
  };
162
233
  }
163
234
  function resolveImportEntryFile(positionalEntryFile, optionEntryFile) {
@@ -168,9 +239,14 @@ function resolveImportEntryFile(positionalEntryFile, optionEntryFile) {
168
239
  }
169
240
  function normalizeReactFilter(filter) {
170
241
  if (filter === void 0) return "all";
171
- if (filter === "component" || filter === "hook") return filter;
242
+ if (filter === "component" || filter === "hook" || filter === "builtin") return filter;
172
243
  throw new Error(`Unknown React filter: ${filter}`);
173
244
  }
245
+ function resolveReactEntryFile(entryFile, nextjs) {
246
+ if (entryFile !== void 0) return entryFile;
247
+ if (nextjs === true) return;
248
+ throw new Error("Missing React entry file. Use `foresthouse react <entry-file>` or `foresthouse react --nextjs`.");
249
+ }
174
250
  //#endregion
175
251
  //#region src/cli.ts
176
252
  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 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 { 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(\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 '--filter <mode>',\n 'Limit output to `component` or `hook` usages.',\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('--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 workspaces?: boolean\n readonly projectOnly?: boolean\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 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,\n options: ParsedReactCliOptions,\n): ReactCliOptions {\n return {\n command: 'react',\n entryFile,\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 }\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/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 { 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 { 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(\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 (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 workspaces?: boolean\n readonly projectOnly?: boolean\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 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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"}
package/dist/index.d.mts CHANGED
@@ -4,6 +4,7 @@ interface AnalyzeOptions {
4
4
  readonly configPath?: string;
5
5
  readonly expandWorkspaces?: boolean;
6
6
  readonly projectOnly?: boolean;
7
+ readonly includeBuiltins?: boolean;
7
8
  }
8
9
  //#endregion
9
10
  //#region src/types/dependency-kind.d.ts
@@ -55,7 +56,7 @@ interface ReactUsageEntry {
55
56
  }
56
57
  //#endregion
57
58
  //#region src/types/react-symbol-kind.d.ts
58
- type ReactSymbolKind = 'component' | 'hook';
59
+ type ReactSymbolKind = 'component' | 'hook' | 'builtin';
59
60
  //#endregion
60
61
  //#region src/types/react-usage-edge-kind.d.ts
61
62
  type ReactUsageEdgeKind = 'render' | 'hook-call';
@@ -81,12 +82,13 @@ interface ReactUsageNode {
81
82
  interface ReactUsageGraph {
82
83
  readonly cwd: string;
83
84
  readonly entryId: string;
85
+ readonly entryIds: readonly string[];
84
86
  readonly nodes: ReadonlyMap<string, ReactUsageNode>;
85
87
  readonly entries: readonly ReactUsageEntry[];
86
88
  }
87
89
  //#endregion
88
90
  //#region src/analyzers/react/index.d.ts
89
- declare function analyzeReactUsage(entryFile: string, options?: AnalyzeOptions): ReactUsageGraph;
91
+ declare function analyzeReactUsage(entryFile: string | readonly string[], options?: AnalyzeOptions): ReactUsageGraph;
90
92
  //#endregion
91
93
  //#region src/types/react-usage-filter.d.ts
92
94
  type ReactUsageFilter = 'all' | ReactSymbolKind;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/analyze-options.ts","../src/types/dependency-kind.ts","../src/types/reference-kind.ts","../src/types/dependency-edge.ts","../src/types/source-module-node.ts","../src/types/dependency-graph.ts","../src/analyzers/import/index.ts","../src/types/react-usage-location.ts","../src/types/react-usage-entry.ts","../src/types/react-symbol-kind.ts","../src/types/react-usage-edge-kind.ts","../src/types/react-usage-edge.ts","../src/types/react-usage-node.ts","../src/types/react-usage-graph.ts","../src/analyzers/react/index.ts","../src/types/react-usage-filter.ts","../src/analyzers/react/queries.ts","../src/types/color-mode.ts","../src/types/print-tree-options.ts","../src/output/ascii/import.ts","../src/types/print-react-tree-options.ts","../src/output/ascii/react.ts","../src/output/json/import.ts","../src/output/json/react.ts"],"mappings":";UAAiB,cAAA;EAAA,SACN,GAAA;EAAA,SACA,UAAA;EAAA,SACA,gBAAA;EAAA,SACA,WAAA;AAAA;;;KCJC,cAAA;;;KCAA,aAAA;;;UCGK,cAAA;EAAA,SACN,SAAA;EAAA,SACA,aAAA,EAAe,aAAA;EAAA,SACf,UAAA;EAAA,SACA,MAAA;EAAA,SACA,IAAA,EAAM,cAAA;EAAA,SACN,MAAA;EAAA,SACA,QAAA;AAAA;;;UCRM,gBAAA;EAAA,SACN,EAAA;EAAA,SACA,YAAA,WAAuB,cAAA;AAAA;;;UCFjB,eAAA;EAAA,SACN,GAAA;EAAA,SACA,OAAA;EAAA,SACA,KAAA,EAAO,WAAA,SAAoB,gBAAA;EAAA,SAC3B,UAAA;AAAA;;;iBCGK,mBAAA,CACd,SAAA,UACA,OAAA,GAAS,cAAA,GACR,eAAA;;;UCZc,kBAAA;EAAA,SACN,QAAA;EAAA,SACA,IAAA;EAAA,SACA,MAAA;AAAA;;;UCDM,eAAA;EAAA,SACN,MAAA;EAAA,SACA,aAAA;EAAA,SACA,QAAA,EAAU,kBAAA;AAAA;;;KCLT,eAAA;;;KCAA,kBAAA;;;UCEK,cAAA;EAAA,SACN,IAAA,EAAM,kBAAA;EAAA,SACN,MAAA;EAAA,SACA,aAAA;AAAA;;;UCFM,cAAA;EAAA,SACN,EAAA;EAAA,SACA,IAAA;EAAA,SACA,IAAA,EAAM,eAAA;EAAA,SACN,QAAA;EAAA,SACA,WAAA;EAAA,SACA,MAAA,WAAiB,cAAA;AAAA;;;UCNX,eAAA;EAAA,SACN,GAAA;EAAA,SACA,OAAA;EAAA,SACA,KAAA,EAAO,WAAA,SAAoB,cAAA;EAAA,SAC3B,OAAA,WAAkB,eAAA;AAAA;;;iBCcb,iBAAA,CACd,SAAA,UACA,OAAA,GAAS,cAAA,GACR,eAAA;;;KCtBS,gBAAA,WAA2B,eAAA;;;iBCKvB,oBAAA,CACd,KAAA,EAAO,eAAA,EACP,MAAA,GAAQ,gBAAA,GACP,eAAA;AAAA,iBAOa,kBAAA,CACd,KAAA,EAAO,eAAA,EACP,MAAA,GAAQ,gBAAA;AAAA,iBAsCM,iBAAA,CACd,IAAA,EAAM,cAAA,EACN,KAAA,EAAO,eAAA,EACP,MAAA,GAAQ,gBAAA,GACP,cAAA;;;KC7DS,SAAA;;;UCEK,gBAAA;EAAA,SACN,GAAA;EAAA,SACA,gBAAA;EAAA,SACA,UAAA;EAAA,SACA,KAAA,GAAQ,SAAA;AAAA;;;iBCAH,mBAAA,CACd,KAAA,EAAO,eAAA,EACP,OAAA,GAAS,gBAAA;;;UCLM,qBAAA;EAAA,SACN,GAAA;EAAA,SACA,MAAA,GAAS,gBAAA;EAAA,SACT,KAAA,GAAQ,SAAA;AAAA;;;iBCaH,mBAAA,CACd,KAAA,EAAO,eAAA,EACP,OAAA,GAAS,qBAAA;;;iBCjBK,uBAAA,CACd,KAAA,EAAO,eAAA,EACP,OAAA;EAAA,SACW,UAAA;AAAA;;;iBC8BG,4BAAA,CACd,KAAA,EAAO,eAAA,EACP,OAAA;EAAA,SACW,MAAA,GAAS,gBAAA;AAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types/analyze-options.ts","../src/types/dependency-kind.ts","../src/types/reference-kind.ts","../src/types/dependency-edge.ts","../src/types/source-module-node.ts","../src/types/dependency-graph.ts","../src/analyzers/import/index.ts","../src/types/react-usage-location.ts","../src/types/react-usage-entry.ts","../src/types/react-symbol-kind.ts","../src/types/react-usage-edge-kind.ts","../src/types/react-usage-edge.ts","../src/types/react-usage-node.ts","../src/types/react-usage-graph.ts","../src/analyzers/react/index.ts","../src/types/react-usage-filter.ts","../src/analyzers/react/queries.ts","../src/types/color-mode.ts","../src/types/print-tree-options.ts","../src/output/ascii/import.ts","../src/types/print-react-tree-options.ts","../src/output/ascii/react.ts","../src/output/json/import.ts","../src/output/json/react.ts"],"mappings":";UAAiB,cAAA;EAAA,SACN,GAAA;EAAA,SACA,UAAA;EAAA,SACA,gBAAA;EAAA,SACA,WAAA;EAAA,SACA,eAAA;AAAA;;;KCLC,cAAA;;;KCAA,aAAA;;;UCGK,cAAA;EAAA,SACN,SAAA;EAAA,SACA,aAAA,EAAe,aAAA;EAAA,SACf,UAAA;EAAA,SACA,MAAA;EAAA,SACA,IAAA,EAAM,cAAA;EAAA,SACN,MAAA;EAAA,SACA,QAAA;AAAA;;;UCRM,gBAAA;EAAA,SACN,EAAA;EAAA,SACA,YAAA,WAAuB,cAAA;AAAA;;;UCFjB,eAAA;EAAA,SACN,GAAA;EAAA,SACA,OAAA;EAAA,SACA,KAAA,EAAO,WAAA,SAAoB,gBAAA;EAAA,SAC3B,UAAA;AAAA;;;iBCGK,mBAAA,CACd,SAAA,UACA,OAAA,GAAS,cAAA,GACR,eAAA;;;UCZc,kBAAA;EAAA,SACN,QAAA;EAAA,SACA,IAAA;EAAA,SACA,MAAA;AAAA;;;UCDM,eAAA;EAAA,SACN,MAAA;EAAA,SACA,aAAA;EAAA,SACA,QAAA,EAAU,kBAAA;AAAA;;;KCLT,eAAA;;;KCAA,kBAAA;;;UCEK,cAAA;EAAA,SACN,IAAA,EAAM,kBAAA;EAAA,SACN,MAAA;EAAA,SACA,aAAA;AAAA;;;UCFM,cAAA;EAAA,SACN,EAAA;EAAA,SACA,IAAA;EAAA,SACA,IAAA,EAAM,eAAA;EAAA,SACN,QAAA;EAAA,SACA,WAAA;EAAA,SACA,MAAA,WAAiB,cAAA;AAAA;;;UCNX,eAAA;EAAA,SACN,GAAA;EAAA,SACA,OAAA;EAAA,SACA,QAAA;EAAA,SACA,KAAA,EAAO,WAAA,SAAoB,cAAA;EAAA,SAC3B,OAAA,WAAkB,eAAA;AAAA;;;iBCgBb,iBAAA,CACd,SAAA,8BACA,OAAA,GAAS,cAAA,GACR,eAAA;;;KCzBS,gBAAA,WAA2B,eAAA;;;iBCKvB,oBAAA,CACd,KAAA,EAAO,eAAA,EACP,MAAA,GAAQ,gBAAA,GACP,eAAA;AAAA,iBAOa,kBAAA,CACd,KAAA,EAAO,eAAA,EACP,MAAA,GAAQ,gBAAA;AAAA,iBAsCM,iBAAA,CACd,IAAA,EAAM,cAAA,EACN,KAAA,EAAO,eAAA,EACP,MAAA,GAAQ,gBAAA,GACP,cAAA;;;KC7DS,SAAA;;;UCEK,gBAAA;EAAA,SACN,GAAA;EAAA,SACA,gBAAA;EAAA,SACA,UAAA;EAAA,SACA,KAAA,GAAQ,SAAA;AAAA;;;iBCAH,mBAAA,CACd,KAAA,EAAO,eAAA,EACP,OAAA,GAAS,gBAAA;;;UCLM,qBAAA;EAAA,SACN,GAAA;EAAA,SACA,MAAA,GAAS,gBAAA;EAAA,SACT,KAAA,GAAQ,SAAA;AAAA;;;iBCaH,mBAAA,CACd,KAAA,EAAO,eAAA,EACP,OAAA,GAAS,qBAAA;;;iBCjBK,uBAAA,CACd,KAAA,EAAO,eAAA,EACP,OAAA;EAAA,SACW,UAAA;AAAA;;;iBC8BG,4BAAA,CACd,KAAA,EAAO,eAAA,EACP,OAAA;EAAA,SACW,MAAA,GAAS,gBAAA;AAAA"}
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { a as getFilteredUsages, c as analyzeReactUsage, i as printDependencyTree, l as analyzeDependencies, n as graphToSerializableTree, o as getReactUsageEntries, r as printReactUsageTree, s as getReactUsageRoots, t as graphToSerializableReactTree } from "./react-a7ugf8JN.mjs";
1
+ import { a as getFilteredUsages, c as analyzeReactUsage, i as printDependencyTree, l as analyzeDependencies, n as graphToSerializableTree, o as getReactUsageEntries, r as printReactUsageTree, s as getReactUsageRoots, t as graphToSerializableReactTree } from "./react-B0frvTuH.mjs";
2
2
  export { analyzeDependencies, analyzeReactUsage, getFilteredUsages, getReactUsageEntries, getReactUsageRoots, graphToSerializableReactTree, graphToSerializableTree, printDependencyTree, printReactUsageTree };
@@ -617,6 +617,10 @@ function getComponentReferenceName(node) {
617
617
  const name = getJsxName(node.openingElement.name);
618
618
  return name !== void 0 && isComponentName(name) ? name : void 0;
619
619
  }
620
+ function getBuiltinReferenceName(node) {
621
+ const name = getJsxName(node.openingElement.name);
622
+ return name !== void 0 && isIntrinsicElementName(name) ? name : void 0;
623
+ }
620
624
  function getHookReferenceName(node) {
621
625
  const calleeName = getIdentifierName(node.callee);
622
626
  return calleeName !== void 0 && isHookName(calleeName) ? calleeName : void 0;
@@ -633,6 +637,9 @@ function isHookName(name) {
633
637
  function isComponentName(name) {
634
638
  return /^[A-Z]/.test(name);
635
639
  }
640
+ function isIntrinsicElementName(name) {
641
+ return /^[a-z]/.test(name);
642
+ }
636
643
  function returnsReactElement(declaration) {
637
644
  if (declaration.type === "ArrowFunctionExpression" && declaration.expression) return containsReactElementLikeExpression(declaration.body);
638
645
  const body = declaration.body;
@@ -668,17 +675,17 @@ function unwrapExpression(expression) {
668
675
  }
669
676
  //#endregion
670
677
  //#region src/analyzers/react/entries.ts
671
- function collectEntryUsages(program, filePath, sourceText) {
678
+ function collectEntryUsages(program, filePath, sourceText, includeBuiltins) {
672
679
  const entries = /* @__PURE__ */ new Map();
673
680
  program.body.forEach((statement) => {
674
- collectStatementEntryUsages(statement, filePath, sourceText, entries);
681
+ collectStatementEntryUsages(statement, filePath, sourceText, entries, includeBuiltins);
675
682
  });
676
683
  return [...entries.values()].sort(comparePendingReactUsageEntries);
677
684
  }
678
- function collectStatementEntryUsages(statement, filePath, sourceText, entries) {
679
- collectNodeEntryUsages(statement, filePath, sourceText, entries, false);
685
+ function collectStatementEntryUsages(statement, filePath, sourceText, entries, includeBuiltins) {
686
+ collectNodeEntryUsages(statement, filePath, sourceText, entries, includeBuiltins, false);
680
687
  }
681
- function collectNodeEntryUsages(node, filePath, sourceText, entries, hasComponentAncestor) {
688
+ function collectNodeEntryUsages(node, filePath, sourceText, entries, includeBuiltins, hasComponentAncestor) {
682
689
  if (FUNCTION_NODE_TYPES.has(node.type)) return;
683
690
  let nextHasComponentAncestor = hasComponentAncestor;
684
691
  if (node.type === "JSXElement") {
@@ -686,6 +693,12 @@ function collectNodeEntryUsages(node, filePath, sourceText, entries, hasComponen
686
693
  if (referenceName !== void 0) {
687
694
  if (!hasComponentAncestor) addPendingReactUsageEntry(entries, referenceName, "component", createReactUsageLocation(filePath, sourceText, node.start));
688
695
  nextHasComponentAncestor = true;
696
+ } else if (includeBuiltins) {
697
+ const builtinName = getBuiltinReferenceName(node);
698
+ if (builtinName !== void 0) {
699
+ if (!hasComponentAncestor) addPendingReactUsageEntry(entries, builtinName, "builtin", createReactUsageLocation(filePath, sourceText, node.start));
700
+ nextHasComponentAncestor = true;
701
+ }
689
702
  }
690
703
  } else if (node.type === "CallExpression") {
691
704
  const hookReference = getHookReferenceName(node);
@@ -700,18 +713,18 @@ function collectNodeEntryUsages(node, filePath, sourceText, entries, hasComponen
700
713
  if (keys === void 0) return;
701
714
  keys.forEach((key) => {
702
715
  const value = node[key];
703
- collectEntryUsageChild(value, filePath, sourceText, entries, nextHasComponentAncestor);
716
+ collectEntryUsageChild(value, filePath, sourceText, entries, includeBuiltins, nextHasComponentAncestor);
704
717
  });
705
718
  }
706
- function collectEntryUsageChild(value, filePath, sourceText, entries, hasComponentAncestor) {
719
+ function collectEntryUsageChild(value, filePath, sourceText, entries, includeBuiltins, hasComponentAncestor) {
707
720
  if (Array.isArray(value)) {
708
721
  value.forEach((entry) => {
709
- collectEntryUsageChild(entry, filePath, sourceText, entries, hasComponentAncestor);
722
+ collectEntryUsageChild(entry, filePath, sourceText, entries, includeBuiltins, hasComponentAncestor);
710
723
  });
711
724
  return;
712
725
  }
713
726
  if (!isNode(value)) return;
714
- collectNodeEntryUsages(value, filePath, sourceText, entries, hasComponentAncestor);
727
+ collectNodeEntryUsages(value, filePath, sourceText, entries, includeBuiltins, hasComponentAncestor);
715
728
  }
716
729
  function addPendingReactUsageEntry(entries, referenceName, kind, location) {
717
730
  const key = `${location.filePath}:${location.line}:${location.column}:${kind}:${referenceName}`;
@@ -800,18 +813,23 @@ function createPendingSymbol(filePath, name, kind, declaration) {
800
813
  declaration,
801
814
  exportNames: /* @__PURE__ */ new Set(),
802
815
  componentReferences: /* @__PURE__ */ new Set(),
803
- hookReferences: /* @__PURE__ */ new Set()
816
+ hookReferences: /* @__PURE__ */ new Set(),
817
+ builtinReferences: /* @__PURE__ */ new Set()
804
818
  };
805
819
  }
806
820
  //#endregion
807
821
  //#region src/analyzers/react/usage.ts
808
- function analyzeSymbolUsages(symbol) {
822
+ function analyzeSymbolUsages(symbol, includeBuiltins) {
809
823
  const root = symbol.declaration.type === "ArrowFunctionExpression" ? symbol.declaration.body : symbol.declaration.body;
810
824
  if (root === null) return;
811
825
  walkReactUsageTree(root, (node) => {
812
826
  if (node.type === "JSXElement") {
813
827
  const name = getComponentReferenceName(node);
814
828
  if (name !== void 0) symbol.componentReferences.add(name);
829
+ if (includeBuiltins) {
830
+ const builtinName = getBuiltinReferenceName(node);
831
+ if (builtinName !== void 0) symbol.builtinReferences.add(builtinName);
832
+ }
815
833
  return;
816
834
  }
817
835
  if (node.type === "CallExpression") {
@@ -824,7 +842,7 @@ function analyzeSymbolUsages(symbol) {
824
842
  }
825
843
  //#endregion
826
844
  //#region src/analyzers/react/file.ts
827
- function analyzeReactFile(program, filePath, sourceText, includeNestedRenderEntries, sourceDependencies) {
845
+ function analyzeReactFile(program, filePath, sourceText, includeNestedRenderEntries, sourceDependencies, includeBuiltins) {
828
846
  const symbolsByName = /* @__PURE__ */ new Map();
829
847
  program.body.forEach((statement) => {
830
848
  collectTopLevelReactSymbols(statement, filePath, symbolsByName);
@@ -833,12 +851,12 @@ function analyzeReactFile(program, filePath, sourceText, includeNestedRenderEntr
833
851
  const exportsByName = /* @__PURE__ */ new Map();
834
852
  const reExportBindingsByName = /* @__PURE__ */ new Map();
835
853
  const exportAllBindings = [];
836
- const directEntryUsages = includeNestedRenderEntries ? collectEntryUsages(program, filePath, sourceText) : [];
854
+ const directEntryUsages = includeNestedRenderEntries ? collectEntryUsages(program, filePath, sourceText, includeBuiltins) : [];
837
855
  program.body.forEach((statement) => {
838
856
  collectImportsAndExports(statement, sourceDependencies, symbolsByName, importsByLocalName, exportsByName, reExportBindingsByName, exportAllBindings);
839
857
  });
840
858
  symbolsByName.forEach((symbol) => {
841
- analyzeSymbolUsages(symbol);
859
+ analyzeSymbolUsages(symbol, includeBuiltins);
842
860
  });
843
861
  return {
844
862
  filePath,
@@ -866,6 +884,7 @@ function collectComponentDeclarationEntryUsages(symbolsByName, sourceText) {
866
884
  //#endregion
867
885
  //#region src/analyzers/react/references.ts
868
886
  function resolveReactReference(fileAnalysis, fileAnalyses, name, kind) {
887
+ if (kind === "builtin") return getBuiltinNodeId(name);
869
888
  const localSymbol = fileAnalysis.symbolsByName.get(name);
870
889
  if (localSymbol !== void 0 && localSymbol.kind === kind) return localSymbol.id;
871
890
  const importBinding = fileAnalysis.importsByLocalName.get(name);
@@ -909,6 +928,21 @@ function addExternalHookNodes(fileAnalyses, nodes) {
909
928
  if (!nodes.has(externalNode.id)) nodes.set(externalNode.id, externalNode);
910
929
  });
911
930
  }
931
+ function addBuiltinNodes(fileAnalyses, nodes) {
932
+ for (const fileAnalysis of fileAnalyses.values()) {
933
+ fileAnalysis.entryUsages.forEach((entry) => {
934
+ if (entry.kind !== "builtin") return;
935
+ const builtinNode = createBuiltinNode(entry.referenceName);
936
+ if (!nodes.has(builtinNode.id)) nodes.set(builtinNode.id, builtinNode);
937
+ });
938
+ fileAnalysis.symbolsById.forEach((symbol) => {
939
+ symbol.builtinReferences.forEach((name) => {
940
+ const builtinNode = createBuiltinNode(name);
941
+ if (!nodes.has(builtinNode.id)) nodes.set(builtinNode.id, builtinNode);
942
+ });
943
+ });
944
+ }
945
+ }
912
946
  function createExternalHookNode(binding, localName) {
913
947
  const name = getExternalHookName(binding, localName);
914
948
  return {
@@ -920,9 +954,22 @@ function createExternalHookNode(binding, localName) {
920
954
  usages: []
921
955
  };
922
956
  }
957
+ function createBuiltinNode(name) {
958
+ return {
959
+ id: getBuiltinNodeId(name),
960
+ name,
961
+ kind: "builtin",
962
+ filePath: "html",
963
+ exportNames: [],
964
+ usages: []
965
+ };
966
+ }
923
967
  function getExternalHookNodeId(binding, localName) {
924
968
  return `external:${binding.sourceSpecifier}#hook:${getExternalHookName(binding, localName)}`;
925
969
  }
970
+ function getBuiltinNodeId(name) {
971
+ return `builtin:${name}`;
972
+ }
926
973
  function getExternalHookName(binding, localName) {
927
974
  return binding.importedName === "default" ? localName : binding.importedName;
928
975
  }
@@ -941,11 +988,19 @@ function compareReactNodes(left, right) {
941
988
  //#endregion
942
989
  //#region src/analyzers/react/index.ts
943
990
  function analyzeReactUsage(entryFile, options = {}) {
944
- return new ReactAnalyzer(entryFile, options).analyze();
991
+ return new ReactAnalyzer(normalizeEntryFiles(entryFile), options).analyze();
945
992
  }
946
993
  var ReactAnalyzer = class extends BaseAnalyzer {
994
+ constructor(entryFiles, options) {
995
+ const [firstEntryFile] = entryFiles;
996
+ if (firstEntryFile === void 0) throw new Error("At least one React entry file is required.");
997
+ super(firstEntryFile, options);
998
+ this.entryFiles = entryFiles;
999
+ }
947
1000
  doAnalyze() {
948
- const dependencyGraph = analyzeDependencies(this.entryFile, this.options);
1001
+ const dependencyGraphs = this.entryFiles.map((entryFile) => analyzeDependencies(entryFile, this.options));
1002
+ const dependencyGraph = mergeDependencyGraphs(dependencyGraphs);
1003
+ const entryIds = dependencyGraphs.map((graph) => graph.entryId);
949
1004
  const fileAnalyses = this.collectFileAnalyses(dependencyGraph);
950
1005
  const nodes = this.createNodes(fileAnalyses);
951
1006
  this.attachUsages(fileAnalyses, nodes);
@@ -953,12 +1008,13 @@ var ReactAnalyzer = class extends BaseAnalyzer {
953
1008
  return {
954
1009
  cwd: dependencyGraph.cwd,
955
1010
  entryId: dependencyGraph.entryId,
1011
+ entryIds,
956
1012
  nodes,
957
1013
  entries
958
1014
  };
959
1015
  }
960
1016
  collectFileAnalyses(dependencyGraph) {
961
- const reachableFiles = new Set([dependencyGraph.entryId, ...dependencyGraph.nodes.keys()]);
1017
+ const reachableFiles = new Set([...dependencyGraph.entryIds, ...dependencyGraph.nodes.keys()]);
962
1018
  const fileAnalyses = /* @__PURE__ */ new Map();
963
1019
  for (const filePath of [...reachableFiles].sort()) {
964
1020
  if (!isSourceCodeFile(filePath) || filePath.endsWith(".d.ts")) continue;
@@ -972,7 +1028,7 @@ var ReactAnalyzer = class extends BaseAnalyzer {
972
1028
  dependencyNode?.dependencies.forEach((dependency) => {
973
1029
  if (dependency.kind === "source") sourceDependencies.set(dependency.specifier, dependency.target);
974
1030
  });
975
- fileAnalyses.set(filePath, analyzeReactFile(parseResult.program, filePath, sourceText, filePath === dependencyGraph.entryId, sourceDependencies));
1031
+ fileAnalyses.set(filePath, analyzeReactFile(parseResult.program, filePath, sourceText, dependencyGraph.entryIds.includes(filePath), sourceDependencies, this.options.includeBuiltins === true));
976
1032
  }
977
1033
  return fileAnalyses;
978
1034
  }
@@ -987,6 +1043,7 @@ var ReactAnalyzer = class extends BaseAnalyzer {
987
1043
  usages: []
988
1044
  });
989
1045
  addExternalHookNodes(fileAnalyses, nodes);
1046
+ if (this.options.includeBuiltins === true) addBuiltinNodes(fileAnalyses, nodes);
990
1047
  return nodes;
991
1048
  }
992
1049
  attachUsages(fileAnalyses, nodes) {
@@ -1008,6 +1065,14 @@ var ReactAnalyzer = class extends BaseAnalyzer {
1008
1065
  referenceName
1009
1066
  });
1010
1067
  });
1068
+ if (this.options.includeBuiltins === true) symbol.builtinReferences.forEach((referenceName) => {
1069
+ const targetId = getBuiltinNodeId(referenceName);
1070
+ usages.set(`render:${targetId}:${referenceName}`, {
1071
+ kind: "render",
1072
+ target: targetId,
1073
+ referenceName
1074
+ });
1075
+ });
1011
1076
  const node = nodes.get(symbol.id);
1012
1077
  if (node === void 0) continue;
1013
1078
  const sortedUsages = [...usages.values()].sort((left, right) => compareReactNodeIds(left.target, right.target, nodes));
@@ -1032,6 +1097,27 @@ var ReactAnalyzer = class extends BaseAnalyzer {
1032
1097
  return [...entriesByKey.values()].sort((left, right) => compareReactUsageEntries(left, right, nodes));
1033
1098
  }
1034
1099
  };
1100
+ function normalizeEntryFiles(entryFile) {
1101
+ const entryFiles = Array.isArray(entryFile) ? entryFile : [entryFile];
1102
+ const dedupedEntryFiles = [...new Set(entryFiles)];
1103
+ if (dedupedEntryFiles.length === 0) throw new Error("At least one React entry file is required.");
1104
+ return dedupedEntryFiles;
1105
+ }
1106
+ function mergeDependencyGraphs(graphs) {
1107
+ const firstGraph = graphs[0];
1108
+ if (firstGraph === void 0) throw new Error("At least one dependency graph is required.");
1109
+ const nodes = /* @__PURE__ */ new Map();
1110
+ for (const graph of graphs) for (const [nodeId, node] of graph.nodes) if (!nodes.has(nodeId)) nodes.set(nodeId, node);
1111
+ const uniqueConfigPaths = [...new Set(graphs.map((graph) => graph.configPath))];
1112
+ const configPath = uniqueConfigPaths.length === 1 ? uniqueConfigPaths[0] : void 0;
1113
+ return {
1114
+ cwd: firstGraph.cwd,
1115
+ entryId: firstGraph.entryId,
1116
+ entryIds: graphs.map((graph) => graph.entryId),
1117
+ nodes,
1118
+ ...configPath === void 0 ? {} : { configPath }
1119
+ };
1120
+ }
1035
1121
  //#endregion
1036
1122
  //#region src/analyzers/react/queries.ts
1037
1123
  function getReactUsageEntries(graph, filter = "all") {
@@ -1076,6 +1162,7 @@ function matchesReactFilter(node, filter) {
1076
1162
  const ANSI_RESET = "\x1B[0m";
1077
1163
  const ANSI_COMPONENT = "\x1B[36m";
1078
1164
  const ANSI_HOOK = "\x1B[35m";
1165
+ const ANSI_BUILTIN = "\x1B[34m";
1079
1166
  const ANSI_MUTED = "\x1B[38;5;244m";
1080
1167
  const ANSI_UNUSED = "\x1B[38;5;214m";
1081
1168
  function resolveColorSupport(mode = "auto", options = {}) {
@@ -1096,7 +1183,9 @@ function formatReactSymbolLabel(name, kind, enabled) {
1096
1183
  return `${getReactSymbolColor(kind)}${label}${ANSI_RESET}`;
1097
1184
  }
1098
1185
  function formatReactSymbolName(name, kind) {
1099
- return kind === "component" ? `<${name} />` : `${name}()`;
1186
+ if (kind === "component") return `<${name} />`;
1187
+ if (kind === "hook") return `${name}()`;
1188
+ return `<${name}>`;
1100
1189
  }
1101
1190
  function colorizeReactLabel(text, kind, enabled) {
1102
1191
  if (!enabled) return text;
@@ -1107,7 +1196,9 @@ function colorizeMuted(text, enabled) {
1107
1196
  return `${ANSI_MUTED}${text}${ANSI_RESET}`;
1108
1197
  }
1109
1198
  function getReactSymbolColor(kind) {
1110
- return kind === "component" ? ANSI_COMPONENT : ANSI_HOOK;
1199
+ if (kind === "component") return ANSI_COMPONENT;
1200
+ if (kind === "hook") return ANSI_HOOK;
1201
+ return ANSI_BUILTIN;
1111
1202
  }
1112
1203
  //#endregion
1113
1204
  //#region src/utils/to-display-path.ts
@@ -1229,11 +1320,14 @@ function renderUsage(usage, graph, cwd, filter, color, visited, prefix, isLast)
1229
1320
  return childLines;
1230
1321
  }
1231
1322
  function formatReactNodeLabel(node, cwd, color, referenceName) {
1232
- return `${referenceName !== void 0 && referenceName !== node.name ? `${colorizeReactLabel(formatReactSymbolName(node.name, node.kind), node.kind, color)} ${colorizeMuted(`as ${referenceName}`, color)} ${colorizeReactLabel(`[${node.kind}]`, node.kind, color)}` : formatReactSymbolLabel(node.name, node.kind, color)} (${toDisplayPath(node.filePath, cwd)})`;
1323
+ return `${referenceName !== void 0 && referenceName !== node.name ? `${colorizeReactLabel(formatReactSymbolName(node.name, node.kind), node.kind, color)} ${colorizeMuted(`as ${referenceName}`, color)} ${colorizeReactLabel(`[${node.kind}]`, node.kind, color)}` : formatReactSymbolLabel(node.name, node.kind, color)} (${formatReactNodeFilePath$1(node, cwd)})`;
1233
1324
  }
1234
1325
  function formatReactEntryLabel(entry, cwd) {
1235
1326
  return `${toDisplayPath(entry.location.filePath, cwd)}:${entry.location.line}:${entry.location.column}`;
1236
1327
  }
1328
+ function formatReactNodeFilePath$1(node, cwd) {
1329
+ return node.kind === "builtin" ? "html" : toDisplayPath(node.filePath, cwd);
1330
+ }
1237
1331
  //#endregion
1238
1332
  //#region src/output/json/import.ts
1239
1333
  function graphToSerializableTree(graph, options = {}) {
@@ -1311,7 +1405,7 @@ function serializeReactUsageNode(nodeId, graph, filter, visited) {
1311
1405
  id: node.id,
1312
1406
  name: node.name,
1313
1407
  symbolKind: "circular",
1314
- filePath: toDisplayPath(node.filePath, graph.cwd),
1408
+ filePath: formatReactNodeFilePath(node.filePath, node.kind, graph.cwd),
1315
1409
  exportNames: node.exportNames,
1316
1410
  usages: []
1317
1411
  };
@@ -1321,7 +1415,7 @@ function serializeReactUsageNode(nodeId, graph, filter, visited) {
1321
1415
  id: node.id,
1322
1416
  name: node.name,
1323
1417
  symbolKind: node.kind,
1324
- filePath: toDisplayPath(node.filePath, graph.cwd),
1418
+ filePath: formatReactNodeFilePath(node.filePath, node.kind, graph.cwd),
1325
1419
  exportNames: node.exportNames,
1326
1420
  usages: getFilteredUsages(node, graph, filter).map((usage) => ({
1327
1421
  kind: usage.kind,
@@ -1341,7 +1435,10 @@ function serializeReactUsageEntry(entry, graph, filter) {
1341
1435
  node: serializeReactUsageNode(entry.target, graph, filter, /* @__PURE__ */ new Set())
1342
1436
  };
1343
1437
  }
1438
+ function formatReactNodeFilePath(filePath, kind, cwd) {
1439
+ return kind === "builtin" ? "html" : toDisplayPath(filePath, cwd);
1440
+ }
1344
1441
  //#endregion
1345
- 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 };
1442
+ 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 };
1346
1443
 
1347
- //# sourceMappingURL=react-a7ugf8JN.mjs.map
1444
+ //# sourceMappingURL=react-B0frvTuH.mjs.map