@kirrosh/zond 0.14.0 → 0.17.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/CHANGELOG.md +132 -112
- package/README.md +3 -10
- package/package.json +4 -4
- package/src/cli/commands/ci-init.ts +12 -1
- package/src/cli/commands/coverage.ts +21 -1
- package/src/cli/commands/db.ts +121 -0
- package/src/cli/commands/describe.ts +60 -0
- package/src/cli/commands/export.ts +144 -0
- package/src/cli/commands/generate.ts +158 -0
- package/src/cli/commands/guide.ts +127 -0
- package/src/cli/commands/init.ts +57 -0
- package/src/cli/commands/request.ts +57 -0
- package/src/cli/commands/run.ts +74 -14
- package/src/cli/commands/serve.ts +62 -3
- package/src/cli/commands/sync.ts +240 -0
- package/src/cli/commands/validate.ts +18 -2
- package/src/cli/index.ts +258 -17
- package/src/cli/json-envelope.ts +19 -0
- package/src/core/diagnostics/db-analysis.ts +423 -0
- package/src/core/diagnostics/failure-hints.ts +40 -0
- package/src/core/exporter/postman.ts +963 -0
- package/src/core/generator/data-factory.ts +55 -9
- package/src/core/generator/describe.ts +250 -0
- package/src/core/generator/guide-builder.ts +20 -0
- package/src/core/generator/index.ts +1 -1
- package/src/core/generator/openapi-reader.ts +6 -0
- package/src/core/generator/serializer.ts +17 -2
- package/src/core/generator/suite-generator.ts +291 -29
- package/src/core/generator/types.ts +1 -0
- package/src/core/meta/meta-store.ts +78 -0
- package/src/core/meta/types.ts +21 -0
- package/src/core/parser/schema.ts +12 -2
- package/src/core/parser/types.ts +12 -1
- package/src/core/parser/variables.ts +3 -0
- package/src/core/parser/yaml-parser.ts +2 -1
- package/src/core/runner/assertions.ts +44 -20
- package/src/core/runner/execute-run.ts +31 -8
- package/src/core/runner/executor.ts +35 -8
- package/src/core/runner/http-client.ts +1 -1
- package/src/core/runner/send-request.ts +94 -0
- package/src/core/runner/types.ts +2 -0
- package/src/core/sync/spec-differ.ts +38 -0
- package/src/db/queries.ts +4 -2
- package/src/db/schema.ts +11 -3
- package/src/web/views/suites-tab.ts +1 -1
- package/src/cli/commands/mcp.ts +0 -16
- package/src/mcp/descriptions.ts +0 -71
- package/src/mcp/server.ts +0 -45
- package/src/mcp/tools/ci-init.ts +0 -54
- package/src/mcp/tools/coverage-analysis.ts +0 -141
- package/src/mcp/tools/describe-endpoint.ts +0 -242
- package/src/mcp/tools/generate-and-save.ts +0 -202
- package/src/mcp/tools/manage-server.ts +0 -86
- package/src/mcp/tools/query-db.ts +0 -300
- package/src/mcp/tools/run-tests.ts +0 -115
- package/src/mcp/tools/save-test-suite.ts +0 -218
- package/src/mcp/tools/send-request.ts +0 -97
- package/src/mcp/tools/set-work-dir.ts +0 -35
- package/src/mcp/tools/setup-api.ts +0 -88
package/src/cli/index.ts
CHANGED
|
@@ -3,9 +3,16 @@
|
|
|
3
3
|
import { runCommand } from "./commands/run.ts";
|
|
4
4
|
import { validateCommand } from "./commands/validate.ts";
|
|
5
5
|
import { serveCommand } from "./commands/serve.ts";
|
|
6
|
-
import { mcpCommand } from "./commands/mcp.ts";
|
|
7
6
|
import { coverageCommand } from "./commands/coverage.ts";
|
|
8
7
|
import { ciInitCommand } from "./commands/ci-init.ts";
|
|
8
|
+
import { initCommand } from "./commands/init.ts";
|
|
9
|
+
import { describeCommand } from "./commands/describe.ts";
|
|
10
|
+
import { dbCommand } from "./commands/db.ts";
|
|
11
|
+
import { requestCommand } from "./commands/request.ts";
|
|
12
|
+
import { guideCommand } from "./commands/guide.ts";
|
|
13
|
+
import { generateCommand } from "./commands/generate.ts";
|
|
14
|
+
import { exportCommand } from "./commands/export.ts";
|
|
15
|
+
import { syncCommand } from "./commands/sync.ts";
|
|
9
16
|
import { printError } from "./output.ts";
|
|
10
17
|
import { getRuntimeInfo } from "./runtime.ts";
|
|
11
18
|
import { getDb } from "../db/schema.ts";
|
|
@@ -21,6 +28,23 @@ export interface ParsedArgs {
|
|
|
21
28
|
flags: Record<string, string | boolean>;
|
|
22
29
|
}
|
|
23
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Strip MSYS/Git Bash automatic path conversion.
|
|
33
|
+
* Git Bash on Windows converts "/foo" → "C:/Program Files/Git/foo".
|
|
34
|
+
* Detect and reverse this for flags that expect API paths (e.g. --path /users).
|
|
35
|
+
*/
|
|
36
|
+
const MSYS_PREFIX_RE = /^[A-Z]:[\\/](?:Program Files[\\/]Git|msys64|usr)[\\/]/i;
|
|
37
|
+
|
|
38
|
+
function stripMsysPath(value: string): string {
|
|
39
|
+
if (!MSYS_PREFIX_RE.test(value)) return value;
|
|
40
|
+
// Extract the original path: "C:/Program Files/Git/products" → "/products"
|
|
41
|
+
const stripped = value.replace(MSYS_PREFIX_RE, "/");
|
|
42
|
+
return stripped;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Flags whose values are API paths, not filesystem paths — subject to MSYS fix */
|
|
46
|
+
const API_PATH_FLAGS = new Set(["path", "json-path"]);
|
|
47
|
+
|
|
24
48
|
export function parseArgs(argv: string[]): ParsedArgs {
|
|
25
49
|
// argv: [bunPath, scriptPath, ...userArgs]
|
|
26
50
|
const args = argv.slice(2);
|
|
@@ -36,12 +60,15 @@ export function parseArgs(argv: string[]): ParsedArgs {
|
|
|
36
60
|
const eqIndex = arg.indexOf("=");
|
|
37
61
|
if (eqIndex !== -1) {
|
|
38
62
|
// --flag=value
|
|
39
|
-
|
|
63
|
+
const key = arg.slice(2, eqIndex);
|
|
64
|
+
let val = arg.slice(eqIndex + 1);
|
|
65
|
+
if (API_PATH_FLAGS.has(key)) val = stripMsysPath(val);
|
|
66
|
+
flags[key] = val;
|
|
40
67
|
} else {
|
|
41
68
|
const key = arg.slice(2);
|
|
42
69
|
const next = args[i + 1];
|
|
43
70
|
if (next !== undefined && !next.startsWith("-")) {
|
|
44
|
-
flags[key] = next;
|
|
71
|
+
flags[key] = API_PATH_FLAGS.has(key) ? stripMsysPath(next) : next;
|
|
45
72
|
i++;
|
|
46
73
|
} else {
|
|
47
74
|
flags[key] = true;
|
|
@@ -69,10 +96,17 @@ Usage:
|
|
|
69
96
|
zond run <path> Run API tests
|
|
70
97
|
zond validate <path> Validate test files without running
|
|
71
98
|
zond coverage Analyze API test coverage
|
|
99
|
+
zond init Register a new API for testing
|
|
100
|
+
zond describe <spec> Describe endpoints from OpenAPI spec
|
|
101
|
+
zond db <subcommand> Query the test database
|
|
102
|
+
zond request <method> <url> Send an ad-hoc HTTP request
|
|
103
|
+
zond generate <spec> Generate test suites from OpenAPI spec
|
|
104
|
+
zond guide <spec> Generate test generation guide from OpenAPI spec
|
|
72
105
|
zond serve Start web dashboard
|
|
73
|
-
zond
|
|
74
|
-
--dir <path> Set working directory (relative paths resolve here)
|
|
106
|
+
zond ui Alias for 'serve --open' (start dashboard & open browser)
|
|
75
107
|
zond ci init Generate CI/CD workflow (GitHub Actions, GitLab CI)
|
|
108
|
+
zond export postman <path> Export YAML tests as Postman Collection v2.1
|
|
109
|
+
zond sync <spec> Detect new/removed endpoints and generate tests for new ones
|
|
76
110
|
|
|
77
111
|
Options for 'run':
|
|
78
112
|
--dry-run Show requests without sending them (exit code always 0)
|
|
@@ -88,6 +122,40 @@ Options for 'run':
|
|
|
88
122
|
--safe Run only GET tests (read-only, safe mode)
|
|
89
123
|
--tag <tag> Filter suites by tag (repeatable, comma-separated, OR logic)
|
|
90
124
|
|
|
125
|
+
Options for 'init':
|
|
126
|
+
--name <name> API name (auto-detected from spec title if omitted)
|
|
127
|
+
--spec <path> Path to OpenAPI spec file
|
|
128
|
+
--base-url <url> Override base URL
|
|
129
|
+
--force Overwrite existing API collection
|
|
130
|
+
|
|
131
|
+
Options for 'describe':
|
|
132
|
+
--compact List all endpoints briefly
|
|
133
|
+
--method <method> HTTP method for single endpoint detail
|
|
134
|
+
--path <path> Endpoint path for single endpoint detail
|
|
135
|
+
|
|
136
|
+
Options for 'db':
|
|
137
|
+
zond db collections List all API collections
|
|
138
|
+
zond db runs [--limit N] List recent test runs
|
|
139
|
+
zond db run <id> [--verbose] Show run details
|
|
140
|
+
zond db diagnose <id> Diagnose run failures
|
|
141
|
+
zond db compare <idA> <idB> Compare two runs
|
|
142
|
+
|
|
143
|
+
Options for 'request':
|
|
144
|
+
--header <H> Request header "Name: Value" (repeatable)
|
|
145
|
+
--body <json> Request body (JSON string)
|
|
146
|
+
--env <name> Environment for variable interpolation
|
|
147
|
+
--api <name> Collection name (loads env from its directory)
|
|
148
|
+
--json-path <path> Extract value from response (dot notation)
|
|
149
|
+
|
|
150
|
+
Options for 'generate':
|
|
151
|
+
--output <dir> Output directory for generated test files (required)
|
|
152
|
+
--tag <tag> Generate only for endpoints with this tag
|
|
153
|
+
--uncovered-only Skip endpoints already covered by existing tests
|
|
154
|
+
|
|
155
|
+
Options for 'guide':
|
|
156
|
+
--tests-dir <dir> Filter to uncovered endpoints only
|
|
157
|
+
--tag <tag> Generate only for endpoints with this tag
|
|
158
|
+
|
|
91
159
|
Options for 'coverage':
|
|
92
160
|
--api <name> Use API collection (auto-resolves spec and tests dir)
|
|
93
161
|
--spec <path> Path to OpenAPI spec (required unless --api used)
|
|
@@ -95,11 +163,11 @@ Options for 'coverage':
|
|
|
95
163
|
--fail-on-coverage N Exit 1 when coverage percentage is below N (0–100)
|
|
96
164
|
--run-id <number> Cross-reference with a test run for pass/fail/5xx breakdown
|
|
97
165
|
|
|
98
|
-
Options for 'serve':
|
|
166
|
+
Options for 'serve' / 'ui':
|
|
99
167
|
--port <port> Server port (default: 8080)
|
|
100
168
|
--host <host> Server host (default: 0.0.0.0)
|
|
101
|
-
--openapi <spec> Path to OpenAPI spec for Explorer
|
|
102
169
|
--db <path> Path to SQLite database file (default: zond.db)
|
|
170
|
+
--open Open dashboard in browser after starting
|
|
103
171
|
--watch Enable dev mode with hot reload (auto-refresh browser on file changes)
|
|
104
172
|
|
|
105
173
|
Options for 'ci init':
|
|
@@ -108,7 +176,18 @@ Options for 'ci init':
|
|
|
108
176
|
--dir <path> Project root directory (default: current directory)
|
|
109
177
|
--force Overwrite existing CI config
|
|
110
178
|
|
|
179
|
+
Options for 'export postman':
|
|
180
|
+
--output <file> Output file path (default: collection.postman.json)
|
|
181
|
+
--env <file> Also export .env.yaml as Postman environment
|
|
182
|
+
--collection-name <name> Collection name (default: derived from path)
|
|
183
|
+
|
|
184
|
+
Options for 'sync':
|
|
185
|
+
--tests <dir> Path to test files directory (required)
|
|
186
|
+
--dry-run Show what would be generated without writing files
|
|
187
|
+
--tag <tag> Limit sync to endpoints with this tag
|
|
188
|
+
|
|
111
189
|
General:
|
|
190
|
+
--json Output in JSON envelope format (available for all commands)
|
|
112
191
|
--help, -h Show this help
|
|
113
192
|
--version, -v Show version`);
|
|
114
193
|
}
|
|
@@ -117,6 +196,7 @@ const VALID_REPORTERS = new Set<string>(["console", "json", "junit"]);
|
|
|
117
196
|
|
|
118
197
|
async function main(): Promise<number> {
|
|
119
198
|
const { command, positional, flags } = parseArgs(process.argv);
|
|
199
|
+
const jsonFlag = flags["json"] === true;
|
|
120
200
|
|
|
121
201
|
// Help
|
|
122
202
|
if (command === "help" || command === "--help" || flags["help"] === true || flags["h"] === true) {
|
|
@@ -205,6 +285,7 @@ async function main(): Promise<number> {
|
|
|
205
285
|
tag: tags.length > 0 ? tags : undefined,
|
|
206
286
|
envVars: envVarValues.length > 0 ? envVarValues : undefined,
|
|
207
287
|
dryRun: flags["dry-run"] === true,
|
|
288
|
+
json: jsonFlag,
|
|
208
289
|
});
|
|
209
290
|
}
|
|
210
291
|
|
|
@@ -215,9 +296,10 @@ async function main(): Promise<number> {
|
|
|
215
296
|
return 2;
|
|
216
297
|
}
|
|
217
298
|
|
|
218
|
-
return validateCommand({ path });
|
|
299
|
+
return validateCommand({ path, json: jsonFlag });
|
|
219
300
|
}
|
|
220
301
|
|
|
302
|
+
case "ui":
|
|
221
303
|
case "serve": {
|
|
222
304
|
const portRaw = flags["port"];
|
|
223
305
|
let port: number | undefined;
|
|
@@ -231,16 +313,9 @@ async function main(): Promise<number> {
|
|
|
231
313
|
return serveCommand({
|
|
232
314
|
port,
|
|
233
315
|
host: typeof flags["host"] === "string" ? flags["host"] : undefined,
|
|
234
|
-
openapiSpec: typeof flags["openapi"] === "string" ? flags["openapi"] : undefined,
|
|
235
316
|
dbPath: typeof flags["db"] === "string" ? flags["db"] : undefined,
|
|
236
317
|
watch: flags["watch"] === true,
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
case "mcp": {
|
|
241
|
-
return mcpCommand({
|
|
242
|
-
dbPath: typeof flags["db"] === "string" ? flags["db"] : undefined,
|
|
243
|
-
dir: typeof flags["dir"] === "string" ? flags["dir"] : undefined,
|
|
318
|
+
open: command === "ui" || flags["open"] === true,
|
|
244
319
|
});
|
|
245
320
|
}
|
|
246
321
|
|
|
@@ -257,6 +332,7 @@ async function main(): Promise<number> {
|
|
|
257
332
|
platform,
|
|
258
333
|
force: flags["force"] === true,
|
|
259
334
|
dir: typeof flags["dir"] === "string" ? flags["dir"] : undefined,
|
|
335
|
+
json: jsonFlag,
|
|
260
336
|
});
|
|
261
337
|
}
|
|
262
338
|
|
|
@@ -304,7 +380,172 @@ async function main(): Promise<number> {
|
|
|
304
380
|
return 2;
|
|
305
381
|
}
|
|
306
382
|
}
|
|
307
|
-
return coverageCommand({ spec, tests, failOnCoverage, runId });
|
|
383
|
+
return coverageCommand({ spec, tests, failOnCoverage, runId, json: jsonFlag });
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
case "init": {
|
|
387
|
+
return initCommand({
|
|
388
|
+
name: typeof flags["name"] === "string" ? flags["name"] : undefined,
|
|
389
|
+
spec: typeof flags["spec"] === "string" ? flags["spec"] : positional[0],
|
|
390
|
+
baseUrl: typeof flags["base-url"] === "string" ? flags["base-url"] : undefined,
|
|
391
|
+
dir: typeof flags["dir"] === "string" ? flags["dir"] : undefined,
|
|
392
|
+
force: flags["force"] === true,
|
|
393
|
+
insecure: flags["insecure"] === true,
|
|
394
|
+
dbPath: typeof flags["db"] === "string" ? flags["db"] : undefined,
|
|
395
|
+
json: jsonFlag,
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
case "describe": {
|
|
400
|
+
const specPath = positional[0];
|
|
401
|
+
if (!specPath) {
|
|
402
|
+
printError("Missing spec path. Usage: zond describe <spec> [--compact | --method <M> --path <P>]");
|
|
403
|
+
return 2;
|
|
404
|
+
}
|
|
405
|
+
return describeCommand({
|
|
406
|
+
specPath,
|
|
407
|
+
compact: flags["compact"] === true,
|
|
408
|
+
method: typeof flags["method"] === "string" ? flags["method"] : undefined,
|
|
409
|
+
path: typeof flags["path"] === "string" ? flags["path"] : undefined,
|
|
410
|
+
json: jsonFlag,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
case "db": {
|
|
415
|
+
const dbSub = positional[0];
|
|
416
|
+
if (!dbSub) {
|
|
417
|
+
printError("Missing subcommand. Usage: zond db <collections|runs|run|diagnose|compare> [args]");
|
|
418
|
+
return 2;
|
|
419
|
+
}
|
|
420
|
+
const limitRaw = flags["limit"];
|
|
421
|
+
let limit: number | undefined;
|
|
422
|
+
if (typeof limitRaw === "string") {
|
|
423
|
+
limit = parseInt(limitRaw, 10);
|
|
424
|
+
if (isNaN(limit) || limit <= 0) limit = undefined;
|
|
425
|
+
}
|
|
426
|
+
return dbCommand({
|
|
427
|
+
subcommand: dbSub,
|
|
428
|
+
positional: positional.slice(1),
|
|
429
|
+
limit,
|
|
430
|
+
verbose: flags["verbose"] === true,
|
|
431
|
+
dbPath: typeof flags["db"] === "string" ? flags["db"] : undefined,
|
|
432
|
+
json: jsonFlag,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
case "request": {
|
|
437
|
+
const method = positional[0];
|
|
438
|
+
const url = positional[1];
|
|
439
|
+
if (!method || !url) {
|
|
440
|
+
printError("Missing arguments. Usage: zond request <METHOD> <URL> [--header H] [--body JSON]");
|
|
441
|
+
return 2;
|
|
442
|
+
}
|
|
443
|
+
// Collect all --header flags
|
|
444
|
+
const headerValues: string[] = [];
|
|
445
|
+
const rawArgs = process.argv.slice(2);
|
|
446
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
447
|
+
const arg = rawArgs[i]!;
|
|
448
|
+
if (arg === "--header" && rawArgs[i + 1]) {
|
|
449
|
+
headerValues.push(rawArgs[i + 1]!);
|
|
450
|
+
i++;
|
|
451
|
+
} else if (arg.startsWith("--header=")) {
|
|
452
|
+
headerValues.push(arg.slice("--header=".length));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const timeoutRaw = flags["timeout"];
|
|
457
|
+
let timeout: number | undefined;
|
|
458
|
+
if (typeof timeoutRaw === "string") {
|
|
459
|
+
timeout = parseInt(timeoutRaw, 10);
|
|
460
|
+
if (isNaN(timeout) || timeout <= 0) timeout = undefined;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return requestCommand({
|
|
464
|
+
method,
|
|
465
|
+
url,
|
|
466
|
+
headers: headerValues.length > 0 ? headerValues : undefined,
|
|
467
|
+
body: typeof flags["body"] === "string" ? flags["body"] : undefined,
|
|
468
|
+
timeout,
|
|
469
|
+
env: typeof flags["env"] === "string" ? flags["env"] : undefined,
|
|
470
|
+
api: typeof flags["api"] === "string" ? flags["api"] : undefined,
|
|
471
|
+
jsonPath: typeof flags["json-path"] === "string" ? flags["json-path"] : undefined,
|
|
472
|
+
dbPath: typeof flags["db"] === "string" ? flags["db"] : undefined,
|
|
473
|
+
json: jsonFlag,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
case "generate": {
|
|
478
|
+
const specPath = positional[0];
|
|
479
|
+
if (!specPath) {
|
|
480
|
+
printError("Missing spec path. Usage: zond generate <spec> --output <dir> [--tag <tag>] [--uncovered-only] [--json]");
|
|
481
|
+
return 2;
|
|
482
|
+
}
|
|
483
|
+
const output = typeof flags["output"] === "string" ? flags["output"] : undefined;
|
|
484
|
+
if (!output) {
|
|
485
|
+
printError("Missing --output <dir>. Usage: zond generate <spec> --output <dir>");
|
|
486
|
+
return 2;
|
|
487
|
+
}
|
|
488
|
+
return generateCommand({
|
|
489
|
+
specPath,
|
|
490
|
+
output,
|
|
491
|
+
tag: typeof flags["tag"] === "string" ? flags["tag"] : undefined,
|
|
492
|
+
uncoveredOnly: flags["uncovered-only"] === true,
|
|
493
|
+
json: jsonFlag,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
case "guide": {
|
|
498
|
+
const specPath = positional[0];
|
|
499
|
+
if (!specPath) {
|
|
500
|
+
printError("Missing spec path. Usage: zond guide <spec> [--tests-dir <dir>] [--tag <tag>]");
|
|
501
|
+
return 2;
|
|
502
|
+
}
|
|
503
|
+
return guideCommand({
|
|
504
|
+
specPath,
|
|
505
|
+
testsDir: typeof flags["tests-dir"] === "string" ? flags["tests-dir"] : undefined,
|
|
506
|
+
tag: typeof flags["tag"] === "string" ? flags["tag"] : undefined,
|
|
507
|
+
json: jsonFlag,
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
case "export": {
|
|
512
|
+
const subcommand = positional[0];
|
|
513
|
+
if (subcommand !== "postman") {
|
|
514
|
+
printError(`Unknown export subcommand: ${subcommand ?? "(none)"}. Usage: zond export postman <path>`);
|
|
515
|
+
return 2;
|
|
516
|
+
}
|
|
517
|
+
const testsPath = positional[1];
|
|
518
|
+
if (!testsPath) {
|
|
519
|
+
printError("Missing tests path. Usage: zond export postman <path> [--output <file>]");
|
|
520
|
+
return 2;
|
|
521
|
+
}
|
|
522
|
+
return exportCommand({
|
|
523
|
+
testsPath,
|
|
524
|
+
output: typeof flags["output"] === "string" ? flags["output"] : "collection.postman.json",
|
|
525
|
+
env: typeof flags["env"] === "string" ? flags["env"] : undefined,
|
|
526
|
+
collectionName: typeof flags["collection-name"] === "string" ? flags["collection-name"] : undefined,
|
|
527
|
+
json: jsonFlag,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
case "sync": {
|
|
532
|
+
const specPath = positional[0];
|
|
533
|
+
if (!specPath) {
|
|
534
|
+
printError("Missing spec path. Usage: zond sync <spec> --tests <dir> [--dry-run] [--tag <tag>]");
|
|
535
|
+
return 2;
|
|
536
|
+
}
|
|
537
|
+
const testsDir = typeof flags["tests"] === "string" ? flags["tests"] : undefined;
|
|
538
|
+
if (!testsDir) {
|
|
539
|
+
printError("Missing --tests <dir>. Usage: zond sync <spec> --tests <dir>");
|
|
540
|
+
return 2;
|
|
541
|
+
}
|
|
542
|
+
return syncCommand({
|
|
543
|
+
specPath,
|
|
544
|
+
testsDir,
|
|
545
|
+
dryRun: flags["dry-run"] === true,
|
|
546
|
+
tag: typeof flags["tag"] === "string" ? flags["tag"] : undefined,
|
|
547
|
+
json: jsonFlag,
|
|
548
|
+
});
|
|
308
549
|
}
|
|
309
550
|
|
|
310
551
|
default: {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface JsonEnvelope<T = unknown> {
|
|
2
|
+
ok: boolean;
|
|
3
|
+
command: string;
|
|
4
|
+
data: T;
|
|
5
|
+
warnings: string[];
|
|
6
|
+
errors: string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function jsonOk<T>(command: string, data: T, warnings?: string[]): JsonEnvelope<T> {
|
|
10
|
+
return { ok: true, command, data, warnings: warnings ?? [], errors: [] };
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function jsonError(command: string, errors: string[], warnings?: string[]): JsonEnvelope<null> {
|
|
14
|
+
return { ok: false, command, data: null, warnings: warnings ?? [], errors };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function printJson(envelope: JsonEnvelope): void {
|
|
18
|
+
process.stdout.write(JSON.stringify(envelope, null, 2) + "\n");
|
|
19
|
+
}
|