@kirrosh/zond 0.21.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 +758 -3
- package/README.md +78 -15
- package/package.json +17 -10
- 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 +55 -6
- package/src/cli/commands/clean.ts +212 -0
- package/src/cli/commands/cleanup.ts +262 -0
- package/src/cli/commands/completions.ts +192 -0
- package/src/cli/commands/coverage.ts +605 -132
- package/src/cli/commands/db.ts +180 -8
- 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 -47
- package/src/cli/commands/init/agents-md.ts +61 -0
- package/src/cli/commands/init/bootstrap.ts +108 -0
- package/src/cli/commands/init/index.ts +244 -0
- package/src/cli/commands/init/skills.ts +98 -0
- package/src/cli/commands/init/templates/agents.md +77 -0
- package/src/cli/commands/init/templates/markdown.d.ts +4 -0
- 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 +651 -0
- package/src/cli/commands/init/templates/zond-config.yml +14 -0
- 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 +911 -33
- package/src/cli/commands/session.ts +244 -0
- package/src/cli/commands/use.ts +74 -0
- package/src/cli/index.ts +36 -607
- package/src/cli/json-envelope.ts +112 -3
- package/src/cli/json-schemas.ts +263 -0
- package/src/cli/program.ts +218 -0
- 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 +8 -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 +51 -0
- 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 +161 -12
- 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 +53 -15
- 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 +490 -33
- 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 +55 -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 +114 -15
- package/src/core/generator/suite-generator.ts +484 -77
- 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 +132 -5
- package/src/core/parser/types.ts +29 -2
- package/src/core/parser/variables.ts +0 -0
- package/src/core/parser/yaml-parser.ts +108 -13
- 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 +164 -0
- package/src/core/probe/method-shared.ts +69 -0
- package/src/core/probe/negative-probe.ts +691 -0
- 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 +69 -4
- package/src/core/reporter/index.ts +2 -3
- package/src/core/reporter/json.ts +15 -2
- 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 +62 -2
- 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 +391 -52
- package/src/core/runner/form-encode.ts +51 -0
- package/src/core/runner/http-client.ts +115 -7
- 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 +203 -0
- 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 +419 -17
- 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/root.ts +94 -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 +179 -48
- package/src/cli/commands/export.ts +0 -144
- package/src/cli/commands/guide.ts +0 -127
- package/src/cli/commands/init.ts +0 -57
- package/src/cli/commands/serve.ts +0 -81
- package/src/cli/commands/sync.ts +0 -269
- package/src/cli/commands/update.ts +0 -189
- package/src/cli/commands/validate.ts +0 -34
- package/src/core/exporter/postman.ts +0 -963
- package/src/core/generator/guide-builder.ts +0 -253
- package/src/core/meta/types.ts +0 -21
- 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
|
@@ -25,6 +25,9 @@ permissions:
|
|
|
25
25
|
contents: read
|
|
26
26
|
checks: write
|
|
27
27
|
pull-requests: write
|
|
28
|
+
# ARV-5: required for github/codeql-action/upload-sarif to surface
|
|
29
|
+
# zond-checks findings in the Code Scanning tab.
|
|
30
|
+
security-events: write
|
|
28
31
|
|
|
29
32
|
jobs:
|
|
30
33
|
test:
|
|
@@ -42,16 +45,34 @@ jobs:
|
|
|
42
45
|
- name: Run smoke tests (read-only, safe for production)
|
|
43
46
|
run: |
|
|
44
47
|
mkdir -p test-results
|
|
45
|
-
|
|
48
|
+
# --exclude-tag needs-id skips positive smoke that needs real IDs from .env.yaml
|
|
49
|
+
zond run apis/ --tag smoke --exclude-tag needs-id --safe --report junit --no-db > test-results/smoke.xml
|
|
46
50
|
# Use --env-var "API_KEY=\${{ secrets.API_KEY }}" to inject secrets without writing to disk
|
|
47
51
|
continue-on-error: true
|
|
48
52
|
|
|
49
|
-
- name: Run CRUD tests (staging only)
|
|
53
|
+
- name: Run CRUD tests (staging only — ephemeral suites only)
|
|
50
54
|
run: |
|
|
51
|
-
|
|
55
|
+
# --exclude-tag persistent-write keeps only ephemeral CRUD (suites that DELETE what they create).
|
|
56
|
+
# Drop --exclude-tag persistent-write to opt into write suites that leave residual data.
|
|
57
|
+
zond run apis/ --tag crud --exclude-tag persistent-write --env staging --report junit --no-db > test-results/crud.xml
|
|
52
58
|
# Add --env-var "BASE_URL=\${{ secrets.STAGING_URL }}" for staging URL
|
|
53
59
|
continue-on-error: true
|
|
54
60
|
|
|
61
|
+
- name: Run depth checks (SARIF for Code Scanning)
|
|
62
|
+
run: |
|
|
63
|
+
zond checks run --api myapi --report sarif --output test-results/zond-checks.sarif || true
|
|
64
|
+
# ARV-5: \`|| true\` keeps the workflow green even when HIGH/CRITICAL
|
|
65
|
+
# findings would otherwise exit 1 — Code Scanning will still get the
|
|
66
|
+
# SARIF and gate on the alerts via branch protection if desired.
|
|
67
|
+
continue-on-error: true
|
|
68
|
+
|
|
69
|
+
- name: Upload SARIF to GitHub Code Scanning
|
|
70
|
+
if: always()
|
|
71
|
+
uses: github/codeql-action/upload-sarif@v3
|
|
72
|
+
with:
|
|
73
|
+
sarif_file: test-results/zond-checks.sarif
|
|
74
|
+
category: zond-checks
|
|
75
|
+
|
|
55
76
|
- name: Publish test results
|
|
56
77
|
uses: EnricoMi/publish-unit-test-result-action@v2
|
|
57
78
|
if: always()
|
|
@@ -86,8 +107,9 @@ api-smoke:
|
|
|
86
107
|
- curl -fsSL https://raw.githubusercontent.com/kirrosh/zond/master/install.sh | sh
|
|
87
108
|
script:
|
|
88
109
|
- mkdir -p test-results
|
|
89
|
-
# Use --env-var to inject secrets without writing to disk
|
|
90
|
-
-
|
|
110
|
+
# Use --env-var to inject secrets without writing to disk.
|
|
111
|
+
# --exclude-tag needs-id skips positive smoke that needs real IDs from .env.yaml.
|
|
112
|
+
- zond run apis/ --tag smoke --exclude-tag needs-id --safe --report junit --no-db --env-var "API_KEY=$API_KEY" > test-results/smoke.xml
|
|
91
113
|
allow_failure:
|
|
92
114
|
exit_codes: 1
|
|
93
115
|
artifacts:
|
|
@@ -102,7 +124,9 @@ api-crud:
|
|
|
102
124
|
- curl -fsSL https://raw.githubusercontent.com/kirrosh/zond/master/install.sh | sh
|
|
103
125
|
script:
|
|
104
126
|
- mkdir -p test-results
|
|
105
|
-
-
|
|
127
|
+
# --exclude-tag persistent-write keeps only ephemeral CRUD (suites that DELETE what they create).
|
|
128
|
+
# Drop --exclude-tag persistent-write to opt into write suites that leave residual data.
|
|
129
|
+
- zond run apis/ --tag crud --exclude-tag persistent-write --env staging --report junit --no-db > test-results/crud.xml
|
|
106
130
|
allow_failure:
|
|
107
131
|
exit_codes: 1
|
|
108
132
|
artifacts:
|
|
@@ -172,3 +196,28 @@ export async function ciInitCommand(options: CiInitOptions): Promise<number> {
|
|
|
172
196
|
|
|
173
197
|
return 0;
|
|
174
198
|
}
|
|
199
|
+
|
|
200
|
+
import type { Command } from "commander";
|
|
201
|
+
import { globalJson } from "../resolve.ts";
|
|
202
|
+
|
|
203
|
+
export function registerCi(program: Command): void {
|
|
204
|
+
const ci = program.command("ci").description("CI/CD scaffolding");
|
|
205
|
+
ci
|
|
206
|
+
.command("init")
|
|
207
|
+
.description("Generate CI/CD workflow (GitHub Actions, GitLab CI)")
|
|
208
|
+
.option("--github", "Generate GitHub Actions workflow")
|
|
209
|
+
.option("--gitlab", "Generate GitLab CI config")
|
|
210
|
+
.option("--dir <path>", "Project root directory (default: current directory)")
|
|
211
|
+
.option("--force", "Overwrite existing CI config")
|
|
212
|
+
.action(async (opts, cmd: Command) => {
|
|
213
|
+
let platform: "github" | "gitlab" | undefined;
|
|
214
|
+
if (opts.github === true) platform = "github";
|
|
215
|
+
else if (opts.gitlab === true) platform = "gitlab";
|
|
216
|
+
process.exitCode = await ciInitCommand({
|
|
217
|
+
platform,
|
|
218
|
+
force: opts.force === true,
|
|
219
|
+
dir: opts.dir,
|
|
220
|
+
json: globalJson(cmd),
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `zond clean` — remove auto-generated files tracked in `.zond/manifest.json`.
|
|
3
|
+
*
|
|
4
|
+
* Default mode is dry-run; `--force` is required to actually delete. Files
|
|
5
|
+
* whose sha256 no longer matches the manifest entry are treated as
|
|
6
|
+
* manually-edited and skipped (TASK-156, m-9).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { rmSync, rmdirSync, readdirSync, existsSync } from "node:fs";
|
|
10
|
+
import { dirname, resolve } from "node:path";
|
|
11
|
+
import { findWorkspaceRoot } from "../../core/workspace/root.ts";
|
|
12
|
+
import {
|
|
13
|
+
hasManifest,
|
|
14
|
+
inspectEntries,
|
|
15
|
+
loadManifest,
|
|
16
|
+
removeManifestEntries,
|
|
17
|
+
selectEntriesEx,
|
|
18
|
+
type CleanItem,
|
|
19
|
+
type ManifestCategory,
|
|
20
|
+
} from "../../core/workspace/manifest.ts";
|
|
21
|
+
import type { Command } from "commander";
|
|
22
|
+
import { jsonOk, jsonError, printJson } from "../json-envelope.ts";
|
|
23
|
+
import { printError, printSuccess } from "../output.ts";
|
|
24
|
+
import { globalJson } from "../resolve.ts";
|
|
25
|
+
import { getApi } from "../util/api-context.ts";
|
|
26
|
+
|
|
27
|
+
export interface CleanOptions {
|
|
28
|
+
api?: string;
|
|
29
|
+
probes?: boolean;
|
|
30
|
+
all?: boolean;
|
|
31
|
+
force?: boolean;
|
|
32
|
+
json?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function cleanCommand(opts: CleanOptions): Promise<number> {
|
|
36
|
+
const ws = findWorkspaceRoot();
|
|
37
|
+
if (ws.fromFallback) {
|
|
38
|
+
const m = "No workspace detected. Run `zond init` first.";
|
|
39
|
+
if (opts.json) printJson(jsonError("clean", [m])); else printError(m);
|
|
40
|
+
return 2;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!hasManifest(ws.root)) {
|
|
44
|
+
const msg = `No .zond/manifest.json — nothing tracked yet. Run \`zond add api\`, \`zond generate\`, or a probe-* --emit first.`;
|
|
45
|
+
if (opts.json) {
|
|
46
|
+
printJson(jsonOk("clean", { dryRun: true, deleted: [], modified: [], missing: [], message: msg }));
|
|
47
|
+
} else {
|
|
48
|
+
console.log(msg);
|
|
49
|
+
}
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!opts.api && !opts.probes && !opts.all) {
|
|
54
|
+
const m = "Specify a scope: --api <name>, --probes, or --all.";
|
|
55
|
+
if (opts.json) printJson(jsonError("clean", [m])); else printError(m);
|
|
56
|
+
return 2;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const manifest = loadManifest(ws.root);
|
|
60
|
+
const category: ManifestCategory | undefined = opts.probes ? "probes" : undefined;
|
|
61
|
+
const { selected: entries, probesPreserved } = selectEntriesEx(manifest, {
|
|
62
|
+
api: opts.api,
|
|
63
|
+
category,
|
|
64
|
+
all: opts.all && !opts.api && !opts.probes,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (entries.length === 0 && probesPreserved.length === 0) {
|
|
68
|
+
const m = "No matching auto-generated files in manifest.";
|
|
69
|
+
if (opts.json) {
|
|
70
|
+
printJson(jsonOk("clean", { dryRun: true, deleted: [], modified: [], missing: [], message: m }));
|
|
71
|
+
} else {
|
|
72
|
+
console.log(m);
|
|
73
|
+
}
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const items = inspectEntries(ws.root, entries);
|
|
78
|
+
const toDelete = items.filter((i) => i.verdict === "delete");
|
|
79
|
+
const modified = items.filter((i) => i.verdict === "modified");
|
|
80
|
+
const missing = items.filter((i) => i.verdict === "missing");
|
|
81
|
+
|
|
82
|
+
const dryRun = !opts.force;
|
|
83
|
+
|
|
84
|
+
if (!dryRun) {
|
|
85
|
+
for (const item of toDelete) {
|
|
86
|
+
try {
|
|
87
|
+
rmSync(item.absPath, { force: true });
|
|
88
|
+
} catch {
|
|
89
|
+
// best-effort
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
pruneEmptyDirs(ws.root, toDelete);
|
|
93
|
+
const removedPaths = [...toDelete, ...missing].map((i) => i.entry.path);
|
|
94
|
+
removeManifestEntries(ws.root, removedPaths);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (opts.json) {
|
|
98
|
+
printJson(jsonOk("clean", {
|
|
99
|
+
dryRun,
|
|
100
|
+
scope: { api: opts.api, probes: !!opts.probes, all: !!opts.all },
|
|
101
|
+
deleted: toDelete.map(itemSummary),
|
|
102
|
+
modified: modified.map(itemSummary),
|
|
103
|
+
missing: missing.map(itemSummary),
|
|
104
|
+
probesPreserved: probesPreserved.map((e) => ({ path: e.path, api: e.api })),
|
|
105
|
+
}));
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const verb = dryRun ? "Would delete" : "Deleted";
|
|
110
|
+
printSuccess(`${verb} ${toDelete.length} file(s); ${modified.length} skipped (manually edited); ${missing.length} already missing.`);
|
|
111
|
+
if (toDelete.length > 0) {
|
|
112
|
+
console.log("");
|
|
113
|
+
console.log(`${verb}:`);
|
|
114
|
+
for (const i of toDelete) console.log(` - ${i.entry.path}`);
|
|
115
|
+
}
|
|
116
|
+
if (modified.length > 0) {
|
|
117
|
+
console.log("");
|
|
118
|
+
console.log("Skipped (manually edited, sha256 mismatch):");
|
|
119
|
+
for (const i of modified) console.log(` ! ${i.entry.path}`);
|
|
120
|
+
}
|
|
121
|
+
// TASK-258: probes belong to a separate pipeline. Scoping by --api alone
|
|
122
|
+
// preserves them and surfaces the regen command so users don't lose
|
|
123
|
+
// 30s+ of probe-validation/-methods work to a typo.
|
|
124
|
+
if (probesPreserved.length > 0) {
|
|
125
|
+
console.log("");
|
|
126
|
+
console.log(`Preserved ${probesPreserved.length} probe-suite file(s) under apis/${opts.api ?? "<api>"}/probes/.`);
|
|
127
|
+
console.log("Pass --probes to also remove them, or regenerate via:");
|
|
128
|
+
console.log(` zond probe-validation --api ${opts.api} --output apis/${opts.api}/probes`);
|
|
129
|
+
console.log(` zond probe-methods --api ${opts.api} --output apis/${opts.api}/probes`);
|
|
130
|
+
}
|
|
131
|
+
if (dryRun) {
|
|
132
|
+
console.log("");
|
|
133
|
+
console.log("Re-run with --force to actually delete.");
|
|
134
|
+
}
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function itemSummary(i: CleanItem) {
|
|
139
|
+
return {
|
|
140
|
+
path: i.entry.path,
|
|
141
|
+
by: i.entry.by,
|
|
142
|
+
ts: i.entry.ts,
|
|
143
|
+
api: i.entry.api,
|
|
144
|
+
category: i.entry.category,
|
|
145
|
+
verdict: i.verdict,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* After deleting tracked files, remove any directories that are now empty
|
|
151
|
+
* and live inside the workspace. Best-effort: stops at first non-empty dir.
|
|
152
|
+
*/
|
|
153
|
+
function pruneEmptyDirs(workspaceRoot: string, items: CleanItem[]): void {
|
|
154
|
+
const dirs = new Set<string>();
|
|
155
|
+
for (const i of items) dirs.add(dirname(i.absPath));
|
|
156
|
+
// Process deepest first.
|
|
157
|
+
const sorted = [...dirs].sort((a, b) => b.length - a.length);
|
|
158
|
+
for (const d of sorted) {
|
|
159
|
+
let cur = d;
|
|
160
|
+
while (cur.startsWith(workspaceRoot) && cur !== workspaceRoot) {
|
|
161
|
+
if (!existsSync(cur)) {
|
|
162
|
+
cur = dirname(cur);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
let entries: string[] = [];
|
|
166
|
+
try {
|
|
167
|
+
entries = readdirSync(cur);
|
|
168
|
+
} catch {
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
if (entries.length > 0) break;
|
|
172
|
+
try {
|
|
173
|
+
rmdirSync(cur);
|
|
174
|
+
} catch {
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
cur = dirname(cur);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Keep `resolve` reachable for type-only imports.
|
|
181
|
+
void resolve;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function registerClean(program: Command): void {
|
|
185
|
+
program
|
|
186
|
+
.command("clean")
|
|
187
|
+
.description("Remove auto-generated files tracked in .zond/manifest.json (TASK-156, m-9)")
|
|
188
|
+
.option("--api <name>", "Limit to a single API (apis/<name>/). Preserves probes/ unless --probes is also passed (TASK-258)")
|
|
189
|
+
.option(
|
|
190
|
+
"--probes",
|
|
191
|
+
"Scope deletion to probe-suite files only (apis/<api>/probes/). " +
|
|
192
|
+
"TASK-265: this is effectively `--probes-only` — `tests/`, `spec.json`, " +
|
|
193
|
+
"and `.api-catalog.yaml` are NEVER touched in this mode. Combine with " +
|
|
194
|
+
"--api <name> to limit to one API; alone, removes probes for every API.",
|
|
195
|
+
)
|
|
196
|
+
.option("--all", "Remove every tracked auto-generated file in the workspace (includes probes/)")
|
|
197
|
+
.option("--force", "Actually delete files (default is dry-run)")
|
|
198
|
+
.action(async (opts, cmd: Command) => {
|
|
199
|
+
// ARV-238 (R-03/F11/SD9): `--api` exists both at program level (TASK-290)
|
|
200
|
+
// and on this subcommand; commander absorbs the value into the program
|
|
201
|
+
// option, leaving `opts.api` undefined. Fall back via `getApi` so users
|
|
202
|
+
// who follow `zond clean --api <name>` (per skill / --help) actually
|
|
203
|
+
// scope the run instead of hitting "Specify a scope".
|
|
204
|
+
process.exitCode = await cleanCommand({
|
|
205
|
+
api: getApi(cmd, opts),
|
|
206
|
+
probes: opts.probes === true,
|
|
207
|
+
all: opts.all === true,
|
|
208
|
+
force: opts.force === true,
|
|
209
|
+
json: globalJson(cmd),
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `zond cleanup` — retry housekeeping that probes couldn't finish.
|
|
3
|
+
*
|
|
4
|
+
* v1 ships only `--orphans`: read every record from
|
|
5
|
+
* `~/.zond/orphans/<api>/<run-id>.jsonl` and re-issue the DELETE for any
|
|
6
|
+
* resource that survived the probe's own cleanup attempts. 404 is treated
|
|
7
|
+
* as success — the goal is "resource is gone", and the API getting there
|
|
8
|
+
* before us is fine. (TASK-278.)
|
|
9
|
+
*/
|
|
10
|
+
import { loadOrphans, markRemoved } from "../../core/probe/orphan-tracker.ts";
|
|
11
|
+
import type { OrphanRecord } from "../../core/probe/orphan-tracker.ts";
|
|
12
|
+
import { executeRequest } from "../../core/runner/http-client.ts";
|
|
13
|
+
import { loadEnvironment, loadEnvMeta } from "../../core/parser/variables.ts";
|
|
14
|
+
import { resolveTimeoutMs } from "../../core/workspace/config.ts";
|
|
15
|
+
import { jsonOk, jsonError, printJson } from "../json-envelope.ts";
|
|
16
|
+
import { printError, printWarning, printSuccess } from "../output.ts";
|
|
17
|
+
import { readCurrentApi } from "../../core/context/current.ts";
|
|
18
|
+
import type { Command } from "commander";
|
|
19
|
+
import { globalJson } from "../resolve.ts";
|
|
20
|
+
|
|
21
|
+
export interface CleanupOptions {
|
|
22
|
+
orphans: boolean;
|
|
23
|
+
api?: string;
|
|
24
|
+
/** ARV-139: pass true to disable the default current-api scoping and look
|
|
25
|
+
* at orphans across every API in the tracker. */
|
|
26
|
+
allApis?: boolean;
|
|
27
|
+
runId?: string;
|
|
28
|
+
dryRun?: boolean;
|
|
29
|
+
json?: boolean;
|
|
30
|
+
/** Override base_url resolution. By default the env from cwd .env.yaml or
|
|
31
|
+
* apis/<api>/.env.yaml is used. Tests inject a known URL via this. */
|
|
32
|
+
baseUrl?: string;
|
|
33
|
+
/** Per-request timeout, ms. */
|
|
34
|
+
timeoutMs?: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* ARV-244 (R-04/F15): orphan records store `deletePath` as it was used at
|
|
39
|
+
* probe time — typically built by concatenating literal id segments captured
|
|
40
|
+
* from the response. CRLF / open-redirect probes can poison those ids with
|
|
41
|
+
* raw `\r`, `\n`, spaces, etc. (e.g. label name `zond-safe\rX-Zond: yes`),
|
|
42
|
+
* which makes the DELETE URL malformed: the API gets a request line with an
|
|
43
|
+
* embedded CR and silently splits or routes elsewhere → 404 → record stays
|
|
44
|
+
* in the queue and the resource leaks.
|
|
45
|
+
*
|
|
46
|
+
* Encode unsafe characters per path-segment while preserving anything that
|
|
47
|
+
* is already percent-encoded. Slashes (segment separators), the unreserved
|
|
48
|
+
* set, and a conservative slice of sub-delims are kept verbatim.
|
|
49
|
+
*/
|
|
50
|
+
export function encodeOrphanPath(deletePath: string): string {
|
|
51
|
+
const SAFE = /[A-Za-z0-9._~!$&'()*+,;=:@-]/;
|
|
52
|
+
return deletePath
|
|
53
|
+
.split("/")
|
|
54
|
+
.map((segment) => {
|
|
55
|
+
if (segment.length === 0) return segment;
|
|
56
|
+
let out = "";
|
|
57
|
+
for (let i = 0; i < segment.length; i++) {
|
|
58
|
+
const ch = segment.charAt(i);
|
|
59
|
+
// Preserve existing percent escapes (`%XX`) — probe pre-encoded.
|
|
60
|
+
if (ch === "%" && /^[0-9A-Fa-f]{2}$/.test(segment.slice(i + 1, i + 3))) {
|
|
61
|
+
out += segment.slice(i, i + 3);
|
|
62
|
+
i += 2;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (SAFE.test(ch)) {
|
|
66
|
+
out += ch;
|
|
67
|
+
} else {
|
|
68
|
+
out += encodeURIComponent(ch);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
})
|
|
73
|
+
.join("/");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function cleanupCommand(opts: CleanupOptions): Promise<number> {
|
|
77
|
+
if (!opts.orphans) {
|
|
78
|
+
const m = "Nothing to do — pass --orphans to retry leaked probe resources.";
|
|
79
|
+
if (opts.json) printJson(jsonError("cleanup", [m]));
|
|
80
|
+
else printError(m);
|
|
81
|
+
return 2;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ARV-139: scope the orphan queue to the active API by default. The on-disk
|
|
85
|
+
// tracker at ~/.zond/orphans/<api>/... is shared across the workspace, so
|
|
86
|
+
// switching APIs (e.g. apis/resend → apis/sentry) would otherwise surface
|
|
87
|
+
// orphans from previous work on unrelated APIs — including DELETE attempts
|
|
88
|
+
// against endpoints that aren't even part of the active spec. Explicit
|
|
89
|
+
// `--api <name>` wins; `--all-apis` opts back into the pre-ARV-139 behaviour.
|
|
90
|
+
let scopedApi = opts.api;
|
|
91
|
+
if (!scopedApi && !opts.allApis) {
|
|
92
|
+
scopedApi = readCurrentApi() ?? undefined;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
let records: OrphanRecord[];
|
|
96
|
+
try {
|
|
97
|
+
const filter: { api?: string; runId?: string } = {};
|
|
98
|
+
if (scopedApi) filter.api = scopedApi;
|
|
99
|
+
if (opts.runId) filter.runId = opts.runId;
|
|
100
|
+
records = await loadOrphans(filter);
|
|
101
|
+
} catch (err) {
|
|
102
|
+
const m = `Failed to load orphan tracker: ${(err as Error).message}`;
|
|
103
|
+
if (opts.json) printJson(jsonError("cleanup", [m]));
|
|
104
|
+
else printError(m);
|
|
105
|
+
return 2;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ARV-102 (F7): split orphans into retriable (have a DELETE plan) and
|
|
109
|
+
// manual-only (probe knew the resource was created but couldn't derive
|
|
110
|
+
// a deletePath / id). Manual-only entries are surfaced separately —
|
|
111
|
+
// we can't auto-DELETE them, but the operator must know they exist.
|
|
112
|
+
const manualOnly = records.filter(r => r.requires_manual_cleanup === true || r.deletePath === "");
|
|
113
|
+
const retriable = records.filter(r => !(r.requires_manual_cleanup === true || r.deletePath === ""));
|
|
114
|
+
|
|
115
|
+
if (records.length === 0) {
|
|
116
|
+
if (opts.json) printJson(jsonOk("cleanup", { retried: 0, removed: 0, failed: 0, items: [], manual_cleanup_required: [] }));
|
|
117
|
+
else console.log("No orphan resources to retry.");
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Group by api so we resolve env once per API instead of per record.
|
|
122
|
+
const baseUrlByApi = new Map<string, string>();
|
|
123
|
+
if (opts.baseUrl) {
|
|
124
|
+
for (const r of retriable) baseUrlByApi.set(r.api, opts.baseUrl);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (opts.dryRun) {
|
|
128
|
+
if (opts.json) printJson(jsonOk("cleanup", { dryRun: true, items: retriable, manual_cleanup_required: manualOnly }));
|
|
129
|
+
else {
|
|
130
|
+
console.log(`Dry-run: ${retriable.length} orphan(s) would be retried:`);
|
|
131
|
+
for (const r of retriable) {
|
|
132
|
+
console.log(` ${r.method} ${r.path} (id=${r.id}); DELETE ${r.deletePath} — last status: ${r.lastCleanupStatus ?? "n/a"}`);
|
|
133
|
+
}
|
|
134
|
+
if (manualOnly.length > 0) {
|
|
135
|
+
console.log(`\nManual cleanup required: ${manualOnly.length} resource(s) (no DELETE plan):`);
|
|
136
|
+
for (const r of manualOnly) {
|
|
137
|
+
console.log(` ${r.method} ${r.path}${r.id ? ` (id=${r.id})` : ""} — ${r.lastCleanupError ?? "no DELETE counterpart"}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Per-API timeout: CLI flag → apis/<api>/.env.yaml `timeoutMs:` → workspace
|
|
145
|
+
// `defaults.timeout_ms` → 30000.
|
|
146
|
+
const timeoutByApi = new Map<string, number>();
|
|
147
|
+
async function timeoutFor(api: string): Promise<number> {
|
|
148
|
+
let t = timeoutByApi.get(api);
|
|
149
|
+
if (t !== undefined) return t;
|
|
150
|
+
let envTimeout: number | undefined;
|
|
151
|
+
try {
|
|
152
|
+
const meta = await loadEnvMeta(undefined, `apis/${api}`);
|
|
153
|
+
envTimeout = meta.timeoutMs;
|
|
154
|
+
} catch { /* meta is best-effort */ }
|
|
155
|
+
t = resolveTimeoutMs(opts.timeoutMs, envTimeout);
|
|
156
|
+
timeoutByApi.set(api, t);
|
|
157
|
+
return t;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const results: Array<{ record: OrphanRecord; status: number | null; ok: boolean; error?: string }> = [];
|
|
161
|
+
for (const r of retriable) {
|
|
162
|
+
let baseUrl = baseUrlByApi.get(r.api);
|
|
163
|
+
if (!baseUrl) {
|
|
164
|
+
try {
|
|
165
|
+
const env = await loadEnvironment(undefined, `apis/${r.api}`);
|
|
166
|
+
baseUrl = env["base_url"];
|
|
167
|
+
} catch { /* fall through */ }
|
|
168
|
+
}
|
|
169
|
+
if (!baseUrl) {
|
|
170
|
+
results.push({ record: r, status: null, ok: false, error: `base_url missing — set ZOND_BASE_URL or apis/${r.api}/.env.yaml` });
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
baseUrlByApi.set(r.api, baseUrl);
|
|
174
|
+
|
|
175
|
+
const url = `${baseUrl.replace(/\/+$/, "")}${encodeOrphanPath(r.deletePath)}`;
|
|
176
|
+
try {
|
|
177
|
+
const resp = await executeRequest(
|
|
178
|
+
{ method: "DELETE", url, headers: {} },
|
|
179
|
+
{ timeout: await timeoutFor(r.api), retries: 0 },
|
|
180
|
+
);
|
|
181
|
+
// 404 = already gone → success. 2xx = just deleted → success.
|
|
182
|
+
const ok = resp.status === 404 || (resp.status >= 200 && resp.status < 300);
|
|
183
|
+
results.push({ record: r, status: resp.status, ok });
|
|
184
|
+
if (ok) await markRemoved(r);
|
|
185
|
+
} catch (err) {
|
|
186
|
+
results.push({ record: r, status: null, ok: false, error: (err as Error).message });
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const removed = results.filter(r => r.ok).length;
|
|
191
|
+
const failed = results.length - removed;
|
|
192
|
+
|
|
193
|
+
if (opts.json) {
|
|
194
|
+
printJson(jsonOk("cleanup", {
|
|
195
|
+
retried: results.length,
|
|
196
|
+
removed,
|
|
197
|
+
failed,
|
|
198
|
+
items: results.map(r => ({
|
|
199
|
+
api: r.record.api,
|
|
200
|
+
runId: r.record.runId,
|
|
201
|
+
method: r.record.method,
|
|
202
|
+
path: r.record.path,
|
|
203
|
+
id: r.record.id,
|
|
204
|
+
deletePath: r.record.deletePath,
|
|
205
|
+
status: r.status,
|
|
206
|
+
ok: r.ok,
|
|
207
|
+
error: r.error ?? null,
|
|
208
|
+
})),
|
|
209
|
+
manual_cleanup_required: manualOnly.map(r => ({
|
|
210
|
+
api: r.api,
|
|
211
|
+
runId: r.runId,
|
|
212
|
+
method: r.method,
|
|
213
|
+
path: r.path,
|
|
214
|
+
id: r.id,
|
|
215
|
+
reason: r.lastCleanupError ?? "no DELETE counterpart",
|
|
216
|
+
})),
|
|
217
|
+
}));
|
|
218
|
+
} else {
|
|
219
|
+
if (removed > 0) printSuccess(`${removed} orphan(s) cleaned up.`);
|
|
220
|
+
if (failed > 0) {
|
|
221
|
+
printWarning(`${failed} orphan(s) still alive:`);
|
|
222
|
+
for (const r of results) {
|
|
223
|
+
if (r.ok) continue;
|
|
224
|
+
const tail = r.status != null ? `→ ${r.status}` : (r.error ? `→ err: ${r.error}` : "");
|
|
225
|
+
process.stderr.write(` ${r.record.method} ${r.record.path} (id=${r.record.id}); DELETE ${r.record.deletePath} ${tail}\n`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (manualOnly.length > 0) {
|
|
229
|
+
printWarning(`${manualOnly.length} resource(s) need manual cleanup (no DELETE plan):`);
|
|
230
|
+
for (const r of manualOnly) {
|
|
231
|
+
process.stderr.write(` ${r.method} ${r.path}${r.id ? ` (id=${r.id})` : ""} — ${r.lastCleanupError ?? "no DELETE counterpart"}\n`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Manual-only orphans count as "still alive" for exit-code purposes —
|
|
237
|
+
// CI must fail loudly when probes leave un-deletable state behind.
|
|
238
|
+
return failed > 0 || manualOnly.length > 0 ? 1 : 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function registerCleanup(program: Command): void {
|
|
242
|
+
program
|
|
243
|
+
.command("cleanup")
|
|
244
|
+
.description("Retry probe-leftover work. Currently only --orphans (TASK-278) — re-issues DELETE for resources captured in ~/.zond/orphans/.")
|
|
245
|
+
.option("--orphans", "Retry DELETE for resources in the orphan tracker")
|
|
246
|
+
.option("--api <name>", "Limit to a single API (matches the orphan-tracker subdirectory; defaults to the current API)")
|
|
247
|
+
.option("--all-apis", "Include orphans from every API in the tracker (disables the default current-api scoping)")
|
|
248
|
+
.option("--run <id>", "Limit to a single probe run id")
|
|
249
|
+
.option("--dry-run", "Print the plan without sending DELETEs")
|
|
250
|
+
.option("--timeout <ms>", "Per-request timeout in ms (overrides .env.yaml `timeoutMs` and zond.config.yml `defaults.timeout_ms`; default 30000)")
|
|
251
|
+
.action(async (opts, cmd: Command) => {
|
|
252
|
+
process.exitCode = await cleanupCommand({
|
|
253
|
+
orphans: opts.orphans === true,
|
|
254
|
+
api: typeof opts.api === "string" ? opts.api : undefined,
|
|
255
|
+
allApis: opts.allApis === true,
|
|
256
|
+
runId: typeof opts.run === "string" ? opts.run : undefined,
|
|
257
|
+
dryRun: opts.dryRun === true,
|
|
258
|
+
timeoutMs: typeof opts.timeout === "string" ? Number(opts.timeout) : undefined,
|
|
259
|
+
json: globalJson(cmd),
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
}
|