@mettlecast/domain-cli 0.2.0
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/CHANGELOG.md +18 -0
- package/README.md +133 -0
- package/dist/builder/build-registry.d.ts +20 -0
- package/dist/builder/build-registry.js +178 -0
- package/dist/builder/build-types.d.ts +10 -0
- package/dist/builder/build-types.js +32 -0
- package/dist/builder/load-domain-module-config.d.ts +12 -0
- package/dist/builder/load-domain-module-config.js +34 -0
- package/dist/builder/load-module.d.ts +26 -0
- package/dist/builder/load-module.js +114 -0
- package/dist/builder/zod-to-json.d.ts +10 -0
- package/dist/builder/zod-to-json.js +15 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +338 -0
- package/dist/commands/add-api.d.ts +32 -0
- package/dist/commands/add-api.js +106 -0
- package/dist/commands/add-component.d.ts +15 -0
- package/dist/commands/add-component.js +78 -0
- package/dist/commands/add-domain.d.ts +21 -0
- package/dist/commands/add-domain.js +71 -0
- package/dist/commands/add-flow.d.ts +14 -0
- package/dist/commands/add-flow.js +108 -0
- package/dist/commands/add-hook.d.ts +15 -0
- package/dist/commands/add-hook.js +65 -0
- package/dist/commands/add-migration.d.ts +26 -0
- package/dist/commands/add-migration.js +93 -0
- package/dist/commands/add-module.d.ts +7 -0
- package/dist/commands/add-module.js +261 -0
- package/dist/commands/add-page.d.ts +13 -0
- package/dist/commands/add-page.js +170 -0
- package/dist/commands/add-seed-page.d.ts +5 -0
- package/dist/commands/add-seed-page.js +251 -0
- package/dist/commands/add-subscriber.d.ts +30 -0
- package/dist/commands/add-subscriber.js +87 -0
- package/dist/commands/add-test.d.ts +22 -0
- package/dist/commands/add-test.js +285 -0
- package/dist/commands/audit.d.ts +39 -0
- package/dist/commands/audit.js +171 -0
- package/dist/commands/build-catalog.d.ts +68 -0
- package/dist/commands/build-catalog.js +105 -0
- package/dist/commands/build-flows.d.ts +26 -0
- package/dist/commands/build-flows.js +174 -0
- package/dist/commands/build.d.ts +17 -0
- package/dist/commands/build.js +39 -0
- package/dist/commands/check-hashes.d.ts +38 -0
- package/dist/commands/check-hashes.js +97 -0
- package/dist/commands/create-project.d.ts +5 -0
- package/dist/commands/create-project.js +272 -0
- package/dist/commands/dev.d.ts +15 -0
- package/dist/commands/dev.js +27 -0
- package/dist/commands/doctor.d.ts +48 -0
- package/dist/commands/doctor.js +1301 -0
- package/dist/commands/explain.d.ts +34 -0
- package/dist/commands/explain.js +167 -0
- package/dist/commands/power-tune.d.ts +1 -0
- package/dist/commands/power-tune.js +87 -0
- package/dist/commands/reseed-page.d.ts +6 -0
- package/dist/commands/reseed-page.js +297 -0
- package/dist/commands/show-dns.d.ts +15 -0
- package/dist/commands/show-dns.js +101 -0
- package/dist/commands/show.d.ts +46 -0
- package/dist/commands/show.js +135 -0
- package/dist/commands/test.d.ts +18 -0
- package/dist/commands/test.js +56 -0
- package/dist/commands/upgrade-backend.d.ts +9 -0
- package/dist/commands/upgrade-backend.js +46 -0
- package/dist/commands/upgrade-event.d.ts +22 -0
- package/dist/commands/upgrade-event.js +111 -0
- package/dist/commands/upgrade-frontend.d.ts +5 -0
- package/dist/commands/upgrade-frontend.js +99 -0
- package/dist/commands/upgrade.d.ts +7 -0
- package/dist/commands/upgrade.js +495 -0
- package/dist/commands/validate.d.ts +30 -0
- package/dist/commands/validate.js +148 -0
- package/dist/runtime-stubs/hydrate-local-ctx.d.ts +49 -0
- package/dist/runtime-stubs/hydrate-local-ctx.js +45 -0
- package/dist/runtime-stubs/load-secrets.d.ts +11 -0
- package/dist/runtime-stubs/load-secrets.js +37 -0
- package/dist/server/api-server.d.ts +21 -0
- package/dist/server/api-server.js +14 -0
- package/dist/server/mount-routes.d.ts +19 -0
- package/dist/server/mount-routes.js +42 -0
- package/dist/templates/api-skeleton.d.ts +18 -0
- package/dist/templates/api-skeleton.js +69 -0
- package/dist/templates/claude-md.d.ts +4 -0
- package/dist/templates/claude-md.js +7 -0
- package/dist/templates/dashboard-pages/index.d.ts +101 -0
- package/dist/templates/dashboard-pages/index.js +22 -0
- package/dist/templates/domain-config.d.ts +10 -0
- package/dist/templates/domain-config.js +19 -0
- package/dist/templates/events-skeleton.d.ts +9 -0
- package/dist/templates/events-skeleton.js +23 -0
- package/dist/templates/patterns/api/create-with-event.d.ts +5 -0
- package/dist/templates/patterns/api/create-with-event.js +69 -0
- package/dist/templates/patterns/api/idempotent-mutation.d.ts +5 -0
- package/dist/templates/patterns/api/idempotent-mutation.js +99 -0
- package/dist/templates/patterns/api/paginated-list.d.ts +5 -0
- package/dist/templates/patterns/api/paginated-list.js +71 -0
- package/dist/templates/patterns/api/simple-crud.d.ts +6 -0
- package/dist/templates/patterns/api/simple-crud.js +95 -0
- package/dist/templates/patterns/api/system-admin.d.ts +5 -0
- package/dist/templates/patterns/api/system-admin.js +98 -0
- package/dist/templates/patterns/api/webhook-receiver-style.d.ts +5 -0
- package/dist/templates/patterns/api/webhook-receiver-style.js +125 -0
- package/dist/templates/patterns/component/index.d.ts +25 -0
- package/dist/templates/patterns/component/index.js +814 -0
- package/dist/templates/patterns/flow/autonomous-remediation.d.ts +5 -0
- package/dist/templates/patterns/flow/autonomous-remediation.js +98 -0
- package/dist/templates/patterns/flow/fan-out-reaction.d.ts +5 -0
- package/dist/templates/patterns/flow/fan-out-reaction.js +53 -0
- package/dist/templates/patterns/flow/human-in-loop.d.ts +5 -0
- package/dist/templates/patterns/flow/human-in-loop.js +90 -0
- package/dist/templates/patterns/flow/multi-step-orchestration.d.ts +5 -0
- package/dist/templates/patterns/flow/multi-step-orchestration.js +67 -0
- package/dist/templates/patterns/hook/index.d.ts +7 -0
- package/dist/templates/patterns/hook/index.js +378 -0
- package/dist/templates/patterns/migration/column-add.d.ts +5 -0
- package/dist/templates/patterns/migration/column-add.js +52 -0
- package/dist/templates/patterns/migration/event-store.d.ts +6 -0
- package/dist/templates/patterns/migration/event-store.js +97 -0
- package/dist/templates/patterns/migration/index.d.ts +5 -0
- package/dist/templates/patterns/migration/index.js +52 -0
- package/dist/templates/patterns/migration/rls-policy.d.ts +5 -0
- package/dist/templates/patterns/migration/rls-policy.js +78 -0
- package/dist/templates/patterns/migration/tenant-scoped-table.d.ts +6 -0
- package/dist/templates/patterns/migration/tenant-scoped-table.js +81 -0
- package/dist/templates/patterns/page/dashboard.d.ts +4 -0
- package/dist/templates/patterns/page/dashboard.js +164 -0
- package/dist/templates/patterns/page/detail.d.ts +4 -0
- package/dist/templates/patterns/page/detail.js +162 -0
- package/dist/templates/patterns/page/form.d.ts +4 -0
- package/dist/templates/patterns/page/form.js +192 -0
- package/dist/templates/patterns/page/index.d.ts +8 -0
- package/dist/templates/patterns/page/index.js +14 -0
- package/dist/templates/patterns/page/list.d.ts +4 -0
- package/dist/templates/patterns/page/list.js +138 -0
- package/dist/templates/patterns/page/settings.d.ts +4 -0
- package/dist/templates/patterns/page/settings.js +318 -0
- package/dist/templates/patterns/page/wizard.d.ts +4 -0
- package/dist/templates/patterns/page/wizard.js +293 -0
- package/dist/templates/patterns/subscriber/audit-relay.d.ts +5 -0
- package/dist/templates/patterns/subscriber/audit-relay.js +51 -0
- package/dist/templates/patterns/subscriber/cascade-deletion.d.ts +5 -0
- package/dist/templates/patterns/subscriber/cascade-deletion.js +60 -0
- package/dist/templates/patterns/subscriber/single-step-projection.d.ts +5 -0
- package/dist/templates/patterns/subscriber/single-step-projection.js +36 -0
- package/dist/templates/readme.d.ts +4 -0
- package/dist/templates/readme.js +7 -0
- package/dist/templates/subscriber-skeleton.d.ts +11 -0
- package/dist/templates/subscriber-skeleton.js +23 -0
- package/dist/utils/checksum.d.ts +18 -0
- package/dist/utils/checksum.js +39 -0
- package/dist/utils/file-helpers.d.ts +35 -0
- package/dist/utils/file-helpers.js +59 -0
- package/dist/utils/header-inject.d.ts +15 -0
- package/dist/utils/header-inject.js +93 -0
- package/dist/utils/install-file.d.ts +17 -0
- package/dist/utils/install-file.js +78 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.js +24 -0
- package/dist/utils/manifest.d.ts +28 -0
- package/dist/utils/manifest.js +93 -0
- package/dist/utils/s3-fetch.d.ts +25 -0
- package/dist/utils/s3-fetch.js +52 -0
- package/dist/utils/scaffold-config.d.ts +95 -0
- package/dist/utils/scaffold-config.js +53 -0
- package/dist/utils/token-substitution.d.ts +35 -0
- package/dist/utils/token-substitution.js +52 -0
- package/dist/utils/zod-extractor.d.ts +10 -0
- package/dist/utils/zod-extractor.js +28 -0
- package/package.json +43 -0
- package/src/__tests__/build-registry.test.ts +115 -0
- package/src/__tests__/build-types.test.ts +100 -0
- package/src/__tests__/builder/walkDomainDir.test.ts +140 -0
- package/src/__tests__/commands/add-domain.test.ts +174 -0
- package/src/__tests__/commands/add-flow.test.ts +69 -0
- package/src/__tests__/commands/add-module.test.ts +207 -0
- package/src/__tests__/commands/build-flows.test.ts +154 -0
- package/src/__tests__/commands/check-hashes.test.ts +142 -0
- package/src/__tests__/commands/create-project.test.ts +712 -0
- package/src/__tests__/commands/upgrade.test.ts +762 -0
- package/src/__tests__/doctor.test.ts +291 -0
- package/src/__tests__/hydrate-local-ctx.test.ts +71 -0
- package/src/__tests__/load-domain-module-config.test.ts +10 -0
- package/src/__tests__/mount-routes.test.ts +100 -0
- package/src/__tests__/scaffold-src/part-a-layout.test.ts +46 -0
- package/src/__tests__/scripts/package-scaffold.test.ts +45 -0
- package/src/__tests__/utils/checksum.test.ts +90 -0
- package/src/__tests__/utils/header-inject.test.ts +89 -0
- package/src/__tests__/utils/install-file.test.ts +207 -0
- package/src/__tests__/utils/manifest.test.ts +229 -0
- package/src/__tests__/utils/token-substitution.test.ts +134 -0
- package/src/__tests__/validate.test.ts +18 -0
- package/src/builder/build-registry.ts +237 -0
- package/src/builder/build-types.ts +36 -0
- package/src/builder/load-domain-module-config.ts +43 -0
- package/src/builder/load-module.ts +133 -0
- package/src/builder/zod-to-json.ts +16 -0
- package/src/cli.ts +387 -0
- package/src/commands/add-api.ts +145 -0
- package/src/commands/add-component.ts +116 -0
- package/src/commands/add-domain.ts +100 -0
- package/src/commands/add-flow.ts +136 -0
- package/src/commands/add-hook.ts +100 -0
- package/src/commands/add-migration.ts +129 -0
- package/src/commands/add-module.ts +347 -0
- package/src/commands/add-page.ts +242 -0
- package/src/commands/add-seed-page.ts +338 -0
- package/src/commands/add-subscriber.ts +123 -0
- package/src/commands/add-test.ts +328 -0
- package/src/commands/audit.ts +217 -0
- package/src/commands/build-catalog.ts +181 -0
- package/src/commands/build-flows.ts +198 -0
- package/src/commands/build.ts +59 -0
- package/src/commands/check-hashes.ts +150 -0
- package/src/commands/create-project.ts +367 -0
- package/src/commands/dev.ts +47 -0
- package/src/commands/doctor.ts +1362 -0
- package/src/commands/explain.ts +209 -0
- package/src/commands/power-tune.ts +112 -0
- package/src/commands/reseed-page.ts +410 -0
- package/src/commands/show-dns.ts +139 -0
- package/src/commands/show.ts +179 -0
- package/src/commands/test.ts +91 -0
- package/src/commands/upgrade-backend.ts +72 -0
- package/src/commands/upgrade-event.ts +175 -0
- package/src/commands/upgrade-frontend.ts +135 -0
- package/src/commands/upgrade.ts +668 -0
- package/src/commands/validate.ts +208 -0
- package/src/runtime-stubs/hydrate-local-ctx.ts +102 -0
- package/src/runtime-stubs/load-secrets.ts +40 -0
- package/src/server/api-server.ts +34 -0
- package/src/server/mount-routes.ts +68 -0
- package/src/templates/api-skeleton.ts +72 -0
- package/src/templates/claude-md.ts +7 -0
- package/src/templates/dashboard-pages/account/api-keys.tsx +107 -0
- package/src/templates/dashboard-pages/account/audit-log.tsx +60 -0
- package/src/templates/dashboard-pages/account/index.ts +5 -0
- package/src/templates/dashboard-pages/account/members.tsx +107 -0
- package/src/templates/dashboard-pages/account/profile.tsx +53 -0
- package/src/templates/dashboard-pages/account/workspace-settings.tsx +64 -0
- package/src/templates/dashboard-pages/auth/accept-invitation.tsx +62 -0
- package/src/templates/dashboard-pages/auth/choose-org.tsx +66 -0
- package/src/templates/dashboard-pages/auth/forgot-password.tsx +69 -0
- package/src/templates/dashboard-pages/auth/index.ts +10 -0
- package/src/templates/dashboard-pages/auth/login.tsx +75 -0
- package/src/templates/dashboard-pages/auth/mfa-setup.tsx +56 -0
- package/src/templates/dashboard-pages/auth/mfa-verify.tsx +49 -0
- package/src/templates/dashboard-pages/auth/reset-password.tsx +64 -0
- package/src/templates/dashboard-pages/auth/sign-out.tsx +20 -0
- package/src/templates/dashboard-pages/auth/signup.tsx +71 -0
- package/src/templates/dashboard-pages/auth/verify-email.tsx +53 -0
- package/src/templates/dashboard-pages/index.ts +22 -0
- package/src/templates/dashboard-pages/shell/dashboard-home.tsx +41 -0
- package/src/templates/dashboard-pages/shell/forbidden.tsx +46 -0
- package/src/templates/dashboard-pages/shell/index.ts +5 -0
- package/src/templates/dashboard-pages/shell/maintenance.tsx +27 -0
- package/src/templates/dashboard-pages/shell/not-found.tsx +34 -0
- package/src/templates/dashboard-pages/shell/server-error.tsx +44 -0
- package/src/templates/domain-config.ts +20 -0
- package/src/templates/events-skeleton.ts +24 -0
- package/src/templates/patterns/api/create-with-event.ts +72 -0
- package/src/templates/patterns/api/idempotent-mutation.ts +102 -0
- package/src/templates/patterns/api/paginated-list.ts +74 -0
- package/src/templates/patterns/api/simple-crud.ts +100 -0
- package/src/templates/patterns/api/system-admin.ts +101 -0
- package/src/templates/patterns/api/webhook-receiver-style.ts +128 -0
- package/src/templates/patterns/component/index.ts +833 -0
- package/src/templates/patterns/flow/autonomous-remediation.ts +101 -0
- package/src/templates/patterns/flow/fan-out-reaction.ts +56 -0
- package/src/templates/patterns/flow/human-in-loop.ts +93 -0
- package/src/templates/patterns/flow/multi-step-orchestration.ts +70 -0
- package/src/templates/patterns/hook/index.ts +382 -0
- package/src/templates/patterns/migration/column-add.ts +54 -0
- package/src/templates/patterns/migration/event-store.ts +99 -0
- package/src/templates/patterns/migration/index.ts +54 -0
- package/src/templates/patterns/migration/rls-policy.ts +80 -0
- package/src/templates/patterns/migration/tenant-scoped-table.ts +83 -0
- package/src/templates/patterns/page/dashboard.ts +164 -0
- package/src/templates/patterns/page/detail.ts +162 -0
- package/src/templates/patterns/page/form.ts +192 -0
- package/src/templates/patterns/page/index.ts +17 -0
- package/src/templates/patterns/page/list.ts +138 -0
- package/src/templates/patterns/page/settings.ts +318 -0
- package/src/templates/patterns/page/wizard.ts +293 -0
- package/src/templates/patterns/subscriber/audit-relay.ts +54 -0
- package/src/templates/patterns/subscriber/cascade-deletion.ts +63 -0
- package/src/templates/patterns/subscriber/single-step-projection.ts +39 -0
- package/src/templates/readme.ts +7 -0
- package/src/templates/subscriber-skeleton.ts +28 -0
- package/src/utils/checksum.ts +43 -0
- package/src/utils/file-helpers.ts +91 -0
- package/src/utils/header-inject.ts +126 -0
- package/src/utils/install-file.ts +99 -0
- package/src/utils/logger.ts +26 -0
- package/src/utils/manifest.ts +137 -0
- package/src/utils/s3-fetch.ts +91 -0
- package/src/utils/scaffold-config.ts +172 -0
- package/src/utils/token-substitution.ts +80 -0
- package/src/utils/zod-extractor.ts +31 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { readFile, rm, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import { runAddFlow } from '../../commands/add-flow.js';
|
|
6
|
+
|
|
7
|
+
describe('add-flow command', () => {
|
|
8
|
+
let tmpDir: string;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
tmpDir = join(os.tmpdir(), `tib-test-${Math.random().toString(36).slice(2)}`);
|
|
12
|
+
// Create domain directory so domain existence check passes
|
|
13
|
+
await mkdir(join(tmpDir, 'domains', 'auth'), { recursive: true });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(async () => {
|
|
17
|
+
try {
|
|
18
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
19
|
+
} catch {
|
|
20
|
+
// ignore cleanup errors
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('scaffolds a new flow file under domains/{domain}/flows/', async () => {
|
|
25
|
+
await runAddFlow({ domain: 'auth', id: 'test-flow', projectRoot: tmpDir });
|
|
26
|
+
|
|
27
|
+
const flowFile = join(tmpDir, 'domains', 'auth', 'flows', 'test-flow.ts');
|
|
28
|
+
const content = await readFile(flowFile, 'utf8');
|
|
29
|
+
|
|
30
|
+
expect(content).toContain("defineFlow({");
|
|
31
|
+
expect(content).toContain("id: 'test-flow'");
|
|
32
|
+
expect(content).toContain("owningDomain: 'auth'");
|
|
33
|
+
expect(content).toContain("type: 'express'");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('respects --type standard', async () => {
|
|
37
|
+
await runAddFlow({ domain: 'auth', id: 'long-flow', type: 'standard', projectRoot: tmpDir });
|
|
38
|
+
|
|
39
|
+
const flowFile = join(tmpDir, 'domains', 'auth', 'flows', 'long-flow.ts');
|
|
40
|
+
const content = await readFile(flowFile, 'utf8');
|
|
41
|
+
expect(content).toContain("type: 'standard'");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('rejects adding the same flow twice', async () => {
|
|
45
|
+
await runAddFlow({ domain: 'auth', id: 'test-flow', projectRoot: tmpDir });
|
|
46
|
+
await expect(runAddFlow({ domain: 'auth', id: 'test-flow', projectRoot: tmpDir }))
|
|
47
|
+
.rejects.toThrow(/already exists/);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('rejects invalid flow id (not kebab-case)', async () => {
|
|
51
|
+
await expect(runAddFlow({ domain: 'auth', id: 'InvalidId', projectRoot: tmpDir }))
|
|
52
|
+
.rejects.toThrow(/must be kebab-case/);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('rejects flow id starting with number', async () => {
|
|
56
|
+
await expect(runAddFlow({ domain: 'auth', id: '123-flow', projectRoot: tmpDir }))
|
|
57
|
+
.rejects.toThrow(/must be kebab-case/);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('rejects non-existent domain', async () => {
|
|
61
|
+
await expect(runAddFlow({ domain: 'missing-domain', id: 'test-flow', projectRoot: tmpDir }))
|
|
62
|
+
.rejects.toThrow(/does not exist/);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('rejects invalid domain id', async () => {
|
|
66
|
+
await expect(runAddFlow({ domain: 'Invalid', id: 'test-flow', projectRoot: tmpDir }))
|
|
67
|
+
.rejects.toThrow(/must be kebab-case/);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
|
|
3
|
+
vi.mock('../../utils/s3-fetch.js', () => ({
|
|
4
|
+
fetchVersionsJson: vi.fn(),
|
|
5
|
+
fetchModulesJson: vi.fn().mockResolvedValue({
|
|
6
|
+
scaffoldVersion: '1.0.0',
|
|
7
|
+
releasedAt: '2026-01-01T00:00:00Z',
|
|
8
|
+
modules: [
|
|
9
|
+
{
|
|
10
|
+
id: 'core',
|
|
11
|
+
version: '1.0.0',
|
|
12
|
+
description: 'Core scaffold',
|
|
13
|
+
dependencies: [],
|
|
14
|
+
tarball: 'core-1.0.0.tar.gz',
|
|
15
|
+
sha256: 'abc123',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'backend-lambda',
|
|
19
|
+
version: '1.0.0',
|
|
20
|
+
description: 'Backend Lambda support',
|
|
21
|
+
dependencies: ['core'],
|
|
22
|
+
tarball: 'backend-lambda-1.0.0.tar.gz',
|
|
23
|
+
sha256: 'def456',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'db-aurora',
|
|
27
|
+
version: '1.0.0',
|
|
28
|
+
description: 'Aurora database support',
|
|
29
|
+
dependencies: ['backend-lambda'],
|
|
30
|
+
tarball: 'db-aurora-1.0.0.tar.gz',
|
|
31
|
+
sha256: 'ghi789',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'data-management',
|
|
35
|
+
version: '1.0.0',
|
|
36
|
+
description: 'Data management module',
|
|
37
|
+
dependencies: ['backend-lambda', 'db-aurora'],
|
|
38
|
+
tarball: 'data-management-1.0.0.tar.gz',
|
|
39
|
+
sha256: 'jkl012',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'frontend',
|
|
43
|
+
version: '1.0.0',
|
|
44
|
+
description: 'Frontend support',
|
|
45
|
+
dependencies: ['core'],
|
|
46
|
+
tarball: 'frontend-1.0.0.tar.gz',
|
|
47
|
+
sha256: 'mno345',
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
compatibility: { cliMinVersion: '0.2.0', nodeMinVersion: '20.0.0' },
|
|
51
|
+
}),
|
|
52
|
+
fetchModuleTarball: vi.fn(),
|
|
53
|
+
}));
|
|
54
|
+
|
|
55
|
+
vi.mock('../../utils/scaffold-config.js', () => ({
|
|
56
|
+
readScaffoldConfig: vi.fn().mockResolvedValue({
|
|
57
|
+
scaffoldVersion: '1.0.0',
|
|
58
|
+
enabledModules: ['core', 'backend-lambda'],
|
|
59
|
+
projectName: 'test-project',
|
|
60
|
+
awsRegion: 'eu-north-1',
|
|
61
|
+
awsAccountId: '123456789012',
|
|
62
|
+
domainIds: [],
|
|
63
|
+
flowIds: [],
|
|
64
|
+
scaffoldBucket: 'mc-scaffold',
|
|
65
|
+
}),
|
|
66
|
+
writeScaffoldConfig: vi.fn().mockResolvedValue(undefined),
|
|
67
|
+
}));
|
|
68
|
+
|
|
69
|
+
vi.mock('../../utils/manifest.js', () => ({
|
|
70
|
+
readManifest: vi.fn().mockResolvedValue({
|
|
71
|
+
scaffoldVersion: '1.0.0',
|
|
72
|
+
files: [],
|
|
73
|
+
enabledModules: ['core', 'backend-lambda'],
|
|
74
|
+
projectName: 'test',
|
|
75
|
+
awsRegion: 'eu-north-1',
|
|
76
|
+
createdAt: '',
|
|
77
|
+
updatedAt: '',
|
|
78
|
+
}),
|
|
79
|
+
writeManifest: vi.fn().mockResolvedValue(undefined),
|
|
80
|
+
upsertManifestFile: vi.fn(),
|
|
81
|
+
createManifest: vi.fn(),
|
|
82
|
+
isScaffoldOwned: vi.fn().mockReturnValue(false),
|
|
83
|
+
}));
|
|
84
|
+
|
|
85
|
+
import { runAddModule } from '../../commands/add-module.js';
|
|
86
|
+
import { readScaffoldConfig, writeScaffoldConfig } from '../../utils/scaffold-config.js';
|
|
87
|
+
import { fetchModulesJson } from '../../utils/s3-fetch.js';
|
|
88
|
+
|
|
89
|
+
const mockReadScaffoldConfig = vi.mocked(readScaffoldConfig);
|
|
90
|
+
const mockFetchModulesJson = vi.mocked(fetchModulesJson);
|
|
91
|
+
|
|
92
|
+
describe('add-module command', () => {
|
|
93
|
+
beforeEach(() => {
|
|
94
|
+
vi.clearAllMocks();
|
|
95
|
+
|
|
96
|
+
// Reset to default mock state
|
|
97
|
+
mockReadScaffoldConfig.mockResolvedValue({
|
|
98
|
+
scaffoldVersion: '1.0.0',
|
|
99
|
+
enabledModules: ['core', 'backend-lambda'],
|
|
100
|
+
projectName: 'test-project',
|
|
101
|
+
awsRegion: 'eu-north-1',
|
|
102
|
+
awsAccountId: '123456789012',
|
|
103
|
+
domainIds: [],
|
|
104
|
+
flowIds: [],
|
|
105
|
+
scaffoldBucket: 'mc-scaffold',
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
mockFetchModulesJson.mockResolvedValue({
|
|
109
|
+
scaffoldVersion: '1.0.0',
|
|
110
|
+
releasedAt: '2026-01-01T00:00:00Z',
|
|
111
|
+
modules: [
|
|
112
|
+
{
|
|
113
|
+
id: 'core',
|
|
114
|
+
version: '1.0.0',
|
|
115
|
+
description: 'Core scaffold',
|
|
116
|
+
dependencies: [],
|
|
117
|
+
tarball: 'core-1.0.0.tar.gz',
|
|
118
|
+
sha256: 'abc123',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
id: 'backend-lambda',
|
|
122
|
+
version: '1.0.0',
|
|
123
|
+
description: 'Backend Lambda support',
|
|
124
|
+
dependencies: ['core'],
|
|
125
|
+
tarball: 'backend-lambda-1.0.0.tar.gz',
|
|
126
|
+
sha256: 'def456',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'db-aurora',
|
|
130
|
+
version: '1.0.0',
|
|
131
|
+
description: 'Aurora database support',
|
|
132
|
+
dependencies: ['backend-lambda'],
|
|
133
|
+
tarball: 'db-aurora-1.0.0.tar.gz',
|
|
134
|
+
sha256: 'ghi789',
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 'data-management',
|
|
138
|
+
version: '1.0.0',
|
|
139
|
+
description: 'Data management module',
|
|
140
|
+
dependencies: ['backend-lambda', 'db-aurora'],
|
|
141
|
+
tarball: 'data-management-1.0.0.tar.gz',
|
|
142
|
+
sha256: 'jkl012',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'frontend',
|
|
146
|
+
version: '1.0.0',
|
|
147
|
+
description: 'Frontend support',
|
|
148
|
+
dependencies: ['core'],
|
|
149
|
+
tarball: 'frontend-1.0.0.tar.gz',
|
|
150
|
+
sha256: 'mno345',
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
compatibility: { cliMinVersion: '0.2.0', nodeMinVersion: '20.0.0' },
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('rejects already-installed module', async () => {
|
|
158
|
+
mockReadScaffoldConfig.mockResolvedValue({
|
|
159
|
+
scaffoldVersion: '1.0.0',
|
|
160
|
+
enabledModules: ['core', 'backend-lambda'],
|
|
161
|
+
projectName: 'test-project',
|
|
162
|
+
awsRegion: 'eu-north-1',
|
|
163
|
+
awsAccountId: '123456789012',
|
|
164
|
+
domainIds: [],
|
|
165
|
+
flowIds: [],
|
|
166
|
+
scaffoldBucket: 'mc-scaffold',
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await expect(
|
|
170
|
+
runAddModule({
|
|
171
|
+
moduleId: 'core',
|
|
172
|
+
projectDir: '/tmp/fake-project',
|
|
173
|
+
})
|
|
174
|
+
).rejects.toThrow('Module "core" is already installed');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('rejects missing dependency', async () => {
|
|
178
|
+
mockReadScaffoldConfig.mockResolvedValue({
|
|
179
|
+
scaffoldVersion: '1.0.0',
|
|
180
|
+
enabledModules: ['core', 'backend-lambda'],
|
|
181
|
+
projectName: 'test-project',
|
|
182
|
+
awsRegion: 'eu-north-1',
|
|
183
|
+
awsAccountId: '123456789012',
|
|
184
|
+
domainIds: [],
|
|
185
|
+
flowIds: [],
|
|
186
|
+
scaffoldBucket: 'mc-scaffold',
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
await expect(
|
|
190
|
+
runAddModule({
|
|
191
|
+
moduleId: 'data-management',
|
|
192
|
+
projectDir: '/tmp/fake-project',
|
|
193
|
+
})
|
|
194
|
+
).rejects.toThrow(
|
|
195
|
+
'Module "data-management" requires "db-aurora" which is not installed'
|
|
196
|
+
);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it('rejects unknown module', async () => {
|
|
200
|
+
await expect(
|
|
201
|
+
runAddModule({
|
|
202
|
+
moduleId: 'unknown-module',
|
|
203
|
+
projectDir: '/tmp/fake-project',
|
|
204
|
+
})
|
|
205
|
+
).rejects.toThrow('Unknown module "unknown-module"');
|
|
206
|
+
});
|
|
207
|
+
});
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { writeFile, readFile, rm, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import * as os from 'node:os';
|
|
5
|
+
import { runBuildFlows, FlowIdCollisionError } from '../../commands/build-flows.js';
|
|
6
|
+
import type { FlowRegistry } from '@mettlecast/domain-cdk-packer';
|
|
7
|
+
|
|
8
|
+
describe('build-flows command', () => {
|
|
9
|
+
let tmpDir: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(async () => {
|
|
12
|
+
tmpDir = join(os.tmpdir(), `tib-test-${Math.random().toString(36).slice(2)}`);
|
|
13
|
+
await mkdir(tmpDir, { recursive: true });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(async () => {
|
|
17
|
+
try {
|
|
18
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
19
|
+
} catch {
|
|
20
|
+
// ignore cleanup errors
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('builds registry from flows directory that does not exist', async () => {
|
|
25
|
+
const outFile = await runBuildFlows({ projectRoot: tmpDir });
|
|
26
|
+
|
|
27
|
+
const content = await readFile(outFile, 'utf8');
|
|
28
|
+
const registry = JSON.parse(content) as FlowRegistry;
|
|
29
|
+
|
|
30
|
+
expect(registry.schemaVersion).toBe('1');
|
|
31
|
+
expect(registry.flows).toHaveLength(0);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('builds registry from JSON flow files', async () => {
|
|
35
|
+
const flowsDir = join(tmpDir, 'flows');
|
|
36
|
+
await mkdir(flowsDir, { recursive: true });
|
|
37
|
+
|
|
38
|
+
const flowJson = {
|
|
39
|
+
id: 'test-flow',
|
|
40
|
+
name: 'Test Flow',
|
|
41
|
+
steps: [
|
|
42
|
+
{
|
|
43
|
+
type: 'flow-control',
|
|
44
|
+
control: 'succeed',
|
|
45
|
+
name: 'End',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
await writeFile(join(flowsDir, 'test-flow.json'), JSON.stringify(flowJson));
|
|
51
|
+
|
|
52
|
+
const outFile = await runBuildFlows({ projectRoot: tmpDir });
|
|
53
|
+
const content = await readFile(outFile, 'utf8');
|
|
54
|
+
const registry = JSON.parse(content) as FlowRegistry;
|
|
55
|
+
|
|
56
|
+
expect(registry.flows).toHaveLength(1);
|
|
57
|
+
expect(registry.flows[0]?.id).toBe('test-flow');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('detects ID collision between .ts and .json files', async () => {
|
|
61
|
+
const flowsDir = join(tmpDir, 'flows');
|
|
62
|
+
await mkdir(flowsDir, { recursive: true });
|
|
63
|
+
|
|
64
|
+
// Create both .ts and .json with same ID
|
|
65
|
+
const flowConfig = {
|
|
66
|
+
id: 'collision-flow',
|
|
67
|
+
name: 'Collision Flow',
|
|
68
|
+
steps: [
|
|
69
|
+
{
|
|
70
|
+
type: 'flow-control',
|
|
71
|
+
control: 'succeed',
|
|
72
|
+
name: 'End',
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Write as JSON (which we can do directly)
|
|
78
|
+
await writeFile(join(flowsDir, 'collision-flow.json'), JSON.stringify(flowConfig));
|
|
79
|
+
|
|
80
|
+
// For .ts, we'd need actual TypeScript but that requires tsx execution
|
|
81
|
+
// Skip this test if we can't write valid .ts files that will parse correctly
|
|
82
|
+
// This is covered by the loadModuleExports tests in isolation
|
|
83
|
+
|
|
84
|
+
expect(await readFile(join(flowsDir, 'collision-flow.json'), 'utf8')).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('validates flow config against schema', async () => {
|
|
88
|
+
const flowsDir = join(tmpDir, 'flows');
|
|
89
|
+
await mkdir(flowsDir, { recursive: true });
|
|
90
|
+
|
|
91
|
+
// Invalid flow: missing eventId namespace
|
|
92
|
+
const invalidFlow = {
|
|
93
|
+
id: 'bad-event-flow',
|
|
94
|
+
name: 'Bad Event',
|
|
95
|
+
steps: [
|
|
96
|
+
{
|
|
97
|
+
type: 'domain-event',
|
|
98
|
+
name: 'BadEvent',
|
|
99
|
+
eventId: 'nonamespace',
|
|
100
|
+
payload: {},
|
|
101
|
+
version: 1,
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
await writeFile(join(flowsDir, 'bad-flow.json'), JSON.stringify(invalidFlow));
|
|
107
|
+
|
|
108
|
+
await expect(runBuildFlows({ projectRoot: tmpDir }))
|
|
109
|
+
.rejects
|
|
110
|
+
.toThrow();
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it('uses custom output file path', async () => {
|
|
114
|
+
const flowsDir = join(tmpDir, 'flows');
|
|
115
|
+
const customOut = join(tmpDir, 'custom', 'registry.json');
|
|
116
|
+
await mkdir(flowsDir, { recursive: true });
|
|
117
|
+
|
|
118
|
+
const outFile = await runBuildFlows({ projectRoot: tmpDir, outFile: customOut });
|
|
119
|
+
|
|
120
|
+
expect(outFile).toBe(customOut);
|
|
121
|
+
const content = await readFile(customOut, 'utf8');
|
|
122
|
+
expect(JSON.parse(content)).toHaveProperty('schemaVersion');
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('includes flow trigger in registry', async () => {
|
|
126
|
+
const flowsDir = join(tmpDir, 'flows');
|
|
127
|
+
await mkdir(flowsDir, { recursive: true });
|
|
128
|
+
|
|
129
|
+
const flowWithTrigger = {
|
|
130
|
+
id: 'triggered-flow',
|
|
131
|
+
name: 'Triggered Flow',
|
|
132
|
+
steps: [
|
|
133
|
+
{
|
|
134
|
+
type: 'flow-control',
|
|
135
|
+
control: 'succeed',
|
|
136
|
+
name: 'End',
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
trigger: {
|
|
140
|
+
type: 'event',
|
|
141
|
+
eventId: 'domain.event',
|
|
142
|
+
semverRange: '^1.0.0',
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
await writeFile(join(flowsDir, 'triggered.json'), JSON.stringify(flowWithTrigger));
|
|
147
|
+
|
|
148
|
+
const outFile = await runBuildFlows({ projectRoot: tmpDir });
|
|
149
|
+
const content = await readFile(outFile, 'utf8');
|
|
150
|
+
const registry = JSON.parse(content) as FlowRegistry;
|
|
151
|
+
|
|
152
|
+
expect(registry.flows[0]?.trigger?.eventId).toBe('domain.event');
|
|
153
|
+
});
|
|
154
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { mkdtemp, writeFile, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { rmSync } from 'node:fs';
|
|
7
|
+
import { runCheckHashes } from '../../commands/check-hashes.js';
|
|
8
|
+
|
|
9
|
+
describe('check-hashes', () => {
|
|
10
|
+
let tempDir: string;
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
tempDir = await mkdtemp(join(tmpdir(), 'tib-check-hashes-'));
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should return ok: true when all files match manifest', async () => {
|
|
21
|
+
// Set up directory structure
|
|
22
|
+
await mkdir(join(tempDir, '.tib'), { recursive: true });
|
|
23
|
+
await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
|
|
24
|
+
|
|
25
|
+
// Create a test file
|
|
26
|
+
const testContent = 'export const MyStack = {};';
|
|
27
|
+
const testPath = join(tempDir, 'infra', 'modules', 'test.ts');
|
|
28
|
+
await writeFile(testPath, testContent);
|
|
29
|
+
|
|
30
|
+
// Create manifest with correct hash
|
|
31
|
+
const hash = createHash('sha256').update(testContent).digest('hex');
|
|
32
|
+
const manifest = {
|
|
33
|
+
version: '1' as const,
|
|
34
|
+
generatedAt: new Date().toISOString(),
|
|
35
|
+
files: {
|
|
36
|
+
'infra/modules/test.ts': hash,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
await writeFile(join(tempDir, '.tib', 'modules-hashes.json'), JSON.stringify(manifest));
|
|
40
|
+
|
|
41
|
+
const result = await runCheckHashes({ projectRoot: tempDir });
|
|
42
|
+
expect(result.ok).toBe(true);
|
|
43
|
+
expect(result.drifted).toHaveLength(0);
|
|
44
|
+
expect(result.missing).toHaveLength(0);
|
|
45
|
+
expect(result.unexpected).toHaveLength(0);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should detect modified files', async () => {
|
|
49
|
+
await mkdir(join(tempDir, '.tib'), { recursive: true });
|
|
50
|
+
await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
|
|
51
|
+
|
|
52
|
+
const originalContent = 'export const MyStack = {};';
|
|
53
|
+
const testPath = join(tempDir, 'infra', 'modules', 'test.ts');
|
|
54
|
+
await writeFile(testPath, originalContent);
|
|
55
|
+
|
|
56
|
+
// Create manifest with original hash
|
|
57
|
+
const originalHash = createHash('sha256').update(originalContent).digest('hex');
|
|
58
|
+
const manifest = {
|
|
59
|
+
version: '1' as const,
|
|
60
|
+
generatedAt: new Date().toISOString(),
|
|
61
|
+
files: {
|
|
62
|
+
'infra/modules/test.ts': originalHash,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
await writeFile(join(tempDir, '.tib', 'modules-hashes.json'), JSON.stringify(manifest));
|
|
66
|
+
|
|
67
|
+
// Modify the file
|
|
68
|
+
const modifiedContent = 'export const MyStack = { foo: "bar" };';
|
|
69
|
+
await writeFile(testPath, modifiedContent);
|
|
70
|
+
|
|
71
|
+
const result = await runCheckHashes({ projectRoot: tempDir });
|
|
72
|
+
expect(result.ok).toBe(false);
|
|
73
|
+
expect(result.drifted).toHaveLength(1);
|
|
74
|
+
expect(result.drifted[0]?.path).toBe('infra/modules/test.ts');
|
|
75
|
+
expect(result.drifted[0]?.expectedHash).toBe(originalHash);
|
|
76
|
+
expect(result.drifted[0]?.actualHash).toBe(createHash('sha256').update(modifiedContent).digest('hex'));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should detect deleted files', async () => {
|
|
80
|
+
await mkdir(join(tempDir, '.tib'), { recursive: true });
|
|
81
|
+
await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
|
|
82
|
+
|
|
83
|
+
const content = 'export const MyStack = {};';
|
|
84
|
+
const testPath = join(tempDir, 'infra', 'modules', 'test.ts');
|
|
85
|
+
await writeFile(testPath, content);
|
|
86
|
+
|
|
87
|
+
// Create manifest
|
|
88
|
+
const hash = createHash('sha256').update(content).digest('hex');
|
|
89
|
+
const manifest = {
|
|
90
|
+
version: '1' as const,
|
|
91
|
+
generatedAt: new Date().toISOString(),
|
|
92
|
+
files: {
|
|
93
|
+
'infra/modules/test.ts': hash,
|
|
94
|
+
'infra/modules/deleted.ts': 'somehash',
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
await writeFile(join(tempDir, '.tib', 'modules-hashes.json'), JSON.stringify(manifest));
|
|
98
|
+
|
|
99
|
+
const result = await runCheckHashes({ projectRoot: tempDir });
|
|
100
|
+
expect(result.ok).toBe(false);
|
|
101
|
+
expect(result.missing).toHaveLength(1);
|
|
102
|
+
expect(result.missing[0]).toBe('infra/modules/deleted.ts');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should detect unexpected files', async () => {
|
|
106
|
+
await mkdir(join(tempDir, '.tib'), { recursive: true });
|
|
107
|
+
await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
|
|
108
|
+
|
|
109
|
+
const content = 'export const MyStack = {};';
|
|
110
|
+
const testPath = join(tempDir, 'infra', 'modules', 'test.ts');
|
|
111
|
+
await writeFile(testPath, content);
|
|
112
|
+
|
|
113
|
+
// Create an extra file
|
|
114
|
+
const extraPath = join(tempDir, 'infra', 'modules', 'extra.ts');
|
|
115
|
+
await writeFile(extraPath, 'extra content');
|
|
116
|
+
|
|
117
|
+
// Create manifest with only test.ts
|
|
118
|
+
const hash = createHash('sha256').update(content).digest('hex');
|
|
119
|
+
const manifest = {
|
|
120
|
+
version: '1' as const,
|
|
121
|
+
generatedAt: new Date().toISOString(),
|
|
122
|
+
files: {
|
|
123
|
+
'infra/modules/test.ts': hash,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
await writeFile(join(tempDir, '.tib', 'modules-hashes.json'), JSON.stringify(manifest));
|
|
127
|
+
|
|
128
|
+
const result = await runCheckHashes({ projectRoot: tempDir });
|
|
129
|
+
expect(result.ok).toBe(false);
|
|
130
|
+
expect(result.unexpected).toHaveLength(1);
|
|
131
|
+
expect(result.unexpected[0]).toBe('infra/modules/extra.ts');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should throw with helpful message when manifest is missing', async () => {
|
|
135
|
+
await mkdir(join(tempDir, '.tib'), { recursive: true });
|
|
136
|
+
await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
|
|
137
|
+
|
|
138
|
+
await expect(runCheckHashes({ projectRoot: tempDir })).rejects.toThrow(
|
|
139
|
+
/cannot read.*modules-hashes\.json/
|
|
140
|
+
);
|
|
141
|
+
});
|
|
142
|
+
});
|