@mettlecast/domain-cli 0.2.59 → 0.2.61
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 +1 -1
- package/dist/builder/build-registry.js +129 -49
- package/dist/builder/build-types.d.ts +1 -1
- package/dist/builder/load-module.d.ts +1 -1
- package/dist/builder/load-module.js +3 -3
- package/dist/cli.js +3 -3
- package/dist/commands/add-api.js +2 -2
- package/dist/commands/add-domain.js +4 -4
- package/dist/commands/add-fixture-factory.js +5 -6
- package/dist/commands/build-catalog.d.ts +8 -24
- package/dist/commands/build-catalog.js +12 -23
- package/dist/commands/build-flows.js +1 -1
- package/dist/commands/build.js +7 -6
- package/dist/commands/check-hashes.js +2 -2
- package/dist/commands/create-project.js +1 -1
- package/dist/commands/dev.js +1 -1
- package/dist/commands/doctor.d.ts +5 -7
- package/dist/commands/doctor.js +110 -202
- package/dist/commands/explain.js +13 -13
- package/dist/commands/generate-openapi.d.ts +10 -1
- package/dist/commands/generate-openapi.js +19 -33
- package/dist/commands/power-tune.js +2 -2
- package/dist/commands/show-dns.d.ts +1 -1
- package/dist/commands/show-dns.js +5 -5
- package/dist/commands/show.d.ts +2 -3
- package/dist/commands/show.js +0 -2
- package/dist/commands/test.js +0 -1
- package/dist/commands/upgrade-backend.js +3 -3
- package/dist/commands/upgrade.js +17 -11
- package/dist/commands/validate.js +144 -39
- package/dist/server/api-server.d.ts +1 -1
- package/dist/server/mount-routes.d.ts +11 -2
- package/dist/server/mount-routes.js +20 -8
- package/dist/templates/api-skeleton.d.ts +5 -0
- package/dist/templates/api-skeleton.js +28 -27
- package/dist/templates/claude-md.js +1 -1
- package/dist/templates/patterns/api/create-with-event.d.ts +4 -0
- package/dist/templates/patterns/api/create-with-event.js +38 -32
- package/dist/templates/patterns/api/idempotent-mutation.d.ts +4 -0
- package/dist/templates/patterns/api/idempotent-mutation.js +47 -41
- package/dist/templates/patterns/api/paginated-list.d.ts +4 -0
- package/dist/templates/patterns/api/paginated-list.js +30 -24
- package/dist/templates/patterns/api/simple-crud.d.ts +4 -0
- package/dist/templates/patterns/api/simple-crud.js +46 -35
- package/dist/templates/patterns/api/streaming-list.d.ts +4 -0
- package/dist/templates/patterns/api/streaming-list.js +46 -41
- package/dist/templates/patterns/api/system-admin.d.ts +4 -0
- package/dist/templates/patterns/api/system-admin.js +59 -52
- package/dist/templates/patterns/api/webhook-receiver-style.d.ts +4 -0
- package/dist/templates/patterns/api/webhook-receiver-style.js +43 -35
- package/dist/types.d.ts +100 -0
- package/dist/types.js +1 -0
- package/dist/utils/file-helpers.d.ts +0 -2
- package/dist/utils/file-helpers.js +2 -3
- package/dist/utils/header-inject.js +2 -2
- package/dist/utils/install-file.d.ts +1 -1
- package/dist/utils/install-file.js +1 -1
- package/dist/utils/manifest.js +1 -2
- package/dist/utils/scaffold-config.d.ts +8 -2
- package/dist/utils/scaffold-config.js +3 -1
- package/package.json +1 -1
- package/src/__tests__/build-registry.test.ts +43 -20
- package/src/__tests__/build-types.test.ts +4 -7
- package/src/__tests__/builder/walkDomainDir.test.ts +19 -21
- package/src/__tests__/commands/add-api.test.ts +12 -10
- package/src/__tests__/commands/add-domain.test.ts +8 -5
- package/src/__tests__/commands/check-hashes.test.ts +9 -9
- package/src/__tests__/commands/create-project.test.ts +5 -5
- package/src/__tests__/commands/dev.test.ts +0 -1
- package/src/__tests__/commands/upgrade.test.ts +7 -7
- package/src/__tests__/doctor.test.ts +60 -67
- package/src/__tests__/mount-routes.test.ts +64 -23
- package/src/__tests__/package-freshness.test.ts +94 -0
- package/src/__tests__/scaffold-src/part-a-layout.test.ts +10 -10
- package/src/__tests__/scripts/package-scaffold.test.ts +5 -5
- package/src/__tests__/smoke/scaffold.test.ts +13 -15
- package/src/__tests__/utils/install-file.test.ts +2 -2
- package/src/__tests__/utils/manifest.test.ts +2 -2
- package/src/__tests__/validate.test.ts +570 -1
- package/src/builder/build-registry.ts +154 -59
- package/src/builder/build-types.ts +1 -1
- package/src/builder/load-module.ts +3 -3
- package/src/cli.ts +4 -4
- package/src/commands/add-api.ts +2 -2
- package/src/commands/add-domain.ts +4 -4
- package/src/commands/add-fixture-factory.ts +5 -6
- package/src/commands/build-catalog.ts +18 -40
- package/src/commands/build-flows.ts +1 -1
- package/src/commands/build.ts +8 -7
- package/src/commands/check-hashes.ts +2 -2
- package/src/commands/create-project.ts +1 -1
- package/src/commands/dev.ts +1 -1
- package/src/commands/doctor.ts +120 -218
- package/src/commands/explain.ts +13 -13
- package/src/commands/generate-openapi.ts +30 -52
- package/src/commands/power-tune.ts +2 -2
- package/src/commands/show-dns.ts +5 -5
- package/src/commands/show.ts +2 -5
- package/src/commands/test.ts +0 -1
- package/src/commands/upgrade-backend.ts +3 -3
- package/src/commands/upgrade.ts +16 -10
- package/src/commands/validate.ts +180 -40
- package/src/server/api-server.ts +1 -1
- package/src/server/mount-routes.ts +21 -10
- package/src/templates/api-skeleton.ts +29 -28
- package/src/templates/claude-md.ts +1 -1
- package/src/templates/patterns/api/create-with-event.ts +39 -33
- package/src/templates/patterns/api/idempotent-mutation.ts +48 -42
- package/src/templates/patterns/api/paginated-list.ts +31 -25
- package/src/templates/patterns/api/simple-crud.ts +47 -36
- package/src/templates/patterns/api/streaming-list.ts +47 -42
- package/src/templates/patterns/api/system-admin.ts +60 -53
- package/src/templates/patterns/api/webhook-receiver-style.ts +48 -40
- package/src/types.ts +128 -0
- package/src/utils/file-helpers.ts +2 -5
- package/src/utils/header-inject.ts +2 -2
- package/src/utils/install-file.ts +1 -1
- package/src/utils/manifest.ts +1 -2
- package/src/utils/scaffold-config.ts +12 -3
|
@@ -24,7 +24,6 @@ describe('buildRegistry', () => {
|
|
|
24
24
|
it('throws if domain.config.ts is missing', async () => {
|
|
25
25
|
mockWalkDomainDir.mockResolvedValue({
|
|
26
26
|
domain: undefined,
|
|
27
|
-
apis: [],
|
|
28
27
|
webhooks: [],
|
|
29
28
|
subscribers: [],
|
|
30
29
|
actions: [],
|
|
@@ -40,7 +39,6 @@ describe('buildRegistry', () => {
|
|
|
40
39
|
it('throws if domain.config.ts has no domain export', async () => {
|
|
41
40
|
mockWalkDomainDir.mockResolvedValue({
|
|
42
41
|
domain: '/fake/domains/billing/domain.config.ts',
|
|
43
|
-
apis: [],
|
|
44
42
|
webhooks: [],
|
|
45
43
|
subscribers: [],
|
|
46
44
|
actions: [],
|
|
@@ -54,13 +52,15 @@ describe('buildRegistry', () => {
|
|
|
54
52
|
await expect(buildRegistry(DOMAIN_ROOT)).rejects.toThrow("no 'domain' export found");
|
|
55
53
|
});
|
|
56
54
|
|
|
57
|
-
it('builds a registry with domain + one
|
|
55
|
+
it('builds a registry with domain + one API-exposed action', async () => {
|
|
56
|
+
// Issue #4689: defineApi was removed; the canonical HTTP endpoint
|
|
57
|
+
// surface is `defineAction({ exposure: { type: 'api', ... } })`.
|
|
58
|
+
// The action is recorded under `registry.actions` with API exposure metadata.
|
|
58
59
|
mockWalkDomainDir.mockResolvedValue({
|
|
59
60
|
domain: '/fake/domains/billing/domain.config.ts',
|
|
60
|
-
apis: ['/fake/domains/billing/api/get-invoice.ts'],
|
|
61
61
|
webhooks: [],
|
|
62
62
|
subscribers: [],
|
|
63
|
-
actions: [],
|
|
63
|
+
actions: ['/fake/domains/billing/actions/get-invoice.ts'],
|
|
64
64
|
schedules: [],
|
|
65
65
|
jobs: [],
|
|
66
66
|
integrations: [],
|
|
@@ -72,7 +72,20 @@ describe('buildRegistry', () => {
|
|
|
72
72
|
{ _kind: 'domain', _exportName: 'default', id: 'billing', name: 'Billing', tenancy: 'required' },
|
|
73
73
|
])
|
|
74
74
|
.mockResolvedValueOnce([
|
|
75
|
-
{
|
|
75
|
+
{
|
|
76
|
+
_kind: 'action',
|
|
77
|
+
_exportName: 'getInvoice',
|
|
78
|
+
id: 'get-invoice',
|
|
79
|
+
backendAccess: 'domain',
|
|
80
|
+
exposure: {
|
|
81
|
+
type: 'api',
|
|
82
|
+
path: '/v1/tenants/{tenantId}/billing/invoices/{id}',
|
|
83
|
+
method: 'GET',
|
|
84
|
+
auth: 'required',
|
|
85
|
+
tenancy: 'required',
|
|
86
|
+
},
|
|
87
|
+
idempotent: true,
|
|
88
|
+
},
|
|
76
89
|
]);
|
|
77
90
|
|
|
78
91
|
const { registry, warnings } = await buildRegistry(DOMAIN_ROOT);
|
|
@@ -80,21 +93,18 @@ describe('buildRegistry', () => {
|
|
|
80
93
|
expect(warnings).toHaveLength(0);
|
|
81
94
|
expect(registry.schemaVersion).toBe('1');
|
|
82
95
|
expect(registry.domain.id).toBe('billing');
|
|
83
|
-
expect(registry.
|
|
84
|
-
expect(registry.
|
|
85
|
-
expect(registry.
|
|
86
|
-
expect(registry.
|
|
87
|
-
expect(registry.apis[0]!.handlerFile.replaceAll('\\', '/')).toBe('api/get-invoice.ts');
|
|
88
|
-
expect(registry.apis[0]!.outboundAccess).toBe('internal');
|
|
96
|
+
expect(registry.actions).toHaveLength(1);
|
|
97
|
+
expect(registry.actions[0]!.id).toBe('get-invoice');
|
|
98
|
+
expect(registry.actions[0]!.backendAccess).toBe('domain');
|
|
99
|
+
expect(registry.actions[0]!.exposure.type).toBe('api');
|
|
89
100
|
});
|
|
90
101
|
|
|
91
|
-
it('preserves explicit outboundAccess on
|
|
102
|
+
it('preserves explicit outboundAccess on actions', async () => {
|
|
92
103
|
mockWalkDomainDir.mockResolvedValue({
|
|
93
104
|
domain: '/fake/domains/billing/domain.config.ts',
|
|
94
|
-
apis: ['/fake/domains/billing/api/sync-invoice.ts'],
|
|
95
105
|
webhooks: [],
|
|
96
106
|
subscribers: [],
|
|
97
|
-
actions: [],
|
|
107
|
+
actions: ['/fake/domains/billing/actions/sync-invoice.ts'],
|
|
98
108
|
schedules: [],
|
|
99
109
|
jobs: [],
|
|
100
110
|
integrations: [],
|
|
@@ -106,21 +116,34 @@ describe('buildRegistry', () => {
|
|
|
106
116
|
{ _kind: 'domain', _exportName: 'default', id: 'billing', name: 'Billing', tenancy: 'required' },
|
|
107
117
|
])
|
|
108
118
|
.mockResolvedValueOnce([
|
|
109
|
-
{
|
|
119
|
+
{
|
|
120
|
+
_kind: 'action',
|
|
121
|
+
_exportName: 'syncInvoice',
|
|
122
|
+
id: 'sync-invoice',
|
|
123
|
+
backendAccess: 'domain',
|
|
124
|
+
exposure: {
|
|
125
|
+
type: 'api',
|
|
126
|
+
path: '/v1/tenants/{tenantId}/billing/invoices/sync',
|
|
127
|
+
method: 'POST',
|
|
128
|
+
auth: 'required',
|
|
129
|
+
tenancy: 'required',
|
|
130
|
+
},
|
|
131
|
+
outboundAccess: 'internet',
|
|
132
|
+
idempotent: false,
|
|
133
|
+
},
|
|
110
134
|
]);
|
|
111
135
|
|
|
112
136
|
const { registry } = await buildRegistry(DOMAIN_ROOT);
|
|
113
137
|
|
|
114
|
-
expect(registry.
|
|
138
|
+
expect(registry.actions[0]!.outboundAccess).toBe('internet');
|
|
115
139
|
});
|
|
116
140
|
|
|
117
141
|
it('collects warnings when a file fails to load', async () => {
|
|
118
142
|
mockWalkDomainDir.mockResolvedValue({
|
|
119
143
|
domain: '/fake/domains/billing/domain.config.ts',
|
|
120
|
-
apis: ['/fake/domains/billing/api/broken.ts'],
|
|
121
144
|
webhooks: [],
|
|
122
145
|
subscribers: [],
|
|
123
|
-
actions: [],
|
|
146
|
+
actions: ['/fake/domains/billing/actions/broken.ts'],
|
|
124
147
|
schedules: [],
|
|
125
148
|
jobs: [],
|
|
126
149
|
integrations: [],
|
|
@@ -137,6 +160,6 @@ describe('buildRegistry', () => {
|
|
|
137
160
|
|
|
138
161
|
expect(warnings).toHaveLength(1);
|
|
139
162
|
expect(warnings[0]).toContain('tsx error');
|
|
140
|
-
expect(registry.
|
|
163
|
+
expect(registry.actions).toHaveLength(0);
|
|
141
164
|
});
|
|
142
165
|
});
|
|
@@ -8,7 +8,6 @@ describe('buildActionsTypes', () => {
|
|
|
8
8
|
schemaVersion: '1',
|
|
9
9
|
domainRoot: '/test/auth',
|
|
10
10
|
domain: { id: 'auth', kind: 'domain', name: 'Auth', tenancy: 'required' },
|
|
11
|
-
apis: [],
|
|
12
11
|
webhooks: [],
|
|
13
12
|
subscribers: [],
|
|
14
13
|
schedules: [],
|
|
@@ -18,7 +17,8 @@ describe('buildActionsTypes', () => {
|
|
|
18
17
|
id: 'verify-token',
|
|
19
18
|
kind: 'action',
|
|
20
19
|
handlerFile: 'verify-token.ts',
|
|
21
|
-
|
|
20
|
+
backendAccess: 'domain',
|
|
21
|
+
exposure: { type: 'internal' },
|
|
22
22
|
idempotent: false,
|
|
23
23
|
},
|
|
24
24
|
],
|
|
@@ -39,13 +39,12 @@ describe('buildActionsTypes', () => {
|
|
|
39
39
|
schemaVersion: '1',
|
|
40
40
|
domainRoot: '/test/auth',
|
|
41
41
|
domain: { id: 'auth', kind: 'domain', name: 'Auth', tenancy: 'required' },
|
|
42
|
-
apis: [],
|
|
43
42
|
webhooks: [],
|
|
44
43
|
subscribers: [],
|
|
45
44
|
schedules: [],
|
|
46
45
|
jobs: [],
|
|
47
46
|
actions: [
|
|
48
|
-
{ id: 'verify', kind: 'action', handlerFile: 'verify.ts',
|
|
47
|
+
{ id: 'verify', kind: 'action', handlerFile: 'verify.ts', backendAccess: 'platform', exposure: { type: 'internal' }, idempotent: true },
|
|
49
48
|
],
|
|
50
49
|
integrations: [],
|
|
51
50
|
events: [],
|
|
@@ -55,13 +54,12 @@ describe('buildActionsTypes', () => {
|
|
|
55
54
|
schemaVersion: '1',
|
|
56
55
|
domainRoot: '/test/payments',
|
|
57
56
|
domain: { id: 'payments', kind: 'domain', name: 'Payments', tenancy: 'required' },
|
|
58
|
-
apis: [],
|
|
59
57
|
webhooks: [],
|
|
60
58
|
subscribers: [],
|
|
61
59
|
schedules: [],
|
|
62
60
|
jobs: [],
|
|
63
61
|
actions: [
|
|
64
|
-
{ id: 'charge', kind: 'action', handlerFile: 'charge.ts',
|
|
62
|
+
{ id: 'charge', kind: 'action', handlerFile: 'charge.ts', backendAccess: 'domain', exposure: { type: 'internal' }, idempotent: false },
|
|
65
63
|
],
|
|
66
64
|
integrations: [],
|
|
67
65
|
events: [],
|
|
@@ -78,7 +76,6 @@ describe('buildActionsTypes', () => {
|
|
|
78
76
|
schemaVersion: '1',
|
|
79
77
|
domainRoot: '/test',
|
|
80
78
|
domain: { id: 'test', kind: 'domain', name: 'Test', tenancy: 'none' },
|
|
81
|
-
apis: [],
|
|
82
79
|
webhooks: [],
|
|
83
80
|
subscribers: [],
|
|
84
81
|
schedules: [],
|
|
@@ -19,59 +19,59 @@ describe('walkDomainDir recursive walk', () => {
|
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
it('should find .ts files at root of
|
|
23
|
-
const
|
|
24
|
-
await mkdir(
|
|
25
|
-
await writeFile(join(
|
|
22
|
+
it('should find .ts files at root of actions directory', async () => {
|
|
23
|
+
const actionDir = join(tmpDir, 'actions');
|
|
24
|
+
await mkdir(actionDir, { recursive: true });
|
|
25
|
+
await writeFile(join(actionDir, 'simple.ts'), 'export const action = {}');
|
|
26
26
|
|
|
27
27
|
const paths = await walkDomainDir(tmpDir);
|
|
28
28
|
|
|
29
|
-
expect(paths.
|
|
29
|
+
expect(paths.actions).toContain(join(actionDir, 'simple.ts'));
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
it('should recursively find .ts files in nested
|
|
33
|
-
const nestedDir = join(tmpDir, '
|
|
32
|
+
it('should recursively find .ts files in nested action directories', async () => {
|
|
33
|
+
const nestedDir = join(tmpDir, 'actions', 'users', 'v1');
|
|
34
34
|
await mkdir(nestedDir, { recursive: true });
|
|
35
|
-
await writeFile(join(nestedDir, 'get.ts'), 'export const
|
|
35
|
+
await writeFile(join(nestedDir, 'get.ts'), 'export const action = {}');
|
|
36
36
|
|
|
37
37
|
const paths = await walkDomainDir(tmpDir);
|
|
38
38
|
|
|
39
|
-
expect(paths.
|
|
39
|
+
expect(paths.actions).toContain(join(nestedDir, 'get.ts'));
|
|
40
40
|
});
|
|
41
41
|
|
|
42
42
|
it('should skip __tests__ directories', async () => {
|
|
43
|
-
const testDir = join(tmpDir, '
|
|
43
|
+
const testDir = join(tmpDir, 'actions', '__tests__');
|
|
44
44
|
await mkdir(testDir, { recursive: true });
|
|
45
45
|
await writeFile(join(testDir, 'test.ts'), 'export const test = {}');
|
|
46
46
|
|
|
47
47
|
const paths = await walkDomainDir(tmpDir);
|
|
48
48
|
|
|
49
|
-
expect(paths.
|
|
49
|
+
expect(paths.actions).not.toContain(join(testDir, 'test.ts'));
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
it('should skip dist directories', async () => {
|
|
53
|
-
const distDir = join(tmpDir, '
|
|
53
|
+
const distDir = join(tmpDir, 'actions', 'dist');
|
|
54
54
|
await mkdir(distDir, { recursive: true });
|
|
55
55
|
await writeFile(join(distDir, 'bundle.ts'), 'export const bundle = {}');
|
|
56
56
|
|
|
57
57
|
const paths = await walkDomainDir(tmpDir);
|
|
58
58
|
|
|
59
|
-
expect(paths.
|
|
59
|
+
expect(paths.actions).not.toContain(join(distDir, 'bundle.ts'));
|
|
60
60
|
});
|
|
61
61
|
|
|
62
62
|
it('should skip node_modules directories', async () => {
|
|
63
|
-
const nodeModulesDir = join(tmpDir, '
|
|
63
|
+
const nodeModulesDir = join(tmpDir, 'actions', 'node_modules');
|
|
64
64
|
await mkdir(nodeModulesDir, { recursive: true });
|
|
65
65
|
await writeFile(join(nodeModulesDir, 'dep.ts'), 'export const dep = {}');
|
|
66
66
|
|
|
67
67
|
const paths = await walkDomainDir(tmpDir);
|
|
68
68
|
|
|
69
|
-
expect(paths.
|
|
69
|
+
expect(paths.actions).not.toContain(join(nodeModulesDir, 'dep.ts'));
|
|
70
70
|
});
|
|
71
71
|
|
|
72
72
|
it('should discover files in all domain primitive directories', async () => {
|
|
73
73
|
// Create files in all conventional directories
|
|
74
|
-
const dirs = ['
|
|
74
|
+
const dirs = ['webhooks', 'subscribers', 'actions', 'schedules', 'jobs', 'integrations'];
|
|
75
75
|
|
|
76
76
|
for (const dir of dirs) {
|
|
77
77
|
const dirPath = join(tmpDir, dir, 'nested', 'deep');
|
|
@@ -82,7 +82,6 @@ describe('walkDomainDir recursive walk', () => {
|
|
|
82
82
|
const paths = await walkDomainDir(tmpDir);
|
|
83
83
|
|
|
84
84
|
// Verify files are found in correct slots
|
|
85
|
-
expect(paths.apis.some(p => p.includes('api-handler.ts'))).toBe(true);
|
|
86
85
|
expect(paths.webhooks.some(p => p.includes('webhooks-handler.ts'))).toBe(true);
|
|
87
86
|
expect(paths.subscribers.some(p => p.includes('subscribers-handler.ts'))).toBe(true);
|
|
88
87
|
expect(paths.actions.some(p => p.includes('actions-handler.ts'))).toBe(true);
|
|
@@ -112,7 +111,6 @@ describe('walkDomainDir recursive walk', () => {
|
|
|
112
111
|
it('should return empty arrays for missing directories', async () => {
|
|
113
112
|
const paths = await walkDomainDir(tmpDir);
|
|
114
113
|
|
|
115
|
-
expect(paths.apis).toEqual([]);
|
|
116
114
|
expect(paths.webhooks).toEqual([]);
|
|
117
115
|
expect(paths.subscribers).toEqual([]);
|
|
118
116
|
expect(paths.actions).toEqual([]);
|
|
@@ -129,12 +127,12 @@ describe('walkDomainDir recursive walk', () => {
|
|
|
129
127
|
});
|
|
130
128
|
|
|
131
129
|
it('should handle deeply nested files', async () => {
|
|
132
|
-
const deepPath = join(tmpDir, '
|
|
130
|
+
const deepPath = join(tmpDir, 'actions', 'v1', 'users', 'admin', 'permissions');
|
|
133
131
|
await mkdir(deepPath, { recursive: true });
|
|
134
|
-
await writeFile(join(deepPath, 'check.ts'), 'export const
|
|
132
|
+
await writeFile(join(deepPath, 'check.ts'), 'export const action = {}');
|
|
135
133
|
|
|
136
134
|
const paths = await walkDomainDir(tmpDir);
|
|
137
135
|
|
|
138
|
-
expect(paths.
|
|
136
|
+
expect(paths.actions.some(p => p.includes('check.ts'))).toBe(true);
|
|
139
137
|
});
|
|
140
138
|
});
|
|
@@ -11,7 +11,7 @@ describe('add-api command', () => {
|
|
|
11
11
|
beforeEach(async () => {
|
|
12
12
|
tmpDir = await mkdtemp(join(tmpdir(), 'tib-add-api-test-'));
|
|
13
13
|
domainDir = join(tmpDir, 'demo');
|
|
14
|
-
await mkdir(join(domainDir, '
|
|
14
|
+
await mkdir(join(domainDir, 'actions'), { recursive: true });
|
|
15
15
|
await writeFile(
|
|
16
16
|
join(domainDir, 'domain.config.ts'),
|
|
17
17
|
`export default { id: 'demo', tenancy: 'required' };\n`,
|
|
@@ -34,8 +34,8 @@ describe('add-api command', () => {
|
|
|
34
34
|
domainsDir: tmpDir,
|
|
35
35
|
});
|
|
36
36
|
|
|
37
|
-
const apiFile = join(domainDir, '
|
|
38
|
-
const fixtureFile = join(domainDir, '
|
|
37
|
+
const apiFile = join(domainDir, 'actions', 'ping-thing.ts');
|
|
38
|
+
const fixtureFile = join(domainDir, 'actions', '__tests__', 'ping-thing.fixture.json');
|
|
39
39
|
|
|
40
40
|
const apiContent = await readFile(apiFile, 'utf8');
|
|
41
41
|
const fixtureContent = await readFile(fixtureFile, 'utf8');
|
|
@@ -45,9 +45,11 @@ describe('add-api command', () => {
|
|
|
45
45
|
const parsed = JSON.parse(fixtureContent) as { event: { body: string } };
|
|
46
46
|
expect(parsed.event.body).toBe('{}');
|
|
47
47
|
|
|
48
|
-
// The API file must contain .default() on input
|
|
48
|
+
// The API file must contain .default() on input. Output uses a variable.
|
|
49
49
|
expect(apiContent).toMatch(/input:\s*z\.object\(\{\}\)\.default\(\{\}\)/);
|
|
50
|
-
|
|
50
|
+
// The output schema is assigned to a variable then referenced via `output: outputSchema`.
|
|
51
|
+
expect(apiContent).toMatch(/output:\s*outputSchema/);
|
|
52
|
+
expect(apiContent).toMatch(/const outputSchema = z\.object\(\{\s*ok:\s*z\.boolean\(\)\s*\}\)\.default\(\{\s*ok:\s*true\s*\}\)/);
|
|
51
53
|
});
|
|
52
54
|
|
|
53
55
|
it('scaffolds a simple-crud pattern API with .default() on schemas', async () => {
|
|
@@ -59,8 +61,8 @@ describe('add-api command', () => {
|
|
|
59
61
|
domainsDir: tmpDir,
|
|
60
62
|
});
|
|
61
63
|
|
|
62
|
-
const apiFile = join(domainDir, '
|
|
63
|
-
const fixtureFile = join(domainDir, '
|
|
64
|
+
const apiFile = join(domainDir, 'actions', 'widget.ts');
|
|
65
|
+
const fixtureFile = join(domainDir, 'actions', '__tests__', 'widget.fixture.json');
|
|
64
66
|
|
|
65
67
|
const apiContent = await readFile(apiFile, 'utf8');
|
|
66
68
|
const fixtureContent = await readFile(fixtureFile, 'utf8');
|
|
@@ -83,8 +85,8 @@ describe('add-api command', () => {
|
|
|
83
85
|
domainsDir: tmpDir,
|
|
84
86
|
});
|
|
85
87
|
|
|
86
|
-
const apiFile = join(domainDir, '
|
|
87
|
-
const fixtureFile = join(domainDir, '
|
|
88
|
+
const apiFile = join(domainDir, 'actions', 'order.ts');
|
|
89
|
+
const fixtureFile = join(domainDir, 'actions', '__tests__', 'order.fixture.json');
|
|
88
90
|
|
|
89
91
|
const apiContent = await readFile(apiFile, 'utf8');
|
|
90
92
|
const fixtureContent = await readFile(fixtureFile, 'utf8');
|
|
@@ -106,7 +108,7 @@ describe('add-api command', () => {
|
|
|
106
108
|
domainsDir: tmpDir,
|
|
107
109
|
});
|
|
108
110
|
|
|
109
|
-
const apiFile = join(domainDir, '
|
|
111
|
+
const apiFile = join(domainDir, 'actions', 'console.ts');
|
|
110
112
|
const apiContent = await readFile(apiFile, 'utf8');
|
|
111
113
|
|
|
112
114
|
// First API: input defaults to command: 'status'; output defaults to { ok: true }.
|
|
@@ -33,7 +33,7 @@ describe('add-domain command', () => {
|
|
|
33
33
|
|
|
34
34
|
// Verify directory structure
|
|
35
35
|
const domainDir = join(tmpDir, 'test-domain');
|
|
36
|
-
await stat(join(domainDir, '
|
|
36
|
+
await stat(join(domainDir, 'actions'));
|
|
37
37
|
await stat(join(domainDir, 'subscribers'));
|
|
38
38
|
await stat(join(domainDir, 'publishes'));
|
|
39
39
|
|
|
@@ -42,9 +42,10 @@ describe('add-domain command', () => {
|
|
|
42
42
|
expect(configContent).toContain(`id: 'test-domain'`);
|
|
43
43
|
expect(configContent).toContain(`tenancy: 'required'`);
|
|
44
44
|
|
|
45
|
-
const apiContent = await readFile(join(domainDir, '
|
|
45
|
+
const apiContent = await readFile(join(domainDir, 'actions', 'example.ts'), 'utf8');
|
|
46
46
|
expect(apiContent).toContain(`id: 'example'`);
|
|
47
|
-
expect(apiContent).toContain('
|
|
47
|
+
expect(apiContent).toContain('defineAction');
|
|
48
|
+
expect(apiContent).toContain("type: 'api'");
|
|
48
49
|
|
|
49
50
|
const eventsContent = await readFile(join(domainDir, 'publishes', 'events.ts'), 'utf8');
|
|
50
51
|
expect(eventsContent).toContain(`id: 'test-domain.example'`);
|
|
@@ -154,10 +155,12 @@ describe('add-domain command', () => {
|
|
|
154
155
|
const configContent = await readFile(join(tmpDir, 'system-domain', 'domain.config.ts'), 'utf8');
|
|
155
156
|
expect(configContent).toContain(`tenancy: 'system'`);
|
|
156
157
|
|
|
157
|
-
const apiContent = await readFile(join(tmpDir, 'system-domain', '
|
|
158
|
+
const apiContent = await readFile(join(tmpDir, 'system-domain', 'actions', 'example.ts'), 'utf8');
|
|
158
159
|
expect(apiContent).toContain(`tenancy: 'system'`);
|
|
159
160
|
// For 'system' tenancy, path should not include /v1/tenants/{tenantId}
|
|
160
161
|
expect(apiContent).toContain(`path: '/v1/system-domain/example'`);
|
|
162
|
+
// System tenancy should default to service-to-service auth.
|
|
163
|
+
expect(apiContent).toContain(`auth: 'service'`);
|
|
161
164
|
});
|
|
162
165
|
|
|
163
166
|
it('should use correct path structure for required tenancy', async () => {
|
|
@@ -167,7 +170,7 @@ describe('add-domain command', () => {
|
|
|
167
170
|
domainsDir: tmpDir,
|
|
168
171
|
});
|
|
169
172
|
|
|
170
|
-
const apiContent = await readFile(join(tmpDir, 'tenant-domain', '
|
|
173
|
+
const apiContent = await readFile(join(tmpDir, 'tenant-domain', 'actions', 'example.ts'), 'utf8');
|
|
171
174
|
// For 'required' tenancy, path should include /v1/tenants/{tenantId}
|
|
172
175
|
expect(apiContent).toContain(`path: '/v1/tenants/{tenantId}/tenant-domain/example'`);
|
|
173
176
|
});
|
|
@@ -19,7 +19,7 @@ describe('check-hashes', () => {
|
|
|
19
19
|
|
|
20
20
|
it('should return ok: true when all files match manifest', async () => {
|
|
21
21
|
// Set up directory structure
|
|
22
|
-
await mkdir(join(tempDir, '.
|
|
22
|
+
await mkdir(join(tempDir, '.mc'), { recursive: true });
|
|
23
23
|
await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
|
|
24
24
|
|
|
25
25
|
// Create a test file
|
|
@@ -36,7 +36,7 @@ describe('check-hashes', () => {
|
|
|
36
36
|
'infra/modules/test.ts': hash,
|
|
37
37
|
},
|
|
38
38
|
};
|
|
39
|
-
await writeFile(join(tempDir, '.
|
|
39
|
+
await writeFile(join(tempDir, '.mc', 'modules-hashes.json'), JSON.stringify(manifest));
|
|
40
40
|
|
|
41
41
|
const result = await runCheckHashes({ projectRoot: tempDir });
|
|
42
42
|
expect(result.ok).toBe(true);
|
|
@@ -46,7 +46,7 @@ describe('check-hashes', () => {
|
|
|
46
46
|
});
|
|
47
47
|
|
|
48
48
|
it('should detect modified files', async () => {
|
|
49
|
-
await mkdir(join(tempDir, '.
|
|
49
|
+
await mkdir(join(tempDir, '.mc'), { recursive: true });
|
|
50
50
|
await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
|
|
51
51
|
|
|
52
52
|
const originalContent = 'export const MyStack = {};';
|
|
@@ -62,7 +62,7 @@ describe('check-hashes', () => {
|
|
|
62
62
|
'infra/modules/test.ts': originalHash,
|
|
63
63
|
},
|
|
64
64
|
};
|
|
65
|
-
await writeFile(join(tempDir, '.
|
|
65
|
+
await writeFile(join(tempDir, '.mc', 'modules-hashes.json'), JSON.stringify(manifest));
|
|
66
66
|
|
|
67
67
|
// Modify the file
|
|
68
68
|
const modifiedContent = 'export const MyStack = { foo: "bar" };';
|
|
@@ -77,7 +77,7 @@ describe('check-hashes', () => {
|
|
|
77
77
|
});
|
|
78
78
|
|
|
79
79
|
it('should detect deleted files', async () => {
|
|
80
|
-
await mkdir(join(tempDir, '.
|
|
80
|
+
await mkdir(join(tempDir, '.mc'), { recursive: true });
|
|
81
81
|
await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
|
|
82
82
|
|
|
83
83
|
const content = 'export const MyStack = {};';
|
|
@@ -94,7 +94,7 @@ describe('check-hashes', () => {
|
|
|
94
94
|
'infra/modules/deleted.ts': 'somehash',
|
|
95
95
|
},
|
|
96
96
|
};
|
|
97
|
-
await writeFile(join(tempDir, '.
|
|
97
|
+
await writeFile(join(tempDir, '.mc', 'modules-hashes.json'), JSON.stringify(manifest));
|
|
98
98
|
|
|
99
99
|
const result = await runCheckHashes({ projectRoot: tempDir });
|
|
100
100
|
expect(result.ok).toBe(false);
|
|
@@ -103,7 +103,7 @@ describe('check-hashes', () => {
|
|
|
103
103
|
});
|
|
104
104
|
|
|
105
105
|
it('should detect unexpected files', async () => {
|
|
106
|
-
await mkdir(join(tempDir, '.
|
|
106
|
+
await mkdir(join(tempDir, '.mc'), { recursive: true });
|
|
107
107
|
await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
|
|
108
108
|
|
|
109
109
|
const content = 'export const MyStack = {};';
|
|
@@ -123,7 +123,7 @@ describe('check-hashes', () => {
|
|
|
123
123
|
'infra/modules/test.ts': hash,
|
|
124
124
|
},
|
|
125
125
|
};
|
|
126
|
-
await writeFile(join(tempDir, '.
|
|
126
|
+
await writeFile(join(tempDir, '.mc', 'modules-hashes.json'), JSON.stringify(manifest));
|
|
127
127
|
|
|
128
128
|
const result = await runCheckHashes({ projectRoot: tempDir });
|
|
129
129
|
expect(result.ok).toBe(false);
|
|
@@ -132,7 +132,7 @@ describe('check-hashes', () => {
|
|
|
132
132
|
});
|
|
133
133
|
|
|
134
134
|
it('should throw with helpful message when manifest is missing', async () => {
|
|
135
|
-
await mkdir(join(tempDir, '.
|
|
135
|
+
await mkdir(join(tempDir, '.mc'), { recursive: true });
|
|
136
136
|
await mkdir(join(tempDir, 'infra', 'modules'), { recursive: true });
|
|
137
137
|
|
|
138
138
|
await expect(runCheckHashes({ projectRoot: tempDir })).rejects.toThrow(
|
|
@@ -263,7 +263,7 @@ describe('runCreateProject', () => {
|
|
|
263
263
|
const coreTarball = await createTestTarball([
|
|
264
264
|
{
|
|
265
265
|
path: 'lib/domain-runtime.ts',
|
|
266
|
-
content: 'export const
|
|
266
|
+
content: 'export const defineAction = () => {};',
|
|
267
267
|
isTemplate: false,
|
|
268
268
|
},
|
|
269
269
|
{
|
|
@@ -285,14 +285,14 @@ describe('runCreateProject', () => {
|
|
|
285
285
|
|
|
286
286
|
const authTarball = await createTestTarball([
|
|
287
287
|
{
|
|
288
|
-
path: 'domains/auth/
|
|
289
|
-
content: 'import {
|
|
288
|
+
path: 'domains/auth/actions/login.ts',
|
|
289
|
+
content: 'import { defineAction } from "../../lib/domain-runtime";',
|
|
290
290
|
isTemplate: false,
|
|
291
291
|
},
|
|
292
292
|
{
|
|
293
293
|
path: 'MANIFEST.json',
|
|
294
294
|
content: createTarballManifest([
|
|
295
|
-
{ path: 'domains/auth/
|
|
295
|
+
{ path: 'domains/auth/actions/login.ts', isTemplate: false },
|
|
296
296
|
]),
|
|
297
297
|
isTemplate: false,
|
|
298
298
|
},
|
|
@@ -319,7 +319,7 @@ describe('runCreateProject', () => {
|
|
|
319
319
|
await runCreateProject({ name: 'test-project', outDir: tempDir });
|
|
320
320
|
|
|
321
321
|
// Read the generated auth login file
|
|
322
|
-
const authPath = join(outputDir, 'domains/auth/
|
|
322
|
+
const authPath = join(outputDir, 'domains/auth/actions/login.ts');
|
|
323
323
|
const authContent = await readFile(authPath, 'utf-8');
|
|
324
324
|
|
|
325
325
|
expect(authContent).toContain('../../lib/domain-runtime');
|
|
@@ -55,7 +55,7 @@ describe('upgrade command', () => {
|
|
|
55
55
|
let tmpDir: string;
|
|
56
56
|
|
|
57
57
|
beforeEach(async () => {
|
|
58
|
-
tmpDir = await mkdtemp(join(tmpdir(), '
|
|
58
|
+
tmpDir = await mkdtemp(join(tmpdir(), 'mc-upgrade-test-'));
|
|
59
59
|
vi.clearAllMocks();
|
|
60
60
|
});
|
|
61
61
|
|
|
@@ -213,8 +213,8 @@ describe('upgrade command', () => {
|
|
|
213
213
|
});
|
|
214
214
|
});
|
|
215
215
|
|
|
216
|
-
describe('drift detection and .
|
|
217
|
-
it('writes .
|
|
216
|
+
describe('drift detection and .mc-upgrade conflict files', () => {
|
|
217
|
+
it('writes .mc-upgrade file when file is drifted', async () => {
|
|
218
218
|
const manifestChecksum = computeChecksumString('original content');
|
|
219
219
|
const driftedContent = 'locally modified content by user';
|
|
220
220
|
const driftedChecksum = computeChecksumString(driftedContent);
|
|
@@ -313,8 +313,8 @@ describe('upgrade command', () => {
|
|
|
313
313
|
const originalContent = await readFile(join(tmpDir, filePath), 'utf-8');
|
|
314
314
|
expect(originalContent).toBe(driftedContent);
|
|
315
315
|
|
|
316
|
-
// Assert .
|
|
317
|
-
const conflictFile = await readFile(join(tmpDir, `${filePath}.
|
|
316
|
+
// Assert .mc-upgrade file was created with new content
|
|
317
|
+
const conflictFile = await readFile(join(tmpDir, `${filePath}.mc-upgrade`), 'utf-8');
|
|
318
318
|
expect(conflictFile).toContain(newContent);
|
|
319
319
|
});
|
|
320
320
|
});
|
|
@@ -620,8 +620,8 @@ describe('upgrade command', () => {
|
|
|
620
620
|
const originalContent = await readFile(join(tmpDir, filePath), 'utf-8');
|
|
621
621
|
expect(originalContent).toBe(driftedContent);
|
|
622
622
|
|
|
623
|
-
// .
|
|
624
|
-
const conflictFile = await readFile(join(tmpDir, `${filePath}.
|
|
623
|
+
// .mc-upgrade file should be created
|
|
624
|
+
const conflictFile = await readFile(join(tmpDir, `${filePath}.mc-upgrade`), 'utf-8');
|
|
625
625
|
expect(conflictFile).toContain(newContent);
|
|
626
626
|
});
|
|
627
627
|
|