@rsdk/yarn.constraints 6.0.0-next.39 → 6.0.0-next.40

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 (118) hide show
  1. package/DEPENDENCY_MODEL.md +452 -0
  2. package/README.MD +24 -0
  3. package/__tests__/compatibility.test.ts +321 -0
  4. package/__tests__/engine.test.ts +1002 -0
  5. package/__tests__/fixtures/imports/bin.js +4 -0
  6. package/__tests__/fixtures/imports/export-entry.mjs +1 -0
  7. package/__tests__/fixtures/imports/root-entry.js +3 -0
  8. package/__tests__/fixtures/imports/src/common.cjs +3 -0
  9. package/__tests__/fixtures/imports/src/common.cts +3 -0
  10. package/__tests__/fixtures/imports/src/component.tsx +4 -0
  11. package/__tests__/fixtures/imports/src/index.ts +13 -0
  12. package/__tests__/fixtures/imports/src/module.mjs +3 -0
  13. package/__tests__/fixtures/imports/src/module.mts +3 -0
  14. package/__tests__/fixtures/imports/src/plain.js +3 -0
  15. package/__tests__/fixtures/imports/src/test-only-usage.ts +1 -0
  16. package/__tests__/imports.test.ts +206 -0
  17. package/__tests__/manifest-writer.test.ts +157 -0
  18. package/dist/ansi.d.ts +9 -0
  19. package/dist/ansi.js +24 -0
  20. package/dist/ansi.js.map +1 -0
  21. package/dist/bin/depdoc.d.ts +2 -0
  22. package/dist/bin/depdoc.js +157 -0
  23. package/dist/bin/depdoc.js.map +1 -0
  24. package/dist/collectors/config.d.ts +2 -0
  25. package/dist/collectors/config.js +25 -0
  26. package/dist/collectors/config.js.map +1 -0
  27. package/dist/collectors/external-metadata.d.ts +5 -0
  28. package/dist/collectors/external-metadata.js +110 -0
  29. package/dist/collectors/external-metadata.js.map +1 -0
  30. package/dist/collectors/package-extensions.d.ts +3 -0
  31. package/dist/collectors/package-extensions.js +43 -0
  32. package/dist/collectors/package-extensions.js.map +1 -0
  33. package/dist/collectors/type-providers.d.ts +3 -0
  34. package/dist/collectors/type-providers.js +46 -0
  35. package/dist/collectors/type-providers.js.map +1 -0
  36. package/dist/collectors/workspaces.d.ts +2 -0
  37. package/dist/collectors/workspaces.js +88 -0
  38. package/dist/collectors/workspaces.js.map +1 -0
  39. package/dist/dependency-model.d.ts +11 -0
  40. package/dist/dependency-model.js +18 -0
  41. package/dist/dependency-model.js.map +1 -0
  42. package/dist/index.d.ts +9 -5
  43. package/dist/index.js +13 -33
  44. package/dist/index.js.map +1 -1
  45. package/dist/lib/imports.d.ts +9 -0
  46. package/dist/lib/imports.js +249 -0
  47. package/dist/lib/imports.js.map +1 -0
  48. package/dist/lib/package-json.d.ts +21 -0
  49. package/dist/lib/package-json.js +32 -0
  50. package/dist/lib/package-json.js.map +1 -0
  51. package/dist/model/diagnostics.d.ts +4 -0
  52. package/dist/model/diagnostics.js +273 -0
  53. package/dist/model/diagnostics.js.map +1 -0
  54. package/dist/model/engine.d.ts +5 -0
  55. package/dist/model/engine.js +52 -0
  56. package/dist/model/engine.js.map +1 -0
  57. package/dist/model/expected.d.ts +20 -0
  58. package/dist/model/expected.js +89 -0
  59. package/dist/model/expected.js.map +1 -0
  60. package/dist/model/peer-propagation.d.ts +2 -0
  61. package/dist/model/peer-propagation.js +124 -0
  62. package/dist/model/peer-propagation.js.map +1 -0
  63. package/dist/model/placement.d.ts +9 -0
  64. package/dist/model/placement.js +205 -0
  65. package/dist/model/placement.js.map +1 -0
  66. package/dist/model/rules.d.ts +14 -0
  67. package/dist/model/rules.js +46 -0
  68. package/dist/model/rules.js.map +1 -0
  69. package/dist/model/types.d.ts +117 -0
  70. package/dist/model/types.js +9 -0
  71. package/dist/model/types.js.map +1 -0
  72. package/dist/model/versions.d.ts +3 -0
  73. package/dist/model/versions.js +73 -0
  74. package/dist/model/versions.js.map +1 -0
  75. package/dist/reporting.d.ts +3 -0
  76. package/dist/reporting.js +80 -0
  77. package/dist/reporting.js.map +1 -0
  78. package/dist/runner.d.ts +2 -0
  79. package/dist/runner.js +70 -0
  80. package/dist/runner.js.map +1 -0
  81. package/dist/writer/manifest-writer.d.ts +2 -0
  82. package/dist/writer/manifest-writer.js +72 -0
  83. package/dist/writer/manifest-writer.js.map +1 -0
  84. package/eslint.config.cjs +3 -0
  85. package/jest.config.js +1 -0
  86. package/package.json +7 -3
  87. package/src/ansi.ts +23 -0
  88. package/src/bin/depdoc.ts +213 -0
  89. package/src/collectors/config.ts +26 -0
  90. package/src/collectors/external-metadata.ts +148 -0
  91. package/src/collectors/package-extensions.ts +52 -0
  92. package/src/collectors/type-providers.ts +51 -0
  93. package/src/collectors/workspaces.ts +99 -0
  94. package/src/dependency-model.ts +26 -0
  95. package/src/index.ts +28 -45
  96. package/src/lib/imports.ts +293 -0
  97. package/src/lib/package-json.ts +46 -0
  98. package/src/model/diagnostics.ts +328 -0
  99. package/src/model/engine.ts +120 -0
  100. package/src/model/expected.ts +141 -0
  101. package/src/model/peer-propagation.ts +199 -0
  102. package/src/model/placement.ts +372 -0
  103. package/src/model/rules.ts +73 -0
  104. package/src/model/types.ts +164 -0
  105. package/src/model/versions.ts +109 -0
  106. package/src/reporting.ts +117 -0
  107. package/src/runner.ts +102 -0
  108. package/src/writer/manifest-writer.ts +111 -0
  109. package/tsconfig.build.json +1 -0
  110. package/tsconfig.json +6 -1
  111. package/dist/constraint-schema.d.ts +0 -1
  112. package/dist/constraint-schema.js +0 -17
  113. package/dist/constraint-schema.js.map +0 -1
  114. package/dist/dependency-checker.d.ts +0 -8
  115. package/dist/dependency-checker.js +0 -40
  116. package/dist/dependency-checker.js.map +0 -1
  117. package/src/constraint-schema.ts +0 -20
  118. package/src/dependency-checker.ts +0 -41
@@ -0,0 +1,321 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { resolve } from 'node:path';
5
+
6
+ // Paths relative to this test file:
7
+ // __tests__/ -> constraints/ -> packages/ -> monorepo root
8
+ const MONOREPO_ROOT = resolve(__dirname, '../../..');
9
+ const YARN_BIN = resolve(MONOREPO_ROOT, '.yarn/releases/yarn-4.5.3.cjs');
10
+ const YARN_CACHE = resolve(MONOREPO_ROOT, '.yarn/cache');
11
+ // Reuse the main project's tsc instead of installing typescript in each temp monorepo.
12
+ const MAIN_TSC = resolve(MONOREPO_ROOT, 'node_modules/typescript/bin/tsc');
13
+
14
+ jest.setTimeout(120_000);
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Helpers
18
+ // ---------------------------------------------------------------------------
19
+
20
+ function yarn(args: string[], cwd: string): string {
21
+ const r = spawnSync(process.execPath, [YARN_BIN, ...args], {
22
+ cwd,
23
+ encoding: 'utf8',
24
+ stdio: 'pipe',
25
+ timeout: 90_000,
26
+ });
27
+ const output = (r.stdout ?? '') + (r.stderr ?? '');
28
+
29
+ if (r.status !== 0 || r.error) {
30
+ throw new Error(
31
+ [
32
+ `yarn ${args.join(' ')} failed in ${cwd}`,
33
+ `status: ${r.status ?? '<none>'}`,
34
+ r.signal ? `signal: ${r.signal}` : null,
35
+ r.error ? `error: ${r.error.message}` : null,
36
+ 'output:',
37
+ output || '<empty>',
38
+ ]
39
+ .filter(Boolean)
40
+ .join('\n'),
41
+ );
42
+ }
43
+
44
+ return output;
45
+ }
46
+
47
+ function tscNoEmit(tsConfigPath: string, cwd: string): { ok: boolean; output: string } {
48
+ // Use the main project's tsc binary — avoids installing typescript (~48 MiB)
49
+ // inside each temp monorepo. rxjs types are still resolved from the temp
50
+ // monorepo's node_modules, so the type-checking result is accurate.
51
+ const r = spawnSync(process.execPath, [MAIN_TSC, '-p', tsConfigPath], {
52
+ cwd,
53
+ encoding: 'utf8',
54
+ stdio: 'pipe',
55
+ timeout: 30_000,
56
+ });
57
+
58
+ return { ok: r.status === 0, output: (r.stdout ?? '') + (r.stderr ?? '') };
59
+ }
60
+
61
+ interface WorkspaceSpec {
62
+ location: string;
63
+ manifest: Record<string, unknown>;
64
+ files?: Record<string, string>;
65
+ }
66
+
67
+ function createMonorepo(
68
+ rootManifest: Record<string, unknown>,
69
+ workspaces: WorkspaceSpec[],
70
+ ): { dir: string; cleanup: () => void } {
71
+ const dir = mkdtempSync(resolve(tmpdir(), 'rsdk-compat-'));
72
+
73
+ writeFileSync(
74
+ resolve(dir, 'package.json'),
75
+ JSON.stringify(
76
+ { ...rootManifest, workspaces: workspaces.map((w) => w.location) },
77
+ null,
78
+ 2,
79
+ ) + '\n',
80
+ );
81
+
82
+ writeFileSync(
83
+ resolve(dir, '.yarnrc.yml'),
84
+ [
85
+ `yarnPath: "${YARN_BIN}"`,
86
+ `cacheFolder: "${YARN_CACHE}"`,
87
+ 'enableGlobalCache: false',
88
+ 'nodeLinker: node-modules',
89
+ ].join('\n') + '\n',
90
+ );
91
+
92
+ for (const { location, manifest, files } of workspaces) {
93
+ const wsDir = resolve(dir, location);
94
+ mkdirSync(wsDir, { recursive: true });
95
+ writeFileSync(resolve(wsDir, 'package.json'), JSON.stringify(manifest, null, 2) + '\n');
96
+
97
+ for (const [relPath, content] of Object.entries(files ?? {})) {
98
+ const abs = resolve(wsDir, relPath);
99
+ mkdirSync(resolve(abs, '..'), { recursive: true });
100
+ writeFileSync(abs, content);
101
+ }
102
+ }
103
+
104
+ return {
105
+ dir,
106
+ cleanup: () => {
107
+ try {
108
+ rmSync(dir, { recursive: true, force: true });
109
+ } catch {}
110
+ },
111
+ };
112
+ }
113
+
114
+ function hasMissingPeer(output: string): boolean {
115
+ // YN0002 = MISSING_PEER_DEPENDENCY warning code in yarn 4.x
116
+ return output.includes('YN0002');
117
+ }
118
+
119
+ function hasFailedRequirement(output: string): boolean {
120
+ // yarn explain peer-requirements marks unmet requirements with ✘ (U+2718)
121
+ return output.includes('✘');
122
+ }
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // yarn-install-clean: library peer+dev mirror accepted by yarn
126
+ // ---------------------------------------------------------------------------
127
+
128
+ describe('compatibility: yarn-install-clean', () => {
129
+ let installOut: string;
130
+ let explainOut: string;
131
+ let cleanup: () => void;
132
+
133
+ beforeAll(() => {
134
+ // Library declares lodash as a peer dependency with a matching dev mirror
135
+ // (model invariants L1+L2). Service provides lodash in dependencies (P1).
136
+ const { dir, cleanup: c } = createMonorepo(
137
+ { private: true, name: '@compat/root' },
138
+ [
139
+ {
140
+ location: 'packages/lib',
141
+ manifest: {
142
+ name: '@compat/lib',
143
+ version: '1.0.0',
144
+ peerDependencies: { lodash: '^4.17.21' },
145
+ devDependencies: { lodash: '^4.17.21' },
146
+ },
147
+ },
148
+ {
149
+ location: 'packages/svc',
150
+ manifest: {
151
+ name: '@compat/svc',
152
+ version: '1.0.0',
153
+ dependencies: {
154
+ '@compat/lib': 'workspace:*',
155
+ lodash: '^4.17.21',
156
+ },
157
+ },
158
+ },
159
+ ],
160
+ );
161
+ cleanup = c;
162
+ installOut = yarn(['install'], dir);
163
+ explainOut = yarn(['explain', 'peer-requirements'], dir);
164
+ });
165
+
166
+ afterAll(() => cleanup());
167
+
168
+ it('no YN0002 missing-peer warnings on install', () => {
169
+ expect(hasMissingPeer(installOut)).toBe(false);
170
+ });
171
+
172
+ it('no failed peer requirements after install', () => {
173
+ expect(hasFailedRequirement(explainOut)).toBe(false);
174
+ });
175
+ });
176
+
177
+ // ---------------------------------------------------------------------------
178
+ // yarn-peer-requirements-clean: required external peers propagated to service
179
+ // ---------------------------------------------------------------------------
180
+
181
+ describe('compatibility: yarn-peer-requirements-clean', () => {
182
+ let installOut: string;
183
+ let explainOut: string;
184
+ let cleanup: () => void;
185
+
186
+ beforeAll(() => {
187
+ // @nestjs/common requires rxjs (required) and reflect-metadata (required).
188
+ // The model propagates both into service dependencies (invariant P2 + S1).
189
+ const { dir, cleanup: c } = createMonorepo(
190
+ { private: true, name: '@compat/root' },
191
+ [
192
+ {
193
+ location: 'packages/svc',
194
+ manifest: {
195
+ name: '@compat/svc',
196
+ version: '1.0.0',
197
+ dependencies: {
198
+ '@nestjs/common': '10.4.8',
199
+ rxjs: '^7.8.1',
200
+ 'reflect-metadata': '^0.1.12 || ^0.2.0',
201
+ },
202
+ },
203
+ },
204
+ ],
205
+ );
206
+ cleanup = c;
207
+ installOut = yarn(['install'], dir);
208
+ explainOut = yarn(['explain', 'peer-requirements'], dir);
209
+ });
210
+
211
+ afterAll(() => cleanup());
212
+
213
+ it('service with all required peers: no YN0002 on install', () => {
214
+ expect(hasMissingPeer(installOut)).toBe(false);
215
+ });
216
+
217
+ it('service with all required peers: no failed peer requirements', () => {
218
+ expect(hasFailedRequirement(explainOut)).toBe(false);
219
+ });
220
+ });
221
+
222
+ // ---------------------------------------------------------------------------
223
+ // missing-peer-detected: negative test — proves the assertions are meaningful
224
+ // ---------------------------------------------------------------------------
225
+
226
+ describe('compatibility: missing-peer-detected (negative)', () => {
227
+ let installOut: string;
228
+ let explainOut: string;
229
+ let cleanup: () => void;
230
+
231
+ beforeAll(() => {
232
+ // Required peers rxjs and reflect-metadata intentionally omitted.
233
+ const { dir, cleanup: c } = createMonorepo(
234
+ { private: true, name: '@compat/root' },
235
+ [
236
+ {
237
+ location: 'packages/svc',
238
+ manifest: {
239
+ name: '@compat/svc',
240
+ version: '1.0.0',
241
+ dependencies: { '@nestjs/common': '10.4.8' },
242
+ },
243
+ },
244
+ ],
245
+ );
246
+ cleanup = c;
247
+ installOut = yarn(['install'], dir);
248
+ explainOut = yarn(['explain', 'peer-requirements'], dir);
249
+ });
250
+
251
+ afterAll(() => cleanup());
252
+
253
+ it('missing required peers: yarn install reports YN0002', () => {
254
+ expect(hasMissingPeer(installOut)).toBe(true);
255
+ });
256
+
257
+ it('missing required peers: yarn explain peer-requirements reports failure', () => {
258
+ expect(hasFailedRequirement(explainOut)).toBe(true);
259
+ });
260
+ });
261
+
262
+ // ---------------------------------------------------------------------------
263
+ // tsc-library-build: library with peer+dev mirror compiles without errors
264
+ // ---------------------------------------------------------------------------
265
+
266
+ describe('compatibility: tsc-library-build', () => {
267
+ let result: { ok: boolean; output: string };
268
+ let cleanup: () => void;
269
+
270
+ beforeAll(() => {
271
+ // Library imports rxjs (which ships with bundled types). After install the
272
+ // dev mirror (devDependencies = peerDependencies) makes rxjs available for
273
+ // tsc without any additional @types packages.
274
+ const { dir, cleanup: c } = createMonorepo(
275
+ { private: true, name: '@compat/root' },
276
+ [
277
+ {
278
+ location: 'packages/lib',
279
+ manifest: {
280
+ name: '@compat/lib',
281
+ version: '1.0.0',
282
+ peerDependencies: { rxjs: '^7.8.1' },
283
+ devDependencies: { rxjs: '^7.8.1' },
284
+ },
285
+ files: {
286
+ 'src/index.ts': [
287
+ "import { Observable } from 'rxjs';",
288
+ 'export type Obs<T> = Observable<T>;',
289
+ '',
290
+ ].join('\n'),
291
+ 'tsconfig.json': JSON.stringify(
292
+ {
293
+ compilerOptions: {
294
+ strict: true,
295
+ noEmit: true,
296
+ module: 'commonjs',
297
+ target: 'es2020',
298
+ },
299
+ include: ['src/**/*.ts'],
300
+ },
301
+ null,
302
+ 2,
303
+ ) + '\n',
304
+ },
305
+ },
306
+ ],
307
+ );
308
+ cleanup = c;
309
+ yarn(['install'], dir);
310
+ result = tscNoEmit('packages/lib/tsconfig.json', dir);
311
+ });
312
+
313
+ afterAll(() => cleanup());
314
+
315
+ it('library with peer+dev mirror (rxjs): tsc type-checks without errors', () => {
316
+ if (result.output.includes('error TS')) {
317
+ throw new Error(`tsc failed:\n${result.output}`);
318
+ }
319
+ expect(result.ok).toBe(true);
320
+ });
321
+ });