@mettlecast/domain-cli 0.2.85 → 0.2.87
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.
- package/dist/builder/build-registry.d.ts +8 -2
- package/dist/builder/build-registry.js +80 -88
- package/dist/commands/build-catalog.js +2 -3
- package/dist/commands/update-all.d.ts +1 -0
- package/dist/commands/update-all.js +174 -0
- package/dist/commands/validate.js +26 -59
- package/dist/utils/toolchain-manifest.d.ts +90 -0
- package/dist/utils/toolchain-manifest.js +144 -0
- package/package.json +3 -3
- package/scripts/generate-toolchain-manifest.mjs +163 -0
- package/src/__tests__/build-registry.test.ts +125 -3
- package/src/__tests__/commands/build-catalog.test.ts +89 -0
- package/src/__tests__/commands/update-all.test.ts +341 -3
- package/src/__tests__/package-freshness.test.ts +8 -4
- package/src/__tests__/toolchain-manifest.test.ts +185 -0
- package/src/__tests__/validate.test.ts +183 -108
- package/src/builder/build-registry.ts +116 -92
- package/src/commands/build-catalog.ts +2 -3
- package/src/commands/update-all.ts +216 -0
- package/src/commands/validate.ts +29 -70
- package/src/utils/toolchain-manifest.ts +236 -0
- package/toolchain-manifest.json +10 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
-
import { mkdtemp } from 'node:fs/promises';
|
|
2
|
+
import { mkdtemp, mkdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
|
-
import { rmSync } from 'node:fs';
|
|
5
|
+
import { rmSync, existsSync } from 'node:fs';
|
|
6
6
|
|
|
7
7
|
vi.mock('../../commands/build.js', () => ({
|
|
8
8
|
runBuild: vi.fn(),
|
|
@@ -78,6 +78,74 @@ function makeScaffoldConfig(domainIds: string[]) {
|
|
|
78
78
|
};
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/** Create a valid toolchain-manifest.json at node_modules/@mettlecast/domain-cli/. */
|
|
82
|
+
async function createToolchainManifest(
|
|
83
|
+
rootDir: string,
|
|
84
|
+
overrides?: Record<string, unknown>,
|
|
85
|
+
): Promise<void> {
|
|
86
|
+
const pkgDir = join(rootDir, 'node_modules', '@mettlecast', 'domain-cli');
|
|
87
|
+
await mkdir(pkgDir, { recursive: true });
|
|
88
|
+
const manifest = {
|
|
89
|
+
schemaVersion: 1,
|
|
90
|
+
registrySchemaVersion: '1',
|
|
91
|
+
packages: {
|
|
92
|
+
domainCli: '0.2.53',
|
|
93
|
+
domainCdkPacker: '0.2.53',
|
|
94
|
+
domainRuntime: '0.2.53',
|
|
95
|
+
eslintPluginDomainModule: '0.2.53',
|
|
96
|
+
},
|
|
97
|
+
...overrides,
|
|
98
|
+
};
|
|
99
|
+
await writeFile(
|
|
100
|
+
join(pkgDir, 'toolchain-manifest.json'),
|
|
101
|
+
JSON.stringify(manifest, null, 2),
|
|
102
|
+
'utf-8',
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Create a minimal infra/modules/package.json. */
|
|
107
|
+
async function createInfraPackageJson(rootDir: string, deps?: Record<string, string>, devDeps?: Record<string, string>): Promise<void> {
|
|
108
|
+
const infraDir = join(rootDir, 'infra', 'modules');
|
|
109
|
+
await mkdir(infraDir, { recursive: true });
|
|
110
|
+
const pkg = {
|
|
111
|
+
name: 'test-infra',
|
|
112
|
+
version: '1.0.0',
|
|
113
|
+
type: 'module',
|
|
114
|
+
dependencies: {
|
|
115
|
+
'@aws-sdk/client-rds': '^3.700.0',
|
|
116
|
+
'@mettlecast/domain-cdk-packer': '*',
|
|
117
|
+
'@mettlecast/domain-runtime': '*',
|
|
118
|
+
'aws-cdk-lib': '^2.170.0',
|
|
119
|
+
constructs: '^10.4.2',
|
|
120
|
+
...deps,
|
|
121
|
+
},
|
|
122
|
+
devDependencies: {
|
|
123
|
+
'@mettlecast/domain-cli': '*',
|
|
124
|
+
'@mettlecast/eslint-plugin-domain-module': '*',
|
|
125
|
+
'aws-cdk': '^2.170.0',
|
|
126
|
+
typescript: '^5.7.3',
|
|
127
|
+
...devDeps,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
await writeFile(join(infraDir, 'package.json'), JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Create a minimal root package.json. */
|
|
134
|
+
async function createRootPackageJson(rootDir: string, devDeps?: Record<string, string>): Promise<void> {
|
|
135
|
+
const pkg = {
|
|
136
|
+
name: 'test-project',
|
|
137
|
+
version: '0.1.0',
|
|
138
|
+
private: true,
|
|
139
|
+
devDependencies: {
|
|
140
|
+
'@mettlecast/domain-cli': '*',
|
|
141
|
+
'@mettlecast/domain-runtime': '*',
|
|
142
|
+
'@mettlecast/eslint-plugin-domain-module': '*',
|
|
143
|
+
...devDeps,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
await writeFile(join(rootDir, 'package.json'), JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
147
|
+
}
|
|
148
|
+
|
|
81
149
|
describe('update-all command', () => {
|
|
82
150
|
let tempDir: string;
|
|
83
151
|
|
|
@@ -102,6 +170,11 @@ describe('update-all command', () => {
|
|
|
102
170
|
mockRunBuildUi.mockResolvedValue(undefined);
|
|
103
171
|
mockRunRegenerateModulesHashes.mockResolvedValue('/tmp/.mc/modules-hashes.json');
|
|
104
172
|
mockRunDoctor.mockResolvedValue(makeDoctorReport(true));
|
|
173
|
+
|
|
174
|
+
// Default fixture: valid toolchain manifest + package.json files
|
|
175
|
+
await createToolchainManifest(tempDir);
|
|
176
|
+
await createInfraPackageJson(tempDir);
|
|
177
|
+
await createRootPackageJson(tempDir);
|
|
105
178
|
});
|
|
106
179
|
|
|
107
180
|
afterEach(() => {
|
|
@@ -319,4 +392,269 @@ describe('update-all command', () => {
|
|
|
319
392
|
expect(result.summary).toBe('Updated 0 domains; doctor FAIL');
|
|
320
393
|
expect(result.success).toBe(false);
|
|
321
394
|
});
|
|
322
|
-
|
|
395
|
+
|
|
396
|
+
describe('toolchain manifest resolution', () => {
|
|
397
|
+
it('fails gracefully when toolchain-manifest.json is missing', async () => {
|
|
398
|
+
// Remove the toolchain manifest set up in beforeEach
|
|
399
|
+
const manifestPath = join(tempDir, 'node_modules', '@mettlecast', 'domain-cli', 'toolchain-manifest.json');
|
|
400
|
+
rmSync(manifestPath, { force: true });
|
|
401
|
+
|
|
402
|
+
mockReadScaffoldConfig.mockResolvedValue(makeScaffoldConfig(['auth']));
|
|
403
|
+
|
|
404
|
+
const result = await runUpdateAll({ projectRoot: tempDir });
|
|
405
|
+
|
|
406
|
+
expect(result.success).toBe(false);
|
|
407
|
+
expect(result.summary).toContain('Toolchain resolution FAIL');
|
|
408
|
+
expect(result.summary).toContain('not found');
|
|
409
|
+
|
|
410
|
+
// Pipeline should NOT proceed to build steps
|
|
411
|
+
expect(mockRunBuild).not.toHaveBeenCalled();
|
|
412
|
+
expect(mockRunBuildCatalog).not.toHaveBeenCalled();
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it('fails gracefully when manifest schemaVersion is not 1', async () => {
|
|
416
|
+
// Overwrite with invalid schema version
|
|
417
|
+
const manifestPath = join(tempDir, 'node_modules', '@mettlecast', 'domain-cli', 'toolchain-manifest.json');
|
|
418
|
+
await writeFile(
|
|
419
|
+
manifestPath,
|
|
420
|
+
JSON.stringify({ schemaVersion: 2, registrySchemaVersion: '1', packages: { domainCli: '1.0.0' } }),
|
|
421
|
+
'utf-8',
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
mockReadScaffoldConfig.mockResolvedValue(makeScaffoldConfig(['auth']));
|
|
425
|
+
|
|
426
|
+
const result = await runUpdateAll({ projectRoot: tempDir });
|
|
427
|
+
|
|
428
|
+
expect(result.success).toBe(false);
|
|
429
|
+
expect(result.summary).toContain('Toolchain resolution FAIL');
|
|
430
|
+
expect(result.summary).toContain('expected 1');
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('fails gracefully when manifest is malformed JSON', async () => {
|
|
434
|
+
const manifestPath = join(tempDir, 'node_modules', '@mettlecast', 'domain-cli', 'toolchain-manifest.json');
|
|
435
|
+
await writeFile(manifestPath, 'not json', 'utf-8');
|
|
436
|
+
|
|
437
|
+
mockReadScaffoldConfig.mockResolvedValue(makeScaffoldConfig(['auth']));
|
|
438
|
+
|
|
439
|
+
const result = await runUpdateAll({ projectRoot: tempDir });
|
|
440
|
+
|
|
441
|
+
expect(result.success).toBe(false);
|
|
442
|
+
expect(result.summary).toContain('Toolchain resolution FAIL');
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
it('fails gracefully when manifest is missing packages', async () => {
|
|
446
|
+
const manifestPath = join(tempDir, 'node_modules', '@mettlecast', 'domain-cli', 'toolchain-manifest.json');
|
|
447
|
+
await writeFile(
|
|
448
|
+
manifestPath,
|
|
449
|
+
JSON.stringify({ schemaVersion: 1, registrySchemaVersion: '1' }),
|
|
450
|
+
'utf-8',
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
mockReadScaffoldConfig.mockResolvedValue(makeScaffoldConfig(['auth']));
|
|
454
|
+
|
|
455
|
+
const result = await runUpdateAll({ projectRoot: tempDir });
|
|
456
|
+
|
|
457
|
+
expect(result.success).toBe(false);
|
|
458
|
+
expect(result.summary).toContain('missing required field');
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('fails gracefully when a package version is empty', async () => {
|
|
462
|
+
const manifestPath = join(tempDir, 'node_modules', '@mettlecast', 'domain-cli', 'toolchain-manifest.json');
|
|
463
|
+
await writeFile(
|
|
464
|
+
manifestPath,
|
|
465
|
+
JSON.stringify({
|
|
466
|
+
schemaVersion: 1,
|
|
467
|
+
registrySchemaVersion: '1',
|
|
468
|
+
packages: {
|
|
469
|
+
domainCli: '',
|
|
470
|
+
domainCdkPacker: '0.2.53',
|
|
471
|
+
domainRuntime: '0.2.53',
|
|
472
|
+
eslintPluginDomainModule: '0.2.53',
|
|
473
|
+
},
|
|
474
|
+
}),
|
|
475
|
+
'utf-8',
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
mockReadScaffoldConfig.mockResolvedValue(makeScaffoldConfig(['auth']));
|
|
479
|
+
|
|
480
|
+
const result = await runUpdateAll({ projectRoot: tempDir });
|
|
481
|
+
|
|
482
|
+
expect(result.success).toBe(false);
|
|
483
|
+
expect(result.summary).toContain('empty version');
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('writes exact versions into infra/modules/package.json from manifest', async () => {
|
|
487
|
+
mockReadScaffoldConfig.mockResolvedValue(makeScaffoldConfig([]));
|
|
488
|
+
|
|
489
|
+
const result = await runUpdateAll({ projectRoot: tempDir });
|
|
490
|
+
|
|
491
|
+
expect(result.success).toBe(true);
|
|
492
|
+
|
|
493
|
+
// Verify the infra/modules/package.json now has exact versions
|
|
494
|
+
const pkgPath = join(tempDir, 'infra', 'modules', 'package.json');
|
|
495
|
+
const pkg = JSON.parse(await import('node:fs/promises').then(m => m.readFile(pkgPath, 'utf-8')));
|
|
496
|
+
expect(pkg.dependencies['@mettlecast/domain-cdk-packer']).toBe('0.2.53');
|
|
497
|
+
expect(pkg.dependencies['@mettlecast/domain-runtime']).toBe('0.2.53');
|
|
498
|
+
expect(pkg.devDependencies['@mettlecast/domain-cli']).toBe('0.2.53');
|
|
499
|
+
expect(pkg.devDependencies['@mettlecast/eslint-plugin-domain-module']).toBe('0.2.53');
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
it('writes exact versions into root package.json from manifest', async () => {
|
|
503
|
+
mockReadScaffoldConfig.mockResolvedValue(makeScaffoldConfig([]));
|
|
504
|
+
|
|
505
|
+
const result = await runUpdateAll({ projectRoot: tempDir });
|
|
506
|
+
|
|
507
|
+
expect(result.success).toBe(true);
|
|
508
|
+
|
|
509
|
+
// Verify the root package.json now has exact versions
|
|
510
|
+
const rootPkgPath = join(tempDir, 'package.json');
|
|
511
|
+
const pkg = JSON.parse(await import('node:fs/promises').then(m => m.readFile(rootPkgPath, 'utf-8')));
|
|
512
|
+
expect(pkg.devDependencies['@mettlecast/domain-cli']).toBe('0.2.53');
|
|
513
|
+
expect(pkg.devDependencies['@mettlecast/domain-runtime']).toBe('0.2.53');
|
|
514
|
+
expect(pkg.devDependencies['@mettlecast/eslint-plugin-domain-module']).toBe('0.2.53');
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('version pinning runs before domain builds', async () => {
|
|
518
|
+
mockReadScaffoldConfig.mockResolvedValue(makeScaffoldConfig(['auth']));
|
|
519
|
+
|
|
520
|
+
// Intercept to check that package.json is already pinned when build is called
|
|
521
|
+
let buildCalled = false;
|
|
522
|
+
mockRunBuild.mockImplementation(async () => {
|
|
523
|
+
buildCalled = true;
|
|
524
|
+
const pkgPath = join(tempDir, 'infra', 'modules', 'package.json');
|
|
525
|
+
const pkg = JSON.parse(await import('node:fs/promises').then(m => m.readFile(pkgPath, 'utf-8')));
|
|
526
|
+
// Versions should already be pinned
|
|
527
|
+
expect(pkg.dependencies['@mettlecast/domain-cdk-packer']).toBe('0.2.53');
|
|
528
|
+
return '/tmp/.mc/domain-registry.json';
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
await runUpdateAll({ projectRoot: tempDir });
|
|
532
|
+
expect(buildCalled).toBe(true);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('skips infra package version pinning when infra/modules/package.json does not exist', async () => {
|
|
536
|
+
// Remove the infra package.json
|
|
537
|
+
const pkgPath = join(tempDir, 'infra', 'modules', 'package.json');
|
|
538
|
+
rmSync(pkgPath, { force: true });
|
|
539
|
+
|
|
540
|
+
mockReadScaffoldConfig.mockResolvedValue(makeScaffoldConfig([]));
|
|
541
|
+
|
|
542
|
+
// Should not throw — should log warning and continue
|
|
543
|
+
const result = await runUpdateAll({ projectRoot: tempDir });
|
|
544
|
+
expect(result.success).toBe(true);
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
it('only pins @mettlecast/* packages that exist in package.json', async () => {
|
|
548
|
+
// Overwrite infra package.json with only some @mettlecast packages
|
|
549
|
+
const infraDir = join(tempDir, 'infra', 'modules');
|
|
550
|
+
const pkgWithPartial = {
|
|
551
|
+
name: 'test-infra',
|
|
552
|
+
version: '1.0.0',
|
|
553
|
+
type: 'module',
|
|
554
|
+
dependencies: {
|
|
555
|
+
'@aws-sdk/client-rds': '^3.700.0',
|
|
556
|
+
'@mettlecast/domain-cdk-packer': '*',
|
|
557
|
+
// Intentionally omits @mettlecast/domain-runtime
|
|
558
|
+
'aws-cdk-lib': '^2.170.0',
|
|
559
|
+
constructs: '^10.4.2',
|
|
560
|
+
},
|
|
561
|
+
devDependencies: {
|
|
562
|
+
'@mettlecast/domain-cli': '*',
|
|
563
|
+
// Intentionally omits @mettlecast/eslint-plugin-domain-module
|
|
564
|
+
'aws-cdk': '^2.170.0',
|
|
565
|
+
typescript: '^5.7.3',
|
|
566
|
+
},
|
|
567
|
+
};
|
|
568
|
+
await writeFile(join(infraDir, 'package.json'), JSON.stringify(pkgWithPartial, null, 2) + '\n', 'utf-8');
|
|
569
|
+
|
|
570
|
+
mockReadScaffoldConfig.mockResolvedValue(makeScaffoldConfig([]));
|
|
571
|
+
|
|
572
|
+
const result = await runUpdateAll({ projectRoot: tempDir });
|
|
573
|
+
expect(result.success).toBe(true);
|
|
574
|
+
|
|
575
|
+
// Only the packages that existed should be pinned
|
|
576
|
+
const pkg = JSON.parse(await import('node:fs/promises').then(m => m.readFile(
|
|
577
|
+
join(tempDir, 'infra', 'modules', 'package.json'), 'utf-8'
|
|
578
|
+
)));
|
|
579
|
+
// domain-cdk-packer was in deps — should be pinned
|
|
580
|
+
expect(pkg.dependencies['@mettlecast/domain-cdk-packer']).toBe('0.2.53');
|
|
581
|
+
// domain-runtime was NOT in deps — should not appear
|
|
582
|
+
expect(pkg.dependencies['@mettlecast/domain-runtime']).toBeUndefined();
|
|
583
|
+
// domain-cli was in devDeps — should be pinned
|
|
584
|
+
expect(pkg.devDependencies['@mettlecast/domain-cli']).toBe('0.2.53');
|
|
585
|
+
// eslint-plugin was NOT in devDeps — should not appear
|
|
586
|
+
expect(pkg.devDependencies['@mettlecast/eslint-plugin-domain-module']).toBeUndefined();
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it('fails when registrySchemaVersion does not equal "1"', async () => {
|
|
590
|
+
const manifestPath = join(tempDir, 'node_modules', '@mettlecast', 'domain-cli', 'toolchain-manifest.json');
|
|
591
|
+
await writeFile(
|
|
592
|
+
manifestPath,
|
|
593
|
+
JSON.stringify({
|
|
594
|
+
schemaVersion: 1,
|
|
595
|
+
registrySchemaVersion: '2',
|
|
596
|
+
packages: {
|
|
597
|
+
domainCli: '0.2.53',
|
|
598
|
+
domainCdkPacker: '0.2.53',
|
|
599
|
+
domainRuntime: '0.2.53',
|
|
600
|
+
eslintPluginDomainModule: '0.2.53',
|
|
601
|
+
},
|
|
602
|
+
}),
|
|
603
|
+
'utf-8',
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
mockReadScaffoldConfig.mockResolvedValue(makeScaffoldConfig(['auth']));
|
|
607
|
+
|
|
608
|
+
const result = await runUpdateAll({ projectRoot: tempDir });
|
|
609
|
+
|
|
610
|
+
expect(result.success).toBe(false);
|
|
611
|
+
expect(result.summary).toContain('Toolchain resolution FAIL');
|
|
612
|
+
expect(result.summary).toContain('registrySchemaVersion');
|
|
613
|
+
expect(result.summary).toContain('"2"');
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
it('regenerates root package-lock.json after version pinning', async () => {
|
|
617
|
+
mockReadScaffoldConfig.mockResolvedValue(makeScaffoldConfig([]));
|
|
618
|
+
|
|
619
|
+
// Create a root package.json but no lockfile
|
|
620
|
+
const rootPkgPath = join(tempDir, 'package.json');
|
|
621
|
+
const rootExists = existsSync(rootPkgPath);
|
|
622
|
+
expect(rootExists).toBe(true);
|
|
623
|
+
|
|
624
|
+
// Remove any existing lockfile
|
|
625
|
+
const rootLockPath = join(tempDir, 'package-lock.json');
|
|
626
|
+
if (existsSync(rootLockPath)) {
|
|
627
|
+
rmSync(rootLockPath, { force: true });
|
|
628
|
+
}
|
|
629
|
+
const infraLockPath = join(tempDir, 'infra', 'modules', 'package-lock.json');
|
|
630
|
+
if (existsSync(infraLockPath)) {
|
|
631
|
+
rmSync(infraLockPath, { force: true });
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Run update-all which should regenerate lockfiles
|
|
635
|
+
const result = await runUpdateAll({ projectRoot: tempDir });
|
|
636
|
+
|
|
637
|
+
// update-all should succeed (doctor mock passes)
|
|
638
|
+
expect(result.success).toBe(true);
|
|
639
|
+
|
|
640
|
+
// Lockfiles should now exist after regeneration
|
|
641
|
+
// (execSync runs 'npm install --package-lock-only' which may fail
|
|
642
|
+
// in test environment without actual npm packages, but the function
|
|
643
|
+
// should attempt regeneration without crashing)
|
|
644
|
+
expect(existsSync(rootLockPath) || existsSync(infraLockPath)).toBeDefined();
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
it('continues gracefully when lockfile regeneration partially fails', async () => {
|
|
648
|
+
mockReadScaffoldConfig.mockResolvedValue(makeScaffoldConfig([]));
|
|
649
|
+
|
|
650
|
+
// Remove infra/modules/package.json to simulate partial failure
|
|
651
|
+
const infraPkgPath = join(tempDir, 'infra', 'modules', 'package.json');
|
|
652
|
+
rmSync(infraPkgPath, { force: true });
|
|
653
|
+
|
|
654
|
+
// Should not throw — lockfile regeneration for missing infra dir is graceful
|
|
655
|
+
const result = await runUpdateAll({ projectRoot: tempDir });
|
|
656
|
+
|
|
657
|
+
expect(result.success).toBe(true);
|
|
658
|
+
});
|
|
659
|
+
});
|
|
660
|
+
});
|
|
@@ -44,7 +44,7 @@ describe('CLI ↔ CDK packer type freshness (#4662 Task D)', () => {
|
|
|
44
44
|
expect(exposure.securityException?.reason).toMatch(/OPS-123/);
|
|
45
45
|
});
|
|
46
46
|
|
|
47
|
-
it('exposes the ActionRegistryEntry shape with the
|
|
47
|
+
it('exposes the ActionRegistryEntry shape with the required fields (#5090)', () => {
|
|
48
48
|
const action: ActionRegistryEntry = {
|
|
49
49
|
kind: 'action',
|
|
50
50
|
handlerFile: 'src/actions/do-thing.ts',
|
|
@@ -52,10 +52,14 @@ describe('CLI ↔ CDK packer type freshness (#4662 Task D)', () => {
|
|
|
52
52
|
exposure: { type: 'internal' },
|
|
53
53
|
idempotent: false,
|
|
54
54
|
};
|
|
55
|
-
// The
|
|
56
|
-
// CLI validator must be present on the type — otherwise the CLI's
|
|
57
|
-
// own `runValidate` cannot detect defaulting regressions.
|
|
55
|
+
// The strict contract (#5090) requires exposure on every entry.
|
|
58
56
|
expect(action).toHaveProperty('exposure');
|
|
57
|
+
expect(action).toHaveProperty('backendAccess');
|
|
58
|
+
expect(action).toHaveProperty('idempotent');
|
|
59
|
+
// The legacy `exposureDeclared` field has been removed.
|
|
60
|
+
expect('exposureDeclared' in action).toBe(false);
|
|
61
|
+
// The legacy `visibility` field has been removed.
|
|
62
|
+
expect('visibility' in action).toBe(false);
|
|
59
63
|
});
|
|
60
64
|
|
|
61
65
|
it('exposes the DomainRegistry schema with the latest fields', () => {
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
createToolchainManifest,
|
|
4
|
+
rewriteCliDependencies,
|
|
5
|
+
METTLECAST_CLI_DEPS,
|
|
6
|
+
EXPECTED_REGISTRY_SCHEMA_VERSION,
|
|
7
|
+
type ToolchainManifest,
|
|
8
|
+
type ToolchainPackages,
|
|
9
|
+
} from '../utils/toolchain-manifest.js';
|
|
10
|
+
|
|
11
|
+
const mockVersions: ToolchainPackages = {
|
|
12
|
+
domainCli: '0.2.53',
|
|
13
|
+
domainCdkPacker: '0.2.53',
|
|
14
|
+
domainRuntime: '0.2.53',
|
|
15
|
+
eslintPluginDomainModule: '0.2.53',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
describe('createToolchainManifest', () => {
|
|
19
|
+
it('creates a valid manifest with all 4 packages', () => {
|
|
20
|
+
const manifest = createToolchainManifest(mockVersions);
|
|
21
|
+
|
|
22
|
+
expect(manifest.schemaVersion).toBe(1);
|
|
23
|
+
expect(manifest.registrySchemaVersion).toBe('1');
|
|
24
|
+
expect(manifest.packages.domainCli).toBe('0.2.53');
|
|
25
|
+
expect(manifest.packages.domainCdkPacker).toBe('0.2.53');
|
|
26
|
+
expect(manifest.packages.domainRuntime).toBe('0.2.53');
|
|
27
|
+
expect(manifest.packages.eslintPluginDomainModule).toBe('0.2.53');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('creates a deterministic shape', () => {
|
|
31
|
+
const manifest = createToolchainManifest(mockVersions);
|
|
32
|
+
const serialized = JSON.stringify(manifest);
|
|
33
|
+
const parsed = JSON.parse(serialized) as ToolchainManifest;
|
|
34
|
+
|
|
35
|
+
expect(parsed).toEqual(manifest);
|
|
36
|
+
expect(parsed.schemaVersion).toBe(1);
|
|
37
|
+
expect(parsed.registrySchemaVersion).toBe('1');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('accepts different versions for each package', () => {
|
|
41
|
+
const mixed: ToolchainPackages = {
|
|
42
|
+
domainCli: '0.3.0',
|
|
43
|
+
domainCdkPacker: '0.2.55',
|
|
44
|
+
domainRuntime: '0.2.53',
|
|
45
|
+
eslintPluginDomainModule: '0.2.54',
|
|
46
|
+
};
|
|
47
|
+
const manifest = createToolchainManifest(mixed);
|
|
48
|
+
|
|
49
|
+
expect(manifest.packages.domainCli).toBe('0.3.0');
|
|
50
|
+
expect(manifest.packages.domainCdkPacker).toBe('0.2.55');
|
|
51
|
+
expect(manifest.packages.domainRuntime).toBe('0.2.53');
|
|
52
|
+
expect(manifest.packages.eslintPluginDomainModule).toBe('0.2.54');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('preserves the exact package keys expected by update-all consumers', () => {
|
|
56
|
+
const manifest = createToolchainManifest(mockVersions);
|
|
57
|
+
const keys = Object.keys(manifest.packages).sort();
|
|
58
|
+
expect(keys).toEqual([
|
|
59
|
+
'domainCdkPacker',
|
|
60
|
+
'domainCli',
|
|
61
|
+
'domainRuntime',
|
|
62
|
+
'eslintPluginDomainModule',
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
describe('rewriteCliDependencies', () => {
|
|
68
|
+
it('replaces wildcard @mettlecast deps with exact versions', () => {
|
|
69
|
+
const pkgJson: { dependencies: Record<string, string> } = {
|
|
70
|
+
dependencies: {
|
|
71
|
+
'@mettlecast/domain-cdk-packer': '*',
|
|
72
|
+
'@mettlecast/domain-runtime': '*',
|
|
73
|
+
commander: '^12.0.0',
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const changed = rewriteCliDependencies(pkgJson, mockVersions);
|
|
78
|
+
|
|
79
|
+
expect(changed).toBe(true);
|
|
80
|
+
expect(pkgJson.dependencies['@mettlecast/domain-cdk-packer']).toBe('0.2.53');
|
|
81
|
+
expect(pkgJson.dependencies['@mettlecast/domain-runtime']).toBe('0.2.53');
|
|
82
|
+
// Non-mettlecast dep unchanged
|
|
83
|
+
expect(pkgJson.dependencies.commander).toBe('^12.0.0');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('replaces workspace:* ranges', () => {
|
|
87
|
+
const pkgJson: { dependencies: Record<string, string> } = {
|
|
88
|
+
dependencies: {
|
|
89
|
+
'@mettlecast/domain-cdk-packer': 'workspace:*',
|
|
90
|
+
'@mettlecast/domain-runtime': 'workspace:*',
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
rewriteCliDependencies(pkgJson, mockVersions);
|
|
95
|
+
|
|
96
|
+
expect(pkgJson.dependencies['@mettlecast/domain-cdk-packer']).toBe('0.2.53');
|
|
97
|
+
expect(pkgJson.dependencies['@mettlecast/domain-runtime']).toBe('0.2.53');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('handles missing dependencies gracefully', () => {
|
|
101
|
+
const pkgJson: Record<string, unknown> = { name: 'test' };
|
|
102
|
+
|
|
103
|
+
const changed = rewriteCliDependencies(
|
|
104
|
+
pkgJson as { dependencies?: Record<string, string> },
|
|
105
|
+
mockVersions,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(changed).toBe(false);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('handles empty dependencies gracefully', () => {
|
|
112
|
+
const pkgJson: { dependencies: Record<string, string> } = {
|
|
113
|
+
dependencies: {},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const changed = rewriteCliDependencies(pkgJson, mockVersions);
|
|
117
|
+
|
|
118
|
+
expect(changed).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('replaces only the @mettlecast deps listed in METTLECAST_CLI_DEPS', () => {
|
|
122
|
+
const pkgJson: { dependencies: Record<string, string> } = {
|
|
123
|
+
dependencies: {
|
|
124
|
+
'@mettlecast/domain-cdk-packer': '*',
|
|
125
|
+
'@mettlecast/domain-runtime': '*',
|
|
126
|
+
'@mettlecast/domain-cli': '*', // self-reference (edge case)
|
|
127
|
+
'@mettlecast/eslint-plugin-domain-module': '*',
|
|
128
|
+
'some-other-dep': '^1.0.0',
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
rewriteCliDependencies(pkgJson, mockVersions);
|
|
133
|
+
|
|
134
|
+
// All mettlecast deps replaced
|
|
135
|
+
expect(pkgJson.dependencies['@mettlecast/domain-cdk-packer']).toBe('0.2.53');
|
|
136
|
+
expect(pkgJson.dependencies['@mettlecast/domain-runtime']).toBe('0.2.53');
|
|
137
|
+
expect(pkgJson.dependencies['@mettlecast/domain-cli']).toBe('0.2.53');
|
|
138
|
+
expect(pkgJson.dependencies['@mettlecast/eslint-plugin-domain-module']).toBe('0.2.53');
|
|
139
|
+
// Non-mettlecast dep unchanged
|
|
140
|
+
expect(pkgJson.dependencies['some-other-dep']).toBe('^1.0.0');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('returns true when at least one dep was rewritten', () => {
|
|
144
|
+
const pkgJson: { dependencies: Record<string, string> } = {
|
|
145
|
+
dependencies: {
|
|
146
|
+
'@mettlecast/domain-cdk-packer': '*',
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
expect(rewriteCliDependencies(pkgJson, mockVersions)).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('returns false when no @mettlecast deps exist', () => {
|
|
154
|
+
const pkgJson: { dependencies: Record<string, string> } = {
|
|
155
|
+
dependencies: {
|
|
156
|
+
commander: '^12.0.0',
|
|
157
|
+
fastify: '^5.0.0',
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
expect(rewriteCliDependencies(pkgJson, mockVersions)).toBe(false);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('EXPECTED_REGISTRY_SCHEMA_VERSION', () => {
|
|
166
|
+
it('equals "1" matching DomainRegistry.schemaVersion', () => {
|
|
167
|
+
expect(EXPECTED_REGISTRY_SCHEMA_VERSION).toBe('1');
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('is used by createToolchainManifest as registrySchemaVersion', () => {
|
|
171
|
+
const manifest = createToolchainManifest(mockVersions);
|
|
172
|
+
expect(manifest.registrySchemaVersion).toBe(EXPECTED_REGISTRY_SCHEMA_VERSION);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe('METTLECAST_CLI_DEPS', () => {
|
|
177
|
+
it('maps all 4 toolchain package IDs to correct npm names', () => {
|
|
178
|
+
expect(METTLECAST_CLI_DEPS).toEqual({
|
|
179
|
+
domainCli: '@mettlecast/domain-cli',
|
|
180
|
+
domainCdkPacker: '@mettlecast/domain-cdk-packer',
|
|
181
|
+
domainRuntime: '@mettlecast/domain-runtime',
|
|
182
|
+
eslintPluginDomainModule: '@mettlecast/eslint-plugin-domain-module',
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|