@stsepelin/checktrail 0.1.0-alpha.1
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/CONTRIBUTING.md +42 -0
- package/LICENSE +21 -0
- package/README.md +355 -0
- package/SECURITY.md +46 -0
- package/dist/src/actionlint-evidence.d.ts +2 -0
- package/dist/src/actionlint-evidence.js +166 -0
- package/dist/src/actionlint-inputs.d.ts +5 -0
- package/dist/src/actionlint-inputs.js +123 -0
- package/dist/src/actionlint-runner.d.ts +1 -0
- package/dist/src/actionlint-runner.js +122 -0
- package/dist/src/actionlint.d.ts +19 -0
- package/dist/src/actionlint.js +80 -0
- package/dist/src/adapters.d.ts +49 -0
- package/dist/src/adapters.js +430 -0
- package/dist/src/architecture.d.ts +107 -0
- package/dist/src/architecture.js +230 -0
- package/dist/src/clang-evidence.d.ts +2 -0
- package/dist/src/clang-evidence.js +194 -0
- package/dist/src/clang-protocol.d.ts +3 -0
- package/dist/src/clang-protocol.js +25 -0
- package/dist/src/clang-runner.d.ts +1 -0
- package/dist/src/clang-runner.js +159 -0
- package/dist/src/clang.d.ts +44 -0
- package/dist/src/clang.js +197 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +338 -0
- package/dist/src/config.d.ts +33 -0
- package/dist/src/config.js +140 -0
- package/dist/src/contract-schema.d.ts +85 -0
- package/dist/src/contract-schema.js +56 -0
- package/dist/src/contract-worker.d.ts +1 -0
- package/dist/src/contract-worker.js +123 -0
- package/dist/src/contracts.d.ts +22 -0
- package/dist/src/contracts.js +123 -0
- package/dist/src/django-evidence.d.ts +2 -0
- package/dist/src/django-evidence.js +100 -0
- package/dist/src/django-runner.d.ts +1 -0
- package/dist/src/django-runner.js +80 -0
- package/dist/src/django.d.ts +9 -0
- package/dist/src/django.js +50 -0
- package/dist/src/dotnet-compiler.d.ts +1 -0
- package/dist/src/dotnet-compiler.js +87 -0
- package/dist/src/dotnet-evidence.d.ts +2 -0
- package/dist/src/dotnet-evidence.js +127 -0
- package/dist/src/dotnet-runner.d.ts +1 -0
- package/dist/src/dotnet-runner.js +167 -0
- package/dist/src/dotnet.d.ts +65 -0
- package/dist/src/dotnet.js +120 -0
- package/dist/src/engine.d.ts +19 -0
- package/dist/src/engine.js +239 -0
- package/dist/src/environment.d.ts +6 -0
- package/dist/src/environment.js +36 -0
- package/dist/src/eslint-evidence.d.ts +2 -0
- package/dist/src/eslint-evidence.js +98 -0
- package/dist/src/eslint-runner.d.ts +1 -0
- package/dist/src/eslint-runner.js +61 -0
- package/dist/src/eslint.d.ts +2 -0
- package/dist/src/eslint.js +40 -0
- package/dist/src/evidence.d.ts +2 -0
- package/dist/src/evidence.js +313 -0
- package/dist/src/external-adapter.d.ts +164 -0
- package/dist/src/external-adapter.js +265 -0
- package/dist/src/external-evidence.d.ts +2 -0
- package/dist/src/external-evidence.js +127 -0
- package/dist/src/external-runner.d.ts +1 -0
- package/dist/src/external-runner.js +122 -0
- package/dist/src/fastapi-evidence.d.ts +2 -0
- package/dist/src/fastapi-evidence.js +126 -0
- package/dist/src/fastapi-runner.d.ts +1 -0
- package/dist/src/fastapi-runner.js +84 -0
- package/dist/src/fastapi.d.ts +10 -0
- package/dist/src/fastapi.js +52 -0
- package/dist/src/fetch-pack.d.ts +16 -0
- package/dist/src/fetch-pack.js +154 -0
- package/dist/src/finding-policy-schema.d.ts +146 -0
- package/dist/src/finding-policy-schema.js +70 -0
- package/dist/src/finding-policy.d.ts +62 -0
- package/dist/src/finding-policy.js +206 -0
- package/dist/src/git-selection.d.ts +3 -0
- package/dist/src/git-selection.js +229 -0
- package/dist/src/go-directives.d.ts +1 -0
- package/dist/src/go-directives.js +32 -0
- package/dist/src/go-scope.d.ts +11 -0
- package/dist/src/go-scope.js +122 -0
- package/dist/src/golangci-evidence.d.ts +2 -0
- package/dist/src/golangci-evidence.js +92 -0
- package/dist/src/golangci-runner.d.ts +1 -0
- package/dist/src/golangci-runner.js +124 -0
- package/dist/src/golangci.d.ts +2 -0
- package/dist/src/golangci.js +35 -0
- package/dist/src/guidance.d.ts +98 -0
- package/dist/src/guidance.js +247 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.js +16 -0
- package/dist/src/inventory.d.ts +4 -0
- package/dist/src/inventory.js +94 -0
- package/dist/src/java-compiler.d.ts +1 -0
- package/dist/src/java-compiler.js +110 -0
- package/dist/src/java-evidence.d.ts +2 -0
- package/dist/src/java-evidence.js +114 -0
- package/dist/src/java-runner.d.ts +1 -0
- package/dist/src/java-runner.js +79 -0
- package/dist/src/java.d.ts +25 -0
- package/dist/src/java.js +102 -0
- package/dist/src/jest-evidence.d.ts +2 -0
- package/dist/src/jest-evidence.js +117 -0
- package/dist/src/jest-runner.d.ts +1 -0
- package/dist/src/jest-runner.js +46 -0
- package/dist/src/jest.d.ts +2 -0
- package/dist/src/jest.js +36 -0
- package/dist/src/junit.d.ts +18 -0
- package/dist/src/junit.js +157 -0
- package/dist/src/laravel-evidence.d.ts +2 -0
- package/dist/src/laravel-evidence.js +175 -0
- package/dist/src/laravel-runner.d.ts +1 -0
- package/dist/src/laravel-runner.js +168 -0
- package/dist/src/laravel.d.ts +8 -0
- package/dist/src/laravel.js +50 -0
- package/dist/src/local-tool.d.ts +1 -0
- package/dist/src/local-tool.js +19 -0
- package/dist/src/mcp.d.ts +14 -0
- package/dist/src/mcp.js +362 -0
- package/dist/src/mutation.d.ts +118 -0
- package/dist/src/mutation.js +370 -0
- package/dist/src/mypy-evidence.d.ts +2 -0
- package/dist/src/mypy-evidence.js +63 -0
- package/dist/src/mypy.d.ts +2 -0
- package/dist/src/mypy.js +59 -0
- package/dist/src/node-reporter.d.ts +1 -0
- package/dist/src/node-reporter.js +28 -0
- package/dist/src/nuxt-evidence.d.ts +2 -0
- package/dist/src/nuxt-evidence.js +160 -0
- package/dist/src/nuxt-protocol.d.ts +52 -0
- package/dist/src/nuxt-protocol.js +29 -0
- package/dist/src/nuxt-runner.d.ts +1 -0
- package/dist/src/nuxt-runner.js +165 -0
- package/dist/src/nuxt.d.ts +24 -0
- package/dist/src/nuxt.js +105 -0
- package/dist/src/output.d.ts +3 -0
- package/dist/src/output.js +48 -0
- package/dist/src/phpstan-evidence.d.ts +2 -0
- package/dist/src/phpstan-evidence.js +81 -0
- package/dist/src/phpstan.d.ts +2 -0
- package/dist/src/phpstan.js +51 -0
- package/dist/src/phpunit-evidence.d.ts +2 -0
- package/dist/src/phpunit-evidence.js +53 -0
- package/dist/src/phpunit.d.ts +2 -0
- package/dist/src/phpunit.js +58 -0
- package/dist/src/pint-evidence.d.ts +2 -0
- package/dist/src/pint-evidence.js +79 -0
- package/dist/src/pint-runner.d.ts +1 -0
- package/dist/src/pint-runner.js +53 -0
- package/dist/src/pint.d.ts +2 -0
- package/dist/src/pint.js +37 -0
- package/dist/src/playwright-evidence.d.ts +2 -0
- package/dist/src/playwright-evidence.js +127 -0
- package/dist/src/playwright-reporter.d.ts +10 -0
- package/dist/src/playwright-reporter.js +37 -0
- package/dist/src/playwright-runner.d.ts +1 -0
- package/dist/src/playwright-runner.js +61 -0
- package/dist/src/playwright.d.ts +2 -0
- package/dist/src/playwright.js +35 -0
- package/dist/src/policy-pack.d.ts +21 -0
- package/dist/src/policy-pack.js +41 -0
- package/dist/src/pytest-evidence.d.ts +2 -0
- package/dist/src/pytest-evidence.js +116 -0
- package/dist/src/pytest-runner.d.ts +1 -0
- package/dist/src/pytest-runner.js +48 -0
- package/dist/src/pytest.d.ts +2 -0
- package/dist/src/pytest.js +25 -0
- package/dist/src/report-validation.d.ts +129 -0
- package/dist/src/report-validation.js +27 -0
- package/dist/src/review.d.ts +270 -0
- package/dist/src/review.js +349 -0
- package/dist/src/ruby.d.ts +2 -0
- package/dist/src/ruby.js +25 -0
- package/dist/src/ruff-evidence.d.ts +2 -0
- package/dist/src/ruff-evidence.js +99 -0
- package/dist/src/ruff.d.ts +2 -0
- package/dist/src/ruff.js +40 -0
- package/dist/src/runner.d.ts +8 -0
- package/dist/src/runner.js +117 -0
- package/dist/src/runtime-inventory.d.ts +112 -0
- package/dist/src/runtime-inventory.js +200 -0
- package/dist/src/rust-dep-info.d.ts +1 -0
- package/dist/src/rust-dep-info.js +46 -0
- package/dist/src/rust-evidence.d.ts +2 -0
- package/dist/src/rust-evidence.js +157 -0
- package/dist/src/rust-runner.d.ts +1 -0
- package/dist/src/rust-runner.js +161 -0
- package/dist/src/rust.d.ts +7 -0
- package/dist/src/rust.js +39 -0
- package/dist/src/sarif.d.ts +86 -0
- package/dist/src/sarif.js +113 -0
- package/dist/src/schemas.d.ts +393 -0
- package/dist/src/schemas.js +222 -0
- package/dist/src/staticcheck-evidence.d.ts +2 -0
- package/dist/src/staticcheck-evidence.js +75 -0
- package/dist/src/swift.d.ts +2 -0
- package/dist/src/swift.js +20 -0
- package/dist/src/task-store.d.ts +37 -0
- package/dist/src/task-store.js +349 -0
- package/dist/src/tool-versions.d.ts +3 -0
- package/dist/src/tool-versions.js +260 -0
- package/dist/src/types.d.ts +157 -0
- package/dist/src/types.js +37 -0
- package/dist/src/typescript-build-evidence.d.ts +2 -0
- package/dist/src/typescript-build-evidence.js +86 -0
- package/dist/src/typescript-build-runner.d.ts +1 -0
- package/dist/src/typescript-build-runner.js +132 -0
- package/dist/src/typescript-build.d.ts +2 -0
- package/dist/src/typescript-build.js +41 -0
- package/dist/src/typescript.d.ts +2 -0
- package/dist/src/typescript.js +54 -0
- package/dist/src/validation-task-worker.d.ts +1 -0
- package/dist/src/validation-task-worker.js +205 -0
- package/dist/src/validation-tasks-protocol.d.ts +69 -0
- package/dist/src/validation-tasks-protocol.js +57 -0
- package/dist/src/validation-tasks.d.ts +11 -0
- package/dist/src/validation-tasks.js +144 -0
- package/dist/src/vitest-evidence.d.ts +2 -0
- package/dist/src/vitest-evidence.js +112 -0
- package/dist/src/vitest-runner.d.ts +1 -0
- package/dist/src/vitest-runner.js +48 -0
- package/dist/src/vitest.d.ts +2 -0
- package/dist/src/vitest.js +36 -0
- package/dist/src/vue-router-capture.d.ts +42 -0
- package/dist/src/vue-router-capture.js +87 -0
- package/dist/src/vue-router-evidence.d.ts +2 -0
- package/dist/src/vue-router-evidence.js +126 -0
- package/dist/src/vue-router-protocol.d.ts +63 -0
- package/dist/src/vue-router-protocol.js +44 -0
- package/dist/src/vue-router-runner.d.ts +1 -0
- package/dist/src/vue-router-runner.js +88 -0
- package/dist/src/vue-router.d.ts +23 -0
- package/dist/src/vue-router.js +108 -0
- package/dist/src/vue-tsc-runner.d.ts +1 -0
- package/dist/src/vue-tsc-runner.js +24 -0
- package/dist/src/workspace.d.ts +7 -0
- package/dist/src/workspace.js +53 -0
- package/docs/ACCEPTANCE.md +56 -0
- package/docs/ACTIONLINT.md +112 -0
- package/docs/ARCHITECTURE-POLICY.md +82 -0
- package/docs/ARCHITECTURE.md +122 -0
- package/docs/CLANG.md +113 -0
- package/docs/CLIENTS.md +109 -0
- package/docs/CONTRACTS.md +73 -0
- package/docs/DEPENDENCIES.md +42 -0
- package/docs/DJANGO.md +70 -0
- package/docs/DOTNET.md +114 -0
- package/docs/ENVIRONMENTS.md +52 -0
- package/docs/ESLINT.md +68 -0
- package/docs/EVALUATION.md +124 -0
- package/docs/EXECUTION.md +250 -0
- package/docs/EXTERNAL-ADAPTERS.md +170 -0
- package/docs/EXTERNAL-EVALUATION.md +155 -0
- package/docs/EXTERNAL-RUFF-EVALUATION.md +85 -0
- package/docs/FASTAPI.md +73 -0
- package/docs/FINDING-POLICY.md +67 -0
- package/docs/GO-RACE.md +19 -0
- package/docs/GO-SCOPE.md +63 -0
- package/docs/GOLANGCI-LINT.md +64 -0
- package/docs/GUIDANCE.md +46 -0
- package/docs/IMPACT-MEASUREMENT.md +97 -0
- package/docs/INSTALLATION.md +102 -0
- package/docs/JAVA.md +79 -0
- package/docs/JEST.md +36 -0
- package/docs/JUNIT.md +31 -0
- package/docs/LANGUAGES.md +113 -0
- package/docs/LARAVEL.md +111 -0
- package/docs/MCP-COMPATIBILITY.md +68 -0
- package/docs/MUTATIONS.md +93 -0
- package/docs/MYPY.md +37 -0
- package/docs/NATIVE-CI.md +96 -0
- package/docs/NUXT.md +123 -0
- package/docs/PACK-DISTRIBUTION.md +98 -0
- package/docs/PERFORMANCE.md +71 -0
- package/docs/PEST.md +36 -0
- package/docs/PHPSTAN.md +48 -0
- package/docs/PHPUNIT.md +32 -0
- package/docs/PINT.md +41 -0
- package/docs/PLAN.md +265 -0
- package/docs/PLAYWRIGHT.md +73 -0
- package/docs/POLICY-PACKS.md +106 -0
- package/docs/PRIOR-WORKFLOW-EVALUATION.md +116 -0
- package/docs/PUBLICATION.md +28 -0
- package/docs/PYTEST.md +51 -0
- package/docs/RELEASE.md +115 -0
- package/docs/RENAMING.md +55 -0
- package/docs/REVIEW-EXCHANGE.md +136 -0
- package/docs/RUBY.md +50 -0
- package/docs/RUFF.md +39 -0
- package/docs/RUNTIME-INVENTORY.md +56 -0
- package/docs/RUST.md +74 -0
- package/docs/SARIF.md +64 -0
- package/docs/STATUS.md +530 -0
- package/docs/SWIFT.md +48 -0
- package/docs/TASK-STORAGE.md +141 -0
- package/docs/TOOL-IDENTITY.md +30 -0
- package/docs/TYPESCRIPT-BUILD.md +55 -0
- package/docs/VALIDATION-TASKS.md +119 -0
- package/docs/VITEST.md +36 -0
- package/docs/VUE-ROUTER.md +130 -0
- package/docs/VUE-TSC.md +26 -0
- package/docs/WORKSPACES.md +81 -0
- package/docs/licenses/nodable-entities-3.0.0.txt +21 -0
- package/docs/measurements/client-claude-before-schema-fix-darwin-arm64-node26.json +60 -0
- package/docs/measurements/client-claude-darwin-arm64-node26.json +55 -0
- package/docs/measurements/client-codex-darwin-arm64-node26.json +175 -0
- package/docs/measurements/evaluation-corpus-repo-verifier.json +332 -0
- package/docs/measurements/evaluation-darwin-arm64-node26.json +1236 -0
- package/docs/measurements/evaluation-linux-arm64-node22.json +1236 -0
- package/docs/measurements/external-eslint-darwin-arm64-node26.json +11866 -0
- package/docs/measurements/external-eslint-linux-arm64-node22.json +11866 -0
- package/docs/measurements/external-eslint-preservation.json +1527 -0
- package/docs/measurements/external-ruff-darwin-arm64-node26.json +1031 -0
- package/docs/measurements/external-ruff-linux-arm64-node22.json +1031 -0
- package/docs/measurements/hosted-ci-52ba415.json +1005 -0
- package/docs/measurements/impact-darwin-arm64-node26.json +6685 -0
- package/docs/measurements/impact-linux-arm64-node22.json +6690 -0
- package/docs/measurements/performance-darwin-arm64-node26.json +845 -0
- package/docs/measurements/performance-linux-arm64-node22.json +845 -0
- package/docs/measurements/required-native-profiles.json +500 -0
- package/package.json +72 -0
- package/packs/actionlint.json +8 -0
- package/packs/cpp.json +8 -0
- package/packs/dotnet.json +8 -0
- package/packs/go.json +7 -0
- package/packs/java.json +8 -0
- package/packs/javascript-node.json +7 -0
- package/packs/nuxt.json +7 -0
- package/packs/php-pest.json +7 -0
- package/packs/php-phpunit.json +7 -0
- package/packs/python-pytest.json +7 -0
- package/packs/ruby.json +7 -0
- package/packs/rust.json +7 -0
- package/packs/swift.json +7 -0
- package/packs/vue-router.json +7 -0
- package/packs/vue.json +11 -0
- package/schemas/actionlint-config.schema.json +19 -0
- package/schemas/architecture-policy.schema.json +51 -0
- package/schemas/architecture-report.schema.json +123 -0
- package/schemas/architecture-summary.schema.json +82 -0
- package/schemas/clang-database.schema.json +23 -0
- package/schemas/config.schema.json +69 -0
- package/schemas/contract-bundle.schema.json +78 -0
- package/schemas/contract-report.schema.json +114 -0
- package/schemas/contract-summary.schema.json +72 -0
- package/schemas/dependency-graph.schema.json +65 -0
- package/schemas/django-config.schema.json +16 -0
- package/schemas/dotnet-config.schema.json +60 -0
- package/schemas/external-manifest.schema.json +97 -0
- package/schemas/external-reference.schema.json +10 -0
- package/schemas/external-request.schema.json +56 -0
- package/schemas/external-result.schema.json +111 -0
- package/schemas/fastapi-config.schema.json +27 -0
- package/schemas/finding-baseline.schema.json +92 -0
- package/schemas/finding-comparison-summary.schema.json +95 -0
- package/schemas/finding-comparison.schema.json +121 -0
- package/schemas/guidance-context.schema.json +34 -0
- package/schemas/guidance-report.schema.json +176 -0
- package/schemas/guidance-summary.schema.json +68 -0
- package/schemas/java-config.schema.json +24 -0
- package/schemas/junit.schema.json +69 -0
- package/schemas/laravel-config.schema.json +11 -0
- package/schemas/mutation-recipe.schema.json +31 -0
- package/schemas/mutation-report.schema.json +238 -0
- package/schemas/mutation-summary.schema.json +98 -0
- package/schemas/nuxt-config.schema.json +51 -0
- package/schemas/plan-summary.schema.json +63 -0
- package/schemas/plan.schema.json +244 -0
- package/schemas/policy-pack.schema.json +41 -0
- package/schemas/report-summary.schema.json +100 -0
- package/schemas/report.schema.json +463 -0
- package/schemas/review-assessment.schema.json +133 -0
- package/schemas/review-context-summary.schema.json +31 -0
- package/schemas/review-context.schema.json +254 -0
- package/schemas/review-receipt-summary.schema.json +104 -0
- package/schemas/review-receipt.schema.json +271 -0
- package/schemas/review-selection.schema.json +30 -0
- package/schemas/runtime-comparison-summary.schema.json +68 -0
- package/schemas/runtime-comparison.schema.json +127 -0
- package/schemas/runtime-inventory.schema.json +99 -0
- package/schemas/vue-router-config.schema.json +67 -0
- package/server.json +47 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { findingBaselineSchema, findingBaselineEntrySchema, findingKeySchema, } from "./finding-policy-schema.js";
|
|
4
|
+
import { validatedReport } from "./report-validation.js";
|
|
5
|
+
import { VERSION } from "./types.js";
|
|
6
|
+
const digest = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
7
|
+
export function projectFindingComparison(result, detailed) {
|
|
8
|
+
if (detailed)
|
|
9
|
+
return result;
|
|
10
|
+
return {
|
|
11
|
+
schemaVersion: result.schemaVersion,
|
|
12
|
+
engineVersion: result.engineVersion,
|
|
13
|
+
provenance: result.provenance,
|
|
14
|
+
outcome: result.outcome,
|
|
15
|
+
validationOutcome: result.validationOutcome,
|
|
16
|
+
reason: result.reason,
|
|
17
|
+
sourceVerified: result.sourceVerified,
|
|
18
|
+
baselineFingerprint: result.baselineFingerprint,
|
|
19
|
+
counts: result.counts,
|
|
20
|
+
limitExceeded: result.limitExceeded,
|
|
21
|
+
unaccountedFailures: result.unaccountedFailures,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const keyId = (key) => digest([
|
|
25
|
+
key.checkId,
|
|
26
|
+
key.project,
|
|
27
|
+
key.ruleId,
|
|
28
|
+
key.file,
|
|
29
|
+
key.line,
|
|
30
|
+
key.level,
|
|
31
|
+
key.messageHash,
|
|
32
|
+
]);
|
|
33
|
+
function baseline(input) {
|
|
34
|
+
const result = findingBaselineSchema.parse(input);
|
|
35
|
+
const ids = new Set();
|
|
36
|
+
for (const entry of result.entries) {
|
|
37
|
+
if (entry.id !== keyId(entry) || ids.has(entry.id))
|
|
38
|
+
throw new Error("Invalid or duplicate baseline finding identity");
|
|
39
|
+
ids.add(entry.id);
|
|
40
|
+
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
function findings(report) {
|
|
44
|
+
const result = new Map();
|
|
45
|
+
for (const check of report.checks)
|
|
46
|
+
for (const finding of check.findings ?? []) {
|
|
47
|
+
const parsed = findingKeySchema.safeParse({
|
|
48
|
+
checkId: check.id,
|
|
49
|
+
project: check.project,
|
|
50
|
+
ruleId: finding.ruleId,
|
|
51
|
+
file: finding.file,
|
|
52
|
+
line: finding.line,
|
|
53
|
+
level: finding.level,
|
|
54
|
+
messageHash: digest(finding.message),
|
|
55
|
+
});
|
|
56
|
+
const id = parsed.success
|
|
57
|
+
? keyId(parsed.data)
|
|
58
|
+
: digest([check.id, check.project, finding]);
|
|
59
|
+
const previous = result.get(id);
|
|
60
|
+
result.set(id, {
|
|
61
|
+
key: parsed.success ? parsed.data : undefined,
|
|
62
|
+
count: (previous?.count ?? 0) + 1,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
export function createFindingBaseline(input, options) {
|
|
68
|
+
const report = validatedReport(input);
|
|
69
|
+
const metadata = findingBaselineEntrySchema
|
|
70
|
+
.pick({ owner: true, reason: true, kind: true, expiresAt: true })
|
|
71
|
+
.parse({ ...options, kind: options.kind ?? "baseline" });
|
|
72
|
+
if (report.outcome === "incomplete" ||
|
|
73
|
+
report.sourceFingerprint !== report.finalSourceFingerprint ||
|
|
74
|
+
report.sourceChanged ||
|
|
75
|
+
report.sourceError ||
|
|
76
|
+
report.checks.some((check) => !["passed", "failed"].includes(check.status) ||
|
|
77
|
+
check.tools?.some((tool) => tool.status !== "identified")))
|
|
78
|
+
throw new Error("Baseline creation requires current and conclusive validation evidence");
|
|
79
|
+
if (report.checks.some((check) => check.status === "failed" &&
|
|
80
|
+
(!check.findingsComplete || !check.findings?.length)))
|
|
81
|
+
throw new Error("Baseline creation cannot account for every native failure");
|
|
82
|
+
const entries = [...findings(report)].map(([id, item]) => {
|
|
83
|
+
if (!item.key)
|
|
84
|
+
throw new Error("Baselines require an exact source file, line and native finding identity");
|
|
85
|
+
return findingBaselineEntrySchema.parse({
|
|
86
|
+
...item.key,
|
|
87
|
+
id,
|
|
88
|
+
occurrences: item.count,
|
|
89
|
+
...metadata,
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
return baseline({
|
|
93
|
+
schemaVersion: 1,
|
|
94
|
+
createdFrom: {
|
|
95
|
+
runId: report.runId,
|
|
96
|
+
sourceFingerprint: report.sourceFingerprint,
|
|
97
|
+
policyFingerprint: report.policyFingerprint,
|
|
98
|
+
},
|
|
99
|
+
limits: {
|
|
100
|
+
maxEntries: entries.length,
|
|
101
|
+
maxExceptions: entries.filter((entry) => entry.kind === "exception")
|
|
102
|
+
.length,
|
|
103
|
+
},
|
|
104
|
+
entries,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
export function compareFindings(input, baselineInput, options = {}) {
|
|
108
|
+
const report = validatedReport(input);
|
|
109
|
+
const policy = baseline(baselineInput);
|
|
110
|
+
const previous = options.previousBaseline === undefined
|
|
111
|
+
? undefined
|
|
112
|
+
: baseline(options.previousBaseline);
|
|
113
|
+
const now = options.now === undefined
|
|
114
|
+
? Date.now()
|
|
115
|
+
: Date.parse(z.iso.datetime().parse(options.now));
|
|
116
|
+
const sourceVerified = !report.sourceChanged &&
|
|
117
|
+
!report.sourceError &&
|
|
118
|
+
report.finalSourceFingerprint === report.sourceFingerprint;
|
|
119
|
+
const current = findings(report);
|
|
120
|
+
const counts = {
|
|
121
|
+
current: [...current.values()].reduce((sum, item) => sum + item.count, 0),
|
|
122
|
+
new: 0,
|
|
123
|
+
matched: 0,
|
|
124
|
+
stale: 0,
|
|
125
|
+
expired: 0,
|
|
126
|
+
changed: 0,
|
|
127
|
+
unverified: 0,
|
|
128
|
+
expanded: 0,
|
|
129
|
+
modified: 0,
|
|
130
|
+
};
|
|
131
|
+
const entries = [];
|
|
132
|
+
for (const entry of policy.entries) {
|
|
133
|
+
const found = current.get(entry.id);
|
|
134
|
+
const check = report.checks.find((check) => check.id === entry.checkId && check.project === entry.project);
|
|
135
|
+
const accounted = check &&
|
|
136
|
+
(check.status === "passed" ||
|
|
137
|
+
(check.status === "failed" && check.findingsComplete === true));
|
|
138
|
+
let status;
|
|
139
|
+
if (entry.expiresAt && Date.parse(entry.expiresAt) <= now)
|
|
140
|
+
status = "expired";
|
|
141
|
+
else if (!sourceVerified || !accounted)
|
|
142
|
+
status = "unverified";
|
|
143
|
+
else if (!found)
|
|
144
|
+
status = "stale";
|
|
145
|
+
else if (found.count !== entry.occurrences)
|
|
146
|
+
status = "changed";
|
|
147
|
+
else
|
|
148
|
+
status = "matched";
|
|
149
|
+
counts[status]++;
|
|
150
|
+
entries.push({ id: entry.id, status, occurrences: found?.count ?? 0 });
|
|
151
|
+
}
|
|
152
|
+
const ids = new Set(policy.entries.map((entry) => entry.id));
|
|
153
|
+
const newFindingIds = [...current.keys()].filter((id) => !ids.has(id));
|
|
154
|
+
counts.new = newFindingIds.reduce((sum, id) => sum + current.get(id).count, 0);
|
|
155
|
+
if (previous) {
|
|
156
|
+
const old = new Map(previous.entries.map((entry) => [entry.id, entry]));
|
|
157
|
+
for (const entry of policy.entries) {
|
|
158
|
+
const existing = old.get(entry.id);
|
|
159
|
+
if (!existing)
|
|
160
|
+
counts.expanded++;
|
|
161
|
+
else if (digest(existing) !== digest(entry))
|
|
162
|
+
counts.modified++;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const limitExceeded = policy.entries.length > policy.limits.maxEntries ||
|
|
166
|
+
policy.entries.filter((entry) => entry.kind === "exception").length >
|
|
167
|
+
policy.limits.maxExceptions ||
|
|
168
|
+
Boolean(previous &&
|
|
169
|
+
(policy.limits.maxEntries > previous.limits.maxEntries ||
|
|
170
|
+
policy.limits.maxExceptions > previous.limits.maxExceptions));
|
|
171
|
+
const unaccountedFailures = report.checks.filter((check) => check.status === "failed" &&
|
|
172
|
+
(!check.findingsComplete || !check.findings?.length)).length;
|
|
173
|
+
const failed = counts.new ||
|
|
174
|
+
counts.stale ||
|
|
175
|
+
counts.expired ||
|
|
176
|
+
counts.changed ||
|
|
177
|
+
counts.expanded ||
|
|
178
|
+
counts.modified ||
|
|
179
|
+
limitExceeded ||
|
|
180
|
+
unaccountedFailures;
|
|
181
|
+
const incomplete = !sourceVerified ||
|
|
182
|
+
counts.unverified ||
|
|
183
|
+
report.outcome === "incomplete" ||
|
|
184
|
+
report.checks.some((check) => !["passed", "failed"].includes(check.status) ||
|
|
185
|
+
check.tools?.some((tool) => tool.status !== "identified"));
|
|
186
|
+
const outcome = failed ? "failed" : incomplete ? "incomplete" : "passed";
|
|
187
|
+
return {
|
|
188
|
+
schemaVersion: 1,
|
|
189
|
+
engineVersion: VERSION,
|
|
190
|
+
provenance: "finding-comparison",
|
|
191
|
+
outcome,
|
|
192
|
+
validationOutcome: report.outcome,
|
|
193
|
+
reason: outcome === "passed"
|
|
194
|
+
? "Finding policy matches the supplied report; the native validation outcome is retained separately."
|
|
195
|
+
: outcome === "failed"
|
|
196
|
+
? "Finding policy has new, stale, expired, changed or expanded entries, exceeded limits, or unaccounted native failures."
|
|
197
|
+
: "Finding policy cannot be reconciled against complete current-source evidence.",
|
|
198
|
+
sourceVerified,
|
|
199
|
+
baselineFingerprint: digest(policy),
|
|
200
|
+
counts,
|
|
201
|
+
limitExceeded,
|
|
202
|
+
unaccountedFailures,
|
|
203
|
+
entries,
|
|
204
|
+
newFindingIds,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ChangeSelection, GitIdentity, Inventory, Workspace } from "./types.js";
|
|
2
|
+
export declare function selectGitChanges(source: Inventory, projects: string[], base: string, workspace?: Workspace): Promise<ChangeSelection>;
|
|
3
|
+
export declare function verifyGitIdentity(root: string, identity: GitIdentity): Promise<"same" | "changed" | "error">;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { access, lstat, readFile, realpath } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { constants } from "node:fs";
|
|
5
|
+
import { withinRoot } from "./inventory.js";
|
|
6
|
+
import { runProcess } from "./runner.js";
|
|
7
|
+
import { affectedProjects } from "./workspace.js";
|
|
8
|
+
const gitEnvironment = {
|
|
9
|
+
GIT_CONFIG_NOSYSTEM: "1",
|
|
10
|
+
GIT_CONFIG_GLOBAL: "/dev/null",
|
|
11
|
+
GIT_OPTIONAL_LOCKS: "0",
|
|
12
|
+
GIT_TERMINAL_PROMPT: "0",
|
|
13
|
+
GIT_NO_LAZY_FETCH: "1",
|
|
14
|
+
GIT_NO_REPLACE_OBJECTS: "1",
|
|
15
|
+
GIT_ATTR_NOSYSTEM: "1",
|
|
16
|
+
};
|
|
17
|
+
async function hostGit(root) {
|
|
18
|
+
const outside = (file) => {
|
|
19
|
+
const relative = path.relative(root, file);
|
|
20
|
+
return (relative === ".." ||
|
|
21
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
22
|
+
path.isAbsolute(relative));
|
|
23
|
+
};
|
|
24
|
+
for (const directory of (process.env.PATH ?? "").split(path.delimiter)) {
|
|
25
|
+
if (!path.isAbsolute(directory))
|
|
26
|
+
continue;
|
|
27
|
+
const candidate = path.join(directory, "git");
|
|
28
|
+
if (!outside(candidate))
|
|
29
|
+
continue;
|
|
30
|
+
try {
|
|
31
|
+
const resolved = await realpath(candidate);
|
|
32
|
+
if (outside(resolved) && (await lstat(resolved)).isFile()) {
|
|
33
|
+
await access(resolved, constants.X_OK);
|
|
34
|
+
return resolved;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
throw new Error("A host Git executable outside the project is required");
|
|
42
|
+
}
|
|
43
|
+
function gitReader(root) {
|
|
44
|
+
const executable = hostGit(root);
|
|
45
|
+
const deadline = Date.now() + 5000;
|
|
46
|
+
let bytes = 4 * 1024 * 1024;
|
|
47
|
+
return async (args) => {
|
|
48
|
+
const remaining = deadline - Date.now();
|
|
49
|
+
if (remaining <= 0 || bytes <= 0)
|
|
50
|
+
throw new Error("Git inspection budget exceeded");
|
|
51
|
+
const result = await runProcess(root, {
|
|
52
|
+
executable: await executable,
|
|
53
|
+
args: [
|
|
54
|
+
"--no-pager",
|
|
55
|
+
"-c",
|
|
56
|
+
"core.fsmonitor=false",
|
|
57
|
+
"-c",
|
|
58
|
+
"core.hooksPath=/dev/null",
|
|
59
|
+
"-c",
|
|
60
|
+
"submodule.recurse=false",
|
|
61
|
+
...args,
|
|
62
|
+
],
|
|
63
|
+
cwd: ".",
|
|
64
|
+
env: gitEnvironment,
|
|
65
|
+
}, { timeoutMs: remaining, maxOutputBytes: bytes });
|
|
66
|
+
bytes -=
|
|
67
|
+
Buffer.byteLength(result.stdout) + Buffer.byteLength(result.stderr);
|
|
68
|
+
if (result.exitCode !== 0 ||
|
|
69
|
+
result.signal ||
|
|
70
|
+
result.errorCode ||
|
|
71
|
+
result.timedOut ||
|
|
72
|
+
result.cancelled ||
|
|
73
|
+
result.truncated ||
|
|
74
|
+
result.stderr.trim())
|
|
75
|
+
throw new Error("Git inspection was not conclusive");
|
|
76
|
+
return result.stdout;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
const digest = (value) => createHash("sha256").update(value).digest("hex");
|
|
80
|
+
const oid = (value) => {
|
|
81
|
+
const hash = value.trim();
|
|
82
|
+
if (!/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/.test(hash))
|
|
83
|
+
throw new Error("Invalid Git object identity");
|
|
84
|
+
return hash;
|
|
85
|
+
};
|
|
86
|
+
function entries(output, index) {
|
|
87
|
+
if (output && !output.endsWith("\0"))
|
|
88
|
+
throw new Error("Unterminated Git evidence");
|
|
89
|
+
const result = new Map();
|
|
90
|
+
for (const entry of output.split("\0").filter(Boolean)) {
|
|
91
|
+
const match = (index
|
|
92
|
+
? /^(100644|100755) ([a-f0-9]{40}|[a-f0-9]{64}) 0\t([^]*)$/
|
|
93
|
+
: /^(100644|100755) blob ([a-f0-9]{40}|[a-f0-9]{64})\t([^]*)$/).exec(entry);
|
|
94
|
+
if (!match)
|
|
95
|
+
throw new Error("Unsupported Git entry or unresolved merge");
|
|
96
|
+
const file = match[3];
|
|
97
|
+
if (!file ||
|
|
98
|
+
path.posix.isAbsolute(file) ||
|
|
99
|
+
file.split("/").some((part) => !part || part === "." || part === "..") ||
|
|
100
|
+
result.has(file))
|
|
101
|
+
throw new Error("Ambiguous Git path");
|
|
102
|
+
result.set(file, { mode: match[1], hash: match[2] });
|
|
103
|
+
if (result.size > 20_000)
|
|
104
|
+
throw new Error("Git entry limit exceeded");
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
function differences(a, b, target) {
|
|
109
|
+
for (const key of new Set([...a.keys(), ...b.keys()]))
|
|
110
|
+
if (a.get(key)?.hash !== b.get(key)?.hash ||
|
|
111
|
+
a.get(key)?.mode !== b.get(key)?.mode)
|
|
112
|
+
target.add(key);
|
|
113
|
+
}
|
|
114
|
+
export async function selectGitChanges(source, projects, base, workspace) {
|
|
115
|
+
const full = {
|
|
116
|
+
mode: "full",
|
|
117
|
+
reason: "Git change scope could not be established; retaining the full configured plan.",
|
|
118
|
+
changedFiles: [],
|
|
119
|
+
selectedProjects: [...new Set(projects)].sort(),
|
|
120
|
+
};
|
|
121
|
+
try {
|
|
122
|
+
if (!base || base.length > 1024 || base.includes("\0"))
|
|
123
|
+
return full;
|
|
124
|
+
const read = gitReader(source.root);
|
|
125
|
+
const top = (await read(["rev-parse", "--show-toplevel"])).trimEnd();
|
|
126
|
+
if ((await realpath(top)) !== source.root)
|
|
127
|
+
return {
|
|
128
|
+
...full,
|
|
129
|
+
reason: "The configured root is not the Git worktree root.",
|
|
130
|
+
};
|
|
131
|
+
const version = (await read(["--version"])).trim();
|
|
132
|
+
if (!/^git version \d+\.\d+\.\d+[^\n]*$/.test(version))
|
|
133
|
+
return full;
|
|
134
|
+
const headCommit = oid(await read([
|
|
135
|
+
"rev-parse",
|
|
136
|
+
"--verify",
|
|
137
|
+
"--end-of-options",
|
|
138
|
+
"HEAD^{commit}",
|
|
139
|
+
]));
|
|
140
|
+
const baseCommit = oid(await read([
|
|
141
|
+
"rev-parse",
|
|
142
|
+
"--verify",
|
|
143
|
+
"--end-of-options",
|
|
144
|
+
`${base}^{commit}`,
|
|
145
|
+
]));
|
|
146
|
+
const baseTree = entries(await read(["ls-tree", "-rz", "--full-tree", baseCommit]), false);
|
|
147
|
+
const headTree = entries(await read(["ls-tree", "-rz", "--full-tree", headCommit]), false);
|
|
148
|
+
const indexOutput = await read(["ls-files", "--stage", "-z"]);
|
|
149
|
+
const index = entries(indexOutput, true);
|
|
150
|
+
const identity = {
|
|
151
|
+
baseCommit,
|
|
152
|
+
headCommit,
|
|
153
|
+
version,
|
|
154
|
+
worktreeId: digest(source.root),
|
|
155
|
+
indexFingerprint: digest(indexOutput),
|
|
156
|
+
};
|
|
157
|
+
full.git = identity;
|
|
158
|
+
const files = new Set(source.files);
|
|
159
|
+
if ([...index.keys()].some((file) => !files.has(file) &&
|
|
160
|
+
source.excluded.some((excluded) => file === excluded || file.startsWith(`${excluded}/`))))
|
|
161
|
+
return {
|
|
162
|
+
...full,
|
|
163
|
+
reason: "Tracked excluded content prevents complete working-tree comparison.",
|
|
164
|
+
};
|
|
165
|
+
const changed = new Set();
|
|
166
|
+
differences(baseTree, headTree, changed);
|
|
167
|
+
differences(headTree, index, changed);
|
|
168
|
+
let size = 0;
|
|
169
|
+
const working = new Map();
|
|
170
|
+
for (const file of source.files) {
|
|
171
|
+
const resolved = await withinRoot(source.root, file);
|
|
172
|
+
const stat = await lstat(resolved);
|
|
173
|
+
size += stat.size;
|
|
174
|
+
if (!stat.isFile() ||
|
|
175
|
+
stat.size > 8 * 1024 * 1024 ||
|
|
176
|
+
size > 64 * 1024 * 1024)
|
|
177
|
+
return full;
|
|
178
|
+
const bytes = await readFile(resolved);
|
|
179
|
+
const hash = createHash(headCommit.length === 40 ? "sha1" : "sha256")
|
|
180
|
+
.update(`blob ${bytes.length}\0`)
|
|
181
|
+
.update(bytes)
|
|
182
|
+
.digest("hex");
|
|
183
|
+
working.set(file, {
|
|
184
|
+
mode: stat.mode & 0o111 ? "100755" : "100644",
|
|
185
|
+
hash,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
differences(index, working, changed);
|
|
189
|
+
if (oid(await read([
|
|
190
|
+
"rev-parse",
|
|
191
|
+
"--verify",
|
|
192
|
+
"--end-of-options",
|
|
193
|
+
"HEAD^{commit}",
|
|
194
|
+
])) !== headCommit ||
|
|
195
|
+
digest(await read(["ls-files", "--stage", "-z"])) !==
|
|
196
|
+
identity.indexFingerprint)
|
|
197
|
+
return full;
|
|
198
|
+
const changedFiles = [...changed].sort();
|
|
199
|
+
const impact = affectedProjects(projects, changedFiles, workspace);
|
|
200
|
+
return {
|
|
201
|
+
mode: impact.affected ? "affected" : "full",
|
|
202
|
+
reason: impact.reason,
|
|
203
|
+
changedFiles,
|
|
204
|
+
selectedProjects: impact.projects,
|
|
205
|
+
git: identity,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return full;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
export async function verifyGitIdentity(root, identity) {
|
|
213
|
+
try {
|
|
214
|
+
const read = gitReader(root);
|
|
215
|
+
const head = oid(await read([
|
|
216
|
+
"rev-parse",
|
|
217
|
+
"--verify",
|
|
218
|
+
"--end-of-options",
|
|
219
|
+
"HEAD^{commit}",
|
|
220
|
+
]));
|
|
221
|
+
const index = digest(await read(["ls-files", "--stage", "-z"]));
|
|
222
|
+
return head === identity.headCommit && index === identity.indexFingerprint
|
|
223
|
+
? "same"
|
|
224
|
+
: "changed";
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return "error";
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function hasGoSuppression(source: string): boolean;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function hasGoSuppression(source) {
|
|
2
|
+
for (let i = 0; i < source.length; i++) {
|
|
3
|
+
const char = source[i];
|
|
4
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
5
|
+
for (i++; i < source.length; i++) {
|
|
6
|
+
if (source[i] === char)
|
|
7
|
+
break;
|
|
8
|
+
if (char !== "`" && source[i] === "\\")
|
|
9
|
+
i++;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
else if (char === "/" && source[i + 1] === "/") {
|
|
13
|
+
const end = source.indexOf("\n", i);
|
|
14
|
+
const text = source
|
|
15
|
+
.slice(i, end === -1 ? source.length : end)
|
|
16
|
+
.replace(/\r/g, "")
|
|
17
|
+
.replace(/^[ /]+/, "");
|
|
18
|
+
if (/^(?:nolint(?: |:|$)|lint:(?:ignore|file-ignore)(?: |$))/.test(text))
|
|
19
|
+
return true;
|
|
20
|
+
if (end === -1)
|
|
21
|
+
break;
|
|
22
|
+
i = end;
|
|
23
|
+
}
|
|
24
|
+
else if (char === "/" && source[i + 1] === "*") {
|
|
25
|
+
const end = source.indexOf("*/", i + 2);
|
|
26
|
+
if (end === -1)
|
|
27
|
+
break;
|
|
28
|
+
i = end + 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Check, Command, ProcessResult } from "./types.js";
|
|
2
|
+
export declare const goEnvironment: {
|
|
3
|
+
GOPROXY: string;
|
|
4
|
+
GOTOOLCHAIN: string;
|
|
5
|
+
GOFLAGS: string;
|
|
6
|
+
GOWORK: string;
|
|
7
|
+
GOENV: string;
|
|
8
|
+
GOCACHEPROG: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function goScopeCommand(project: string, env?: Record<string, string>): Command;
|
|
11
|
+
export declare function goScopeComplete(check: Check, process: ProcessResult | undefined, root: string | undefined, testOutput?: string): boolean;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export const goEnvironment = {
|
|
4
|
+
GOPROXY: "off",
|
|
5
|
+
GOTOOLCHAIN: "local",
|
|
6
|
+
GOFLAGS: "-mod=readonly",
|
|
7
|
+
GOWORK: "off",
|
|
8
|
+
GOENV: "off",
|
|
9
|
+
GOCACHEPROG: "",
|
|
10
|
+
};
|
|
11
|
+
export function goScopeCommand(project, env = goEnvironment) {
|
|
12
|
+
return {
|
|
13
|
+
executable: "go",
|
|
14
|
+
args: ["list", "-json", "./..."],
|
|
15
|
+
cwd: project,
|
|
16
|
+
env,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function objects(text) {
|
|
20
|
+
const result = [];
|
|
21
|
+
let depth = 0, start = 0, quoted = false, escaped = false;
|
|
22
|
+
for (let i = 0; i < text.length; i++) {
|
|
23
|
+
const char = text[i];
|
|
24
|
+
if (!depth && char?.trim() && char !== "{")
|
|
25
|
+
throw new Error("Unexpected package output");
|
|
26
|
+
if (quoted) {
|
|
27
|
+
if (escaped)
|
|
28
|
+
escaped = false;
|
|
29
|
+
else if (char === "\\")
|
|
30
|
+
escaped = true;
|
|
31
|
+
else if (char === '"')
|
|
32
|
+
quoted = false;
|
|
33
|
+
}
|
|
34
|
+
else if (char === '"')
|
|
35
|
+
quoted = true;
|
|
36
|
+
else if (char === "{") {
|
|
37
|
+
if (!depth)
|
|
38
|
+
start = i;
|
|
39
|
+
depth++;
|
|
40
|
+
}
|
|
41
|
+
else if (char === "}") {
|
|
42
|
+
depth--;
|
|
43
|
+
if (!depth)
|
|
44
|
+
result.push(JSON.parse(text.slice(start, i + 1)));
|
|
45
|
+
if (depth < 0)
|
|
46
|
+
throw new Error("Invalid package JSON");
|
|
47
|
+
}
|
|
48
|
+
else if (!depth && char?.trim())
|
|
49
|
+
throw new Error("Unexpected package output");
|
|
50
|
+
}
|
|
51
|
+
if (depth || quoted)
|
|
52
|
+
throw new Error("Partial package output");
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
const names = z.array(z.string()).default([]);
|
|
56
|
+
const packageSchema = z.object({
|
|
57
|
+
Dir: z.string(),
|
|
58
|
+
ImportPath: z.string().min(1),
|
|
59
|
+
GoFiles: names,
|
|
60
|
+
CgoFiles: names,
|
|
61
|
+
TestGoFiles: names,
|
|
62
|
+
XTestGoFiles: names,
|
|
63
|
+
Error: z.unknown().optional(),
|
|
64
|
+
DepsErrors: z.array(z.unknown()).optional(),
|
|
65
|
+
});
|
|
66
|
+
export function goScopeComplete(check, process, root, testOutput) {
|
|
67
|
+
if (!root ||
|
|
68
|
+
!process ||
|
|
69
|
+
process.exitCode !== 0 ||
|
|
70
|
+
process.stderr.trim() ||
|
|
71
|
+
!check.scope.length)
|
|
72
|
+
return false;
|
|
73
|
+
try {
|
|
74
|
+
const packages = objects(process.stdout).map((value) => packageSchema.parse(value));
|
|
75
|
+
const expected = new Set(check.scope.map((file) => path.resolve(root, check.project, file)));
|
|
76
|
+
const seen = new Set();
|
|
77
|
+
const ids = new Set();
|
|
78
|
+
for (const item of packages) {
|
|
79
|
+
if (!path.isAbsolute(item.Dir) ||
|
|
80
|
+
ids.has(item.ImportPath) ||
|
|
81
|
+
item.Error ||
|
|
82
|
+
item.DepsErrors?.length)
|
|
83
|
+
return false;
|
|
84
|
+
ids.add(item.ImportPath);
|
|
85
|
+
for (const file of [
|
|
86
|
+
...item.GoFiles,
|
|
87
|
+
...item.CgoFiles,
|
|
88
|
+
...item.TestGoFiles,
|
|
89
|
+
...item.XTestGoFiles,
|
|
90
|
+
]) {
|
|
91
|
+
if (path.basename(file) !== file || !file.endsWith(".go"))
|
|
92
|
+
return false;
|
|
93
|
+
const resolved = path.resolve(item.Dir, file);
|
|
94
|
+
if (!expected.has(resolved) || seen.has(resolved))
|
|
95
|
+
return false;
|
|
96
|
+
seen.add(resolved);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (testOutput !== undefined) {
|
|
100
|
+
const tested = new Set();
|
|
101
|
+
for (const line of testOutput.split("\n").filter(Boolean)) {
|
|
102
|
+
const event = z
|
|
103
|
+
.object({
|
|
104
|
+
Action: z.string(),
|
|
105
|
+
Package: z.string(),
|
|
106
|
+
Test: z.string().optional(),
|
|
107
|
+
})
|
|
108
|
+
.parse(JSON.parse(line));
|
|
109
|
+
if (!ids.has(event.Package))
|
|
110
|
+
return false;
|
|
111
|
+
if (event.Action === "pass" && event.Test)
|
|
112
|
+
tested.add(event.Package);
|
|
113
|
+
}
|
|
114
|
+
if (tested.size !== ids.size)
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
return ids.size > 0 && seen.size === expected.size;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|