@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,120 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFile, stat } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { readProjectFile, withinRoot } from "./inventory.js";
|
|
7
|
+
export const dotnetConfigSchema = z.strictObject({
|
|
8
|
+
schemaVersion: z.literal(1),
|
|
9
|
+
targetFramework: z.literal("net10.0"),
|
|
10
|
+
assemblyName: z
|
|
11
|
+
.string()
|
|
12
|
+
.regex(/^[A-Za-z_][A-Za-z0-9_.-]*$/)
|
|
13
|
+
.max(128),
|
|
14
|
+
languageVersion: z.enum(["12", "13", "14"]),
|
|
15
|
+
outputKind: z.enum(["library", "console"]),
|
|
16
|
+
nullable: z.enum(["enable", "disable", "warnings", "annotations"]),
|
|
17
|
+
warningsAsErrors: z.boolean(),
|
|
18
|
+
allowUnsafe: z.boolean(),
|
|
19
|
+
checkedArithmetic: z.boolean(),
|
|
20
|
+
implicitUsings: z.boolean(),
|
|
21
|
+
defines: z
|
|
22
|
+
.array(z
|
|
23
|
+
.string()
|
|
24
|
+
.regex(/^[A-Za-z_][A-Za-z0-9_]*$/)
|
|
25
|
+
.max(128))
|
|
26
|
+
.max(128)
|
|
27
|
+
.refine((values) => new Set(values).size === values.length &&
|
|
28
|
+
!values.some((value) => ["true", "false"].includes(value))),
|
|
29
|
+
references: z
|
|
30
|
+
.array(z.strictObject({
|
|
31
|
+
path: z
|
|
32
|
+
.string()
|
|
33
|
+
.min(1)
|
|
34
|
+
.max(4096)
|
|
35
|
+
.refine((value) => !/[\0\r\n]/.test(value)),
|
|
36
|
+
sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
|
37
|
+
}))
|
|
38
|
+
.max(128),
|
|
39
|
+
});
|
|
40
|
+
export const dotnetInvocationSchema = z.strictObject({
|
|
41
|
+
config: dotnetConfigSchema,
|
|
42
|
+
scope: z.array(z.string().min(1)).min(1).max(20_000),
|
|
43
|
+
});
|
|
44
|
+
export async function dotnetReferences(root, project, config) {
|
|
45
|
+
let total = 0;
|
|
46
|
+
const files = [];
|
|
47
|
+
for (const item of config.references) {
|
|
48
|
+
const declared = path.resolve(root, project, item.path);
|
|
49
|
+
const file = await withinRoot(root, path.relative(root, declared));
|
|
50
|
+
if (file !== declared)
|
|
51
|
+
throw new Error(".NET references must not traverse symbolic links");
|
|
52
|
+
const info = await stat(file);
|
|
53
|
+
total += info.size;
|
|
54
|
+
if (!info.isFile() ||
|
|
55
|
+
!file.endsWith(".dll") ||
|
|
56
|
+
info.size > 32 * 1024 * 1024 ||
|
|
57
|
+
total > 128 * 1024 * 1024)
|
|
58
|
+
throw new Error(".NET references require bounded, local DLL files");
|
|
59
|
+
if (files.includes(file))
|
|
60
|
+
throw new Error("Duplicate .NET reference");
|
|
61
|
+
if (createHash("sha256")
|
|
62
|
+
.update(await readFile(file))
|
|
63
|
+
.digest("hex") !== item.sha256)
|
|
64
|
+
throw new Error(".NET reference checksum does not match");
|
|
65
|
+
files.push(file);
|
|
66
|
+
}
|
|
67
|
+
return files;
|
|
68
|
+
}
|
|
69
|
+
export async function dotnetCheck(source, project) {
|
|
70
|
+
const scope = project.files.filter((file) => file.endsWith(".cs"));
|
|
71
|
+
const check = {
|
|
72
|
+
id: "dotnet.csharp",
|
|
73
|
+
adapter: project.adapter,
|
|
74
|
+
project: project.path,
|
|
75
|
+
scope,
|
|
76
|
+
kind: "analysis",
|
|
77
|
+
parser: "dotnet-json",
|
|
78
|
+
commands: [],
|
|
79
|
+
reason: "Compile explicitly configured C# sources with native syntax/semantic accounting and pinned local references.",
|
|
80
|
+
};
|
|
81
|
+
try {
|
|
82
|
+
if (!project.files.includes("checktrail.dotnet.json"))
|
|
83
|
+
throw new Error("Prepare an inventoried checktrail.dotnet.json with explicit compiler settings; MSBuild and NuGet are not invoked");
|
|
84
|
+
if (project.markers.filter((file) => file.endsWith(".csproj")).length !== 1 ||
|
|
85
|
+
project.markers.some((file) => /\.(?:fsproj|vbproj)$/.test(file)))
|
|
86
|
+
throw new Error("Use one C# project per root; mixed project languages require separate profiles");
|
|
87
|
+
if (!scope.length)
|
|
88
|
+
throw new Error("No C# source was inventoried");
|
|
89
|
+
if (project.files.some((file) => /\.(?:fs|fsx|vb|csx|razor|cshtml|xaml)$/.test(file)))
|
|
90
|
+
throw new Error("Mixed languages, scripts and generated UI sources require separate verified profiles");
|
|
91
|
+
const config = dotnetConfigSchema.parse(JSON.parse(await readProjectFile(source.root, path.posix.join(project.path, "checktrail.dotnet.json"))));
|
|
92
|
+
await dotnetReferences(source.root, project.path, config);
|
|
93
|
+
const invocation = JSON.stringify({ config, scope });
|
|
94
|
+
if (Buffer.byteLength(invocation) > 100 * 1024)
|
|
95
|
+
throw new Error(".NET invocation exceeds the 100 KiB argument limit");
|
|
96
|
+
check.commands.push({
|
|
97
|
+
executable: process.execPath,
|
|
98
|
+
args: [
|
|
99
|
+
fileURLToPath(new URL("./dotnet-runner.js", import.meta.url)),
|
|
100
|
+
source.root,
|
|
101
|
+
invocation,
|
|
102
|
+
],
|
|
103
|
+
cwd: project.path,
|
|
104
|
+
env: {
|
|
105
|
+
PATH: process.env.PATH ?? "",
|
|
106
|
+
DOTNET_STARTUP_HOOKS: "",
|
|
107
|
+
DOTNET_ADDITIONAL_DEPS: "",
|
|
108
|
+
DOTNET_SHARED_STORE: "",
|
|
109
|
+
CORECLR_ENABLE_PROFILING: "0",
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
check.unavailableReason =
|
|
115
|
+
error instanceof Error
|
|
116
|
+
? error.message
|
|
117
|
+
: ".NET configuration could not be prepared";
|
|
118
|
+
}
|
|
119
|
+
return check;
|
|
120
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type ExternalReference } from "./external-adapter.js";
|
|
2
|
+
import type { CheckResult, Inventory, Plan, Report } from "./types.js";
|
|
3
|
+
export interface PlanOptions {
|
|
4
|
+
externalAdapters?: ExternalReference[];
|
|
5
|
+
base?: string;
|
|
6
|
+
policyOverlay?: string;
|
|
7
|
+
environment?: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
export declare function createPlan(root: string, options?: PlanOptions): Promise<{
|
|
10
|
+
source: Inventory;
|
|
11
|
+
plan: Plan;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function aggregate(checks: CheckResult[], stale?: boolean): Report["outcome"];
|
|
14
|
+
export interface ValidationOptions extends PlanOptions {
|
|
15
|
+
trusted: boolean;
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
signal?: AbortSignal;
|
|
18
|
+
}
|
|
19
|
+
export declare function validate(root: string, options: ValidationOptions): Promise<Report>;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { externalChecks, externalPolicyFingerprint, externalReferencesSchema, loadExternalAdapters, } from "./external-adapter.js";
|
|
2
|
+
import { selectGitChanges, verifyGitIdentity } from "./git-selection.js";
|
|
3
|
+
import { operatorEnvironment, selectEnvironment, environmentFingerprint, } from "./environment.js";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { discover, checksFor } from "./adapters.js";
|
|
6
|
+
import { loadConfig } from "./config.js";
|
|
7
|
+
import { evaluate } from "./evidence.js";
|
|
8
|
+
import { inventory } from "./inventory.js";
|
|
9
|
+
import { runProcess } from "./runner.js";
|
|
10
|
+
import { identifyTool, toolsFor } from "./tool-versions.js";
|
|
11
|
+
import { VERSION } from "./types.js";
|
|
12
|
+
export async function createPlan(root, options = {}) {
|
|
13
|
+
const suppliedEnvironment = operatorEnvironment(options.environment);
|
|
14
|
+
const source = await inventory(root);
|
|
15
|
+
const external = await loadExternalAdapters(options.externalAdapters);
|
|
16
|
+
const projects = discover(source, external);
|
|
17
|
+
const { config, fingerprint: baseFingerprint } = await loadConfig(source, options.policyOverlay);
|
|
18
|
+
const fingerprint = externalPolicyFingerprint(baseFingerprint, external);
|
|
19
|
+
const selected = new Map(config?.projects.map((p) => [p.path, p.checks]));
|
|
20
|
+
for (const key of selected.keys()) {
|
|
21
|
+
if (!projects.some((p) => p.path === key))
|
|
22
|
+
throw new Error(`No detected project at configured path: ${key}`);
|
|
23
|
+
}
|
|
24
|
+
const checks = [];
|
|
25
|
+
const accepted = new Map();
|
|
26
|
+
for (const project of projects) {
|
|
27
|
+
if (config && !selected.has(project.path))
|
|
28
|
+
continue;
|
|
29
|
+
const requested = selected.get(project.path);
|
|
30
|
+
const extension = external.find((item) => item.identity.id === project.adapter);
|
|
31
|
+
const candidates = extension
|
|
32
|
+
? externalChecks(source, project, extension)
|
|
33
|
+
: await checksFor(source, project, requested);
|
|
34
|
+
for (const check of candidates) {
|
|
35
|
+
if (requested && !requested.includes(check.id))
|
|
36
|
+
continue;
|
|
37
|
+
const names = config?.projects.find((item) => item.path === project.path)
|
|
38
|
+
?.environment ?? [];
|
|
39
|
+
if (names.length) {
|
|
40
|
+
check.environment = [...names].sort();
|
|
41
|
+
if (names.some((name) => !Object.hasOwn(suppliedEnvironment, name)))
|
|
42
|
+
check.unavailableReason ??=
|
|
43
|
+
"Required project environment was not supplied by the operator.";
|
|
44
|
+
if (check.commands.some((command) => names.some((name) => Object.hasOwn(command.env ?? {}, name))))
|
|
45
|
+
throw new Error("Project environment conflicts with protected adapter settings");
|
|
46
|
+
}
|
|
47
|
+
check.tools = await toolsFor(source.root, check);
|
|
48
|
+
checks.push(check);
|
|
49
|
+
const ids = accepted.get(project.path) ?? new Set();
|
|
50
|
+
ids.add(check.id);
|
|
51
|
+
accepted.set(project.path, ids);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const [project, ids] of selected) {
|
|
55
|
+
for (const id of ids) {
|
|
56
|
+
if (!accepted.get(project)?.has(id))
|
|
57
|
+
throw new Error(`Check is unknown or inapplicable: ${id}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const selection = options.base === undefined
|
|
61
|
+
? undefined
|
|
62
|
+
: await selectGitChanges(source, [...new Set(checks.map((check) => check.project))], options.base, config?.workspace);
|
|
63
|
+
if (selection &&
|
|
64
|
+
(external.length > 0 ||
|
|
65
|
+
options.policyOverlay !== undefined ||
|
|
66
|
+
config?.projects.some((project) => project.packs?.length))) {
|
|
67
|
+
selection.mode = "full";
|
|
68
|
+
selection.reason = external.length
|
|
69
|
+
? "External adapter impact requires complete project validation."
|
|
70
|
+
: "Pack and operator-overlay impact requires complete project validation.";
|
|
71
|
+
selection.selectedProjects = [
|
|
72
|
+
...new Set(checks.map((check) => check.project)),
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
const selectedChecks = selection
|
|
76
|
+
? checks.filter((check) => selection.selectedProjects.includes(check.project))
|
|
77
|
+
: checks;
|
|
78
|
+
return {
|
|
79
|
+
source,
|
|
80
|
+
plan: {
|
|
81
|
+
schemaVersion: 1,
|
|
82
|
+
engineVersion: VERSION,
|
|
83
|
+
sourceFingerprint: source.fingerprint,
|
|
84
|
+
policyFingerprint: fingerprint,
|
|
85
|
+
excluded: source.excluded,
|
|
86
|
+
projects,
|
|
87
|
+
checks: selectedChecks,
|
|
88
|
+
...(config?.workspace ? { workspace: config.workspace } : {}),
|
|
89
|
+
...(selection ? { selection } : {}),
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export function aggregate(checks, stale = false) {
|
|
94
|
+
if (checks.some((check) => check.status === "failed"))
|
|
95
|
+
return "failed";
|
|
96
|
+
if (stale ||
|
|
97
|
+
checks.length === 0 ||
|
|
98
|
+
checks.some((check) => check.status !== "passed"))
|
|
99
|
+
return "incomplete";
|
|
100
|
+
return "passed";
|
|
101
|
+
}
|
|
102
|
+
export async function validate(root, options) {
|
|
103
|
+
if (!options.trusted)
|
|
104
|
+
throw new Error("Execution requires operator trust. Use --trust-project or start MCP with --allow-execution.");
|
|
105
|
+
const timeoutMs = options.timeoutMs ?? 30_000;
|
|
106
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs < 1 || timeoutMs > 120_000)
|
|
107
|
+
throw new Error("Timeout must be between 1 and 120000 milliseconds");
|
|
108
|
+
const externalAdapters = externalReferencesSchema.parse(options.externalAdapters ?? []);
|
|
109
|
+
const started = Date.now();
|
|
110
|
+
const suppliedEnvironment = operatorEnvironment(options.environment);
|
|
111
|
+
const { source, plan } = await createPlan(root, {
|
|
112
|
+
environment: suppliedEnvironment,
|
|
113
|
+
externalAdapters,
|
|
114
|
+
...(options.base !== undefined ? { base: options.base } : {}),
|
|
115
|
+
...(options.policyOverlay !== undefined
|
|
116
|
+
? { policyOverlay: options.policyOverlay }
|
|
117
|
+
: {}),
|
|
118
|
+
});
|
|
119
|
+
let activeEnvironment = {};
|
|
120
|
+
const checks = [];
|
|
121
|
+
let outputBudget = 4 * 1024 * 1024;
|
|
122
|
+
const identified = new Map();
|
|
123
|
+
const execute = async (command, maximum = 1024 * 1024) => {
|
|
124
|
+
const remaining = timeoutMs - (Date.now() - started);
|
|
125
|
+
if (remaining <= 0 || outputBudget <= 0 || options.signal?.aborted)
|
|
126
|
+
return undefined;
|
|
127
|
+
const result = await runProcess(source.root, command, {
|
|
128
|
+
timeoutMs: remaining,
|
|
129
|
+
environment: activeEnvironment,
|
|
130
|
+
maxOutputBytes: Math.min(outputBudget, maximum),
|
|
131
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
132
|
+
});
|
|
133
|
+
outputBudget -=
|
|
134
|
+
Buffer.byteLength(result.stdout) + Buffer.byteLength(result.stderr);
|
|
135
|
+
return result;
|
|
136
|
+
};
|
|
137
|
+
for (const check of plan.checks) {
|
|
138
|
+
activeEnvironment = selectEnvironment(check.environment ?? [], suppliedEnvironment);
|
|
139
|
+
const environmentIdentity = environmentFingerprint(activeEnvironment);
|
|
140
|
+
const tools = [];
|
|
141
|
+
if (!check.unavailableReason)
|
|
142
|
+
for (const tool of check.tools ?? []) {
|
|
143
|
+
const key = JSON.stringify([tool, environmentIdentity]);
|
|
144
|
+
let identity = identified.get(key);
|
|
145
|
+
if (!identity) {
|
|
146
|
+
try {
|
|
147
|
+
identity = await identifyTool(source.root, tool, (command) => execute(command, 8192));
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
identity = {
|
|
151
|
+
name: tool.name,
|
|
152
|
+
source: tool.source,
|
|
153
|
+
version: null,
|
|
154
|
+
status: "inconclusive",
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
identified.set(key, identity);
|
|
158
|
+
}
|
|
159
|
+
tools.push(identity);
|
|
160
|
+
}
|
|
161
|
+
const processes = [];
|
|
162
|
+
let executionError = false;
|
|
163
|
+
if (!check.unavailableReason) {
|
|
164
|
+
for (const command of check.commands) {
|
|
165
|
+
const remaining = timeoutMs - (Date.now() - started);
|
|
166
|
+
if (remaining <= 0 || outputBudget <= 0 || options.signal?.aborted)
|
|
167
|
+
break;
|
|
168
|
+
try {
|
|
169
|
+
const processResult = await execute(command);
|
|
170
|
+
if (!processResult)
|
|
171
|
+
break;
|
|
172
|
+
processes.push(processResult);
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
executionError = true;
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
const result = evaluate(check, processes, source.root);
|
|
181
|
+
result.tools = tools;
|
|
182
|
+
if (check.environment?.length)
|
|
183
|
+
result.environment = {
|
|
184
|
+
names: Object.keys(activeEnvironment),
|
|
185
|
+
fingerprint: environmentIdentity,
|
|
186
|
+
};
|
|
187
|
+
if (result.status === "passed" &&
|
|
188
|
+
tools.some((tool) => tool.status !== "identified")) {
|
|
189
|
+
result.status = "inconclusive";
|
|
190
|
+
result.reason =
|
|
191
|
+
"The check completed but required tool identity could not be established.";
|
|
192
|
+
}
|
|
193
|
+
if (executionError) {
|
|
194
|
+
result.status = "error";
|
|
195
|
+
result.reason = "The command could not be prepared or started safely.";
|
|
196
|
+
}
|
|
197
|
+
checks.push(result);
|
|
198
|
+
}
|
|
199
|
+
let finalSourceFingerprint = null;
|
|
200
|
+
try {
|
|
201
|
+
finalSourceFingerprint = (await inventory(source.root)).fingerprint;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
/* The report must retain results when source inspection fails. */
|
|
205
|
+
}
|
|
206
|
+
let sourceError = finalSourceFingerprint === null;
|
|
207
|
+
let sourceChanged = !sourceError && finalSourceFingerprint !== source.fingerprint;
|
|
208
|
+
if (plan.selection?.git) {
|
|
209
|
+
const state = await verifyGitIdentity(source.root, plan.selection.git);
|
|
210
|
+
sourceError ||= state === "error";
|
|
211
|
+
sourceChanged ||= state === "changed";
|
|
212
|
+
}
|
|
213
|
+
try {
|
|
214
|
+
const currentPolicy = await loadConfig(source, options.policyOverlay);
|
|
215
|
+
const currentExternal = await loadExternalAdapters(externalAdapters);
|
|
216
|
+
sourceChanged ||=
|
|
217
|
+
externalPolicyFingerprint(currentPolicy.fingerprint, currentExternal) !==
|
|
218
|
+
plan.policyFingerprint;
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
sourceError = true;
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
...(plan.selection ? { selection: plan.selection } : {}),
|
|
225
|
+
schemaVersion: 1,
|
|
226
|
+
engineVersion: VERSION,
|
|
227
|
+
runId: randomUUID(),
|
|
228
|
+
startedAt: new Date(started).toISOString(),
|
|
229
|
+
durationMs: Date.now() - started,
|
|
230
|
+
sourceFingerprint: source.fingerprint,
|
|
231
|
+
finalSourceFingerprint,
|
|
232
|
+
policyFingerprint: plan.policyFingerprint,
|
|
233
|
+
excluded: source.excluded,
|
|
234
|
+
sourceChanged,
|
|
235
|
+
sourceError,
|
|
236
|
+
outcome: aggregate(checks, sourceChanged || sourceError),
|
|
237
|
+
checks,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const environmentName: z.ZodString;
|
|
3
|
+
export declare function operatorEnvironment(input?: Record<string, string>): Record<string, string>;
|
|
4
|
+
export declare function inheritEnvironment(names: string[], source?: NodeJS.ProcessEnv): Record<string, string>;
|
|
5
|
+
export declare function selectEnvironment(names: string[], supplied: Record<string, string>): Record<string, string>;
|
|
6
|
+
export declare function environmentFingerprint(environment: Record<string, string>): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
export const environmentName = z
|
|
4
|
+
.string()
|
|
5
|
+
.max(128)
|
|
6
|
+
.regex(/^[A-Z_][A-Z0-9_]*$/);
|
|
7
|
+
const environmentSchema = z
|
|
8
|
+
.record(environmentName, z
|
|
9
|
+
.string()
|
|
10
|
+
.max(16_384)
|
|
11
|
+
.refine((value) => !value.includes("\0")))
|
|
12
|
+
.refine((value) => Object.keys(value).length <= 64);
|
|
13
|
+
export function operatorEnvironment(input = {}) {
|
|
14
|
+
return environmentSchema.parse(input);
|
|
15
|
+
}
|
|
16
|
+
export function inheritEnvironment(names, source = process.env) {
|
|
17
|
+
if (new Set(names).size !== names.length)
|
|
18
|
+
throw new Error("Duplicate environment permission");
|
|
19
|
+
return operatorEnvironment(Object.fromEntries(names.map((name) => {
|
|
20
|
+
environmentName.parse(name);
|
|
21
|
+
if (!Object.hasOwn(source, name) || source[name] === undefined)
|
|
22
|
+
throw new Error(`Allowed environment variable is unset: ${name}`);
|
|
23
|
+
return [name, source[name]];
|
|
24
|
+
})));
|
|
25
|
+
}
|
|
26
|
+
export function selectEnvironment(names, supplied) {
|
|
27
|
+
return Object.fromEntries(names
|
|
28
|
+
.filter((name) => Object.hasOwn(supplied, name))
|
|
29
|
+
.sort()
|
|
30
|
+
.map((name) => [name, supplied[name]]));
|
|
31
|
+
}
|
|
32
|
+
export function environmentFingerprint(environment) {
|
|
33
|
+
return createHash("sha256")
|
|
34
|
+
.update(JSON.stringify(Object.entries(environment).sort(([a], [b]) => a.localeCompare(b, "en"))))
|
|
35
|
+
.digest("hex");
|
|
36
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
const count = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
|
|
4
|
+
const evidenceSchema = z.object({
|
|
5
|
+
schemaVersion: z.literal(1),
|
|
6
|
+
toolVersion: z.string().regex(/^\d+\.\d+\.\d+/),
|
|
7
|
+
files: z.array(z.object({
|
|
8
|
+
path: z.string(),
|
|
9
|
+
configured: z.boolean(),
|
|
10
|
+
activeRules: count,
|
|
11
|
+
results: z.array(z.object({
|
|
12
|
+
filePath: z.string(),
|
|
13
|
+
errorCount: count,
|
|
14
|
+
warningCount: count,
|
|
15
|
+
fatalErrorCount: count,
|
|
16
|
+
messages: z.array(z.object({
|
|
17
|
+
ruleId: z.string().min(1).nullable(),
|
|
18
|
+
severity: z.union([z.literal(1), z.literal(2)]),
|
|
19
|
+
fatal: z.boolean().optional(),
|
|
20
|
+
message: z.string(),
|
|
21
|
+
line: z.number().int().positive().optional(),
|
|
22
|
+
})),
|
|
23
|
+
})),
|
|
24
|
+
})),
|
|
25
|
+
});
|
|
26
|
+
export function eslintEvidence(check, processes, root) {
|
|
27
|
+
if (processes.some((p) => p.exitCode !== 0 && p.exitCode !== 1))
|
|
28
|
+
return {
|
|
29
|
+
status: "error",
|
|
30
|
+
reason: "ESLint could not complete; inspect its configuration, plugins and runtime locally.",
|
|
31
|
+
};
|
|
32
|
+
const incomplete = {
|
|
33
|
+
status: "inconclusive",
|
|
34
|
+
reason: "ESLint evidence is incomplete, inconsistent, or does not cover every planned file with enabled rules.",
|
|
35
|
+
};
|
|
36
|
+
if (!root ||
|
|
37
|
+
processes.length !== 1 ||
|
|
38
|
+
processes[0].stderr.trim() ||
|
|
39
|
+
!check.scope.length)
|
|
40
|
+
return incomplete;
|
|
41
|
+
try {
|
|
42
|
+
const evidence = evidenceSchema.parse(JSON.parse(processes[0].stdout));
|
|
43
|
+
const expected = new Set(check.scope);
|
|
44
|
+
const seen = new Set();
|
|
45
|
+
let violations = 0;
|
|
46
|
+
const findings = [];
|
|
47
|
+
for (const file of evidence.files) {
|
|
48
|
+
if (!expected.has(file.path) ||
|
|
49
|
+
seen.has(file.path) ||
|
|
50
|
+
!file.configured ||
|
|
51
|
+
file.activeRules === 0 ||
|
|
52
|
+
file.results.length !== 1)
|
|
53
|
+
return incomplete;
|
|
54
|
+
seen.add(file.path);
|
|
55
|
+
const result = file.results[0];
|
|
56
|
+
if (result.filePath !== path.resolve(root, check.project, file.path))
|
|
57
|
+
return incomplete;
|
|
58
|
+
if (result.errorCount !==
|
|
59
|
+
result.messages.filter((m) => m.severity === 2).length ||
|
|
60
|
+
result.warningCount !==
|
|
61
|
+
result.messages.filter((m) => m.severity === 1).length ||
|
|
62
|
+
result.fatalErrorCount !==
|
|
63
|
+
result.messages.filter((m) => m.fatal).length ||
|
|
64
|
+
result.messages.some((m) => (m.fatal && m.severity !== 2) ||
|
|
65
|
+
(m.severity === 1 && m.ruleId === null)))
|
|
66
|
+
return incomplete;
|
|
67
|
+
violations += result.errorCount + result.warningCount;
|
|
68
|
+
findings.push(...result.messages.map((message) => ({
|
|
69
|
+
ruleId: message.ruleId ?? "parse-error",
|
|
70
|
+
level: message.severity === 2 ? "error" : "warning",
|
|
71
|
+
message: message.message,
|
|
72
|
+
file: path.posix.join(check.project, file.path),
|
|
73
|
+
...(message.line ? { line: message.line } : {}),
|
|
74
|
+
})));
|
|
75
|
+
}
|
|
76
|
+
if (seen.size !== expected.size)
|
|
77
|
+
return incomplete;
|
|
78
|
+
if (violations)
|
|
79
|
+
return {
|
|
80
|
+
status: "failed",
|
|
81
|
+
reason: "ESLint reported errors or warnings in the planned files.",
|
|
82
|
+
findings,
|
|
83
|
+
findingsComplete: processes[0].exitCode === 1 &&
|
|
84
|
+
findings.every((finding) => finding.ruleId !== "parse-error"),
|
|
85
|
+
};
|
|
86
|
+
if (processes[0].exitCode !== 0)
|
|
87
|
+
return incomplete;
|
|
88
|
+
return {
|
|
89
|
+
status: "passed",
|
|
90
|
+
findings,
|
|
91
|
+
findingsComplete: true,
|
|
92
|
+
reason: "ESLint checked every planned file with enabled rules and reported no errors or warnings.",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return incomplete;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { readProjectFile, withinRoot } from "./inventory.js";
|
|
4
|
+
async function main() {
|
|
5
|
+
const [entry, root, config, ...files] = process.argv.slice(2);
|
|
6
|
+
if (!entry || !root || !config || !files.length)
|
|
7
|
+
throw new Error("Invalid ESLint runner arguments");
|
|
8
|
+
const cwd = process.cwd();
|
|
9
|
+
const tool = await withinRoot(root, path.relative(root, entry));
|
|
10
|
+
const configPath = await withinRoot(root, path.relative(root, path.resolve(cwd, config)));
|
|
11
|
+
const { ESLint } = (await import(pathToFileURL(tool).href));
|
|
12
|
+
const eslint = new ESLint({
|
|
13
|
+
cwd,
|
|
14
|
+
overrideConfigFile: configPath,
|
|
15
|
+
fix: false,
|
|
16
|
+
cache: false,
|
|
17
|
+
});
|
|
18
|
+
const evidence = [];
|
|
19
|
+
let violations = false;
|
|
20
|
+
for (const file of files) {
|
|
21
|
+
const filePath = await withinRoot(root, path.relative(root, path.resolve(cwd, file)));
|
|
22
|
+
const configuration = await eslint.calculateConfigForFile(filePath);
|
|
23
|
+
const activeRules = Object.values(configuration?.rules ?? {}).filter((rule) => {
|
|
24
|
+
const severity = Array.isArray(rule) ? rule[0] : rule;
|
|
25
|
+
return (severity === 1 ||
|
|
26
|
+
severity === 2 ||
|
|
27
|
+
severity === "warn" ||
|
|
28
|
+
severity === "error");
|
|
29
|
+
}).length;
|
|
30
|
+
const source = configuration
|
|
31
|
+
? await readProjectFile(root, path.relative(root, filePath))
|
|
32
|
+
: undefined;
|
|
33
|
+
const results = source === undefined
|
|
34
|
+
? []
|
|
35
|
+
: await eslint.lintText(source, { filePath, warnIgnored: true });
|
|
36
|
+
for (const result of results)
|
|
37
|
+
violations ||= result.errorCount + result.warningCount > 0;
|
|
38
|
+
evidence.push({
|
|
39
|
+
path: file,
|
|
40
|
+
configured: configuration !== undefined,
|
|
41
|
+
activeRules,
|
|
42
|
+
results: results.map(({ filePath, messages, errorCount, warningCount, fatalErrorCount, }) => ({
|
|
43
|
+
filePath,
|
|
44
|
+
messages,
|
|
45
|
+
errorCount,
|
|
46
|
+
warningCount,
|
|
47
|
+
fatalErrorCount,
|
|
48
|
+
})),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
process.stdout.write(JSON.stringify({
|
|
52
|
+
schemaVersion: 1,
|
|
53
|
+
toolVersion: ESLint.version,
|
|
54
|
+
files: evidence,
|
|
55
|
+
}) + "\n");
|
|
56
|
+
process.exitCode = violations ? 1 : 0;
|
|
57
|
+
}
|
|
58
|
+
main().catch((error) => {
|
|
59
|
+
process.stderr.write(`${error instanceof Error ? error.message : "ESLint execution failed"}\n`);
|
|
60
|
+
process.exitCode = 2;
|
|
61
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { localTool } from "./local-tool.js";
|
|
3
|
+
export async function eslintCheck(source, project) {
|
|
4
|
+
const files = project.files.filter((file) => /\.(?:[cm]?[jt]s|[jt]sx|vue)$/.test(file));
|
|
5
|
+
const configs = project.files.filter((file) => /^eslint\.config\.(?:js|mjs|cjs)$/.test(file));
|
|
6
|
+
const tool = await localTool(source.root, project.path, "eslint/lib/api.js");
|
|
7
|
+
const check = {
|
|
8
|
+
id: "javascript.eslint",
|
|
9
|
+
adapter: project.adapter,
|
|
10
|
+
project: project.path,
|
|
11
|
+
scope: files,
|
|
12
|
+
kind: "analysis",
|
|
13
|
+
parser: "eslint-json",
|
|
14
|
+
reason: "Lint each inventoried JS/TS/Vue file with the project's flat config; require matching configuration and enabled rules for every file.",
|
|
15
|
+
commands: tool && configs.length === 1
|
|
16
|
+
? [
|
|
17
|
+
{
|
|
18
|
+
executable: process.execPath,
|
|
19
|
+
args: [
|
|
20
|
+
fileURLToPath(new URL("./eslint-runner.js", import.meta.url)),
|
|
21
|
+
tool,
|
|
22
|
+
source.root,
|
|
23
|
+
configs[0],
|
|
24
|
+
...files,
|
|
25
|
+
],
|
|
26
|
+
cwd: project.path,
|
|
27
|
+
},
|
|
28
|
+
]
|
|
29
|
+
: [],
|
|
30
|
+
};
|
|
31
|
+
if (configs.length !== 1)
|
|
32
|
+
check.unavailableReason =
|
|
33
|
+
"Exactly one project-local eslint.config.js, .mjs or .cjs is required.";
|
|
34
|
+
else if (!files.length)
|
|
35
|
+
check.unavailableReason = "No supported source files were inventoried.";
|
|
36
|
+
else if (!tool)
|
|
37
|
+
check.unavailableReason =
|
|
38
|
+
"No project-local or root-hoisted ESLint installation is available within the configured root.";
|
|
39
|
+
return check;
|
|
40
|
+
}
|