ng-di-graph 0.4.6 → 0.5.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 CHANGED
@@ -8,95 +8,78 @@ A command-line tool that analyzes Angular TypeScript codebases to extract depend
8
8
 
9
9
  **Target Angular Versions:** 17-20
10
10
 
11
- ## Requirements
12
-
13
- - Node.js 20.x LTS (see `.node-version`; e.g., `mise use node@$(cat .node-version)`)
14
- - npm 10+ (ships with Node 20). Use your preferred version manager (mise recommended) to match the pinned runtime before installing dependencies.
15
-
16
- ## Features
17
-
18
- ✨ **Complete Feature Set** - Production-ready dependency graph analysis for Angular applications
19
-
20
- - 🔍 **Dependency Analysis** - Extract DI relationships from `@Injectable`, `@Component`, and `@Directive` classes
21
- - 🎯 **Constructor Injection** - Analyze constructor parameters with type annotations and `@Inject()` tokens
22
- - 🏷️ **Decorator Flags** - Capture `@Optional`, `@Self`, `@SkipSelf`, and `@Host` parameter decorators
23
- - 📊 **Multiple Output Formats** - JSON (machine-readable) and Mermaid (visual flowcharts)
24
- - 🎨 **Entry Point Filtering** - Generate sub-graphs from specific starting nodes
25
- - 🔄 **Bidirectional Analysis** - Explore upstream dependencies, downstream consumers, or both
26
- - 🔁 **Circular Detection** - Automatically detect and report circular dependencies
11
+ ## Prerequisites
12
+ - Node.js 20.x (npm 10+)
13
+ - Angular project targeting v17-20 with a `tsconfig.json` to discover or reference
27
14
 
28
15
  ## Installation
29
-
30
16
  ```bash
31
17
  npm install -g ng-di-graph
32
18
  ```
33
19
 
34
- ## Usage
35
-
20
+ ## Quick start
36
21
  ```bash
37
- # Target specific files without the --files flag (positional shortcut)
38
- ng-di-graph src/app/app.component.ts --project ./tsconfig.json --format json
22
+ # Analyze the whole project and print JSON (auto-discover tsconfig)
23
+ ng-di-graph --format json
39
24
 
40
- # Analyze an Angular project and output JSON
41
- ng-di-graph --project ./my-angular-app/tsconfig.json --format json
25
+ # Generate a Mermaid diagram and save it
26
+ ng-di-graph --format mermaid --out docs/di-graph.mmd
42
27
 
43
- # Generate a Mermaid flowchart
44
- ng-di-graph --project ./tsconfig.json --format mermaid --out graph.mmd
28
+ # Target specific files via positional filePaths
29
+ ng-di-graph src/app/app.component.ts src/app/auth.service.ts --format json
45
30
 
46
- # Analyze dependencies of a specific component
47
- ng-di-graph --project ./tsconfig.json --entry AppComponent --format mermaid
31
+ # Auto-discover the nearest tsconfig.json (no --project needed)
32
+ ng-di-graph src/app --format mermaid --out docs/di-graph.mmd
48
33
 
49
- # Focus on a specific file (similar to eslint targets)
50
- ng-di-graph --project ./tsconfig.json --files src/app/app.component.ts
34
+ # Focus on a symbol and see who depends on it
35
+ ng-di-graph --entry UserService --direction upstream
51
36
 
52
- # Show verbose logging with detailed type resolution
53
- ng-di-graph --project ./tsconfig.json --verbose
37
+ # Override auto-discovery with an explicit tsconfig
38
+ ng-di-graph --project ./tsconfig.json --format json
39
+ ```
54
40
 
55
- # Include parameter decorator flags
56
- ng-di-graph --project ./tsconfig.json --include-decorators
41
+ ## Features
57
42
 
58
- # Analyze upstream dependencies (who depends on this?)
59
- ng-di-graph --project ./tsconfig.json --entry UserService --direction upstream
60
- ```
43
+ **Complete Feature Set** - Production-ready dependency graph analysis for Angular applications
44
+
45
+ - 🔍 **Dependency Analysis** - Extract DI relationships from `@Injectable`, `@Component`, and `@Directive` classes
46
+ - 🎯 **Constructor Injection** - Analyze constructor parameters with type annotations and `@Inject()` tokens
47
+ - 🏷️ **Decorator Flags** - Capture `@Optional`, `@Self`, `@SkipSelf`, and `@Host` parameter decorators
48
+ - 📊 **Multiple Output Formats** - JSON (machine-readable) and Mermaid (visual flowcharts)
49
+ - 🎨 **Entry Point Filtering** - Generate sub-graphs from specific starting nodes
50
+ - 🔄 **Bidirectional Analysis** - Explore upstream dependencies, downstream consumers, or both
51
+ - 🔁 **Circular Detection** - Automatically detect and report circular dependencies
61
52
 
62
- ## CLI Reference
53
+ ## Common commands
54
+ - Full project, JSON output:
55
+ `ng-di-graph --format json`
56
+ - Mermaid diagram to file:
57
+ `ng-di-graph --format mermaid --out docs/di-graph.mmd`
58
+ - Filter to specific symbols:
59
+ `ng-di-graph --entry AppComponent`
60
+ - Upstream consumers of a service:
61
+ `ng-di-graph --entry UserService --direction upstream`
62
+ - Include decorator flags with verbose logging:
63
+ `ng-di-graph --include-decorators --verbose`
64
+
65
+ ## CLI options at a glance
63
66
 
64
67
  ```
65
68
  ng-di-graph [filePaths...] [options]
66
-
67
- Arguments:
68
- filePaths Optional list of files/directories to analyze (alias for --files)
69
-
70
- Options:
71
- -p, --project <path> Path to tsconfig.json (default: ./tsconfig.json)
72
- --files <paths...> Specific file paths to analyze (similar to eslint targets)
73
- -f, --format <format> Output format: json | mermaid (default: json)
74
- -e, --entry <symbol...> Starting nodes for sub-graph filtering
75
- -d, --direction <dir> Filter direction: upstream | downstream | both (default: downstream)
76
- --include-decorators Include @Optional, @Self, @SkipSelf, @Host flags in output
77
- --out <file> Output file path (prints to stdout if omitted)
78
- -v, --verbose Show detailed parsing and resolution information
79
- -h, --help Display help information
80
69
  ```
81
70
 
82
- ### Option Details
83
-
84
- - **`filePaths` (positional)**: Optional positional arguments that act as a shortcut for `--files`,
85
- allowing you to target specific files or directories without the flag; omit to scan the full project
86
- - **`--project`**: Specifies the TypeScript configuration file to use for project analysis (required
87
- when your tsconfig lives outside `./tsconfig.json`)
88
- - **`--files`**: Restrict analysis to one or more specific TypeScript files (relative paths supported)
89
- and merges with positional `filePaths` when both are provided; useful when targeting a subset of your
90
- project similar to ESLint CLI usage
91
- - **`--format`**: Choose between JSON (structured data) or Mermaid (visual diagram)
92
- - **`--entry`**: Filter the graph to show only dependencies related to specified symbols (supports multiple entries)
93
- - **`--direction`**:
94
- - `downstream` (default): Show what the entry depends on
95
- - `upstream`: Show what depends on the entry
96
- - `both`: Show both upstream and downstream dependencies
97
- - **`--include-decorators`**: Add parameter decorator information to edge flags
98
- - **`--out`**: Save output to a file instead of stdout
99
- - **`--verbose`**: Enable detailed logging including timing metrics, memory usage, and type resolution details
71
+ Option | Default | Description
72
+ -- | -- | --
73
+ `filePaths` | none | Positional alias for `--files`; combines with `--files` when both are set.
74
+ `-p, --project <path>` | auto-discovered | Path to the TypeScript config file to analyze (omit to auto-discover).
75
+ `--files <paths...>` | none | Limit analysis to specific files or directories.
76
+ `-f, --format <format>` | `json` | Output as `json` or `mermaid`.
77
+ `-e, --entry <symbol...>` | none | Start the graph from one or more symbols.
78
+ `-d, --direction <dir>` | `downstream` | `downstream`, `upstream`, or `both` relative to entries.
79
+ `--include-decorators` | `false` | Add `@Optional`, `@Self`, `@SkipSelf`, `@Host` flags to edges.
80
+ `--out <file>` | stdout | Write output to a file.
81
+ `-v, --verbose` | `false` | Show detailed parsing and resolution logs.
82
+ `-h, --help` | `false` | Display CLI help.
100
83
 
101
84
  ## Output Formats
102
85
 
@@ -107,21 +90,26 @@ Options:
107
90
  "nodes": [
108
91
  { "id": "AppComponent", "kind": "component" },
109
92
  { "id": "UserService", "kind": "service" },
110
- { "id": "AuthService", "kind": "service" }
93
+ { "id": "AuthService", "kind": "service" },
94
+ { "id": "Logger", "kind": "service" }
111
95
  ],
112
96
  "edges": [
113
97
  {
114
98
  "from": "AppComponent",
115
- "to": "UserService",
116
- "flags": { "optional": false }
99
+ "to": "UserService"
117
100
  },
118
101
  {
119
102
  "from": "UserService",
120
103
  "to": "AuthService",
121
- "flags": { "optional": true, "self": false }
104
+ "flags": { "optional": true, "self": false, "skipSelf": false, "host": false }
105
+ },
106
+ {
107
+ "from": "AuthService",
108
+ "to": "Logger",
109
+ "flags": { "optional": false, "self": false, "skipSelf": false, "host": false }
122
110
  }
123
111
  ],
124
- "circularDependencies": []
112
+ "circularDependencies": [["UserService", "AuthService", "UserService"]]
125
113
  }
126
114
  ```
127
115
 
@@ -145,66 +133,18 @@ flowchart LR
145
133
  UserService --> AuthService
146
134
  ```
147
135
 
148
- Mermaid diagrams can be:
149
- - Rendered in GitHub/GitLab markdown
150
- - Viewed in the [Mermaid Live Editor](https://mermaid.live/)
151
- - Embedded in documentation sites
152
- - Converted to images using CLI tools
153
-
154
- ## Use Cases
155
-
156
- ### 1. Test Planning
157
- Quickly identify all dependencies of a component to plan test mocks:
158
- ```bash
159
- ng-di-graph --project ./tsconfig.json --entry MyComponent --format json
160
- ```
161
-
162
- ### 2. Impact Analysis
163
- Find all consumers of a service before making breaking changes:
164
- ```bash
165
- ng-di-graph --project ./tsconfig.json --entry UserService --direction upstream
166
- ```
167
-
168
- ### 3. Documentation
169
- Generate visual dependency diagrams for README or design docs:
170
- ```bash
171
- ng-di-graph --project ./tsconfig.json --format mermaid --out docs/architecture.mmd
172
- ```
173
-
174
- ### 4. Circular Dependency Detection
175
- Identify circular dependencies in your DI graph:
176
- ```bash
177
- ng-di-graph --project ./tsconfig.json --verbose
178
- # Check the "circularDependencies" array in output
179
- ```
136
+ Mermaid diagrams can be rendered in GitHub/GitLab markdown, viewed in the [Mermaid Live Editor](https://mermaid.live/), or converted to images with CLI tools.
180
137
 
181
- ### 5. Debugging Type Issues
182
- Investigate type resolution problems with verbose logging:
183
- ```bash
184
- ng-di-graph --project ./tsconfig.json --verbose
185
- # Shows detailed type resolution and warnings
186
- ```
138
+ ## Use cases
139
+ - Test planning: surface dependencies to decide what to mock.
140
+ - Impact analysis: list consumers before changing a service.
141
+ - Documentation: add Mermaid diagrams to READMEs or architecture docs.
187
142
 
188
143
  ## Release workflow
144
+ Releases are automated via Release Please; tag pushes run lint → typecheck → test → build → pack → publish. Maintainer runbook: see `docs/release/ci-publish.md`.
189
145
 
190
- - PR-driven releases via Release Please; release PR merges tag `vX.Y.Z`.
191
- - Tag pushes trigger publish workflow: lint typecheck test build pack → publish with provenance.
192
- - Maintainer runbook: see docs/release/ci-publish.md.
193
-
194
- ## Error Handling
195
-
196
- The tool provides graceful error handling:
197
-
198
- - **File Parsing Failures** - Skips unparseable files and continues processing
199
- - **Type Resolution Issues** - Logs warnings for `any`/`unknown` types
200
- - **Missing tsconfig.json** - Clear error message with suggestion
201
- - **Invalid CLI Arguments** - Help message with usage examples
202
- - **Circular Dependencies** - Detected and reported without blocking analysis
203
- - **Memory Constraints** - Suggestions for chunking large codebases
204
-
205
- Exit codes:
206
- - `0` - Success
207
- - `1` - Fatal error (invalid config, parsing failure, etc.)
146
+ ## Error handling
147
+ The CLI reports clear failures and continues past unparseable files; rerun with `--verbose` for detailed logs.
208
148
 
209
149
  ## Contributing
210
150
 
@@ -213,9 +153,3 @@ Contributions are welcome! Please see our [Contributing Guide](CONTRIBUTING.md)
213
153
  ## License
214
154
 
215
155
  MIT License - See [LICENSE](LICENSE) file for details
216
-
217
- ## Version
218
-
219
- Current version: **0.1.0**
220
-
221
- All core features are complete and production-ready.
@@ -26,10 +26,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
26
26
  }) : target, mod));
27
27
 
28
28
  //#endregion
29
+ let commander = require("commander");
29
30
  let node_fs = require("node:fs");
30
31
  let node_path = require("node:path");
31
32
  node_path = __toESM(node_path);
32
- let commander = require("commander");
33
33
  let ts_morph = require("ts-morph");
34
34
  let typescript = require("typescript");
35
35
  typescript = __toESM(typescript);
@@ -402,11 +402,11 @@ function detectCircularDependencies(edges, nodes) {
402
402
  /**
403
403
  * DFS helper function to detect cycles
404
404
  */
405
- function dfs(node, path$1) {
405
+ function dfs(node, path$2) {
406
406
  if (processedNodes.has(node)) return;
407
407
  if (recursionStack.has(node)) {
408
- const cycleStartIndex = path$1.indexOf(node);
409
- const cyclePath = [...path$1.slice(cycleStartIndex), node];
408
+ const cycleStartIndex = path$2.indexOf(node);
409
+ const cyclePath = [...path$2.slice(cycleStartIndex), node];
410
410
  circularDependencies.push(cyclePath);
411
411
  for (let i = 0; i < cyclePath.length - 1; i++) {
412
412
  const edgeKey = `${cyclePath[i]}->${cyclePath[i + 1]}`;
@@ -415,7 +415,7 @@ function detectCircularDependencies(edges, nodes) {
415
415
  return;
416
416
  }
417
417
  recursionStack.add(node);
418
- const newPath = [...path$1, node];
418
+ const newPath = [...path$2, node];
419
419
  const neighbors = adjacencyList.get(node) || [];
420
420
  for (const neighbor of neighbors) dfs(neighbor, newPath);
421
421
  recursionStack.delete(node);
@@ -1974,6 +1974,176 @@ var MermaidFormatter = class {
1974
1974
  }
1975
1975
  };
1976
1976
 
1977
+ //#endregion
1978
+ //#region src/cli/project-resolver.ts
1979
+ const TSCONFIG_NAME = "tsconfig.json";
1980
+ const WORKSPACE_FILES = ["angular.json", "workspace.json"];
1981
+ const WORKSPACE_TARGET_ORDER = [
1982
+ "build",
1983
+ "test",
1984
+ "serve",
1985
+ "lint",
1986
+ "e2e"
1987
+ ];
1988
+ function resolveProjectPath(options) {
1989
+ const { projectOption, fileTargets, cwd, logger } = options;
1990
+ if (projectOption) {
1991
+ const resolved = normalizeExplicitProject(projectOption);
1992
+ logDiscovery(logger, "Using explicit --project path", { project: resolved });
1993
+ return resolved;
1994
+ }
1995
+ const resolvedFromTargets = resolveFromFileTargets(fileTargets, cwd, logger);
1996
+ if (resolvedFromTargets) {
1997
+ logDiscovery(logger, "Auto-discovered tsconfig.json from file targets", { project: resolvedFromTargets });
1998
+ return resolvedFromTargets;
1999
+ }
2000
+ const startDir = fileTargets.length > 0 ? getCommonAncestor(fileTargets, cwd) : cwd;
2001
+ const tsconfigPath = findNearestTsconfig(startDir);
2002
+ if (tsconfigPath) {
2003
+ logDiscovery(logger, "Auto-discovered tsconfig.json", {
2004
+ startDir,
2005
+ project: tsconfigPath
2006
+ });
2007
+ return tsconfigPath;
2008
+ }
2009
+ const workspaceResolution = resolveWorkspaceTsconfig(startDir);
2010
+ if (workspaceResolution) {
2011
+ logDiscovery(logger, "Resolved tsconfig via Angular workspace", {
2012
+ workspace: workspaceResolution.workspacePath,
2013
+ project: workspaceResolution.tsconfigPath
2014
+ });
2015
+ return workspaceResolution.tsconfigPath;
2016
+ }
2017
+ throw ErrorHandler.createError(`tsconfig.json not found starting from: ${startDir}`, "TSCONFIG_NOT_FOUND", startDir, { startDir });
2018
+ }
2019
+ function normalizeExplicitProject(projectOption) {
2020
+ try {
2021
+ if ((0, node_fs.statSync)(projectOption).isDirectory()) {
2022
+ const tsconfigPath = node_path.default.join(projectOption, TSCONFIG_NAME);
2023
+ if ((0, node_fs.existsSync)(tsconfigPath)) return tsconfigPath;
2024
+ }
2025
+ } catch {}
2026
+ return projectOption;
2027
+ }
2028
+ function resolveFromFileTargets(fileTargets, cwd, logger) {
2029
+ if (fileTargets.length === 0) return;
2030
+ const targetDirectories = getTargetDirectories(fileTargets, cwd);
2031
+ const resolvedConfigs = /* @__PURE__ */ new Map();
2032
+ const missingTargets = [];
2033
+ for (const directory of targetDirectories) {
2034
+ const configPath = findNearestTsconfig(directory);
2035
+ if (configPath) {
2036
+ const targets = resolvedConfigs.get(configPath) ?? [];
2037
+ targets.push(directory);
2038
+ resolvedConfigs.set(configPath, targets);
2039
+ } else missingTargets.push(directory);
2040
+ }
2041
+ if (resolvedConfigs.size > 1) {
2042
+ const configPaths = Array.from(resolvedConfigs.keys());
2043
+ throw ErrorHandler.createError("File targets resolve to multiple tsconfig.json files. Use --project to select one.", "INVALID_ARGUMENTS", void 0, { configs: configPaths });
2044
+ }
2045
+ if (resolvedConfigs.size === 1 && missingTargets.length > 0) {
2046
+ const [configPath] = resolvedConfigs.keys();
2047
+ throw ErrorHandler.createError("Some file targets do not resolve to a tsconfig.json. Use --project to select one.", "INVALID_ARGUMENTS", void 0, {
2048
+ configPath,
2049
+ missingTargets
2050
+ });
2051
+ }
2052
+ const [singleConfig] = resolvedConfigs.keys();
2053
+ if (singleConfig) {
2054
+ logDiscovery(logger, "Resolved tsconfig.json for file targets", { project: singleConfig });
2055
+ return singleConfig;
2056
+ }
2057
+ }
2058
+ function getTargetDirectories(fileTargets, cwd) {
2059
+ const directories = /* @__PURE__ */ new Set();
2060
+ for (const target of fileTargets) {
2061
+ const directory = resolveTargetDirectory(resolvePath(target, cwd));
2062
+ directories.add(directory);
2063
+ }
2064
+ return [...directories];
2065
+ }
2066
+ function resolveTargetDirectory(targetPath) {
2067
+ try {
2068
+ if ((0, node_fs.statSync)(targetPath).isDirectory()) return targetPath;
2069
+ } catch {}
2070
+ return node_path.default.dirname(targetPath);
2071
+ }
2072
+ function findNearestTsconfig(startDir) {
2073
+ return typescript.findConfigFile(startDir, typescript.sys.fileExists, TSCONFIG_NAME);
2074
+ }
2075
+ function resolveWorkspaceTsconfig(startDir) {
2076
+ for (const workspaceFile of WORKSPACE_FILES) {
2077
+ const workspacePath = typescript.findConfigFile(startDir, typescript.sys.fileExists, workspaceFile);
2078
+ if (!workspacePath) continue;
2079
+ const workspaceConfig = readJsonConfig(workspacePath);
2080
+ if (!workspaceConfig) continue;
2081
+ const tsconfigPath = selectWorkspaceTsconfig(workspaceConfig, workspacePath);
2082
+ if (tsconfigPath) return {
2083
+ workspacePath,
2084
+ tsconfigPath
2085
+ };
2086
+ }
2087
+ }
2088
+ function readJsonConfig(configPath) {
2089
+ const configFile = typescript.readConfigFile(configPath, typescript.sys.readFile);
2090
+ if (configFile.error) return;
2091
+ return configFile.config;
2092
+ }
2093
+ function selectWorkspaceTsconfig(workspaceConfig, workspacePath) {
2094
+ if (!workspaceConfig || typeof workspaceConfig !== "object") return;
2095
+ const config = workspaceConfig;
2096
+ const projects = config.projects ?? {};
2097
+ const projectNames = Object.keys(projects);
2098
+ if (projectNames.length === 0) return;
2099
+ const projectConfig = projects[(config.defaultProject && projects[config.defaultProject] ? config.defaultProject : projectNames.length === 1 ? projectNames[0] : void 0) ?? projectNames[0]];
2100
+ const tsconfig = findWorkspaceTargetTsconfig(projectConfig?.architect ?? projectConfig?.targets ?? {});
2101
+ if (!tsconfig) return;
2102
+ const resolvedPath = node_path.default.resolve(node_path.default.dirname(workspacePath), tsconfig);
2103
+ return (0, node_fs.existsSync)(resolvedPath) ? resolvedPath : void 0;
2104
+ }
2105
+ function findWorkspaceTargetTsconfig(targets) {
2106
+ for (const targetName of WORKSPACE_TARGET_ORDER) {
2107
+ const tsconfig = targets[targetName]?.options?.tsConfig;
2108
+ if (tsconfig) return tsconfig;
2109
+ }
2110
+ for (const targetName of Object.keys(targets).sort()) {
2111
+ const tsconfig = targets[targetName]?.options?.tsConfig;
2112
+ if (tsconfig) return tsconfig;
2113
+ }
2114
+ }
2115
+ function getCommonAncestor(fileTargets, cwd) {
2116
+ const targetDirs = getTargetDirectories(fileTargets, cwd);
2117
+ if (targetDirs.length === 0) return cwd;
2118
+ const [first, ...rest] = targetDirs.map((dir) => node_path.default.resolve(dir));
2119
+ const baseParts = splitPath(first);
2120
+ let commonLength = baseParts.length;
2121
+ for (const dir of rest) {
2122
+ const parts = splitPath(dir);
2123
+ commonLength = Math.min(commonLength, parts.length);
2124
+ for (let i = 0; i < commonLength; i += 1) if (parts[i] !== baseParts[i]) {
2125
+ commonLength = i;
2126
+ break;
2127
+ }
2128
+ }
2129
+ const commonParts = baseParts.slice(0, commonLength);
2130
+ if (commonParts.length === 0) return node_path.default.parse(first).root;
2131
+ const [root, ...segments] = commonParts;
2132
+ return root ? node_path.default.join(root, ...segments) : node_path.default.join(...segments);
2133
+ }
2134
+ function splitPath(value) {
2135
+ const resolved = node_path.default.resolve(value);
2136
+ const { root } = node_path.default.parse(resolved);
2137
+ const segments = resolved.slice(root.length).split(node_path.default.sep).filter(Boolean);
2138
+ return root ? [root, ...segments] : segments;
2139
+ }
2140
+ function resolvePath(value, cwd) {
2141
+ return node_path.default.isAbsolute(value) ? value : node_path.default.resolve(cwd, value);
2142
+ }
2143
+ function logDiscovery(logger, message, context) {
2144
+ logger?.info(LogCategory.FILE_PROCESSING, message, context);
2145
+ }
2146
+
1977
2147
  //#endregion
1978
2148
  //#region src/cli/index.ts
1979
2149
  /**
@@ -1996,22 +2166,19 @@ function enforceMinimumNodeVersion() {
1996
2166
  process.exit(1);
1997
2167
  }
1998
2168
  enforceMinimumNodeVersion();
1999
- function resolveProjectPath(projectOption) {
2000
- const candidatePath = projectOption;
2001
- try {
2002
- if ((0, node_fs.statSync)(candidatePath).isDirectory()) {
2003
- const tsconfigPath = (0, node_path.join)(candidatePath, "tsconfig.json");
2004
- if ((0, node_fs.existsSync)(tsconfigPath)) return tsconfigPath;
2005
- }
2006
- } catch {}
2007
- return candidatePath;
2008
- }
2009
2169
  const program = new commander.Command();
2010
2170
  program.name("ng-di-graph").description("Angular DI dependency graph CLI tool").version("0.1.0");
2011
- program.argument("[filePaths...]", "TypeScript files to analyze (alias for --files)").option("-p, --project <path>", "tsconfig.json path", "./tsconfig.json").option("--files <paths...>", "specific file paths to analyze (similar to eslint targets)").option("-f, --format <format>", "output format: json | mermaid", "json").option("-e, --entry <symbol...>", "starting nodes for sub-graph").option("-d, --direction <dir>", "filtering direction: upstream|downstream|both", "downstream").option("--include-decorators", "include Optional/Self/SkipSelf/Host flags", false).option("--out <file>", "output file (stdout if omitted)").option("-v, --verbose", "show detailed parsing information", false);
2171
+ program.argument("[filePaths...]", "TypeScript files to analyze (alias for --files)").option("-p, --project <path>", "tsconfig.json path (auto-discovered if omitted)").option("--files <paths...>", "specific file paths to analyze (similar to eslint targets)").option("-f, --format <format>", "output format: json | mermaid", "json").option("-e, --entry <symbol...>", "starting nodes for sub-graph").option("-d, --direction <dir>", "filtering direction: upstream|downstream|both", "downstream").option("--include-decorators", "include Optional/Self/SkipSelf/Host flags", false).option("--out <file>", "output file (stdout if omitted)").option("-v, --verbose", "show detailed parsing information", false);
2012
2172
  program.action(async (filePaths = [], options) => {
2013
2173
  try {
2014
- const project = resolveProjectPath(options.project);
2174
+ const mergedFiles = mergeFileTargets(filePaths, options.files);
2175
+ const logger = createLogger(options.verbose);
2176
+ const project = resolveProjectPath({
2177
+ projectOption: options.project,
2178
+ fileTargets: mergedFiles,
2179
+ cwd: process.cwd(),
2180
+ logger
2181
+ });
2015
2182
  if (options.direction && ![
2016
2183
  "upstream",
2017
2184
  "downstream",
@@ -2020,7 +2187,6 @@ program.action(async (filePaths = [], options) => {
2020
2187
  if (options.format && !["json", "mermaid"].includes(options.format)) throw ErrorHandler.createError(`Invalid format: ${options.format}. Must be 'json' or 'mermaid'`, "INVALID_ARGUMENTS");
2021
2188
  const tsconfigLikePositional = filePaths.find((filePath) => /(?:^|[\\/])tsconfig(?:\.[^/\\]+)?\.json$/.test(filePath));
2022
2189
  if (tsconfigLikePositional) throw ErrorHandler.createError(`Positional argument "${tsconfigLikePositional}" looks like a tsconfig. Use --project "${tsconfigLikePositional}" instead.`, "INVALID_ARGUMENTS");
2023
- const mergedFiles = mergeFileTargets(filePaths, options.files);
2024
2190
  const cliOptions = {
2025
2191
  project,
2026
2192
  files: mergedFiles.length > 0 ? mergedFiles : void 0,
@@ -2031,7 +2197,6 @@ program.action(async (filePaths = [], options) => {
2031
2197
  out: options.out,
2032
2198
  verbose: options.verbose
2033
2199
  };
2034
- const logger = createLogger(cliOptions.verbose);
2035
2200
  if (logger) {
2036
2201
  logger.time("total-execution");
2037
2202
  logger.info(LogCategory.FILE_PROCESSING, "CLI execution started", {