@kirrosh/zond 0.22.0 → 0.23.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 +648 -0
- package/README.md +58 -6
- package/package.json +9 -6
- package/src/cli/argv.ts +122 -0
- package/src/cli/commands/add-api.ts +134 -0
- package/src/cli/commands/api/annotate/idempotency.ts +59 -0
- package/src/cli/commands/api/annotate/index.ts +525 -0
- package/src/cli/commands/api/annotate/lifecycle.ts +74 -0
- package/src/cli/commands/api/annotate/overlay.ts +206 -0
- package/src/cli/commands/api/annotate/pagination.ts +60 -0
- package/src/cli/commands/api/annotate/prompts.ts +183 -0
- package/src/cli/commands/api/annotate/readback.ts +58 -0
- package/src/cli/commands/api/annotate/resources.ts +91 -0
- package/src/cli/commands/api/annotate/seed-bodies.ts +61 -0
- package/src/cli/commands/audit.ts +480 -0
- package/src/cli/commands/bootstrap.ts +710 -0
- package/src/cli/commands/catalog.ts +35 -0
- package/src/cli/commands/check.ts +348 -0
- package/src/cli/commands/checks.ts +756 -0
- package/src/cli/commands/ci-init.ts +43 -0
- package/src/cli/commands/clean.ts +212 -0
- package/src/cli/commands/cleanup.ts +262 -0
- package/src/cli/commands/completions.ts +16 -0
- package/src/cli/commands/coverage.ts +605 -132
- package/src/cli/commands/db.ts +178 -7
- package/src/cli/commands/describe.ts +37 -2
- package/src/cli/commands/discover.ts +1236 -0
- package/src/cli/commands/doctor.ts +607 -0
- package/src/cli/commands/fixtures.ts +402 -0
- package/src/cli/commands/generate.ts +420 -46
- package/src/cli/commands/init/bootstrap.ts +30 -1
- package/src/cli/commands/{init.ts → init/index.ts} +99 -5
- package/src/cli/commands/init/skills.ts +56 -3
- package/src/cli/commands/init/templates/agents.md +65 -61
- package/src/cli/commands/init/templates/skills/zond-checks.md +397 -0
- package/src/cli/commands/init/templates/skills/zond-triage.md +210 -0
- package/src/cli/commands/init/templates/skills/zond.md +592 -125
- package/src/cli/commands/init/templates/zond-config.yml +8 -9
- package/src/cli/commands/prepare-fixtures.ts +135 -0
- package/src/cli/commands/probe/mass-assignment.ts +503 -0
- package/src/cli/commands/probe/security.ts +454 -0
- package/src/cli/commands/probe/static.ts +255 -0
- package/src/cli/commands/probe/webhooks.ts +161 -0
- package/src/cli/commands/probe.ts +459 -0
- package/src/cli/commands/reference.ts +87 -0
- package/src/cli/commands/refresh-api.ts +169 -0
- package/src/cli/commands/remove-api.ts +150 -0
- package/src/cli/commands/report-bundle.ts +318 -0
- package/src/cli/commands/report.ts +241 -0
- package/src/cli/commands/request.ts +379 -4
- package/src/cli/commands/run.ts +842 -53
- package/src/cli/commands/session.ts +244 -0
- package/src/cli/commands/use.ts +18 -1
- package/src/cli/index.ts +20 -3
- package/src/cli/json-envelope.ts +112 -3
- package/src/cli/json-schemas.ts +263 -0
- package/src/cli/program.ts +198 -635
- package/src/cli/resolve.ts +105 -0
- package/src/cli/status-filter.ts +124 -0
- package/src/cli/util/api-context.ts +85 -0
- package/src/cli/version.ts +5 -0
- package/src/core/anti-fp/bootstrap.ts +34 -0
- package/src/core/anti-fp/index.ts +33 -0
- package/src/core/anti-fp/registry.ts +44 -0
- package/src/core/anti-fp/rules/baseline-echo.ts +74 -0
- package/src/core/anti-fp/rules/schemathesis/body_negation_becomes_valid.ts +52 -0
- package/src/core/anti-fp/rules/schemathesis/coverage_phase_boundary_positive.ts +38 -0
- package/src/core/anti-fp/rules/schemathesis/has_unverifiable_mutations.ts +35 -0
- package/src/core/anti-fp/rules/schemathesis/index.ts +24 -0
- package/src/core/anti-fp/rules/schemathesis/string_type_mutation_becomes_valid.ts +53 -0
- package/src/core/anti-fp/rules/subscription-gated/index.ts +11 -0
- package/src/core/anti-fp/rules/subscription-gated/paid-plan-403.ts +75 -0
- package/src/core/anti-fp/types.ts +68 -0
- package/src/core/checks/checks/_crud-helpers.ts +133 -0
- package/src/core/checks/checks/_negative_mutator.ts +133 -0
- package/src/core/checks/checks/_readback-helpers.ts +133 -0
- package/src/core/checks/checks/content_type_conformance.ts +39 -0
- package/src/core/checks/checks/cross_call_references.ts +134 -0
- package/src/core/checks/checks/ensure_resource_availability.ts +62 -0
- package/src/core/checks/checks/idempotency_replay.ts +246 -0
- package/src/core/checks/checks/ignored_auth.ts +211 -0
- package/src/core/checks/checks/index.ts +65 -0
- package/src/core/checks/checks/lifecycle_transitions.ts +273 -0
- package/src/core/checks/checks/missing_required_header.ts +40 -0
- package/src/core/checks/checks/negative_data_rejection.ts +45 -0
- package/src/core/checks/checks/not_a_server_error.ts +27 -0
- package/src/core/checks/checks/open_cors_on_sensitive.ts +131 -0
- package/src/core/checks/checks/pagination_invariants.ts +238 -0
- package/src/core/checks/checks/positive_data_acceptance.ts +36 -0
- package/src/core/checks/checks/rate_limit_headers_absent.ts +77 -0
- package/src/core/checks/checks/response_headers_conformance.ts +74 -0
- package/src/core/checks/checks/response_schema_conformance.ts +30 -0
- package/src/core/checks/checks/status_code_conformance.ts +61 -0
- package/src/core/checks/checks/unsupported_method.ts +63 -0
- package/src/core/checks/checks/use_after_free.ts +78 -0
- package/src/core/checks/index.ts +30 -0
- package/src/core/checks/mode.ts +79 -0
- package/src/core/checks/recommended-action.ts +64 -0
- package/src/core/checks/registry.ts +78 -0
- package/src/core/checks/runner.ts +874 -0
- package/src/core/checks/sarif.ts +230 -0
- package/src/core/checks/stateful.ts +121 -0
- package/src/core/checks/types.ts +189 -0
- package/src/core/classifier/recommended-action.ts +222 -0
- package/src/core/context/current.ts +22 -6
- package/src/core/context/session.ts +78 -0
- package/src/core/coverage/loader.ts +185 -0
- package/src/core/coverage/reasons.ts +300 -0
- package/src/core/diagnostics/db-analysis.ts +151 -11
- package/src/core/diagnostics/failure-class.ts +120 -0
- package/src/core/diagnostics/failure-hints.ts +212 -9
- package/src/core/diagnostics/spec-pointer.ts +99 -0
- package/src/core/diagnostics/suggested-fixes.ts +156 -0
- package/src/core/exporter/case-study/index.ts +270 -0
- package/src/core/exporter/curl.ts +40 -0
- package/src/core/exporter/exporter.ts +48 -0
- package/src/core/exporter/html-report/escape.ts +24 -0
- package/src/core/exporter/html-report/index.ts +479 -0
- package/src/core/exporter/html-report/script.ts +100 -0
- package/src/core/exporter/html-report/styles.ts +408 -0
- package/src/core/generator/chunker.ts +42 -16
- package/src/core/generator/coverage-phase.ts +0 -0
- package/src/core/generator/create-body.ts +89 -0
- package/src/core/generator/data-factory.ts +445 -19
- package/src/core/generator/describe.ts +1 -1
- package/src/core/generator/fixtures-builder.ts +325 -0
- package/src/core/generator/index.ts +7 -5
- package/src/core/generator/openapi-reader.ts +37 -3
- package/src/core/generator/path-param-disambig.ts +114 -0
- package/src/core/generator/resources-builder.ts +648 -0
- package/src/core/generator/schema-utils.ts +11 -3
- package/src/core/generator/serializer.ts +103 -13
- package/src/core/generator/suite-generator.ts +419 -111
- package/src/core/generator/types.ts +8 -0
- package/src/core/identity/identity-file.ts +129 -0
- package/src/core/lint/affects.ts +28 -0
- package/src/core/lint/config.ts +96 -0
- package/src/core/lint/format.ts +42 -0
- package/src/core/lint/index.ts +94 -0
- package/src/core/lint/reporter.ts +128 -0
- package/src/core/lint/rules/consistency.ts +158 -0
- package/src/core/lint/rules/heuristics.ts +97 -0
- package/src/core/lint/rules/strictness.ts +109 -0
- package/src/core/lint/types.ts +96 -0
- package/src/core/lint/walker.ts +248 -0
- package/src/core/meta/meta-store.ts +6 -73
- package/src/core/output/README.md +91 -0
- package/src/core/output/index.ts +13 -0
- package/src/core/output/run.ts +126 -0
- package/src/core/output/types.ts +129 -0
- package/src/core/parser/env-interpolation.ts +104 -0
- package/src/core/parser/filter.ts +57 -0
- package/src/core/parser/schema.ts +129 -4
- package/src/core/parser/types.ts +19 -1
- package/src/core/parser/variables.ts +0 -0
- package/src/core/parser/yaml-parser.ts +58 -12
- package/src/core/probe/bootstrap.ts +34 -0
- package/src/core/probe/dry-run-envelope.ts +57 -0
- package/src/core/probe/mass-assignment-probe-class.ts +198 -0
- package/src/core/probe/mass-assignment-probe.ts +1122 -0
- package/src/core/probe/mass-assignment-template.ts +212 -0
- package/src/core/probe/method-probe.ts +43 -76
- package/src/core/probe/method-shared.ts +69 -0
- package/src/core/probe/negative-probe.ts +183 -149
- package/src/core/probe/orphan-tracker.ts +188 -0
- package/src/core/probe/path-discovery.ts +440 -0
- package/src/core/probe/probe-harness.ts +120 -0
- package/src/core/probe/registry.ts +89 -0
- package/src/core/probe/runner.ts +136 -0
- package/src/core/probe/security-probe-class.ts +201 -0
- package/src/core/probe/security-probe.ts +1453 -0
- package/src/core/probe/shared.ts +505 -0
- package/src/core/probe/static-probe-class.ts +125 -0
- package/src/core/probe/types.ts +165 -0
- package/src/core/probe/verdict-aggregator.ts +33 -0
- package/src/core/probe/webhooks-probe.ts +284 -0
- package/src/core/reporter/console.ts +41 -2
- package/src/core/reporter/index.ts +2 -3
- package/src/core/reporter/json.ts +11 -1
- package/src/core/reporter/junit.ts +27 -12
- package/src/core/reporter/ndjson.ts +37 -0
- package/src/core/reporter/types.ts +3 -0
- package/src/core/runner/assertions.ts +58 -1
- package/src/core/runner/async-pool.ts +108 -0
- package/src/core/runner/auth-path.ts +8 -0
- package/src/core/runner/ci-context.ts +72 -0
- package/src/core/runner/executor.ts +264 -20
- package/src/core/runner/form-encode.ts +51 -0
- package/src/core/runner/http-client.ts +75 -2
- package/src/core/runner/learn-drift.ts +293 -0
- package/src/core/runner/preflight-vars.ts +149 -0
- package/src/core/runner/progress-tracker.ts +73 -0
- package/src/core/runner/rate-limiter.ts +89 -17
- package/src/core/runner/run-kind.ts +39 -0
- package/src/core/runner/schema-validator.ts +312 -0
- package/src/core/runner/send-request.ts +153 -20
- package/src/core/runner/types.ts +38 -0
- package/src/core/secrets/registry.ts +164 -0
- package/src/core/secrets/secrets-file.ts +115 -0
- package/src/core/selectors/operation-filter.ts +144 -0
- package/src/core/setup-api.ts +415 -16
- package/src/core/severity/category.ts +94 -0
- package/src/core/severity/index.ts +121 -0
- package/src/core/spec/layers.ts +154 -0
- package/src/core/util/format-eta.ts +21 -0
- package/src/core/utils.ts +5 -1
- package/src/core/workspace/config.ts +129 -0
- package/src/core/workspace/manifest.ts +283 -0
- package/src/core/workspace/output-rotation.ts +62 -0
- package/src/core/workspace/triage-path.ts +87 -0
- package/src/db/lint-runs.ts +47 -0
- package/src/db/migrate.ts +126 -0
- package/src/db/migrations/0001_run_kind.sql +25 -0
- package/src/db/migrations/sql.d.ts +4 -0
- package/src/db/queries/collections.ts +133 -0
- package/src/db/queries/coverage.ts +9 -0
- package/src/db/queries/dashboard.ts +59 -0
- package/src/db/queries/results.ts +128 -0
- package/src/db/queries/runs.ts +235 -0
- package/src/db/queries/sessions.ts +42 -0
- package/src/db/queries/settings.ts +28 -0
- package/src/db/queries/types.ts +172 -0
- package/src/db/queries.ts +72 -802
- package/src/db/schema.ts +178 -50
- package/src/cli/commands/export.ts +0 -144
- package/src/cli/commands/guide.ts +0 -127
- package/src/cli/commands/init/templates/skills/scenarios.md +0 -97
- package/src/cli/commands/probe-methods.ts +0 -108
- package/src/cli/commands/probe-validation.ts +0 -124
- package/src/cli/commands/serve.ts +0 -114
- package/src/cli/commands/sync.ts +0 -268
- package/src/cli/commands/update.ts +0 -189
- package/src/cli/commands/validate.ts +0 -34
- package/src/core/diagnostics/render-md.ts +0 -112
- package/src/core/exporter/postman.ts +0 -963
- package/src/core/generator/guide-builder.ts +0 -253
- package/src/core/meta/types.ts +0 -19
- package/src/core/parser/index.ts +0 -21
- package/src/core/runner/execute-run.ts +0 -132
- package/src/core/runner/index.ts +0 -12
- package/src/core/sync/spec-differ.ts +0 -38
- package/src/web/data/collection-state.ts +0 -362
- package/src/web/routes/api.ts +0 -314
- package/src/web/routes/dashboard.ts +0 -350
- package/src/web/routes/runs.ts +0 -64
- package/src/web/schemas.ts +0 -121
- package/src/web/server.ts +0 -134
- package/src/web/static/htmx.min.cjs +0 -1
- package/src/web/static/style.css +0 -1148
- package/src/web/views/endpoints-tab.ts +0 -174
- package/src/web/views/explorer-tab.ts +0 -402
- package/src/web/views/health-strip.ts +0 -92
- package/src/web/views/layout.ts +0 -48
- package/src/web/views/results.ts +0 -210
- package/src/web/views/runs-tab.ts +0 -126
- package/src/web/views/suites-tab.ts +0 -181
package/src/cli/program.ts
CHANGED
|
@@ -1,157 +1,41 @@
|
|
|
1
|
-
import { Command
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
|
|
3
|
+
import { registerRun } from "./commands/run.ts";
|
|
4
|
+
import { registerCheck, registerLint } from "./commands/check.ts";
|
|
5
|
+
import { registerChecks } from "./commands/checks.ts";
|
|
6
|
+
import { registerCoverage } from "./commands/coverage.ts";
|
|
7
|
+
import { registerCi } from "./commands/ci-init.ts";
|
|
8
|
+
import { registerClean } from "./commands/clean.ts";
|
|
9
|
+
import { registerCleanup } from "./commands/cleanup.ts";
|
|
10
|
+
import { registerInit } from "./commands/init/index.ts";
|
|
11
|
+
import { registerDescribe } from "./commands/describe.ts";
|
|
12
|
+
import { registerDb } from "./commands/db.ts";
|
|
13
|
+
import { registerRequest } from "./commands/request.ts";
|
|
14
|
+
import { registerGenerate } from "./commands/generate.ts";
|
|
15
|
+
import { registerPrepareFixtures } from "./commands/prepare-fixtures.ts";
|
|
16
|
+
import { registerFixtures } from "./commands/fixtures.ts";
|
|
17
|
+
import { registerProbes } from "./commands/probe.ts";
|
|
18
|
+
import { bootstrapProbes } from "../core/probe/bootstrap.ts";
|
|
19
|
+
import { bootstrapAntiFp } from "../core/anti-fp/bootstrap.ts";
|
|
20
|
+
import { registerReport } from "./commands/report.ts";
|
|
21
|
+
import { registerCatalog } from "./commands/catalog.ts";
|
|
22
|
+
import { registerCompletions } from "./commands/completions.ts";
|
|
23
|
+
import { registerUse } from "./commands/use.ts";
|
|
24
|
+
import { registerSession } from "./commands/session.ts";
|
|
25
|
+
import { registerDoctor } from "./commands/doctor.ts";
|
|
26
|
+
import { registerRefreshApi } from "./commands/refresh-api.ts";
|
|
27
|
+
import { registerAdd } from "./commands/add-api.ts";
|
|
28
|
+
import { registerRemove } from "./commands/remove-api.ts";
|
|
29
|
+
import { registerAudit } from "./commands/audit.ts";
|
|
30
|
+
import { registerReference } from "./commands/reference.ts";
|
|
31
|
+
import { registerApiAnnotate } from "./commands/api/annotate/index.ts";
|
|
32
|
+
|
|
33
|
+
import { getSecretRegistry } from "../core/secrets/registry.ts";
|
|
25
34
|
import { getRuntimeInfo } from "./runtime.ts";
|
|
26
35
|
import { VERSION } from "./version.ts";
|
|
27
|
-
import {
|
|
28
|
-
import { findCollectionByNameOrId } from "../db/queries.ts";
|
|
29
|
-
import type { ReporterName } from "../core/reporter/types.ts";
|
|
30
|
-
|
|
31
|
-
// ── MSYS path preprocessing ──
|
|
32
|
-
//
|
|
33
|
-
// Git Bash on Windows converts API paths like "/users" → "C:/Program Files/Git/users".
|
|
34
|
-
// We reverse that for flags whose values are API paths, not filesystem paths.
|
|
35
|
-
|
|
36
|
-
const MSYS_PREFIX_RE = /^[A-Z]:[\\/](?:Program Files[\\/]Git|msys64|usr)[\\/]/i;
|
|
37
|
-
|
|
38
|
-
const API_PATH_FLAGS = new Set(["--path", "--json-path"]);
|
|
39
|
-
|
|
40
|
-
function stripMsysPath(value: string): string {
|
|
41
|
-
if (!MSYS_PREFIX_RE.test(value)) return value;
|
|
42
|
-
return value.replace(MSYS_PREFIX_RE, "/");
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Pre-process argv before commander sees it: undo Git Bash's MSYS path conversion
|
|
47
|
-
* for `--path` and `--json-path` values (both `--path X` and `--path=X` forms).
|
|
48
|
-
*/
|
|
49
|
-
export function preprocessArgv(argv: string[]): string[] {
|
|
50
|
-
const out = [...argv];
|
|
51
|
-
for (let i = 0; i < out.length; i++) {
|
|
52
|
-
const arg = out[i]!;
|
|
53
|
-
|
|
54
|
-
// --flag=value form
|
|
55
|
-
const eqIdx = arg.indexOf("=");
|
|
56
|
-
if (arg.startsWith("--") && eqIdx !== -1) {
|
|
57
|
-
const flag = arg.slice(0, eqIdx);
|
|
58
|
-
if (API_PATH_FLAGS.has(flag)) {
|
|
59
|
-
out[i] = `${flag}=${stripMsysPath(arg.slice(eqIdx + 1))}`;
|
|
60
|
-
}
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// --flag value form
|
|
65
|
-
if (API_PATH_FLAGS.has(arg)) {
|
|
66
|
-
const next = out[i + 1];
|
|
67
|
-
if (next !== undefined && !next.startsWith("-")) {
|
|
68
|
-
out[i + 1] = stripMsysPath(next);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return out;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// ── Argument parsers ──
|
|
76
|
-
|
|
77
|
-
function parsePositiveInt(name: string): (raw: string) => number {
|
|
78
|
-
return (raw: string) => {
|
|
79
|
-
const n = Number.parseInt(raw, 10);
|
|
80
|
-
if (Number.isNaN(n) || n <= 0) {
|
|
81
|
-
throw new InvalidArgumentError(`Invalid ${name} value: ${raw}`);
|
|
82
|
-
}
|
|
83
|
-
return n;
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** `--rate-limit` accepts a positive integer (req/sec cap) or the literal
|
|
88
|
-
* string `auto` (no static cap; throttle adaptively from ratelimit-* headers). */
|
|
89
|
-
function parseRateLimit(raw: string): number | "auto" {
|
|
90
|
-
if (raw.toLowerCase() === "auto") return "auto";
|
|
91
|
-
const n = Number.parseInt(raw, 10);
|
|
92
|
-
if (Number.isNaN(n) || n <= 0) {
|
|
93
|
-
throw new InvalidArgumentError(`Invalid --rate-limit value: ${raw} (expected a positive integer or "auto")`);
|
|
94
|
-
}
|
|
95
|
-
return n;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function parseInteger(name: string): (raw: string) => number {
|
|
99
|
-
return (raw: string) => {
|
|
100
|
-
const n = Number.parseInt(raw, 10);
|
|
101
|
-
if (Number.isNaN(n)) {
|
|
102
|
-
throw new InvalidArgumentError(`Invalid ${name} value: ${raw}`);
|
|
103
|
-
}
|
|
104
|
-
return n;
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function parsePercentage(raw: string): number {
|
|
109
|
-
const n = Number.parseInt(raw, 10);
|
|
110
|
-
if (Number.isNaN(n) || n < 0 || n > 100) {
|
|
111
|
-
throw new InvalidArgumentError(`Invalid --fail-on-coverage value: ${raw} (must be 0–100)`);
|
|
112
|
-
}
|
|
113
|
-
return n;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const collect = (val: string, prev: string[]): string[] => [...prev, val];
|
|
117
|
-
|
|
118
|
-
const VALID_REPORTERS = new Set<string>(["console", "json", "junit"]);
|
|
119
|
-
|
|
120
|
-
function parseReporter(raw: string): ReporterName {
|
|
121
|
-
if (!VALID_REPORTERS.has(raw)) {
|
|
122
|
-
throw new InvalidArgumentError(`Unknown reporter: ${raw}. Available: console, json, junit`);
|
|
123
|
-
}
|
|
124
|
-
return raw as ReporterName;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Helper: split repeatable values like ["a,b", "c"] → ["a", "b", "c"]
|
|
128
|
-
function flatSplit(values: string[] | undefined): string[] | undefined {
|
|
129
|
-
if (!values || values.length === 0) return undefined;
|
|
130
|
-
const out = values.flatMap((v) => v.split(",")).filter(Boolean);
|
|
131
|
-
return out.length > 0 ? out : undefined;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Helper: read a global option from any command in the tree
|
|
135
|
-
function globalJson(cmd: Command): boolean {
|
|
136
|
-
return cmd.optsWithGlobals().json === true;
|
|
137
|
-
}
|
|
36
|
+
import { preprocessArgv } from "./argv.ts";
|
|
138
37
|
|
|
139
|
-
|
|
140
|
-
function resolveApiCollection(apiName: string, dbPath: string | undefined):
|
|
141
|
-
| { spec: string | null; testPath: string | null }
|
|
142
|
-
| { error: string } {
|
|
143
|
-
if (typeof apiName !== "string" || apiName.length === 0) {
|
|
144
|
-
return { error: "Internal: --api received non-string value" };
|
|
145
|
-
}
|
|
146
|
-
try {
|
|
147
|
-
getDb(dbPath);
|
|
148
|
-
const col = findCollectionByNameOrId(apiName);
|
|
149
|
-
if (!col) return { error: `API '${apiName}' not found` };
|
|
150
|
-
return { spec: col.openapi_spec ?? null, testPath: col.test_path ?? null };
|
|
151
|
-
} catch (err) {
|
|
152
|
-
return { error: `Failed to resolve --api: ${(err as Error).message}` };
|
|
153
|
-
}
|
|
154
|
-
}
|
|
38
|
+
export { preprocessArgv };
|
|
155
39
|
|
|
156
40
|
// ── Program builder ──
|
|
157
41
|
|
|
@@ -160,496 +44,175 @@ export function buildProgram(): Command {
|
|
|
160
44
|
.description("API Testing Platform")
|
|
161
45
|
.version(`${VERSION} (${getRuntimeInfo()})`, "-v, --version", "Show version")
|
|
162
46
|
.helpOption("-h, --help", "Show this help")
|
|
163
|
-
.option("--json", "Output in JSON envelope format")
|
|
164
47
|
.showHelpAfterError("(run 'zond --help' for usage)")
|
|
165
|
-
.exitOverride()
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
.
|
|
170
|
-
.
|
|
171
|
-
.option(
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
new Option("--report <format>", "Output format")
|
|
175
|
-
.choices(["console", "json", "junit"])
|
|
176
|
-
.default("console")
|
|
177
|
-
.argParser(parseReporter),
|
|
48
|
+
.exitOverride()
|
|
49
|
+
// TASK-166 (m-10): global escape hatch for local debugging — disables
|
|
50
|
+
// the secret registry's redaction pass everywhere (DB writes,
|
|
51
|
+
// exporters, stdout). Default is redact-on. Hook is read from the
|
|
52
|
+
// env var so it survives across nested subcommand parsers.
|
|
53
|
+
.option("--no-redact", "Disable auto-redaction of registered secret values (debug only)")
|
|
54
|
+
.option(
|
|
55
|
+
"--api <name>",
|
|
56
|
+
"TASK-290: Select the active API for this invocation. Resolution order: per-command --api > global --api (this flag) > ZOND_API env > .zond/current-api file (set via `zond use <name>`).",
|
|
178
57
|
)
|
|
179
|
-
.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
let path = pathArg;
|
|
195
|
-
const apiFlag = (opts.api as string | undefined) ?? (path ? undefined : readCurrentApi() ?? undefined);
|
|
196
|
-
const dbPath = typeof opts.db === "string" ? opts.db : undefined;
|
|
197
|
-
|
|
198
|
-
if (!path && apiFlag) {
|
|
199
|
-
const resolved = resolveApiCollection(apiFlag, dbPath);
|
|
200
|
-
if ("error" in resolved) {
|
|
201
|
-
printError(resolved.error);
|
|
202
|
-
process.exitCode = resolved.error.startsWith("Failed") ? 2 : 1;
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (!resolved.testPath) {
|
|
206
|
-
printError(`API '${apiFlag}' has no test_path`);
|
|
207
|
-
process.exitCode = 1;
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
path = resolved.testPath;
|
|
211
|
-
}
|
|
212
|
-
if (!path) {
|
|
213
|
-
printError("No path given and .zond-current not set; run `zond use <api>` or pass path explicitly (or use --api <name>)");
|
|
214
|
-
process.exitCode = 2;
|
|
215
|
-
return;
|
|
58
|
+
.hook("preAction", (thisCommand) => {
|
|
59
|
+
const enabled = thisCommand.opts().redact !== false;
|
|
60
|
+
// Mirror the flag into env so deeply-nested code that doesn't have
|
|
61
|
+
// access to `cmd` (e.g. setup-api, exporters) can still consult it.
|
|
62
|
+
process.env.ZOND_REDACT = enabled ? "1" : "0";
|
|
63
|
+
getSecretRegistry().setEnabled(enabled);
|
|
64
|
+
// TASK-290: mirror the global --api flag into env so the resolution
|
|
65
|
+
// chain in core/context/current.ts (which has no `cmd` ref) sees it.
|
|
66
|
+
// Per-command --api still wins because it is passed positionally to
|
|
67
|
+
// resolveSpecArg / resolveApiCollection.
|
|
68
|
+
const apiGlobal = thisCommand.opts().api;
|
|
69
|
+
if (typeof apiGlobal === "string" && apiGlobal.length > 0) {
|
|
70
|
+
process.env.ZOND_API_GLOBAL = apiGlobal;
|
|
71
|
+
} else {
|
|
72
|
+
delete process.env.ZOND_API_GLOBAL;
|
|
216
73
|
}
|
|
217
|
-
|
|
218
|
-
const tags = flatSplit(opts.tag);
|
|
219
|
-
const excludeTags = flatSplit(opts.excludeTag);
|
|
220
|
-
const envVars = (opts.envVar as string[] | undefined)?.length ? (opts.envVar as string[]) : undefined;
|
|
221
|
-
|
|
222
|
-
process.exitCode = await runCommand({
|
|
223
|
-
path,
|
|
224
|
-
env: opts.env,
|
|
225
|
-
report: opts.report as ReporterName,
|
|
226
|
-
timeout: opts.timeout,
|
|
227
|
-
rateLimit: opts.rateLimit,
|
|
228
|
-
bail: opts.bail === true,
|
|
229
|
-
sequential: opts.sequential === true,
|
|
230
|
-
// Commander's `--no-db` produces { db: false }; keep semantics: when --no-db given → noDb=true
|
|
231
|
-
noDb: opts.db === false,
|
|
232
|
-
dbPath: typeof opts.db === "string" ? opts.db : undefined,
|
|
233
|
-
authToken: opts.authToken,
|
|
234
|
-
safe: opts.safe === true,
|
|
235
|
-
tag: tags,
|
|
236
|
-
excludeTag: excludeTags,
|
|
237
|
-
method: opts.method,
|
|
238
|
-
envVars,
|
|
239
|
-
dryRun: opts.dryRun === true,
|
|
240
|
-
reportOut: typeof opts.reportOut === "string" ? opts.reportOut : undefined,
|
|
241
|
-
json: globalJson(cmd),
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
// ── validate ──
|
|
246
|
-
program
|
|
247
|
-
.command("validate <path>")
|
|
248
|
-
.description("Validate test files without running")
|
|
249
|
-
.action(async (path: string, _opts, cmd: Command) => {
|
|
250
|
-
process.exitCode = await validateCommand({ path, json: globalJson(cmd) });
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
// ── serve ──
|
|
254
|
-
program
|
|
255
|
-
.command("serve")
|
|
256
|
-
.description("Start web dashboard")
|
|
257
|
-
.option("--port <port>", "Server port (default: 8080)", parsePositiveInt("--port"))
|
|
258
|
-
.option("--host <host>", "Server host (default: 0.0.0.0)")
|
|
259
|
-
.option("--db <path>", "Path to SQLite database file (default: zond.db)")
|
|
260
|
-
.option("--open", "Open dashboard in browser after starting")
|
|
261
|
-
.option("--watch", "Enable dev mode with hot reload (auto-refresh on file changes)")
|
|
262
|
-
.option("--kill-existing", "Kill any process holding the requested port (DANGEROUS — can terminate your dev server)")
|
|
263
|
-
.action(async (opts) => {
|
|
264
|
-
process.exitCode = await serveCommand({
|
|
265
|
-
port: opts.port,
|
|
266
|
-
host: opts.host,
|
|
267
|
-
dbPath: opts.db,
|
|
268
|
-
watch: opts.watch === true,
|
|
269
|
-
open: opts.open === true,
|
|
270
|
-
killExisting: opts.killExisting === true,
|
|
271
|
-
});
|
|
272
74
|
});
|
|
273
75
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
.
|
|
361
|
-
|
|
362
|
-
.option("--db <path>", "Path to SQLite database file")
|
|
363
|
-
.option("--workspace", "Bootstrap a zond workspace (zond.config.yml, apis/, AGENTS.md)")
|
|
364
|
-
.option("--with-spec <path>", "Bootstrap workspace AND register first API from spec")
|
|
365
|
-
.option("--no-agents-md", "Skip writing AGENTS.md when bootstrapping")
|
|
366
|
-
.option("--no-skills", "Skip writing Claude Code skills under .claude/skills/")
|
|
367
|
-
.action(async (specPos: string | undefined, opts, cmd: Command) => {
|
|
368
|
-
process.exitCode = await initCommand({
|
|
369
|
-
name: opts.name,
|
|
370
|
-
spec: opts.spec ?? specPos,
|
|
371
|
-
baseUrl: opts.baseUrl,
|
|
372
|
-
dir: opts.dir,
|
|
373
|
-
force: opts.force === true,
|
|
374
|
-
insecure: opts.insecure === true,
|
|
375
|
-
dbPath: opts.db,
|
|
376
|
-
workspace: opts.workspace === true,
|
|
377
|
-
withSpec: opts.withSpec,
|
|
378
|
-
noAgents: opts.agentsMd === false,
|
|
379
|
-
noSkills: opts.skills === false,
|
|
380
|
-
json: globalJson(cmd),
|
|
381
|
-
});
|
|
382
|
-
});
|
|
383
|
-
|
|
384
|
-
// ── describe ──
|
|
385
|
-
program
|
|
386
|
-
.command("describe <spec>")
|
|
387
|
-
.description("Describe endpoints from OpenAPI spec")
|
|
388
|
-
.option("--compact", "List all endpoints briefly")
|
|
389
|
-
.option("--list-params", "List all unique parameters across all endpoints")
|
|
390
|
-
.option("--method <method>", "HTTP method for single endpoint detail")
|
|
391
|
-
.option("--path <path>", "Endpoint path for single endpoint detail")
|
|
392
|
-
.action(async (specPath: string, opts, cmd: Command) => {
|
|
393
|
-
process.exitCode = await describeCommand({
|
|
394
|
-
specPath,
|
|
395
|
-
compact: opts.compact === true,
|
|
396
|
-
listParams: opts.listParams === true,
|
|
397
|
-
method: opts.method,
|
|
398
|
-
path: opts.path,
|
|
399
|
-
json: globalJson(cmd),
|
|
400
|
-
});
|
|
401
|
-
});
|
|
402
|
-
|
|
403
|
-
// ── db (nested) ──
|
|
404
|
-
const db = program.command("db").description("Query the test database");
|
|
405
|
-
|
|
406
|
-
db
|
|
407
|
-
.command("collections")
|
|
408
|
-
.description("List all API collections")
|
|
409
|
-
.option("--db <path>", "Path to SQLite database file")
|
|
410
|
-
.action(async (opts, cmd: Command) => {
|
|
411
|
-
process.exitCode = await dbCommand({
|
|
412
|
-
subcommand: "collections",
|
|
413
|
-
positional: [],
|
|
414
|
-
dbPath: opts.db,
|
|
415
|
-
json: globalJson(cmd),
|
|
416
|
-
});
|
|
417
|
-
});
|
|
418
|
-
|
|
419
|
-
db
|
|
420
|
-
.command("runs")
|
|
421
|
-
.description("List recent test runs")
|
|
422
|
-
.option("--limit <N>", "Maximum number of runs to display", parsePositiveInt("--limit"))
|
|
423
|
-
.option("--db <path>", "Path to SQLite database file")
|
|
424
|
-
.action(async (opts, cmd: Command) => {
|
|
425
|
-
process.exitCode = await dbCommand({
|
|
426
|
-
subcommand: "runs",
|
|
427
|
-
positional: [],
|
|
428
|
-
limit: opts.limit,
|
|
429
|
-
dbPath: opts.db,
|
|
430
|
-
json: globalJson(cmd),
|
|
431
|
-
});
|
|
432
|
-
});
|
|
433
|
-
|
|
434
|
-
db
|
|
435
|
-
.command("run <id>")
|
|
436
|
-
.description("Show run details")
|
|
437
|
-
.option("--verbose", "Show all results")
|
|
438
|
-
.option("--method <method>", "Filter by HTTP method")
|
|
439
|
-
.option("--status <code>", "Filter by HTTP status code", parseInteger("--status"))
|
|
440
|
-
.option("--db <path>", "Path to SQLite database file")
|
|
441
|
-
.action(async (id: string, opts, cmd: Command) => {
|
|
442
|
-
process.exitCode = await dbCommand({
|
|
443
|
-
subcommand: "run",
|
|
444
|
-
positional: [id],
|
|
445
|
-
verbose: opts.verbose === true,
|
|
446
|
-
method: opts.method,
|
|
447
|
-
status: opts.status,
|
|
448
|
-
dbPath: opts.db,
|
|
449
|
-
json: globalJson(cmd),
|
|
450
|
-
});
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
db
|
|
454
|
-
.command("diagnose <id>")
|
|
455
|
-
.description("Diagnose run failures")
|
|
456
|
-
.option("--limit <N>", "Examples per failure group", parsePositiveInt("--limit"))
|
|
457
|
-
.option("--verbose", "Show all examples (not grouped)")
|
|
458
|
-
.option("--db <path>", "Path to SQLite database file")
|
|
459
|
-
.action(async (id: string, opts, cmd: Command) => {
|
|
460
|
-
process.exitCode = await dbCommand({
|
|
461
|
-
subcommand: "diagnose",
|
|
462
|
-
positional: [id],
|
|
463
|
-
limit: opts.limit,
|
|
464
|
-
verbose: opts.verbose === true,
|
|
465
|
-
dbPath: opts.db,
|
|
466
|
-
json: globalJson(cmd),
|
|
467
|
-
});
|
|
468
|
-
});
|
|
469
|
-
|
|
470
|
-
db
|
|
471
|
-
.command("compare <idA> <idB>")
|
|
472
|
-
.description("Compare two runs")
|
|
473
|
-
.option("--db <path>", "Path to SQLite database file")
|
|
474
|
-
.action(async (idA: string, idB: string, opts, cmd: Command) => {
|
|
475
|
-
process.exitCode = await dbCommand({
|
|
476
|
-
subcommand: "compare",
|
|
477
|
-
positional: [idA, idB],
|
|
478
|
-
dbPath: opts.db,
|
|
479
|
-
json: globalJson(cmd),
|
|
480
|
-
});
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
// ── request ──
|
|
484
|
-
program
|
|
485
|
-
.command("request <method> <url>")
|
|
486
|
-
.description("Send an ad-hoc HTTP request")
|
|
487
|
-
.option("--header <H>", `Request header "Name: Value" (repeatable)`, collect, [])
|
|
488
|
-
.option("--body <json>", "Request body (JSON string)")
|
|
489
|
-
.option("--timeout <ms>", "Request timeout", parsePositiveInt("--timeout"))
|
|
490
|
-
.option("--env <name>", "Environment for variable interpolation")
|
|
491
|
-
.option("--api <name>", "Collection name (loads env from its directory)")
|
|
492
|
-
.option("--json-path <path>", "Extract value from response (dot notation)")
|
|
493
|
-
.option("--db <path>", "Path to SQLite database file")
|
|
494
|
-
.action(async (method: string, url: string, opts, cmd: Command) => {
|
|
495
|
-
const headers = (opts.header as string[] | undefined)?.length ? (opts.header as string[]) : undefined;
|
|
496
|
-
const api = (opts.api as string | undefined) ?? readCurrentApi() ?? undefined;
|
|
497
|
-
process.exitCode = await requestCommand({
|
|
498
|
-
method,
|
|
499
|
-
url,
|
|
500
|
-
headers,
|
|
501
|
-
body: opts.body,
|
|
502
|
-
timeout: opts.timeout,
|
|
503
|
-
env: opts.env,
|
|
504
|
-
api,
|
|
505
|
-
jsonPath: opts.jsonPath,
|
|
506
|
-
dbPath: opts.db,
|
|
507
|
-
json: globalJson(cmd),
|
|
508
|
-
});
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
// ── generate ──
|
|
512
|
-
program
|
|
513
|
-
.command("generate <spec>")
|
|
514
|
-
.description("Generate test suites from OpenAPI spec")
|
|
515
|
-
.requiredOption("--output <dir>", "Output directory for generated test files")
|
|
516
|
-
.option("--tag <tag>", "Generate only for endpoints with this tag")
|
|
517
|
-
.option("--uncovered-only", "Skip endpoints already covered by existing tests")
|
|
518
|
-
.action(async (specPath: string, opts, cmd: Command) => {
|
|
519
|
-
process.exitCode = await generateCommand({
|
|
520
|
-
specPath,
|
|
521
|
-
output: opts.output,
|
|
522
|
-
tag: opts.tag,
|
|
523
|
-
uncoveredOnly: opts.uncoveredOnly === true,
|
|
524
|
-
json: globalJson(cmd),
|
|
525
|
-
});
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
// ── probe-validation ──
|
|
529
|
-
program
|
|
530
|
-
.command("probe-validation <spec>")
|
|
531
|
-
.description("Generate negative-input probe suites (catches 5xx-on-bad-input bugs)")
|
|
532
|
-
.requiredOption("--output <dir>", "Output directory for generated probe files")
|
|
533
|
-
.option("--tag <tag>", "Probe only endpoints with this tag")
|
|
534
|
-
.option("--list-tags", "List available tags from spec and exit")
|
|
535
|
-
.option("--max-per-endpoint <N>", "Cap probes per endpoint (default 50)", parsePositiveInt("--max-per-endpoint"))
|
|
536
|
-
.option("--no-cleanup", "Skip emission of follow-up DELETE cleanup steps for mutating probes (use in namespace-isolated test envs)")
|
|
537
|
-
.action(async (specPath: string, opts, cmd: Command) => {
|
|
538
|
-
process.exitCode = await probeValidationCommand({
|
|
539
|
-
specPath,
|
|
540
|
-
output: opts.output,
|
|
541
|
-
tag: opts.tag,
|
|
542
|
-
maxPerEndpoint: opts.maxPerEndpoint,
|
|
543
|
-
// Commander: --no-cleanup → opts.cleanup === false; default is true.
|
|
544
|
-
noCleanup: opts.cleanup === false,
|
|
545
|
-
json: globalJson(cmd),
|
|
546
|
-
listTags: opts.listTags,
|
|
547
|
-
});
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
// ── probe-methods ──
|
|
551
|
-
program
|
|
552
|
-
.command("probe-methods <spec>")
|
|
553
|
-
.description("Generate negative-method probe suites (catches 5xx/2xx on undeclared HTTP methods)")
|
|
554
|
-
.requiredOption("--output <dir>", "Output directory for generated probe files")
|
|
555
|
-
.option("--tag <tag>", "Probe only endpoints with this tag")
|
|
556
|
-
.action(async (specPath: string, opts, cmd: Command) => {
|
|
557
|
-
process.exitCode = await probeMethodsCommand({
|
|
558
|
-
specPath,
|
|
559
|
-
output: opts.output,
|
|
560
|
-
tag: opts.tag,
|
|
561
|
-
json: globalJson(cmd),
|
|
562
|
-
});
|
|
563
|
-
});
|
|
564
|
-
|
|
565
|
-
// ── catalog ──
|
|
566
|
-
program
|
|
567
|
-
.command("catalog <spec>")
|
|
568
|
-
.description("Generate API catalog (compact endpoint reference)")
|
|
569
|
-
.option("--output <dir>", "Output directory (default: current directory)")
|
|
570
|
-
.action(async (specPath: string, opts, cmd: Command) => {
|
|
571
|
-
process.exitCode = await catalogCommand({
|
|
572
|
-
specPath,
|
|
573
|
-
output: opts.output,
|
|
574
|
-
json: globalJson(cmd),
|
|
575
|
-
});
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
// ── guide ──
|
|
579
|
-
program
|
|
580
|
-
.command("guide <spec>")
|
|
581
|
-
.description("Generate test generation guide from OpenAPI spec")
|
|
582
|
-
.option("--tests-dir <dir>", "Filter to uncovered endpoints only")
|
|
583
|
-
.option("--tag <tag>", "Generate only for endpoints with this tag")
|
|
584
|
-
.action(async (specPath: string, opts, cmd: Command) => {
|
|
585
|
-
process.exitCode = await guideCommand({
|
|
586
|
-
specPath,
|
|
587
|
-
testsDir: opts.testsDir,
|
|
588
|
-
tag: opts.tag,
|
|
589
|
-
json: globalJson(cmd),
|
|
590
|
-
});
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
// ── export (with subcommand: postman) ──
|
|
594
|
-
const exportCmd = program.command("export").description("Export tests to other formats");
|
|
595
|
-
exportCmd
|
|
596
|
-
.command("postman <path>")
|
|
597
|
-
.description("Export YAML tests as Postman Collection v2.1")
|
|
598
|
-
.option("--output <file>", "Output file path", "collection.postman.json")
|
|
599
|
-
.option("--env <file>", "Also export .env.yaml as Postman environment")
|
|
600
|
-
.option("--collection-name <name>", "Collection name (default: derived from path)")
|
|
601
|
-
.action(async (testsPath: string, opts, cmd: Command) => {
|
|
602
|
-
process.exitCode = await exportCommand({
|
|
603
|
-
testsPath,
|
|
604
|
-
output: opts.output,
|
|
605
|
-
env: opts.env,
|
|
606
|
-
collectionName: opts.collectionName,
|
|
607
|
-
json: globalJson(cmd),
|
|
608
|
-
});
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
// ── update / self-update ──
|
|
612
|
-
program
|
|
613
|
-
.command("update")
|
|
614
|
-
.alias("self-update")
|
|
615
|
-
.description("Check for updates and self-update the binary")
|
|
616
|
-
.option("--check", "Only check for updates, do not download")
|
|
617
|
-
.action(async (opts, cmd: Command) => {
|
|
618
|
-
process.exitCode = await updateCommand({
|
|
619
|
-
check: opts.check === true,
|
|
620
|
-
json: globalJson(cmd),
|
|
621
|
-
});
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
// ── sync ──
|
|
625
|
-
program
|
|
626
|
-
.command("sync <spec>")
|
|
627
|
-
.description("Detect new/removed endpoints and generate tests for new ones")
|
|
628
|
-
.requiredOption("--tests <dir>", "Path to test files directory")
|
|
629
|
-
.option("--dry-run", "Show what would be generated without writing files")
|
|
630
|
-
.option("--tag <tag>", "Limit sync to endpoints with this tag")
|
|
631
|
-
.action(async (specPath: string, opts, cmd: Command) => {
|
|
632
|
-
process.exitCode = await syncCommand({
|
|
633
|
-
specPath,
|
|
634
|
-
testsDir: opts.tests,
|
|
635
|
-
dryRun: opts.dryRun === true,
|
|
636
|
-
tag: opts.tag,
|
|
637
|
-
json: globalJson(cmd),
|
|
638
|
-
});
|
|
639
|
-
});
|
|
76
|
+
registerRun(program);
|
|
77
|
+
|
|
78
|
+
registerCheck(program);
|
|
79
|
+
registerChecks(program);
|
|
80
|
+
registerLint(program);
|
|
81
|
+
|
|
82
|
+
registerCi(program);
|
|
83
|
+
|
|
84
|
+
registerUse(program);
|
|
85
|
+
registerRefreshApi(program);
|
|
86
|
+
registerDoctor(program);
|
|
87
|
+
|
|
88
|
+
registerSession(program);
|
|
89
|
+
registerCoverage(program);
|
|
90
|
+
|
|
91
|
+
registerInit(program);
|
|
92
|
+
registerAdd(program);
|
|
93
|
+
registerRemove(program);
|
|
94
|
+
|
|
95
|
+
registerDescribe(program);
|
|
96
|
+
registerDb(program);
|
|
97
|
+
registerRequest(program);
|
|
98
|
+
|
|
99
|
+
registerClean(program);
|
|
100
|
+
registerCleanup(program);
|
|
101
|
+
|
|
102
|
+
registerGenerate(program);
|
|
103
|
+
registerPrepareFixtures(program);
|
|
104
|
+
registerFixtures(program);
|
|
105
|
+
registerAudit(program);
|
|
106
|
+
|
|
107
|
+
// m-17 / ARV-49: validate registered probes implement the Probe
|
|
108
|
+
// contract before commander gets to wire them up. Boot-throws on a
|
|
109
|
+
// missing slot, so a regression can't slip past CI.
|
|
110
|
+
bootstrapProbes();
|
|
111
|
+
// ARV-123/124: populate the anti-FP rule registry before checks run
|
|
112
|
+
// — checks call applyAntiFp() and need every shipped rule present.
|
|
113
|
+
bootstrapAntiFp();
|
|
114
|
+
registerProbes(program);
|
|
115
|
+
|
|
116
|
+
registerCatalog(program);
|
|
117
|
+
registerReport(program);
|
|
118
|
+
|
|
119
|
+
registerCompletions(program);
|
|
120
|
+
registerReference(program);
|
|
121
|
+
registerApiAnnotate(program);
|
|
122
|
+
|
|
123
|
+
// TASK-267: group top-level commands by phase in `zond --help`. Without
|
|
124
|
+
// grouping, the flat 20+ command list buries the workflow shape; with it,
|
|
125
|
+
// a new tester can see "setup → generate → run → analyze → report" at a
|
|
126
|
+
// glance. Commands not listed below stay in the default group.
|
|
127
|
+
const HELP_GROUPS: Record<string, string> = {
|
|
128
|
+
// setup: register an API, prepare workspace
|
|
129
|
+
"init": "Setup:",
|
|
130
|
+
"add": "Setup:",
|
|
131
|
+
"remove": "Setup:",
|
|
132
|
+
"use": "Setup:",
|
|
133
|
+
"refresh-api": "Setup:",
|
|
134
|
+
"doctor": "Setup:",
|
|
135
|
+
"clean": "Setup:",
|
|
136
|
+
"cleanup": "Setup:",
|
|
137
|
+
// generate: produce suites/probes from the spec
|
|
138
|
+
"generate": "Generate:",
|
|
139
|
+
"prepare-fixtures": "Generate:",
|
|
140
|
+
"probe": "Generate:",
|
|
141
|
+
// run: execute suites against a live API
|
|
142
|
+
"run": "Run:",
|
|
143
|
+
"session": "Run:",
|
|
144
|
+
"request": "Run:",
|
|
145
|
+
// analyze: post-run inspection and triage
|
|
146
|
+
"coverage": "Analyze:",
|
|
147
|
+
"db": "Analyze:",
|
|
148
|
+
"audit": "Analyze:",
|
|
149
|
+
"check": "Analyze:",
|
|
150
|
+
"checks": "Analyze:",
|
|
151
|
+
"describe": "Analyze:",
|
|
152
|
+
// report: outbound artefacts (HTML, bundles, catalog)
|
|
153
|
+
"report": "Report:",
|
|
154
|
+
"catalog": "Report:",
|
|
155
|
+
// other: scaffolding / shell integration
|
|
156
|
+
"ci": "Other:",
|
|
157
|
+
"completions": "Other:",
|
|
158
|
+
"reference": "Other:",
|
|
159
|
+
};
|
|
160
|
+
for (const sub of program.commands) {
|
|
161
|
+
const group = HELP_GROUPS[sub.name()];
|
|
162
|
+
if (group) sub.helpGroup(group);
|
|
163
|
+
}
|
|
640
164
|
|
|
641
|
-
//
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
165
|
+
// TASK-73: previously `--json` was a top-level/global option that propagated
|
|
166
|
+
// to every subcommand, which collided with `run --report json` (and broke
|
|
167
|
+
// `run --json` outright). Now it is per-command. Attach `--json` to every
|
|
168
|
+
// subcommand that previously read it via globalJson(), EXCEPT `run` —
|
|
169
|
+
// run's only JSON output path is `--report json`.
|
|
170
|
+
// Skip by fully-qualified path so `db run` (inner) keeps --json while
|
|
171
|
+
// top-level `run` does not.
|
|
172
|
+
const skipJson = new Set(["run", "completions"]);
|
|
173
|
+
const attachJson = (cmd: Command, parentPath: string): void => {
|
|
174
|
+
const path = parentPath ? `${parentPath} ${cmd.name()}` : cmd.name();
|
|
175
|
+
// Only leaf commands (those with action handlers) get --json — parent
|
|
176
|
+
// namespace commands like `db` and `ci` would otherwise shadow the option
|
|
177
|
+
// on their children and `cmd.opts()` on the leaf would not see --json.
|
|
178
|
+
const hasAction = (cmd as unknown as { _actionHandler?: unknown })._actionHandler != null;
|
|
179
|
+
if (hasAction && !skipJson.has(path)) {
|
|
180
|
+
cmd.option(
|
|
181
|
+
"--json",
|
|
182
|
+
"Emit a `{ok, command, data, warnings, errors}` envelope on stdout. " +
|
|
183
|
+
"Distinct from `zond run --report json` (which is a per-test test-run report).",
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
for (const sub of cmd.commands) attachJson(sub, path);
|
|
187
|
+
};
|
|
188
|
+
for (const sub of program.commands) attachJson(sub, "");
|
|
189
|
+
|
|
190
|
+
// TASK-297: stamp every leaf with a "related skill" footer so `zond <cmd>
|
|
191
|
+
// --help` is a single-stop entry point for an agent: discover the flag
|
|
192
|
+
// surface AND know which skill file to open for the workflow context.
|
|
193
|
+
// Mapped by fully-qualified path; `*` matches any unnamed leaf and is
|
|
194
|
+
// tried last.
|
|
195
|
+
const skillFor: Record<string, string> = {
|
|
196
|
+
// Depth-check & probe commands → checks skill (annotate flow + m-20).
|
|
197
|
+
"checks run": "skills/zond-checks.md",
|
|
198
|
+
"checks list": "skills/zond-checks.md",
|
|
199
|
+
"api annotate": "skills/zond-checks.md",
|
|
200
|
+
"probe security": "skills/zond-checks.md",
|
|
201
|
+
"probe mass-assignment": "skills/zond-checks.md",
|
|
202
|
+
// Triage-class commands.
|
|
203
|
+
"db diagnose": "skills/zond-triage.md",
|
|
204
|
+
"db compare": "skills/zond-triage.md",
|
|
205
|
+
};
|
|
206
|
+
const attachHelp = (cmd: Command, parentPath: string): void => {
|
|
207
|
+
const path = parentPath ? `${parentPath} ${cmd.name()}` : cmd.name();
|
|
208
|
+
const hasAction = (cmd as unknown as { _actionHandler?: unknown })._actionHandler != null;
|
|
209
|
+
if (hasAction) {
|
|
210
|
+
const skill = skillFor[path] ?? "skills/zond.md";
|
|
211
|
+
cmd.addHelpText("after", `\nRelated skill: ${skill}`);
|
|
212
|
+
}
|
|
213
|
+
for (const sub of cmd.commands) attachHelp(sub, path);
|
|
214
|
+
};
|
|
215
|
+
for (const sub of program.commands) attachHelp(sub, "");
|
|
653
216
|
|
|
654
217
|
return program;
|
|
655
218
|
}
|