@maykonpaulo/maestro-cli 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +457 -0
- package/dist/chunk-CSDLIL7L.js +1309 -0
- package/dist/chunk-CSDLIL7L.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +13 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +338 -0
- package/dist/index.js +61 -0
- package/dist/index.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,1309 @@
|
|
|
1
|
+
// src/commands/registry.ts
|
|
2
|
+
var CommandRegistry = class {
|
|
3
|
+
commands = /* @__PURE__ */ new Map();
|
|
4
|
+
register(command) {
|
|
5
|
+
if (this.commands.has(command.name)) {
|
|
6
|
+
throw new Error(`Command '${command.name}' is already registered.`);
|
|
7
|
+
}
|
|
8
|
+
this.commands.set(command.name, command);
|
|
9
|
+
}
|
|
10
|
+
get(name) {
|
|
11
|
+
return this.commands.get(name);
|
|
12
|
+
}
|
|
13
|
+
list() {
|
|
14
|
+
return [...this.commands.values()];
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/commands/planned.ts
|
|
19
|
+
var PLANNED_COMMANDS = [];
|
|
20
|
+
|
|
21
|
+
// src/parseArgs.ts
|
|
22
|
+
function parseArgs(argv) {
|
|
23
|
+
const help = argv.includes("--help") || argv.includes("-h");
|
|
24
|
+
const version = argv.includes("--version") || argv.includes("-v");
|
|
25
|
+
const [first, ...rest] = argv;
|
|
26
|
+
const hasCommand = first !== void 0 && !first.startsWith("-");
|
|
27
|
+
return {
|
|
28
|
+
command: hasCommand ? first : void 0,
|
|
29
|
+
args: hasCommand ? rest : argv,
|
|
30
|
+
help,
|
|
31
|
+
version
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/help.ts
|
|
36
|
+
function formatHelp(registry) {
|
|
37
|
+
const lines = [
|
|
38
|
+
"maestro \u2014 Maestro CLI",
|
|
39
|
+
"",
|
|
40
|
+
"Command-line interface for the Maestro declarative engine.",
|
|
41
|
+
"",
|
|
42
|
+
"Usage:",
|
|
43
|
+
" maestro <command> [options]",
|
|
44
|
+
"",
|
|
45
|
+
"Options:",
|
|
46
|
+
" -h, --help Show this help message",
|
|
47
|
+
" -v, --version Show the CLI version",
|
|
48
|
+
""
|
|
49
|
+
];
|
|
50
|
+
const available = registry.list();
|
|
51
|
+
if (available.length > 0) {
|
|
52
|
+
lines.push("Available commands:");
|
|
53
|
+
for (const command of available) {
|
|
54
|
+
lines.push(` ${command.name.padEnd(12)} ${command.description}`);
|
|
55
|
+
}
|
|
56
|
+
lines.push("");
|
|
57
|
+
}
|
|
58
|
+
if (PLANNED_COMMANDS.length > 0) {
|
|
59
|
+
lines.push("Planned commands (not yet implemented):");
|
|
60
|
+
for (const planned of PLANNED_COMMANDS) {
|
|
61
|
+
lines.push(` ${planned.name.padEnd(12)} ${planned.description}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return lines.join("\n");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/version.ts
|
|
68
|
+
import { createRequire } from "module";
|
|
69
|
+
var require2 = createRequire(import.meta.url);
|
|
70
|
+
function getCliVersion() {
|
|
71
|
+
const pkg = require2("../package.json");
|
|
72
|
+
return pkg.version;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// src/commands/generate/parseGenerateArgs.ts
|
|
76
|
+
function parseFlags(argv) {
|
|
77
|
+
const flags = {};
|
|
78
|
+
for (let i = 0; i < argv.length; i++) {
|
|
79
|
+
const arg = argv[i];
|
|
80
|
+
if (!arg.startsWith("--")) continue;
|
|
81
|
+
const eqIndex = arg.indexOf("=");
|
|
82
|
+
if (eqIndex !== -1) {
|
|
83
|
+
flags[arg.slice(2, eqIndex)] = arg.slice(eqIndex + 1);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const key = arg.slice(2);
|
|
87
|
+
const next = argv[i + 1];
|
|
88
|
+
if (next !== void 0 && !next.startsWith("--")) {
|
|
89
|
+
flags[key] = next;
|
|
90
|
+
i++;
|
|
91
|
+
} else {
|
|
92
|
+
flags[key] = "true";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return flags;
|
|
96
|
+
}
|
|
97
|
+
function parseGenerateArgs(argv) {
|
|
98
|
+
const flags = parseFlags(argv);
|
|
99
|
+
const input = flags["input"];
|
|
100
|
+
if (!input) {
|
|
101
|
+
return { ok: false, error: "Missing required flag --input <path>." };
|
|
102
|
+
}
|
|
103
|
+
const type = flags["type"];
|
|
104
|
+
if (type !== "metadata" && type !== "schema") {
|
|
105
|
+
return {
|
|
106
|
+
ok: false,
|
|
107
|
+
error: `Missing or invalid --type flag. Expected "metadata" or "schema"${type ? `, got "${type}"` : ""}.`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const format = flags["format"] ?? "json";
|
|
111
|
+
if (format !== "json" && format !== "yaml") {
|
|
112
|
+
return { ok: false, error: `Invalid --format flag. Expected "json" or "yaml", got "${format}".` };
|
|
113
|
+
}
|
|
114
|
+
return { ok: true, value: { input, type, format, out: flags["out"] } };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/commands/generate/GenerateCommand.ts
|
|
118
|
+
import { readFileSync, writeFileSync } from "fs";
|
|
119
|
+
import * as YAML from "yaml";
|
|
120
|
+
import {
|
|
121
|
+
generateDeclarativeConfigFromMetadata,
|
|
122
|
+
generateDeclarativeConfigFromSchema,
|
|
123
|
+
serializeDeclarativeConfig
|
|
124
|
+
} from "@maykonpaulo/maestro-core";
|
|
125
|
+
var defaultDeps = {
|
|
126
|
+
readFile: (path) => readFileSync(path, "utf-8"),
|
|
127
|
+
writeFile: (path, content) => writeFileSync(path, content, "utf-8")
|
|
128
|
+
};
|
|
129
|
+
function errorMessage(error) {
|
|
130
|
+
return error instanceof Error ? error.message : String(error);
|
|
131
|
+
}
|
|
132
|
+
var GENERATE_HELP_TEXT = `maestro generate \u2014 Generate a declarative config from a local metadata/schema JSON file
|
|
133
|
+
|
|
134
|
+
Reads a local JSON file containing already-built EntityMetadata[] (--type metadata) or
|
|
135
|
+
EntitySchema[] (--type schema) and generates a declarative config (EntityDeclaration[], plus an
|
|
136
|
+
optional default consumer) using the core's Declarative Config Generator. The result is written to
|
|
137
|
+
stdout, or to a file with --out.
|
|
138
|
+
|
|
139
|
+
This command does NOT perform introspection, does NOT connect to any database or datasource, and
|
|
140
|
+
does NOT create a Maestro engine \u2014 it only reads a file, transforms it, and writes text. The input
|
|
141
|
+
must already be metadata/schema you produced some other way (e.g. via engine.getMetadata(), or a
|
|
142
|
+
hand-written EntitySchema[]). Introspecting a live datasource is a separate, not-yet-implemented
|
|
143
|
+
command ("maestro introspect").
|
|
144
|
+
|
|
145
|
+
Usage:
|
|
146
|
+
maestro generate --input <path> --type <metadata|schema> [--format <json|yaml>] [--out <path>]
|
|
147
|
+
|
|
148
|
+
Flags:
|
|
149
|
+
--input <path> Required. Path to a local JSON file.
|
|
150
|
+
--type <metadata|schema> Required. Whether --input holds EntityMetadata[] or EntitySchema[].
|
|
151
|
+
--format <json|yaml> Optional. Output format. Default: json.
|
|
152
|
+
--out <path> Optional. Write the result to this file instead of stdout.
|
|
153
|
+
|
|
154
|
+
Examples:
|
|
155
|
+
maestro generate --input ./metadata.json --type metadata --format json
|
|
156
|
+
maestro generate --input ./metadata.json --type metadata --format yaml --out ./maestro.config.yaml
|
|
157
|
+
maestro generate --input ./schema.json --type schema --format json --out ./maestro.config.json
|
|
158
|
+
|
|
159
|
+
Recommended flow:
|
|
160
|
+
1. Produce EntityMetadata[] or EntitySchema[] as JSON (e.g. JSON.stringify(engine.getMetadata().entities)).
|
|
161
|
+
2. maestro generate --input <that file> --type <metadata|schema> --out maestro.config.yaml
|
|
162
|
+
3. Load it back: loadDeclarativeConfigFromFile('./maestro.config.yaml', { yamlParser })
|
|
163
|
+
4. Pass the result to createMaestro({ declarations })`;
|
|
164
|
+
function assertGeneratorInput(value, sourcePath) {
|
|
165
|
+
const isObject2 = typeof value === "object" && value !== null && !Array.isArray(value);
|
|
166
|
+
const hasEntities = isObject2 && Array.isArray(value["entities"]);
|
|
167
|
+
if (!hasEntities) {
|
|
168
|
+
throw new Error(`'${sourcePath}' must be a JSON object with an "entities" array.`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function createGenerateCommand(deps = defaultDeps) {
|
|
172
|
+
return {
|
|
173
|
+
name: "generate",
|
|
174
|
+
description: "Generate a declarative config from a local metadata/schema JSON file",
|
|
175
|
+
helpText: GENERATE_HELP_TEXT,
|
|
176
|
+
run(context) {
|
|
177
|
+
const parsed = parseGenerateArgs(context.argv);
|
|
178
|
+
if (!parsed.ok) {
|
|
179
|
+
context.stderr(`maestro generate: ${parsed.error}`);
|
|
180
|
+
return 1;
|
|
181
|
+
}
|
|
182
|
+
const { input, type, format, out } = parsed.value;
|
|
183
|
+
let rawInput;
|
|
184
|
+
try {
|
|
185
|
+
rawInput = deps.readFile(input);
|
|
186
|
+
} catch (error) {
|
|
187
|
+
context.stderr(`maestro generate: could not read input file '${input}': ${errorMessage(error)}`);
|
|
188
|
+
return 1;
|
|
189
|
+
}
|
|
190
|
+
let parsedInput;
|
|
191
|
+
try {
|
|
192
|
+
parsedInput = JSON.parse(rawInput);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
context.stderr(`maestro generate: '${input}' is not valid JSON: ${errorMessage(error)}`);
|
|
195
|
+
return 1;
|
|
196
|
+
}
|
|
197
|
+
let generated;
|
|
198
|
+
try {
|
|
199
|
+
assertGeneratorInput(parsedInput, input);
|
|
200
|
+
generated = type === "metadata" ? generateDeclarativeConfigFromMetadata(parsedInput) : generateDeclarativeConfigFromSchema(parsedInput);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
context.stderr(`maestro generate: failed to generate declarative config: ${errorMessage(error)}`);
|
|
203
|
+
return 1;
|
|
204
|
+
}
|
|
205
|
+
let output;
|
|
206
|
+
try {
|
|
207
|
+
output = serializeDeclarativeConfig(generated, {
|
|
208
|
+
format,
|
|
209
|
+
yamlSerializer: format === "yaml" ? { stringify: (value) => YAML.stringify(value) } : void 0
|
|
210
|
+
});
|
|
211
|
+
} catch (error) {
|
|
212
|
+
context.stderr(`maestro generate: ${errorMessage(error)}`);
|
|
213
|
+
return 1;
|
|
214
|
+
}
|
|
215
|
+
if (out) {
|
|
216
|
+
try {
|
|
217
|
+
deps.writeFile(out, output);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
context.stderr(`maestro generate: could not write output file '${out}': ${errorMessage(error)}`);
|
|
220
|
+
return 1;
|
|
221
|
+
}
|
|
222
|
+
context.stdout(`Declarative config written to ${out}`);
|
|
223
|
+
} else {
|
|
224
|
+
context.stdout(output);
|
|
225
|
+
}
|
|
226
|
+
return 0;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/commands/validate/parseValidateArgs.ts
|
|
232
|
+
var ALIASES = { i: "input" };
|
|
233
|
+
function parseFlags2(argv) {
|
|
234
|
+
const flags = {};
|
|
235
|
+
for (let i = 0; i < argv.length; i++) {
|
|
236
|
+
const arg = argv[i];
|
|
237
|
+
const isLong = arg.startsWith("--");
|
|
238
|
+
const isShort = !isLong && arg.startsWith("-") && arg.length > 1;
|
|
239
|
+
if (!isLong && !isShort) continue;
|
|
240
|
+
const body = isLong ? arg.slice(2) : arg.slice(1);
|
|
241
|
+
const eqIndex = body.indexOf("=");
|
|
242
|
+
if (eqIndex !== -1) {
|
|
243
|
+
const key2 = body.slice(0, eqIndex);
|
|
244
|
+
flags[ALIASES[key2] ?? key2] = body.slice(eqIndex + 1);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const key = ALIASES[body] ?? body;
|
|
248
|
+
const next = argv[i + 1];
|
|
249
|
+
if (next !== void 0 && !next.startsWith("-")) {
|
|
250
|
+
flags[key] = next;
|
|
251
|
+
i++;
|
|
252
|
+
} else {
|
|
253
|
+
flags[key] = "true";
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return flags;
|
|
257
|
+
}
|
|
258
|
+
function parseValidateArgs(argv) {
|
|
259
|
+
const flags = parseFlags2(argv);
|
|
260
|
+
const input = flags["input"];
|
|
261
|
+
if (!input || input === "true") {
|
|
262
|
+
return { ok: false, error: "Missing required flag --input <path> (or -i <path>)." };
|
|
263
|
+
}
|
|
264
|
+
const format = flags["format"];
|
|
265
|
+
if (format !== void 0 && format !== "json" && format !== "yaml") {
|
|
266
|
+
return { ok: false, error: `Invalid --format flag. Expected "json" or "yaml", got "${format}".` };
|
|
267
|
+
}
|
|
268
|
+
return { ok: true, value: { input, format } };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// src/commands/validate/ValidateCommand.ts
|
|
272
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
273
|
+
import { extname } from "path";
|
|
274
|
+
import * as YAML2 from "yaml";
|
|
275
|
+
import {
|
|
276
|
+
loadDeclarativeConfigFromString
|
|
277
|
+
} from "@maykonpaulo/maestro-core";
|
|
278
|
+
var defaultDeps2 = {
|
|
279
|
+
readFile: (path) => readFileSync2(path, "utf-8")
|
|
280
|
+
};
|
|
281
|
+
function errorMessage2(error) {
|
|
282
|
+
return error instanceof Error ? error.message : String(error);
|
|
283
|
+
}
|
|
284
|
+
var VALIDATE_HELP_TEXT = `maestro validate \u2014 Validate a declarative config file (YAML or JSON)
|
|
285
|
+
|
|
286
|
+
Loads a local declarative config file (maestro.config.yaml/.yml/.json) and validates its structure
|
|
287
|
+
using the core's Declarative File Loader. Prints a readable summary on success and a readable error
|
|
288
|
+
on failure.
|
|
289
|
+
|
|
290
|
+
This command does NOT create a Maestro engine (no createMaestro()), does NOT connect to any database,
|
|
291
|
+
datasource or provider, and does NOT require declared operations to have a real implementation.
|
|
292
|
+
Structural validation of the declarative file is all it does; wiring operations to code and starting
|
|
293
|
+
the runtime is what createMaestro() does separately.
|
|
294
|
+
|
|
295
|
+
Usage:
|
|
296
|
+
maestro validate --input <path> [--format <json|yaml>]
|
|
297
|
+
maestro validate -i <path>
|
|
298
|
+
|
|
299
|
+
Flags:
|
|
300
|
+
--input, -i <path> Required. Path to a local declarative config file (.yaml, .yml or .json).
|
|
301
|
+
--format <json|yaml> Optional. Overrides the format detected from the file extension.
|
|
302
|
+
|
|
303
|
+
Accepted file formats:
|
|
304
|
+
.json \u2192 parsed as JSON
|
|
305
|
+
.yaml, .yml \u2192 parsed as YAML
|
|
306
|
+
(other) \u2192 pass --format explicitly
|
|
307
|
+
|
|
308
|
+
What is validated:
|
|
309
|
+
- The file parses as JSON/YAML.
|
|
310
|
+
- The top-level shape (optional "entities" and "consumers" arrays; no unknown keys).
|
|
311
|
+
- Each entity declaration (fields, types, capabilities, operations, relationships, enumOptions).
|
|
312
|
+
- Each consumer declaration against its referenced entity.
|
|
313
|
+
|
|
314
|
+
What is NOT validated:
|
|
315
|
+
- Operation bindings (whether declared operations have a real handler) \u2014 that's createMaestro().
|
|
316
|
+
- Datasource/provider connectivity \u2014 no database or external system is contacted.
|
|
317
|
+
|
|
318
|
+
Examples:
|
|
319
|
+
maestro validate --input ./maestro.config.yaml
|
|
320
|
+
maestro validate --input ./maestro.config.json
|
|
321
|
+
maestro validate -i ./maestro.config.yaml
|
|
322
|
+
maestro validate --input ./maestro.config --format yaml`;
|
|
323
|
+
var yamlParser = { parse: (content) => YAML2.parse(content) };
|
|
324
|
+
function resolveFormat(input, override) {
|
|
325
|
+
if (override) return { ok: true, format: override };
|
|
326
|
+
const ext = extname(input).toLowerCase();
|
|
327
|
+
if (ext === ".json") return { ok: true, format: "json" };
|
|
328
|
+
if (ext === ".yaml" || ext === ".yml") return { ok: true, format: "yaml" };
|
|
329
|
+
return {
|
|
330
|
+
ok: false,
|
|
331
|
+
error: `cannot detect format from file extension "${ext || "(none)"}" of '${input}'. Supported: .json, .yaml, .yml. Pass --format <json|yaml> to override.`
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function createValidateCommand(deps = defaultDeps2) {
|
|
335
|
+
return {
|
|
336
|
+
name: "validate",
|
|
337
|
+
description: "Validate a declarative config file (YAML or JSON)",
|
|
338
|
+
helpText: VALIDATE_HELP_TEXT,
|
|
339
|
+
run(context) {
|
|
340
|
+
const parsed = parseValidateArgs(context.argv);
|
|
341
|
+
if (!parsed.ok) {
|
|
342
|
+
context.stderr(`maestro validate: ${parsed.error}`);
|
|
343
|
+
return 1;
|
|
344
|
+
}
|
|
345
|
+
const { input, format: formatOverride } = parsed.value;
|
|
346
|
+
const resolved = resolveFormat(input, formatOverride);
|
|
347
|
+
if (!resolved.ok) {
|
|
348
|
+
context.stderr(`maestro validate: ${resolved.error}`);
|
|
349
|
+
return 1;
|
|
350
|
+
}
|
|
351
|
+
const { format } = resolved;
|
|
352
|
+
let content;
|
|
353
|
+
try {
|
|
354
|
+
content = deps.readFile(input);
|
|
355
|
+
} catch (error) {
|
|
356
|
+
context.stderr(`maestro validate: could not read input file '${input}': ${errorMessage2(error)}`);
|
|
357
|
+
return 1;
|
|
358
|
+
}
|
|
359
|
+
let config;
|
|
360
|
+
try {
|
|
361
|
+
config = loadDeclarativeConfigFromString(content, {
|
|
362
|
+
format,
|
|
363
|
+
yamlParser: format === "yaml" ? yamlParser : void 0,
|
|
364
|
+
source: input
|
|
365
|
+
});
|
|
366
|
+
} catch (error) {
|
|
367
|
+
context.stderr(`maestro validate: ${errorMessage2(error)}`);
|
|
368
|
+
return 1;
|
|
369
|
+
}
|
|
370
|
+
context.stdout(`Valid declarative config: ${input}`);
|
|
371
|
+
context.stdout(`Entities: ${config.entities.length}`);
|
|
372
|
+
context.stdout(`Consumers: ${config.consumers?.length ?? 0}`);
|
|
373
|
+
return 0;
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/commands/diff/parseDiffArgs.ts
|
|
379
|
+
var ALIASES2 = { f: "from", t: "to" };
|
|
380
|
+
function parseFlags3(argv) {
|
|
381
|
+
const flags = {};
|
|
382
|
+
for (let i = 0; i < argv.length; i++) {
|
|
383
|
+
const arg = argv[i];
|
|
384
|
+
const isLong = arg.startsWith("--");
|
|
385
|
+
const isShort = !isLong && arg.startsWith("-") && arg.length > 1;
|
|
386
|
+
if (!isLong && !isShort) continue;
|
|
387
|
+
const body = isLong ? arg.slice(2) : arg.slice(1);
|
|
388
|
+
const eqIndex = body.indexOf("=");
|
|
389
|
+
if (eqIndex !== -1) {
|
|
390
|
+
const key2 = body.slice(0, eqIndex);
|
|
391
|
+
flags[ALIASES2[key2] ?? key2] = body.slice(eqIndex + 1);
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
const key = ALIASES2[body] ?? body;
|
|
395
|
+
const next = argv[i + 1];
|
|
396
|
+
if (next !== void 0 && !next.startsWith("-")) {
|
|
397
|
+
flags[key] = next;
|
|
398
|
+
i++;
|
|
399
|
+
} else {
|
|
400
|
+
flags[key] = "true";
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
return flags;
|
|
404
|
+
}
|
|
405
|
+
function validateFormat(value, flag) {
|
|
406
|
+
if (value === void 0) return { ok: true, value: void 0 };
|
|
407
|
+
if (value !== "json" && value !== "yaml") {
|
|
408
|
+
return { ok: false, error: `Invalid ${flag} flag. Expected "json" or "yaml", got "${value}".` };
|
|
409
|
+
}
|
|
410
|
+
return { ok: true, value };
|
|
411
|
+
}
|
|
412
|
+
function parseDiffArgs(argv) {
|
|
413
|
+
const flags = parseFlags3(argv);
|
|
414
|
+
const from = flags["from"];
|
|
415
|
+
if (!from || from === "true") {
|
|
416
|
+
return { ok: false, error: "Missing required flag --from <path> (or -f <path>)." };
|
|
417
|
+
}
|
|
418
|
+
const to = flags["to"];
|
|
419
|
+
if (!to || to === "true") {
|
|
420
|
+
return { ok: false, error: "Missing required flag --to <path> (or -t <path>)." };
|
|
421
|
+
}
|
|
422
|
+
const fromFormat = validateFormat(flags["from-format"], "--from-format");
|
|
423
|
+
if (!fromFormat.ok) return fromFormat;
|
|
424
|
+
const toFormat = validateFormat(flags["to-format"], "--to-format");
|
|
425
|
+
if (!toFormat.ok) return toFormat;
|
|
426
|
+
return { ok: true, value: { from, to, fromFormat: fromFormat.value, toFormat: toFormat.value } };
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// src/commands/diff/diffDeclarativeConfigs.ts
|
|
430
|
+
function canonicalize(value) {
|
|
431
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
432
|
+
if (value !== null && typeof value === "object") {
|
|
433
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
434
|
+
const out = {};
|
|
435
|
+
for (const [k, v] of entries) out[k] = canonicalize(v);
|
|
436
|
+
return out;
|
|
437
|
+
}
|
|
438
|
+
return value;
|
|
439
|
+
}
|
|
440
|
+
function canonicalString(value) {
|
|
441
|
+
return JSON.stringify(canonicalize(value)) ?? "undefined";
|
|
442
|
+
}
|
|
443
|
+
function deepEqual(a, b) {
|
|
444
|
+
return canonicalString(a) === canonicalString(b);
|
|
445
|
+
}
|
|
446
|
+
function sortedKeyUnion(...records) {
|
|
447
|
+
const keys = /* @__PURE__ */ new Set();
|
|
448
|
+
for (const record of records) {
|
|
449
|
+
if (record) for (const key of Object.keys(record)) keys.add(key);
|
|
450
|
+
}
|
|
451
|
+
return [...keys].sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
|
|
452
|
+
}
|
|
453
|
+
function leafChanges(a, b, keys) {
|
|
454
|
+
const paths = keys ?? sortedKeyUnion(a, b);
|
|
455
|
+
const changes = [];
|
|
456
|
+
for (const path of paths) {
|
|
457
|
+
if (!deepEqual(a[path], b[path])) {
|
|
458
|
+
changes.push({ path, from: a[path], to: b[path] });
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return changes;
|
|
462
|
+
}
|
|
463
|
+
function diffRecord(from, to) {
|
|
464
|
+
const result = [];
|
|
465
|
+
for (const name of sortedKeyUnion(from, to)) {
|
|
466
|
+
const a = from?.[name];
|
|
467
|
+
const b = to?.[name];
|
|
468
|
+
if (a === void 0) {
|
|
469
|
+
result.push({ name, kind: "added" });
|
|
470
|
+
} else if (b === void 0) {
|
|
471
|
+
result.push({ name, kind: "removed" });
|
|
472
|
+
} else {
|
|
473
|
+
const changes = leafChanges(a, b);
|
|
474
|
+
if (changes.length > 0) result.push({ name, kind: "changed", leafChanges: changes });
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return result;
|
|
478
|
+
}
|
|
479
|
+
function relationRecord(relationships) {
|
|
480
|
+
const record = {};
|
|
481
|
+
for (const relation of relationships ?? []) {
|
|
482
|
+
const key = relation.id ?? `${relation.fromField}->${relation.toEntity}.${relation.toField}`;
|
|
483
|
+
record[key] = relation;
|
|
484
|
+
}
|
|
485
|
+
return record;
|
|
486
|
+
}
|
|
487
|
+
var ENTITY_SCALAR_KEYS = ["label", "pluralLabel", "description", "capabilities"];
|
|
488
|
+
var CONSUMER_KEYS = ["list", "detail", "forms", "actions"];
|
|
489
|
+
function diffEntity(a, b) {
|
|
490
|
+
const leaf = leafChanges(
|
|
491
|
+
a,
|
|
492
|
+
b,
|
|
493
|
+
ENTITY_SCALAR_KEYS
|
|
494
|
+
);
|
|
495
|
+
const groups = [];
|
|
496
|
+
const fields = diffRecord(a.fields, b.fields);
|
|
497
|
+
if (fields.length > 0) groups.push({ title: "fields", members: fields });
|
|
498
|
+
const operations = diffRecord(a.operations, b.operations);
|
|
499
|
+
if (operations.length > 0) groups.push({ title: "operations", members: operations });
|
|
500
|
+
const relationships = diffRecord(relationRecord(a.relationships), relationRecord(b.relationships));
|
|
501
|
+
if (relationships.length > 0) groups.push({ title: "relationships", members: relationships });
|
|
502
|
+
if (leaf.length === 0 && groups.length === 0) return null;
|
|
503
|
+
return { name: a.entity, kind: "changed", leafChanges: leaf, groups };
|
|
504
|
+
}
|
|
505
|
+
function entityIndex(config) {
|
|
506
|
+
const index = {};
|
|
507
|
+
for (const entity of config.entities) index[entity.entity] = entity;
|
|
508
|
+
return index;
|
|
509
|
+
}
|
|
510
|
+
function consumerIndex(config) {
|
|
511
|
+
const index = {};
|
|
512
|
+
for (const consumer of config.consumers ?? []) {
|
|
513
|
+
index[`${consumer.consumer}:${consumer.entity}`] = consumer;
|
|
514
|
+
}
|
|
515
|
+
return index;
|
|
516
|
+
}
|
|
517
|
+
function diffEntities(from, to) {
|
|
518
|
+
const a = entityIndex(from);
|
|
519
|
+
const b = entityIndex(to);
|
|
520
|
+
const result = [];
|
|
521
|
+
for (const name of sortedKeyUnion(a, b)) {
|
|
522
|
+
if (a[name] === void 0) {
|
|
523
|
+
result.push({ name, kind: "added" });
|
|
524
|
+
} else if (b[name] === void 0) {
|
|
525
|
+
result.push({ name, kind: "removed" });
|
|
526
|
+
} else {
|
|
527
|
+
const changed = diffEntity(a[name], b[name]);
|
|
528
|
+
if (changed) result.push(changed);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return result;
|
|
532
|
+
}
|
|
533
|
+
function diffConsumers(from, to) {
|
|
534
|
+
const a = consumerIndex(from);
|
|
535
|
+
const b = consumerIndex(to);
|
|
536
|
+
const result = [];
|
|
537
|
+
for (const name of sortedKeyUnion(a, b)) {
|
|
538
|
+
if (a[name] === void 0) {
|
|
539
|
+
result.push({ name, kind: "added" });
|
|
540
|
+
} else if (b[name] === void 0) {
|
|
541
|
+
result.push({ name, kind: "removed" });
|
|
542
|
+
} else {
|
|
543
|
+
const changes = leafChanges(
|
|
544
|
+
a[name],
|
|
545
|
+
b[name],
|
|
546
|
+
CONSUMER_KEYS
|
|
547
|
+
);
|
|
548
|
+
if (changes.length > 0) result.push({ name, kind: "changed", leafChanges: changes });
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
return result;
|
|
552
|
+
}
|
|
553
|
+
function diffDeclarativeConfigs(from, to) {
|
|
554
|
+
const entities = diffEntities(from, to);
|
|
555
|
+
const consumers = diffConsumers(from, to);
|
|
556
|
+
return { entities, consumers, hasDifferences: entities.length > 0 || consumers.length > 0 };
|
|
557
|
+
}
|
|
558
|
+
var SYMBOL = { added: "+", removed: "-", changed: "~" };
|
|
559
|
+
function renderValue(value) {
|
|
560
|
+
if (value === void 0) return "(unset)";
|
|
561
|
+
return canonicalString(value);
|
|
562
|
+
}
|
|
563
|
+
function renderLeafChange(change, indent) {
|
|
564
|
+
return `${indent}${change.path}: ${renderValue(change.from)} \u2192 ${renderValue(change.to)}`;
|
|
565
|
+
}
|
|
566
|
+
function renderMember(member, indent, lines) {
|
|
567
|
+
lines.push(`${indent}${SYMBOL[member.kind]} ${member.name}`);
|
|
568
|
+
const childIndent = `${indent} `;
|
|
569
|
+
for (const change of member.leafChanges ?? []) {
|
|
570
|
+
lines.push(renderLeafChange(change, childIndent));
|
|
571
|
+
}
|
|
572
|
+
for (const group of member.groups ?? []) {
|
|
573
|
+
lines.push(`${childIndent}${group.title}:`);
|
|
574
|
+
for (const child of group.members) {
|
|
575
|
+
renderMember(child, `${childIndent} `, lines);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
function formatDeclarativeDiff(diff) {
|
|
580
|
+
if (!diff.hasDifferences) return "No differences found.";
|
|
581
|
+
const lines = ["Differences found."];
|
|
582
|
+
if (diff.entities.length > 0) {
|
|
583
|
+
lines.push("", "Entities:");
|
|
584
|
+
for (const member of diff.entities) renderMember(member, " ", lines);
|
|
585
|
+
}
|
|
586
|
+
if (diff.consumers.length > 0) {
|
|
587
|
+
lines.push("", "Consumers:");
|
|
588
|
+
for (const member of diff.consumers) renderMember(member, " ", lines);
|
|
589
|
+
}
|
|
590
|
+
return lines.join("\n");
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/commands/diff/DiffCommand.ts
|
|
594
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
595
|
+
import { extname as extname2 } from "path";
|
|
596
|
+
import * as YAML3 from "yaml";
|
|
597
|
+
import {
|
|
598
|
+
loadDeclarativeConfigFromString as loadDeclarativeConfigFromString2
|
|
599
|
+
} from "@maykonpaulo/maestro-core";
|
|
600
|
+
var defaultDeps3 = {
|
|
601
|
+
readFile: (path) => readFileSync3(path, "utf-8")
|
|
602
|
+
};
|
|
603
|
+
var EXIT_DIFFERENCES = 1;
|
|
604
|
+
var EXIT_ERROR = 2;
|
|
605
|
+
function errorMessage3(error) {
|
|
606
|
+
return error instanceof Error ? error.message : String(error);
|
|
607
|
+
}
|
|
608
|
+
var DIFF_HELP_TEXT = `maestro diff \u2014 Compare two declarative config files (YAML or JSON)
|
|
609
|
+
|
|
610
|
+
Loads two local declarative config files (maestro.config.yaml/.yml/.json) with the core's Declarative
|
|
611
|
+
File Loader, then compares their normalised structures and prints a readable, deterministic report of
|
|
612
|
+
what changed. Useful before promoting, reviewing or versioning a config.
|
|
613
|
+
|
|
614
|
+
This command does NOT create a Maestro engine (no createMaestro()), does NOT connect to any database,
|
|
615
|
+
datasource or provider, does NOT execute operations, and does NOT require declared operations to have
|
|
616
|
+
a real implementation. It is a read-only, local comparison of two declarative files.
|
|
617
|
+
|
|
618
|
+
Usage:
|
|
619
|
+
maestro diff --from <path> --to <path> [--from-format <json|yaml>] [--to-format <json|yaml>]
|
|
620
|
+
maestro diff -f <path> -t <path>
|
|
621
|
+
|
|
622
|
+
Flags:
|
|
623
|
+
--from, -f <path> Required. Path to the baseline declarative config file.
|
|
624
|
+
--to, -t <path> Required. Path to the changed declarative config file.
|
|
625
|
+
--from-format <json|yaml> Optional. Overrides the format detected from the --from extension.
|
|
626
|
+
--to-format <json|yaml> Optional. Overrides the format detected from the --to extension.
|
|
627
|
+
|
|
628
|
+
Accepted file formats:
|
|
629
|
+
.json \u2192 parsed as JSON
|
|
630
|
+
.yaml, .yml \u2192 parsed as YAML
|
|
631
|
+
(other) \u2192 pass --from-format / --to-format explicitly
|
|
632
|
+
|
|
633
|
+
What is compared:
|
|
634
|
+
- Entities (added / removed / changed): label, pluralLabel, description, capabilities.
|
|
635
|
+
- Entity fields (added / removed / changed): type, label, required, readonly, description,
|
|
636
|
+
enumOptions, relationEntity and any other declarative field property.
|
|
637
|
+
- Entity operations (added / removed / changed) \u2014 no handler/implementation required.
|
|
638
|
+
- Entity relationships (added / removed / changed).
|
|
639
|
+
- Consumers (added / removed / changed): list, detail, forms and actions projections.
|
|
640
|
+
|
|
641
|
+
What is NOT compared:
|
|
642
|
+
- Operation bindings or handlers \u2014 that's createMaestro(), not diff.
|
|
643
|
+
- Live database/introspection metadata \u2014 this diffs declarative files, not a real schema.
|
|
644
|
+
|
|
645
|
+
Exit codes:
|
|
646
|
+
0 Both files are valid and there are no differences.
|
|
647
|
+
1 Both files are valid and there are differences.
|
|
648
|
+
2 Usage, file-read, parse or validation error.
|
|
649
|
+
|
|
650
|
+
Examples:
|
|
651
|
+
maestro diff --from ./maestro.config.old.yaml --to ./maestro.config.new.yaml
|
|
652
|
+
maestro diff -f ./before.json -t ./after.json
|
|
653
|
+
maestro diff --from ./before.config --from-format yaml --to ./after.config --to-format yaml`;
|
|
654
|
+
var yamlParser2 = { parse: (content) => YAML3.parse(content) };
|
|
655
|
+
function resolveFormat2(path, override, flag) {
|
|
656
|
+
if (override) return { ok: true, format: override };
|
|
657
|
+
const ext = extname2(path).toLowerCase();
|
|
658
|
+
if (ext === ".json") return { ok: true, format: "json" };
|
|
659
|
+
if (ext === ".yaml" || ext === ".yml") return { ok: true, format: "yaml" };
|
|
660
|
+
return {
|
|
661
|
+
ok: false,
|
|
662
|
+
error: `cannot detect format from file extension "${ext || "(none)"}" of '${path}'. Supported: .json, .yaml, .yml. Pass ${flag} <json|yaml> to override.`
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
function loadConfig(deps, path, format) {
|
|
666
|
+
let content;
|
|
667
|
+
try {
|
|
668
|
+
content = deps.readFile(path);
|
|
669
|
+
} catch (error) {
|
|
670
|
+
return { ok: false, error: `could not read file '${path}': ${errorMessage3(error)}` };
|
|
671
|
+
}
|
|
672
|
+
try {
|
|
673
|
+
const config = loadDeclarativeConfigFromString2(content, {
|
|
674
|
+
format,
|
|
675
|
+
yamlParser: format === "yaml" ? yamlParser2 : void 0,
|
|
676
|
+
source: path
|
|
677
|
+
});
|
|
678
|
+
return { ok: true, config };
|
|
679
|
+
} catch (error) {
|
|
680
|
+
return { ok: false, error: errorMessage3(error) };
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function createDiffCommand(deps = defaultDeps3) {
|
|
684
|
+
return {
|
|
685
|
+
name: "diff",
|
|
686
|
+
description: "Compare two declarative config files (YAML or JSON)",
|
|
687
|
+
helpText: DIFF_HELP_TEXT,
|
|
688
|
+
run(context) {
|
|
689
|
+
const parsed = parseDiffArgs(context.argv);
|
|
690
|
+
if (!parsed.ok) {
|
|
691
|
+
context.stderr(`maestro diff: ${parsed.error}`);
|
|
692
|
+
return EXIT_ERROR;
|
|
693
|
+
}
|
|
694
|
+
const { from, to, fromFormat, toFormat } = parsed.value;
|
|
695
|
+
const fromResolved = resolveFormat2(from, fromFormat, "--from-format");
|
|
696
|
+
if (!fromResolved.ok) {
|
|
697
|
+
context.stderr(`maestro diff: ${fromResolved.error}`);
|
|
698
|
+
return EXIT_ERROR;
|
|
699
|
+
}
|
|
700
|
+
const toResolved = resolveFormat2(to, toFormat, "--to-format");
|
|
701
|
+
if (!toResolved.ok) {
|
|
702
|
+
context.stderr(`maestro diff: ${toResolved.error}`);
|
|
703
|
+
return EXIT_ERROR;
|
|
704
|
+
}
|
|
705
|
+
const fromConfig = loadConfig(deps, from, fromResolved.format);
|
|
706
|
+
if (!fromConfig.ok) {
|
|
707
|
+
context.stderr(`maestro diff: ${fromConfig.error}`);
|
|
708
|
+
return EXIT_ERROR;
|
|
709
|
+
}
|
|
710
|
+
const toConfig = loadConfig(deps, to, toResolved.format);
|
|
711
|
+
if (!toConfig.ok) {
|
|
712
|
+
context.stderr(`maestro diff: ${toConfig.error}`);
|
|
713
|
+
return EXIT_ERROR;
|
|
714
|
+
}
|
|
715
|
+
const diff = diffDeclarativeConfigs(fromConfig.config, toConfig.config);
|
|
716
|
+
context.stdout(formatDeclarativeDiff(diff));
|
|
717
|
+
return diff.hasDifferences ? EXIT_DIFFERENCES : 0;
|
|
718
|
+
}
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// src/commands/snapshot/parseSnapshotArgs.ts
|
|
723
|
+
var ALIASES3 = { i: "input" };
|
|
724
|
+
function parseFlags4(argv) {
|
|
725
|
+
const flags = {};
|
|
726
|
+
for (let i = 0; i < argv.length; i++) {
|
|
727
|
+
const arg = argv[i];
|
|
728
|
+
const isLong = arg.startsWith("--");
|
|
729
|
+
const isShort = !isLong && arg.startsWith("-") && arg.length > 1;
|
|
730
|
+
if (!isLong && !isShort) continue;
|
|
731
|
+
const body = isLong ? arg.slice(2) : arg.slice(1);
|
|
732
|
+
const eqIndex = body.indexOf("=");
|
|
733
|
+
if (eqIndex !== -1) {
|
|
734
|
+
const key2 = body.slice(0, eqIndex);
|
|
735
|
+
flags[ALIASES3[key2] ?? key2] = body.slice(eqIndex + 1);
|
|
736
|
+
continue;
|
|
737
|
+
}
|
|
738
|
+
const key = ALIASES3[body] ?? body;
|
|
739
|
+
const next = argv[i + 1];
|
|
740
|
+
if (next !== void 0 && !next.startsWith("-")) {
|
|
741
|
+
flags[key] = next;
|
|
742
|
+
i++;
|
|
743
|
+
} else {
|
|
744
|
+
flags[key] = "true";
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return flags;
|
|
748
|
+
}
|
|
749
|
+
function validateInputFormat(value) {
|
|
750
|
+
if (value === void 0) return { ok: true, value: void 0 };
|
|
751
|
+
if (value !== "json" && value !== "yaml") {
|
|
752
|
+
return {
|
|
753
|
+
ok: false,
|
|
754
|
+
error: `Invalid --input-format flag. Expected "json" or "yaml", got "${value}".`
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
return { ok: true, value };
|
|
758
|
+
}
|
|
759
|
+
function validateOutputFormat(value) {
|
|
760
|
+
if (value === void 0) return { ok: true, value: "json" };
|
|
761
|
+
if (value !== "json") {
|
|
762
|
+
return { ok: false, error: `Invalid --format flag. Expected "json", got "${value}".` };
|
|
763
|
+
}
|
|
764
|
+
return { ok: true, value };
|
|
765
|
+
}
|
|
766
|
+
function parseSnapshotArgs(argv) {
|
|
767
|
+
const flags = parseFlags4(argv);
|
|
768
|
+
const input = flags["input"];
|
|
769
|
+
if (!input || input === "true") {
|
|
770
|
+
return { ok: false, error: "Missing required flag --input <path> (or -i <path>)." };
|
|
771
|
+
}
|
|
772
|
+
const inputFormat = validateInputFormat(flags["input-format"]);
|
|
773
|
+
if (!inputFormat.ok) return inputFormat;
|
|
774
|
+
const format = validateOutputFormat(flags["format"]);
|
|
775
|
+
if (!format.ok) return format;
|
|
776
|
+
const out = flags["out"] && flags["out"] !== "true" ? flags["out"] : void 0;
|
|
777
|
+
return {
|
|
778
|
+
ok: true,
|
|
779
|
+
value: { input, inputFormat: inputFormat.value, out, format: format.value }
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// src/commands/snapshot/buildDeclarativeSnapshot.ts
|
|
784
|
+
var SNAPSHOT_SCHEMA_VERSION = 1;
|
|
785
|
+
var SNAPSHOT_KIND = "maestro.declarative.snapshot";
|
|
786
|
+
function relationId(fromEntity, relation) {
|
|
787
|
+
return relation.id ?? `${fromEntity}.${relation.toEntity}`;
|
|
788
|
+
}
|
|
789
|
+
function compareStrings(a, b) {
|
|
790
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
791
|
+
}
|
|
792
|
+
function buildDeclarativeSnapshot(config, source) {
|
|
793
|
+
const entities = [...config.entities].sort((a, b) => compareStrings(a.entity, b.entity)).map((entity) => canonicalizeEntity(entity));
|
|
794
|
+
const consumers = [...config.consumers ?? []].sort(
|
|
795
|
+
(a, b) => compareStrings(a.consumer, b.consumer)
|
|
796
|
+
);
|
|
797
|
+
const relations = entities.flatMap(
|
|
798
|
+
(entity) => (entity.relationships ?? []).map(
|
|
799
|
+
(relation) => ({ fromEntity: entity.entity, ...relation })
|
|
800
|
+
)
|
|
801
|
+
).sort((a, b) => {
|
|
802
|
+
const byId = compareStrings(relationId(a.fromEntity, a), relationId(b.fromEntity, b));
|
|
803
|
+
return byId !== 0 ? byId : compareStrings(a.fromEntity, b.fromEntity);
|
|
804
|
+
});
|
|
805
|
+
return {
|
|
806
|
+
schemaVersion: SNAPSHOT_SCHEMA_VERSION,
|
|
807
|
+
kind: SNAPSHOT_KIND,
|
|
808
|
+
source,
|
|
809
|
+
summary: {
|
|
810
|
+
entities: entities.length,
|
|
811
|
+
consumers: consumers.length,
|
|
812
|
+
relations: relations.length
|
|
813
|
+
},
|
|
814
|
+
declarations: { entities, consumers, relations }
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
function canonicalizeEntity(entity) {
|
|
818
|
+
if (!entity.relationships || entity.relationships.length === 0) return entity;
|
|
819
|
+
const relationships = [...entity.relationships].sort(
|
|
820
|
+
(a, b) => compareStrings(relationId(entity.entity, a), relationId(entity.entity, b))
|
|
821
|
+
);
|
|
822
|
+
return { ...entity, relationships };
|
|
823
|
+
}
|
|
824
|
+
function sortKeys(value) {
|
|
825
|
+
if (Array.isArray(value)) return value.map(sortKeys);
|
|
826
|
+
if (value !== null && typeof value === "object") {
|
|
827
|
+
const sorted = {};
|
|
828
|
+
for (const key of Object.keys(value).sort()) {
|
|
829
|
+
sorted[key] = sortKeys(value[key]);
|
|
830
|
+
}
|
|
831
|
+
return sorted;
|
|
832
|
+
}
|
|
833
|
+
return value;
|
|
834
|
+
}
|
|
835
|
+
function serializeSnapshot(snapshot) {
|
|
836
|
+
return `${JSON.stringify(sortKeys(snapshot), null, 2)}
|
|
837
|
+
`;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// src/commands/snapshot/SnapshotCommand.ts
|
|
841
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
842
|
+
import { extname as extname3 } from "path";
|
|
843
|
+
import * as YAML4 from "yaml";
|
|
844
|
+
import {
|
|
845
|
+
loadDeclarativeConfigFromString as loadDeclarativeConfigFromString3
|
|
846
|
+
} from "@maykonpaulo/maestro-core";
|
|
847
|
+
var defaultDeps4 = {
|
|
848
|
+
readFile: (path) => readFileSync4(path, "utf-8"),
|
|
849
|
+
writeFile: (path, content) => writeFileSync2(path, content, "utf-8")
|
|
850
|
+
};
|
|
851
|
+
function errorMessage4(error) {
|
|
852
|
+
return error instanceof Error ? error.message : String(error);
|
|
853
|
+
}
|
|
854
|
+
var SNAPSHOT_HELP_TEXT = `maestro snapshot \u2014 Produce a canonical, deterministic snapshot of a declarative config file
|
|
855
|
+
|
|
856
|
+
Loads a local declarative config file (maestro.config.yaml/.yml/.json) with the core's Declarative
|
|
857
|
+
File Loader, validates it, then emits a canonical JSON snapshot of its declarations. The snapshot is a
|
|
858
|
+
stable, versionable artifact for auditing, PR review and future comparison \u2014 the same (semantically
|
|
859
|
+
equivalent) input always produces byte-identical output.
|
|
860
|
+
|
|
861
|
+
This command does NOT create a Maestro engine (no createMaestro()), does NOT connect to any database,
|
|
862
|
+
datasource or provider, does NOT execute operations, and does NOT require declared operations to have
|
|
863
|
+
a real implementation. It is a read-only, local, declarative snapshot \u2014 NOT a snapshot of a live
|
|
864
|
+
database or of runtime introspection.
|
|
865
|
+
|
|
866
|
+
Usage:
|
|
867
|
+
maestro snapshot --input <path> [--input-format <json|yaml>] [--format json] [--out <path>]
|
|
868
|
+
maestro snapshot -i <path>
|
|
869
|
+
|
|
870
|
+
Flags:
|
|
871
|
+
--input, -i <path> Required. Path to a local declarative config file (.yaml, .yml or .json).
|
|
872
|
+
--input-format <json|yaml> Optional. Overrides the format detected from the --input extension.
|
|
873
|
+
--format json Optional. Snapshot output format. Only "json" is supported. Default: json.
|
|
874
|
+
--out <path> Optional. Write the snapshot to this file instead of stdout.
|
|
875
|
+
|
|
876
|
+
Accepted input formats:
|
|
877
|
+
.json \u2192 parsed as JSON
|
|
878
|
+
.yaml, .yml \u2192 parsed as YAML
|
|
879
|
+
(other) \u2192 pass --input-format <json|yaml> explicitly
|
|
880
|
+
|
|
881
|
+
Snapshot format (JSON):
|
|
882
|
+
{
|
|
883
|
+
"schemaVersion": 1,
|
|
884
|
+
"kind": "maestro.declarative.snapshot",
|
|
885
|
+
"source": { "path": "<the --input value>", "format": "yaml" },
|
|
886
|
+
"summary": { "entities": N, "consumers": N, "relations": N },
|
|
887
|
+
"declarations": { "entities": [...], "consumers": [...], "relations": [...] }
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
What is included:
|
|
891
|
+
- Every declared entity (fields, capabilities, operations, relationships), consumer and relationship.
|
|
892
|
+
- A flattened "relations" index of all entity relationships, each tagged with its "fromEntity".
|
|
893
|
+
- Stable ordering (entities/consumers/relations sorted; object keys sorted) for deterministic output.
|
|
894
|
+
|
|
895
|
+
What is NOT included:
|
|
896
|
+
- Timestamps, absolute paths, hashes or any value that varies between runs of the same input.
|
|
897
|
+
- Operation bindings/handlers, live database or runtime introspection data.
|
|
898
|
+
|
|
899
|
+
Exit codes:
|
|
900
|
+
0 The file is valid and the snapshot was produced.
|
|
901
|
+
1 Usage, file-read, parse, validation or write error.
|
|
902
|
+
|
|
903
|
+
Examples:
|
|
904
|
+
maestro snapshot --input ./maestro.config.yaml
|
|
905
|
+
maestro snapshot -i ./maestro.config.json
|
|
906
|
+
maestro snapshot --input ./maestro.config.yaml --out ./maestro.snapshot.json
|
|
907
|
+
maestro snapshot --input ./maestro.config --input-format yaml --out ./maestro.snapshot.json`;
|
|
908
|
+
var yamlParser3 = { parse: (content) => YAML4.parse(content) };
|
|
909
|
+
function resolveFormat3(input, override) {
|
|
910
|
+
if (override) return { ok: true, format: override };
|
|
911
|
+
const ext = extname3(input).toLowerCase();
|
|
912
|
+
if (ext === ".json") return { ok: true, format: "json" };
|
|
913
|
+
if (ext === ".yaml" || ext === ".yml") return { ok: true, format: "yaml" };
|
|
914
|
+
return {
|
|
915
|
+
ok: false,
|
|
916
|
+
error: `cannot detect format from file extension "${ext || "(none)"}" of '${input}'. Supported: .json, .yaml, .yml. Pass --input-format <json|yaml> to override.`
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
function createSnapshotCommand(deps = defaultDeps4) {
|
|
920
|
+
return {
|
|
921
|
+
name: "snapshot",
|
|
922
|
+
description: "Produce a canonical snapshot of a declarative config file (YAML or JSON)",
|
|
923
|
+
helpText: SNAPSHOT_HELP_TEXT,
|
|
924
|
+
run(context) {
|
|
925
|
+
const parsed = parseSnapshotArgs(context.argv);
|
|
926
|
+
if (!parsed.ok) {
|
|
927
|
+
context.stderr(`maestro snapshot: ${parsed.error}`);
|
|
928
|
+
return 1;
|
|
929
|
+
}
|
|
930
|
+
const { input, inputFormat, out } = parsed.value;
|
|
931
|
+
const resolved = resolveFormat3(input, inputFormat);
|
|
932
|
+
if (!resolved.ok) {
|
|
933
|
+
context.stderr(`maestro snapshot: ${resolved.error}`);
|
|
934
|
+
return 1;
|
|
935
|
+
}
|
|
936
|
+
const { format } = resolved;
|
|
937
|
+
let content;
|
|
938
|
+
try {
|
|
939
|
+
content = deps.readFile(input);
|
|
940
|
+
} catch (error) {
|
|
941
|
+
context.stderr(`maestro snapshot: could not read input file '${input}': ${errorMessage4(error)}`);
|
|
942
|
+
return 1;
|
|
943
|
+
}
|
|
944
|
+
let config;
|
|
945
|
+
try {
|
|
946
|
+
config = loadDeclarativeConfigFromString3(content, {
|
|
947
|
+
format,
|
|
948
|
+
yamlParser: format === "yaml" ? yamlParser3 : void 0,
|
|
949
|
+
source: input
|
|
950
|
+
});
|
|
951
|
+
} catch (error) {
|
|
952
|
+
context.stderr(`maestro snapshot: ${errorMessage4(error)}`);
|
|
953
|
+
return 1;
|
|
954
|
+
}
|
|
955
|
+
const snapshot = buildDeclarativeSnapshot(config, { path: input, format });
|
|
956
|
+
const output = serializeSnapshot(snapshot);
|
|
957
|
+
if (out) {
|
|
958
|
+
try {
|
|
959
|
+
deps.writeFile(out, output);
|
|
960
|
+
} catch (error) {
|
|
961
|
+
context.stderr(`maestro snapshot: could not write output file '${out}': ${errorMessage4(error)}`);
|
|
962
|
+
return 1;
|
|
963
|
+
}
|
|
964
|
+
context.stdout(`Snapshot written: ${out}`);
|
|
965
|
+
context.stdout(`Entities: ${snapshot.summary.entities}`);
|
|
966
|
+
context.stdout(`Consumers: ${snapshot.summary.consumers}`);
|
|
967
|
+
context.stdout(`Relations: ${snapshot.summary.relations}`);
|
|
968
|
+
} else {
|
|
969
|
+
context.stdout(output.replace(/\n$/, ""));
|
|
970
|
+
}
|
|
971
|
+
return 0;
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// src/commands/introspect/parseIntrospectArgs.ts
|
|
977
|
+
var ALIASES4 = { i: "input" };
|
|
978
|
+
function parseFlags5(argv) {
|
|
979
|
+
const flags = {};
|
|
980
|
+
for (let i = 0; i < argv.length; i++) {
|
|
981
|
+
const arg = argv[i];
|
|
982
|
+
const isLong = arg.startsWith("--");
|
|
983
|
+
const isShort = !isLong && arg.startsWith("-") && arg.length > 1;
|
|
984
|
+
if (!isLong && !isShort) continue;
|
|
985
|
+
const body = isLong ? arg.slice(2) : arg.slice(1);
|
|
986
|
+
const eqIndex = body.indexOf("=");
|
|
987
|
+
if (eqIndex !== -1) {
|
|
988
|
+
const key2 = body.slice(0, eqIndex);
|
|
989
|
+
flags[ALIASES4[key2] ?? key2] = body.slice(eqIndex + 1);
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
const key = ALIASES4[body] ?? body;
|
|
993
|
+
const next = argv[i + 1];
|
|
994
|
+
if (next !== void 0 && !next.startsWith("-")) {
|
|
995
|
+
flags[key] = next;
|
|
996
|
+
i++;
|
|
997
|
+
} else {
|
|
998
|
+
flags[key] = "true";
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
return flags;
|
|
1002
|
+
}
|
|
1003
|
+
function parseIntrospectArgs(argv) {
|
|
1004
|
+
const flags = parseFlags5(argv);
|
|
1005
|
+
const input = flags["input"];
|
|
1006
|
+
if (!input || input === "true") {
|
|
1007
|
+
return { ok: false, error: "Missing required flag --input <path> (or -i <path>)." };
|
|
1008
|
+
}
|
|
1009
|
+
const out = flags["out"] && flags["out"] !== "true" ? flags["out"] : void 0;
|
|
1010
|
+
return { ok: true, value: { input, out } };
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// src/commands/introspect/buildIntrospection.ts
|
|
1014
|
+
var INTROSPECTION_SCHEMA_VERSION = 1;
|
|
1015
|
+
var INTROSPECTION_KIND = "maestro.introspection";
|
|
1016
|
+
function isObject(value) {
|
|
1017
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1018
|
+
}
|
|
1019
|
+
function nonEmptyString(value) {
|
|
1020
|
+
return typeof value === "string" && value.length > 0;
|
|
1021
|
+
}
|
|
1022
|
+
function validateEndpoint(endpoint, path) {
|
|
1023
|
+
if (!isObject(endpoint)) return `${path} must be an object with "table" and "field".`;
|
|
1024
|
+
if (!nonEmptyString(endpoint["table"])) return `${path}.table must be a non-empty string.`;
|
|
1025
|
+
if (!nonEmptyString(endpoint["field"])) return `${path}.field must be a non-empty string.`;
|
|
1026
|
+
return void 0;
|
|
1027
|
+
}
|
|
1028
|
+
function validateIntrospectionResult(value) {
|
|
1029
|
+
if (!isObject(value)) {
|
|
1030
|
+
return { ok: false, error: 'input must be a JSON object with "entities" and "relations" arrays.' };
|
|
1031
|
+
}
|
|
1032
|
+
if (!Array.isArray(value["entities"])) {
|
|
1033
|
+
return { ok: false, error: '"entities" must be an array.' };
|
|
1034
|
+
}
|
|
1035
|
+
if (!Array.isArray(value["relations"])) {
|
|
1036
|
+
return { ok: false, error: '"relations" must be an array.' };
|
|
1037
|
+
}
|
|
1038
|
+
const entities = value["entities"];
|
|
1039
|
+
for (let i = 0; i < entities.length; i++) {
|
|
1040
|
+
const entity = entities[i];
|
|
1041
|
+
if (!isObject(entity)) return { ok: false, error: `entities[${i}] must be an object.` };
|
|
1042
|
+
if (!nonEmptyString(entity["table"])) {
|
|
1043
|
+
return { ok: false, error: `entities[${i}].table must be a non-empty string.` };
|
|
1044
|
+
}
|
|
1045
|
+
if (!Array.isArray(entity["fields"])) {
|
|
1046
|
+
return { ok: false, error: `entities[${i}].fields must be an array.` };
|
|
1047
|
+
}
|
|
1048
|
+
const fields = entity["fields"];
|
|
1049
|
+
for (let j = 0; j < fields.length; j++) {
|
|
1050
|
+
const field = fields[j];
|
|
1051
|
+
const at = `entities[${i}].fields[${j}]`;
|
|
1052
|
+
if (!isObject(field)) return { ok: false, error: `${at} must be an object.` };
|
|
1053
|
+
if (!nonEmptyString(field["name"])) return { ok: false, error: `${at}.name must be a non-empty string.` };
|
|
1054
|
+
if (typeof field["nativeType"] !== "string") return { ok: false, error: `${at}.nativeType must be a string.` };
|
|
1055
|
+
if (typeof field["type"] !== "string") return { ok: false, error: `${at}.type must be a string.` };
|
|
1056
|
+
if (typeof field["nullable"] !== "boolean") return { ok: false, error: `${at}.nullable must be a boolean.` };
|
|
1057
|
+
if (typeof field["isPrimaryKey"] !== "boolean") {
|
|
1058
|
+
return { ok: false, error: `${at}.isPrimaryKey must be a boolean.` };
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
const relations = value["relations"];
|
|
1063
|
+
for (let i = 0; i < relations.length; i++) {
|
|
1064
|
+
const relation = relations[i];
|
|
1065
|
+
const at = `relations[${i}]`;
|
|
1066
|
+
if (!isObject(relation)) return { ok: false, error: `${at} must be an object.` };
|
|
1067
|
+
if (!nonEmptyString(relation["id"])) return { ok: false, error: `${at}.id must be a non-empty string.` };
|
|
1068
|
+
if (typeof relation["type"] !== "string") return { ok: false, error: `${at}.type must be a string.` };
|
|
1069
|
+
const fromError = validateEndpoint(relation["from"], `${at}.from`);
|
|
1070
|
+
if (fromError) return { ok: false, error: fromError };
|
|
1071
|
+
const toError = validateEndpoint(relation["to"], `${at}.to`);
|
|
1072
|
+
if (toError) return { ok: false, error: toError };
|
|
1073
|
+
if (relation["confidence"] !== "definite" && relation["confidence"] !== "inferred") {
|
|
1074
|
+
return { ok: false, error: `${at}.confidence must be "definite" or "inferred".` };
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return { ok: true, value };
|
|
1078
|
+
}
|
|
1079
|
+
function compareStrings2(a, b) {
|
|
1080
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
1081
|
+
}
|
|
1082
|
+
function canonicalizeResult(result) {
|
|
1083
|
+
const entities = [...result.entities].sort((a, b) => compareStrings2(a.table, b.table));
|
|
1084
|
+
const relations = [...result.relations].sort((a, b) => compareStrings2(a.id, b.id));
|
|
1085
|
+
return { entities, relations };
|
|
1086
|
+
}
|
|
1087
|
+
function sortKeys2(value) {
|
|
1088
|
+
if (Array.isArray(value)) return value.map(sortKeys2);
|
|
1089
|
+
if (value !== null && typeof value === "object") {
|
|
1090
|
+
const sorted = {};
|
|
1091
|
+
for (const key of Object.keys(value).sort()) {
|
|
1092
|
+
sorted[key] = sortKeys2(value[key]);
|
|
1093
|
+
}
|
|
1094
|
+
return sorted;
|
|
1095
|
+
}
|
|
1096
|
+
return value;
|
|
1097
|
+
}
|
|
1098
|
+
function buildIntrospectionArtifact(result, source) {
|
|
1099
|
+
const canonical = canonicalizeResult(result);
|
|
1100
|
+
return {
|
|
1101
|
+
schemaVersion: INTROSPECTION_SCHEMA_VERSION,
|
|
1102
|
+
kind: INTROSPECTION_KIND,
|
|
1103
|
+
source,
|
|
1104
|
+
summary: {
|
|
1105
|
+
entities: canonical.entities.length,
|
|
1106
|
+
relations: canonical.relations.length
|
|
1107
|
+
},
|
|
1108
|
+
result: canonical
|
|
1109
|
+
};
|
|
1110
|
+
}
|
|
1111
|
+
function serializeIntrospection(artifact) {
|
|
1112
|
+
return `${JSON.stringify(sortKeys2(artifact), null, 2)}
|
|
1113
|
+
`;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
// src/commands/introspect/IntrospectCommand.ts
|
|
1117
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
1118
|
+
var defaultDeps5 = {
|
|
1119
|
+
readFile: (path) => readFileSync5(path, "utf-8"),
|
|
1120
|
+
writeFile: (path, content) => writeFileSync3(path, content, "utf-8")
|
|
1121
|
+
};
|
|
1122
|
+
function errorMessage5(error) {
|
|
1123
|
+
return error instanceof Error ? error.message : String(error);
|
|
1124
|
+
}
|
|
1125
|
+
var INTROSPECT_HELP_TEXT = `maestro introspect \u2014 Validate and canonicalize a local IntrospectionResult (offline)
|
|
1126
|
+
|
|
1127
|
+
Reads a local JSON file describing an IntrospectionResult (the core's introspection contract:
|
|
1128
|
+
{ "entities": [...], "relations": [...] }), validates it against that contract, and emits a canonical,
|
|
1129
|
+
deterministic JSON artifact. The same (semantically equivalent) input always produces byte-identical
|
|
1130
|
+
output, so the artifact is a stable, versionable input for future comparison, generation or review.
|
|
1131
|
+
|
|
1132
|
+
OFFLINE / LOCAL ONLY \u2014 this is NOT a database connector (yet). It does NOT connect to PostgreSQL,
|
|
1133
|
+
MySQL, MongoDB or any database, does NOT read a connection string or .env, does NOT load an ORM/driver,
|
|
1134
|
+
and does NOT execute any query. It operates only on the local JSON file you pass with --input, which is
|
|
1135
|
+
an intermediate/offline representation of an introspection \u2014 not a live schema read. Connecting to real
|
|
1136
|
+
datasources is a separate, not-yet-implemented step.
|
|
1137
|
+
|
|
1138
|
+
This command does NOT create a Maestro engine (no createMaestro()), does NOT execute operations, and
|
|
1139
|
+
does NOT require declared operations to have an implementation. It never modifies the input file.
|
|
1140
|
+
|
|
1141
|
+
Usage:
|
|
1142
|
+
maestro introspect --input <path> [--out <path>]
|
|
1143
|
+
maestro introspect -i <path>
|
|
1144
|
+
|
|
1145
|
+
Flags:
|
|
1146
|
+
--input, -i <path> Required. Path to a local IntrospectionResult JSON file.
|
|
1147
|
+
--out <path> Optional. Write the canonical artifact to this file instead of stdout.
|
|
1148
|
+
|
|
1149
|
+
Input format (JSON \u2014 the core's IntrospectionResult contract):
|
|
1150
|
+
{
|
|
1151
|
+
"entities": [
|
|
1152
|
+
{
|
|
1153
|
+
"table": "users",
|
|
1154
|
+
"fields": [
|
|
1155
|
+
{ "name": "id", "nativeType": "uuid", "type": "uuid", "nullable": false, "isPrimaryKey": true }
|
|
1156
|
+
]
|
|
1157
|
+
}
|
|
1158
|
+
],
|
|
1159
|
+
"relations": [
|
|
1160
|
+
{
|
|
1161
|
+
"id": "orders.user_id->users.id",
|
|
1162
|
+
"type": "many-to-one",
|
|
1163
|
+
"from": { "table": "orders", "field": "user_id" },
|
|
1164
|
+
"to": { "table": "users", "field": "id" },
|
|
1165
|
+
"confidence": "definite"
|
|
1166
|
+
}
|
|
1167
|
+
]
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
Output format (JSON):
|
|
1171
|
+
{
|
|
1172
|
+
"schemaVersion": 1,
|
|
1173
|
+
"kind": "maestro.introspection",
|
|
1174
|
+
"source": { "path": "<the --input value>" },
|
|
1175
|
+
"summary": { "entities": N, "relations": N },
|
|
1176
|
+
"result": { "entities": [...], "relations": [...] }
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
Determinism:
|
|
1180
|
+
Entities are sorted by table, relations by id, and every object key is sorted. No timestamps,
|
|
1181
|
+
absolute paths or hashes are ever included.
|
|
1182
|
+
|
|
1183
|
+
Difference from other commands:
|
|
1184
|
+
- introspect: validates/canonicalizes an IntrospectionResult (real DB-schema contract), offline.
|
|
1185
|
+
- snapshot: canonicalizes a declarative config file (maestro.config.yaml/json).
|
|
1186
|
+
- diff: compares two declarative config files.
|
|
1187
|
+
|
|
1188
|
+
Exit codes:
|
|
1189
|
+
0 The input is a valid IntrospectionResult and the artifact was produced.
|
|
1190
|
+
1 Usage, file-read, parse, validation or write error.
|
|
1191
|
+
|
|
1192
|
+
Examples:
|
|
1193
|
+
maestro introspect --input ./introspection.json
|
|
1194
|
+
maestro introspect -i ./introspection.json
|
|
1195
|
+
maestro introspect --input ./introspection.json --out ./maestro.introspection.json`;
|
|
1196
|
+
function createIntrospectCommand(deps = defaultDeps5) {
|
|
1197
|
+
return {
|
|
1198
|
+
name: "introspect",
|
|
1199
|
+
description: "Validate and canonicalize a local IntrospectionResult JSON file (offline)",
|
|
1200
|
+
helpText: INTROSPECT_HELP_TEXT,
|
|
1201
|
+
run(context) {
|
|
1202
|
+
const parsed = parseIntrospectArgs(context.argv);
|
|
1203
|
+
if (!parsed.ok) {
|
|
1204
|
+
context.stderr(`maestro introspect: ${parsed.error}`);
|
|
1205
|
+
return 1;
|
|
1206
|
+
}
|
|
1207
|
+
const { input, out } = parsed.value;
|
|
1208
|
+
let content;
|
|
1209
|
+
try {
|
|
1210
|
+
content = deps.readFile(input);
|
|
1211
|
+
} catch (error) {
|
|
1212
|
+
context.stderr(`maestro introspect: could not read input file '${input}': ${errorMessage5(error)}`);
|
|
1213
|
+
return 1;
|
|
1214
|
+
}
|
|
1215
|
+
let raw;
|
|
1216
|
+
try {
|
|
1217
|
+
raw = JSON.parse(content);
|
|
1218
|
+
} catch (error) {
|
|
1219
|
+
context.stderr(`maestro introspect: invalid JSON in '${input}': ${errorMessage5(error)}`);
|
|
1220
|
+
return 1;
|
|
1221
|
+
}
|
|
1222
|
+
const validated = validateIntrospectionResult(raw);
|
|
1223
|
+
if (!validated.ok) {
|
|
1224
|
+
context.stderr(`maestro introspect: ${validated.error}`);
|
|
1225
|
+
return 1;
|
|
1226
|
+
}
|
|
1227
|
+
const artifact = buildIntrospectionArtifact(validated.value, { path: input });
|
|
1228
|
+
const output = serializeIntrospection(artifact);
|
|
1229
|
+
if (out) {
|
|
1230
|
+
try {
|
|
1231
|
+
deps.writeFile(out, output);
|
|
1232
|
+
} catch (error) {
|
|
1233
|
+
context.stderr(`maestro introspect: could not write output file '${out}': ${errorMessage5(error)}`);
|
|
1234
|
+
return 1;
|
|
1235
|
+
}
|
|
1236
|
+
context.stdout(`Introspection written: ${out}`);
|
|
1237
|
+
context.stdout(`Entities: ${artifact.summary.entities}`);
|
|
1238
|
+
context.stdout(`Relations: ${artifact.summary.relations}`);
|
|
1239
|
+
} else {
|
|
1240
|
+
context.stdout(output.replace(/\n$/, ""));
|
|
1241
|
+
}
|
|
1242
|
+
return 0;
|
|
1243
|
+
}
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// src/runCli.ts
|
|
1248
|
+
var defaultRegistry = new CommandRegistry();
|
|
1249
|
+
defaultRegistry.register(createGenerateCommand());
|
|
1250
|
+
defaultRegistry.register(createValidateCommand());
|
|
1251
|
+
defaultRegistry.register(createDiffCommand());
|
|
1252
|
+
defaultRegistry.register(createSnapshotCommand());
|
|
1253
|
+
defaultRegistry.register(createIntrospectCommand());
|
|
1254
|
+
async function runCli(options) {
|
|
1255
|
+
const stdout = options.stdout ?? ((message) => console.log(message));
|
|
1256
|
+
const stderr = options.stderr ?? ((message) => console.error(message));
|
|
1257
|
+
const registry = options.registry ?? defaultRegistry;
|
|
1258
|
+
const parsed = parseArgs(options.argv);
|
|
1259
|
+
if (parsed.version) {
|
|
1260
|
+
stdout(getCliVersion());
|
|
1261
|
+
return 0;
|
|
1262
|
+
}
|
|
1263
|
+
const command = parsed.command ? registry.get(parsed.command) : void 0;
|
|
1264
|
+
if (parsed.help) {
|
|
1265
|
+
stdout(command?.helpText ?? formatHelp(registry));
|
|
1266
|
+
return 0;
|
|
1267
|
+
}
|
|
1268
|
+
if (!parsed.command) {
|
|
1269
|
+
stdout(formatHelp(registry));
|
|
1270
|
+
return 0;
|
|
1271
|
+
}
|
|
1272
|
+
if (!command) {
|
|
1273
|
+
stderr(`Unknown command '${parsed.command}'. Run "maestro --help" to see available commands.`);
|
|
1274
|
+
return 1;
|
|
1275
|
+
}
|
|
1276
|
+
return command.run({ argv: parsed.args, stdout, stderr });
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
export {
|
|
1280
|
+
CommandRegistry,
|
|
1281
|
+
PLANNED_COMMANDS,
|
|
1282
|
+
parseArgs,
|
|
1283
|
+
formatHelp,
|
|
1284
|
+
getCliVersion,
|
|
1285
|
+
parseGenerateArgs,
|
|
1286
|
+
createGenerateCommand,
|
|
1287
|
+
parseValidateArgs,
|
|
1288
|
+
createValidateCommand,
|
|
1289
|
+
parseDiffArgs,
|
|
1290
|
+
diffDeclarativeConfigs,
|
|
1291
|
+
formatDeclarativeDiff,
|
|
1292
|
+
createDiffCommand,
|
|
1293
|
+
parseSnapshotArgs,
|
|
1294
|
+
SNAPSHOT_SCHEMA_VERSION,
|
|
1295
|
+
SNAPSHOT_KIND,
|
|
1296
|
+
buildDeclarativeSnapshot,
|
|
1297
|
+
serializeSnapshot,
|
|
1298
|
+
createSnapshotCommand,
|
|
1299
|
+
parseIntrospectArgs,
|
|
1300
|
+
INTROSPECTION_SCHEMA_VERSION,
|
|
1301
|
+
INTROSPECTION_KIND,
|
|
1302
|
+
validateIntrospectionResult,
|
|
1303
|
+
buildIntrospectionArtifact,
|
|
1304
|
+
serializeIntrospection,
|
|
1305
|
+
createIntrospectCommand,
|
|
1306
|
+
defaultRegistry,
|
|
1307
|
+
runCli
|
|
1308
|
+
};
|
|
1309
|
+
//# sourceMappingURL=chunk-CSDLIL7L.js.map
|