cabloy 5.1.219 → 5.1.221

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 (89) hide show
  1. package/.cabloy-version +1 -1
  2. package/.github/workflows/agent-governance.yml +2 -2
  3. package/.github/workflows/create-cabloy.yml +3 -3
  4. package/.github/workflows/docs-pages.yml +2 -2
  5. package/.github/workflows/playwright-e2e.yml +4 -4
  6. package/.github/workflows/vona-cov-pg.yml +2 -2
  7. package/.github/workflows/vona-test-crud.yml +2 -2
  8. package/.github/workflows/vona-test-mysql.yml +2 -2
  9. package/.github/workflows/vona-test-pg.yml +2 -2
  10. package/.github/workflows/vona-test-sqlite3.yml +2 -2
  11. package/.github/workflows/vona-tsc.yml +2 -2
  12. package/.github/workflows/zova-ui.yml +2 -2
  13. package/CHANGELOG.md +23 -0
  14. package/README.md +1 -1
  15. package/package.json +4 -3
  16. package/repo-docs/frontend/openapi-sdk-guide.md +2 -0
  17. package/repo-docs/fullstack/quickstart.md +3 -3
  18. package/repo-docs/reference/repo-scripts.md +16 -0
  19. package/scripts/generateOpenapiIsolated.test.ts +25 -0
  20. package/scripts/generateOpenapiIsolated.ts +46 -12
  21. package/scripts/runIsolatedTest.test.ts +165 -45
  22. package/scripts/runIsolatedTest.ts +111 -24
  23. package/scripts/strictGenerationDiagnostics.test.ts +1009 -0
  24. package/scripts/strictGenerationDiagnostics.ts +61 -0
  25. package/scripts/strictGenerationStartup.test.ts +212 -0
  26. package/vona/package.original.json +3 -2
  27. package/vona/packages-cli/cli/package.json +1 -1
  28. package/vona/packages-cli/cli-set-api/package.json +1 -1
  29. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +14 -4
  30. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/generateEntryFiles.ts +6 -1
  31. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/generateVonaMeta.ts +2 -10
  32. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/validateGenerationPaths.ts +27 -0
  33. package/vona/packages-cli/cli-set-api/test/binDevLifecycle.test.ts +1 -1
  34. package/vona/packages-cli/cli-set-api/test/strictGenerationPrivacy.test.ts +439 -0
  35. package/vona/packages-vona/vona/package.json +1 -1
  36. package/vona/packages-vona/vona-core/package.json +1 -1
  37. package/vona/packages-vona/vona-core/src/lib/bean/beanBase.ts +4 -0
  38. package/vona/packages-vona/vona-core/src/lib/core/asyncLocalStorage.ts +84 -12
  39. package/vona/packages-vona/vona-core/src/lib/core/index.ts +14 -0
  40. package/vona/packages-vona/vona-core/src/lib/core/logger/appLogger.ts +19 -3
  41. package/vona/packages-vona/vona-core/src/lib/core/logger/diagnosticLogger.test.ts +346 -0
  42. package/vona/packages-vona/vona-core/src/lib/core/logger/diagnosticLogger.ts +138 -0
  43. package/vona/packages-vona/vona-core/src/lib/core/logger/index.ts +1 -0
  44. package/vona/packages-vona/vona-core/src/lib/core/logger/utils.ts +25 -5
  45. package/vona/packages-vona/vona-core/src/lib/core/requestBoundary.ts +89 -0
  46. package/vona/packages-vona/vona-core/src/lib/core/testFreshness.test.ts +379 -0
  47. package/vona/packages-vona/vona-core/src/lib/core/testFreshness.ts +232 -0
  48. package/vona/packages-vona/vona-core/src/lib/core/testStrictMode.ts +9 -0
  49. package/vona/packages-vona/vona-core/src/lib/framework/start.test.ts +4 -3
  50. package/vona/packages-vona/vona-core/src/lib/framework/start.ts +2 -0
  51. package/vona/packages-vona/vona-mock/package.json +1 -1
  52. package/vona/pnpm-lock.yaml +691 -356
  53. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  54. package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
  55. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/bean/bean.executor.ts +111 -60
  56. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/service/executor.ts +6 -6
  57. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/types/executor.ts +13 -1
  58. package/vona/src/suite-vendor/a-vona/modules/a-jwt/package.json +1 -1
  59. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/lib/jwtClientLogging.test.ts +182 -0
  60. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/service/jwtClient.ts +2 -8
  61. package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
  62. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/aopMethod.log.ts +4 -1
  63. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/bean.logger.ts +9 -0
  64. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/middlewareSystem.httpLog.ts +27 -14
  65. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactHttpLog.test.ts +207 -0
  66. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactHttpLog.ts +30 -0
  67. package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +33 -1
  68. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/package.json +1 -1
  69. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/index.ts +11 -0
  70. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/lib/protectedSqliteRegistry.ts +356 -0
  71. package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
  72. package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +6 -0
  73. package/vona/src/suite-vendor/a-vona/modules/a-startup/test/devSeed.test.ts +2 -2
  74. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/package.json +1 -1
  75. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/bean.telemetry.ts +8 -1
  76. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/middlewareSystem.trace.ts +9 -14
  77. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/lib/spanDiagnostics.ts +70 -0
  78. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/service/telemetry.ts +37 -23
  79. package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
  80. package/vona/src/suite-vendor/a-vona/modules/a-version/src/service/database.ts +31 -4
  81. package/vona/src/suite-vendor/a-vona/package.json +2 -2
  82. package/zova/package.original.json +3 -2
  83. package/zova/packages-cli/cli/package.json +2 -2
  84. package/zova/packages-cli/cli-set-front/package.json +1 -1
  85. package/zova/packages-cli/cli-set-front/src/lib/bean/cli.bin.buildRest.ts +11 -101
  86. package/zova/packages-cli/cli-set-front/src/lib/common/restDependencies.ts +78 -0
  87. package/zova/packages-cli/cli-set-front/src/lib/common/restDependencyVersions.ts +252 -0
  88. package/zova/packages-cli/cli-set-front/test/restDependencies.test.ts +568 -0
  89. package/zova/pnpm-lock.yaml +442 -564
@@ -0,0 +1,568 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { describe, it } from 'node:test';
6
+
7
+ import { extractRestDependencies } from '../src/lib/common/restDependencies.ts';
8
+ import { extractRestDependencyVersions } from '../src/lib/common/restDependencyVersions.ts';
9
+
10
+ const sideEffectModules = [
11
+ 'zova-module-a-fetch',
12
+ 'zova-module-a-app',
13
+ 'zova-module-a-logger',
14
+ 'zova-module-a-meta',
15
+ 'zova-module-a-ssrhmr',
16
+ 'zova-module-a-zod',
17
+ 'zova-module-a-zova',
18
+ 'zova-module-a-rbac',
19
+ 'zova-module-pay-paypal',
20
+ 'zova-module-pay-stripe',
21
+ 'zova-module-a-behaviors',
22
+ 'zova-module-a-routerstack',
23
+ 'zova-module-a-ssrserver',
24
+ ];
25
+
26
+ async function withFixture(callback: (projectPath: string) => Promise<void>): Promise<void> {
27
+ const projectPath = await mkdtemp(path.join(os.tmpdir(), 'rest-dependency-'));
28
+ try {
29
+ await callback(projectPath);
30
+ } finally {
31
+ await rm(projectPath, { recursive: true, force: true });
32
+ }
33
+ }
34
+
35
+ async function writeFixtureFile(
36
+ projectPath: string,
37
+ relativePath: string,
38
+ content: string,
39
+ ): Promise<void> {
40
+ const filePath = path.join(projectPath, relativePath);
41
+ await mkdir(path.dirname(filePath), { recursive: true });
42
+ await writeFile(filePath, content);
43
+ }
44
+
45
+ async function writePackage(
46
+ projectPath: string,
47
+ relativePath: string,
48
+ packageJson: Record<string, unknown>,
49
+ ): Promise<void> {
50
+ await writeFixtureFile(
51
+ projectPath,
52
+ path.join(relativePath, 'package.json'),
53
+ JSON.stringify(packageJson),
54
+ );
55
+ }
56
+
57
+ async function writeLockfile(projectPath: string, content: string): Promise<void> {
58
+ await writeFixtureFile(projectPath, 'pnpm-lock.yaml', content);
59
+ }
60
+
61
+ describe('extractRestDependencies', () => {
62
+ it('retains consecutive side-effect imports before named imports', () => {
63
+ const content = `${sideEffectModules.map(name => `import "${name}";`).join('\n')}
64
+ import type { Schema } from 'zod';
65
+ //#region bundled declarations
66
+ export declare const schema: Schema;
67
+ `;
68
+ assert.deepEqual(extractRestDependencies(content), [...sideEffectModules, 'zod'].sort());
69
+ });
70
+
71
+ it('collects imports and re-exports without relying on region comments', () => {
72
+ assert.deepEqual(
73
+ extractRestDependencies(`
74
+ import type {
75
+ Foo,
76
+ } from '@scope/types/subpath';
77
+ import Default from "plain-package";
78
+ export * from '@scope/exported';
79
+ export type { Bar } from 'exported/subpath';
80
+ export { Default };
81
+ `),
82
+ ['@scope/exported', '@scope/types', 'exported', 'plain-package'],
83
+ );
84
+ });
85
+
86
+ it('collects nested import types and external import assignments after region comments', () => {
87
+ assert.deepEqual(
88
+ extractRestDependencies(`
89
+ //#region generated
90
+ import Legacy = require('legacy/subpath');
91
+ export type Options = {
92
+ value: import('@scope/types/options').Options;
93
+ factory: typeof import('factory');
94
+ };
95
+ export * from 'after-region';
96
+ `),
97
+ ['@scope/types', 'after-region', 'factory', 'legacy'],
98
+ );
99
+ });
100
+
101
+ it('does not treat module declarations or import-like text as dependencies', () => {
102
+ assert.deepEqual(
103
+ extractRestDependencies(`
104
+ // import 'comment-only';
105
+ /** export * from 'comment-export'; */
106
+ declare const text: "import 'string-only'";
107
+ declare module 'bundled-business-module' {
108
+ import type { External } from 'real-dependency';
109
+ export type Value = import('@scope/nested').Value;
110
+ }
111
+ declare module 'augmentation-target' {
112
+ interface Options { enabled: boolean }
113
+ }
114
+ `),
115
+ ['@scope/nested', 'real-dependency'],
116
+ );
117
+ });
118
+
119
+ it('ignores local paths, builtins, internal aliases and virtual identifiers', () => {
120
+ assert.deepEqual(
121
+ extractRestDependencies(`
122
+ import './local.js';
123
+ import '../parent.js';
124
+ import '/absolute/file.js';
125
+ import 'C:/absolute/file.js';
126
+ import 'node:fs/promises';
127
+ import 'fs/promises';
128
+ import 'path';
129
+ import '#internal';
130
+ import 'virtual:generated';
131
+ import '\\0virtual';
132
+ import 'file:///local/file.js';
133
+ import 'https://example.invalid/module.js';
134
+ `),
135
+ [],
136
+ );
137
+ });
138
+
139
+ it('deduplicates package roots and sorts deterministically', () => {
140
+ assert.deepEqual(
141
+ extractRestDependencies(`
142
+ import 'zod';
143
+ export * from 'zod/v4';
144
+ import '@scope/pkg';
145
+ export type Value = import('@scope/pkg/subpath').Value;
146
+ import 'alpha/subpath';
147
+ `),
148
+ ['@scope/pkg', 'alpha', 'zod'],
149
+ );
150
+ assert.deepEqual(extractRestDependencies(''), []);
151
+ });
152
+
153
+ it('rejects malformed declarations instead of returning partial dependencies', () => {
154
+ assert.throws(
155
+ () => extractRestDependencies("import 'valid-package';\nexport type Broken = {"),
156
+ /Invalid REST declaration at index\.d\.mts:2:23: .+/,
157
+ );
158
+ });
159
+
160
+ it('rejects path and type reference directives', () => {
161
+ for (const [kind, target] of [
162
+ ['path', './external.d.ts'],
163
+ ['types', 'external-types'],
164
+ ]) {
165
+ assert.throws(
166
+ () =>
167
+ extractRestDependencies(`/// <reference ${kind}="${target}" />\nimport 'valid-package';`),
168
+ {
169
+ message:
170
+ `Unsupported REST declaration reference directive: ${kind} ${JSON.stringify(target)}. ` +
171
+ 'The current REST bundling workflow does not support this directive.',
172
+ },
173
+ );
174
+ }
175
+ });
176
+
177
+ it('does not treat compiler lib references as package dependencies', () => {
178
+ assert.deepEqual(
179
+ extractRestDependencies(`
180
+ /// <reference lib="esnext" />
181
+ /// <reference no-default-lib="true" />
182
+ import 'real-package';
183
+ `),
184
+ ['real-package'],
185
+ );
186
+ });
187
+
188
+ it('rejects empty and incomplete scoped package references', () => {
189
+ for (const specifier of ['', '@scope', '@scope/', '@/package', '@']) {
190
+ assert.throws(
191
+ () => extractRestDependencies(`import '${specifier}';`),
192
+ /(?:Invalid|Unsupported) REST declaration package reference:/,
193
+ );
194
+ }
195
+ });
196
+
197
+ it('rejects colon-containing package references that are not URL schemes', () => {
198
+ for (const specifier of ['@scope/package:invalid', 'package/subpath:invalid']) {
199
+ assert.throws(
200
+ () => extractRestDependencies(`export * from '${specifier}';`),
201
+ /Unsupported REST declaration package reference:/,
202
+ );
203
+ }
204
+ });
205
+
206
+ it('rejects nonliteral import types and non-string module specifiers', () => {
207
+ assert.throws(
208
+ () => extractRestDependencies('export type Value = import(PackageName).Value;'),
209
+ /Unsupported REST declaration import type/,
210
+ );
211
+ assert.throws(
212
+ () => extractRestDependencies('export type Value = import(42).Value;'),
213
+ /Unsupported REST declaration module specifier/,
214
+ );
215
+ });
216
+
217
+ it('rejects dynamic import calls instead of silently omitting their dependencies', () => {
218
+ assert.throws(
219
+ () => extractRestDependencies("const value = import('runtime-package');"),
220
+ /Unsupported REST declaration dynamic import/,
221
+ );
222
+ });
223
+
224
+ it('retains external import assignments and ignores internal import aliases', () => {
225
+ assert.deepEqual(
226
+ extractRestDependencies(`
227
+ import Scoped = require('@scope/legacy/subpath');
228
+ import Local = require('./local.js');
229
+ import Builtin = require('node:fs');
230
+ import Internal = require('#internal');
231
+ import Remote = require('https://example.invalid/module.js');
232
+ declare namespace Existing { interface Value {} }
233
+ import Alias = Existing.Value;
234
+ `),
235
+ ['@scope/legacy'],
236
+ );
237
+ });
238
+ });
239
+
240
+ describe('extractRestDependencyVersions', () => {
241
+ const managementLockfile = `---
242
+ lockfileVersion: '9.0'
243
+ importers:
244
+ .:
245
+ configDependencies: {}
246
+ packageManagerDependencies:
247
+ pnpm:
248
+ specifier: 12.14.0
249
+ version: 12.14.0
250
+ dependencies:
251
+ root-package:
252
+ version: 99.0.0
253
+ management-only:
254
+ version: 99.0.0
255
+ `;
256
+ const workspaceLockfile = `lockfileVersion: '9.0'
257
+ importers:
258
+ .:
259
+ dependencies:
260
+ root-package:
261
+ version: 1.2.3
262
+ alias-package:
263
+ specifier: npm:@scope/package@^2.0.0
264
+ version: '@scope/package@2.0.4'
265
+ src/module/example:
266
+ dependencies:
267
+ owned-package:
268
+ version: 3.1.7(vue@3.5.42)
269
+ `;
270
+
271
+ for (const trailingDocument of ['', '---\n# empty document\n']) {
272
+ it(`uses the workspace document of a pnpm 12 lockfile${trailingDocument ? ' with a trailing empty document' : ''}`, async () => {
273
+ await withFixture(async projectPath => {
274
+ await writeLockfile(
275
+ projectPath,
276
+ `${managementLockfile}---\n${workspaceLockfile}${trailingDocument}`,
277
+ );
278
+ assert.deepEqual(
279
+ await extractRestDependencyVersions(
280
+ projectPath,
281
+ ['root-package', 'alias-package', 'owned-package'],
282
+ ['src/module/example'],
283
+ ),
284
+ {
285
+ 'root-package': '^1.2.3',
286
+ 'alias-package': 'npm:@scope/package@^2.0.0',
287
+ 'owned-package': '^3.1.7',
288
+ },
289
+ );
290
+ for (const dependency of ['management-only', 'pnpm']) {
291
+ await assert.rejects(
292
+ extractRestDependencyVersions(projectPath, [dependency]),
293
+ /Unable to resolve REST dependency version/,
294
+ );
295
+ }
296
+ });
297
+ });
298
+ }
299
+
300
+ it('accepts a single workspace document with an explicit start marker', async () => {
301
+ await withFixture(async projectPath => {
302
+ await writeLockfile(projectPath, `---\n${workspaceLockfile}`);
303
+ assert.deepEqual(await extractRestDependencyVersions(projectPath, ['root-package']), {
304
+ 'root-package': '^1.2.3',
305
+ });
306
+ });
307
+ });
308
+
309
+ for (const [label, invalidDocument, code] of [
310
+ ['malformed YAML', 'importers: [\n', 'BAD_INDENT'],
311
+ ['duplicate keys', 'importers: {}\nimporters: {}\n', 'DUPLICATE_KEY'],
312
+ ]) {
313
+ for (const position of ['management', 'workspace']) {
314
+ it(`rejects ${label} in the ${position} document before installed-package fallback`, async () => {
315
+ await withFixture(async projectPath => {
316
+ const content =
317
+ position === 'management'
318
+ ? `${invalidDocument}---\n${workspaceLockfile}`
319
+ : `${managementLockfile}---\n${invalidDocument}`;
320
+ await writeLockfile(projectPath, content);
321
+ await writePackage(projectPath, 'node_modules/root-package', { version: '5.0.0' });
322
+ await assert.rejects(extractRestDependencyVersions(projectPath, ['root-package']), {
323
+ name: 'YAMLParseError',
324
+ code,
325
+ });
326
+ });
327
+ });
328
+ }
329
+ }
330
+
331
+ for (const [label, invalidDocument] of [
332
+ ['empty', ''],
333
+ ['comment-only', '# no lockfile\n'],
334
+ ['empty marked', '---\n# no lockfile\n'],
335
+ ['null', 'null\n'],
336
+ ['tilde null', '~\n'],
337
+ ['empty string', '""\n'],
338
+ ['scalar', 'invalid\n'],
339
+ ['sequence', '- invalid\n'],
340
+ ['missing importers', "lockfileVersion: '9.0'\n"],
341
+ ['null importers', 'importers: null\n'],
342
+ ['scalar importers', 'importers: invalid\n'],
343
+ ['sequence importers', 'importers: []\n'],
344
+ ]) {
345
+ it(`rejects a ${label} workspace document`, async () => {
346
+ await withFixture(async projectPath => {
347
+ await writePackage(projectPath, 'node_modules/root-package', { version: '5.0.0' });
348
+ await writeLockfile(projectPath, invalidDocument);
349
+ await assert.rejects(
350
+ extractRestDependencyVersions(projectPath, ['root-package']),
351
+ /Invalid pnpm-lock.yaml: expected a workspace document with an importers mapping/,
352
+ );
353
+ if (
354
+ invalidDocument.trim() &&
355
+ !invalidDocument.startsWith('#') &&
356
+ !invalidDocument.startsWith('---')
357
+ ) {
358
+ await writeLockfile(projectPath, `${workspaceLockfile}---\n${invalidDocument}`);
359
+ await assert.rejects(
360
+ extractRestDependencyVersions(projectPath, ['root-package']),
361
+ /Invalid pnpm-lock.yaml: expected a workspace document with an importers mapping/,
362
+ );
363
+ }
364
+ });
365
+ });
366
+ }
367
+
368
+ it('allows installed-package fallback with an empty workspace importer map', async () => {
369
+ await withFixture(async projectPath => {
370
+ await writeLockfile(projectPath, `${managementLockfile}---\nimporters: {}\n`);
371
+ await writePackage(projectPath, 'node_modules/root-package', { version: '5.0.0' });
372
+ assert.deepEqual(await extractRestDependencyVersions(projectPath, ['root-package']), {
373
+ 'root-package': '^5.0.0',
374
+ });
375
+ });
376
+ });
377
+
378
+ it('uses the root importer range when the dependency is declared there', async () => {
379
+ await withFixture(async projectPath => {
380
+ await writeLockfile(
381
+ projectPath,
382
+ `lockfileVersion: '9.0'
383
+ importers:
384
+ .:
385
+ dependencies:
386
+ root-package:
387
+ specifier: ^1.2.3
388
+ version: 1.2.3
389
+ `,
390
+ );
391
+ assert.deepEqual(await extractRestDependencyVersions(projectPath, ['root-package']), {
392
+ 'root-package': '^1.2.3',
393
+ });
394
+ });
395
+ });
396
+
397
+ it('resolves a dependency from its owning module importer', async () => {
398
+ await withFixture(async projectPath => {
399
+ const moduleRoot = 'zova/src/suite/example/modules/example-one';
400
+ await writeLockfile(
401
+ projectPath,
402
+ `lockfileVersion: '9.0'
403
+ importers:
404
+ .:
405
+ dependencies: {}
406
+ ${moduleRoot}:
407
+ dependencies:
408
+ owned-package:
409
+ specifier: ^2.0.4
410
+ version: 2.0.4
411
+ `,
412
+ );
413
+ assert.deepEqual(
414
+ await extractRestDependencyVersions(projectPath, ['owned-package'], [moduleRoot]),
415
+ { 'owned-package': '^2.0.4' },
416
+ );
417
+ });
418
+ });
419
+
420
+ it('falls back to an installed module dependency when the lock importer is absent', async () => {
421
+ await withFixture(async projectPath => {
422
+ const moduleRoot = 'zova/src/module/example-one';
423
+ await writePackage(projectPath, moduleRoot, {
424
+ name: 'zova-module-example-one',
425
+ dependencies: { 'installed-package': '^3.1.0' },
426
+ });
427
+ await writePackage(projectPath, path.join(moduleRoot, 'node_modules', 'installed-package'), {
428
+ name: 'installed-package',
429
+ version: '3.1.7',
430
+ });
431
+ await writeLockfile(
432
+ projectPath,
433
+ "lockfileVersion: '9.0'\nimporters:\n .:\n dependencies: {}\n",
434
+ );
435
+ assert.deepEqual(
436
+ await extractRestDependencyVersions(projectPath, ['installed-package'], [moduleRoot]),
437
+ { 'installed-package': '^3.1.7' },
438
+ );
439
+ });
440
+ });
441
+
442
+ it('rejects conflicting module dependency versions', async () => {
443
+ await withFixture(async projectPath => {
444
+ const firstModule = 'zova/src/module/first';
445
+ const secondModule = 'zova/src/module/second';
446
+ await writeLockfile(
447
+ projectPath,
448
+ `lockfileVersion: '9.0'
449
+ importers:
450
+ .:
451
+ dependencies: {}
452
+ ${firstModule}:
453
+ dependencies:
454
+ conflicting-package:
455
+ specifier: ^1.0.0
456
+ version: 1.0.0
457
+ ${secondModule}:
458
+ dependencies:
459
+ conflicting-package:
460
+ specifier: ^2.0.0
461
+ version: 2.0.0
462
+ `,
463
+ );
464
+ await assert.rejects(
465
+ extractRestDependencyVersions(
466
+ projectPath,
467
+ ['conflicting-package'],
468
+ [firstModule, secondModule],
469
+ ),
470
+ /Conflicting REST dependency versions for conflicting-package/,
471
+ );
472
+ });
473
+ });
474
+
475
+ it('preserves npm aliases and strips peer suffixes from locked versions', async () => {
476
+ await withFixture(async projectPath => {
477
+ await writeLockfile(
478
+ projectPath,
479
+ `
480
+ importers:
481
+ .:
482
+ dependencies:
483
+ zod:
484
+ specifier: npm:@cabloy/zod@4.3.8
485
+ version: '@cabloy/zod@4.3.8'
486
+ devDependencies:
487
+ peer-package:
488
+ specifier: ^1.2.0
489
+ version: 1.2.3(vue@3.5.42)
490
+ optionalDependencies:
491
+ optional-package:
492
+ specifier: ^2.0.0
493
+ version: 2.0.1
494
+ `,
495
+ );
496
+ assert.deepEqual(
497
+ await extractRestDependencyVersions(projectPath, [
498
+ 'zod',
499
+ 'peer-package',
500
+ 'optional-package',
501
+ ]),
502
+ { 'zod': 'npm:@cabloy/zod@4.3.8', 'peer-package': '^1.2.3', 'optional-package': '^2.0.1' },
503
+ );
504
+ });
505
+ });
506
+
507
+ it('accepts matching module candidates with absolute and relative module roots', async () => {
508
+ await withFixture(async projectPath => {
509
+ const firstModule = 'src/module/first';
510
+ const secondModule = 'src/module/second';
511
+ await writeLockfile(
512
+ projectPath,
513
+ `
514
+ importers:
515
+ ${firstModule}:
516
+ dependencies:
517
+ shared-package:
518
+ version: 1.2.3(vue@3.5.42)
519
+ ${secondModule}:
520
+ dependencies:
521
+ shared-package:
522
+ version: 1.2.3(vue@3.5.43)
523
+ `,
524
+ );
525
+ assert.deepEqual(
526
+ await extractRestDependencyVersions(
527
+ projectPath,
528
+ ['shared-package'],
529
+ [firstModule, path.join(projectPath, secondModule)],
530
+ ),
531
+ { 'shared-package': '^1.2.3' },
532
+ );
533
+ });
534
+ });
535
+
536
+ it('uses an installed root package for a linked dependency', async () => {
537
+ await withFixture(async projectPath => {
538
+ await writeLockfile(
539
+ projectPath,
540
+ `
541
+ importers:
542
+ .:
543
+ dependencies:
544
+ linked-package:
545
+ specifier: workspace:*
546
+ version: link:packages/linked-package
547
+ `,
548
+ );
549
+ await writePackage(projectPath, 'node_modules/linked-package', { version: '5.1.2' });
550
+ assert.deepEqual(await extractRestDependencyVersions(projectPath, ['linked-package']), {
551
+ 'linked-package': '^5.1.2',
552
+ });
553
+ });
554
+ });
555
+
556
+ it('rejects dependencies without a resolvable version', async () => {
557
+ await withFixture(async projectPath => {
558
+ await writeLockfile(
559
+ projectPath,
560
+ "lockfileVersion: '9.0'\nimporters:\n .:\n dependencies: {}\n",
561
+ );
562
+ await assert.rejects(
563
+ extractRestDependencyVersions(projectPath, ['missing-package']),
564
+ /Unable to resolve REST dependency version for missing-package/,
565
+ );
566
+ });
567
+ });
568
+ });