@zodmire/cli 0.1.2 → 0.1.3
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/mod.mjs +313 -16
- package/package.json +3 -3
package/mod.mjs
CHANGED
|
@@ -2,8 +2,9 @@ import { register } from "node:module";
|
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { dirname, resolve } from "node:path";
|
|
5
|
-
import { VENDORED_FILE_MANIFEST, buildCompositionArtifacts, buildContextNormalizedSpecFromConfig, buildFileArtifactTags, buildNormalizedCompositionSpec, buildNormalizedSpecTags, buildV5ContextArtifacts, filterPerContextArtifactsForComposition, generateContextArtifacts, inferReconcileScopes, mergeGeneratedArtifacts, prepareAllVendoredFiles, resolveAbsoluteContextInputs, serializeCompositionSpec, serializeContextSpec } from "@zodmire/core";
|
|
6
|
-
import { materializeOwnedArtifacts } from "@zodmire/core/materialize";
|
|
5
|
+
import { VENDORED_FILE_MANIFEST, buildCompositionArtifacts, buildContextNormalizedSpecFromConfig, buildFileArtifactTags, buildNormalizedCompositionSpec, buildNormalizedSpecTags, buildV5ContextArtifacts, ensureContextSupportFilesExist, filterPerContextArtifactsForComposition, generateContextArtifacts, inferReconcileScopes, mergeGeneratedArtifacts, prepareAllVendoredFiles, resolveAbsoluteContextInputs, resolveArtifactOwnership, serializeCompositionSpec, serializeContextSpec, validateContextImports } from "@zodmire/core";
|
|
6
|
+
import { MaterializationManifestSchema, getPersistedMaterializationManifestPath, materializeOwnedArtifacts } from "@zodmire/core/materialize";
|
|
7
|
+
import { CompositionConfigSchema } from "@zodmire/config";
|
|
7
8
|
import { statSync } from "node:fs";
|
|
8
9
|
import { spawn } from "node:child_process";
|
|
9
10
|
|
|
@@ -109,10 +110,10 @@ async function mirrorToSwampDataLayer(opts) {
|
|
|
109
110
|
//#region packages/cli/generate.ts
|
|
110
111
|
async function runGenerate(opts) {
|
|
111
112
|
const configPath = opts.configPath.startsWith("/") ? opts.configPath : `${process.cwd()}/${opts.configPath}`;
|
|
112
|
-
if (opts.composition) await runCompositionGenerate(configPath, opts.targetOverride, opts.versionData);
|
|
113
|
-
else await runContextGenerate(configPath, opts.targetOverride, opts.versionData);
|
|
113
|
+
if (opts.composition) await runCompositionGenerate(configPath, opts.targetOverride, opts.versionData, opts.dryRun);
|
|
114
|
+
else await runContextGenerate(configPath, opts.targetOverride, opts.versionData, opts.dryRun);
|
|
114
115
|
}
|
|
115
|
-
async function runContextGenerate(configPath, targetOverride, versionData) {
|
|
116
|
+
async function runContextGenerate(configPath, targetOverride, versionData, dryRun) {
|
|
116
117
|
const resolvedInputs = await resolveAbsoluteContextInputs(configPath);
|
|
117
118
|
const config = resolvedInputs.config;
|
|
118
119
|
const codegenSupportPath = resolvedInputs.codegenSupportPath;
|
|
@@ -120,13 +121,21 @@ async function runContextGenerate(configPath, targetOverride, versionData) {
|
|
|
120
121
|
console.log(` codegen support: ${codegenSupportPath}`);
|
|
121
122
|
const artifacts = await buildV5ContextArtifacts({
|
|
122
123
|
contextConfigPath: configPath,
|
|
123
|
-
codegenSupportPath
|
|
124
|
+
codegenSupportPath,
|
|
125
|
+
generation: config.generation
|
|
124
126
|
});
|
|
125
127
|
const targetRoot = targetOverride ?? config.materialization?.targetRoot;
|
|
126
128
|
if (!targetRoot) throw new Error("No --target specified and context config has no materialization.targetRoot");
|
|
127
129
|
const resolvedTarget = targetRoot.startsWith("/") ? targetRoot : `${process.cwd()}/${targetRoot}`;
|
|
128
130
|
console.log(` target: ${resolvedTarget}`);
|
|
129
|
-
const result = await materializeOwnedArtifacts(resolvedTarget, artifacts, GENERATOR_VERSION, {
|
|
131
|
+
const result = await materializeOwnedArtifacts(resolvedTarget, artifacts, GENERATOR_VERSION, {
|
|
132
|
+
reconcileScopes: inferReconcileScopes(artifacts),
|
|
133
|
+
dryRun
|
|
134
|
+
});
|
|
135
|
+
if (dryRun) {
|
|
136
|
+
printDryRunSummary(resolvedTarget, result);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
130
139
|
if (versionData) {
|
|
131
140
|
if (!isSwampRepoAvailable()) throw new Error("Swamp repo not initialized. Run `swamp init` or omit --version-data");
|
|
132
141
|
const contextSpec = await buildContextNormalizedSpecFromConfig({
|
|
@@ -159,12 +168,27 @@ async function runContextGenerate(configPath, targetOverride, versionData) {
|
|
|
159
168
|
}
|
|
160
169
|
console.log(`\nMaterialized ${result.files.length} files to ${result.targetRoot}`);
|
|
161
170
|
}
|
|
162
|
-
|
|
163
|
-
const
|
|
171
|
+
function printDryRunSummary(target, result) {
|
|
172
|
+
const counts = {
|
|
173
|
+
write: 0,
|
|
174
|
+
overwrite: 0,
|
|
175
|
+
conflict: 0,
|
|
176
|
+
skip: 0,
|
|
177
|
+
delete: 0
|
|
178
|
+
};
|
|
179
|
+
for (const file of result.files) counts[file.action] = (counts[file.action] ?? 0) + 1;
|
|
180
|
+
console.log(`\nDry run for ${target}:`);
|
|
181
|
+
console.log(` write: ${counts.write} files`);
|
|
182
|
+
console.log(` overwrite: ${counts.overwrite} files`);
|
|
183
|
+
console.log(` delete: ${counts.delete} files`);
|
|
184
|
+
console.log(` skip: ${counts.skip} files`);
|
|
185
|
+
}
|
|
186
|
+
async function runCompositionGenerate(compositionConfigPath, targetOverride, versionData, dryRun) {
|
|
187
|
+
const configModule = await import(new URL(compositionConfigPath, "file:///").href);
|
|
188
|
+
const compositionConfig = CompositionConfigSchema.parse(configModule.default);
|
|
164
189
|
const targetRoot = targetOverride ?? compositionConfig.materialization?.targetRoot;
|
|
165
190
|
if (!targetRoot) throw new Error("No --target specified and composition config has no materialization.targetRoot");
|
|
166
191
|
const resolvedTarget = targetRoot.startsWith("/") ? targetRoot : `${process.cwd()}/${targetRoot}`;
|
|
167
|
-
if (!compositionConfig.contextConfigs || compositionConfig.contextConfigs.length === 0) throw new Error("Composition config must include contextConfigs array with { contextConfigPath, codegenSupportPath } entries");
|
|
168
192
|
const compositionDir = compositionConfigPath.substring(0, compositionConfigPath.lastIndexOf("/") + 1);
|
|
169
193
|
const resolvedContextConfigs = compositionConfig.contextConfigs.map((cfg) => ({
|
|
170
194
|
contextConfigPath: cfg.contextConfigPath.startsWith("/") ? cfg.contextConfigPath : compositionDir + cfg.contextConfigPath.replace(/^\.\//, ""),
|
|
@@ -178,7 +202,14 @@ async function runCompositionGenerate(compositionConfigPath, targetOverride, ver
|
|
|
178
202
|
const compositionArtifacts = await buildCompositionArtifacts(compositionConfigPath, contextSpecs);
|
|
179
203
|
const allArtifacts = mergeGeneratedArtifacts(...perContextArtifacts.map(filterPerContextArtifactsForComposition), compositionArtifacts).sort((a, b) => a.logicalPath.localeCompare(b.logicalPath));
|
|
180
204
|
console.log(` target: ${resolvedTarget}`);
|
|
181
|
-
const result = await materializeOwnedArtifacts(resolvedTarget, allArtifacts, GENERATOR_VERSION, {
|
|
205
|
+
const result = await materializeOwnedArtifacts(resolvedTarget, allArtifacts, GENERATOR_VERSION, {
|
|
206
|
+
reconcileScopes: inferReconcileScopes(allArtifacts),
|
|
207
|
+
dryRun
|
|
208
|
+
});
|
|
209
|
+
if (dryRun) {
|
|
210
|
+
printDryRunSummary(resolvedTarget, result);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
182
213
|
if (versionData) {
|
|
183
214
|
if (!isSwampRepoAvailable()) throw new Error("Swamp repo not initialized. Run `swamp init` or omit --version-data");
|
|
184
215
|
const specJson = serializeCompositionSpec(compositionSpec, GENERATOR_VERSION);
|
|
@@ -208,6 +239,211 @@ async function runCompositionGenerate(compositionConfigPath, targetOverride, ver
|
|
|
208
239
|
console.log(`\nMaterialized ${result.files.length} files to ${result.targetRoot}`);
|
|
209
240
|
}
|
|
210
241
|
|
|
242
|
+
//#endregion
|
|
243
|
+
//#region packages/cli/validate.ts
|
|
244
|
+
async function runCheck(name, fn) {
|
|
245
|
+
try {
|
|
246
|
+
await fn();
|
|
247
|
+
return {
|
|
248
|
+
name,
|
|
249
|
+
pass: true
|
|
250
|
+
};
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return {
|
|
253
|
+
name,
|
|
254
|
+
pass: false,
|
|
255
|
+
error: error instanceof Error ? error.message : String(error)
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async function runValidate(opts) {
|
|
260
|
+
const configPath = opts.configPath.startsWith("/") ? opts.configPath : `${process.cwd()}/${opts.configPath}`;
|
|
261
|
+
const results = [];
|
|
262
|
+
if (opts.composition) {
|
|
263
|
+
results.push(await runCheck("composition-config-import", async () => {
|
|
264
|
+
const configModule = await import(new URL(configPath, "file:///").href);
|
|
265
|
+
CompositionConfigSchema.parse(configModule.default);
|
|
266
|
+
}));
|
|
267
|
+
results.push(await runCheck("composition-context-configs", async () => {
|
|
268
|
+
const configModule = await import(new URL(configPath, "file:///").href);
|
|
269
|
+
const compositionConfig = CompositionConfigSchema.parse(configModule.default);
|
|
270
|
+
const compositionDir = configPath.substring(0, configPath.lastIndexOf("/") + 1);
|
|
271
|
+
for (const cfg of compositionConfig.contextConfigs) {
|
|
272
|
+
const contextConfigPath = cfg.contextConfigPath.startsWith("/") ? cfg.contextConfigPath : compositionDir + cfg.contextConfigPath.replace(/^\.\//, "");
|
|
273
|
+
const codegenSupportPath = cfg.codegenSupportPath.startsWith("/") ? cfg.codegenSupportPath : compositionDir + cfg.codegenSupportPath.replace(/^\.\//, "");
|
|
274
|
+
const resolved = await resolveAbsoluteContextInputs(contextConfigPath);
|
|
275
|
+
await ensureContextSupportFilesExist(resolved);
|
|
276
|
+
await validateContextImports({
|
|
277
|
+
...resolved,
|
|
278
|
+
codegenSupportPath
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}));
|
|
282
|
+
} else {
|
|
283
|
+
results.push(await runCheck("context-inputs-resolve", async () => {
|
|
284
|
+
await resolveAbsoluteContextInputs(configPath);
|
|
285
|
+
}));
|
|
286
|
+
results.push(await runCheck("schema-files-exist", async () => {
|
|
287
|
+
await ensureContextSupportFilesExist(await resolveAbsoluteContextInputs(configPath));
|
|
288
|
+
}));
|
|
289
|
+
results.push(await runCheck("schema-imports-parse", async () => {
|
|
290
|
+
await validateContextImports(await resolveAbsoluteContextInputs(configPath));
|
|
291
|
+
}));
|
|
292
|
+
}
|
|
293
|
+
let hasFailure = false;
|
|
294
|
+
for (const result of results) {
|
|
295
|
+
const icon = result.pass ? "✓" : "✗";
|
|
296
|
+
console.log(` ${icon} ${result.name}`);
|
|
297
|
+
if (!result.pass && result.error) {
|
|
298
|
+
console.log(` ${result.error}`);
|
|
299
|
+
hasFailure = true;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (hasFailure) {
|
|
303
|
+
console.log("\nValidation failed.");
|
|
304
|
+
process.exit(1);
|
|
305
|
+
} else console.log("\nAll checks passed.");
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region packages/cli/inspect.ts
|
|
310
|
+
async function runInspect(opts) {
|
|
311
|
+
const configPath = opts.configPath.startsWith("/") ? opts.configPath : `${process.cwd()}/${opts.configPath}`;
|
|
312
|
+
if (opts.composition) await runCompositionInspect(configPath);
|
|
313
|
+
else await runContextInspect(configPath);
|
|
314
|
+
}
|
|
315
|
+
async function runContextInspect(configPath) {
|
|
316
|
+
const codegenSupportPath = (await resolveAbsoluteContextInputs(configPath)).codegenSupportPath;
|
|
317
|
+
const json = serializeContextSpec(await buildContextNormalizedSpecFromConfig({
|
|
318
|
+
contextConfigPath: configPath,
|
|
319
|
+
codegenSupportPath
|
|
320
|
+
}), GENERATOR_VERSION);
|
|
321
|
+
console.log(json);
|
|
322
|
+
}
|
|
323
|
+
async function runCompositionInspect(compositionConfigPath) {
|
|
324
|
+
const configModule = await import(new URL(compositionConfigPath, "file:///").href);
|
|
325
|
+
const compositionConfig = CompositionConfigSchema.parse(configModule.default);
|
|
326
|
+
const compositionDir = compositionConfigPath.substring(0, compositionConfigPath.lastIndexOf("/") + 1);
|
|
327
|
+
const resolvedContextConfigs = compositionConfig.contextConfigs.map((cfg) => ({
|
|
328
|
+
contextConfigPath: cfg.contextConfigPath.startsWith("/") ? cfg.contextConfigPath : compositionDir + cfg.contextConfigPath.replace(/^\.\//, ""),
|
|
329
|
+
codegenSupportPath: cfg.codegenSupportPath.startsWith("/") ? cfg.codegenSupportPath : compositionDir + cfg.codegenSupportPath.replace(/^\.\//, "")
|
|
330
|
+
}));
|
|
331
|
+
const json = serializeCompositionSpec(buildNormalizedCompositionSpec(compositionConfig, await Promise.all(resolvedContextConfigs.map((cfg) => buildContextNormalizedSpecFromConfig(cfg)))), GENERATOR_VERSION);
|
|
332
|
+
console.log(json);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
//#endregion
|
|
336
|
+
//#region packages/cli/plan_cmd.ts
|
|
337
|
+
async function runPlan(opts) {
|
|
338
|
+
const configPath = opts.configPath.startsWith("/") ? opts.configPath : `${process.cwd()}/${opts.configPath}`;
|
|
339
|
+
if (opts.composition) await runCompositionPlan(configPath);
|
|
340
|
+
else await runContextPlan(configPath);
|
|
341
|
+
}
|
|
342
|
+
async function runContextPlan(configPath) {
|
|
343
|
+
const codegenSupportPath = (await resolveAbsoluteContextInputs(configPath)).codegenSupportPath;
|
|
344
|
+
printPlan(configPath, await buildV5ContextArtifacts({
|
|
345
|
+
contextConfigPath: configPath,
|
|
346
|
+
codegenSupportPath
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
async function runCompositionPlan(compositionConfigPath) {
|
|
350
|
+
const configModule = await import(new URL(compositionConfigPath, "file:///").href);
|
|
351
|
+
const compositionConfig = CompositionConfigSchema.parse(configModule.default);
|
|
352
|
+
const compositionDir = compositionConfigPath.substring(0, compositionConfigPath.lastIndexOf("/") + 1);
|
|
353
|
+
const resolvedContextConfigs = compositionConfig.contextConfigs.map((cfg) => ({
|
|
354
|
+
contextConfigPath: cfg.contextConfigPath.startsWith("/") ? cfg.contextConfigPath : compositionDir + cfg.contextConfigPath.replace(/^\.\//, ""),
|
|
355
|
+
codegenSupportPath: cfg.codegenSupportPath.startsWith("/") ? cfg.codegenSupportPath : compositionDir + cfg.codegenSupportPath.replace(/^\.\//, "")
|
|
356
|
+
}));
|
|
357
|
+
const contextSpecs = await Promise.all(resolvedContextConfigs.map((cfg) => buildContextNormalizedSpecFromConfig(cfg)));
|
|
358
|
+
const compositionSpec = buildNormalizedCompositionSpec(compositionConfig, contextSpecs);
|
|
359
|
+
const perContextArtifacts = contextSpecs.map((contextSpec) => generateContextArtifacts(contextSpec, { infrastructureStrategy: compositionSpec.infrastructure }));
|
|
360
|
+
const compositionArtifacts = await buildCompositionArtifacts(compositionConfigPath, contextSpecs);
|
|
361
|
+
printPlan(compositionConfigPath, mergeGeneratedArtifacts(...perContextArtifacts.map(filterPerContextArtifactsForComposition), compositionArtifacts).sort((a, b) => a.logicalPath.localeCompare(b.logicalPath)));
|
|
362
|
+
}
|
|
363
|
+
function printPlan(configPath, artifacts) {
|
|
364
|
+
console.log(`Plan for ${configPath}:`);
|
|
365
|
+
for (const artifact of artifacts) {
|
|
366
|
+
const label = `[${resolveArtifactOwnership(artifact).ownershipScope}]`;
|
|
367
|
+
console.log(` ${label.padEnd(16)} ${artifact.logicalPath}`);
|
|
368
|
+
}
|
|
369
|
+
console.log(`\n${artifacts.length} artifacts would be generated.`);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
//#endregion
|
|
373
|
+
//#region packages/cli/diff.ts
|
|
374
|
+
async function runDiff(opts) {
|
|
375
|
+
const configPath = opts.configPath.startsWith("/") ? opts.configPath : `${process.cwd()}/${opts.configPath}`;
|
|
376
|
+
if (opts.composition) await runCompositionDiff(configPath, opts.targetOverride);
|
|
377
|
+
else await runContextDiff(configPath, opts.targetOverride);
|
|
378
|
+
}
|
|
379
|
+
async function runContextDiff(configPath, targetOverride) {
|
|
380
|
+
const resolvedInputs = await resolveAbsoluteContextInputs(configPath);
|
|
381
|
+
const config = resolvedInputs.config;
|
|
382
|
+
const codegenSupportPath = resolvedInputs.codegenSupportPath;
|
|
383
|
+
const artifacts = await buildV5ContextArtifacts({
|
|
384
|
+
contextConfigPath: configPath,
|
|
385
|
+
codegenSupportPath
|
|
386
|
+
});
|
|
387
|
+
const targetRoot = targetOverride ?? config.materialization?.targetRoot;
|
|
388
|
+
if (!targetRoot) throw new Error("No --target specified and context config has no materialization.targetRoot");
|
|
389
|
+
printDiff(targetRoot.startsWith("/") ? targetRoot : `${process.cwd()}/${targetRoot}`, artifacts);
|
|
390
|
+
}
|
|
391
|
+
async function runCompositionDiff(compositionConfigPath, targetOverride) {
|
|
392
|
+
const configModule = await import(new URL(compositionConfigPath, "file:///").href);
|
|
393
|
+
const compositionConfig = CompositionConfigSchema.parse(configModule.default);
|
|
394
|
+
const targetRoot = targetOverride ?? compositionConfig.materialization?.targetRoot;
|
|
395
|
+
if (!targetRoot) throw new Error("No --target specified and composition config has no materialization.targetRoot");
|
|
396
|
+
const resolvedTarget = targetRoot.startsWith("/") ? targetRoot : `${process.cwd()}/${targetRoot}`;
|
|
397
|
+
const compositionDir = compositionConfigPath.substring(0, compositionConfigPath.lastIndexOf("/") + 1);
|
|
398
|
+
const resolvedContextConfigs = compositionConfig.contextConfigs.map((cfg) => ({
|
|
399
|
+
contextConfigPath: cfg.contextConfigPath.startsWith("/") ? cfg.contextConfigPath : compositionDir + cfg.contextConfigPath.replace(/^\.\//, ""),
|
|
400
|
+
codegenSupportPath: cfg.codegenSupportPath.startsWith("/") ? cfg.codegenSupportPath : compositionDir + cfg.codegenSupportPath.replace(/^\.\//, "")
|
|
401
|
+
}));
|
|
402
|
+
const contextSpecs = await Promise.all(resolvedContextConfigs.map((cfg) => buildContextNormalizedSpecFromConfig(cfg)));
|
|
403
|
+
const compositionSpec = buildNormalizedCompositionSpec(compositionConfig, contextSpecs);
|
|
404
|
+
const perContextArtifacts = contextSpecs.map((contextSpec) => generateContextArtifacts(contextSpec, { infrastructureStrategy: compositionSpec.infrastructure }));
|
|
405
|
+
const compositionArtifacts = await buildCompositionArtifacts(compositionConfigPath, contextSpecs);
|
|
406
|
+
printDiff(resolvedTarget, mergeGeneratedArtifacts(...perContextArtifacts.map(filterPerContextArtifactsForComposition), compositionArtifacts).sort((a, b) => a.logicalPath.localeCompare(b.logicalPath)));
|
|
407
|
+
}
|
|
408
|
+
async function printDiff(targetRoot, artifacts) {
|
|
409
|
+
const manifestPath = getPersistedMaterializationManifestPath(targetRoot);
|
|
410
|
+
let manifestEntries;
|
|
411
|
+
try {
|
|
412
|
+
const manifestText = await readFile(manifestPath, "utf-8");
|
|
413
|
+
const manifest = MaterializationManifestSchema.parse(JSON.parse(manifestText));
|
|
414
|
+
manifestEntries = new Set(manifest.files.map((f) => f.logicalPath));
|
|
415
|
+
} catch (error) {
|
|
416
|
+
if (error.code === "ENOENT") {
|
|
417
|
+
console.log(`No existing manifest found at ${manifestPath}`);
|
|
418
|
+
console.log(`All ${artifacts.length} artifacts would be new.\n`);
|
|
419
|
+
for (const artifact of artifacts) console.log(` + ${artifact.logicalPath}`);
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
throw error;
|
|
423
|
+
}
|
|
424
|
+
const plannedPaths = new Set(artifacts.map((a) => a.logicalPath));
|
|
425
|
+
const added = [];
|
|
426
|
+
const removed = [];
|
|
427
|
+
const unchanged = [];
|
|
428
|
+
for (const artifact of artifacts) if (manifestEntries.has(artifact.logicalPath)) unchanged.push(artifact.logicalPath);
|
|
429
|
+
else added.push(artifact.logicalPath);
|
|
430
|
+
for (const existingPath of manifestEntries) if (!plannedPaths.has(existingPath)) removed.push(existingPath);
|
|
431
|
+
console.log(`Diff against ${manifestPath}:\n`);
|
|
432
|
+
if (added.length > 0) {
|
|
433
|
+
console.log(` Added (${added.length}):`);
|
|
434
|
+
for (const path of added) console.log(` + ${path}`);
|
|
435
|
+
}
|
|
436
|
+
if (removed.length > 0) {
|
|
437
|
+
console.log(` Removed (${removed.length}):`);
|
|
438
|
+
for (const path of removed) console.log(` - ${path}`);
|
|
439
|
+
}
|
|
440
|
+
if (unchanged.length > 0) {
|
|
441
|
+
console.log(` Unchanged (${unchanged.length}):`);
|
|
442
|
+
for (const path of unchanged) console.log(` = ${path}`);
|
|
443
|
+
}
|
|
444
|
+
console.log(`\nSummary: ${added.length} added, ${removed.length} removed, ${unchanged.length} unchanged`);
|
|
445
|
+
}
|
|
446
|
+
|
|
211
447
|
//#endregion
|
|
212
448
|
//#region packages/cli/mod.ts
|
|
213
449
|
register(`data:text/javascript,${encodeURIComponent(`
|
|
@@ -226,20 +462,29 @@ function printUsage() {
|
|
|
226
462
|
|
|
227
463
|
Usage:
|
|
228
464
|
zodmire init --target <dir>
|
|
229
|
-
zodmire generate --config <path> [--composition] [--target <dir>] [--version-data]
|
|
465
|
+
zodmire generate --config <path> [--composition] [--target <dir>] [--version-data] [--dry-run]
|
|
466
|
+
zodmire validate --config <path> [--composition]
|
|
467
|
+
zodmire inspect-spec --config <path> [--composition]
|
|
468
|
+
zodmire plan --config <path> [--composition] [--target <dir>]
|
|
469
|
+
zodmire diff --config <path> [--composition] [--target <dir>]
|
|
230
470
|
zodmire --version
|
|
231
471
|
zodmire --help
|
|
232
472
|
|
|
233
473
|
Commands:
|
|
234
|
-
init
|
|
235
|
-
generate
|
|
474
|
+
init Scaffold codegen helper files into your project
|
|
475
|
+
generate Generate a bounded context skeleton from Zod schemas
|
|
476
|
+
validate Run pre-flight checks on context or composition configs
|
|
477
|
+
inspect-spec Print the normalized spec as JSON
|
|
478
|
+
plan Show what artifacts would be generated
|
|
479
|
+
diff Compare planned output against existing manifest
|
|
236
480
|
|
|
237
481
|
Options:
|
|
238
482
|
--target For init: directory to write helpers into
|
|
239
|
-
For generate: override materialization target directory
|
|
483
|
+
For generate/plan/diff: override materialization target directory
|
|
240
484
|
--config Path to context config (.context.ts) or composition config
|
|
241
485
|
--composition Treat --config as a composition config (multi-context)
|
|
242
486
|
--version-data Mirror artifacts to Swamp data layer (requires .swamp/ repo)
|
|
487
|
+
--dry-run Run full pipeline but skip file writes (generate only)
|
|
243
488
|
--version Print version
|
|
244
489
|
--help Print this help
|
|
245
490
|
`);
|
|
@@ -252,6 +497,7 @@ function parseArgs(args) {
|
|
|
252
497
|
const arg = args[i];
|
|
253
498
|
if (arg === "--composition") flags.composition = true;
|
|
254
499
|
else if (arg === "--version-data") flags["version-data"] = true;
|
|
500
|
+
else if (arg === "--dry-run") flags["dry-run"] = true;
|
|
255
501
|
else if (arg.startsWith("--") && i + 1 < args.length && !args[i + 1].startsWith("--")) {
|
|
256
502
|
flags[arg.slice(2)] = args[i + 1];
|
|
257
503
|
i++;
|
|
@@ -292,7 +538,58 @@ async function main() {
|
|
|
292
538
|
configPath: config,
|
|
293
539
|
composition: flags.composition === true,
|
|
294
540
|
targetOverride: typeof flags.target === "string" ? flags.target : void 0,
|
|
295
|
-
versionData: flags["version-data"] === true
|
|
541
|
+
versionData: flags["version-data"] === true,
|
|
542
|
+
dryRun: flags["dry-run"] === true
|
|
543
|
+
});
|
|
544
|
+
break;
|
|
545
|
+
}
|
|
546
|
+
case "validate": {
|
|
547
|
+
const config = flags.config;
|
|
548
|
+
if (typeof config !== "string") {
|
|
549
|
+
console.error("Error: --config <path> is required for validate");
|
|
550
|
+
process.exit(1);
|
|
551
|
+
}
|
|
552
|
+
await runValidate({
|
|
553
|
+
configPath: config,
|
|
554
|
+
composition: flags.composition === true
|
|
555
|
+
});
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
case "inspect-spec": {
|
|
559
|
+
const config = flags.config;
|
|
560
|
+
if (typeof config !== "string") {
|
|
561
|
+
console.error("Error: --config <path> is required for inspect-spec");
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
await runInspect({
|
|
565
|
+
configPath: config,
|
|
566
|
+
composition: flags.composition === true
|
|
567
|
+
});
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
case "plan": {
|
|
571
|
+
const config = flags.config;
|
|
572
|
+
if (typeof config !== "string") {
|
|
573
|
+
console.error("Error: --config <path> is required for plan");
|
|
574
|
+
process.exit(1);
|
|
575
|
+
}
|
|
576
|
+
await runPlan({
|
|
577
|
+
configPath: config,
|
|
578
|
+
composition: flags.composition === true,
|
|
579
|
+
targetOverride: typeof flags.target === "string" ? flags.target : void 0
|
|
580
|
+
});
|
|
581
|
+
break;
|
|
582
|
+
}
|
|
583
|
+
case "diff": {
|
|
584
|
+
const config = flags.config;
|
|
585
|
+
if (typeof config !== "string") {
|
|
586
|
+
console.error("Error: --config <path> is required for diff");
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
await runDiff({
|
|
590
|
+
configPath: config,
|
|
591
|
+
composition: flags.composition === true,
|
|
592
|
+
targetOverride: typeof flags.target === "string" ? flags.target : void 0
|
|
296
593
|
});
|
|
297
594
|
break;
|
|
298
595
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zodmire/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./mod.mjs",
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@zodmire/config": "^0.1.
|
|
17
|
-
"@zodmire/core": "^0.1.
|
|
16
|
+
"@zodmire/config": "^0.1.3",
|
|
17
|
+
"@zodmire/core": "^0.1.3"
|
|
18
18
|
},
|
|
19
19
|
"bin": {
|
|
20
20
|
"zodmire": "./bin/zodmire.js"
|