tdepend 0.2.0 → 0.3.1
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/LICENSE +21 -0
- package/README.md +212 -116
- package/dist/api/analyze.d.ts +8 -68
- package/dist/api/analyze.js +33 -108
- package/dist/api/analyze.js.map +1 -1
- package/dist/api/explain.d.ts +22 -0
- package/dist/api/explain.js +38 -0
- package/dist/api/explain.js.map +1 -0
- package/dist/api/export.d.ts +4 -47
- package/dist/api/export.js +6 -45
- package/dist/api/export.js.map +1 -1
- package/dist/api/serialization.d.ts +6 -26
- package/dist/api/serialization.js +68 -58
- package/dist/api/serialization.js.map +1 -1
- package/dist/cli/index.js +11 -129
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/run.d.ts +12 -0
- package/dist/cli/run.js +112 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/config/loadConfig.d.ts +10 -10
- package/dist/config/loadConfig.js +73 -36
- package/dist/config/loadConfig.js.map +1 -1
- package/dist/config/schema.d.ts +59 -29
- package/dist/config/schema.js +45 -51
- package/dist/config/schema.js.map +1 -1
- package/dist/errors.d.ts +9 -0
- package/dist/errors.js +21 -0
- package/dist/errors.js.map +1 -0
- package/dist/files/collectFiles.d.ts +21 -0
- package/dist/files/collectFiles.js +189 -0
- package/dist/files/collectFiles.js.map +1 -0
- package/dist/fs.d.ts +16 -0
- package/dist/fs.js +50 -0
- package/dist/fs.js.map +1 -0
- package/dist/graph/buildFileGraph.d.ts +33 -0
- package/dist/graph/buildFileGraph.js +163 -0
- package/dist/graph/buildFileGraph.js.map +1 -0
- package/dist/graph/fileGraph.d.ts +20 -0
- package/dist/graph/fileGraph.js +75 -0
- package/dist/graph/fileGraph.js.map +1 -0
- package/dist/index.d.ts +10 -21
- package/dist/index.js +6 -29
- package/dist/index.js.map +1 -1
- package/dist/output/analyzeText.d.ts +7 -0
- package/dist/output/analyzeText.js +91 -0
- package/dist/output/analyzeText.js.map +1 -0
- package/dist/parser/extractSites.d.ts +3 -0
- package/dist/parser/extractSites.js +704 -0
- package/dist/parser/extractSites.js.map +1 -0
- package/dist/parser/loadsTarget.d.ts +10 -0
- package/dist/parser/loadsTarget.js +304 -0
- package/dist/parser/loadsTarget.js.map +1 -0
- package/dist/parser/types.d.ts +30 -0
- package/dist/{types/graph.js → parser/types.js} +1 -1
- package/dist/parser/types.js.map +1 -0
- package/dist/project/discoverProject.d.ts +2 -0
- package/dist/project/discoverProject.js +477 -0
- package/dist/project/discoverProject.js.map +1 -0
- package/dist/project/gitignore.d.ts +13 -0
- package/dist/project/gitignore.js +138 -0
- package/dist/project/gitignore.js.map +1 -0
- package/dist/project/types.d.ts +44 -0
- package/dist/{types/parser.js → project/types.js} +1 -1
- package/dist/project/types.js.map +1 -0
- package/dist/resolver/createResolver.d.ts +4 -0
- package/dist/resolver/createResolver.js +276 -0
- package/dist/resolver/createResolver.js.map +1 -0
- package/dist/resolver/types.d.ts +28 -0
- package/dist/resolver/types.js +3 -0
- package/dist/resolver/types.js.map +1 -0
- package/dist/resolver/workspaceHost.d.ts +12 -0
- package/dist/resolver/workspaceHost.js +253 -0
- package/dist/resolver/workspaceHost.js.map +1 -0
- package/dist/types/api.d.ts +71 -64
- package/package.json +24 -25
- package/dist/analysis/reporter.d.ts +0 -22
- package/dist/analysis/reporter.js +0 -116
- package/dist/analysis/reporter.js.map +0 -1
- package/dist/graph/cycleDetector.d.ts +0 -2
- package/dist/graph/cycleDetector.js +0 -56
- package/dist/graph/cycleDetector.js.map +0 -1
- package/dist/graph/dependencyGraph.d.ts +0 -10
- package/dist/graph/dependencyGraph.js +0 -41
- package/dist/graph/dependencyGraph.js.map +0 -1
- package/dist/metrics/abstractness.d.ts +0 -2
- package/dist/metrics/abstractness.js +0 -10
- package/dist/metrics/abstractness.js.map +0 -1
- package/dist/metrics/distance.d.ts +0 -1
- package/dist/metrics/distance.js +0 -7
- package/dist/metrics/distance.js.map +0 -1
- package/dist/metrics/index.d.ts +0 -4
- package/dist/metrics/index.js +0 -30
- package/dist/metrics/index.js.map +0 -1
- package/dist/metrics/instability.d.ts +0 -1
- package/dist/metrics/instability.js +0 -8
- package/dist/metrics/instability.js.map +0 -1
- package/dist/parser/fileScanner.d.ts +0 -8
- package/dist/parser/fileScanner.js +0 -22
- package/dist/parser/fileScanner.js.map +0 -1
- package/dist/parser/tsParser.d.ts +0 -18
- package/dist/parser/tsParser.js +0 -158
- package/dist/parser/tsParser.js.map +0 -1
- package/dist/types/graph.d.ts +0 -14
- package/dist/types/graph.js.map +0 -1
- package/dist/types/parser.d.ts +0 -26
- package/dist/types/parser.js.map +0 -1
- package/dist/utils/deepMerge.d.ts +0 -1
- package/dist/utils/deepMerge.js +0 -23
- package/dist/utils/deepMerge.js.map +0 -1
- package/dist/utils/log.d.ts +0 -2
- package/dist/utils/log.js +0 -9
- package/dist/utils/log.js.map +0 -1
- package/dist/utils/pathUtils.d.ts +0 -12
- package/dist/utils/pathUtils.js +0 -27
- package/dist/utils/pathUtils.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Mohamed Saad
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,167 +1,263 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
TDepend is a JDepend-inspired dependency analysis tool for the TypeScript ecosystem.
|
|
1
|
+
# tdepend
|
|
4
2
|
|
|
5
3
|
[](https://opensource.org/licenses/MIT)
|
|
6
4
|
|
|
7
|
-
|
|
5
|
+
tdepend's goal is to make TypeScript architecture contracts executable. Version 0.3 delivers the foundation those contracts need: an import graph that matches the TypeScript compiler.
|
|
6
|
+
|
|
7
|
+
It finds your projects, workspace packages and `tsconfig` files, then resolves every import through TypeScript's own resolver. For each import it records:
|
|
8
8
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- 📦 Zero runtime dependencies for analysis
|
|
9
|
+
- where it is, by file, line and column;
|
|
10
|
+
- what form it takes, such as a static import, a `require` or a type-only import;
|
|
11
|
+
- what it resolves to, in one of ten classes;
|
|
12
|
+
- whether it really loads its target at runtime, or the compiler erases it.
|
|
13
|
+
|
|
14
|
+
Architecture rules, metrics, cycles and baselines are not in this version. See [Migrating from 0.2](#migrating-from-02).
|
|
16
15
|
|
|
17
16
|
## Installation
|
|
18
17
|
|
|
18
|
+
tdepend needs Node 22.12 or later.
|
|
19
|
+
|
|
19
20
|
```bash
|
|
20
|
-
npm install -
|
|
21
|
+
npm install --save-dev tdepend
|
|
21
22
|
# or
|
|
22
|
-
pnpm add -
|
|
23
|
-
# or
|
|
24
|
-
yarn global add tdepend
|
|
23
|
+
pnpm add --save-dev tdepend
|
|
25
24
|
```
|
|
26
25
|
|
|
27
|
-
##
|
|
28
|
-
|
|
29
|
-
### CLI Usage
|
|
26
|
+
## Usage
|
|
30
27
|
|
|
31
28
|
```bash
|
|
32
|
-
#
|
|
33
|
-
tdepend analyze
|
|
29
|
+
tdepend analyze # report on the current directory
|
|
30
|
+
tdepend analyze --root path/to/repo # report on another directory
|
|
31
|
+
tdepend explain src/main.tsx # show every import in one file
|
|
32
|
+
```
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
tdepend analyze --config my-config.json
|
|
34
|
+
Both commands take `--root <dir>` and `--config <path>`. `tdepend --version` prints the version and `tdepend --help` lists the commands.
|
|
37
35
|
|
|
38
|
-
|
|
39
|
-
tdepend analyze --ci
|
|
36
|
+
tdepend reads the checkout as it is, so it works before a build and usually before `pnpm install`. The exception is a `tsconfig` whose `extends` names a package or a generated file. That target must exist, or tdepend exits 2 and names the config. tdepend never writes to the analysed repo unless you point `--export` there.
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
tdepend analyze --export analysis-result.json
|
|
43
|
-
```
|
|
38
|
+
### `tdepend analyze`
|
|
44
39
|
|
|
45
|
-
|
|
40
|
+
| Option | Meaning |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `-f, --format <text\|json>` | Output format, `text` by default |
|
|
43
|
+
| `-e, --export <path>` | Also write the full result to a JSON file |
|
|
44
|
+
| `--strict-resolution` | Exit 2 when any import is unresolved |
|
|
46
45
|
|
|
47
|
-
|
|
46
|
+
The exit code is 0, or 2 on a config or parse error, or when the analysis root doesn't exist. With `--strict-resolution` it is also 2 when an import is unresolved, which makes that flag the one gate this version offers for CI.
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
import { analyze, exportToFile } from 'tdepend';
|
|
48
|
+
The text report looks like this:
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
failOnCycle: true
|
|
57
|
-
});
|
|
50
|
+
```text
|
|
51
|
+
Files: 6
|
|
52
|
+
Import sites: 10
|
|
53
|
+
Edges: 2
|
|
58
54
|
|
|
59
|
-
|
|
60
|
-
|
|
55
|
+
Sites by class:
|
|
56
|
+
builtin: 1
|
|
57
|
+
asset: 2
|
|
58
|
+
virtual: 1
|
|
59
|
+
internal: 2
|
|
60
|
+
workspace: 0
|
|
61
|
+
excluded: 2
|
|
62
|
+
external: 0
|
|
63
|
+
ignored: 0
|
|
64
|
+
unsupported: 0
|
|
65
|
+
unresolved: 2
|
|
61
66
|
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
Unresolved imports (2):
|
|
68
|
+
src/main.tsx:6 ./missing
|
|
69
|
+
src/main.tsx:9 left-pad
|
|
64
70
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
+
Ignored (gitignored targets): 0
|
|
72
|
+
|
|
73
|
+
Unsupported imports: 0
|
|
74
|
+
|
|
75
|
+
Self-imports: 0
|
|
76
|
+
Files outside every tsconfig: 2
|
|
71
77
|
```
|
|
72
78
|
|
|
73
|
-
|
|
79
|
+
**Files** is the analysis set. **Import sites** counts every import statement or call. **Edges** counts the file pairs connected by at least one site.
|
|
74
80
|
|
|
75
|
-
|
|
76
|
-
- `analyze(options?)` - Analyze a TypeScript project
|
|
77
|
-
- `analyzeWithConfig(config)` - Analyze using a full config object
|
|
78
|
-
- `exportToFile(result, filePath, options?)` - Export analysis to JSON file
|
|
79
|
-
- `exportToJson(result, options?)` - Convert analysis to JSON string
|
|
81
|
+
Every site gets exactly one class:
|
|
80
82
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
| Class | Meaning | Adds a graph edge |
|
|
84
|
+
|---|---|---|
|
|
85
|
+
| `builtin` | A Node built-in; `fs` and `node:fs` count as the same module | No |
|
|
86
|
+
| `virtual` | A `virtual:` or `~icons/` module, or a `resolve.virtual` pattern | No |
|
|
87
|
+
| `asset` | A stylesheet, image, font, JSON or Markdown file, or anything with a `?query` | No |
|
|
88
|
+
| `internal` | A file in the analysis set, in the same package | Yes |
|
|
89
|
+
| `workspace` | A file in the analysis set, in a different workspace package | Yes |
|
|
90
|
+
| `excluded` | A repo file outside the analysis set, such as a test or generated file | No |
|
|
91
|
+
| `external` | A package from `node_modules`, or one declared in `package.json` but not installed | No |
|
|
92
|
+
| `ignored` | An unresolved import that matches `resolve.ignoreUnresolved`, or whose target is gitignored | No |
|
|
93
|
+
| `unsupported` | A form tdepend can't follow, such as `import(name)`, a shadowed `require`, or a JSDoc import type | No |
|
|
94
|
+
| `unresolved` | None of the above; this is usually a broken import | No |
|
|
87
95
|
|
|
88
|
-
|
|
96
|
+
The report also counts self-imports and files that no `tsconfig` covers. When a package is declared but missing, it adds a line asking whether `node_modules` is installed. Discovery warnings, such as a compiler option TypeScript 5.9 doesn't know, are listed at the end.
|
|
89
97
|
|
|
90
|
-
|
|
98
|
+
### `tdepend explain <file>`
|
|
91
99
|
|
|
92
|
-
|
|
100
|
+
`explain` is the debugging tool. It prints one row per import: position, kind, class, target and the runtime verdict with its reason.
|
|
93
101
|
|
|
94
|
-
|
|
102
|
+
```text
|
|
103
|
+
1:8 side-effect internal '@/components/button' -> src/components/button.tsx loads (retained): a side-effect import is evaluated and every compiler keeps it
|
|
104
|
+
3:8 side-effect asset './styles.css' -> - loads (retained): a side-effect import is evaluated and every compiler keeps it
|
|
105
|
+
7:27 static excluded './routeTree.gen' -> src/routeTree.gen.ts loads (retained): at least one binding is used as a value
|
|
106
|
+
10:16 static builtin 'node:fs' -> node:fs loads (retained): at least one binding is used as a value
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The verdict rule is conservative: an import loads its target unless `tsc`, esbuild and swc would all drop it. A reported runtime edge may therefore be one a single compiler would erase, but an import reported as erased is erased by every listed compiler.
|
|
110
|
+
|
|
111
|
+
The word in brackets is the kind of evidence. `syntax` means the form alone decides, as with `import type`. `usage` means no binding is used as a value. `retained` means a compiler keeps the import.
|
|
112
|
+
|
|
113
|
+
### JSON export
|
|
114
|
+
|
|
115
|
+
`--export <path>` and `--format json` write the same versioned document. It has no timestamp and every list is sorted, so two runs on the same inputs produce byte-identical output.
|
|
95
116
|
|
|
96
117
|
```json
|
|
97
118
|
{
|
|
98
|
-
"
|
|
99
|
-
"
|
|
100
|
-
"
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
119
|
+
"version": 1,
|
|
120
|
+
"files": ["src/main.ts", "src/util.ts"],
|
|
121
|
+
"edges": [
|
|
122
|
+
{ "from": "src/main.ts", "to": "src/util.ts", "runtime": true, "mapped": false, "kinds": ["static"] }
|
|
123
|
+
],
|
|
124
|
+
"sites": [
|
|
125
|
+
{
|
|
126
|
+
"file": "src/main.ts",
|
|
127
|
+
"specifier": "./util",
|
|
128
|
+
"line": 1,
|
|
129
|
+
"column": 22,
|
|
130
|
+
"kind": "static",
|
|
131
|
+
"form": "declaration",
|
|
132
|
+
"deferred": false,
|
|
133
|
+
"unsupported": null,
|
|
134
|
+
"loadsTarget": true,
|
|
135
|
+
"loadsTargetBy": "retained",
|
|
136
|
+
"class": "internal",
|
|
137
|
+
"reason": null,
|
|
138
|
+
"target": { "file": "src/util.ts" },
|
|
139
|
+
"mapped": null,
|
|
140
|
+
"requested": null
|
|
105
141
|
}
|
|
106
|
-
|
|
107
|
-
"
|
|
108
|
-
"target": null,
|
|
109
|
-
"value": null
|
|
110
|
-
},
|
|
111
|
-
"ci": {
|
|
112
|
-
"failOnThreshold": true,
|
|
113
|
-
"outputFormat": "json"
|
|
114
|
-
}
|
|
142
|
+
],
|
|
143
|
+
"report": { "...": "class counts, unresolved, ignored and unsupported sites, warnings" }
|
|
115
144
|
}
|
|
116
145
|
```
|
|
117
146
|
|
|
118
|
-
|
|
147
|
+
Paths are relative to the analysis root and use `/` on every platform. Lines and columns start at 1, and the column points at the opening quote of the specifier. An edge is `runtime` when at least one of its sites loads the target, and `mapped` when a declared output path such as `dist/index.d.ts` was mapped back to its source file.
|
|
119
148
|
|
|
120
|
-
|
|
121
|
-
- **include**: Glob patterns for files to include (default: `["src/**/*.ts", "src/**/*.tsx"]`)
|
|
122
|
-
- **exclude**: Glob patterns for files to exclude (default: `["**/*.test.ts", "**/*.spec.ts", "dist"]`)
|
|
123
|
-
- **metrics.enabled**: Array of metrics to compute (options: `"coupling"`, `"abstractness"`, `"distance"`, `"cycles"`)
|
|
124
|
-
- **metrics.thresholds.distance**: Maximum allowed distance from main sequence (default: `0.6`)
|
|
125
|
-
- **analysis.target**: Scope analysis to `"module"`, `"class"`, or `"namespace"` (default: `null`)
|
|
126
|
-
- **analysis.value**: Value for the target scope (default: `null`)
|
|
127
|
-
- **ci.failOnThreshold**: Exit with code 1 on threshold violations (default: `true`)
|
|
128
|
-
- **ci.failOnCycle**: Exit with code 1 when cycles are detected (default: `false`)
|
|
129
|
-
- **ci.outputFormat**: Output format - `"console"` or `"json"` (default: `"console"`)
|
|
149
|
+
## Library API
|
|
130
150
|
|
|
131
|
-
|
|
151
|
+
The package exports four functions, plus the types of the values they return.
|
|
132
152
|
|
|
133
|
-
```
|
|
134
|
-
|
|
135
|
-
|
|
153
|
+
```ts
|
|
154
|
+
import { analyze, explainFile, exportToFile, toSerializable } from 'tdepend';
|
|
155
|
+
|
|
156
|
+
const result = await analyze({ root: '.' });
|
|
136
157
|
|
|
137
|
-
|
|
138
|
-
|
|
158
|
+
const { edges, report } = toSerializable(result);
|
|
159
|
+
console.log(`${edges.length} edges, ${report.unresolved.length} unresolved imports`);
|
|
139
160
|
|
|
140
|
-
|
|
141
|
-
pnpm test
|
|
161
|
+
await exportToFile(result, 'graph.json');
|
|
142
162
|
|
|
143
|
-
|
|
144
|
-
|
|
163
|
+
const rows = await explainFile({ root: '.', file: 'src/main.tsx' });
|
|
164
|
+
for (const row of rows) {
|
|
165
|
+
console.log(row.specifier, row.class, row.loadsTarget);
|
|
166
|
+
}
|
|
145
167
|
```
|
|
146
168
|
|
|
147
|
-
|
|
169
|
+
- `analyze(options?)` runs the whole pipeline. Its options are `root`, `config` and `typeOnly`.
|
|
170
|
+
- `explainFile(options)` takes the same options plus `file`, and returns one row per import.
|
|
171
|
+
- `toSerializable(result)` returns the JSON document above.
|
|
172
|
+
- `exportToFile(result, path)` writes that document to a file.
|
|
173
|
+
|
|
174
|
+
## Configuration
|
|
175
|
+
|
|
176
|
+
tdepend looks for `tdepend.config.json` in the analysis root, or uses `--config <path>`. A config is optional. These are the keys that work in 0.3, with their defaults:
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{
|
|
180
|
+
"root": ".",
|
|
181
|
+
"include": [],
|
|
182
|
+
"exclude": [],
|
|
183
|
+
"typeOnly": "usage",
|
|
184
|
+
"compilers": ["tsc", "esbuild", "swc"],
|
|
185
|
+
"resolve": { "aliases": {}, "virtual": [], "ignoreUnresolved": [] }
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
- **`root`** is the analysis root, relative to `--root` or the working directory.
|
|
190
|
+
- **`include`** replaces discovery when it isn't empty. It takes root-relative globs, and `exclude` still applies.
|
|
191
|
+
- **`exclude`** adds to the built-in skips below. An entry starting with `!` removes an earlier pattern, so `"!**/public/**"` keeps `public/`.
|
|
192
|
+
- **`typeOnly`** is `usage` or `syntax`. With `syntax`, every static import counts as loading its target, however its bindings are used.
|
|
193
|
+
- **`compilers`** names the compilers you really use. In 0.3 it only changes empty `import {} from` statements, which `tsc` erases and swc keeps.
|
|
194
|
+
- **`resolve.aliases`** is for aliases that exist only in a bundler config, as a prefix map relative to the importing file's project, such as `{ "@/": "src/" }`. Aliases in `tsconfig` `paths` need no config.
|
|
195
|
+
- **`resolve.virtual`** lists extra patterns to treat as `virtual`.
|
|
196
|
+
- **`resolve.ignoreUnresolved`** lists unresolved imports to class as `ignored`.
|
|
197
|
+
|
|
198
|
+
The built-in skips are test and spec files, `__tests__`, stories, declaration files, `*.min.js`, `public` and a root `e2e` folder. Gitignored files are skipped too. Directories named `node_modules`, `dist`, `build`, `out` and `coverage`, and dot directories such as `.storybook`, are never walked, so `include` can't reach them. A `tsconfig` that lists a file in a dot directory still brings that file in.
|
|
199
|
+
|
|
200
|
+
`exclude` also applies to `tsconfig` files, so a deliberately broken one under a fixtures folder can be left out.
|
|
201
|
+
|
|
202
|
+
The keys `presets` and `metrics.abstractness` are accepted and have no effect yet. An unknown key exits 2, and so do `rules`, `components`, `baseline` and `metrics.thresholds`, which belong to later releases.
|
|
203
|
+
|
|
204
|
+
## Migrating from 0.2
|
|
205
|
+
|
|
206
|
+
Version 0.3 is a rewrite, with no compatibility layer. The old graph missed aliases, re-exports, dynamic imports and workspace packages, so it was replaced rather than patched.
|
|
207
|
+
|
|
208
|
+
- **Metrics and cycles are gone for now.** 0.3 reports the graph only. Coupling metrics, cycles and rules arrive in 0.4, through a new `check` command with baselines. Comparing a pull request against its base arrives in 0.5.
|
|
209
|
+
- **Removed exports:** `analyzeWithConfig`, `exportToJson`, `DependencyGraph`, `detectCycles`, `computeAllMetrics`, `parseProject` and `scanFiles`. The public API is now the four functions above.
|
|
210
|
+
- **Removed CLI option:** `--ci`. Use `--format json` for machine output and `--strict-resolution` to fail a build.
|
|
211
|
+
- **Removed config keys:** `rootDir`, `ci`, `analysis` and `metrics.enabled`. `metrics.thresholds` returns in 0.4.
|
|
212
|
+
- **Old configs are rejected.** A config with `rootDir`, `ci` or `analysis` exits 2 with this message:
|
|
213
|
+
|
|
214
|
+
```text
|
|
215
|
+
This looks like a tdepend 0.2 config. See 'Migrating from 0.2' in the README.
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Start again from the keys in [Configuration](#configuration).
|
|
219
|
+
- **Node 22.12 or later** is now required.
|
|
220
|
+
|
|
221
|
+
## Support levels
|
|
222
|
+
|
|
223
|
+
| Level | Layouts |
|
|
224
|
+
|---|---|
|
|
225
|
+
| **Supported**, covered by a fixture | NestJS with CommonJS or NodeNext, NestJS monorepos using `paths`, Next.js apps, Vite with TanStack Router, TanStack Start, Express in CommonJS JavaScript, Fastify in ESM, plain Node libraries, feature-based frontends, pnpm and npm workspaces including unbuilt and bundler-built packages, several apps without a workspace config, nested projects |
|
|
226
|
+
| **Configured manually** | Aliases that exist only in a bundler config, through `resolve.aliases`; unusual layouts, through `include` |
|
|
227
|
+
| **Experimental** | Yarn workspaces without Plug'n'Play, which use the same `package.json` form as npm but have no fixture of their own |
|
|
228
|
+
| **Unsupported** | Vue, Svelte and Astro single-file components, Yarn Plug'n'Play, non-literal dynamic loading |
|
|
229
|
+
|
|
230
|
+
## Development
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
pnpm install
|
|
234
|
+
pnpm run clean # remove dist
|
|
235
|
+
pnpm run build # clean, then compile
|
|
236
|
+
pnpm test # run the test suite
|
|
237
|
+
pnpm run typecheck # check the sources and the tests
|
|
148
238
|
```
|
|
239
|
+
|
|
240
|
+
## Project structure
|
|
241
|
+
|
|
242
|
+
```text
|
|
149
243
|
src/
|
|
150
|
-
api/
|
|
151
|
-
cli/
|
|
152
|
-
config/
|
|
153
|
-
|
|
154
|
-
graph/
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
244
|
+
api/ analyze, explainFile, exportToFile, toSerializable
|
|
245
|
+
cli/ command line entry point
|
|
246
|
+
config/ config schema and loading
|
|
247
|
+
files/ the analysis set
|
|
248
|
+
graph/ the file graph and resolution report
|
|
249
|
+
output/ the text report
|
|
250
|
+
parser/ import sites and runtime-load classification
|
|
251
|
+
project/ projects, workspace packages, tsconfigs, gitignore rules
|
|
252
|
+
resolver/ TypeScript resolution, workspace host, classification
|
|
253
|
+
tests/ tests and fixtures
|
|
254
|
+
types/ public result types
|
|
255
|
+
fs.ts file system port
|
|
256
|
+
errors.ts errors that exit 2
|
|
257
|
+
index.ts library entry point
|
|
258
|
+
scripts/ regenerates the compiler fixture expectations
|
|
164
259
|
```
|
|
165
260
|
|
|
166
261
|
## License
|
|
167
|
-
|
|
262
|
+
|
|
263
|
+
[MIT](LICENSE)
|
package/dist/api/analyze.d.ts
CHANGED
|
@@ -1,75 +1,15 @@
|
|
|
1
|
-
import type { TDependConfig } from '../config/schema';
|
|
2
1
|
import type { AnalysisResult } from '../types/api';
|
|
3
|
-
/**
|
|
4
|
-
* Options for the analyze() function.
|
|
5
|
-
* These options are merged with config file settings (if any).
|
|
6
|
-
*/
|
|
7
2
|
export interface AnalyzeOptions {
|
|
8
|
-
/** Root directory to
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
include?: string[];
|
|
12
|
-
/** Glob patterns for files to exclude (default: ['**\/*.test.ts', '**\/*.spec.ts', 'dist']) */
|
|
13
|
-
exclude?: string[];
|
|
14
|
-
/** Path to config file. If not provided, looks for tdepend.config.json */
|
|
3
|
+
/** Root directory to analyse. Defaults to the current working directory. */
|
|
4
|
+
root?: string;
|
|
5
|
+
/** Path to the tdepend config file. Discovered under the root when omitted. */
|
|
15
6
|
config?: string;
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
/** Fail analysis if distance threshold is exceeded (default: true) */
|
|
19
|
-
failOnThreshold?: boolean;
|
|
7
|
+
/** Overrides the config's type-only detection for this run. */
|
|
8
|
+
typeOnly?: 'usage' | 'syntax';
|
|
20
9
|
}
|
|
21
10
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* It orchestrates the entire analysis pipeline:
|
|
26
|
-
* 1. Load and merge configuration
|
|
27
|
-
* 2. Scan files matching include/exclude patterns
|
|
28
|
-
* 3. Parse TypeScript modules
|
|
29
|
-
* 4. Build dependency graph
|
|
30
|
-
* 5. Detect cycles
|
|
31
|
-
* 6. Compute metrics (coupling, abstractness, instability, distance)
|
|
32
|
-
* 7. Generate report with violations
|
|
33
|
-
*
|
|
34
|
-
* @param options - Analysis options (merged with config file if present)
|
|
35
|
-
* @returns Complete analysis result with modules, graph, metrics, and report
|
|
36
|
-
*
|
|
37
|
-
* @example
|
|
38
|
-
* ```typescript
|
|
39
|
-
* import { analyze } from 'tdepend';
|
|
40
|
-
*
|
|
41
|
-
* const result = await analyze({
|
|
42
|
-
* rootDir: 'src',
|
|
43
|
-
* include: ['src/**\/*.ts'],
|
|
44
|
-
* failOnCycle: true
|
|
45
|
-
* });
|
|
46
|
-
*
|
|
47
|
-
* console.log(`Analyzed ${result.modules.length} modules`);
|
|
48
|
-
* console.log(`Found ${result.cycles.length} cycles`);
|
|
49
|
-
* ```
|
|
11
|
+
* Runs the whole pipeline once: discover projects, packages and tsconfigs,
|
|
12
|
+
* collect the analysis set, parse every file's import sites, resolve each site
|
|
13
|
+
* through TypeScript with a workspace host, and build the file graph.
|
|
50
14
|
*/
|
|
51
15
|
export declare function analyze(options?: AnalyzeOptions): Promise<AnalysisResult>;
|
|
52
|
-
/**
|
|
53
|
-
* Analyzes a TypeScript project using a provided configuration object.
|
|
54
|
-
*
|
|
55
|
-
* This is useful when you want full control over the configuration
|
|
56
|
-
* without loading from a file or merging with defaults.
|
|
57
|
-
*
|
|
58
|
-
* @param config - Complete TDependConfig object
|
|
59
|
-
* @returns Complete analysis result with modules, graph, metrics, and report
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* ```typescript
|
|
63
|
-
* import { analyzeWithConfig, defaultConfig } from 'tdepend';
|
|
64
|
-
*
|
|
65
|
-
* const result = await analyzeWithConfig({
|
|
66
|
-
* ...defaultConfig,
|
|
67
|
-
* rootDir: 'src',
|
|
68
|
-
* ci: {
|
|
69
|
-
* ...defaultConfig.ci,
|
|
70
|
-
* failOnCycle: true
|
|
71
|
-
* }
|
|
72
|
-
* });
|
|
73
|
-
* ```
|
|
74
|
-
*/
|
|
75
|
-
export declare function analyzeWithConfig(config: TDependConfig): Promise<AnalysisResult>;
|
package/dist/api/analyze.js
CHANGED
|
@@ -1,120 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.analyze = analyze;
|
|
4
|
-
|
|
4
|
+
const node_path_1 = require("node:path");
|
|
5
5
|
const loadConfig_1 = require("../config/loadConfig");
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const deepMerge_1 = require("../utils/deepMerge");
|
|
6
|
+
const errors_1 = require("../errors");
|
|
7
|
+
const collectFiles_1 = require("../files/collectFiles");
|
|
8
|
+
const fs_1 = require("../fs");
|
|
9
|
+
const buildFileGraph_1 = require("../graph/buildFileGraph");
|
|
10
|
+
const discoverProject_1 = require("../project/discoverProject");
|
|
11
|
+
const createResolver_1 = require("../resolver/createResolver");
|
|
13
12
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* It orchestrates the entire analysis pipeline:
|
|
18
|
-
* 1. Load and merge configuration
|
|
19
|
-
* 2. Scan files matching include/exclude patterns
|
|
20
|
-
* 3. Parse TypeScript modules
|
|
21
|
-
* 4. Build dependency graph
|
|
22
|
-
* 5. Detect cycles
|
|
23
|
-
* 6. Compute metrics (coupling, abstractness, instability, distance)
|
|
24
|
-
* 7. Generate report with violations
|
|
25
|
-
*
|
|
26
|
-
* @param options - Analysis options (merged with config file if present)
|
|
27
|
-
* @returns Complete analysis result with modules, graph, metrics, and report
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* ```typescript
|
|
31
|
-
* import { analyze } from 'tdepend';
|
|
32
|
-
*
|
|
33
|
-
* const result = await analyze({
|
|
34
|
-
* rootDir: 'src',
|
|
35
|
-
* include: ['src/**\/*.ts'],
|
|
36
|
-
* failOnCycle: true
|
|
37
|
-
* });
|
|
38
|
-
*
|
|
39
|
-
* console.log(`Analyzed ${result.modules.length} modules`);
|
|
40
|
-
* console.log(`Found ${result.cycles.length} cycles`);
|
|
41
|
-
* ```
|
|
13
|
+
* Runs the whole pipeline once: discover projects, packages and tsconfigs,
|
|
14
|
+
* collect the analysis set, parse every file's import sites, resolve each site
|
|
15
|
+
* through TypeScript with a workspace host, and build the file graph.
|
|
42
16
|
*/
|
|
43
17
|
async function analyze(options = {}) {
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
optionsConfig.rootDir = options.rootDir;
|
|
18
|
+
const baseDir = (0, node_path_1.resolve)(options.root ?? process.cwd());
|
|
19
|
+
const { config } = (0, loadConfig_1.loadConfig)({ cwd: baseDir, configPath: options.config });
|
|
20
|
+
const rootDir = config.root === '.' ? baseDir : (0, node_path_1.resolve)(baseDir, config.root);
|
|
21
|
+
if (!(0, fs_1.isDir)((0, fs_1.nodeFileSystem)(), rootDir)) {
|
|
22
|
+
throw new errors_1.ConfigError(`Root directory not found: ${rootDir}`);
|
|
50
23
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
// Merge configs (options override file config)
|
|
65
|
-
const config = (0, deepMerge_1.deepMerge)(baseConfig, optionsConfig);
|
|
66
|
-
// Run analysis pipeline
|
|
67
|
-
const files = await (0, fileScanner_1.scanFiles)(config);
|
|
68
|
-
const modules = (0, tsParser_1.parseProject)(files);
|
|
69
|
-
const graph = new dependencyGraph_1.DependencyGraph(modules);
|
|
70
|
-
const cycles = (0, cycleDetector_1.detectCycles)(graph);
|
|
71
|
-
const metrics = (0, metrics_1.computeAllMetrics)(graph, modules, cycles);
|
|
72
|
-
const report = (0, reporter_1.generateReport)(modules, metrics, cycles, config);
|
|
73
|
-
return {
|
|
74
|
-
modules,
|
|
75
|
-
graph,
|
|
76
|
-
cycles,
|
|
77
|
-
metrics,
|
|
78
|
-
report,
|
|
79
|
-
config,
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* Analyzes a TypeScript project using a provided configuration object.
|
|
84
|
-
*
|
|
85
|
-
* This is useful when you want full control over the configuration
|
|
86
|
-
* without loading from a file or merging with defaults.
|
|
87
|
-
*
|
|
88
|
-
* @param config - Complete TDependConfig object
|
|
89
|
-
* @returns Complete analysis result with modules, graph, metrics, and report
|
|
90
|
-
*
|
|
91
|
-
* @example
|
|
92
|
-
* ```typescript
|
|
93
|
-
* import { analyzeWithConfig, defaultConfig } from 'tdepend';
|
|
94
|
-
*
|
|
95
|
-
* const result = await analyzeWithConfig({
|
|
96
|
-
* ...defaultConfig,
|
|
97
|
-
* rootDir: 'src',
|
|
98
|
-
* ci: {
|
|
99
|
-
* ...defaultConfig.ci,
|
|
100
|
-
* failOnCycle: true
|
|
101
|
-
* }
|
|
102
|
-
* });
|
|
103
|
-
* ```
|
|
104
|
-
*/
|
|
105
|
-
async function analyzeWithConfig(config) {
|
|
106
|
-
const files = await (0, fileScanner_1.scanFiles)(config);
|
|
107
|
-
const modules = (0, tsParser_1.parseProject)(files);
|
|
108
|
-
const graph = new dependencyGraph_1.DependencyGraph(modules);
|
|
109
|
-
const cycles = (0, cycleDetector_1.detectCycles)(graph);
|
|
110
|
-
const metrics = (0, metrics_1.computeAllMetrics)(graph, modules, cycles);
|
|
111
|
-
const report = (0, reporter_1.generateReport)(modules, metrics, cycles, config);
|
|
24
|
+
const ctx = (0, discoverProject_1.discoverProject)(rootDir, {
|
|
25
|
+
isExcluded: (0, collectFiles_1.createExcludeMatcher)(collectFiles_1.DEFAULT_SKIPS, config.exclude),
|
|
26
|
+
isConfigExcluded: (0, collectFiles_1.createExcludeMatcher)([], config.exclude),
|
|
27
|
+
});
|
|
28
|
+
const files = (0, collectFiles_1.collectFiles)(ctx, {
|
|
29
|
+
include: config.include,
|
|
30
|
+
exclude: config.exclude,
|
|
31
|
+
presetSkips: collectFiles_1.DEFAULT_SKIPS,
|
|
32
|
+
});
|
|
33
|
+
const resolver = (0, createResolver_1.createResolver)(ctx, new Set(files.files), config.resolve);
|
|
34
|
+
const built = (0, buildFileGraph_1.buildFileGraph)(ctx, files, resolver, options.typeOnly ?? config.typeOnly, {
|
|
35
|
+
compilers: config.compilers,
|
|
36
|
+
});
|
|
112
37
|
return {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
report,
|
|
38
|
+
ctx,
|
|
39
|
+
files,
|
|
40
|
+
graph: built.graph,
|
|
41
|
+
sites: built.sites,
|
|
42
|
+
report: built.report,
|
|
118
43
|
config,
|
|
119
44
|
};
|
|
120
45
|
}
|