@rsdk/depdoc.cli 6.0.0-next.42 → 6.0.0-next.43
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/DEPDOC_MODEL.md +22 -11
- package/__tests__/compatibility.test.ts +74 -15
- package/__tests__/config-validation.test.ts +16 -1
- package/__tests__/engine.test.ts +77 -0
- package/__tests__/fixtures/imports/config/aliases.json +8 -0
- package/__tests__/fixtures/imports/src/aliases.ts +4 -0
- package/__tests__/fixtures/imports/src/virtual.ts +3 -0
- package/__tests__/fixtures/imports/tsconfig.build.json +3 -0
- package/__tests__/fixtures/imports/tsconfig.json +8 -0
- package/__tests__/imports.test.ts +31 -0
- package/dist/collectors/tsconfig-aliases.d.ts +1 -0
- package/dist/collectors/tsconfig-aliases.js +40 -0
- package/dist/collectors/tsconfig-aliases.js.map +1 -0
- package/dist/collectors/workspaces.d.ts +1 -1
- package/dist/collectors/workspaces.js +12 -5
- package/dist/collectors/workspaces.js.map +1 -1
- package/dist/lib/imports.d.ts +6 -3
- package/dist/lib/imports.js +24 -8
- package/dist/lib/imports.js.map +1 -1
- package/dist/model/config-validation.d.ts +1 -0
- package/dist/model/config-validation.js +15 -1
- package/dist/model/config-validation.js.map +1 -1
- package/dist/model/diagnostics.d.ts +1 -0
- package/dist/model/diagnostics.js +67 -0
- package/dist/model/diagnostics.js.map +1 -1
- package/dist/model/engine.js +1 -0
- package/dist/model/engine.js.map +1 -1
- package/dist/model/types.d.ts +2 -1
- package/dist/model/types.js.map +1 -1
- package/dist/runner.js +1 -1
- package/dist/runner.js.map +1 -1
- package/package.json +2 -2
- package/src/collectors/tsconfig-aliases.ts +45 -0
- package/src/collectors/workspaces.ts +17 -4
- package/src/lib/imports.ts +57 -6
- package/src/model/config-validation.ts +17 -1
- package/src/model/diagnostics.ts +105 -1
- package/src/model/engine.ts +7 -1
- package/src/model/types.ts +2 -0
- package/src/runner.ts +5 -1
package/DEPDOC_MODEL.md
CHANGED
|
@@ -19,6 +19,7 @@ autofixable.
|
|
|
19
19
|
| Public type surface | Imports that appear in emitted `dist/**/*.d.ts`. |
|
|
20
20
|
| Private dev surface | Test, build, lint, type-only, and tooling usage that does not reach runtime or public `.d.ts`. |
|
|
21
21
|
| Mirror | A `library` package has the same dependency name and range in both `peerDependencies` and `devDependencies`. |
|
|
22
|
+
| Ignored import | A module specifier that appears in source but is provided virtually or resolved as an internal alias, so it is not a package dependency. |
|
|
22
23
|
|
|
23
24
|
## Invariants
|
|
24
25
|
|
|
@@ -86,6 +87,7 @@ autofixable.
|
|
|
86
87
|
| V3 | The constraints config does not describe the whole model. | It contains versions, overrides, root-only rules, manual required dependencies, and workspace-specific exceptions. |
|
|
87
88
|
| V4 | The last matching rule wins. | Enables broad rules plus specific overrides. |
|
|
88
89
|
| V5 | `rootOnly` means the dependency belongs only to the root dev environment. | Workspace declarations and source usage are forbidden unless a more specific override exists. `required` + `rootOnly` means required for the selected workspace but physically owned by root `devDependencies`. |
|
|
90
|
+
| V6 | Concrete dependency rules must describe packages that exist in the repo model. | Wildcard policy rules are allowed, but concrete stale `depdoc.yml` entries are reported. |
|
|
89
91
|
|
|
90
92
|
## Placement Formula
|
|
91
93
|
|
|
@@ -114,8 +116,9 @@ guess something that an earlier phase can derive.
|
|
|
114
116
|
1. Read root `package.json`.
|
|
115
117
|
2. Read every workspace `package.json`.
|
|
116
118
|
3. Build the set of local workspace package names.
|
|
117
|
-
4. Read dependency rules: version rules, section
|
|
118
|
-
manual `required` dependencies, and
|
|
119
|
+
4. Read ignored import patterns and dependency rules: version rules, section
|
|
120
|
+
overrides, `rootOnly` rules, manual `required` dependencies, and
|
|
121
|
+
workspace-specific exceptions.
|
|
119
122
|
5. Read package manager metadata overrides, such as `packageExtensions`.
|
|
120
123
|
|
|
121
124
|
### Phase 2. Validate Basic Shape
|
|
@@ -136,16 +139,21 @@ guess something that an earlier phase can derive.
|
|
|
136
139
|
1. Scan source imports for every workspace from `src/`, `test/`, `tests/`,
|
|
137
140
|
`__tests__/`, concrete package entry-points, and relative imports reachable
|
|
138
141
|
from those entry-points.
|
|
139
|
-
2.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
2. Ignore module specifiers from `ignoredImports` and every `compilerOptions.paths`
|
|
143
|
+
alias collected from root-level `tsconfig*.json` files in the workspace.
|
|
144
|
+
For example, `@docusaurus/router` can be ignored as a virtual Docusaurus
|
|
145
|
+
runtime module, and `~app/*` / `~shared/*` are ignored when declared as
|
|
146
|
+
TypeScript path aliases.
|
|
147
|
+
3. Classify each import as local or external.
|
|
148
|
+
4. Classify each import as runtime, type-only, test-only, or tooling-only.
|
|
149
|
+
5. Scan emitted `dist/**/*.d.ts` imports when `dist/` exists.
|
|
150
|
+
6. In `--with-dts` mode, require `dist/` for workspaces with `src/`; without
|
|
143
151
|
`--with-dts`, missing `dist/` is allowed so local source-only checks remain
|
|
144
152
|
usable before build.
|
|
145
|
-
|
|
146
|
-
|
|
153
|
+
7. Mark all dependencies found in emitted `.d.ts` as public type surface.
|
|
154
|
+
8. Skip generated entry-point roots such as `dist/`, `build/`, `coverage/`, and
|
|
147
155
|
`node_modules/`.
|
|
148
|
-
|
|
156
|
+
9. Warn when a workspace has scanned source files and declared dependencies,
|
|
149
157
|
but zero collected source or `.d.ts` package usage.
|
|
150
158
|
|
|
151
159
|
### Phase 4. Derive Required Public/Runtime Deps
|
|
@@ -214,8 +222,11 @@ For each manifest:
|
|
|
214
222
|
3. Report dependencies with the wrong version range.
|
|
215
223
|
4. Report forbidden dependencies.
|
|
216
224
|
5. Report stale dependencies that are not required by the derived graph.
|
|
217
|
-
6. Report
|
|
218
|
-
|
|
225
|
+
6. Report stale concrete dependency rules from `depdoc.yml` when no matching
|
|
226
|
+
source usage, `.d.ts` usage, manifest declaration, or expected dependency
|
|
227
|
+
exists in the matching workspace scope.
|
|
228
|
+
7. Report missing or incorrect library peer/dev mirrors.
|
|
229
|
+
8. Report root-only violations.
|
|
219
230
|
|
|
220
231
|
### Phase 9. Autofix
|
|
221
232
|
|
|
@@ -4,8 +4,8 @@ import { tmpdir } from 'node:os';
|
|
|
4
4
|
import { resolve } from 'node:path';
|
|
5
5
|
|
|
6
6
|
// Paths relative to this test file:
|
|
7
|
-
// __tests__/ -> depdoc/ -> packages/ -> monorepo root
|
|
8
|
-
const MONOREPO_ROOT = resolve(__dirname, '
|
|
7
|
+
// __tests__/ -> depdoc/ -> dx/ -> packages/ -> monorepo root
|
|
8
|
+
const MONOREPO_ROOT = resolve(__dirname, '../../../..');
|
|
9
9
|
const YARN_BIN = resolve(MONOREPO_ROOT, '.yarn/releases/yarn-4.5.3.cjs');
|
|
10
10
|
const YARN_CACHE = resolve(MONOREPO_ROOT, '.yarn/cache');
|
|
11
11
|
// Reuse the main project's tsc instead of installing typescript in each temp monorepo.
|
|
@@ -136,13 +136,20 @@ describe('compatibility: yarn-install-clean', () => {
|
|
|
136
136
|
const { dir, cleanup: c } = createMonorepo(
|
|
137
137
|
{ private: true, name: '@compat/root' },
|
|
138
138
|
[
|
|
139
|
+
{
|
|
140
|
+
location: 'packages/lodash',
|
|
141
|
+
manifest: {
|
|
142
|
+
name: 'lodash',
|
|
143
|
+
version: '4.17.21',
|
|
144
|
+
},
|
|
145
|
+
},
|
|
139
146
|
{
|
|
140
147
|
location: 'packages/lib',
|
|
141
148
|
manifest: {
|
|
142
149
|
name: '@compat/lib',
|
|
143
150
|
version: '1.0.0',
|
|
144
|
-
peerDependencies: { lodash: '
|
|
145
|
-
devDependencies: { lodash: '
|
|
151
|
+
peerDependencies: { lodash: '*' },
|
|
152
|
+
devDependencies: { lodash: '*' },
|
|
146
153
|
},
|
|
147
154
|
},
|
|
148
155
|
{
|
|
@@ -152,7 +159,7 @@ describe('compatibility: yarn-install-clean', () => {
|
|
|
152
159
|
version: '1.0.0',
|
|
153
160
|
dependencies: {
|
|
154
161
|
'@compat/lib': 'workspace:*',
|
|
155
|
-
lodash: '
|
|
162
|
+
lodash: '*',
|
|
156
163
|
},
|
|
157
164
|
},
|
|
158
165
|
},
|
|
@@ -175,7 +182,7 @@ describe('compatibility: yarn-install-clean', () => {
|
|
|
175
182
|
});
|
|
176
183
|
|
|
177
184
|
// ---------------------------------------------------------------------------
|
|
178
|
-
// yarn-peer-requirements-clean: required
|
|
185
|
+
// yarn-peer-requirements-clean: required peers propagated to service
|
|
179
186
|
// ---------------------------------------------------------------------------
|
|
180
187
|
|
|
181
188
|
describe('compatibility: yarn-peer-requirements-clean', () => {
|
|
@@ -184,20 +191,45 @@ describe('compatibility: yarn-peer-requirements-clean', () => {
|
|
|
184
191
|
let cleanup: () => void;
|
|
185
192
|
|
|
186
193
|
beforeAll(() => {
|
|
187
|
-
// @
|
|
188
|
-
//
|
|
194
|
+
// @compat/framework requires rxjs and reflect-metadata. The model should
|
|
195
|
+
// propagate both into service dependencies (invariant P2 + S1).
|
|
189
196
|
const { dir, cleanup: c } = createMonorepo(
|
|
190
197
|
{ private: true, name: '@compat/root' },
|
|
191
198
|
[
|
|
199
|
+
{
|
|
200
|
+
location: 'packages/framework',
|
|
201
|
+
manifest: {
|
|
202
|
+
name: '@compat/framework',
|
|
203
|
+
version: '1.0.0',
|
|
204
|
+
peerDependencies: {
|
|
205
|
+
rxjs: '*',
|
|
206
|
+
'reflect-metadata': '*',
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
location: 'packages/rxjs',
|
|
212
|
+
manifest: {
|
|
213
|
+
name: 'rxjs',
|
|
214
|
+
version: '7.8.2',
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
location: 'packages/reflect-metadata',
|
|
219
|
+
manifest: {
|
|
220
|
+
name: 'reflect-metadata',
|
|
221
|
+
version: '0.2.2',
|
|
222
|
+
},
|
|
223
|
+
},
|
|
192
224
|
{
|
|
193
225
|
location: 'packages/svc',
|
|
194
226
|
manifest: {
|
|
195
227
|
name: '@compat/svc',
|
|
196
228
|
version: '1.0.0',
|
|
197
229
|
dependencies: {
|
|
198
|
-
'@
|
|
199
|
-
rxjs: '
|
|
200
|
-
'reflect-metadata': '
|
|
230
|
+
'@compat/framework': 'workspace:*',
|
|
231
|
+
rxjs: '*',
|
|
232
|
+
'reflect-metadata': '*',
|
|
201
233
|
},
|
|
202
234
|
},
|
|
203
235
|
},
|
|
@@ -233,12 +265,23 @@ describe('compatibility: missing-peer-detected (negative)', () => {
|
|
|
233
265
|
const { dir, cleanup: c } = createMonorepo(
|
|
234
266
|
{ private: true, name: '@compat/root' },
|
|
235
267
|
[
|
|
268
|
+
{
|
|
269
|
+
location: 'packages/framework',
|
|
270
|
+
manifest: {
|
|
271
|
+
name: '@compat/framework',
|
|
272
|
+
version: '1.0.0',
|
|
273
|
+
peerDependencies: {
|
|
274
|
+
rxjs: '*',
|
|
275
|
+
'reflect-metadata': '*',
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
},
|
|
236
279
|
{
|
|
237
280
|
location: 'packages/svc',
|
|
238
281
|
manifest: {
|
|
239
282
|
name: '@compat/svc',
|
|
240
283
|
version: '1.0.0',
|
|
241
|
-
dependencies: { '@
|
|
284
|
+
dependencies: { '@compat/framework': 'workspace:*' },
|
|
242
285
|
},
|
|
243
286
|
},
|
|
244
287
|
],
|
|
@@ -268,19 +311,35 @@ describe('compatibility: tsc-library-build', () => {
|
|
|
268
311
|
let cleanup: () => void;
|
|
269
312
|
|
|
270
313
|
beforeAll(() => {
|
|
271
|
-
// Library imports rxjs (
|
|
314
|
+
// Library imports rxjs (provided by a local workspace with types). After install the
|
|
272
315
|
// dev mirror (devDependencies = peerDependencies) makes rxjs available for
|
|
273
316
|
// tsc without any additional @types packages.
|
|
274
317
|
const { dir, cleanup: c } = createMonorepo(
|
|
275
318
|
{ private: true, name: '@compat/root' },
|
|
276
319
|
[
|
|
320
|
+
{
|
|
321
|
+
location: 'packages/rxjs',
|
|
322
|
+
manifest: {
|
|
323
|
+
name: 'rxjs',
|
|
324
|
+
version: '7.8.2',
|
|
325
|
+
types: 'index.d.ts',
|
|
326
|
+
},
|
|
327
|
+
files: {
|
|
328
|
+
'index.d.ts': [
|
|
329
|
+
'export declare class Observable<T> {',
|
|
330
|
+
' readonly value?: T;',
|
|
331
|
+
'}',
|
|
332
|
+
'',
|
|
333
|
+
].join('\n'),
|
|
334
|
+
},
|
|
335
|
+
},
|
|
277
336
|
{
|
|
278
337
|
location: 'packages/lib',
|
|
279
338
|
manifest: {
|
|
280
339
|
name: '@compat/lib',
|
|
281
340
|
version: '1.0.0',
|
|
282
|
-
peerDependencies: { rxjs: '
|
|
283
|
-
devDependencies: { rxjs: '
|
|
341
|
+
peerDependencies: { rxjs: '*' },
|
|
342
|
+
devDependencies: { rxjs: '*' },
|
|
284
343
|
},
|
|
285
344
|
files: {
|
|
286
345
|
'src/index.ts': [
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
validateDependencyRules,
|
|
3
|
+
validateIgnoredImports,
|
|
4
|
+
} from '../src/model/config-validation';
|
|
2
5
|
|
|
3
6
|
describe('validateDependencyRules', () => {
|
|
4
7
|
it('rejects required wildcard rules because they cannot expand to concrete deps', () => {
|
|
@@ -40,3 +43,15 @@ describe('validateDependencyRules', () => {
|
|
|
40
43
|
]);
|
|
41
44
|
});
|
|
42
45
|
});
|
|
46
|
+
|
|
47
|
+
describe('validateIgnoredImports', () => {
|
|
48
|
+
it('allows string module specifier patterns', () => {
|
|
49
|
+
expect(validateIgnoredImports(['@docusaurus/router'])).toEqual([]);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('rejects non-string items', () => {
|
|
53
|
+
expect(validateIgnoredImports([42])).toEqual([
|
|
54
|
+
expect.stringContaining('ignoredImports[0] must be a string'),
|
|
55
|
+
]);
|
|
56
|
+
});
|
|
57
|
+
});
|
package/__tests__/engine.test.ts
CHANGED
|
@@ -923,6 +923,83 @@ describe('deriveDependencyModel engine', () => {
|
|
|
923
923
|
});
|
|
924
924
|
});
|
|
925
925
|
|
|
926
|
+
describe('stale-depdoc-rules', () => {
|
|
927
|
+
it('reports stale-rule for unused concrete dependency rules', () => {
|
|
928
|
+
const root = makeRoot();
|
|
929
|
+
const ws = makeWorkspace('@test/svc', 'packages/svc', 'service');
|
|
930
|
+
const result = deriveDependencyModel(emptyFacts([root, ws], [
|
|
931
|
+
{ match: 'unused-lib', version: '^1.0.0' },
|
|
932
|
+
]));
|
|
933
|
+
|
|
934
|
+
expect(
|
|
935
|
+
result.violations.some(
|
|
936
|
+
(v) => v.code === 'stale-rule' && v.dependency === 'unused-lib',
|
|
937
|
+
),
|
|
938
|
+
).toBe(true);
|
|
939
|
+
});
|
|
940
|
+
|
|
941
|
+
it('does not report required concrete rules as stale', () => {
|
|
942
|
+
const root = makeRoot();
|
|
943
|
+
const ws = makeWorkspace('@test/svc', 'packages/svc', 'service');
|
|
944
|
+
const result = deriveDependencyModel(emptyFacts([root, ws], [
|
|
945
|
+
{ match: 'required-lib', version: '^1.0.0', required: true },
|
|
946
|
+
]));
|
|
947
|
+
|
|
948
|
+
expect(
|
|
949
|
+
result.violations.some(
|
|
950
|
+
(v) => v.code === 'stale-rule' && v.dependency === 'required-lib',
|
|
951
|
+
),
|
|
952
|
+
).toBe(false);
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
it('does not report root dev baseline rules as stale', () => {
|
|
956
|
+
const root = makeRoot({
|
|
957
|
+
name: '@test/root',
|
|
958
|
+
private: true,
|
|
959
|
+
devDependencies: { eslint: '^10.0.0' },
|
|
960
|
+
});
|
|
961
|
+
const ws = makeWorkspace('@test/svc', 'packages/svc', 'service');
|
|
962
|
+
const result = deriveDependencyModel(emptyFacts([root, ws], [
|
|
963
|
+
{
|
|
964
|
+
match: 'eslint',
|
|
965
|
+
section: 'devDependencies',
|
|
966
|
+
version: '^10.0.0',
|
|
967
|
+
rootOnly: true,
|
|
968
|
+
},
|
|
969
|
+
]));
|
|
970
|
+
|
|
971
|
+
expect(
|
|
972
|
+
result.violations.some(
|
|
973
|
+
(v) => v.code === 'stale-rule' && v.dependency === 'eslint',
|
|
974
|
+
),
|
|
975
|
+
).toBe(false);
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
it('does not report wildcard rules as stale', () => {
|
|
979
|
+
const root = makeRoot();
|
|
980
|
+
const ws = makeWorkspace('@test/svc', 'packages/svc', 'service');
|
|
981
|
+
const result = deriveDependencyModel(emptyFacts([root, ws], [
|
|
982
|
+
{ match: '@scope/*', version: '^1.0.0' },
|
|
983
|
+
]));
|
|
984
|
+
|
|
985
|
+
expect(result.violations.some((v) => v.code === 'stale-rule')).toBe(false);
|
|
986
|
+
});
|
|
987
|
+
|
|
988
|
+
it('reports workspace-scoped stale rules against the matching workspace', () => {
|
|
989
|
+
const root = makeRoot();
|
|
990
|
+
const ws = makeWorkspace('@test/svc', 'packages/svc', 'service');
|
|
991
|
+
const result = deriveDependencyModel(emptyFacts([root, ws], [
|
|
992
|
+
{ match: 'unused-lib', workspace: '@test/svc', version: '^1.0.0' },
|
|
993
|
+
]));
|
|
994
|
+
const violation = result.violations.find(
|
|
995
|
+
(v) => v.code === 'stale-rule' && v.dependency === 'unused-lib',
|
|
996
|
+
);
|
|
997
|
+
|
|
998
|
+
expect(violation?.workspace).toBe('@test/svc');
|
|
999
|
+
expect(violation?.workspaceLocation).toBe('packages/svc');
|
|
1000
|
+
});
|
|
1001
|
+
});
|
|
1002
|
+
|
|
926
1003
|
describe('dts-mode', () => {
|
|
927
1004
|
it('does not report missing dist when withDts is disabled', () => {
|
|
928
1005
|
const root = makeRoot();
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
collectSourceImports,
|
|
6
6
|
getPackageName,
|
|
7
7
|
} from '../src/lib/imports';
|
|
8
|
+
import { collectTsConfigPathAliases } from '../src/collectors/tsconfig-aliases';
|
|
8
9
|
|
|
9
10
|
const FIXTURES = path.join(__dirname, 'fixtures/imports');
|
|
10
11
|
|
|
@@ -74,6 +75,28 @@ describe('collectSourceImports', () => {
|
|
|
74
75
|
expect(pkgs.has('require-dep')).toBe(true);
|
|
75
76
|
expect(pkgs.has('dynamic-dep')).toBe(true);
|
|
76
77
|
expect(pkgs.has('reexport-dep')).toBe(true);
|
|
78
|
+
expect(pkgs.has('@docusaurus/router')).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('ignores configured virtual imports', () => {
|
|
82
|
+
const filteredEntries = collectSourceImports(srcDir, undefined, {
|
|
83
|
+
ignoredSpecifiers: ['@docusaurus/router'],
|
|
84
|
+
});
|
|
85
|
+
const pkgs = new Set(filteredEntries.map((e) => e.packageName));
|
|
86
|
+
|
|
87
|
+
expect(pkgs.has('@docusaurus/router')).toBe(false);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('ignores TS path aliases collected from all workspace tsconfigs', () => {
|
|
91
|
+
const aliases = collectTsConfigPathAliases(FIXTURES);
|
|
92
|
+
const filteredEntries = collectSourceImports(srcDir, undefined, {
|
|
93
|
+
ignoredSpecifiers: aliases,
|
|
94
|
+
});
|
|
95
|
+
const pkgs = new Set(filteredEntries.map((e) => e.packageName));
|
|
96
|
+
|
|
97
|
+
expect(aliases).toEqual(expect.arrayContaining(['~app/*', '~shared/*']));
|
|
98
|
+
expect(pkgs.has('~app')).toBe(false);
|
|
99
|
+
expect(pkgs.has('~shared')).toBe(false);
|
|
77
100
|
});
|
|
78
101
|
|
|
79
102
|
it('marks import type entries as isTypeOnly', () => {
|
|
@@ -215,4 +238,12 @@ describe('collectDtsImports', () => {
|
|
|
215
238
|
it('returns a set (no duplicates)', () => {
|
|
216
239
|
expect(result).toBeInstanceOf(Set);
|
|
217
240
|
});
|
|
241
|
+
|
|
242
|
+
it('ignores TS path aliases in dts imports', () => {
|
|
243
|
+
const filtered = collectDtsImports(distDir, {
|
|
244
|
+
ignoredSpecifiers: collectTsConfigPathAliases(FIXTURES),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
expect(filtered.has('~shared')).toBe(false);
|
|
248
|
+
});
|
|
218
249
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function collectTsConfigPathAliases(workspaceDir: string): string[];
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.collectTsConfigPathAliases = collectTsConfigPathAliases;
|
|
7
|
+
/**
|
|
8
|
+
* TypeScript path alias collector.
|
|
9
|
+
*
|
|
10
|
+
* Depdoc treats `compilerOptions.paths` entries as internal module specifiers:
|
|
11
|
+
* they are resolved by TypeScript/bundlers, not package manager dependencies.
|
|
12
|
+
*/
|
|
13
|
+
const node_fs_1 = require("node:fs");
|
|
14
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
15
|
+
const ts = require("typescript");
|
|
16
|
+
const TSCONFIG_RE = /^tsconfig(?:\..*)?\.json$/;
|
|
17
|
+
function collectTsConfigPathAliases(workspaceDir) {
|
|
18
|
+
const aliases = new Set();
|
|
19
|
+
let entries;
|
|
20
|
+
try {
|
|
21
|
+
entries = (0, node_fs_1.readdirSync)(workspaceDir, { withFileTypes: true });
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
for (const entry of entries) {
|
|
27
|
+
if (!entry.isFile() || !TSCONFIG_RE.test(entry.name))
|
|
28
|
+
continue;
|
|
29
|
+
const configPath = node_path_1.default.join(workspaceDir, entry.name);
|
|
30
|
+
const config = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
31
|
+
if (config.error || !config.config)
|
|
32
|
+
continue;
|
|
33
|
+
const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, node_path_1.default.dirname(configPath), undefined, configPath);
|
|
34
|
+
for (const alias of Object.keys(parsed.options.paths ?? {})) {
|
|
35
|
+
aliases.add(alias);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return [...aliases].sort();
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=tsconfig-aliases.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tsconfig-aliases.js","sourceRoot":"","sources":["../../src/collectors/tsconfig-aliases.ts"],"names":[],"mappings":";;;;;AAYA,gEAgCC;AA5CD;;;;;GAKG;AACH,qCAAmD;AACnD,0DAA6B;AAC7B,iCAAkC;AAElC,MAAM,WAAW,GAAG,2BAA2B,CAAC;AAEhD,SAAgB,0BAA0B,CAAC,YAAoB;IAC7D,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAElC,IAAI,OAAiB,CAAC;IAEtB,IAAI,CAAC;QACH,OAAO,GAAG,IAAA,qBAAW,EAAC,YAAY,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YAAE,SAAS;QAE/D,MAAM,UAAU,GAAG,mBAAI,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,EAAE,CAAC,cAAc,CAAC,UAAU,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC9D,IAAI,MAAM,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,MAAM;YAAE,SAAS;QAE7C,MAAM,MAAM,GAAG,EAAE,CAAC,0BAA0B,CAC1C,MAAM,CAAC,MAAM,EACb,EAAE,CAAC,GAAG,EACN,mBAAI,CAAC,OAAO,CAAC,UAAU,CAAC,EACxB,SAAS,EACT,UAAU,CACX,CAAC;QAEF,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;YAC5D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;AAC7B,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { WorkspaceContext } from '../model/types';
|
|
2
|
-
export declare function loadWorkspaces(rootDir: string, withDts: boolean): WorkspaceContext[];
|
|
2
|
+
export declare function loadWorkspaces(rootDir: string, withDts: boolean, ignoredImports?: readonly string[]): WorkspaceContext[];
|
|
@@ -17,10 +17,17 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
17
17
|
const imports_1 = require("../lib/imports");
|
|
18
18
|
const package_json_1 = require("../lib/package-json");
|
|
19
19
|
const rules_1 = require("../model/rules");
|
|
20
|
-
|
|
20
|
+
const tsconfig_aliases_1 = require("./tsconfig-aliases");
|
|
21
|
+
function collectUsage(workspace, withDts, ignoredImports) {
|
|
21
22
|
const sourceFiles = (0, imports_1.collectSourceFiles)(workspace.dir, workspace.pkg);
|
|
23
|
+
const ignoredSpecifiers = [
|
|
24
|
+
...ignoredImports,
|
|
25
|
+
...(0, tsconfig_aliases_1.collectTsConfigPathAliases)(workspace.dir),
|
|
26
|
+
];
|
|
22
27
|
workspace.sourceFileCount = sourceFiles.length;
|
|
23
|
-
for (const entry of (0, imports_1.collectSourceImportsFromFiles)(sourceFiles
|
|
28
|
+
for (const entry of (0, imports_1.collectSourceImportsFromFiles)(sourceFiles, {
|
|
29
|
+
ignoredSpecifiers,
|
|
30
|
+
})) {
|
|
24
31
|
if (!workspace.sourceUsage.has(entry.packageName)) {
|
|
25
32
|
workspace.sourceUsage.set(entry.packageName, {
|
|
26
33
|
files: new Set(),
|
|
@@ -39,10 +46,10 @@ function collectUsage(workspace, withDts) {
|
|
|
39
46
|
}
|
|
40
47
|
const distDir = node_path_1.default.join(workspace.dir, 'dist');
|
|
41
48
|
if (withDts || (0, node_fs_1.existsSync)(distDir)) {
|
|
42
|
-
workspace.dtsImports = (0, imports_1.collectDtsImports)(distDir);
|
|
49
|
+
workspace.dtsImports = (0, imports_1.collectDtsImports)(distDir, { ignoredSpecifiers });
|
|
43
50
|
}
|
|
44
51
|
}
|
|
45
|
-
function loadWorkspaces(rootDir, withDts) {
|
|
52
|
+
function loadWorkspaces(rootDir, withDts, ignoredImports = []) {
|
|
46
53
|
const rootPkg = (0, package_json_1.readPackageJson)(rootDir);
|
|
47
54
|
const raw = (0, node_child_process_1.execSync)('yarn workspaces list --json', {
|
|
48
55
|
cwd: rootDir,
|
|
@@ -82,7 +89,7 @@ function loadWorkspaces(rootDir, withDts) {
|
|
|
82
89
|
hasSrc: (0, node_fs_1.existsSync)(node_path_1.default.join(dir, 'src')),
|
|
83
90
|
hasDist: (0, node_fs_1.existsSync)(node_path_1.default.join(dir, 'dist')),
|
|
84
91
|
};
|
|
85
|
-
collectUsage(ctx, withDts);
|
|
92
|
+
collectUsage(ctx, withDts, ignoredImports);
|
|
86
93
|
workspaces.push(ctx);
|
|
87
94
|
}
|
|
88
95
|
return workspaces;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workspaces.js","sourceRoot":"","sources":["../../src/collectors/workspaces.ts"],"names":[],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"workspaces.js","sourceRoot":"","sources":["../../src/collectors/workspaces.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/imports.d.ts
CHANGED
|
@@ -4,8 +4,11 @@ export interface ImportEntry {
|
|
|
4
4
|
isTypeOnly: boolean;
|
|
5
5
|
file: string;
|
|
6
6
|
}
|
|
7
|
+
export interface ImportCollectionOptions {
|
|
8
|
+
ignoredSpecifiers?: readonly string[];
|
|
9
|
+
}
|
|
7
10
|
export declare function getPackageName(specifier: string): string | null;
|
|
8
11
|
export declare function collectSourceFiles(srcDirOrWorkspaceDir: string, pkg?: PackageJson): string[];
|
|
9
|
-
export declare function collectSourceImportsFromFiles(files: string[]): ImportEntry[];
|
|
10
|
-
export declare function collectSourceImports(srcDirOrWorkspaceDir: string, pkg?: PackageJson): ImportEntry[];
|
|
11
|
-
export declare function collectDtsImports(distDir: string): Set<string>;
|
|
12
|
+
export declare function collectSourceImportsFromFiles(files: string[], options?: ImportCollectionOptions): ImportEntry[];
|
|
13
|
+
export declare function collectSourceImports(srcDirOrWorkspaceDir: string, pkg?: PackageJson, options?: ImportCollectionOptions): ImportEntry[];
|
|
14
|
+
export declare function collectDtsImports(distDir: string, options?: ImportCollectionOptions): Set<string>;
|
package/dist/lib/imports.js
CHANGED
|
@@ -64,6 +64,18 @@ function getPackageName(specifier) {
|
|
|
64
64
|
const slash = specifier.indexOf('/');
|
|
65
65
|
return slash === -1 ? specifier : specifier.slice(0, slash);
|
|
66
66
|
}
|
|
67
|
+
function globToRegExp(pattern) {
|
|
68
|
+
const escaped = pattern.replaceAll(/[-[\]{}()+?.,\\^$|#\s]/g, String.raw `\$&`);
|
|
69
|
+
return new RegExp(`^${escaped.replaceAll('*', '.*')}$`);
|
|
70
|
+
}
|
|
71
|
+
function matchesSpecifierPattern(specifier, pattern) {
|
|
72
|
+
return pattern.includes('*')
|
|
73
|
+
? globToRegExp(pattern).test(specifier)
|
|
74
|
+
: specifier === pattern;
|
|
75
|
+
}
|
|
76
|
+
function isIgnoredSpecifier(specifier, options) {
|
|
77
|
+
return (options.ignoredSpecifiers ?? []).some((pattern) => matchesSpecifierPattern(specifier, pattern));
|
|
78
|
+
}
|
|
67
79
|
function walkFiles(dir, filter) {
|
|
68
80
|
const results = [];
|
|
69
81
|
try {
|
|
@@ -109,10 +121,14 @@ function getScriptKind(file) {
|
|
|
109
121
|
function stringLiteralText(node) {
|
|
110
122
|
return node && ts.isStringLiteralLike(node) ? node.text : null;
|
|
111
123
|
}
|
|
112
|
-
function addImport(results, file, specifier, isTypeOnly) {
|
|
124
|
+
function addImport(results, file, specifier, isTypeOnly, options) {
|
|
125
|
+
if (isIgnoredSpecifier(specifier, options))
|
|
126
|
+
return;
|
|
113
127
|
const packageName = getPackageName(specifier);
|
|
114
128
|
if (!packageName)
|
|
115
129
|
return;
|
|
130
|
+
if (isIgnoredSpecifier(packageName, options))
|
|
131
|
+
return;
|
|
116
132
|
results.push({ packageName, isTypeOnly, file });
|
|
117
133
|
}
|
|
118
134
|
function addModuleReference(results, specifier, isTypeOnly) {
|
|
@@ -203,7 +219,7 @@ function extractModuleReferences(file, content, options = {}) {
|
|
|
203
219
|
function extractImports(file, content, options = {}) {
|
|
204
220
|
const results = [];
|
|
205
221
|
for (const reference of extractModuleReferences(file, content, options)) {
|
|
206
|
-
addImport(results, file, reference.specifier, reference.isTypeOnly);
|
|
222
|
+
addImport(results, file, reference.specifier, reference.isTypeOnly, options);
|
|
207
223
|
}
|
|
208
224
|
return results;
|
|
209
225
|
}
|
|
@@ -323,17 +339,17 @@ function collectSourceFiles(srcDirOrWorkspaceDir, pkg) {
|
|
|
323
339
|
}
|
|
324
340
|
return [...files].sort();
|
|
325
341
|
}
|
|
326
|
-
function collectSourceImportsFromFiles(files) {
|
|
327
|
-
return files.flatMap((file) => extractImports(file, (0, node_fs_1.readFileSync)(file, 'utf8')));
|
|
342
|
+
function collectSourceImportsFromFiles(files, options = {}) {
|
|
343
|
+
return files.flatMap((file) => extractImports(file, (0, node_fs_1.readFileSync)(file, 'utf8'), options));
|
|
328
344
|
}
|
|
329
|
-
function collectSourceImports(srcDirOrWorkspaceDir, pkg) {
|
|
330
|
-
return collectSourceImportsFromFiles(collectSourceFiles(srcDirOrWorkspaceDir, pkg));
|
|
345
|
+
function collectSourceImports(srcDirOrWorkspaceDir, pkg, options = {}) {
|
|
346
|
+
return collectSourceImportsFromFiles(collectSourceFiles(srcDirOrWorkspaceDir, pkg), options);
|
|
331
347
|
}
|
|
332
|
-
function collectDtsImports(distDir) {
|
|
348
|
+
function collectDtsImports(distDir, options = {}) {
|
|
333
349
|
const files = walkFiles(distDir, (name) => DTS_RE.test(name));
|
|
334
350
|
const result = new Set();
|
|
335
351
|
for (const file of files) {
|
|
336
|
-
for (const { packageName } of extractImports(file, (0, node_fs_1.readFileSync)(file, 'utf8'), { includeDtsForms: true })) {
|
|
352
|
+
for (const { packageName } of extractImports(file, (0, node_fs_1.readFileSync)(file, 'utf8'), { ...options, includeDtsForms: true })) {
|
|
337
353
|
result.add(packageName);
|
|
338
354
|
}
|
|
339
355
|
}
|