@defai.digital/cli 13.1.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/LICENSE +214 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +11 -0
- package/dist/bin.js.map +1 -0
- package/dist/bootstrap.d.ts +144 -0
- package/dist/bootstrap.d.ts.map +1 -0
- package/dist/bootstrap.js +315 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/cli.d.ts +14 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +84 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/ability.d.ts +17 -0
- package/dist/commands/ability.d.ts.map +1 -0
- package/dist/commands/ability.js +286 -0
- package/dist/commands/ability.js.map +1 -0
- package/dist/commands/agent.d.ts +18 -0
- package/dist/commands/agent.d.ts.map +1 -0
- package/dist/commands/agent.js +361 -0
- package/dist/commands/agent.js.map +1 -0
- package/dist/commands/call.d.ts +15 -0
- package/dist/commands/call.d.ts.map +1 -0
- package/dist/commands/call.js +503 -0
- package/dist/commands/call.js.map +1 -0
- package/dist/commands/cleanup.d.ts +18 -0
- package/dist/commands/cleanup.d.ts.map +1 -0
- package/dist/commands/cleanup.js +300 -0
- package/dist/commands/cleanup.js.map +1 -0
- package/dist/commands/config.d.ts +16 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +513 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/discuss.d.ts +16 -0
- package/dist/commands/discuss.d.ts.map +1 -0
- package/dist/commands/discuss.js +700 -0
- package/dist/commands/discuss.js.map +1 -0
- package/dist/commands/doctor.d.ts +48 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +356 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/guard.d.ts +12 -0
- package/dist/commands/guard.d.ts.map +1 -0
- package/dist/commands/guard.js +225 -0
- package/dist/commands/guard.js.map +1 -0
- package/dist/commands/help.d.ts +11 -0
- package/dist/commands/help.d.ts.map +1 -0
- package/dist/commands/help.js +180 -0
- package/dist/commands/help.js.map +1 -0
- package/dist/commands/history.d.ts +19 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +200 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/index.d.ts +23 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +26 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/iterate.d.ts +16 -0
- package/dist/commands/iterate.d.ts.map +1 -0
- package/dist/commands/iterate.js +72 -0
- package/dist/commands/iterate.js.map +1 -0
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +62 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/mcp.d.ts +16 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +57 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/resume.d.ts +18 -0
- package/dist/commands/resume.d.ts.map +1 -0
- package/dist/commands/resume.js +208 -0
- package/dist/commands/resume.js.map +1 -0
- package/dist/commands/review.d.ts +13 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/review.js +450 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/run.d.ts +6 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +158 -0
- package/dist/commands/run.js.map +1 -0
- package/dist/commands/scaffold.d.ts +20 -0
- package/dist/commands/scaffold.d.ts.map +1 -0
- package/dist/commands/scaffold.js +924 -0
- package/dist/commands/scaffold.js.map +1 -0
- package/dist/commands/session.d.ts +20 -0
- package/dist/commands/session.d.ts.map +1 -0
- package/dist/commands/session.js +504 -0
- package/dist/commands/session.js.map +1 -0
- package/dist/commands/setup.d.ts +14 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +762 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/status.d.ts +17 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +227 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/trace.d.ts +6 -0
- package/dist/commands/trace.d.ts.map +1 -0
- package/dist/commands/trace.js +204 -0
- package/dist/commands/trace.js.map +1 -0
- package/dist/commands/update.d.ts +24 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +296 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/parser.d.ts +14 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +288 -0
- package/dist/parser.js.map +1 -0
- package/dist/types.d.ts +91 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +29 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/dangerous-op-guard.d.ts +33 -0
- package/dist/utils/dangerous-op-guard.d.ts.map +1 -0
- package/dist/utils/dangerous-op-guard.js +112 -0
- package/dist/utils/dangerous-op-guard.js.map +1 -0
- package/dist/utils/database.d.ts +85 -0
- package/dist/utils/database.d.ts.map +1 -0
- package/dist/utils/database.js +184 -0
- package/dist/utils/database.js.map +1 -0
- package/dist/utils/index.d.ts +7 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +7 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/provider-factory.d.ts +31 -0
- package/dist/utils/provider-factory.d.ts.map +1 -0
- package/dist/utils/provider-factory.js +109 -0
- package/dist/utils/provider-factory.js.map +1 -0
- package/dist/utils/storage-instances.d.ts +19 -0
- package/dist/utils/storage-instances.d.ts.map +1 -0
- package/dist/utils/storage-instances.js +20 -0
- package/dist/utils/storage-instances.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,924 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scaffold Command
|
|
3
|
+
*
|
|
4
|
+
* CLI command for scaffolding contract-first project components.
|
|
5
|
+
* Generates Zod schemas, invariants, guard policies, and domain packages.
|
|
6
|
+
*
|
|
7
|
+
* @module cli/commands/scaffold
|
|
8
|
+
*/
|
|
9
|
+
import * as fs from 'node:fs';
|
|
10
|
+
import * as path from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = path.dirname(__filename);
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Templates
|
|
16
|
+
// ============================================================================
|
|
17
|
+
/**
|
|
18
|
+
* Generate Zod schema template
|
|
19
|
+
*/
|
|
20
|
+
function generateSchemaTemplate(name, description) {
|
|
21
|
+
const pascalName = toPascalCase(name);
|
|
22
|
+
const domainCode = name.substring(0, 3).toUpperCase();
|
|
23
|
+
return `/**
|
|
24
|
+
* ${pascalName} Domain Contracts v1
|
|
25
|
+
*
|
|
26
|
+
* ${description}
|
|
27
|
+
*
|
|
28
|
+
* @module @defai.digital/contracts/${name}/v1
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { z } from 'zod';
|
|
32
|
+
|
|
33
|
+
// ============================================================================
|
|
34
|
+
// Value Objects
|
|
35
|
+
// ============================================================================
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* ${pascalName} ID value object
|
|
39
|
+
*/
|
|
40
|
+
export const ${pascalName}IdSchema = z.string().uuid();
|
|
41
|
+
|
|
42
|
+
export type ${pascalName}Id = z.infer<typeof ${pascalName}IdSchema>;
|
|
43
|
+
|
|
44
|
+
// ============================================================================
|
|
45
|
+
// Enums
|
|
46
|
+
// ============================================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* ${pascalName} status
|
|
50
|
+
*/
|
|
51
|
+
export const ${pascalName}StatusSchema = z.enum([
|
|
52
|
+
'draft',
|
|
53
|
+
'active',
|
|
54
|
+
'completed',
|
|
55
|
+
'cancelled',
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
export type ${pascalName}Status = z.infer<typeof ${pascalName}StatusSchema>;
|
|
59
|
+
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Entities
|
|
62
|
+
// ============================================================================
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* ${pascalName} entity - Aggregate Root
|
|
66
|
+
*
|
|
67
|
+
* Invariants:
|
|
68
|
+
* - INV-${domainCode}-001: ID must be valid UUID
|
|
69
|
+
* - INV-${domainCode}-002: Status must be valid enum value
|
|
70
|
+
*/
|
|
71
|
+
export const ${pascalName}Schema = z.object({
|
|
72
|
+
/** Unique identifier */
|
|
73
|
+
id: ${pascalName}IdSchema,
|
|
74
|
+
|
|
75
|
+
/** Current status */
|
|
76
|
+
status: ${pascalName}StatusSchema,
|
|
77
|
+
|
|
78
|
+
/** Timestamps */
|
|
79
|
+
createdAt: z.string().datetime(),
|
|
80
|
+
updatedAt: z.string().datetime(),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
export type ${pascalName} = z.infer<typeof ${pascalName}Schema>;
|
|
84
|
+
|
|
85
|
+
// ============================================================================
|
|
86
|
+
// Domain Events
|
|
87
|
+
// ============================================================================
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* ${pascalName} domain events
|
|
91
|
+
*/
|
|
92
|
+
export const ${pascalName}EventSchema = z.discriminatedUnion('type', [
|
|
93
|
+
z.object({
|
|
94
|
+
type: z.literal('${name}.created'),
|
|
95
|
+
${name}Id: ${pascalName}IdSchema,
|
|
96
|
+
occurredAt: z.string().datetime(),
|
|
97
|
+
}),
|
|
98
|
+
z.object({
|
|
99
|
+
type: z.literal('${name}.updated'),
|
|
100
|
+
${name}Id: ${pascalName}IdSchema,
|
|
101
|
+
changes: z.record(z.unknown()),
|
|
102
|
+
occurredAt: z.string().datetime(),
|
|
103
|
+
}),
|
|
104
|
+
z.object({
|
|
105
|
+
type: z.literal('${name}.deleted'),
|
|
106
|
+
${name}Id: ${pascalName}IdSchema,
|
|
107
|
+
occurredAt: z.string().datetime(),
|
|
108
|
+
}),
|
|
109
|
+
]);
|
|
110
|
+
|
|
111
|
+
export type ${pascalName}Event = z.infer<typeof ${pascalName}EventSchema>;
|
|
112
|
+
|
|
113
|
+
// ============================================================================
|
|
114
|
+
// Request/Response Schemas
|
|
115
|
+
// ============================================================================
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Create ${pascalName} request
|
|
119
|
+
*/
|
|
120
|
+
export const Create${pascalName}RequestSchema = z.object({
|
|
121
|
+
// Add fields as needed
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
export type Create${pascalName}Request = z.infer<typeof Create${pascalName}RequestSchema>;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Update ${pascalName} request
|
|
128
|
+
*/
|
|
129
|
+
export const Update${pascalName}RequestSchema = z.object({
|
|
130
|
+
id: ${pascalName}IdSchema,
|
|
131
|
+
// Add fields as needed
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
export type Update${pascalName}Request = z.infer<typeof Update${pascalName}RequestSchema>;
|
|
135
|
+
|
|
136
|
+
// ============================================================================
|
|
137
|
+
// Validation Functions
|
|
138
|
+
// ============================================================================
|
|
139
|
+
|
|
140
|
+
export function validate${pascalName}(data: unknown): ${pascalName} {
|
|
141
|
+
return ${pascalName}Schema.parse(data);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function validate${pascalName}Event(data: unknown): ${pascalName}Event {
|
|
145
|
+
return ${pascalName}EventSchema.parse(data);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ============================================================================
|
|
149
|
+
// Error Codes
|
|
150
|
+
// ============================================================================
|
|
151
|
+
|
|
152
|
+
export const ${pascalName}ErrorCode = {
|
|
153
|
+
NOT_FOUND: '${domainCode}_NOT_FOUND',
|
|
154
|
+
INVALID_STATUS: '${domainCode}_INVALID_STATUS',
|
|
155
|
+
VALIDATION_FAILED: '${domainCode}_VALIDATION_FAILED',
|
|
156
|
+
} as const;
|
|
157
|
+
|
|
158
|
+
export type ${pascalName}ErrorCode = typeof ${pascalName}ErrorCode[keyof typeof ${pascalName}ErrorCode];
|
|
159
|
+
`;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Generate invariants template
|
|
163
|
+
*/
|
|
164
|
+
function generateInvariantsTemplate(name, description) {
|
|
165
|
+
const pascalName = toPascalCase(name);
|
|
166
|
+
const domainCode = name.substring(0, 3).toUpperCase();
|
|
167
|
+
return `# ${pascalName} Domain Invariants
|
|
168
|
+
|
|
169
|
+
## Overview
|
|
170
|
+
|
|
171
|
+
${description}
|
|
172
|
+
|
|
173
|
+
## Schema Invariants
|
|
174
|
+
|
|
175
|
+
### INV-${domainCode}-001: Valid ID Format
|
|
176
|
+
${pascalName} ID MUST be a valid UUID v4.
|
|
177
|
+
- **Enforcement**: schema
|
|
178
|
+
- **Test**: \`z.string().uuid()\` rejects invalid UUIDs
|
|
179
|
+
|
|
180
|
+
### INV-${domainCode}-002: Valid Status
|
|
181
|
+
Status MUST be one of the defined enum values.
|
|
182
|
+
- **Enforcement**: schema
|
|
183
|
+
- **Test**: \`z.enum([...])\` rejects invalid values
|
|
184
|
+
|
|
185
|
+
## Runtime Invariants
|
|
186
|
+
|
|
187
|
+
### INV-${domainCode}-101: Status Transitions
|
|
188
|
+
Status transitions MUST follow the defined state machine.
|
|
189
|
+
- **Enforcement**: runtime
|
|
190
|
+
- **Test**: Invalid transitions throw error
|
|
191
|
+
- **Valid Transitions**:
|
|
192
|
+
\`\`\`
|
|
193
|
+
draft → active, cancelled
|
|
194
|
+
active → completed, cancelled
|
|
195
|
+
completed → (terminal state)
|
|
196
|
+
cancelled → (terminal state)
|
|
197
|
+
\`\`\`
|
|
198
|
+
|
|
199
|
+
### INV-${domainCode}-102: Timestamp Consistency
|
|
200
|
+
UpdatedAt MUST be >= CreatedAt.
|
|
201
|
+
- **Enforcement**: runtime
|
|
202
|
+
- **Test**: Update with earlier timestamp → error
|
|
203
|
+
|
|
204
|
+
## Business Invariants
|
|
205
|
+
|
|
206
|
+
### INV-${domainCode}-201: [Add business rules]
|
|
207
|
+
[Description of business invariant]
|
|
208
|
+
- **Enforcement**: [schema|runtime|test]
|
|
209
|
+
- **Test**: [How to verify]
|
|
210
|
+
- **Owner**: [Team]
|
|
211
|
+
|
|
212
|
+
## Cross-Aggregate Invariants
|
|
213
|
+
|
|
214
|
+
### INV-${domainCode}-301: [Add cross-aggregate rules]
|
|
215
|
+
[Description of cross-aggregate invariant]
|
|
216
|
+
- **Enforcement**: [saga|event handler]
|
|
217
|
+
- **Aggregates**: [List of aggregates]
|
|
218
|
+
- **Event**: [Triggering event] → [Resulting action]
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Generate guard policy template
|
|
223
|
+
*/
|
|
224
|
+
function generateGuardPolicyTemplate(policyId, domain, radius, gates) {
|
|
225
|
+
return `# ${toPascalCase(domain)} Domain Guard Policy
|
|
226
|
+
#
|
|
227
|
+
# This policy governs changes to the ${domain} domain.
|
|
228
|
+
|
|
229
|
+
policy_id: ${policyId}
|
|
230
|
+
|
|
231
|
+
description: |
|
|
232
|
+
Guard policy for ${domain} domain development.
|
|
233
|
+
|
|
234
|
+
allowed_paths:
|
|
235
|
+
# Contract package
|
|
236
|
+
- packages/contracts/src/${domain}/**
|
|
237
|
+
- packages/contracts/src/${domain}/v1/schema.ts
|
|
238
|
+
- packages/contracts/src/${domain}/v1/invariants.md
|
|
239
|
+
- packages/contracts/src/${domain}/v1/index.ts
|
|
240
|
+
|
|
241
|
+
# Domain package
|
|
242
|
+
- packages/core/${domain}-domain/**
|
|
243
|
+
- packages/core/${domain}-domain/src/**
|
|
244
|
+
|
|
245
|
+
# Tests
|
|
246
|
+
- tests/contract/${domain}.test.ts
|
|
247
|
+
- tests/core/${domain}-domain.test.ts
|
|
248
|
+
- tests/integration/${domain}/**
|
|
249
|
+
|
|
250
|
+
forbidden_paths:
|
|
251
|
+
# Other domains' contracts
|
|
252
|
+
- packages/contracts/src/*/v1/schema.ts
|
|
253
|
+
- "!packages/contracts/src/${domain}/v1/schema.ts"
|
|
254
|
+
|
|
255
|
+
# Infrastructure
|
|
256
|
+
- packages/cli/**
|
|
257
|
+
- packages/mcp-server/**
|
|
258
|
+
- packages/guard/**
|
|
259
|
+
|
|
260
|
+
# Adapters
|
|
261
|
+
- packages/adapters/**
|
|
262
|
+
|
|
263
|
+
required_contracts:
|
|
264
|
+
- ${domain}
|
|
265
|
+
|
|
266
|
+
gates:
|
|
267
|
+
${gates.map((g) => ` - ${g}`).join('\n')}
|
|
268
|
+
|
|
269
|
+
change_radius_limit: ${radius}
|
|
270
|
+
|
|
271
|
+
variables:
|
|
272
|
+
domain: ${domain}
|
|
273
|
+
|
|
274
|
+
metadata:
|
|
275
|
+
owner: ${domain}-team
|
|
276
|
+
created_at: "${new Date().toISOString().split('T')[0]}"
|
|
277
|
+
severity: medium
|
|
278
|
+
auto_apply: false
|
|
279
|
+
`;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Generate index.ts template
|
|
283
|
+
*/
|
|
284
|
+
function generateIndexTemplate(name) {
|
|
285
|
+
return `/**
|
|
286
|
+
* ${toPascalCase(name)} Contracts v1
|
|
287
|
+
*
|
|
288
|
+
* @module @defai.digital/contracts/${name}/v1
|
|
289
|
+
*/
|
|
290
|
+
|
|
291
|
+
export * from './schema.js';
|
|
292
|
+
`;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Generate domain package.json template
|
|
296
|
+
*/
|
|
297
|
+
function generatePackageJsonTemplate(name, scope) {
|
|
298
|
+
return JSON.stringify({
|
|
299
|
+
name: `${scope}/${name}-domain`,
|
|
300
|
+
version: '1.0.0',
|
|
301
|
+
description: `${toPascalCase(name)} domain implementation`,
|
|
302
|
+
type: 'module',
|
|
303
|
+
main: 'dist/index.js',
|
|
304
|
+
types: 'dist/index.d.ts',
|
|
305
|
+
exports: {
|
|
306
|
+
'.': {
|
|
307
|
+
import: './dist/index.js',
|
|
308
|
+
types: './dist/index.d.ts',
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
scripts: {
|
|
312
|
+
build: 'tsc --build',
|
|
313
|
+
clean: 'rm -rf dist',
|
|
314
|
+
typecheck: 'tsc --noEmit',
|
|
315
|
+
},
|
|
316
|
+
dependencies: {
|
|
317
|
+
'@defai.digital/contracts': 'workspace:*',
|
|
318
|
+
},
|
|
319
|
+
devDependencies: {
|
|
320
|
+
typescript: '^5.3.0',
|
|
321
|
+
},
|
|
322
|
+
}, null, 2);
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Generate domain index.ts template
|
|
326
|
+
*/
|
|
327
|
+
function generateDomainIndexTemplate(name) {
|
|
328
|
+
const pascalName = toPascalCase(name);
|
|
329
|
+
return `/**
|
|
330
|
+
* ${pascalName} Domain
|
|
331
|
+
*
|
|
332
|
+
* @module @defai.digital/${name}-domain
|
|
333
|
+
*/
|
|
334
|
+
|
|
335
|
+
export * from './types.js';
|
|
336
|
+
export * from './service.js';
|
|
337
|
+
`;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Generate domain types.ts template
|
|
341
|
+
*/
|
|
342
|
+
function generateDomainTypesTemplate(name) {
|
|
343
|
+
const pascalName = toPascalCase(name);
|
|
344
|
+
return `/**
|
|
345
|
+
* ${pascalName} Domain Types
|
|
346
|
+
*/
|
|
347
|
+
|
|
348
|
+
import type { ${pascalName}, ${pascalName}Event } from '@defai.digital/contracts';
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* ${pascalName} repository interface
|
|
352
|
+
*/
|
|
353
|
+
export interface ${pascalName}Repository {
|
|
354
|
+
findById(id: string): Promise<${pascalName} | undefined>;
|
|
355
|
+
findAll(): Promise<${pascalName}[]>;
|
|
356
|
+
save(entity: ${pascalName}): Promise<void>;
|
|
357
|
+
delete(id: string): Promise<void>;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* ${pascalName} event publisher interface
|
|
362
|
+
*/
|
|
363
|
+
export interface ${pascalName}EventPublisher {
|
|
364
|
+
publish(event: ${pascalName}Event): Promise<void>;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* ${pascalName} service dependencies
|
|
369
|
+
*/
|
|
370
|
+
export interface ${pascalName}ServiceDeps {
|
|
371
|
+
repository: ${pascalName}Repository;
|
|
372
|
+
eventPublisher: ${pascalName}EventPublisher;
|
|
373
|
+
}
|
|
374
|
+
`;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Generate domain service.ts template
|
|
378
|
+
*/
|
|
379
|
+
function generateDomainServiceTemplate(name) {
|
|
380
|
+
const pascalName = toPascalCase(name);
|
|
381
|
+
return `/**
|
|
382
|
+
* ${pascalName} Domain Service
|
|
383
|
+
*/
|
|
384
|
+
|
|
385
|
+
import type { ${pascalName} } from '@defai.digital/contracts';
|
|
386
|
+
import type { ${pascalName}ServiceDeps } from './types.js';
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* ${pascalName} service
|
|
390
|
+
*/
|
|
391
|
+
export class ${pascalName}Service {
|
|
392
|
+
constructor(private readonly deps: ${pascalName}ServiceDeps) {}
|
|
393
|
+
|
|
394
|
+
async findById(id: string): Promise<${pascalName} | undefined> {
|
|
395
|
+
return this.deps.repository.findById(id);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async findAll(): Promise<${pascalName}[]> {
|
|
399
|
+
return this.deps.repository.findAll();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Add domain operations here
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Create ${pascalName} service
|
|
407
|
+
*/
|
|
408
|
+
export function create${pascalName}Service(deps: ${pascalName}ServiceDeps): ${pascalName}Service {
|
|
409
|
+
return new ${pascalName}Service(deps);
|
|
410
|
+
}
|
|
411
|
+
`;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Generate test file template
|
|
415
|
+
*/
|
|
416
|
+
function generateTestTemplate(name) {
|
|
417
|
+
const pascalName = toPascalCase(name);
|
|
418
|
+
const domainCode = name.substring(0, 3).toUpperCase();
|
|
419
|
+
return `/**
|
|
420
|
+
* ${pascalName} Contract Tests
|
|
421
|
+
*
|
|
422
|
+
* Tests for schema validation and invariant enforcement.
|
|
423
|
+
*/
|
|
424
|
+
|
|
425
|
+
import { describe, it, expect } from 'vitest';
|
|
426
|
+
import {
|
|
427
|
+
${pascalName}Schema,
|
|
428
|
+
${pascalName}EventSchema,
|
|
429
|
+
validate${pascalName},
|
|
430
|
+
${pascalName}ErrorCode,
|
|
431
|
+
} from '@defai.digital/contracts';
|
|
432
|
+
|
|
433
|
+
describe('${pascalName} Contract', () => {
|
|
434
|
+
describe('Schema Validation', () => {
|
|
435
|
+
// INV-${domainCode}-001: Valid ID Format
|
|
436
|
+
it('should reject invalid UUID', () => {
|
|
437
|
+
const invalid = {
|
|
438
|
+
id: 'not-a-uuid',
|
|
439
|
+
status: 'active',
|
|
440
|
+
createdAt: new Date().toISOString(),
|
|
441
|
+
updatedAt: new Date().toISOString(),
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
expect(() => ${pascalName}Schema.parse(invalid)).toThrow();
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// INV-${domainCode}-002: Valid Status
|
|
448
|
+
it('should reject invalid status', () => {
|
|
449
|
+
const invalid = {
|
|
450
|
+
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
451
|
+
status: 'invalid-status',
|
|
452
|
+
createdAt: new Date().toISOString(),
|
|
453
|
+
updatedAt: new Date().toISOString(),
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
expect(() => ${pascalName}Schema.parse(invalid)).toThrow();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it('should accept valid entity', () => {
|
|
460
|
+
const valid = {
|
|
461
|
+
id: '550e8400-e29b-41d4-a716-446655440000',
|
|
462
|
+
status: 'active',
|
|
463
|
+
createdAt: new Date().toISOString(),
|
|
464
|
+
updatedAt: new Date().toISOString(),
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
expect(() => ${pascalName}Schema.parse(valid)).not.toThrow();
|
|
468
|
+
});
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
describe('Event Validation', () => {
|
|
472
|
+
it('should accept valid created event', () => {
|
|
473
|
+
const event = {
|
|
474
|
+
type: '${name}.created',
|
|
475
|
+
${name}Id: '550e8400-e29b-41d4-a716-446655440000',
|
|
476
|
+
occurredAt: new Date().toISOString(),
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
expect(() => ${pascalName}EventSchema.parse(event)).not.toThrow();
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it('should reject event with invalid type', () => {
|
|
483
|
+
const event = {
|
|
484
|
+
type: 'invalid.event',
|
|
485
|
+
${name}Id: '550e8400-e29b-41d4-a716-446655440000',
|
|
486
|
+
occurredAt: new Date().toISOString(),
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
expect(() => ${pascalName}EventSchema.parse(event)).toThrow();
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
`;
|
|
494
|
+
}
|
|
495
|
+
// ============================================================================
|
|
496
|
+
// Utilities
|
|
497
|
+
// ============================================================================
|
|
498
|
+
/**
|
|
499
|
+
* Convert string to PascalCase
|
|
500
|
+
*/
|
|
501
|
+
function toPascalCase(str) {
|
|
502
|
+
return str
|
|
503
|
+
.split(/[-_]/)
|
|
504
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
505
|
+
.join('');
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Convert string to UPPER_CASE
|
|
509
|
+
*/
|
|
510
|
+
function toUpperCase(str) {
|
|
511
|
+
return str.toUpperCase();
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Get substring
|
|
515
|
+
*/
|
|
516
|
+
function substring(str, start, end) {
|
|
517
|
+
return str.substring(start, end);
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Simple Handlebars-like template engine
|
|
521
|
+
* Supports: {{variable}}, {{pascalCase variable}}, {{upperCase (substring variable 0 3)}}
|
|
522
|
+
*/
|
|
523
|
+
function processTemplate(template, variables) {
|
|
524
|
+
// Helper function registry
|
|
525
|
+
const helpers = {
|
|
526
|
+
pascalCase: (s) => toPascalCase(s),
|
|
527
|
+
upperCase: (s) => toUpperCase(s),
|
|
528
|
+
substring: (s, start, end) => substring(s, parseInt(start), parseInt(end)),
|
|
529
|
+
};
|
|
530
|
+
// Process nested helpers like {{upperCase (substring domainName 0 3)}}
|
|
531
|
+
let result = template.replace(/\{\{(\w+)\s+\((\w+)\s+(\w+)\s+(\d+)\s+(\d+)\)\}\}/g, (_, outerHelper, innerHelper, varName, start, end) => {
|
|
532
|
+
const value = variables[varName] || varName;
|
|
533
|
+
const innerResult = helpers[innerHelper] ? helpers[innerHelper](value, start, end) : value;
|
|
534
|
+
return helpers[outerHelper] ? helpers[outerHelper](innerResult) : innerResult;
|
|
535
|
+
});
|
|
536
|
+
// Process single helpers like {{pascalCase domainName}}
|
|
537
|
+
result = result.replace(/\{\{(\w+)\s+(\w+)\}\}/g, (_, helper, varName) => {
|
|
538
|
+
const value = variables[varName] || varName;
|
|
539
|
+
return helpers[helper] ? helpers[helper](value) : value;
|
|
540
|
+
});
|
|
541
|
+
// Process simple variables like {{domainName}}
|
|
542
|
+
result = result.replace(/\{\{(\w+)\}\}/g, (_, varName) => {
|
|
543
|
+
return variables[varName] || '';
|
|
544
|
+
});
|
|
545
|
+
return result;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Write file with optional dry-run
|
|
549
|
+
*/
|
|
550
|
+
function writeFile(filePath, content, dryRun, files) {
|
|
551
|
+
const action = fs.existsSync(filePath) ? 'overwrite' : 'create';
|
|
552
|
+
files.push({ path: filePath, action });
|
|
553
|
+
if (!dryRun) {
|
|
554
|
+
const dir = path.dirname(filePath);
|
|
555
|
+
if (!fs.existsSync(dir)) {
|
|
556
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
557
|
+
}
|
|
558
|
+
fs.writeFileSync(filePath, content, 'utf-8');
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
// ============================================================================
|
|
562
|
+
// Subcommand Handlers
|
|
563
|
+
// ============================================================================
|
|
564
|
+
/**
|
|
565
|
+
* Handle scaffold contract subcommand
|
|
566
|
+
*/
|
|
567
|
+
async function handleScaffoldContract(options) {
|
|
568
|
+
const { name, description = `${toPascalCase(name)} domain.`, output, dryRun = false } = options;
|
|
569
|
+
const outputDir = output || `packages/contracts/src/${name}/v1`;
|
|
570
|
+
const files = [];
|
|
571
|
+
// Generate files
|
|
572
|
+
writeFile(path.join(outputDir, 'schema.ts'), generateSchemaTemplate(name, description), dryRun, files);
|
|
573
|
+
writeFile(path.join(outputDir, 'invariants.md'), generateInvariantsTemplate(name, description), dryRun, files);
|
|
574
|
+
writeFile(path.join(outputDir, 'index.ts'), generateIndexTemplate(name), dryRun, files);
|
|
575
|
+
const message = dryRun
|
|
576
|
+
? `Dry run - would create ${files.length} files:\n${files.map((f) => ` ${f.action}: ${f.path}`).join('\n')}`
|
|
577
|
+
: `Created contract for "${name}":\n${files.map((f) => ` ${f.action}: ${f.path}`).join('\n')}`;
|
|
578
|
+
return {
|
|
579
|
+
success: true,
|
|
580
|
+
message,
|
|
581
|
+
data: { name, files, dryRun },
|
|
582
|
+
exitCode: 0,
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Handle scaffold domain subcommand
|
|
587
|
+
*/
|
|
588
|
+
async function handleScaffoldDomain(options) {
|
|
589
|
+
const { name, output, scope = '@defai.digital', noTests = false, noGuard = false, dryRun = false, } = options;
|
|
590
|
+
const domainDir = output || `packages/core/${name}-domain`;
|
|
591
|
+
const files = [];
|
|
592
|
+
// Generate domain package files
|
|
593
|
+
writeFile(path.join(domainDir, 'package.json'), generatePackageJsonTemplate(name, scope), dryRun, files);
|
|
594
|
+
writeFile(path.join(domainDir, 'src', 'index.ts'), generateDomainIndexTemplate(name), dryRun, files);
|
|
595
|
+
writeFile(path.join(domainDir, 'src', 'types.ts'), generateDomainTypesTemplate(name), dryRun, files);
|
|
596
|
+
writeFile(path.join(domainDir, 'src', 'service.ts'), generateDomainServiceTemplate(name), dryRun, files);
|
|
597
|
+
// Generate test file
|
|
598
|
+
if (!noTests) {
|
|
599
|
+
writeFile(`tests/contract/${name}.test.ts`, generateTestTemplate(name), dryRun, files);
|
|
600
|
+
}
|
|
601
|
+
// Generate guard policy
|
|
602
|
+
if (!noGuard) {
|
|
603
|
+
writeFile(`packages/guard/policies/${name}-development.yaml`, generateGuardPolicyTemplate(`${name}-development`, name, 3, ['path_violation', 'dependency', 'change_radius', 'contract_tests']), dryRun, files);
|
|
604
|
+
}
|
|
605
|
+
const message = dryRun
|
|
606
|
+
? `Dry run - would create ${files.length} files:\n${files.map((f) => ` ${f.action}: ${f.path}`).join('\n')}`
|
|
607
|
+
: `Created domain package for "${name}":\n${files.map((f) => ` ${f.action}: ${f.path}`).join('\n')}`;
|
|
608
|
+
return {
|
|
609
|
+
success: true,
|
|
610
|
+
message,
|
|
611
|
+
data: { name, files, dryRun },
|
|
612
|
+
exitCode: 0,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Handle scaffold guard subcommand
|
|
617
|
+
*/
|
|
618
|
+
async function handleScaffoldGuard(options) {
|
|
619
|
+
const { policyId, domain = policyId.replace(/-development$/, ''), radius = 3, gates = 'path_violation,dependency,change_radius,contract_tests', dryRun = false, } = options;
|
|
620
|
+
const gatesList = gates.split(',').map((g) => g.trim());
|
|
621
|
+
const files = [];
|
|
622
|
+
writeFile(`packages/guard/policies/${policyId}.yaml`, generateGuardPolicyTemplate(policyId, domain, radius, gatesList), dryRun, files);
|
|
623
|
+
const message = dryRun
|
|
624
|
+
? `Dry run - would create ${files.length} files:\n${files.map((f) => ` ${f.action}: ${f.path}`).join('\n')}`
|
|
625
|
+
: `Created guard policy "${policyId}":\n${files.map((f) => ` ${f.action}: ${f.path}`).join('\n')}`;
|
|
626
|
+
return {
|
|
627
|
+
success: true,
|
|
628
|
+
message,
|
|
629
|
+
data: { policyId, domain, files, dryRun },
|
|
630
|
+
exitCode: 0,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Handle scaffold project subcommand
|
|
635
|
+
*/
|
|
636
|
+
async function handleScaffoldProject(options) {
|
|
637
|
+
const { projectName, template, domainName, scope = '@myorg', description = 'A contract-first TypeScript project', output, dryRun = false, } = options;
|
|
638
|
+
// Find templates directory (relative to CLI package)
|
|
639
|
+
// Path from packages/cli/dist/commands -> cli/dist -> cli -> packages -> automatosx (root)
|
|
640
|
+
const templatesDir = path.resolve(__dirname, '../../../../templates');
|
|
641
|
+
const templateDir = path.join(templatesDir, template);
|
|
642
|
+
// Check if template exists
|
|
643
|
+
if (!fs.existsSync(templateDir)) {
|
|
644
|
+
return {
|
|
645
|
+
success: false,
|
|
646
|
+
message: `Template "${template}" not found. Available templates: monorepo, standalone`,
|
|
647
|
+
data: undefined,
|
|
648
|
+
exitCode: 1,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
// Load template config
|
|
652
|
+
const configPath = path.join(templateDir, 'template.json');
|
|
653
|
+
if (!fs.existsSync(configPath)) {
|
|
654
|
+
return {
|
|
655
|
+
success: false,
|
|
656
|
+
message: `Template config not found: ${configPath}`,
|
|
657
|
+
data: undefined,
|
|
658
|
+
exitCode: 1,
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
let config;
|
|
662
|
+
try {
|
|
663
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
664
|
+
}
|
|
665
|
+
catch (e) {
|
|
666
|
+
return {
|
|
667
|
+
success: false,
|
|
668
|
+
message: `Failed to parse template config: ${e}`,
|
|
669
|
+
data: undefined,
|
|
670
|
+
exitCode: 1,
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
// Prepare variables for template processing
|
|
674
|
+
const variables = {
|
|
675
|
+
projectName,
|
|
676
|
+
domainName,
|
|
677
|
+
scope,
|
|
678
|
+
description,
|
|
679
|
+
};
|
|
680
|
+
// Determine output directory
|
|
681
|
+
const outputDir = output || projectName;
|
|
682
|
+
const files = [];
|
|
683
|
+
// Process each item in the template structure
|
|
684
|
+
for (const item of config.structure) {
|
|
685
|
+
// Process path with variables (e.g., {{domainName}})
|
|
686
|
+
const outputPath = processTemplate(item.path, variables);
|
|
687
|
+
const fullOutputPath = path.join(outputDir, outputPath);
|
|
688
|
+
// Handle directory items
|
|
689
|
+
if (item.type === 'directory') {
|
|
690
|
+
const action = fs.existsSync(fullOutputPath) ? 'exists' : 'create';
|
|
691
|
+
files.push({ path: fullOutputPath, action: `${action} (dir)` });
|
|
692
|
+
if (!dryRun && !fs.existsSync(fullOutputPath)) {
|
|
693
|
+
fs.mkdirSync(fullOutputPath, { recursive: true });
|
|
694
|
+
}
|
|
695
|
+
continue;
|
|
696
|
+
}
|
|
697
|
+
// Handle file items
|
|
698
|
+
if (!item.template) {
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
// Read template file
|
|
702
|
+
const templatePath = path.join(templateDir, item.template);
|
|
703
|
+
if (!fs.existsSync(templatePath)) {
|
|
704
|
+
// Template file doesn't exist, skip
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
const templateContent = fs.readFileSync(templatePath, 'utf-8');
|
|
708
|
+
// Process template content
|
|
709
|
+
const processedContent = processTemplate(templateContent, variables);
|
|
710
|
+
// Write file
|
|
711
|
+
writeFile(fullOutputPath, processedContent, dryRun, files);
|
|
712
|
+
}
|
|
713
|
+
// Format postCreate commands
|
|
714
|
+
const postCreateCommands = config.postCreate.map(c => typeof c === 'string' ? c : c.command);
|
|
715
|
+
const message = dryRun
|
|
716
|
+
? `Dry run - would create project "${projectName}" with ${files.length} items:\n` +
|
|
717
|
+
`Template: ${config.displayName}\n\n` +
|
|
718
|
+
`Items:\n${files.map((f) => ` ${f.action}: ${f.path}`).join('\n')}\n\n` +
|
|
719
|
+
`Post-create commands:\n${postCreateCommands.map(c => ` ${c}`).join('\n')}`
|
|
720
|
+
: `Created project "${projectName}":\n` +
|
|
721
|
+
`Template: ${config.displayName}\n\n` +
|
|
722
|
+
`Items:\n${files.map((f) => ` ${f.action}: ${f.path}`).join('\n')}\n\n` +
|
|
723
|
+
`Next steps:\n` +
|
|
724
|
+
` cd ${outputDir}\n` +
|
|
725
|
+
postCreateCommands.map(c => ` ${c}`).join('\n');
|
|
726
|
+
return {
|
|
727
|
+
success: true,
|
|
728
|
+
message,
|
|
729
|
+
data: { projectName, template, domainName, files, dryRun },
|
|
730
|
+
exitCode: 0,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
// ============================================================================
|
|
734
|
+
// Main Command
|
|
735
|
+
// ============================================================================
|
|
736
|
+
/**
|
|
737
|
+
* Handles the 'scaffold' command
|
|
738
|
+
*
|
|
739
|
+
* Subcommands:
|
|
740
|
+
* scaffold project <name> - Generate new project from template
|
|
741
|
+
* scaffold contract <name> - Generate Zod schema and invariants
|
|
742
|
+
* scaffold domain <name> - Generate complete domain package
|
|
743
|
+
* scaffold guard <id> - Generate guard policy
|
|
744
|
+
*/
|
|
745
|
+
export async function scaffoldCommand(args, options) {
|
|
746
|
+
// Handle help request
|
|
747
|
+
if (args.length === 0 || args[0] === 'help' || options.help) {
|
|
748
|
+
return {
|
|
749
|
+
success: true,
|
|
750
|
+
message: 'Usage: ax scaffold <subcommand> [options]\n\n' +
|
|
751
|
+
'Subcommands:\n' +
|
|
752
|
+
' project <name> Generate new project from template\n' +
|
|
753
|
+
' contract <name> Generate Zod schema and invariants\n' +
|
|
754
|
+
' domain <name> Generate complete domain package\n' +
|
|
755
|
+
' guard <id> Generate guard policy\n\n' +
|
|
756
|
+
'Options:\n' +
|
|
757
|
+
' -t, --template <type> Template: monorepo or standalone (default: standalone)\n' +
|
|
758
|
+
' -m, --domain <name> Primary domain name (required for project)\n' +
|
|
759
|
+
' -d, --description <desc> Domain/project description\n' +
|
|
760
|
+
' -o, --output <path> Output directory\n' +
|
|
761
|
+
' -s, --scope <scope> Package scope (default: @myorg)\n' +
|
|
762
|
+
' -r, --radius <n> Change radius limit (default: 3)\n' +
|
|
763
|
+
' -g, --gates <gates> Comma-separated gates\n' +
|
|
764
|
+
' --no-tests Skip test scaffolds\n' +
|
|
765
|
+
' --no-guard Skip guard policy\n' +
|
|
766
|
+
' --dry-run Preview without writing files',
|
|
767
|
+
data: undefined,
|
|
768
|
+
exitCode: 0,
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
const subcommand = args[0];
|
|
772
|
+
const subArgs = args.slice(1);
|
|
773
|
+
// Parse common options
|
|
774
|
+
// Note: Due to CLI parser behavior, flag values appear as positional args
|
|
775
|
+
// BEFORE their flags. We need to handle this specially.
|
|
776
|
+
const dryRun = options.verbose || subArgs.includes('--dry-run');
|
|
777
|
+
// Separate flags from non-flags and track flag positions
|
|
778
|
+
const flags = [];
|
|
779
|
+
const nonFlags = [];
|
|
780
|
+
const boolFlags = new Set(['--dry-run', '--no-tests', '--no-guard']);
|
|
781
|
+
for (const arg of subArgs) {
|
|
782
|
+
if (arg.startsWith('-')) {
|
|
783
|
+
flags.push(arg);
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
nonFlags.push(arg);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
// Map of flag to value based on order of appearance
|
|
790
|
+
// The parser puts values before flags, so nonFlags[i] corresponds to flags[i] (if flags[i] expects a value)
|
|
791
|
+
const flagOrder = ['-m', '--domain', '-t', '--template', '-s', '--scope', '-d', '--description',
|
|
792
|
+
'-o', '--output', '-r', '--radius', '-g', '--gates'];
|
|
793
|
+
// Build a map of flags that are present
|
|
794
|
+
const presentFlags = [];
|
|
795
|
+
for (const flag of flags) {
|
|
796
|
+
if (!boolFlags.has(flag) && flagOrder.includes(flag)) {
|
|
797
|
+
presentFlags.push(flag);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
// Match values to flags by order (after skipping the first non-flag which is the name)
|
|
801
|
+
const flagValueMap = new Map();
|
|
802
|
+
const numValues = nonFlags.length > 0 ? nonFlags.length - 1 : 0; // First nonFlag is the name
|
|
803
|
+
// presentFlags appear in the order their values appeared in nonFlags
|
|
804
|
+
for (let i = 0; i < Math.min(numValues, presentFlags.length); i++) {
|
|
805
|
+
const value = nonFlags[i + 1]; // Skip the first (name)
|
|
806
|
+
const flag = presentFlags[i];
|
|
807
|
+
if (value && flag) {
|
|
808
|
+
flagValueMap.set(flag, value);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
// Get option values from the map
|
|
812
|
+
const descriptionValue = flagValueMap.get('-d') || flagValueMap.get('--description');
|
|
813
|
+
const outputValue = flagValueMap.get('-o') || flagValueMap.get('--output');
|
|
814
|
+
const scopeValue = flagValueMap.get('-s') || flagValueMap.get('--scope');
|
|
815
|
+
const radiusValue = flagValueMap.get('-r') || flagValueMap.get('--radius');
|
|
816
|
+
const gatesValue = flagValueMap.get('-g') || flagValueMap.get('--gates');
|
|
817
|
+
const templateValue = flagValueMap.get('-t') || flagValueMap.get('--template');
|
|
818
|
+
const domainValue = flagValueMap.get('-m') || flagValueMap.get('--domain');
|
|
819
|
+
// First non-flag is the name
|
|
820
|
+
const positionalArgs = nonFlags.length > 0 ? [nonFlags[0]] : [];
|
|
821
|
+
switch (subcommand) {
|
|
822
|
+
case 'project': {
|
|
823
|
+
const projectName = positionalArgs[0];
|
|
824
|
+
if (!projectName) {
|
|
825
|
+
return {
|
|
826
|
+
success: false,
|
|
827
|
+
message: 'Usage: ax scaffold project <name> -m <domain> [options]\n\n' +
|
|
828
|
+
'Example: ax scaffold project my-app -m order -t monorepo',
|
|
829
|
+
data: undefined,
|
|
830
|
+
exitCode: 1,
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
if (!domainValue) {
|
|
834
|
+
return {
|
|
835
|
+
success: false,
|
|
836
|
+
message: 'Domain name is required. Use -m or --domain to specify.\n\n' +
|
|
837
|
+
'Example: ax scaffold project my-app -m order',
|
|
838
|
+
data: undefined,
|
|
839
|
+
exitCode: 1,
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
const templateStr = templateValue || 'standalone';
|
|
843
|
+
if (templateStr !== 'monorepo' && templateStr !== 'standalone') {
|
|
844
|
+
return {
|
|
845
|
+
success: false,
|
|
846
|
+
message: `Invalid template: ${templateStr}. Available: monorepo, standalone`,
|
|
847
|
+
data: undefined,
|
|
848
|
+
exitCode: 1,
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
return handleScaffoldProject({
|
|
852
|
+
projectName,
|
|
853
|
+
template: templateStr,
|
|
854
|
+
domainName: domainValue,
|
|
855
|
+
scope: scopeValue,
|
|
856
|
+
description: descriptionValue,
|
|
857
|
+
output: outputValue,
|
|
858
|
+
dryRun,
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
case 'contract': {
|
|
862
|
+
const name = positionalArgs[0];
|
|
863
|
+
if (!name) {
|
|
864
|
+
return {
|
|
865
|
+
success: false,
|
|
866
|
+
message: 'Usage: ax scaffold contract <name> [options]',
|
|
867
|
+
data: undefined,
|
|
868
|
+
exitCode: 1,
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
return handleScaffoldContract({
|
|
872
|
+
name,
|
|
873
|
+
description: descriptionValue,
|
|
874
|
+
output: outputValue,
|
|
875
|
+
dryRun,
|
|
876
|
+
});
|
|
877
|
+
}
|
|
878
|
+
case 'domain': {
|
|
879
|
+
const name = positionalArgs[0];
|
|
880
|
+
if (!name) {
|
|
881
|
+
return {
|
|
882
|
+
success: false,
|
|
883
|
+
message: 'Usage: ax scaffold domain <name> [options]',
|
|
884
|
+
data: undefined,
|
|
885
|
+
exitCode: 1,
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
return handleScaffoldDomain({
|
|
889
|
+
name,
|
|
890
|
+
output: outputValue,
|
|
891
|
+
scope: scopeValue,
|
|
892
|
+
noTests: subArgs.includes('--no-tests'),
|
|
893
|
+
noGuard: subArgs.includes('--no-guard'),
|
|
894
|
+
dryRun,
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
case 'guard': {
|
|
898
|
+
const policyId = positionalArgs[0];
|
|
899
|
+
if (!policyId) {
|
|
900
|
+
return {
|
|
901
|
+
success: false,
|
|
902
|
+
message: 'Usage: ax scaffold guard <policy-id> [options]',
|
|
903
|
+
data: undefined,
|
|
904
|
+
exitCode: 1,
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
return handleScaffoldGuard({
|
|
908
|
+
policyId,
|
|
909
|
+
domain: domainValue || descriptionValue,
|
|
910
|
+
radius: radiusValue ? parseInt(radiusValue, 10) : undefined,
|
|
911
|
+
gates: gatesValue,
|
|
912
|
+
dryRun,
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
default:
|
|
916
|
+
return {
|
|
917
|
+
success: false,
|
|
918
|
+
message: `Unknown subcommand: ${subcommand}\n\nAvailable subcommands: project, contract, domain, guard`,
|
|
919
|
+
data: undefined,
|
|
920
|
+
exitCode: 1,
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
//# sourceMappingURL=scaffold.js.map
|