@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
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { join } from "path";
|
|
2
|
-
import { mkdir } from "fs/promises";
|
|
3
|
-
import {
|
|
4
|
-
readOpenApiSpec,
|
|
5
|
-
extractEndpoints,
|
|
6
|
-
extractSecuritySchemes,
|
|
7
|
-
serializeSuite,
|
|
8
|
-
} from "../../core/generator/index.ts";
|
|
9
|
-
import { filterByTag } from "../../core/generator/chunker.ts";
|
|
10
|
-
import { generateMethodProbes } from "../../core/probe/method-probe.ts";
|
|
11
|
-
import { printError, printSuccess } from "../output.ts";
|
|
12
|
-
import { jsonOk, jsonError, printJson } from "../json-envelope.ts";
|
|
13
|
-
|
|
14
|
-
export interface ProbeMethodsOptions {
|
|
15
|
-
specPath: string;
|
|
16
|
-
output: string;
|
|
17
|
-
tag?: string;
|
|
18
|
-
json?: boolean;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export async function probeMethodsCommand(
|
|
22
|
-
options: ProbeMethodsOptions,
|
|
23
|
-
): Promise<number> {
|
|
24
|
-
try {
|
|
25
|
-
const doc = await readOpenApiSpec(options.specPath);
|
|
26
|
-
let endpoints = extractEndpoints(doc);
|
|
27
|
-
const securitySchemes = extractSecuritySchemes(doc);
|
|
28
|
-
|
|
29
|
-
if (options.tag) endpoints = filterByTag(endpoints, options.tag);
|
|
30
|
-
|
|
31
|
-
if (endpoints.length === 0) {
|
|
32
|
-
const message = "No endpoints to probe.";
|
|
33
|
-
if (options.json) {
|
|
34
|
-
printJson(jsonOk("probe-methods", { files: [], message }));
|
|
35
|
-
} else {
|
|
36
|
-
console.log(message);
|
|
37
|
-
}
|
|
38
|
-
return 0;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const result = generateMethodProbes({
|
|
42
|
-
endpoints,
|
|
43
|
-
securitySchemes,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
if (result.suites.length === 0) {
|
|
47
|
-
const message =
|
|
48
|
-
"Every path declares all of GET/POST/PUT/PATCH/DELETE — nothing to probe.";
|
|
49
|
-
if (options.json) {
|
|
50
|
-
printJson(
|
|
51
|
-
jsonOk("probe-methods", {
|
|
52
|
-
files: [],
|
|
53
|
-
probedPaths: 0,
|
|
54
|
-
skippedPaths: result.skippedPaths,
|
|
55
|
-
totalProbes: 0,
|
|
56
|
-
message,
|
|
57
|
-
}),
|
|
58
|
-
);
|
|
59
|
-
} else {
|
|
60
|
-
console.log(message);
|
|
61
|
-
}
|
|
62
|
-
return 0;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
await mkdir(options.output, { recursive: true });
|
|
66
|
-
|
|
67
|
-
const created: Array<{ file: string; suite: string; tests: number }> = [];
|
|
68
|
-
for (const suite of result.suites) {
|
|
69
|
-
const fileName = `${suite.fileStem ?? suite.name}.yaml`;
|
|
70
|
-
const filePath = join(options.output, fileName);
|
|
71
|
-
await Bun.write(filePath, serializeSuite(suite));
|
|
72
|
-
created.push({ file: filePath, suite: suite.name, tests: suite.tests.length });
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (options.json) {
|
|
76
|
-
printJson(
|
|
77
|
-
jsonOk("probe-methods", {
|
|
78
|
-
files: created,
|
|
79
|
-
probedPaths: result.probedPaths,
|
|
80
|
-
skippedPaths: result.skippedPaths,
|
|
81
|
-
totalProbes: result.totalProbes,
|
|
82
|
-
outputDir: options.output,
|
|
83
|
-
}),
|
|
84
|
-
);
|
|
85
|
-
} else {
|
|
86
|
-
printSuccess(
|
|
87
|
-
`Generated ${result.suites.length} method-probe suite(s) with ${result.totalProbes} probe(s) in ${options.output}`,
|
|
88
|
-
);
|
|
89
|
-
console.log(
|
|
90
|
-
` ${result.probedPaths} path(s) probed, ${result.skippedPaths} skipped (full method coverage)`,
|
|
91
|
-
);
|
|
92
|
-
console.log("");
|
|
93
|
-
console.log("Next steps:");
|
|
94
|
-
console.log(` zond run ${options.output} --report json # any 5xx or 2xx → bug candidate`);
|
|
95
|
-
console.log(` zond db diagnose <run-id> # inspect failures`);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return 0;
|
|
99
|
-
} catch (err) {
|
|
100
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
101
|
-
if (options.json) {
|
|
102
|
-
printJson(jsonError("probe-methods", [message]));
|
|
103
|
-
} else {
|
|
104
|
-
printError(message);
|
|
105
|
-
}
|
|
106
|
-
return 2;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { join } from "path";
|
|
2
|
-
import { mkdir } from "fs/promises";
|
|
3
|
-
import {
|
|
4
|
-
readOpenApiSpec,
|
|
5
|
-
extractEndpoints,
|
|
6
|
-
extractSecuritySchemes,
|
|
7
|
-
serializeSuite,
|
|
8
|
-
} from "../../core/generator/index.ts";
|
|
9
|
-
import { filterByTag, collectTags } from "../../core/generator/chunker.ts";
|
|
10
|
-
import { generateNegativeProbes } from "../../core/probe/negative-probe.ts";
|
|
11
|
-
import { printError, printSuccess, printWarning } from "../output.ts";
|
|
12
|
-
import { jsonOk, jsonError, printJson } from "../json-envelope.ts";
|
|
13
|
-
|
|
14
|
-
export interface ProbeValidationOptions {
|
|
15
|
-
specPath: string;
|
|
16
|
-
output: string;
|
|
17
|
-
tag?: string;
|
|
18
|
-
maxPerEndpoint?: number;
|
|
19
|
-
noCleanup?: boolean;
|
|
20
|
-
json?: boolean;
|
|
21
|
-
listTags?: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function probeValidationCommand(
|
|
25
|
-
options: ProbeValidationOptions,
|
|
26
|
-
): Promise<number> {
|
|
27
|
-
try {
|
|
28
|
-
const doc = await readOpenApiSpec(options.specPath);
|
|
29
|
-
const allEndpoints = extractEndpoints(doc);
|
|
30
|
-
const securitySchemes = extractSecuritySchemes(doc);
|
|
31
|
-
|
|
32
|
-
if (options.listTags) {
|
|
33
|
-
const tags = collectTags(allEndpoints);
|
|
34
|
-
if (options.json) {
|
|
35
|
-
printJson(jsonOk("probe-validation", { tags }));
|
|
36
|
-
} else {
|
|
37
|
-
if (tags.length === 0) {
|
|
38
|
-
console.log("No tags found in spec.");
|
|
39
|
-
} else {
|
|
40
|
-
console.log("Available tags:");
|
|
41
|
-
for (const t of tags) console.log(` - ${t}`);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return 0;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
let endpoints = allEndpoints;
|
|
48
|
-
if (options.tag) {
|
|
49
|
-
endpoints = filterByTag(allEndpoints, options.tag);
|
|
50
|
-
if (endpoints.length === 0) {
|
|
51
|
-
const available = collectTags(allEndpoints);
|
|
52
|
-
const msg = `No endpoints tagged "${options.tag}". Available tags: ${available.length ? available.join(", ") : "(none)"}`;
|
|
53
|
-
if (options.json) {
|
|
54
|
-
printJson(jsonError("probe-validation", [msg]));
|
|
55
|
-
} else {
|
|
56
|
-
printWarning(msg);
|
|
57
|
-
}
|
|
58
|
-
return 2;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
if (endpoints.length === 0) {
|
|
63
|
-
const message = "No endpoints to probe.";
|
|
64
|
-
if (options.json) {
|
|
65
|
-
printJson(jsonOk("probe-validation", { files: [], message }));
|
|
66
|
-
} else {
|
|
67
|
-
console.log(message);
|
|
68
|
-
}
|
|
69
|
-
return 0;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const result = generateNegativeProbes({
|
|
73
|
-
endpoints,
|
|
74
|
-
securitySchemes,
|
|
75
|
-
maxProbesPerEndpoint: options.maxPerEndpoint,
|
|
76
|
-
noCleanup: options.noCleanup,
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
await mkdir(options.output, { recursive: true });
|
|
80
|
-
|
|
81
|
-
const created: Array<{ file: string; suite: string; tests: number }> = [];
|
|
82
|
-
for (const suite of result.suites) {
|
|
83
|
-
const fileName = `${suite.fileStem ?? suite.name}.yaml`;
|
|
84
|
-
const filePath = join(options.output, fileName);
|
|
85
|
-
await Bun.write(filePath, serializeSuite(suite));
|
|
86
|
-
created.push({ file: filePath, suite: suite.name, tests: suite.tests.length });
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (options.json) {
|
|
90
|
-
printJson(
|
|
91
|
-
jsonOk("probe-validation", {
|
|
92
|
-
files: created,
|
|
93
|
-
probedEndpoints: result.probedEndpoints,
|
|
94
|
-
skippedEndpoints: result.skippedEndpoints,
|
|
95
|
-
totalProbes: result.totalProbes,
|
|
96
|
-
outputDir: options.output,
|
|
97
|
-
warnings: result.warnings,
|
|
98
|
-
}),
|
|
99
|
-
);
|
|
100
|
-
} else {
|
|
101
|
-
printSuccess(
|
|
102
|
-
`Generated ${result.suites.length} probe suite(s) with ${result.totalProbes} probe(s) in ${options.output}`,
|
|
103
|
-
);
|
|
104
|
-
console.log(
|
|
105
|
-
` ${result.probedEndpoints} endpoint(s) probed, ${result.skippedEndpoints} skipped (no probable surface)`,
|
|
106
|
-
);
|
|
107
|
-
for (const w of result.warnings) printWarning(w);
|
|
108
|
-
console.log("");
|
|
109
|
-
console.log("Next steps:");
|
|
110
|
-
console.log(` zond run ${options.output} --report json # any 5xx → bug candidate`);
|
|
111
|
-
console.log(` zond db diagnose <run-id> # inspect failures`);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return 0;
|
|
115
|
-
} catch (err) {
|
|
116
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
117
|
-
if (options.json) {
|
|
118
|
-
printJson(jsonError("probe-validation", [message]));
|
|
119
|
-
} else {
|
|
120
|
-
printError(message);
|
|
121
|
-
}
|
|
122
|
-
return 2;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { startServer } from "../../web/server.ts";
|
|
2
|
-
import { printError } from "../output.ts";
|
|
3
|
-
|
|
4
|
-
export interface ServeOptions {
|
|
5
|
-
port?: number;
|
|
6
|
-
host?: string;
|
|
7
|
-
dbPath?: string;
|
|
8
|
-
watch?: boolean;
|
|
9
|
-
open?: boolean;
|
|
10
|
-
/** When true, kill any existing process holding `port` before binding (DANGEROUS). */
|
|
11
|
-
killExisting?: boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** Range scanned when auto-picking a free port (only when --port is not set). */
|
|
15
|
-
const PORT_SCAN_LENGTH = 11; // 8080..8090 inclusive
|
|
16
|
-
|
|
17
|
-
/** Kill any existing process listening on the given port (Windows + Unix). */
|
|
18
|
-
async function killPortHolder(port: number): Promise<void> {
|
|
19
|
-
const isWin = process.platform === "win32";
|
|
20
|
-
try {
|
|
21
|
-
if (isWin) {
|
|
22
|
-
const find = Bun.spawn(["powershell", "-NoProfile", "-Command",
|
|
23
|
-
`(Get-NetTCPConnection -LocalPort ${port} -ErrorAction SilentlyContinue).OwningProcess`], {
|
|
24
|
-
stdout: "pipe", stderr: "ignore",
|
|
25
|
-
});
|
|
26
|
-
const out = await new Response(find.stdout).text();
|
|
27
|
-
const pids = [...new Set(out.trim().split(/\s+/).filter(s => /^\d+$/.test(s) && s !== "0"))];
|
|
28
|
-
for (const pid of pids) {
|
|
29
|
-
Bun.spawn(["powershell", "-NoProfile", "-Command",
|
|
30
|
-
`Stop-Process -Id ${pid} -Force -ErrorAction SilentlyContinue`], {
|
|
31
|
-
stdout: "ignore", stderr: "ignore",
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
if (pids.length > 0) await Bun.sleep(500);
|
|
35
|
-
} else {
|
|
36
|
-
const find = Bun.spawn(["lsof", "-ti", `:${port}`], {
|
|
37
|
-
stdout: "pipe", stderr: "ignore",
|
|
38
|
-
});
|
|
39
|
-
const out = await new Response(find.stdout).text();
|
|
40
|
-
const pids = out.trim().split(/\s+/).filter(s => /^\d+$/.test(s));
|
|
41
|
-
for (const pid of pids) {
|
|
42
|
-
Bun.spawn(["kill", "-9", pid], { stdout: "ignore", stderr: "ignore" });
|
|
43
|
-
}
|
|
44
|
-
if (pids.length > 0) await Bun.sleep(300);
|
|
45
|
-
}
|
|
46
|
-
} catch {
|
|
47
|
-
// Best effort — if we can't kill, the bind below will fail with port-in-use
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** Returns true if `port` is free on `host` (best-effort: tries to bind & immediately stops). */
|
|
52
|
-
async function isPortFree(port: number, host: string): Promise<boolean> {
|
|
53
|
-
try {
|
|
54
|
-
const srv = Bun.serve({ port, hostname: host, fetch: () => new Response() });
|
|
55
|
-
srv.stop(true);
|
|
56
|
-
return true;
|
|
57
|
-
} catch {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Scans `[start, start+count)` and returns the first free port, or null. */
|
|
63
|
-
async function pickAvailablePort(start: number, count: number, host: string): Promise<number | null> {
|
|
64
|
-
for (let p = start; p < start + count; p++) {
|
|
65
|
-
if (await isPortFree(p, host)) return p;
|
|
66
|
-
}
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export async function serveCommand(options: ServeOptions): Promise<number> {
|
|
71
|
-
const requested = options.port ?? 8080;
|
|
72
|
-
const host = options.host ?? "0.0.0.0";
|
|
73
|
-
|
|
74
|
-
let port: number;
|
|
75
|
-
if (options.killExisting) {
|
|
76
|
-
await killPortHolder(requested);
|
|
77
|
-
port = requested;
|
|
78
|
-
} else {
|
|
79
|
-
const picked = await pickAvailablePort(requested, PORT_SCAN_LENGTH, host);
|
|
80
|
-
if (picked === null) {
|
|
81
|
-
printError(
|
|
82
|
-
`All ports ${requested}..${requested + PORT_SCAN_LENGTH - 1} are in use. ` +
|
|
83
|
-
`Use --port <n> to pick another, or --kill-existing to free :${requested}.`,
|
|
84
|
-
);
|
|
85
|
-
return 1;
|
|
86
|
-
}
|
|
87
|
-
if (picked !== requested) {
|
|
88
|
-
process.stderr.write(`[zond] port ${requested} busy, using ${picked}\n`);
|
|
89
|
-
}
|
|
90
|
-
port = picked;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
await startServer({
|
|
94
|
-
port,
|
|
95
|
-
host: options.host,
|
|
96
|
-
dbPath: options.dbPath,
|
|
97
|
-
dev: options.watch,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
if (options.open) {
|
|
101
|
-
const openHost = host === "0.0.0.0" ? "localhost" : host;
|
|
102
|
-
const url = `http://${openHost}:${port}`;
|
|
103
|
-
try {
|
|
104
|
-
const cmd = process.platform === "win32" ? ["cmd", "/c", "start", url]
|
|
105
|
-
: process.platform === "darwin" ? ["open", url]
|
|
106
|
-
: ["xdg-open", url];
|
|
107
|
-
Bun.spawn(cmd, { stdout: "ignore", stderr: "ignore" });
|
|
108
|
-
} catch {
|
|
109
|
-
// Best effort
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return 0;
|
|
114
|
-
}
|
package/src/cli/commands/sync.ts
DELETED
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import { join } from "path";
|
|
2
|
-
import { mkdir } from "fs/promises";
|
|
3
|
-
import {
|
|
4
|
-
readOpenApiSpec,
|
|
5
|
-
extractEndpoints,
|
|
6
|
-
extractSecuritySchemes,
|
|
7
|
-
serializeSuite,
|
|
8
|
-
buildCatalog,
|
|
9
|
-
serializeCatalog,
|
|
10
|
-
} from "../../core/generator/index.ts";
|
|
11
|
-
import { generateSuites } from "../../core/generator/suite-generator.ts";
|
|
12
|
-
import { filterByTag } from "../../core/generator/chunker.ts";
|
|
13
|
-
import { readMeta, writeMeta, hashSpec, buildFileMeta } from "../../core/meta/meta-store.ts";
|
|
14
|
-
import { diffEndpoints } from "../../core/sync/spec-differ.ts";
|
|
15
|
-
import { decycleSchema } from "../../core/generator/schema-utils.ts";
|
|
16
|
-
import { printError, printSuccess, printWarning } from "../output.ts";
|
|
17
|
-
import { jsonOk, jsonError, printJson } from "../json-envelope.ts";
|
|
18
|
-
import { version as ZOND_VERSION } from "../../../package.json";
|
|
19
|
-
import { getDb } from "../../db/schema.ts";
|
|
20
|
-
import { findCollectionByTestPath, updateCollection } from "../../db/queries.ts";
|
|
21
|
-
|
|
22
|
-
export interface SyncOptions {
|
|
23
|
-
specPath: string;
|
|
24
|
-
testsDir: string;
|
|
25
|
-
dryRun?: boolean;
|
|
26
|
-
tag?: string;
|
|
27
|
-
json?: boolean;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export async function syncCommand(options: SyncOptions): Promise<number> {
|
|
31
|
-
try {
|
|
32
|
-
// Load existing metadata
|
|
33
|
-
const meta = await readMeta(options.testsDir);
|
|
34
|
-
if (!meta) {
|
|
35
|
-
const msg =
|
|
36
|
-
"No .zond-meta.json found. Run `zond generate <spec> --output <dir>` first to initialize metadata.";
|
|
37
|
-
if (options.json) {
|
|
38
|
-
printJson(jsonError("sync", [msg]));
|
|
39
|
-
} else {
|
|
40
|
-
printError(msg);
|
|
41
|
-
}
|
|
42
|
-
return 2;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Load current spec
|
|
46
|
-
const doc = await readOpenApiSpec(options.specPath);
|
|
47
|
-
const specContent = JSON.stringify(decycleSchema(doc));
|
|
48
|
-
const currentHash = hashSpec(specContent);
|
|
49
|
-
|
|
50
|
-
if (currentHash === meta.specHash) {
|
|
51
|
-
const msg = "Spec unchanged — nothing to sync.";
|
|
52
|
-
if (options.json) {
|
|
53
|
-
printJson(jsonOk("sync", { newEndpoints: [], generatedFiles: [], removedKeys: [], specChanged: false }, [msg]));
|
|
54
|
-
} else {
|
|
55
|
-
console.log(msg);
|
|
56
|
-
}
|
|
57
|
-
return 0;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Extract current endpoints
|
|
61
|
-
let currentEndpoints = extractEndpoints(doc);
|
|
62
|
-
const securitySchemes = extractSecuritySchemes(doc);
|
|
63
|
-
|
|
64
|
-
if (options.tag) {
|
|
65
|
-
currentEndpoints = filterByTag(currentEndpoints, options.tag);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Collect all previously known endpoint keys from meta
|
|
69
|
-
const prevKeys = Object.values(meta.files).flatMap((f) => f.endpoints);
|
|
70
|
-
|
|
71
|
-
// Compute diff
|
|
72
|
-
const { newEndpoints, removedKeys } = diffEndpoints(prevKeys, currentEndpoints);
|
|
73
|
-
|
|
74
|
-
const warnings: string[] = [];
|
|
75
|
-
|
|
76
|
-
if (removedKeys.length > 0) {
|
|
77
|
-
for (const key of removedKeys) {
|
|
78
|
-
warnings.push(`Removed endpoint not deleted from tests (review manually): ${key}`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (newEndpoints.length === 0) {
|
|
83
|
-
// Update catalog even when no new endpoints — spec schema may have changed
|
|
84
|
-
const allEndpoints = extractEndpoints(doc);
|
|
85
|
-
const catalog = buildCatalog({
|
|
86
|
-
endpoints: allEndpoints,
|
|
87
|
-
securitySchemes,
|
|
88
|
-
specSource: options.specPath,
|
|
89
|
-
specHash: currentHash,
|
|
90
|
-
apiName: (doc as any).info?.title,
|
|
91
|
-
apiVersion: (doc as any).info?.version,
|
|
92
|
-
baseUrl: (doc as any).servers?.[0]?.url,
|
|
93
|
-
});
|
|
94
|
-
await Bun.write(join(options.testsDir, ".api-catalog.yaml"), serializeCatalog(catalog));
|
|
95
|
-
|
|
96
|
-
const msg = "Spec changed (hash differs) but no new endpoints detected. Existing tests may need manual review.";
|
|
97
|
-
warnings.push(msg);
|
|
98
|
-
if (options.json) {
|
|
99
|
-
printJson(jsonOk("sync", {
|
|
100
|
-
newEndpoints: [],
|
|
101
|
-
removedKeys,
|
|
102
|
-
generatedFiles: [],
|
|
103
|
-
specChanged: true,
|
|
104
|
-
}, warnings));
|
|
105
|
-
} else {
|
|
106
|
-
console.log(msg);
|
|
107
|
-
for (const w of warnings) {
|
|
108
|
-
printWarning(w);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return 0;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Generate suites for new endpoints only
|
|
115
|
-
const suites = generateSuites({ endpoints: newEndpoints, securitySchemes });
|
|
116
|
-
|
|
117
|
-
if (options.dryRun) {
|
|
118
|
-
const newEndpointKeys = newEndpoints.map((ep) => `${ep.method.toUpperCase()} ${ep.path}`);
|
|
119
|
-
const plannedFiles = suites.map((s) => ({
|
|
120
|
-
file: `${s.fileStem ?? s.name}.yaml`,
|
|
121
|
-
suite: s.name,
|
|
122
|
-
tests: s.tests.length,
|
|
123
|
-
}));
|
|
124
|
-
|
|
125
|
-
if (options.json) {
|
|
126
|
-
printJson(jsonOk("sync", {
|
|
127
|
-
dryRun: true,
|
|
128
|
-
newEndpoints: newEndpointKeys,
|
|
129
|
-
removedKeys,
|
|
130
|
-
plannedFiles,
|
|
131
|
-
specChanged: true,
|
|
132
|
-
}, warnings));
|
|
133
|
-
} else {
|
|
134
|
-
console.log(`[dry-run] Detected ${newEndpoints.length} new endpoint(s):`);
|
|
135
|
-
for (const ep of newEndpoints) {
|
|
136
|
-
console.log(` + ${ep.method.toUpperCase()} ${ep.path}`);
|
|
137
|
-
}
|
|
138
|
-
console.log(`\nWould generate ${suites.length} new suite file(s):`);
|
|
139
|
-
for (const f of plannedFiles) {
|
|
140
|
-
console.log(` ${f.file} (${f.tests} tests)`);
|
|
141
|
-
}
|
|
142
|
-
if (removedKeys.length > 0) {
|
|
143
|
-
console.log("\nRemoved endpoints (not deleted — review tests):");
|
|
144
|
-
for (const key of removedKeys) {
|
|
145
|
-
console.log(` - ${key}`);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
console.log("\nNo files written (dry-run).");
|
|
149
|
-
}
|
|
150
|
-
return 0;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Write new files (skip if file already exists)
|
|
154
|
-
await mkdir(options.testsDir, { recursive: true });
|
|
155
|
-
|
|
156
|
-
const generatedFiles: Array<{ file: string; suite: string; tests: number }> = [];
|
|
157
|
-
const skippedFiles: string[] = [];
|
|
158
|
-
const updatedMetaFiles: Record<string, import("../../core/meta/types.ts").FileMeta> = {};
|
|
159
|
-
|
|
160
|
-
for (const suite of suites) {
|
|
161
|
-
const fileName = `${suite.fileStem ?? suite.name}.yaml`;
|
|
162
|
-
const filePath = join(options.testsDir, fileName);
|
|
163
|
-
const existing = Bun.file(filePath);
|
|
164
|
-
|
|
165
|
-
if (await existing.exists()) {
|
|
166
|
-
skippedFiles.push(fileName);
|
|
167
|
-
warnings.push(`Skipped ${fileName} (already exists — add new endpoints manually)`);
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const yaml = serializeSuite(suite);
|
|
172
|
-
await Bun.write(filePath, yaml);
|
|
173
|
-
generatedFiles.push({ file: filePath, suite: suite.name, tests: suite.tests.length });
|
|
174
|
-
updatedMetaFiles[fileName] = buildFileMeta(suite, ZOND_VERSION);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Update metadata: merge new file entries, update hash and timestamp
|
|
178
|
-
await writeMeta(options.testsDir, {
|
|
179
|
-
zondVersion: ZOND_VERSION,
|
|
180
|
-
lastSyncedAt: new Date().toISOString(),
|
|
181
|
-
specHash: currentHash,
|
|
182
|
-
files: { ...meta.files, ...updatedMetaFiles },
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
// Update .api-catalog.yaml with current spec state
|
|
186
|
-
const allEndpoints = extractEndpoints(doc);
|
|
187
|
-
const catalog = buildCatalog({
|
|
188
|
-
endpoints: allEndpoints,
|
|
189
|
-
securitySchemes,
|
|
190
|
-
specSource: options.specPath,
|
|
191
|
-
specHash: currentHash,
|
|
192
|
-
apiName: (doc as any).info?.title,
|
|
193
|
-
apiVersion: (doc as any).info?.version,
|
|
194
|
-
baseUrl: (doc as any).servers?.[0]?.url,
|
|
195
|
-
});
|
|
196
|
-
await Bun.write(join(options.testsDir, ".api-catalog.yaml"), serializeCatalog(catalog));
|
|
197
|
-
|
|
198
|
-
// Sync DB collection if one is registered for this tests directory
|
|
199
|
-
try {
|
|
200
|
-
getDb();
|
|
201
|
-
const collection = findCollectionByTestPath(options.testsDir);
|
|
202
|
-
if (collection && collection.openapi_spec !== options.specPath) {
|
|
203
|
-
updateCollection(collection.id, { openapi_spec: options.specPath });
|
|
204
|
-
warnings.push(`Updated collection '${collection.name}' spec reference: ${collection.openapi_spec ?? "(none)"} → ${options.specPath}`);
|
|
205
|
-
}
|
|
206
|
-
} catch {
|
|
207
|
-
// DB unavailable (e.g. no zond.db yet) — not a fatal error for sync
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
const newEndpointKeys = newEndpoints.map((ep) => `${ep.method.toUpperCase()} ${ep.path}`);
|
|
211
|
-
|
|
212
|
-
if (options.json) {
|
|
213
|
-
printJson(jsonOk("sync", {
|
|
214
|
-
newEndpoints: newEndpointKeys,
|
|
215
|
-
removedKeys,
|
|
216
|
-
generatedFiles,
|
|
217
|
-
skippedFiles,
|
|
218
|
-
specChanged: true,
|
|
219
|
-
}, warnings));
|
|
220
|
-
} else {
|
|
221
|
-
console.log(`Spec changed. Detected ${newEndpoints.length} new endpoint(s):`);
|
|
222
|
-
for (const ep of newEndpoints) {
|
|
223
|
-
console.log(` + ${ep.method.toUpperCase()} ${ep.path}`);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (generatedFiles.length > 0) {
|
|
227
|
-
console.log(`\nGenerated ${generatedFiles.length} new suite file(s):`);
|
|
228
|
-
for (const f of generatedFiles) {
|
|
229
|
-
console.log(` ${f.file} (${f.tests} tests)`);
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (skippedFiles.length > 0) {
|
|
234
|
-
console.log("\nSkipped (file exists, review manually):");
|
|
235
|
-
for (const f of skippedFiles) {
|
|
236
|
-
console.log(` ${f}`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (removedKeys.length > 0) {
|
|
241
|
-
console.log("\nRemoved endpoints (not deleted — review tests):");
|
|
242
|
-
for (const key of removedKeys) {
|
|
243
|
-
console.log(` - ${key}`);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
if (generatedFiles.length > 0) {
|
|
248
|
-
printSuccess(`\nSync complete. ${generatedFiles.length} file(s) written.`);
|
|
249
|
-
} else {
|
|
250
|
-
printWarning("No new files written — all target files already exist.");
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
for (const w of warnings) {
|
|
254
|
-
printWarning(w);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return 0;
|
|
259
|
-
} catch (err) {
|
|
260
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
261
|
-
if (options.json) {
|
|
262
|
-
printJson(jsonError("sync", [message]));
|
|
263
|
-
} else {
|
|
264
|
-
printError(message);
|
|
265
|
-
}
|
|
266
|
-
return 2;
|
|
267
|
-
}
|
|
268
|
-
}
|