@rsdk/depdoc.cli 6.0.0-next.42 → 6.0.0-next.44

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 (46) hide show
  1. package/DEPDOC_MODEL.md +22 -11
  2. package/__tests__/compatibility.test.ts +74 -15
  3. package/__tests__/config-validation.test.ts +46 -1
  4. package/__tests__/engine.test.ts +162 -2
  5. package/__tests__/fixtures/imports/config/aliases.json +8 -0
  6. package/__tests__/fixtures/imports/plain-root-file.ts +3 -0
  7. package/__tests__/fixtures/imports/sidebars.ts +5 -0
  8. package/__tests__/fixtures/imports/src/aliases.ts +4 -0
  9. package/__tests__/fixtures/imports/src/virtual.ts +3 -0
  10. package/__tests__/fixtures/imports/tsconfig.build.json +3 -0
  11. package/__tests__/fixtures/imports/tsconfig.json +8 -0
  12. package/__tests__/fixtures/imports/vite.config.ts +3 -0
  13. package/__tests__/imports.test.ts +72 -0
  14. package/dist/collectors/tsconfig-aliases.d.ts +1 -0
  15. package/dist/collectors/tsconfig-aliases.js +40 -0
  16. package/dist/collectors/tsconfig-aliases.js.map +1 -0
  17. package/dist/collectors/workspaces.d.ts +2 -2
  18. package/dist/collectors/workspaces.js +21 -6
  19. package/dist/collectors/workspaces.js.map +1 -1
  20. package/dist/lib/imports.d.ts +9 -4
  21. package/dist/lib/imports.js +62 -9
  22. package/dist/lib/imports.js.map +1 -1
  23. package/dist/model/config-validation.d.ts +2 -0
  24. package/dist/model/config-validation.js +44 -1
  25. package/dist/model/config-validation.js.map +1 -1
  26. package/dist/model/diagnostics.d.ts +1 -0
  27. package/dist/model/diagnostics.js +67 -0
  28. package/dist/model/diagnostics.js.map +1 -1
  29. package/dist/model/engine.js +1 -0
  30. package/dist/model/engine.js.map +1 -1
  31. package/dist/model/placement.js +12 -1
  32. package/dist/model/placement.js.map +1 -1
  33. package/dist/model/types.d.ts +8 -1
  34. package/dist/model/types.js.map +1 -1
  35. package/dist/runner.js +1 -1
  36. package/dist/runner.js.map +1 -1
  37. package/package.json +2 -2
  38. package/src/collectors/tsconfig-aliases.ts +45 -0
  39. package/src/collectors/workspaces.ts +50 -7
  40. package/src/lib/imports.ts +114 -8
  41. package/src/model/config-validation.ts +62 -3
  42. package/src/model/diagnostics.ts +105 -1
  43. package/src/model/engine.ts +7 -1
  44. package/src/model/placement.ts +19 -1
  45. package/src/model/types.ts +16 -0
  46. package/src/runner.ts +6 -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,8 @@
1
- import { validateDependencyRules } from '../src/model/config-validation';
1
+ import {
2
+ validateDependencyRules,
3
+ validateIgnoredImports,
4
+ validateToolingFiles,
5
+ } from '../src/model/config-validation';
2
6
 
3
7
  describe('validateDependencyRules', () => {
4
8
  it('rejects required wildcard rules because they cannot expand to concrete deps', () => {
@@ -40,3 +44,44 @@ describe('validateDependencyRules', () => {
40
44
  ]);
41
45
  });
42
46
  });
47
+
48
+ describe('validateIgnoredImports', () => {
49
+ it('allows string module specifier patterns', () => {
50
+ expect(validateIgnoredImports(['@docusaurus/router'])).toEqual([]);
51
+ });
52
+
53
+ it('rejects non-string items', () => {
54
+ expect(validateIgnoredImports([42])).toEqual([
55
+ expect.stringContaining('ignoredImports[0] must be a string'),
56
+ ]);
57
+ });
58
+ });
59
+
60
+ describe('validateToolingFiles', () => {
61
+ it('allows a global rule with no workspace scoping', () => {
62
+ expect(validateToolingFiles([{ match: 'sidebars.ts' }])).toEqual([]);
63
+ });
64
+
65
+ it('allows a workspace-scoped rule with an array of matches', () => {
66
+ expect(
67
+ validateToolingFiles([
68
+ {
69
+ match: ['sidebars.ts', 'sidebars.js'],
70
+ workspace: 'niisokb.artifactory.docusaurus',
71
+ },
72
+ ]),
73
+ ).toEqual([]);
74
+ });
75
+
76
+ it('rejects entries missing match', () => {
77
+ expect(validateToolingFiles([{ workspace: 'foo' }])).toEqual([
78
+ expect.stringContaining('toolingFiles[0].match must be a string'),
79
+ ]);
80
+ });
81
+
82
+ it('rejects a non-array value', () => {
83
+ expect(validateToolingFiles('sidebars.ts')).toEqual([
84
+ expect.stringContaining('toolingFiles must be an array'),
85
+ ]);
86
+ });
87
+ });