@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,46 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { withinRoot } from "./inventory.js";
|
|
4
|
+
async function main() {
|
|
5
|
+
const [entry, root, ...files] = process.argv.slice(2);
|
|
6
|
+
if (!entry || !root || !files.length)
|
|
7
|
+
throw new Error("Invalid Jest runner arguments");
|
|
8
|
+
const tool = await withinRoot(root, path.relative(root, entry));
|
|
9
|
+
const { runCLI } = createRequire(import.meta.url)(tool);
|
|
10
|
+
const paths = [];
|
|
11
|
+
for (const file of files)
|
|
12
|
+
paths.push(await withinRoot(root, path.relative(root, path.resolve(file))));
|
|
13
|
+
const { results } = await runCLI({
|
|
14
|
+
$0: "checktrail",
|
|
15
|
+
_: paths,
|
|
16
|
+
ci: true,
|
|
17
|
+
runInBand: true,
|
|
18
|
+
runTestsByPath: true,
|
|
19
|
+
watch: false,
|
|
20
|
+
watchAll: false,
|
|
21
|
+
updateSnapshot: false,
|
|
22
|
+
cache: false,
|
|
23
|
+
collectTests: false,
|
|
24
|
+
listTests: false,
|
|
25
|
+
passWithNoTests: false,
|
|
26
|
+
onlyChanged: false,
|
|
27
|
+
onlyFailures: false,
|
|
28
|
+
findRelatedTests: false,
|
|
29
|
+
testNamePattern: "",
|
|
30
|
+
testResultsProcessor: "",
|
|
31
|
+
filter: "",
|
|
32
|
+
bail: 0,
|
|
33
|
+
json: false,
|
|
34
|
+
useStderr: true,
|
|
35
|
+
reporters: ["default"],
|
|
36
|
+
runner: "jest-runner",
|
|
37
|
+
testRunner: "jest-circus/runner",
|
|
38
|
+
}, [process.cwd()]);
|
|
39
|
+
const evidence = { ...results, coverageMap: undefined };
|
|
40
|
+
process.stdout.write(`${JSON.stringify(evidence)}\n`);
|
|
41
|
+
process.exitCode = results.success ? 0 : 1;
|
|
42
|
+
}
|
|
43
|
+
main().catch((error) => {
|
|
44
|
+
process.stderr.write(`${error instanceof Error ? error.message : "Jest execution failed"}\n`);
|
|
45
|
+
process.exitCode = 2;
|
|
46
|
+
});
|
package/dist/src/jest.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { localTool } from "./local-tool.js";
|
|
3
|
+
export async function jestCheck(source, project) {
|
|
4
|
+
const files = project.files.filter((file) => /(?:\.(?:test|spec)\.(?:[cm]?[jt]s|[jt]sx)$|(?:^|\/)__tests__\/.*\.(?:[cm]?[jt]s|[jt]sx)$)/.test(file));
|
|
5
|
+
const tool = await localTool(source.root, project.path, "jest/build/index.js");
|
|
6
|
+
const check = {
|
|
7
|
+
id: "javascript.jest",
|
|
8
|
+
adapter: project.adapter,
|
|
9
|
+
project: project.path,
|
|
10
|
+
scope: files,
|
|
11
|
+
kind: "test",
|
|
12
|
+
parser: "jest-json",
|
|
13
|
+
reason: "Run installed Jest once and account for every planned test file; require passing non-skipped assertions and consistent native JSON evidence.",
|
|
14
|
+
commands: tool
|
|
15
|
+
? [
|
|
16
|
+
{
|
|
17
|
+
executable: process.execPath,
|
|
18
|
+
args: [
|
|
19
|
+
fileURLToPath(new URL("./jest-runner.js", import.meta.url)),
|
|
20
|
+
tool,
|
|
21
|
+
source.root,
|
|
22
|
+
...files,
|
|
23
|
+
],
|
|
24
|
+
cwd: project.path,
|
|
25
|
+
env: { CI: "1" },
|
|
26
|
+
},
|
|
27
|
+
]
|
|
28
|
+
: [],
|
|
29
|
+
};
|
|
30
|
+
if (!files.length)
|
|
31
|
+
check.unavailableReason = "No .test or .spec JS/TS files were inventoried.";
|
|
32
|
+
else if (!tool)
|
|
33
|
+
check.unavailableReason =
|
|
34
|
+
"Jest is not installed within the configured root.";
|
|
35
|
+
return check;
|
|
36
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TestEvidence } from "./types.js";
|
|
2
|
+
export interface JUnitCase {
|
|
3
|
+
name: string;
|
|
4
|
+
file?: string;
|
|
5
|
+
status: "passed" | "failed" | "skipped";
|
|
6
|
+
assertions?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface JUnitImport {
|
|
9
|
+
schemaVersion: 1;
|
|
10
|
+
engineVersion: string;
|
|
11
|
+
format: "junit";
|
|
12
|
+
provenance: "imported-report";
|
|
13
|
+
outcome: "passed" | "failed" | "incomplete";
|
|
14
|
+
reason: string;
|
|
15
|
+
tests?: TestEvidence;
|
|
16
|
+
cases?: JUnitCase[];
|
|
17
|
+
}
|
|
18
|
+
export declare function importJUnit(xml: string): JUnitImport;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { XMLParser, XMLValidator } from "fast-xml-parser";
|
|
2
|
+
import { VERSION } from "./types.js";
|
|
3
|
+
function object(value) {
|
|
4
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
5
|
+
throw new Error("Expected XML element");
|
|
6
|
+
return value;
|
|
7
|
+
}
|
|
8
|
+
const array = (value) => value === undefined ? [] : Array.isArray(value) ? value : [value];
|
|
9
|
+
function count(value) {
|
|
10
|
+
if (typeof value !== "string" ||
|
|
11
|
+
!/^\d+$/.test(value) ||
|
|
12
|
+
!Number.isSafeInteger(Number(value)))
|
|
13
|
+
throw new Error("Invalid JUnit counter");
|
|
14
|
+
return Number(value);
|
|
15
|
+
}
|
|
16
|
+
export function importJUnit(xml) {
|
|
17
|
+
const base = {
|
|
18
|
+
schemaVersion: 1,
|
|
19
|
+
engineVersion: VERSION,
|
|
20
|
+
format: "junit",
|
|
21
|
+
provenance: "imported-report",
|
|
22
|
+
outcome: "incomplete",
|
|
23
|
+
reason: "JUnit evidence is malformed, inconsistent, empty, or entirely skipped; an import does not establish execution freshness.",
|
|
24
|
+
};
|
|
25
|
+
if (Buffer.byteLength(xml) > 8 * 1024 * 1024 ||
|
|
26
|
+
/<!\s*(?:DOCTYPE|ENTITY)/i.test(xml))
|
|
27
|
+
return base;
|
|
28
|
+
try {
|
|
29
|
+
if (XMLValidator.validate(xml) !== true)
|
|
30
|
+
return base;
|
|
31
|
+
const parser = new XMLParser({
|
|
32
|
+
ignoreAttributes: false,
|
|
33
|
+
parseAttributeValue: false,
|
|
34
|
+
parseTagValue: false,
|
|
35
|
+
ignoreDeclaration: true,
|
|
36
|
+
processEntities: true,
|
|
37
|
+
htmlEntities: false,
|
|
38
|
+
});
|
|
39
|
+
const root = object(parser.parse(xml));
|
|
40
|
+
if (Object.keys(root).length !== 1 ||
|
|
41
|
+
!("testsuite" in root || "testsuites" in root))
|
|
42
|
+
return base;
|
|
43
|
+
const cases = [];
|
|
44
|
+
const identities = new Set();
|
|
45
|
+
const walk = (value, depth, trail, wrapper = false) => {
|
|
46
|
+
if (depth > 32)
|
|
47
|
+
throw new Error("JUnit nesting limit");
|
|
48
|
+
const suite = object(value);
|
|
49
|
+
if (wrapper && suite.testcase !== undefined)
|
|
50
|
+
throw new Error("Test case outside suite");
|
|
51
|
+
if (Object.keys(suite).some((key) => !key.startsWith("@_") &&
|
|
52
|
+
![
|
|
53
|
+
"testsuite",
|
|
54
|
+
"testcase",
|
|
55
|
+
"properties",
|
|
56
|
+
"system-out",
|
|
57
|
+
"system-err",
|
|
58
|
+
].includes(key)))
|
|
59
|
+
throw new Error("Unknown JUnit suite element");
|
|
60
|
+
const names = [
|
|
61
|
+
...trail,
|
|
62
|
+
typeof suite["@_name"] === "string" ? suite["@_name"] : "",
|
|
63
|
+
];
|
|
64
|
+
const counts = { total: 0, failures: 0, errors: 0, skipped: 0 };
|
|
65
|
+
for (const value of array(suite.testcase)) {
|
|
66
|
+
if (cases.length >= 100_000)
|
|
67
|
+
throw new Error("JUnit case limit");
|
|
68
|
+
const entry = object(value);
|
|
69
|
+
if (Object.keys(entry).some((key) => !key.startsWith("@_") &&
|
|
70
|
+
![
|
|
71
|
+
"failure",
|
|
72
|
+
"error",
|
|
73
|
+
"skipped",
|
|
74
|
+
"properties",
|
|
75
|
+
"system-out",
|
|
76
|
+
"system-err",
|
|
77
|
+
].includes(key)))
|
|
78
|
+
throw new Error("Unknown JUnit case element");
|
|
79
|
+
if (typeof entry["@_name"] !== "string" || !entry["@_name"].length)
|
|
80
|
+
throw new Error("Missing test name");
|
|
81
|
+
const identity = JSON.stringify([
|
|
82
|
+
names,
|
|
83
|
+
entry["@_file"],
|
|
84
|
+
entry["@_classname"],
|
|
85
|
+
entry["@_class"],
|
|
86
|
+
entry["@_name"],
|
|
87
|
+
]);
|
|
88
|
+
if (identities.has(identity))
|
|
89
|
+
throw new Error("Duplicate JUnit case");
|
|
90
|
+
identities.add(identity);
|
|
91
|
+
const failures = array(entry.failure).length;
|
|
92
|
+
const errors = array(entry.error).length;
|
|
93
|
+
const skipped = array(entry.skipped).length;
|
|
94
|
+
if (failures + errors + skipped > 1)
|
|
95
|
+
throw new Error("Contradictory JUnit case states");
|
|
96
|
+
const item = {
|
|
97
|
+
name: entry["@_name"],
|
|
98
|
+
status: failures || errors ? "failed" : skipped ? "skipped" : "passed",
|
|
99
|
+
};
|
|
100
|
+
if (entry["@_file"] !== undefined) {
|
|
101
|
+
if (typeof entry["@_file"] !== "string" || !entry["@_file"].length)
|
|
102
|
+
throw new Error("Invalid JUnit file");
|
|
103
|
+
item.file = entry["@_file"];
|
|
104
|
+
}
|
|
105
|
+
if (entry["@_assertions"] !== undefined)
|
|
106
|
+
item.assertions = count(entry["@_assertions"]);
|
|
107
|
+
cases.push(item);
|
|
108
|
+
counts.total++;
|
|
109
|
+
counts.failures += failures;
|
|
110
|
+
counts.errors += errors;
|
|
111
|
+
counts.skipped += skipped;
|
|
112
|
+
}
|
|
113
|
+
for (const child of array(suite.testsuite)) {
|
|
114
|
+
const nested = walk(child, depth + 1, names);
|
|
115
|
+
for (const key of ["total", "failures", "errors", "skipped"])
|
|
116
|
+
counts[key] += nested[key];
|
|
117
|
+
}
|
|
118
|
+
for (const [attribute, key] of [
|
|
119
|
+
["tests", "total"],
|
|
120
|
+
["failures", "failures"],
|
|
121
|
+
["errors", "errors"],
|
|
122
|
+
["skipped", "skipped"],
|
|
123
|
+
]) {
|
|
124
|
+
const declared = suite[`@_${attribute}`];
|
|
125
|
+
if (declared !== undefined && count(declared) !== counts[key])
|
|
126
|
+
throw new Error("JUnit counts disagree");
|
|
127
|
+
if (!wrapper && attribute === "tests" && declared === undefined)
|
|
128
|
+
throw new Error("Missing suite count");
|
|
129
|
+
}
|
|
130
|
+
return counts;
|
|
131
|
+
};
|
|
132
|
+
const value = "testsuites" in root
|
|
133
|
+
? walk(root.testsuites, 0, [], true)
|
|
134
|
+
: walk(root.testsuite, 0, []);
|
|
135
|
+
const tests = {
|
|
136
|
+
total: value.total,
|
|
137
|
+
passed: value.total - value.failures - value.errors - value.skipped,
|
|
138
|
+
failed: value.failures + value.errors,
|
|
139
|
+
skipped: value.skipped,
|
|
140
|
+
};
|
|
141
|
+
const outcome = tests.failed
|
|
142
|
+
? "failed"
|
|
143
|
+
: tests.passed
|
|
144
|
+
? "passed"
|
|
145
|
+
: "incomplete";
|
|
146
|
+
return {
|
|
147
|
+
...base,
|
|
148
|
+
outcome,
|
|
149
|
+
tests,
|
|
150
|
+
cases,
|
|
151
|
+
reason: "Imported JUnit counts describe this report only; source identity, scope and execution freshness are not established.",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return base;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runtimeInventorySchema } from "./runtime-inventory.js";
|
|
3
|
+
import { laravelConfigSchema } from "./laravel.js";
|
|
4
|
+
const text = z.string();
|
|
5
|
+
const hash = text.regex(/^[a-f0-9]{64}$/);
|
|
6
|
+
const attributes = {
|
|
7
|
+
routes: z.strictObject({
|
|
8
|
+
domain: text.nullable(),
|
|
9
|
+
method: text.min(1),
|
|
10
|
+
uri: text.min(1),
|
|
11
|
+
name: text.nullable(),
|
|
12
|
+
handler: text.min(1),
|
|
13
|
+
middleware: z.array(text),
|
|
14
|
+
constraintsHash: hash,
|
|
15
|
+
defaultsHash: hash,
|
|
16
|
+
}),
|
|
17
|
+
middleware: z.discriminatedUnion("type", [
|
|
18
|
+
z.strictObject({
|
|
19
|
+
type: z.enum(["global", "priority"]),
|
|
20
|
+
stack: z.array(text),
|
|
21
|
+
}),
|
|
22
|
+
z.strictObject({
|
|
23
|
+
type: z.literal("group"),
|
|
24
|
+
name: text,
|
|
25
|
+
stack: z.array(text),
|
|
26
|
+
}),
|
|
27
|
+
z.strictObject({ type: z.literal("alias"), name: text, target: text }),
|
|
28
|
+
]),
|
|
29
|
+
listeners: z.strictObject({
|
|
30
|
+
type: z.enum(["exact", "wildcard"]),
|
|
31
|
+
event: text,
|
|
32
|
+
listener: text,
|
|
33
|
+
}),
|
|
34
|
+
schedules: z.strictObject({
|
|
35
|
+
type: z.enum(["callback", "command"]),
|
|
36
|
+
target: text,
|
|
37
|
+
expression: text,
|
|
38
|
+
repeatSeconds: z.number().int().nullable(),
|
|
39
|
+
user: text.nullable(),
|
|
40
|
+
environments: z.array(text),
|
|
41
|
+
evenInMaintenanceMode: z.boolean(),
|
|
42
|
+
evenWhenPaused: z.boolean(),
|
|
43
|
+
withoutOverlapping: z.boolean(),
|
|
44
|
+
releaseOnTerminationSignals: z.boolean(),
|
|
45
|
+
onOneServer: z.boolean(),
|
|
46
|
+
expiresAt: z.number().int(),
|
|
47
|
+
runInBackground: z.boolean(),
|
|
48
|
+
description: text.nullable(),
|
|
49
|
+
output: text,
|
|
50
|
+
shouldAppendOutput: z.boolean(),
|
|
51
|
+
timezone: text.nullable(),
|
|
52
|
+
parametersHash: hash,
|
|
53
|
+
filters: z.array(text),
|
|
54
|
+
rejects: z.array(text),
|
|
55
|
+
beforeCallbacks: z.array(text),
|
|
56
|
+
afterCallbacks: z.array(text),
|
|
57
|
+
mutex: text,
|
|
58
|
+
mutexNameResolver: text.nullable(),
|
|
59
|
+
attributesHash: hash,
|
|
60
|
+
}),
|
|
61
|
+
bindings: z.discriminatedUnion("type", [
|
|
62
|
+
z.strictObject({
|
|
63
|
+
type: z.literal("factory"),
|
|
64
|
+
abstract: text,
|
|
65
|
+
target: text,
|
|
66
|
+
shared: z.boolean(),
|
|
67
|
+
scoped: z.boolean(),
|
|
68
|
+
}),
|
|
69
|
+
z.strictObject({
|
|
70
|
+
type: z.enum(["alias", "instance"]),
|
|
71
|
+
abstract: text,
|
|
72
|
+
target: text,
|
|
73
|
+
}),
|
|
74
|
+
z.strictObject({
|
|
75
|
+
type: z.literal("contextual"),
|
|
76
|
+
consumer: text,
|
|
77
|
+
abstract: text,
|
|
78
|
+
target: text,
|
|
79
|
+
}),
|
|
80
|
+
]),
|
|
81
|
+
};
|
|
82
|
+
const schema = z.strictObject({
|
|
83
|
+
version: z.literal(1),
|
|
84
|
+
laravelVersion: z.literal("13.32.0"),
|
|
85
|
+
entryCount: z.number().int().nonnegative().max(20_000),
|
|
86
|
+
runtime: runtimeInventorySchema,
|
|
87
|
+
});
|
|
88
|
+
export function laravelEvidence(check, processes) {
|
|
89
|
+
const incomplete = {
|
|
90
|
+
status: "inconclusive",
|
|
91
|
+
reason: "Laravel assembly projection is unsupported, malformed, empty or incomplete.",
|
|
92
|
+
};
|
|
93
|
+
if (processes.length !== 1)
|
|
94
|
+
return incomplete;
|
|
95
|
+
if (processes[0].exitCode === 3) {
|
|
96
|
+
try {
|
|
97
|
+
z.strictObject({
|
|
98
|
+
unavailable: z.literal("laravel-runtime"),
|
|
99
|
+
reason: z.literal("unsupported-version"),
|
|
100
|
+
}).parse(JSON.parse(processes[0].stdout));
|
|
101
|
+
return {
|
|
102
|
+
status: "unavailable",
|
|
103
|
+
reason: "The pinned Laravel runtime profile is not installed.",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return incomplete;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (processes[0].exitCode !== 0)
|
|
111
|
+
return {
|
|
112
|
+
status: "error",
|
|
113
|
+
reason: "Laravel bootstrap or runtime collector failed.",
|
|
114
|
+
};
|
|
115
|
+
try {
|
|
116
|
+
const result = schema.parse(JSON.parse(processes[0].stdout));
|
|
117
|
+
const profile = laravelConfigSchema.parse(JSON.parse(check.commands[0].args[3]));
|
|
118
|
+
const runtime = result.runtime;
|
|
119
|
+
if (runtime.sourceFingerprint !== check.commands[0].args[4] ||
|
|
120
|
+
runtime.producer.name !== "checktrail.laravel-runtime" ||
|
|
121
|
+
runtime.producer.version !== "1.0.0" ||
|
|
122
|
+
runtime.assembly.name !== profile.assembly ||
|
|
123
|
+
runtime.assembly.environment !== profile.environment ||
|
|
124
|
+
runtime.collections.length !== 5 ||
|
|
125
|
+
new Set(runtime.collections.map((item) => item.kind)).size !== 5 ||
|
|
126
|
+
runtime.collections.reduce((sum, item) => sum + item.entries.length, 0) !== result.entryCount)
|
|
127
|
+
return incomplete;
|
|
128
|
+
for (const collection of runtime.collections) {
|
|
129
|
+
if (collection.ordered !== (collection.kind !== "bindings"))
|
|
130
|
+
return incomplete;
|
|
131
|
+
for (const entry of collection.entries) {
|
|
132
|
+
const value = attributes[collection.kind].parse(entry.attributes);
|
|
133
|
+
if (collection.kind === "routes") {
|
|
134
|
+
const route = attributes.routes.parse(value);
|
|
135
|
+
if (JSON.stringify(JSON.parse(entry.key)) !==
|
|
136
|
+
JSON.stringify([route.domain, route.method, route.uri]))
|
|
137
|
+
return incomplete;
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
const record = value;
|
|
141
|
+
const expected = collection.kind === "listeners"
|
|
142
|
+
? `${record.type}:${record.event}`
|
|
143
|
+
: collection.kind === "schedules"
|
|
144
|
+
? `${record.type}:${record.target}`
|
|
145
|
+
: collection.kind === "bindings"
|
|
146
|
+
? record.type === "contextual"
|
|
147
|
+
? `contextual:${record.consumer}:${record.abstract}`
|
|
148
|
+
: `${record.type}:${record.abstract}`
|
|
149
|
+
: record.type === "global" || record.type === "priority"
|
|
150
|
+
? record.type
|
|
151
|
+
: `${record.type}:${record.name}`;
|
|
152
|
+
if (entry.key !== expected)
|
|
153
|
+
return incomplete;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (!runtime.collections.find((item) => item.kind === "routes").entries
|
|
158
|
+
.length ||
|
|
159
|
+
!runtime.collections.find((item) => item.kind === "bindings").entries
|
|
160
|
+
.length ||
|
|
161
|
+
["global", "priority"].some((key) => runtime.collections
|
|
162
|
+
.find((item) => item.kind === "middleware")
|
|
163
|
+
.entries.filter((entry) => entry.key === key).length !== 1) ||
|
|
164
|
+
runtime.collections.some((item) => !item.complete))
|
|
165
|
+
return { ...incomplete, runtime };
|
|
166
|
+
return {
|
|
167
|
+
status: "passed",
|
|
168
|
+
reason: "Laravel testing bootstrap completed and all five declared assembly projections were captured; compare inventories to evaluate wiring changes.",
|
|
169
|
+
runtime,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
return incomplete;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const laravelRunner: string;
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
export const laravelRunner = String.raw `
|
|
2
|
+
function rv_property(object $object, string $property): mixed {
|
|
3
|
+
return (new ReflectionProperty($object, $property))->getValue($object);
|
|
4
|
+
}
|
|
5
|
+
function rv_identity(mixed $value): string {
|
|
6
|
+
static $sources = [];
|
|
7
|
+
static $sourceBytes = 0;
|
|
8
|
+
if (is_string($value)) return $value;
|
|
9
|
+
if (is_array($value) && count($value) === 2 && isset($value[0], $value[1]) && is_string($value[1])) {
|
|
10
|
+
return (is_object($value[0]) ? get_class($value[0]) : $value[0]).'@'.$value[1];
|
|
11
|
+
}
|
|
12
|
+
if ($value instanceof Closure) {
|
|
13
|
+
$reflection = new ReflectionFunction($value);
|
|
14
|
+
$file = $reflection->getFileName();
|
|
15
|
+
if (!$file || !is_file($file)) throw new RuntimeException('Unsupported closure source');
|
|
16
|
+
$prefix = getcwd().DIRECTORY_SEPARATOR;
|
|
17
|
+
$vendor = dirname($GLOBALS['rv_autoload']).DIRECTORY_SEPARATOR;
|
|
18
|
+
$location = str_starts_with($file, $prefix) ? substr($file, strlen($prefix)) : (str_starts_with($file, $vendor) ? 'vendor/'.substr($file, strlen($vendor)) : null);
|
|
19
|
+
if ($location === null) throw new RuntimeException('Closure source outside project and local vendor');
|
|
20
|
+
if (!isset($sources[$file])) {
|
|
21
|
+
$bytes = file_get_contents($file, false, null, 0, 8 * 1024 * 1024 + 1);
|
|
22
|
+
$sourceBytes += strlen($bytes);
|
|
23
|
+
if (strlen($bytes) > 8 * 1024 * 1024 || $sourceBytes > 64 * 1024 * 1024) throw new RuntimeException('Closure source limit exceeded');
|
|
24
|
+
$sources[$file] = explode("\n", $bytes);
|
|
25
|
+
}
|
|
26
|
+
$lines = $sources[$file];
|
|
27
|
+
$source = implode("\n", array_slice($lines, $reflection->getStartLine() - 1, $reflection->getEndLine() - $reflection->getStartLine() + 1));
|
|
28
|
+
return 'closure:'.$location.':'.$reflection->getStartLine().':'.$reflection->getEndLine().':'.hash('sha256', $source);
|
|
29
|
+
}
|
|
30
|
+
if (is_object($value)) return get_class($value).'@__invoke';
|
|
31
|
+
throw new RuntimeException('Unsupported callable registration');
|
|
32
|
+
}
|
|
33
|
+
function rv_scalar_hash(mixed $value, int $depth = 0): string {
|
|
34
|
+
$walk = function (mixed $value, int $depth) use (&$walk): mixed {
|
|
35
|
+
if ($depth > 32) throw new RuntimeException('Registration data too deep');
|
|
36
|
+
if (is_array($value)) {
|
|
37
|
+
$result = [];
|
|
38
|
+
foreach ($value as $key => $item) $result[$key] = $walk($item, $depth + 1);
|
|
39
|
+
return $result;
|
|
40
|
+
}
|
|
41
|
+
if (is_null($value) || is_scalar($value)) return $value;
|
|
42
|
+
throw new RuntimeException('Unsupported registration data');
|
|
43
|
+
};
|
|
44
|
+
return hash('sha256', json_encode($walk($value, $depth), JSON_THROW_ON_ERROR));
|
|
45
|
+
}
|
|
46
|
+
function rv_collect(string $kind, bool $ordered, callable $read): array {
|
|
47
|
+
$entries = [];
|
|
48
|
+
try {
|
|
49
|
+
$read($entries);
|
|
50
|
+
return ['kind' => $kind, 'complete' => true, 'ordered' => $ordered, 'entries' => $entries];
|
|
51
|
+
} catch (Throwable $error) {
|
|
52
|
+
fwrite(STDERR, $kind.': '.get_class($error).': '.$error->getMessage().PHP_EOL);
|
|
53
|
+
return ['kind' => $kind, 'complete' => false, 'ordered' => $ordered, 'entries' => $entries];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function rv_add(array &$entries, string $key, array $attributes): void {
|
|
57
|
+
if (++$GLOBALS['rv_count'] > 20000) throw new RuntimeException('Assembly entry limit exceeded');
|
|
58
|
+
$entries[] = ['key' => $key, 'attributes' => (object) $attributes];
|
|
59
|
+
}
|
|
60
|
+
$rv_count = 0;
|
|
61
|
+
$rv_temporary = null;
|
|
62
|
+
$rv_autoload = $argv[1];
|
|
63
|
+
ob_start(function ($text) { fwrite(STDERR, $text); return ''; }, 1);
|
|
64
|
+
try {
|
|
65
|
+
require $rv_autoload;
|
|
66
|
+
if (!class_exists(Illuminate\Foundation\Application::class) || Illuminate\Foundation\Application::VERSION !== '13.32.0') {
|
|
67
|
+
fwrite(STDOUT, json_encode(['unavailable' => 'laravel-runtime', 'reason' => 'unsupported-version']));
|
|
68
|
+
exit(3);
|
|
69
|
+
}
|
|
70
|
+
$config = json_decode($argv[2], true, flags: JSON_THROW_ON_ERROR);
|
|
71
|
+
$rv_temporary = sys_get_temp_dir().'/checktrail-laravel-'.bin2hex(random_bytes(16));
|
|
72
|
+
if (!mkdir($rv_temporary, 0700)) throw new RuntimeException('Cannot create temporary cache directory');
|
|
73
|
+
register_shutdown_function(function () use ($rv_temporary) {
|
|
74
|
+
foreach (glob($rv_temporary.'/*') as $file) if (is_file($file) || is_link($file)) @unlink($file);
|
|
75
|
+
@rmdir($rv_temporary);
|
|
76
|
+
});
|
|
77
|
+
$cachePaths = [];
|
|
78
|
+
foreach (['CONFIG', 'ROUTES', 'EVENTS', 'SERVICES', 'PACKAGES'] as $kind) {
|
|
79
|
+
$key = 'APP_'.$kind.'_CACHE';
|
|
80
|
+
$cachePaths[$kind] = $rv_temporary.'/'.strtolower($kind).'.php';
|
|
81
|
+
putenv($key.'='.$cachePaths[$kind]);
|
|
82
|
+
$_ENV[$key] = $_SERVER[$key] = $cachePaths[$kind];
|
|
83
|
+
}
|
|
84
|
+
$app = require getcwd().'/bootstrap/app.php';
|
|
85
|
+
if (!$app instanceof Illuminate\Foundation\Application || realpath($app->basePath()) !== getcwd() || $app->hasBeenBootstrapped()) throw new RuntimeException('Unsupported application bootstrap');
|
|
86
|
+
$app->useEnvironmentPath($rv_temporary)->loadEnvironmentFrom('.env');
|
|
87
|
+
$http = $app->make(Illuminate\Contracts\Http\Kernel::class);
|
|
88
|
+
$console = $app->make(Illuminate\Contracts\Console\Kernel::class);
|
|
89
|
+
$console->bootstrap();
|
|
90
|
+
$console->all();
|
|
91
|
+
$http->bootstrap();
|
|
92
|
+
if ($app->environment() !== 'testing') throw new RuntimeException('Expected isolated testing environment');
|
|
93
|
+
foreach (['CONFIG' => 'getCachedConfigPath', 'ROUTES' => 'getCachedRoutesPath', 'EVENTS' => 'getCachedEventsPath', 'SERVICES' => 'getCachedServicesPath', 'PACKAGES' => 'getCachedPackagesPath'] as $kind => $method) {
|
|
94
|
+
if ($app->$method() !== $cachePaths[$kind]) throw new RuntimeException('Application changed protected cache paths');
|
|
95
|
+
}
|
|
96
|
+
$router = $app->make('router');
|
|
97
|
+
$dispatcher = $app->make('events');
|
|
98
|
+
$schedule = $app->make(Illuminate\Console\Scheduling\Schedule::class);
|
|
99
|
+
$collections = [];
|
|
100
|
+
$collections[] = rv_collect('routes', true, function (&$entries) use ($router) {
|
|
101
|
+
if (get_class($router) !== Illuminate\Routing\Router::class || get_class($router->getRoutes()) !== Illuminate\Routing\RouteCollection::class) throw new RuntimeException('Unsupported router or cached route collection');
|
|
102
|
+
foreach ($router->getRoutes() as $route) {
|
|
103
|
+
if (get_class($route) !== Illuminate\Routing\Route::class) throw new RuntimeException('Unsupported route class');
|
|
104
|
+
$middleware = array_map('rv_identity', $router->gatherRouteMiddleware($route));
|
|
105
|
+
foreach ($route->methods() as $method) {
|
|
106
|
+
rv_add($entries, json_encode([$route->getDomain(), $method, $route->uri()], JSON_THROW_ON_ERROR), [
|
|
107
|
+
'domain' => $route->getDomain(), 'method' => $method, 'uri' => $route->uri(),
|
|
108
|
+
'name' => $route->getName(), 'handler' => rv_identity($route->getAction('uses')),
|
|
109
|
+
'middleware' => array_values($middleware), 'constraintsHash' => rv_scalar_hash($route->wheres), 'defaultsHash' => rv_scalar_hash($route->defaults),
|
|
110
|
+
]);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!$entries) throw new RuntimeException('No served routes');
|
|
114
|
+
});
|
|
115
|
+
$collections[] = rv_collect('middleware', true, function (&$entries) use ($http) {
|
|
116
|
+
foreach (['global' => $http->getGlobalMiddleware(), 'priority' => $http->getMiddlewarePriority()] as $kind => $values) rv_add($entries, $kind, ['type' => $kind, 'stack' => array_values(array_map('rv_identity', $values))]);
|
|
117
|
+
foreach ($http->getMiddlewareGroups() as $name => $values) rv_add($entries, 'group:'.$name, ['type' => 'group', 'name' => $name, 'stack' => array_values(array_map('rv_identity', $values))]);
|
|
118
|
+
foreach ($http->getMiddlewareAliases() as $name => $value) rv_add($entries, 'alias:'.$name, ['type' => 'alias', 'name' => $name, 'target' => rv_identity($value)]);
|
|
119
|
+
});
|
|
120
|
+
$collections[] = rv_collect('listeners', true, function (&$entries) use ($dispatcher) {
|
|
121
|
+
if (get_class($dispatcher) !== Illuminate\Events\Dispatcher::class) throw new RuntimeException('Unsupported event dispatcher');
|
|
122
|
+
foreach (['exact' => $dispatcher->getRawListeners(), 'wildcard' => rv_property($dispatcher, 'wildcards')] as $kind => $events) {
|
|
123
|
+
foreach ($events as $event => $listeners) foreach ($listeners as $listener) rv_add($entries, $kind.':'.$event, ['type' => $kind, 'event' => $event, 'listener' => rv_identity($listener)]);
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
$collections[] = rv_collect('schedules', true, function (&$entries) use ($schedule) {
|
|
127
|
+
if (get_class($schedule) !== Illuminate\Console\Scheduling\Schedule::class) throw new RuntimeException('Unsupported scheduler');
|
|
128
|
+
foreach ($schedule->events() as $event) {
|
|
129
|
+
if (!in_array(get_class($event), [Illuminate\Console\Scheduling\Event::class, Illuminate\Console\Scheduling\CallbackEvent::class], true)) throw new RuntimeException('Unsupported scheduled event');
|
|
130
|
+
$callback = $event instanceof Illuminate\Console\Scheduling\CallbackEvent;
|
|
131
|
+
$target = $callback ? rv_identity(rv_property($event, 'callback')) : $event->command;
|
|
132
|
+
$attributes = ['type' => $callback ? 'callback' : 'command', 'target' => $target];
|
|
133
|
+
foreach (['expression', 'repeatSeconds', 'user', 'environments', 'evenInMaintenanceMode', 'evenWhenPaused', 'withoutOverlapping', 'releaseOnTerminationSignals', 'onOneServer', 'expiresAt', 'runInBackground', 'description', 'output', 'shouldAppendOutput'] as $field) $attributes[$field] = $event->$field;
|
|
134
|
+
$attributes['timezone'] = $event->timezone instanceof DateTimeZone ? $event->timezone->getName() : $event->timezone;
|
|
135
|
+
$attributes['parametersHash'] = rv_scalar_hash($callback ? rv_property($event, 'parameters') : []);
|
|
136
|
+
foreach (['filters', 'rejects', 'beforeCallbacks', 'afterCallbacks'] as $field) $attributes[$field] = array_values(array_map('rv_identity', rv_property($event, $field)));
|
|
137
|
+
$attributes['mutex'] = get_class($event->mutex);
|
|
138
|
+
$attributes['mutexNameResolver'] = $event->mutexNameResolver === null ? null : rv_identity($event->mutexNameResolver);
|
|
139
|
+
$attributes['attributesHash'] = rv_scalar_hash($event->attributes);
|
|
140
|
+
rv_add($entries, ($callback ? 'callback:' : 'command:').$target, $attributes);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
$collections[] = rv_collect('bindings', false, function (&$entries) use ($app) {
|
|
144
|
+
$factoryMethod = new ReflectionMethod(Illuminate\Container\Container::class, 'getClosure');
|
|
145
|
+
$scoped = rv_property($app, 'scopedInstances');
|
|
146
|
+
foreach ($app->getBindings() as $abstract => $binding) {
|
|
147
|
+
$factory = $binding['concrete'];
|
|
148
|
+
$identity = rv_identity($factory);
|
|
149
|
+
if ($factory instanceof Closure) {
|
|
150
|
+
$reflection = new ReflectionFunction($factory);
|
|
151
|
+
if ($reflection->getFileName() === $factoryMethod->getFileName() && $reflection->getStartLine() >= $factoryMethod->getStartLine() && $reflection->getEndLine() <= $factoryMethod->getEndLine()) $identity = $reflection->getStaticVariables()['concrete'];
|
|
152
|
+
}
|
|
153
|
+
rv_add($entries, 'factory:'.$abstract, ['type' => 'factory', 'abstract' => $abstract, 'target' => $identity, 'shared' => $binding['shared'], 'scoped' => in_array($abstract, $scoped, true)]);
|
|
154
|
+
}
|
|
155
|
+
foreach (rv_property($app, 'aliases') as $alias => $abstract) rv_add($entries, 'alias:'.$alias, ['type' => 'alias', 'abstract' => $alias, 'target' => $abstract]);
|
|
156
|
+
foreach ($app->contextual as $consumer => $bindings) foreach ($bindings as $abstract => $value) rv_add($entries, 'contextual:'.$consumer.':'.$abstract, ['type' => 'contextual', 'consumer' => $consumer, 'abstract' => $abstract, 'target' => rv_identity($value)]);
|
|
157
|
+
foreach (rv_property($app, 'instances') as $abstract => $instance) rv_add($entries, 'instance:'.$abstract, ['type' => 'instance', 'abstract' => $abstract, 'target' => is_object($instance) ? get_class($instance) : 'scalar:'.rv_scalar_hash($instance)]);
|
|
158
|
+
});
|
|
159
|
+
$result = ['version' => 1, 'laravelVersion' => Illuminate\Foundation\Application::VERSION, 'entryCount' => array_sum(array_map(fn ($collection) => count($collection['entries']), $collections)), 'runtime' => [
|
|
160
|
+
'schemaVersion' => 1, 'format' => 'runtime-inventory', 'producer' => ['name' => 'checktrail.laravel-runtime', 'version' => '1.0.0'],
|
|
161
|
+
'assembly' => ['name' => $config['assembly'], 'environment' => $config['environment']], 'sourceFingerprint' => $argv[3], 'capturedAt' => gmdate('Y-m-d\TH:i:s\Z'), 'collections' => $collections,
|
|
162
|
+
]];
|
|
163
|
+
fwrite(STDOUT, json_encode($result, JSON_THROW_ON_ERROR | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
|
|
164
|
+
} catch (Throwable $error) {
|
|
165
|
+
fwrite(STDERR, get_class($error).': '.$error->getMessage().PHP_EOL);
|
|
166
|
+
exit(2);
|
|
167
|
+
}
|
|
168
|
+
`;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Check, Inventory, Project } from "./types.js";
|
|
3
|
+
export declare const laravelConfigSchema: z.ZodObject<{
|
|
4
|
+
schemaVersion: z.ZodLiteral<1>;
|
|
5
|
+
assembly: z.ZodString;
|
|
6
|
+
environment: z.ZodLiteral<"testing">;
|
|
7
|
+
}, z.core.$strict>;
|
|
8
|
+
export declare function laravelCheck(source: Inventory, project: Project): Promise<Check>;
|