@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.
Files changed (40) hide show
  1. package/DEPDOC_MODEL.md +22 -11
  2. package/__tests__/compatibility.test.ts +74 -15
  3. package/__tests__/config-validation.test.ts +16 -1
  4. package/__tests__/engine.test.ts +77 -0
  5. package/__tests__/fixtures/imports/config/aliases.json +8 -0
  6. package/__tests__/fixtures/imports/src/aliases.ts +4 -0
  7. package/__tests__/fixtures/imports/src/virtual.ts +3 -0
  8. package/__tests__/fixtures/imports/tsconfig.build.json +3 -0
  9. package/__tests__/fixtures/imports/tsconfig.json +8 -0
  10. package/__tests__/imports.test.ts +31 -0
  11. package/dist/collectors/tsconfig-aliases.d.ts +1 -0
  12. package/dist/collectors/tsconfig-aliases.js +40 -0
  13. package/dist/collectors/tsconfig-aliases.js.map +1 -0
  14. package/dist/collectors/workspaces.d.ts +1 -1
  15. package/dist/collectors/workspaces.js +12 -5
  16. package/dist/collectors/workspaces.js.map +1 -1
  17. package/dist/lib/imports.d.ts +6 -3
  18. package/dist/lib/imports.js +24 -8
  19. package/dist/lib/imports.js.map +1 -1
  20. package/dist/model/config-validation.d.ts +1 -0
  21. package/dist/model/config-validation.js +15 -1
  22. package/dist/model/config-validation.js.map +1 -1
  23. package/dist/model/diagnostics.d.ts +1 -0
  24. package/dist/model/diagnostics.js +67 -0
  25. package/dist/model/diagnostics.js.map +1 -1
  26. package/dist/model/engine.js +1 -0
  27. package/dist/model/engine.js.map +1 -1
  28. package/dist/model/types.d.ts +2 -1
  29. package/dist/model/types.js.map +1 -1
  30. package/dist/runner.js +1 -1
  31. package/dist/runner.js.map +1 -1
  32. package/package.json +2 -2
  33. package/src/collectors/tsconfig-aliases.ts +45 -0
  34. package/src/collectors/workspaces.ts +17 -4
  35. package/src/lib/imports.ts +57 -6
  36. package/src/model/config-validation.ts +17 -1
  37. package/src/model/diagnostics.ts +105 -1
  38. package/src/model/engine.ts +7 -1
  39. package/src/model/types.ts +2 -0
  40. 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 overrides, `rootOnly` rules,
118
- manual `required` dependencies, and workspace-specific exceptions.
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. Classify each import as local or external.
140
- 3. Classify each import as runtime, type-only, test-only, or tooling-only.
141
- 4. Scan emitted `dist/**/*.d.ts` imports when `dist/` exists.
142
- 5. In `--with-dts` mode, require `dist/` for workspaces with `src/`; without
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
- 6. Mark all dependencies found in emitted `.d.ts` as public type surface.
146
- 7. Skip generated entry-point roots such as `dist/`, `build/`, `coverage/`, and
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
- 8. Warn when a workspace has scanned source files and declared dependencies,
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 missing or incorrect library peer/dev mirrors.
218
- 7. Report root-only violations.
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: '^4.17.21' },
145
- devDependencies: { lodash: '^4.17.21' },
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: '^4.17.21',
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 external peers propagated to service
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
- // @nestjs/common requires rxjs (required) and reflect-metadata (required).
188
- // The model propagates both into service dependencies (invariant P2 + S1).
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
- '@nestjs/common': '10.4.8',
199
- rxjs: '^7.8.1',
200
- 'reflect-metadata': '^0.1.12 || ^0.2.0',
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: { '@nestjs/common': '10.4.8' },
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 (which ships with bundled types). After install the
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: '^7.8.1' },
283
- devDependencies: { rxjs: '^7.8.1' },
341
+ peerDependencies: { rxjs: '*' },
342
+ devDependencies: { rxjs: '*' },
284
343
  },
285
344
  files: {
286
345
  'src/index.ts': [
@@ -1,4 +1,7 @@
1
- import { validateDependencyRules } from '../src/model/config-validation';
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
+ });
@@ -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();
@@ -0,0 +1,8 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": "..",
4
+ "paths": {
5
+ "~shared/*": ["src/shared/*"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,4 @@
1
+ import { route } from '~app/routes';
2
+ import type { SharedModel } from '~shared/model';
3
+
4
+ export const aliasRoute = route satisfies SharedModel;
@@ -0,0 +1,3 @@
1
+ import { BrowserOnly } from '@docusaurus/router';
2
+
3
+ export const virtualRouter = BrowserOnly;
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "./config/aliases.json"
3
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "paths": {
5
+ "~app/*": ["src/app/*"]
6
+ }
7
+ }
8
+ }
@@ -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
- function collectUsage(workspace, withDts) {
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":"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"}
1
+ {"version":3,"file":"workspaces.js","sourceRoot":"","sources":["../../src/collectors/workspaces.ts"],"names":[],"mappings":"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"}
@@ -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>;
@@ -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
  }