@esmx/core 3.0.0-rc.117 → 3.0.0-rc.118

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 (82) hide show
  1. package/README.md +17 -3
  2. package/README.zh-CN.md +20 -6
  3. package/dist/app.mjs +4 -2
  4. package/dist/cli/cli.d.ts +1 -0
  5. package/dist/cli/cli.mjs +28 -1
  6. package/dist/cli/validate.d.ts +15 -0
  7. package/dist/cli/validate.mjs +139 -0
  8. package/dist/cli/validate.test.d.ts +1 -0
  9. package/dist/cli/validate.test.mjs +216 -0
  10. package/dist/core.d.ts +31 -0
  11. package/dist/core.mjs +71 -5
  12. package/dist/declaration/index.d.ts +47 -0
  13. package/dist/declaration/index.mjs +63 -0
  14. package/dist/declaration/index.test.d.ts +1 -0
  15. package/dist/declaration/index.test.mjs +202 -0
  16. package/dist/declaration/lower.d.ts +11 -0
  17. package/dist/declaration/lower.mjs +78 -0
  18. package/dist/declaration/lower.test.d.ts +1 -0
  19. package/dist/declaration/lower.test.mjs +333 -0
  20. package/dist/declaration/reader.d.ts +28 -0
  21. package/dist/declaration/reader.mjs +55 -0
  22. package/dist/declaration/reinit.e2e.test.d.ts +1 -0
  23. package/dist/declaration/reinit.e2e.test.mjs +117 -0
  24. package/dist/declaration/resolver.d.ts +59 -0
  25. package/dist/declaration/resolver.mjs +430 -0
  26. package/dist/declaration/resolver.test.d.ts +1 -0
  27. package/dist/declaration/resolver.test.mjs +1005 -0
  28. package/dist/declaration/schema.d.ts +89 -0
  29. package/dist/declaration/schema.mjs +282 -0
  30. package/dist/declaration/schema.test.d.ts +1 -0
  31. package/dist/declaration/schema.test.mjs +101 -0
  32. package/dist/declaration/semver.d.ts +22 -0
  33. package/dist/declaration/semver.mjs +229 -0
  34. package/dist/declaration/semver.test.d.ts +1 -0
  35. package/dist/declaration/semver.test.mjs +87 -0
  36. package/dist/declaration/test-fixtures.d.ts +18 -0
  37. package/dist/declaration/test-fixtures.mjs +69 -0
  38. package/dist/declaration/types.d.ts +58 -0
  39. package/dist/declaration/types.mjs +21 -0
  40. package/dist/index.d.ts +4 -1
  41. package/dist/index.mjs +10 -0
  42. package/dist/manifest-json.d.ts +61 -0
  43. package/dist/manifest-json.mjs +68 -5
  44. package/dist/manifest-json.test.d.ts +1 -0
  45. package/dist/manifest-json.test.mjs +196 -0
  46. package/dist/module-config.d.ts +45 -5
  47. package/dist/module-config.mjs +60 -49
  48. package/dist/module-config.test.mjs +227 -91
  49. package/dist/pack-config.d.ts +2 -9
  50. package/dist/render-context.mjs +22 -5
  51. package/dist/utils/import-map.d.ts +23 -3
  52. package/dist/utils/import-map.mjs +38 -1
  53. package/dist/utils/import-map.test.mjs +228 -0
  54. package/package.json +9 -6
  55. package/src/app.ts +5 -2
  56. package/src/cli/cli.ts +44 -1
  57. package/src/cli/validate.test.ts +251 -0
  58. package/src/cli/validate.ts +196 -0
  59. package/src/core.ts +84 -5
  60. package/src/declaration/index.test.ts +223 -0
  61. package/src/declaration/index.ts +135 -0
  62. package/src/declaration/lower.test.ts +372 -0
  63. package/src/declaration/lower.ts +135 -0
  64. package/src/declaration/reader.ts +93 -0
  65. package/src/declaration/reinit.e2e.test.ts +148 -0
  66. package/src/declaration/resolver.test.ts +1111 -0
  67. package/src/declaration/resolver.ts +638 -0
  68. package/src/declaration/schema.test.ts +118 -0
  69. package/src/declaration/schema.ts +339 -0
  70. package/src/declaration/semver.test.ts +101 -0
  71. package/src/declaration/semver.ts +278 -0
  72. package/src/declaration/test-fixtures.ts +96 -0
  73. package/src/declaration/types.ts +71 -0
  74. package/src/index.ts +28 -1
  75. package/src/manifest-json.test.ts +236 -0
  76. package/src/manifest-json.ts +166 -5
  77. package/src/module-config.test.ts +266 -105
  78. package/src/module-config.ts +130 -58
  79. package/src/pack-config.ts +2 -9
  80. package/src/render-context.ts +34 -6
  81. package/src/utils/import-map.test.ts +261 -0
  82. package/src/utils/import-map.ts +92 -6
@@ -0,0 +1,1005 @@
1
+ import path from "node:path";
2
+ import { afterEach, describe, expect, it } from "vitest";
3
+ import { readDeclaration } from "./reader.mjs";
4
+ import { resolveMounts } from "./resolver.mjs";
5
+ import {
6
+ createFixtureRoot,
7
+ removeFixtureRoot,
8
+ writeFixturePackage
9
+ } from "./test-fixtures.mjs";
10
+ import { DiagnosticCode } from "./types.mjs";
11
+ const fixtureRoots = [];
12
+ async function fixtureRoot() {
13
+ const root = await createFixtureRoot();
14
+ fixtureRoots.push(root);
15
+ return root;
16
+ }
17
+ afterEach(async () => {
18
+ await Promise.all(fixtureRoots.splice(0).map(removeFixtureRoot));
19
+ });
20
+ function readRootDeclaration(appDir) {
21
+ const pkg = readDeclaration(appDir);
22
+ if (!pkg) {
23
+ throw new Error(`fixture app at ${appDir} must declare esmx`);
24
+ }
25
+ return pkg;
26
+ }
27
+ describe("resolveMounts", () => {
28
+ it("should report E_DUP_PROVIDER when a layer self-provides over a used provider of the same (package, major)", async () => {
29
+ const root = await fixtureRoot();
30
+ writeFixturePackage(root, {
31
+ dir: "node_modules/base",
32
+ packageJson: {
33
+ name: "base",
34
+ version: "1.0.0",
35
+ dependencies: { vue: "^3.4.0" },
36
+ esmx: { provides: ["vue"] }
37
+ },
38
+ built: true
39
+ });
40
+ writeFixturePackage(root, {
41
+ dir: "node_modules/base/node_modules/vue",
42
+ packageJson: { name: "vue", version: "3.4.21" }
43
+ });
44
+ writeFixturePackage(root, {
45
+ dir: "node_modules/vue-base",
46
+ packageJson: {
47
+ name: "vue-base",
48
+ version: "1.0.0",
49
+ dependencies: { vue: "^3.4.0" },
50
+ esmx: { uses: ["base"], provides: ["vue"] }
51
+ },
52
+ built: true
53
+ });
54
+ writeFixturePackage(root, {
55
+ dir: "node_modules/vue-base/node_modules/vue",
56
+ packageJson: { name: "vue", version: "3.5.2" }
57
+ });
58
+ const appDir = writeFixturePackage(root, {
59
+ dir: "app",
60
+ packageJson: {
61
+ name: "app",
62
+ version: "1.0.0",
63
+ dependencies: { vue: "^3.4.0" },
64
+ esmx: { uses: ["vue-base"] }
65
+ }
66
+ });
67
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
68
+ const dupes = result.diagnostics.filter(
69
+ (d) => d.code === DiagnosticCode.E_DUP_PROVIDER
70
+ );
71
+ expect(dupes).toHaveLength(1);
72
+ expect(dupes[0].severity).toBe("error");
73
+ expect(dupes[0].module).toBe("vue-base");
74
+ expect(dupes[0].package).toBe("vue");
75
+ expect(dupes[0].found).toBe("base, vue-base");
76
+ expect(Object.keys(result.mounts).sort()).toEqual(["base", "vue-base"]);
77
+ });
78
+ it("should report E_DUP_PROVIDER when two distinct providers reach a consumer via separate subtrees", async () => {
79
+ const root = await fixtureRoot();
80
+ for (const [name, provides] of [
81
+ ["x", true],
82
+ ["y", true],
83
+ ["b1", false],
84
+ ["b2", false]
85
+ ]) {
86
+ writeFixturePackage(root, {
87
+ dir: `node_modules/${name}`,
88
+ packageJson: {
89
+ name,
90
+ version: "1.0.0",
91
+ esmx: provides ? { provides: ["vue"] } : { uses: [name === "b1" ? "x" : "y"] }
92
+ },
93
+ built: true
94
+ });
95
+ }
96
+ writeFixturePackage(root, {
97
+ dir: "node_modules/x/node_modules/vue",
98
+ packageJson: { name: "vue", version: "3.4.0" }
99
+ });
100
+ writeFixturePackage(root, {
101
+ dir: "node_modules/y/node_modules/vue",
102
+ packageJson: { name: "vue", version: "3.5.0" }
103
+ });
104
+ const appDir = writeFixturePackage(root, {
105
+ dir: "app",
106
+ packageJson: {
107
+ name: "app",
108
+ version: "1.0.0",
109
+ dependencies: { vue: "^3.4.0" },
110
+ esmx: { uses: ["b1", "b2"] }
111
+ }
112
+ });
113
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
114
+ const dupes = result.diagnostics.filter(
115
+ (d) => d.code === DiagnosticCode.E_DUP_PROVIDER
116
+ );
117
+ expect(dupes).toHaveLength(1);
118
+ expect(dupes[0].module).toBe("app");
119
+ expect(dupes[0].package).toBe("vue");
120
+ expect(dupes[0].found).toBe("x, y");
121
+ });
122
+ it("should report E_DUP_PROVIDER when a self-provider collides with a used provider of the same (package, major)", async () => {
123
+ const root = await fixtureRoot();
124
+ writeFixturePackage(root, {
125
+ dir: "node_modules/base",
126
+ packageJson: {
127
+ name: "base",
128
+ version: "1.0.0",
129
+ esmx: { provides: ["vue"] }
130
+ },
131
+ built: true
132
+ });
133
+ writeFixturePackage(root, {
134
+ dir: "node_modules/base/node_modules/vue",
135
+ packageJson: { name: "vue", version: "3.4.0" }
136
+ });
137
+ writeFixturePackage(root, {
138
+ dir: "node_modules/vue",
139
+ packageJson: { name: "vue", version: "3.5.2" }
140
+ });
141
+ const appDir = writeFixturePackage(root, {
142
+ dir: "app",
143
+ packageJson: {
144
+ name: "app",
145
+ version: "1.0.0",
146
+ dependencies: { vue: "^3.4.0" },
147
+ esmx: { uses: ["base"], provides: ["vue"] }
148
+ }
149
+ });
150
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
151
+ const dupes = result.diagnostics.filter(
152
+ (d) => d.code === DiagnosticCode.E_DUP_PROVIDER
153
+ );
154
+ expect(dupes).toHaveLength(1);
155
+ expect(dupes[0].module).toBe("app");
156
+ expect(dupes[0].package).toBe("vue");
157
+ expect(dupes[0].found).toBe("base, app");
158
+ });
159
+ it("should report E_TARGET_MISSING when a declared entry/exports target file is absent", async () => {
160
+ const root = await fixtureRoot();
161
+ const appDir = writeFixturePackage(root, {
162
+ dir: "app",
163
+ packageJson: {
164
+ name: "app",
165
+ version: "1.0.0",
166
+ esmx: {
167
+ entry: { client: "./src/entry.client.ts" },
168
+ exports: { "./widget": "./src/widget.ts" }
169
+ }
170
+ },
171
+ noSources: true
172
+ });
173
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
174
+ const missing = result.diagnostics.filter(
175
+ (d) => d.code === DiagnosticCode.E_TARGET_MISSING
176
+ );
177
+ expect(missing).toHaveLength(2);
178
+ expect(missing.every((d) => d.severity === "error")).toBe(true);
179
+ expect(missing.map((d) => d.found).sort()).toEqual([
180
+ "./src/entry.client.ts",
181
+ "./src/widget.ts"
182
+ ]);
183
+ });
184
+ it("should not report E_TARGET_MISSING when declared targets exist", async () => {
185
+ const root = await fixtureRoot();
186
+ const appDir = writeFixturePackage(root, {
187
+ dir: "app",
188
+ packageJson: {
189
+ name: "app",
190
+ version: "1.0.0",
191
+ esmx: { entry: { client: "./src/entry.client.ts" } }
192
+ }
193
+ });
194
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
195
+ expect(
196
+ result.diagnostics.filter(
197
+ (d) => d.code === DiagnosticCode.E_TARGET_MISSING
198
+ )
199
+ ).toHaveLength(0);
200
+ });
201
+ it("should report E_CYCLE when the uses chain revisits a module", async () => {
202
+ const root = await fixtureRoot();
203
+ writeFixturePackage(root, {
204
+ dir: "node_modules/b",
205
+ packageJson: {
206
+ name: "b",
207
+ version: "1.0.0",
208
+ esmx: { uses: ["a"] }
209
+ },
210
+ built: true
211
+ });
212
+ const appDir = writeFixturePackage(root, {
213
+ dir: "a",
214
+ packageJson: {
215
+ name: "a",
216
+ version: "1.0.0",
217
+ esmx: { uses: ["b"] }
218
+ }
219
+ });
220
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
221
+ const cycles = result.diagnostics.filter(
222
+ (d) => d.code === DiagnosticCode.E_CYCLE
223
+ );
224
+ expect(cycles).toHaveLength(1);
225
+ expect(cycles[0].severity).toBe("error");
226
+ expect(cycles[0].module).toBe("a");
227
+ });
228
+ it("should report E_NOT_LINKED for a uses entry with no mount", async () => {
229
+ const root = await fixtureRoot();
230
+ const appDir = writeFixturePackage(root, {
231
+ dir: "app",
232
+ packageJson: {
233
+ name: "app",
234
+ version: "1.0.0",
235
+ esmx: { uses: ["ghost"] }
236
+ }
237
+ });
238
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
239
+ const notLinked = result.diagnostics.filter(
240
+ (d) => d.code === DiagnosticCode.E_NOT_LINKED
241
+ );
242
+ expect(notLinked).toHaveLength(1);
243
+ expect(notLinked[0].module).toBe("app");
244
+ expect(notLinked[0].package).toBe("ghost");
245
+ expect(result.mounts).toEqual({});
246
+ });
247
+ it("should report E_NOT_BUILT but keep declaration wiring", async () => {
248
+ const root = await fixtureRoot();
249
+ writeFixturePackage(root, {
250
+ dir: "node_modules/raw",
251
+ packageJson: {
252
+ name: "raw",
253
+ version: "1.0.0",
254
+ esmx: { provides: ["vue"] }
255
+ }
256
+ });
257
+ writeFixturePackage(root, {
258
+ dir: "node_modules/raw/node_modules/vue",
259
+ packageJson: { name: "vue", version: "3.4.0" }
260
+ });
261
+ const appDir = writeFixturePackage(root, {
262
+ dir: "app",
263
+ packageJson: {
264
+ name: "app",
265
+ version: "1.0.0",
266
+ dependencies: { vue: "^3.4.0" },
267
+ esmx: { uses: ["raw"] }
268
+ }
269
+ });
270
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
271
+ const notBuilt = result.diagnostics.filter(
272
+ (d) => d.code === DiagnosticCode.E_NOT_BUILT
273
+ );
274
+ expect(notBuilt).toHaveLength(1);
275
+ expect(notBuilt[0].package).toBe("raw");
276
+ expect(result.mounts.raw.built).toBe(false);
277
+ expect(result.supply.vue).toEqual({
278
+ groups: [{ major: 3, provider: "raw", version: "3.4.0" }]
279
+ });
280
+ });
281
+ it("should report E_VERSION (intent) when the winner violates a layer range", async () => {
282
+ const root = await fixtureRoot();
283
+ writeFixturePackage(root, {
284
+ dir: "node_modules/base",
285
+ packageJson: {
286
+ name: "base",
287
+ version: "1.0.0",
288
+ dependencies: { vue: "^3.0.0" },
289
+ esmx: { provides: ["vue"] }
290
+ },
291
+ built: true
292
+ });
293
+ writeFixturePackage(root, {
294
+ dir: "node_modules/base/node_modules/vue",
295
+ packageJson: { name: "vue", version: "3.0.5" }
296
+ });
297
+ const appDir = writeFixturePackage(root, {
298
+ dir: "app",
299
+ packageJson: {
300
+ name: "app",
301
+ version: "1.0.0",
302
+ dependencies: { vue: "^3.4.0" },
303
+ esmx: { uses: ["base"] }
304
+ }
305
+ });
306
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
307
+ const versionErrors = result.diagnostics.filter(
308
+ (d) => d.code === DiagnosticCode.E_VERSION
309
+ );
310
+ expect(versionErrors).toHaveLength(1);
311
+ expect(versionErrors[0].check).toBe("intent");
312
+ expect(versionErrors[0].module).toBe("app");
313
+ expect(versionErrors[0].package).toBe("vue");
314
+ expect(versionErrors[0].found).toBe("3.0.5");
315
+ expect(versionErrors[0].required).toBe("^3.4.0");
316
+ });
317
+ it("should report E_VERSION (intent) for used-module version vs range", async () => {
318
+ const root = await fixtureRoot();
319
+ writeFixturePackage(root, {
320
+ dir: "node_modules/shared",
321
+ packageJson: {
322
+ name: "shared",
323
+ version: "2.0.0",
324
+ esmx: {}
325
+ },
326
+ built: true
327
+ });
328
+ const appDir = writeFixturePackage(root, {
329
+ dir: "app",
330
+ packageJson: {
331
+ name: "app",
332
+ version: "1.0.0",
333
+ dependencies: { shared: "^1.0.0" },
334
+ esmx: { uses: ["shared"] }
335
+ }
336
+ });
337
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
338
+ const versionErrors = result.diagnostics.filter(
339
+ (d) => d.code === DiagnosticCode.E_VERSION
340
+ );
341
+ expect(versionErrors).toHaveLength(1);
342
+ expect(versionErrors[0].check).toBe("intent");
343
+ expect(versionErrors[0].package).toBe("shared");
344
+ expect(versionErrors[0].found).toBe("2.0.0");
345
+ });
346
+ it("should skip the used-module version gate for workspace ranges", async () => {
347
+ const root = await fixtureRoot();
348
+ writeFixturePackage(root, {
349
+ dir: "node_modules/shared",
350
+ packageJson: {
351
+ name: "shared",
352
+ version: "2.0.0",
353
+ esmx: {}
354
+ },
355
+ built: true
356
+ });
357
+ const appDir = writeFixturePackage(root, {
358
+ dir: "app",
359
+ packageJson: {
360
+ name: "app",
361
+ version: "1.0.0",
362
+ dependencies: { shared: "workspace:^1.0.0" },
363
+ esmx: { uses: ["shared"] }
364
+ }
365
+ });
366
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
367
+ expect(
368
+ result.diagnostics.filter(
369
+ (d) => d.code === DiagnosticCode.E_VERSION
370
+ )
371
+ ).toEqual([]);
372
+ });
373
+ it("should warn W_NO_RANGE when a layer receives a package without any range", async () => {
374
+ const root = await fixtureRoot();
375
+ writeFixturePackage(root, {
376
+ dir: "node_modules/base",
377
+ packageJson: {
378
+ name: "base",
379
+ version: "1.0.0",
380
+ dependencies: { vue: "^3.4.0" },
381
+ esmx: { provides: ["vue"] }
382
+ },
383
+ built: true
384
+ });
385
+ writeFixturePackage(root, {
386
+ dir: "node_modules/base/node_modules/vue",
387
+ packageJson: { name: "vue", version: "3.4.21" }
388
+ });
389
+ const appDir = writeFixturePackage(root, {
390
+ dir: "app",
391
+ packageJson: {
392
+ name: "app",
393
+ version: "1.0.0",
394
+ esmx: { uses: ["base"] }
395
+ }
396
+ });
397
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
398
+ const noRange = result.diagnostics.filter(
399
+ (d) => d.code === DiagnosticCode.W_NO_RANGE
400
+ );
401
+ expect(noRange).toHaveLength(1);
402
+ expect(noRange[0].severity).toBe("warning");
403
+ expect(noRange[0].module).toBe("app");
404
+ expect(noRange[0].package).toBe("vue");
405
+ });
406
+ it("should warn W_TYPE_DRIFT when the local devDependencies copy diverges from the winner", async () => {
407
+ const root = await fixtureRoot();
408
+ writeFixturePackage(root, {
409
+ dir: "node_modules/base",
410
+ packageJson: {
411
+ name: "base",
412
+ version: "1.0.0",
413
+ dependencies: { vue: "^3.4.0" },
414
+ esmx: { provides: ["vue"] }
415
+ },
416
+ built: true
417
+ });
418
+ writeFixturePackage(root, {
419
+ dir: "node_modules/base/node_modules/vue",
420
+ packageJson: { name: "vue", version: "3.5.2" }
421
+ });
422
+ const appDir = writeFixturePackage(root, {
423
+ dir: "app",
424
+ packageJson: {
425
+ name: "app",
426
+ version: "1.0.0",
427
+ devDependencies: { vue: "^3.4.0" },
428
+ esmx: { uses: ["base"] }
429
+ }
430
+ });
431
+ writeFixturePackage(root, {
432
+ dir: "app/node_modules/vue",
433
+ packageJson: { name: "vue", version: "3.4.0" }
434
+ });
435
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
436
+ const drift = result.diagnostics.filter(
437
+ (d) => d.code === DiagnosticCode.W_TYPE_DRIFT
438
+ );
439
+ expect(drift).toHaveLength(1);
440
+ expect(drift[0].module).toBe("app");
441
+ expect(drift[0].package).toBe("vue");
442
+ expect(drift[0].found).toBe("3.4.0");
443
+ expect(drift[0].required).toBe("3.5.2");
444
+ });
445
+ it("should report E_PROTOCOL for a mounted manifest with a higher protocol", async () => {
446
+ const root = await fixtureRoot();
447
+ writeFixturePackage(root, {
448
+ dir: "node_modules/future",
449
+ packageJson: {
450
+ name: "future",
451
+ version: "1.0.0",
452
+ esmx: {}
453
+ },
454
+ manifest: { protocol: 3, name: "future" }
455
+ });
456
+ const appDir = writeFixturePackage(root, {
457
+ dir: "app",
458
+ packageJson: {
459
+ name: "app",
460
+ version: "1.0.0",
461
+ esmx: { uses: ["future"] }
462
+ }
463
+ });
464
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
465
+ const protocol = result.diagnostics.filter(
466
+ (d) => d.code === DiagnosticCode.E_PROTOCOL
467
+ );
468
+ expect(protocol).toHaveLength(1);
469
+ expect(protocol[0].package).toBe("future");
470
+ expect(protocol[0].found).toBe("3");
471
+ });
472
+ it("should accept protocol 2 and protocol-less (pre-v2) manifests without E_PROTOCOL", async () => {
473
+ const root = await fixtureRoot();
474
+ writeFixturePackage(root, {
475
+ dir: "node_modules/modern",
476
+ packageJson: { name: "modern", version: "1.0.0", esmx: {} },
477
+ manifest: { protocol: 2, name: "modern" }
478
+ });
479
+ writeFixturePackage(root, {
480
+ dir: "node_modules/legacy",
481
+ packageJson: { name: "legacy", version: "1.0.0", esmx: {} },
482
+ manifest: { name: "legacy" }
483
+ });
484
+ const appDir = writeFixturePackage(root, {
485
+ dir: "app",
486
+ packageJson: {
487
+ name: "app",
488
+ version: "1.0.0",
489
+ esmx: { uses: ["modern", "legacy"] }
490
+ }
491
+ });
492
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
493
+ const protocol = result.diagnostics.filter(
494
+ (d) => d.code === DiagnosticCode.E_PROTOCOL
495
+ );
496
+ expect(protocol).toEqual([]);
497
+ });
498
+ it("should honor envLinks overrides resolved relative to rootDir", async () => {
499
+ const root = await fixtureRoot();
500
+ writeFixturePackage(root, {
501
+ dir: "shared",
502
+ packageJson: {
503
+ name: "shared",
504
+ version: "1.0.0",
505
+ esmx: { provides: ["vue"] }
506
+ },
507
+ built: true
508
+ });
509
+ writeFixturePackage(root, {
510
+ dir: "shared/node_modules/vue",
511
+ packageJson: { name: "vue", version: "3.4.21" }
512
+ });
513
+ const appDir = writeFixturePackage(root, {
514
+ dir: "app",
515
+ packageJson: {
516
+ name: "app",
517
+ version: "1.0.0",
518
+ dependencies: { vue: "^3.4.0" },
519
+ esmx: { uses: ["shared"] }
520
+ }
521
+ });
522
+ const result = resolveMounts(appDir, readRootDeclaration(appDir), {
523
+ shared: "../shared/dist"
524
+ });
525
+ expect(result.mounts.shared).toEqual({
526
+ name: "shared",
527
+ root: path.join(root, "shared"),
528
+ artifactDir: path.join(root, "shared/dist"),
529
+ built: true
530
+ });
531
+ expect(result.supply.vue).toEqual({
532
+ groups: [{ major: 3, provider: "shared", version: "3.4.21" }]
533
+ });
534
+ });
535
+ it("should keep coexisting majors as isolated groups with W_MULTI_MAJOR and no E_VERSION", async () => {
536
+ const root = await fixtureRoot();
537
+ writeFixturePackage(root, {
538
+ dir: "node_modules/vue2-app",
539
+ packageJson: {
540
+ name: "vue2-app",
541
+ version: "1.0.0",
542
+ dependencies: { vue: "2.7.16" },
543
+ esmx: { provides: ["vue"] }
544
+ },
545
+ manifest: { provides: { vue: "2.7.16" } }
546
+ });
547
+ writeFixturePackage(root, {
548
+ dir: "node_modules/vue2-app/node_modules/vue",
549
+ packageJson: { name: "vue", version: "2.7.16" }
550
+ });
551
+ writeFixturePackage(root, {
552
+ dir: "node_modules/vue3-base",
553
+ packageJson: {
554
+ name: "vue3-base",
555
+ version: "1.0.0",
556
+ dependencies: { vue: "^3.5.0" },
557
+ esmx: { provides: ["vue"] }
558
+ },
559
+ manifest: { provides: { vue: "3.5.13" } }
560
+ });
561
+ writeFixturePackage(root, {
562
+ dir: "node_modules/vue3-base/node_modules/vue",
563
+ packageJson: { name: "vue", version: "3.5.13" }
564
+ });
565
+ const appDir = writeFixturePackage(root, {
566
+ dir: "app",
567
+ packageJson: {
568
+ name: "app",
569
+ version: "1.0.0",
570
+ peerDependencies: { vue: "^3.5.0" },
571
+ esmx: { uses: ["vue2-app", "vue3-base"] }
572
+ }
573
+ });
574
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
575
+ expect(result.supply.vue).toEqual({
576
+ groups: [
577
+ { major: 3, provider: "vue3-base", version: "3.5.13" },
578
+ { major: 2, provider: "vue2-app", version: "2.7.16" }
579
+ ]
580
+ });
581
+ const errors = result.diagnostics.filter((d) => d.severity === "error");
582
+ expect(errors).toEqual([]);
583
+ const multiMajor = result.diagnostics.filter(
584
+ (d) => d.code === DiagnosticCode.W_MULTI_MAJOR
585
+ );
586
+ expect(multiMajor).toHaveLength(1);
587
+ expect(multiMajor[0].package).toBe("vue");
588
+ expect(multiMajor[0].found).toContain("vue3-base");
589
+ expect(multiMajor[0].found).toContain("vue2-app");
590
+ });
591
+ it("should let different majors coexist across a uses chain with no E_DUP_PROVIDER", async () => {
592
+ const root = await fixtureRoot();
593
+ writeFixturePackage(root, {
594
+ dir: "node_modules/base",
595
+ packageJson: {
596
+ name: "base",
597
+ version: "1.0.0",
598
+ dependencies: { vue: "^2.6.0" },
599
+ esmx: { provides: ["vue"] }
600
+ },
601
+ manifest: { provides: { vue: "2.6.14" } }
602
+ });
603
+ writeFixturePackage(root, {
604
+ dir: "node_modules/base/node_modules/vue",
605
+ packageJson: { name: "vue", version: "2.6.14" }
606
+ });
607
+ writeFixturePackage(root, {
608
+ dir: "node_modules/vue-base",
609
+ packageJson: {
610
+ name: "vue-base",
611
+ version: "1.0.0",
612
+ dependencies: { vue: "^3.4.0" },
613
+ esmx: { uses: ["base"], provides: ["vue"] }
614
+ },
615
+ built: true
616
+ });
617
+ writeFixturePackage(root, {
618
+ dir: "node_modules/vue-base/node_modules/vue",
619
+ packageJson: { name: "vue", version: "3.5.2" }
620
+ });
621
+ const appDir = writeFixturePackage(root, {
622
+ dir: "app",
623
+ packageJson: {
624
+ name: "app",
625
+ version: "1.0.0",
626
+ dependencies: { vue: "^3.4.0" },
627
+ esmx: { uses: ["vue-base"] }
628
+ }
629
+ });
630
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
631
+ expect(
632
+ result.diagnostics.filter(
633
+ (d) => d.code === DiagnosticCode.E_DUP_PROVIDER
634
+ )
635
+ ).toEqual([]);
636
+ expect(
637
+ result.diagnostics.filter((d) => d.severity === "error")
638
+ ).toEqual([]);
639
+ expect(result.supply.vue.groups).toHaveLength(2);
640
+ expect(
641
+ result.diagnostics.filter(
642
+ (d) => d.code === DiagnosticCode.W_MULTI_MAJOR
643
+ )
644
+ ).toHaveLength(1);
645
+ });
646
+ it("should validate intent against the group a layer wires to, not an unrelated major", async () => {
647
+ const root = await fixtureRoot();
648
+ writeFixturePackage(root, {
649
+ dir: "node_modules/vue2-app",
650
+ packageJson: {
651
+ name: "vue2-app",
652
+ version: "1.0.0",
653
+ dependencies: { vue: "2.7.16" },
654
+ esmx: { provides: ["vue"] }
655
+ },
656
+ built: true
657
+ });
658
+ writeFixturePackage(root, {
659
+ dir: "node_modules/vue2-app/node_modules/vue",
660
+ packageJson: { name: "vue", version: "2.7.16" }
661
+ });
662
+ writeFixturePackage(root, {
663
+ dir: "node_modules/vue3-base",
664
+ packageJson: {
665
+ name: "vue3-base",
666
+ version: "1.0.0",
667
+ dependencies: { vue: "^3.5.0" },
668
+ esmx: { provides: ["vue"] }
669
+ },
670
+ built: true
671
+ });
672
+ writeFixturePackage(root, {
673
+ dir: "node_modules/vue3-base/node_modules/vue",
674
+ packageJson: { name: "vue", version: "3.5.13" }
675
+ });
676
+ const appDir = writeFixturePackage(root, {
677
+ dir: "app",
678
+ packageJson: {
679
+ name: "app",
680
+ version: "1.0.0",
681
+ dependencies: { vue: "^2.7.0" },
682
+ esmx: { uses: ["vue3-base", "vue2-app"] }
683
+ }
684
+ });
685
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
686
+ expect(
687
+ result.diagnostics.filter(
688
+ (d) => d.code === DiagnosticCode.E_VERSION
689
+ )
690
+ ).toEqual([]);
691
+ });
692
+ it("should report E_VERSION (intent) when no major group satisfies the range", async () => {
693
+ const root = await fixtureRoot();
694
+ writeFixturePackage(root, {
695
+ dir: "node_modules/vue2-app",
696
+ packageJson: {
697
+ name: "vue2-app",
698
+ version: "1.0.0",
699
+ dependencies: { vue: "2.7.16" },
700
+ esmx: { provides: ["vue"] }
701
+ },
702
+ built: true
703
+ });
704
+ writeFixturePackage(root, {
705
+ dir: "node_modules/vue2-app/node_modules/vue",
706
+ packageJson: { name: "vue", version: "2.7.16" }
707
+ });
708
+ writeFixturePackage(root, {
709
+ dir: "node_modules/vue3-base",
710
+ packageJson: {
711
+ name: "vue3-base",
712
+ version: "1.0.0",
713
+ dependencies: { vue: "^3.5.0" },
714
+ esmx: { provides: ["vue"] }
715
+ },
716
+ built: true
717
+ });
718
+ writeFixturePackage(root, {
719
+ dir: "node_modules/vue3-base/node_modules/vue",
720
+ packageJson: { name: "vue", version: "3.5.13" }
721
+ });
722
+ const appDir = writeFixturePackage(root, {
723
+ dir: "app",
724
+ packageJson: {
725
+ name: "app",
726
+ version: "1.0.0",
727
+ dependencies: { vue: "^4.0.0" },
728
+ esmx: { uses: ["vue2-app", "vue3-base"] }
729
+ }
730
+ });
731
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
732
+ const versionErrors = result.diagnostics.filter(
733
+ (d) => d.code === DiagnosticCode.E_VERSION
734
+ );
735
+ expect(versionErrors).toHaveLength(1);
736
+ expect(versionErrors[0].check).toBe("intent");
737
+ expect(versionErrors[0].module).toBe("app");
738
+ expect(versionErrors[0].required).toBe("^4.0.0");
739
+ });
740
+ it("should resolve the hub-shaped topology (vue2 + multiple vue3 providers) with warnings only", async () => {
741
+ const root = await fixtureRoot();
742
+ writeFixturePackage(root, {
743
+ dir: "node_modules/shared",
744
+ packageJson: {
745
+ name: "shared",
746
+ version: "1.0.0",
747
+ dependencies: { unhead: "^3.1.3" },
748
+ esmx: { provides: ["unhead"] }
749
+ },
750
+ manifest: { provides: { unhead: "3.1.3" } }
751
+ });
752
+ writeFixturePackage(root, {
753
+ dir: "node_modules/shared/node_modules/unhead",
754
+ packageJson: { name: "unhead", version: "3.1.3" }
755
+ });
756
+ writeFixturePackage(root, {
757
+ dir: "node_modules/vue2-app",
758
+ packageJson: {
759
+ name: "vue2-app",
760
+ version: "1.0.0",
761
+ dependencies: { vue: "2.7.16" },
762
+ esmx: { uses: ["shared"], provides: ["vue"] }
763
+ },
764
+ manifest: { provides: { vue: "2.7.16" } }
765
+ });
766
+ writeFixturePackage(root, {
767
+ dir: "node_modules/vue2-app/node_modules/vue",
768
+ packageJson: { name: "vue", version: "2.7.16" }
769
+ });
770
+ const vue3Providers = ["vue3-app", "vite-vue-app", "rsbuild-vue-app"];
771
+ for (const name of vue3Providers) {
772
+ writeFixturePackage(root, {
773
+ dir: `node_modules/${name}`,
774
+ packageJson: {
775
+ name,
776
+ version: "1.0.0",
777
+ dependencies: { vue: "^3.5.0" },
778
+ esmx: { uses: ["shared"], provides: ["vue"] }
779
+ },
780
+ manifest: { provides: { vue: "3.5.40" } }
781
+ });
782
+ writeFixturePackage(root, {
783
+ dir: `node_modules/${name}/node_modules/vue`,
784
+ packageJson: { name: "vue", version: "3.5.40" }
785
+ });
786
+ }
787
+ const passiveApps = [];
788
+ for (let i = 0; i < 11; i++) {
789
+ const name = `app-${i}`;
790
+ passiveApps.push(name);
791
+ writeFixturePackage(root, {
792
+ dir: `node_modules/${name}`,
793
+ packageJson: {
794
+ name,
795
+ version: "1.0.0",
796
+ peerDependencies: { unhead: "^3.1.3" },
797
+ esmx: { uses: ["shared"] }
798
+ },
799
+ built: true
800
+ });
801
+ }
802
+ const appDir = writeFixturePackage(root, {
803
+ dir: "hub",
804
+ packageJson: {
805
+ name: "hub",
806
+ version: "1.0.0",
807
+ peerDependencies: { unhead: "^3.1.3", vue: "^3.5.34" },
808
+ esmx: {
809
+ uses: [
810
+ "shared",
811
+ ...passiveApps,
812
+ "vue2-app",
813
+ ...vue3Providers
814
+ ]
815
+ }
816
+ }
817
+ });
818
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
819
+ const dupes = result.diagnostics.filter(
820
+ (d) => d.code === DiagnosticCode.E_DUP_PROVIDER
821
+ );
822
+ expect(dupes).toHaveLength(1);
823
+ expect(dupes[0].module).toBe("hub");
824
+ expect(dupes[0].package).toBe("vue");
825
+ expect(dupes[0].found).toBe("vue3-app, vite-vue-app");
826
+ const codes = result.diagnostics.map((d) => d.code);
827
+ expect(codes).toContain(DiagnosticCode.W_MULTI_MAJOR);
828
+ });
829
+ it("should report a single de-duped E_DUP_PROVIDER for a 3-provider same-major collision", async () => {
830
+ const root = await fixtureRoot();
831
+ for (const name of ["pa", "pb", "pc"]) {
832
+ writeFixturePackage(root, {
833
+ dir: `node_modules/${name}`,
834
+ packageJson: {
835
+ name,
836
+ version: "1.0.0",
837
+ esmx: { provides: ["vue"] }
838
+ },
839
+ built: true
840
+ });
841
+ writeFixturePackage(root, {
842
+ dir: `node_modules/${name}/node_modules/vue`,
843
+ packageJson: { name: "vue", version: "3.5.0" }
844
+ });
845
+ }
846
+ const appDir = writeFixturePackage(root, {
847
+ dir: "app",
848
+ packageJson: {
849
+ name: "app",
850
+ version: "1.0.0",
851
+ dependencies: { vue: "^3.5.0" },
852
+ esmx: { uses: ["pa", "pb", "pc"] }
853
+ }
854
+ });
855
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
856
+ const dupes = result.diagnostics.filter(
857
+ (d) => d.code === DiagnosticCode.E_DUP_PROVIDER
858
+ );
859
+ expect(dupes).toHaveLength(1);
860
+ expect(dupes[0].module).toBe("app");
861
+ expect(dupes[0].package).toBe("vue");
862
+ expect(dupes[0].found).toBe("pa, pb");
863
+ });
864
+ it("should withhold supply when a uses cycle also provides", async () => {
865
+ const root = await fixtureRoot();
866
+ writeFixturePackage(root, {
867
+ dir: "node_modules/a",
868
+ packageJson: {
869
+ name: "a",
870
+ version: "1.0.0",
871
+ esmx: { uses: ["b"], provides: ["vue"] }
872
+ },
873
+ built: true
874
+ });
875
+ writeFixturePackage(root, {
876
+ dir: "node_modules/a/node_modules/vue",
877
+ packageJson: { name: "vue", version: "3.4.0" }
878
+ });
879
+ writeFixturePackage(root, {
880
+ dir: "node_modules/b",
881
+ packageJson: {
882
+ name: "b",
883
+ version: "1.0.0",
884
+ esmx: { uses: ["a"], provides: ["vue"] }
885
+ },
886
+ built: true
887
+ });
888
+ writeFixturePackage(root, {
889
+ dir: "node_modules/b/node_modules/vue",
890
+ packageJson: { name: "vue", version: "3.4.0" }
891
+ });
892
+ const appDir = writeFixturePackage(root, {
893
+ dir: "app",
894
+ packageJson: {
895
+ name: "app",
896
+ version: "1.0.0",
897
+ dependencies: { vue: "^3.4.0" },
898
+ esmx: { uses: ["a"] }
899
+ }
900
+ });
901
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
902
+ expect(
903
+ result.diagnostics.some((d) => d.code === DiagnosticCode.E_CYCLE)
904
+ ).toBe(true);
905
+ expect(result.supply).toEqual({});
906
+ });
907
+ it("should report E_DUP_PROVIDER when base and a sibling both provide vue@3 in the same closure", async () => {
908
+ const root = await fixtureRoot();
909
+ writeFixturePackage(root, {
910
+ dir: "node_modules/base",
911
+ packageJson: {
912
+ name: "base",
913
+ version: "1.0.0",
914
+ dependencies: { vue: "^3.4.0" },
915
+ esmx: { provides: ["vue"] }
916
+ },
917
+ built: true
918
+ });
919
+ writeFixturePackage(root, {
920
+ dir: "node_modules/base/node_modules/vue",
921
+ packageJson: { name: "vue", version: "3.4.21" }
922
+ });
923
+ writeFixturePackage(root, {
924
+ dir: "node_modules/sibling",
925
+ packageJson: {
926
+ name: "sibling",
927
+ version: "1.0.0",
928
+ dependencies: { vue: "^3.4.0" },
929
+ esmx: { provides: ["vue"] }
930
+ },
931
+ built: true
932
+ });
933
+ writeFixturePackage(root, {
934
+ dir: "node_modules/sibling/node_modules/vue",
935
+ packageJson: { name: "vue", version: "3.5.2" }
936
+ });
937
+ const appDir = writeFixturePackage(root, {
938
+ dir: "app",
939
+ packageJson: {
940
+ name: "app",
941
+ version: "1.0.0",
942
+ dependencies: { vue: "^3.4.0" },
943
+ esmx: { uses: ["base", "sibling"] }
944
+ }
945
+ });
946
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
947
+ const dupes = result.diagnostics.filter(
948
+ (d) => d.code === DiagnosticCode.E_DUP_PROVIDER
949
+ );
950
+ expect(dupes).toHaveLength(1);
951
+ expect(dupes[0].severity).toBe("error");
952
+ expect(dupes[0].module).toBe("app");
953
+ expect(dupes[0].package).toBe("vue");
954
+ expect(dupes[0].found).toBe("base, sibling");
955
+ });
956
+ it("should not report E_DUP_PROVIDER when the SAME provider reaches via two diamond paths", async () => {
957
+ const root = await fixtureRoot();
958
+ writeFixturePackage(root, {
959
+ dir: "node_modules/base",
960
+ packageJson: {
961
+ name: "base",
962
+ version: "1.0.0",
963
+ dependencies: { vue: "^3.4.0" },
964
+ esmx: { provides: ["vue"] }
965
+ },
966
+ built: true
967
+ });
968
+ writeFixturePackage(root, {
969
+ dir: "node_modules/base/node_modules/vue",
970
+ packageJson: { name: "vue", version: "3.5.2" }
971
+ });
972
+ for (const name of ["left", "right"]) {
973
+ writeFixturePackage(root, {
974
+ dir: `node_modules/${name}`,
975
+ packageJson: {
976
+ name,
977
+ version: "1.0.0",
978
+ esmx: { uses: ["base"] }
979
+ },
980
+ built: true
981
+ });
982
+ }
983
+ const appDir = writeFixturePackage(root, {
984
+ dir: "app",
985
+ packageJson: {
986
+ name: "app",
987
+ version: "1.0.0",
988
+ dependencies: { vue: "^3.4.0" },
989
+ esmx: { uses: ["left", "right"] }
990
+ }
991
+ });
992
+ const result = resolveMounts(appDir, readRootDeclaration(appDir));
993
+ expect(
994
+ result.diagnostics.filter(
995
+ (d) => d.code === DiagnosticCode.E_DUP_PROVIDER
996
+ )
997
+ ).toEqual([]);
998
+ expect(
999
+ result.diagnostics.filter((d) => d.severity === "error")
1000
+ ).toEqual([]);
1001
+ expect(result.supply.vue).toEqual({
1002
+ groups: [{ major: 3, provider: "base", version: "3.5.2" }]
1003
+ });
1004
+ });
1005
+ });