@zodmire/cli 0.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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present Vercel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/bin/zodmire.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../mod.mjs";
package/mod.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/mod.mjs ADDED
@@ -0,0 +1,299 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { fileURLToPath } from "node:url";
3
+ import { dirname, resolve } from "node:path";
4
+ import { VENDORED_FILE_MANIFEST, buildCompositionArtifacts, buildContextNormalizedSpecFromConfig, buildFileArtifactTags, buildNormalizedCompositionSpec, buildNormalizedSpecTags, buildV5ContextArtifacts, filterPerContextArtifactsForComposition, inferReconcileScopes, mergeGeneratedArtifacts, prepareAllVendoredFiles, resolveAbsoluteContextInputs, serializeCompositionSpec, serializeContextSpec } from "@zodmire/core";
5
+ import { materializeOwnedArtifacts } from "@zodmire/core/materialize";
6
+ import { statSync } from "node:fs";
7
+ import { spawn } from "node:child_process";
8
+
9
+ //#region packages/cli/version.ts
10
+ const GENERATOR_VERSION = "2026.03.26.1";
11
+
12
+ //#endregion
13
+ //#region packages/cli/init.ts
14
+ function resolveSourcePath(relativePath) {
15
+ return `${`${dirname(fileURLToPath(import.meta.url))}/../..`}/${relativePath}`;
16
+ }
17
+ async function runInit(targetDir) {
18
+ const sourceContents = /* @__PURE__ */ new Map();
19
+ for (const entry of VENDORED_FILE_MANIFEST) {
20
+ const path = resolveSourcePath(entry.sourceRelativePath);
21
+ sourceContents.set(entry.sourceRelativePath, await readFile(path, "utf-8"));
22
+ }
23
+ const prepared = prepareAllVendoredFiles(sourceContents, GENERATOR_VERSION);
24
+ await mkdir(targetDir, { recursive: true });
25
+ for (const file of prepared) {
26
+ const destPath = `${targetDir}/${file.outputName}`;
27
+ await writeFile(destPath, file.content, "utf-8");
28
+ console.log(` wrote ${destPath}`);
29
+ }
30
+ console.log(`\nInit complete. ${prepared.length} files written to ${targetDir}`);
31
+ }
32
+
33
+ //#endregion
34
+ //#region packages/cli/version_data.ts
35
+ function isSwampRepoAvailable(basePath = ".") {
36
+ try {
37
+ return statSync(resolve(basePath, ".swamp")).isDirectory();
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+ function spawnSwampDataPut(args, stdinContent) {
43
+ return new Promise((resolve, reject) => {
44
+ const proc = spawn("swamp", args, { stdio: [
45
+ "pipe",
46
+ "ignore",
47
+ "pipe"
48
+ ] });
49
+ let stderr = "";
50
+ proc.stderr.on("data", (chunk) => {
51
+ stderr += chunk.toString();
52
+ });
53
+ proc.on("close", (code) => {
54
+ if (code !== 0) reject(/* @__PURE__ */ new Error(`swamp data put failed: ${stderr}`));
55
+ else resolve();
56
+ });
57
+ proc.stdin.write(stdinContent);
58
+ proc.stdin.end();
59
+ });
60
+ }
61
+ async function mirrorToSwampDataLayer(opts) {
62
+ const { modelName, artifacts, specJson, specDataName, generatorVersion, generationRunId, methodName, basis, contextName, compositionName, producer, producerName } = opts;
63
+ for (const artifact of artifacts) {
64
+ const tagArgs = Object.entries(buildFileArtifactTags({
65
+ logicalPath: artifact.logicalPath,
66
+ generatorVersion,
67
+ generationRunId,
68
+ methodName,
69
+ producer,
70
+ producerName,
71
+ basis,
72
+ contextName,
73
+ compositionName,
74
+ ownershipScope: artifact.ownershipScope,
75
+ scopeKey: artifact.scopeKey
76
+ })).map(([key, value]) => `${key}=${value}`).flatMap((t) => ["--tag", t]);
77
+ await spawnSwampDataPut([
78
+ "data",
79
+ "put",
80
+ modelName,
81
+ artifact.dataName,
82
+ "--content-type",
83
+ "text/plain",
84
+ ...tagArgs
85
+ ], artifact.content);
86
+ }
87
+ if (specJson !== void 0 && specDataName !== void 0) await spawnSwampDataPut([
88
+ "data",
89
+ "put",
90
+ modelName,
91
+ specDataName,
92
+ "--content-type",
93
+ "application/json",
94
+ ...Object.entries(buildNormalizedSpecTags({
95
+ generatorVersion,
96
+ generationRunId,
97
+ methodName,
98
+ producer,
99
+ producerName,
100
+ basis,
101
+ contextName,
102
+ compositionName
103
+ })).map(([key, value]) => `${key}=${value}`).flatMap((t) => ["--tag", t])
104
+ ], specJson);
105
+ }
106
+
107
+ //#endregion
108
+ //#region packages/cli/generate.ts
109
+ async function runGenerate(opts) {
110
+ const configPath = opts.configPath.startsWith("/") ? opts.configPath : `${process.cwd()}/${opts.configPath}`;
111
+ if (opts.composition) await runCompositionGenerate(configPath, opts.targetOverride, opts.versionData);
112
+ else await runContextGenerate(configPath, opts.targetOverride, opts.versionData);
113
+ }
114
+ async function runContextGenerate(configPath, targetOverride, versionData) {
115
+ const resolvedInputs = await resolveAbsoluteContextInputs(configPath);
116
+ const config = resolvedInputs.config;
117
+ const codegenSupportPath = resolvedInputs.codegenSupportPath;
118
+ console.log(`Generating context from ${configPath}`);
119
+ console.log(` codegen support: ${codegenSupportPath}`);
120
+ const artifacts = await buildV5ContextArtifacts({
121
+ contextConfigPath: configPath,
122
+ codegenSupportPath
123
+ });
124
+ const targetRoot = targetOverride ?? config.materialization?.targetRoot;
125
+ if (!targetRoot) throw new Error("No --target specified and context config has no materialization.targetRoot");
126
+ const resolvedTarget = targetRoot.startsWith("/") ? targetRoot : `${process.cwd()}/${targetRoot}`;
127
+ console.log(` target: ${resolvedTarget}`);
128
+ const result = await materializeOwnedArtifacts(resolvedTarget, artifacts, GENERATOR_VERSION, { reconcileScopes: inferReconcileScopes(artifacts) });
129
+ if (versionData) {
130
+ if (!isSwampRepoAvailable()) throw new Error("Swamp repo not initialized. Run `swamp init` or omit --version-data");
131
+ const contextSpec = await buildContextNormalizedSpecFromConfig({
132
+ contextConfigPath: configPath,
133
+ codegenSupportPath
134
+ });
135
+ const specJson = serializeContextSpec(contextSpec, GENERATOR_VERSION);
136
+ const generationRunId = `run-${(/* @__PURE__ */ new Date()).toISOString()}-${crypto.randomUUID().slice(0, 8)}`;
137
+ console.log(" mirroring to Swamp data layer...");
138
+ await mirrorToSwampDataLayer({
139
+ modelName: "codegen",
140
+ artifacts: artifacts.map((a) => ({
141
+ logicalPath: a.logicalPath,
142
+ dataName: a.logicalPath.replace(/\//g, ":"),
143
+ content: a.content,
144
+ ownershipScope: a.ownershipScope,
145
+ scopeKey: a.scopeKey
146
+ })),
147
+ specJson,
148
+ specDataName: `context-normalized-spec:${contextSpec.context.modulePath}`,
149
+ generatorVersion: GENERATOR_VERSION,
150
+ generationRunId,
151
+ methodName: "generate",
152
+ basis: "context",
153
+ contextName: contextSpec.context.name,
154
+ producer: "cli",
155
+ producerName: "zodmire"
156
+ });
157
+ console.log(" mirrored to Swamp data layer");
158
+ }
159
+ console.log(`\nMaterialized ${result.files.length} files to ${result.targetRoot}`);
160
+ }
161
+ async function runCompositionGenerate(compositionConfigPath, targetOverride, versionData) {
162
+ const compositionConfig = (await import(new URL(compositionConfigPath, "file:///").href)).default;
163
+ const targetRoot = targetOverride ?? compositionConfig.materialization?.targetRoot;
164
+ if (!targetRoot) throw new Error("No --target specified and composition config has no materialization.targetRoot");
165
+ const resolvedTarget = targetRoot.startsWith("/") ? targetRoot : `${process.cwd()}/${targetRoot}`;
166
+ if (!compositionConfig.contextConfigs || compositionConfig.contextConfigs.length === 0) throw new Error("Composition config must include contextConfigs array with { contextConfigPath, codegenSupportPath } entries");
167
+ const compositionDir = compositionConfigPath.substring(0, compositionConfigPath.lastIndexOf("/") + 1);
168
+ const resolvedContextConfigs = compositionConfig.contextConfigs.map((cfg) => ({
169
+ contextConfigPath: cfg.contextConfigPath.startsWith("/") ? cfg.contextConfigPath : compositionDir + cfg.contextConfigPath.replace(/^\.\//, ""),
170
+ codegenSupportPath: cfg.codegenSupportPath.startsWith("/") ? cfg.codegenSupportPath : compositionDir + cfg.codegenSupportPath.replace(/^\.\//, "")
171
+ }));
172
+ console.log(`Generating composition from ${compositionConfigPath}`);
173
+ console.log(` contexts: ${resolvedContextConfigs.length}`);
174
+ const contextSpecs = await Promise.all(resolvedContextConfigs.map((cfg) => buildContextNormalizedSpecFromConfig(cfg)));
175
+ const perContextArtifacts = await Promise.all(resolvedContextConfigs.map((cfg) => buildV5ContextArtifacts(cfg)));
176
+ const compositionArtifacts = await buildCompositionArtifacts(compositionConfigPath, contextSpecs);
177
+ const allArtifacts = mergeGeneratedArtifacts(...perContextArtifacts.map(filterPerContextArtifactsForComposition), compositionArtifacts).sort((a, b) => a.logicalPath.localeCompare(b.logicalPath));
178
+ console.log(` target: ${resolvedTarget}`);
179
+ const result = await materializeOwnedArtifacts(resolvedTarget, allArtifacts, GENERATOR_VERSION, { reconcileScopes: inferReconcileScopes(allArtifacts) });
180
+ if (versionData) {
181
+ if (!isSwampRepoAvailable()) throw new Error("Swamp repo not initialized. Run `swamp init` or omit --version-data");
182
+ const compositionSpec = buildNormalizedCompositionSpec(compositionConfig, contextSpecs);
183
+ const specJson = serializeCompositionSpec(compositionSpec, GENERATOR_VERSION);
184
+ const generationRunId = `run-${(/* @__PURE__ */ new Date()).toISOString()}-${crypto.randomUUID().slice(0, 8)}`;
185
+ console.log(" mirroring to Swamp data layer...");
186
+ await mirrorToSwampDataLayer({
187
+ modelName: "codegen",
188
+ artifacts: allArtifacts.map((a) => ({
189
+ logicalPath: a.logicalPath,
190
+ dataName: a.logicalPath.replace(/\//g, ":"),
191
+ content: a.content,
192
+ ownershipScope: a.ownershipScope,
193
+ scopeKey: a.scopeKey
194
+ })),
195
+ specJson,
196
+ specDataName: `composition-normalized-spec:${compositionSpec.name}`,
197
+ generatorVersion: GENERATOR_VERSION,
198
+ generationRunId,
199
+ methodName: "compose",
200
+ basis: "composition",
201
+ compositionName: compositionSpec.name,
202
+ producer: "cli",
203
+ producerName: "zodmire"
204
+ });
205
+ console.log(" mirrored to Swamp data layer");
206
+ }
207
+ console.log(`\nMaterialized ${result.files.length} files to ${result.targetRoot}`);
208
+ }
209
+
210
+ //#endregion
211
+ //#region packages/cli/mod.ts
212
+ function printUsage() {
213
+ console.log(`zodmire v${GENERATOR_VERSION}
214
+
215
+ Usage:
216
+ zodmire init --target <dir>
217
+ zodmire generate --config <path> [--composition] [--target <dir>] [--version-data]
218
+ zodmire --version
219
+ zodmire --help
220
+
221
+ Commands:
222
+ init Scaffold codegen helper files into your project
223
+ generate Generate a bounded context skeleton from Zod schemas
224
+
225
+ Options:
226
+ --target For init: directory to write helpers into
227
+ For generate: override materialization target directory
228
+ --config Path to context config (.context.ts) or composition config
229
+ --composition Treat --config as a composition config (multi-context)
230
+ --version-data Mirror artifacts to Swamp data layer (requires .swamp/ repo)
231
+ --version Print version
232
+ --help Print this help
233
+ `);
234
+ }
235
+ function parseArgs(args) {
236
+ const command = args[0] && !args[0].startsWith("--") ? args[0] : "";
237
+ const flags = {};
238
+ const startIdx = command ? 1 : 0;
239
+ for (let i = startIdx; i < args.length; i++) {
240
+ const arg = args[i];
241
+ if (arg === "--composition") flags.composition = true;
242
+ else if (arg === "--version-data") flags["version-data"] = true;
243
+ else if (arg.startsWith("--") && i + 1 < args.length && !args[i + 1].startsWith("--")) {
244
+ flags[arg.slice(2)] = args[i + 1];
245
+ i++;
246
+ } else if (arg.startsWith("--")) flags[arg.slice(2)] = true;
247
+ }
248
+ return {
249
+ command,
250
+ flags
251
+ };
252
+ }
253
+ async function main() {
254
+ const { command, flags } = parseArgs(process.argv.slice(2));
255
+ if (flags.version) {
256
+ console.log(GENERATOR_VERSION);
257
+ return;
258
+ }
259
+ if (flags.help || !command) {
260
+ printUsage();
261
+ return;
262
+ }
263
+ switch (command) {
264
+ case "init": {
265
+ const target = flags.target;
266
+ if (typeof target !== "string") {
267
+ console.error("Error: --target <dir> is required for init");
268
+ process.exit(1);
269
+ }
270
+ await runInit(target);
271
+ break;
272
+ }
273
+ case "generate": {
274
+ const config = flags.config;
275
+ if (typeof config !== "string") {
276
+ console.error("Error: --config <path> is required for generate");
277
+ process.exit(1);
278
+ }
279
+ await runGenerate({
280
+ configPath: config,
281
+ composition: flags.composition === true,
282
+ targetOverride: typeof flags.target === "string" ? flags.target : void 0,
283
+ versionData: flags["version-data"] === true
284
+ });
285
+ break;
286
+ }
287
+ default:
288
+ console.error(`Unknown command: ${command}`);
289
+ printUsage();
290
+ process.exit(1);
291
+ }
292
+ }
293
+ main().catch((err) => {
294
+ console.error(`Error: ${err.message}`);
295
+ process.exit(1);
296
+ });
297
+
298
+ //#endregion
299
+ export { };
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "@zodmire/cli",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "./mod.mjs",
7
+ "module": "./mod.mjs",
8
+ "types": "./mod.d.mts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./mod.d.mts",
12
+ "import": "./mod.mjs"
13
+ }
14
+ },
15
+ "dependencies": {
16
+ "@zodmire/config": "^0.1.0",
17
+ "@zodmire/core": "^0.1.0"
18
+ },
19
+ "bin": {
20
+ "zodmire": "./bin/zodmire.js"
21
+ }
22
+ }