cgraphx 2.0.2 → 2.0.6
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/dist/.claude-template/FLOW-MAP.md +13 -3
- package/dist/.claude-template/agents/task-history.md +186 -0
- package/dist/.claude-template/commands/end.md +1 -0
- package/dist/.claude-template/skills/cgraphx-guide/SKILL.md +5 -3
- package/dist/.claude-template/skills/cgraphx-guide/how-to-use.html +8 -8
- package/dist/.claude-template/skills/cgraphx-guide/how-to-use.md +8 -8
- package/dist/.claude-template/skills/clarify-requirements/SKILL.md +13 -9
- package/dist/.claude-template/skills/db-query/SKILL.md +19 -2
- package/dist/.claude-template/skills/iboc-check/SKILL.md +51 -23
- package/dist/.claude-template/skills/iboc-check/filler-prompt.md +3 -4
- package/dist/.claude-template/skills/run-api-test/SKILL.md +15 -15
- package/dist/.claude-template/skills/run-api-test/assets/{template-test-report.md → template-/346/216/245/345/217/243/346/265/213/350/257/225/346/212/245/345/221/212.md} +4 -4
- package/dist/.claude-template/skills/run-api-test/references/bru-run.md +1 -1
- package/dist/.claude-template/skills/run-api-test/references/db-verification.md +2 -2
- package/dist/.claude-template/skills/run-api-test/references/report-format.md +6 -6
- package/dist/.claude-template/skills/run-api-test/references/service-readiness.md +1 -1
- package/dist/.claude-template/skills/run-api-test/references/test-scope.md +6 -6
- package/dist/.claude-template/skills/run-ui-test/SKILL.md +186 -0
- package/dist/.claude-template/skills/run-ui-test/references/report-format.md +77 -0
- package/dist/.claude-template/skills/subagent-implement/SKILL.md +3 -2
- package/dist/.claude-template/skills/write-api/SKILL.md +18 -18
- package/dist/.claude-template/skills/write-api/assets/{template-api-spec.md → template-/346/216/245/345/217/243/346/265/213/350/257/225-spec.md} +1 -1
- package/dist/.claude-template/skills/write-api/references/{api-spec-format.md → /346/216/245/345/217/243/346/265/213/350/257/225-spec-format.md} +3 -3
- package/dist/.claude-template/skills/write-plan/SKILL.md +4 -2
- package/dist/.claude-template/skills/write-ui-test/SKILL.md +125 -0
- package/dist/.claude-template/skills/write-ui-test/references//347/224/250/344/276/213/350/247/204/346/240/274-format.md +94 -0
- package/dist/core/code/engine/cli/analyze-config.js +320 -0
- package/dist/core/code/engine/cli/analyze.js +1086 -0
- package/dist/core/code/engine/cli/cli-message.js +83 -0
- package/dist/core/code/engine/cli/detect-changes-format.js +58 -0
- package/dist/core/code/engine/cli/embedding-dims.js +43 -0
- package/dist/core/code/engine/cli/format-elapsed.js +12 -0
- package/dist/core/code/engine/cli/help-i18n.js +144 -0
- package/dist/core/code/engine/cli/i18n/en.js +110 -0
- package/dist/core/code/engine/cli/i18n/index.js +47 -0
- package/dist/core/code/engine/cli/i18n/resources.js +9 -0
- package/dist/core/code/engine/cli/i18n/zh-CN.js +110 -0
- package/dist/core/code/engine/cli/lazy-action.js +67 -0
- package/dist/core/code/engine/cli/optional-grammars.js +136 -0
- package/dist/core/code/engine/cli/resolve-invocation.js +76 -0
- package/dist/core/code/engine/cli/status.js +156 -0
- package/dist/core/code/engine/cli/tool.js +384 -0
- package/dist/core/code/engine/config/ignore-service.js +511 -0
- package/dist/core/code/engine/config/supported-languages.js +17 -0
- package/dist/core/code/engine/core/analysis-features.js +64 -0
- package/dist/core/code/engine/core/analyzer-identity.js +2171 -0
- package/dist/core/code/engine/core/git-staleness.js +180 -0
- package/dist/core/code/engine/core/graph/graph.js +180 -0
- package/dist/core/code/engine/core/graph/import-cycles.js +106 -0
- package/dist/core/code/engine/core/graph/types.js +2 -0
- package/dist/core/code/engine/core/index-freshness.js +12 -0
- package/dist/core/code/engine/core/ingestion/binding-accumulator.js +341 -0
- package/dist/core/code/engine/core/ingestion/call-extractors/configs/c-cpp.js +168 -0
- package/dist/core/code/engine/core/ingestion/call-extractors/configs/csharp.js +9 -0
- package/dist/core/code/engine/core/ingestion/call-extractors/configs/dart.js +8 -0
- package/dist/core/code/engine/core/ingestion/call-extractors/configs/go.js +8 -0
- package/dist/core/code/engine/core/ingestion/call-extractors/configs/jvm.js +54 -0
- package/dist/core/code/engine/core/ingestion/call-extractors/configs/php.js +8 -0
- package/dist/core/code/engine/core/ingestion/call-extractors/configs/python.js +8 -0
- package/dist/core/code/engine/core/ingestion/call-extractors/configs/ruby.js +8 -0
- package/dist/core/code/engine/core/ingestion/call-extractors/configs/rust.js +8 -0
- package/dist/core/code/engine/core/ingestion/call-extractors/configs/swift.js +8 -0
- package/dist/core/code/engine/core/ingestion/call-extractors/configs/typescript-javascript.js +11 -0
- package/dist/core/code/engine/core/ingestion/call-extractors/generic.js +62 -0
- package/dist/core/code/engine/core/ingestion/call-processor.js +503 -0
- package/dist/core/code/engine/core/ingestion/call-routing.js +98 -0
- package/dist/core/code/engine/core/ingestion/call-types.js +3 -0
- package/dist/core/code/engine/core/ingestion/cfg/callee-cell-format.js +45 -0
- package/dist/core/code/engine/core/ingestion/cfg/cfg-builder.js +202 -0
- package/dist/core/code/engine/core/ingestion/cfg/collect.js +81 -0
- package/dist/core/code/engine/core/ingestion/cfg/control-dependence.js +185 -0
- package/dist/core/code/engine/core/ingestion/cfg/control-flow-context.js +130 -0
- package/dist/core/code/engine/core/ingestion/cfg/emit.js +646 -0
- package/dist/core/code/engine/core/ingestion/cfg/post-dominators.js +182 -0
- package/dist/core/code/engine/core/ingestion/cfg/reaching-def-reason-codec.js +139 -0
- package/dist/core/code/engine/core/ingestion/cfg/reaching-defs-graph.js +322 -0
- package/dist/core/code/engine/core/ingestion/cfg/reaching-defs.js +792 -0
- package/dist/core/code/engine/core/ingestion/cfg/synthetic-escape.js +305 -0
- package/dist/core/code/engine/core/ingestion/cfg/traversal-result.js +6 -0
- package/dist/core/code/engine/core/ingestion/cfg/types.js +14 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/c-cpp-harvest.js +545 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/c-cpp.js +590 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/call-site-harvest.js +356 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/csharp-harvest.js +593 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/csharp.js +871 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/dart-harvest.js +874 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/dart.js +840 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/go-harvest.js +625 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/go.js +642 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/java-harvest.js +517 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/java.js +816 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/kotlin-harvest.js +723 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/kotlin.js +813 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/php-harvest.js +630 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/php.js +725 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/python-harvest.js +776 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/python.js +562 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/ruby-harvest.js +591 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/ruby.js +760 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/rust-harvest.js +877 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/rust.js +562 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/scope-tree-harvest.js +120 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/swift-harvest.js +683 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/swift.js +791 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/typescript-harvest.js +1060 -0
- package/dist/core/code/engine/core/ingestion/cfg/visitors/typescript.js +587 -0
- package/dist/core/code/engine/core/ingestion/class-extractors/configs/c-cpp.js +77 -0
- package/dist/core/code/engine/core/ingestion/class-extractors/configs/csharp.js +24 -0
- package/dist/core/code/engine/core/ingestion/class-extractors/configs/dart.js +10 -0
- package/dist/core/code/engine/core/ingestion/class-extractors/configs/go.js +28 -0
- package/dist/core/code/engine/core/ingestion/class-extractors/configs/jvm.js +67 -0
- package/dist/core/code/engine/core/ingestion/class-extractors/configs/php.js +10 -0
- package/dist/core/code/engine/core/ingestion/class-extractors/configs/python.js +10 -0
- package/dist/core/code/engine/core/ingestion/class-extractors/configs/ruby.js +13 -0
- package/dist/core/code/engine/core/ingestion/class-extractors/configs/rust.js +10 -0
- package/dist/core/code/engine/core/ingestion/class-extractors/configs/swift.js +21 -0
- package/dist/core/code/engine/core/ingestion/class-extractors/configs/typescript-javascript.js +31 -0
- package/dist/core/code/engine/core/ingestion/class-extractors/generic.js +144 -0
- package/dist/core/code/engine/core/ingestion/class-types.js +2 -0
- package/dist/core/code/engine/core/ingestion/cluster-enricher.js +174 -0
- package/dist/core/code/engine/core/ingestion/community-processor.js +604 -0
- package/dist/core/code/engine/core/ingestion/constants.js +26 -0
- package/dist/core/code/engine/core/ingestion/cpp-ue-preprocessor.js +263 -0
- package/dist/core/code/engine/core/ingestion/csharp-namespace-gate.js +133 -0
- package/dist/core/code/engine/core/ingestion/di-extractors/index.js +38 -0
- package/dist/core/code/engine/core/ingestion/di-extractors/spring.js +310 -0
- package/dist/core/code/engine/core/ingestion/emit-references.js +244 -0
- package/dist/core/code/engine/core/ingestion/entry-point-scoring.js +201 -0
- package/dist/core/code/engine/core/ingestion/export-detection.js +244 -0
- package/dist/core/code/engine/core/ingestion/field-extractor.js +29 -0
- package/dist/core/code/engine/core/ingestion/field-extractors/configs/c-cpp.js +107 -0
- package/dist/core/code/engine/core/ingestion/field-extractors/configs/csharp.js +124 -0
- package/dist/core/code/engine/core/ingestion/field-extractors/configs/dart.js +99 -0
- package/dist/core/code/engine/core/ingestion/field-extractors/configs/go.js +102 -0
- package/dist/core/code/engine/core/ingestion/field-extractors/configs/helpers.js +198 -0
- package/dist/core/code/engine/core/ingestion/field-extractors/configs/jvm.js +172 -0
- package/dist/core/code/engine/core/ingestion/field-extractors/configs/php.js +67 -0
- package/dist/core/code/engine/core/ingestion/field-extractors/configs/python.js +94 -0
- package/dist/core/code/engine/core/ingestion/field-extractors/configs/ruby.js +79 -0
- package/dist/core/code/engine/core/ingestion/field-extractors/configs/rust.js +55 -0
- package/dist/core/code/engine/core/ingestion/field-extractors/configs/swift.js +93 -0
- package/dist/core/code/engine/core/ingestion/field-extractors/configs/typescript-javascript.js +59 -0
- package/dist/core/code/engine/core/ingestion/field-extractors/generic.js +147 -0
- package/dist/core/code/engine/core/ingestion/field-extractors/typescript.js +266 -0
- package/dist/core/code/engine/core/ingestion/field-types.js +3 -0
- package/dist/core/code/engine/core/ingestion/filesystem-walker.js +136 -0
- package/dist/core/code/engine/core/ingestion/finalize-orchestrator.js +159 -0
- package/dist/core/code/engine/core/ingestion/framework-detection.js +432 -0
- package/dist/core/code/engine/core/ingestion/frameworks/spring/analysis-features.js +38 -0
- package/dist/core/code/engine/core/ingestion/frameworks/spring/annotation-arguments.js +234 -0
- package/dist/core/code/engine/core/ingestion/frameworks/spring/aop-candidates.js +88 -0
- package/dist/core/code/engine/core/ingestion/frameworks/spring/aop.js +487 -0
- package/dist/core/code/engine/core/ingestion/frameworks/spring/auto-configuration.js +21 -0
- package/dist/core/code/engine/core/ingestion/frameworks/spring/bean-candidates.js +189 -0
- package/dist/core/code/engine/core/ingestion/frameworks/spring/bean-catalog.js +32 -0
- package/dist/core/code/engine/core/ingestion/frameworks/spring/bean-factories.js +73 -0
- package/dist/core/code/engine/core/ingestion/frameworks/spring/conditionals.js +323 -0
- package/dist/core/code/engine/core/ingestion/frameworks/spring/config-bindings.js +119 -0
- package/dist/core/code/engine/core/ingestion/frameworks/spring/di-metadata.js +385 -0
- package/dist/core/code/engine/core/ingestion/frameworks/spring/resource-injection.js +96 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/configs/c-cpp.js +17 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/configs/csharp.js +46 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/configs/dart.js +59 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/configs/go.js +30 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/configs/jvm.js +73 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/configs/php.js +19 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/configs/python.js +45 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/configs/ruby.js +20 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/configs/rust.js +58 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/configs/swift.js +94 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/configs/typescript-javascript.js +26 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/csharp.js +128 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/go.js +50 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/jvm.js +112 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/php.js +80 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/python.js +75 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/resolver-factory.js +36 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/ruby.js +20 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/rust.js +79 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/standard.js +180 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/types.js +7 -0
- package/dist/core/code/engine/core/ingestion/import-resolvers/utils.js +153 -0
- package/dist/core/code/engine/core/ingestion/import-target-adapter.js +99 -0
- package/dist/core/code/engine/core/ingestion/language-config.js +391 -0
- package/dist/core/code/engine/core/ingestion/language-provider.js +25 -0
- package/dist/core/code/engine/core/ingestion/languages/c/arity-metadata.js +98 -0
- package/dist/core/code/engine/core/ingestion/languages/c/arity.js +21 -0
- package/dist/core/code/engine/core/ingestion/languages/c/capture-side-channel.js +69 -0
- package/dist/core/code/engine/core/ingestion/languages/c/captures.js +189 -0
- package/dist/core/code/engine/core/ingestion/languages/c/header-scan.js +58 -0
- package/dist/core/code/engine/core/ingestion/languages/c/import-decomposer.js +68 -0
- package/dist/core/code/engine/core/ingestion/languages/c/import-target.js +103 -0
- package/dist/core/code/engine/core/ingestion/languages/c/index.js +33 -0
- package/dist/core/code/engine/core/ingestion/languages/c/interpret.js +53 -0
- package/dist/core/code/engine/core/ingestion/languages/c/merge-bindings.js +26 -0
- package/dist/core/code/engine/core/ingestion/languages/c/query.js +210 -0
- package/dist/core/code/engine/core/ingestion/languages/c/scope-resolver.js +112 -0
- package/dist/core/code/engine/core/ingestion/languages/c/simple-hooks.js +24 -0
- package/dist/core/code/engine/core/ingestion/languages/c/static-linkage.js +109 -0
- package/dist/core/code/engine/core/ingestion/languages/c-cpp.js +506 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/adl.js +804 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/arity-metadata.js +258 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/arity.js +37 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/capture-side-channel.js +89 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/captures.js +1975 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/constraint-extractor.js +311 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/constraint-filter.js +210 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/conversion-rank.js +163 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/file-local-linkage.js +327 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/header-scan.js +53 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/import-decomposer.js +134 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/import-target.js +16 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/index.js +33 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/inline-namespaces.js +379 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/interpret.js +239 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/member-lookup.js +470 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/merge-bindings.js +32 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/query.js +763 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/range-bindings.js +230 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/scope-resolver.js +354 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/simple-hooks.js +67 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/two-phase-lookup.js +348 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/type-classifier.js +56 -0
- package/dist/core/code/engine/core/ingestion/languages/cpp/user-defined-conversions.js +128 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/accessor-unwrap.js +67 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/arity-metadata.js +49 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/arity.js +40 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/cache-stats.js +32 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/captures.js +557 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/import-decomposer.js +96 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/import-target.js +176 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/index.js +95 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/interpret.js +150 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/merge-bindings.js +58 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/namespace-siblings.js +708 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/qualified-type-names.js +62 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/query.js +578 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/receiver-binding.js +142 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/resolution-config.js +18 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/scope-resolver.js +84 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp/simple-hooks.js +81 -0
- package/dist/core/code/engine/core/ingestion/languages/csharp.js +204 -0
- package/dist/core/code/engine/core/ingestion/languages/dart/arity-metadata.js +38 -0
- package/dist/core/code/engine/core/ingestion/languages/dart/arity.js +34 -0
- package/dist/core/code/engine/core/ingestion/languages/dart/built-ins.js +37 -0
- package/dist/core/code/engine/core/ingestion/languages/dart/cache-stats.js +30 -0
- package/dist/core/code/engine/core/ingestion/languages/dart/captures.js +1096 -0
- package/dist/core/code/engine/core/ingestion/languages/dart/expand-wildcards.js +34 -0
- package/dist/core/code/engine/core/ingestion/languages/dart/extension-type-preprocess.js +33 -0
- package/dist/core/code/engine/core/ingestion/languages/dart/import-target.js +68 -0
- package/dist/core/code/engine/core/ingestion/languages/dart/index.js +45 -0
- package/dist/core/code/engine/core/ingestion/languages/dart/interpret.js +101 -0
- package/dist/core/code/engine/core/ingestion/languages/dart/merge-bindings.js +42 -0
- package/dist/core/code/engine/core/ingestion/languages/dart/query.js +246 -0
- package/dist/core/code/engine/core/ingestion/languages/dart/receiver-binding.js +90 -0
- package/dist/core/code/engine/core/ingestion/languages/dart/scope-resolver.js +197 -0
- package/dist/core/code/engine/core/ingestion/languages/dart/signature-bindings.js +54 -0
- package/dist/core/code/engine/core/ingestion/languages/dart/simple-hooks.js +61 -0
- package/dist/core/code/engine/core/ingestion/languages/dart.js +138 -0
- package/dist/core/code/engine/core/ingestion/languages/go/arity-metadata.js +71 -0
- package/dist/core/code/engine/core/ingestion/languages/go/arity.js +17 -0
- package/dist/core/code/engine/core/ingestion/languages/go/cache-stats.js +21 -0
- package/dist/core/code/engine/core/ingestion/languages/go/captures.js +492 -0
- package/dist/core/code/engine/core/ingestion/languages/go/expand-wildcards.js +97 -0
- package/dist/core/code/engine/core/ingestion/languages/go/generic-type-parameters.js +146 -0
- package/dist/core/code/engine/core/ingestion/languages/go/import-decomposer.js +47 -0
- package/dist/core/code/engine/core/ingestion/languages/go/import-target.js +70 -0
- package/dist/core/code/engine/core/ingestion/languages/go/index.js +39 -0
- package/dist/core/code/engine/core/ingestion/languages/go/interface-impls.js +955 -0
- package/dist/core/code/engine/core/ingestion/languages/go/interpret.js +177 -0
- package/dist/core/code/engine/core/ingestion/languages/go/merge-bindings.js +21 -0
- package/dist/core/code/engine/core/ingestion/languages/go/method-owners.js +131 -0
- package/dist/core/code/engine/core/ingestion/languages/go/namespace-mirror.js +56 -0
- package/dist/core/code/engine/core/ingestion/languages/go/package-clause.js +79 -0
- package/dist/core/code/engine/core/ingestion/languages/go/package-siblings.js +83 -0
- package/dist/core/code/engine/core/ingestion/languages/go/query.js +298 -0
- package/dist/core/code/engine/core/ingestion/languages/go/range-binding.js +127 -0
- package/dist/core/code/engine/core/ingestion/languages/go/receiver-binding.js +24 -0
- package/dist/core/code/engine/core/ingestion/languages/go/scope-resolver.js +75 -0
- package/dist/core/code/engine/core/ingestion/languages/go/simple-hooks.js +31 -0
- package/dist/core/code/engine/core/ingestion/languages/go/type-binding.js +279 -0
- package/dist/core/code/engine/core/ingestion/languages/go.js +160 -0
- package/dist/core/code/engine/core/ingestion/languages/index.js +66 -0
- package/dist/core/code/engine/core/ingestion/languages/java/analysis-features.js +16 -0
- package/dist/core/code/engine/core/ingestion/languages/java/arity-metadata.js +43 -0
- package/dist/core/code/engine/core/ingestion/languages/java/arity.js +27 -0
- package/dist/core/code/engine/core/ingestion/languages/java/cache-stats.js +32 -0
- package/dist/core/code/engine/core/ingestion/languages/java/capture-side-channel.js +123 -0
- package/dist/core/code/engine/core/ingestion/languages/java/captures.js +791 -0
- package/dist/core/code/engine/core/ingestion/languages/java/import-decomposer.js +88 -0
- package/dist/core/code/engine/core/ingestion/languages/java/import-target.js +103 -0
- package/dist/core/code/engine/core/ingestion/languages/java/index.js +43 -0
- package/dist/core/code/engine/core/ingestion/languages/java/interpret.js +146 -0
- package/dist/core/code/engine/core/ingestion/languages/java/merge-bindings.js +43 -0
- package/dist/core/code/engine/core/ingestion/languages/java/package-facts.js +16 -0
- package/dist/core/code/engine/core/ingestion/languages/java/package-siblings.js +11 -0
- package/dist/core/code/engine/core/ingestion/languages/java/query.js +319 -0
- package/dist/core/code/engine/core/ingestion/languages/java/receiver-binding.js +98 -0
- package/dist/core/code/engine/core/ingestion/languages/java/scope-resolver.js +213 -0
- package/dist/core/code/engine/core/ingestion/languages/java/simple-hooks.js +39 -0
- package/dist/core/code/engine/core/ingestion/languages/java/spring-aop.js +53 -0
- package/dist/core/code/engine/core/ingestion/languages/java/spring-bean-metadata.js +11 -0
- package/dist/core/code/engine/core/ingestion/languages/java/spring-conditionals.js +52 -0
- package/dist/core/code/engine/core/ingestion/languages/java/spring-config-bindings.js +222 -0
- package/dist/core/code/engine/core/ingestion/languages/java/spring-di.js +165 -0
- package/dist/core/code/engine/core/ingestion/languages/java.js +192 -0
- package/dist/core/code/engine/core/ingestion/languages/javascript/arity.js +15 -0
- package/dist/core/code/engine/core/ingestion/languages/javascript/captures.js +1122 -0
- package/dist/core/code/engine/core/ingestion/languages/javascript/import-target.js +56 -0
- package/dist/core/code/engine/core/ingestion/languages/javascript/index.js +109 -0
- package/dist/core/code/engine/core/ingestion/languages/javascript/interpret.js +45 -0
- package/dist/core/code/engine/core/ingestion/languages/javascript/merge-bindings.js +21 -0
- package/dist/core/code/engine/core/ingestion/languages/javascript/query.js +659 -0
- package/dist/core/code/engine/core/ingestion/languages/javascript/scope-resolver.js +78 -0
- package/dist/core/code/engine/core/ingestion/languages/javascript/simple-hooks.js +44 -0
- package/dist/core/code/engine/core/ingestion/languages/jvm/package-facts.js +46 -0
- package/dist/core/code/engine/core/ingestion/languages/jvm/package-siblings.js +200 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/arity-metadata.js +23 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/arity.js +18 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/cache-stats.js +21 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/capture-side-channel.js +160 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/captures.js +1262 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/companion-scopes.js +72 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/import-decomposer.js +40 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/import-target.js +135 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/index.js +26 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/interpret.js +75 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/merge-bindings.js +28 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/owners.js +134 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/package-facts.js +16 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/package-siblings.js +11 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/query.js +243 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/receiver-binding.js +103 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/scope-resolver.js +207 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/simple-hooks.js +42 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/spring-aop.js +68 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/spring-bean-metadata.js +11 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/spring-conditionals.js +53 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin/spring-di.js +304 -0
- package/dist/core/code/engine/core/ingestion/languages/kotlin.js +189 -0
- package/dist/core/code/engine/core/ingestion/languages/php/arity-metadata.js +66 -0
- package/dist/core/code/engine/core/ingestion/languages/php/arity.js +43 -0
- package/dist/core/code/engine/core/ingestion/languages/php/cache-stats.js +32 -0
- package/dist/core/code/engine/core/ingestion/languages/php/captures.js +1166 -0
- package/dist/core/code/engine/core/ingestion/languages/php/import-decomposer.js +238 -0
- package/dist/core/code/engine/core/ingestion/languages/php/import-target.js +213 -0
- package/dist/core/code/engine/core/ingestion/languages/php/index.js +85 -0
- package/dist/core/code/engine/core/ingestion/languages/php/interpret.js +256 -0
- package/dist/core/code/engine/core/ingestion/languages/php/merge-bindings.js +50 -0
- package/dist/core/code/engine/core/ingestion/languages/php/namespace-siblings.js +353 -0
- package/dist/core/code/engine/core/ingestion/languages/php/query.js +391 -0
- package/dist/core/code/engine/core/ingestion/languages/php/receiver-binding.js +135 -0
- package/dist/core/code/engine/core/ingestion/languages/php/scope-resolver.js +361 -0
- package/dist/core/code/engine/core/ingestion/languages/php/simple-hooks.js +116 -0
- package/dist/core/code/engine/core/ingestion/languages/php.js +302 -0
- package/dist/core/code/engine/core/ingestion/languages/python/arity-metadata.js +49 -0
- package/dist/core/code/engine/core/ingestion/languages/python/arity.js +41 -0
- package/dist/core/code/engine/core/ingestion/languages/python/cache-stats.js +34 -0
- package/dist/core/code/engine/core/ingestion/languages/python/captures.js +299 -0
- package/dist/core/code/engine/core/ingestion/languages/python/depends-references.js +68 -0
- package/dist/core/code/engine/core/ingestion/languages/python/import-decomposer.js +115 -0
- package/dist/core/code/engine/core/ingestion/languages/python/import-target.js +440 -0
- package/dist/core/code/engine/core/ingestion/languages/python/index-stats.js +30 -0
- package/dist/core/code/engine/core/ingestion/languages/python/index.js +96 -0
- package/dist/core/code/engine/core/ingestion/languages/python/interpret.js +430 -0
- package/dist/core/code/engine/core/ingestion/languages/python/merge-bindings.js +47 -0
- package/dist/core/code/engine/core/ingestion/languages/python/query.js +323 -0
- package/dist/core/code/engine/core/ingestion/languages/python/receiver-binding.js +310 -0
- package/dist/core/code/engine/core/ingestion/languages/python/scope-resolver.js +80 -0
- package/dist/core/code/engine/core/ingestion/languages/python/simple-hooks.js +53 -0
- package/dist/core/code/engine/core/ingestion/languages/python.js +140 -0
- package/dist/core/code/engine/core/ingestion/languages/ruby/arity.js +41 -0
- package/dist/core/code/engine/core/ingestion/languages/ruby/cache-stats.js +21 -0
- package/dist/core/code/engine/core/ingestion/languages/ruby/captures.js +864 -0
- package/dist/core/code/engine/core/ingestion/languages/ruby/import-target.js +88 -0
- package/dist/core/code/engine/core/ingestion/languages/ruby/index.js +29 -0
- package/dist/core/code/engine/core/ingestion/languages/ruby/interpret.js +115 -0
- package/dist/core/code/engine/core/ingestion/languages/ruby/merge-bindings.js +21 -0
- package/dist/core/code/engine/core/ingestion/languages/ruby/query.js +349 -0
- package/dist/core/code/engine/core/ingestion/languages/ruby/receiver-binding.js +70 -0
- package/dist/core/code/engine/core/ingestion/languages/ruby/scope-resolver.js +263 -0
- package/dist/core/code/engine/core/ingestion/languages/ruby/simple-hooks.js +68 -0
- package/dist/core/code/engine/core/ingestion/languages/ruby.js +215 -0
- package/dist/core/code/engine/core/ingestion/languages/rust/arity.js +16 -0
- package/dist/core/code/engine/core/ingestion/languages/rust/cache-stats.js +21 -0
- package/dist/core/code/engine/core/ingestion/languages/rust/captures.js +304 -0
- package/dist/core/code/engine/core/ingestion/languages/rust/import-decomposer.js +167 -0
- package/dist/core/code/engine/core/ingestion/languages/rust/import-target.js +108 -0
- package/dist/core/code/engine/core/ingestion/languages/rust/index.js +29 -0
- package/dist/core/code/engine/core/ingestion/languages/rust/interpret.js +201 -0
- package/dist/core/code/engine/core/ingestion/languages/rust/merge-bindings.js +21 -0
- package/dist/core/code/engine/core/ingestion/languages/rust/method-owners.js +76 -0
- package/dist/core/code/engine/core/ingestion/languages/rust/module-path.js +222 -0
- package/dist/core/code/engine/core/ingestion/languages/rust/qualified-call.js +482 -0
- package/dist/core/code/engine/core/ingestion/languages/rust/query.js +280 -0
- package/dist/core/code/engine/core/ingestion/languages/rust/range-binding.js +687 -0
- package/dist/core/code/engine/core/ingestion/languages/rust/receiver-binding.js +148 -0
- package/dist/core/code/engine/core/ingestion/languages/rust/scope-resolver.js +151 -0
- package/dist/core/code/engine/core/ingestion/languages/rust/simple-hooks.js +32 -0
- package/dist/core/code/engine/core/ingestion/languages/rust.js +183 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/arity-metadata.js +44 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/arity.js +45 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/base-type.js +30 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/cache-stats.js +32 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/captures.js +594 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/conditional-directive-preprocess.js +256 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/implicit-imports.js +60 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/import-decomposer.js +87 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/import-target.js +84 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/index.js +56 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/interpret.js +93 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/merge-bindings.js +51 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/query.js +226 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/receiver-binding.js +169 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/scope-resolver.js +192 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/sibling-type-bindings.js +68 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/signature-bindings.js +69 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/simple-hooks.js +65 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/target-grouping.js +97 -0
- package/dist/core/code/engine/core/ingestion/languages/swift/target-siblings.js +74 -0
- package/dist/core/code/engine/core/ingestion/languages/swift.js +246 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/arity-metadata.js +106 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/arity.js +57 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/array-callback.js +58 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/cache-stats.js +34 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/captures.js +956 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/cjs-export-assignment.js +535 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/cjs-module-exports.js +196 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/import-decomposer.js +374 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/import-target.js +65 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/index.js +108 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/interpret.js +344 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/merge-bindings.js +161 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/nuxt-auto-imports.js +325 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/query.js +1328 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/receiver-binding.js +201 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/scope-resolver.js +293 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript/simple-hooks.js +139 -0
- package/dist/core/code/engine/core/ingestion/languages/typescript.js +455 -0
- package/dist/core/code/engine/core/ingestion/languages/vue/captures.js +70 -0
- package/dist/core/code/engine/core/ingestion/languages/vue/import-target.js +61 -0
- package/dist/core/code/engine/core/ingestion/languages/vue/index.js +55 -0
- package/dist/core/code/engine/core/ingestion/languages/vue/scope-resolver.js +295 -0
- package/dist/core/code/engine/core/ingestion/languages/vue.js +96 -0
- package/dist/core/code/engine/core/ingestion/local-symbol-pruner.js +68 -0
- package/dist/core/code/engine/core/ingestion/method-extractors/configs/c-cpp.js +387 -0
- package/dist/core/code/engine/core/ingestion/method-extractors/configs/csharp.js +290 -0
- package/dist/core/code/engine/core/ingestion/method-extractors/configs/dart.js +392 -0
- package/dist/core/code/engine/core/ingestion/method-extractors/configs/go.js +179 -0
- package/dist/core/code/engine/core/ingestion/method-extractors/configs/jvm.js +350 -0
- package/dist/core/code/engine/core/ingestion/method-extractors/configs/php.js +306 -0
- package/dist/core/code/engine/core/ingestion/method-extractors/configs/python.js +312 -0
- package/dist/core/code/engine/core/ingestion/method-extractors/configs/ruby.js +289 -0
- package/dist/core/code/engine/core/ingestion/method-extractors/configs/rust.js +198 -0
- package/dist/core/code/engine/core/ingestion/method-extractors/configs/swift.js +286 -0
- package/dist/core/code/engine/core/ingestion/method-extractors/configs/typescript-javascript.js +341 -0
- package/dist/core/code/engine/core/ingestion/method-extractors/generic.js +209 -0
- package/dist/core/code/engine/core/ingestion/method-types.js +3 -0
- package/dist/core/code/engine/core/ingestion/model/field-registry.js +41 -0
- package/dist/core/code/engine/core/ingestion/model/index.js +52 -0
- package/dist/core/code/engine/core/ingestion/model/method-registry.js +138 -0
- package/dist/core/code/engine/core/ingestion/model/owned-members-lookup.js +46 -0
- package/dist/core/code/engine/core/ingestion/model/registration-table.js +234 -0
- package/dist/core/code/engine/core/ingestion/model/resolve.js +183 -0
- package/dist/core/code/engine/core/ingestion/model/scope-resolution-indexes.js +43 -0
- package/dist/core/code/engine/core/ingestion/model/semantic-model.js +179 -0
- package/dist/core/code/engine/core/ingestion/model/symbol-table.js +216 -0
- package/dist/core/code/engine/core/ingestion/model/type-registry.js +84 -0
- package/dist/core/code/engine/core/ingestion/mro-processor.js +709 -0
- package/dist/core/code/engine/core/ingestion/parsing-processor.js +312 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/communities.js +69 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/cross-file.js +71 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/di.js +338 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/http-api-calls.js +312 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/index.js +54 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/mro.js +40 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/orm.js +78 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/parse-impl.js +1286 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/parse.js +41 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/processes.js +193 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/prune-local-symbols.js +29 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/registry.js +52 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/routes.js +409 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/rpc-edges.js +301 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/runner.js +207 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/scan.js +49 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/spring-aop.js +442 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/spring-auto-configuration.js +264 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/spring-config.js +440 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/structure.js +38 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/tools.js +89 -0
- package/dist/core/code/engine/core/ingestion/pipeline-phases/types.js +40 -0
- package/dist/core/code/engine/core/ingestion/pipeline.js +152 -0
- package/dist/core/code/engine/core/ingestion/process-processor.js +325 -0
- package/dist/core/code/engine/core/ingestion/resolve-references.js +205 -0
- package/dist/core/code/engine/core/ingestion/route-extractors/constant-resolver.js +135 -0
- package/dist/core/code/engine/core/ingestion/route-extractors/django-root-discovery.js +221 -0
- package/dist/core/code/engine/core/ingestion/route-extractors/django.js +428 -0
- package/dist/core/code/engine/core/ingestion/route-extractors/expo.js +39 -0
- package/dist/core/code/engine/core/ingestion/route-extractors/fastapi-router-bindings.js +264 -0
- package/dist/core/code/engine/core/ingestion/route-extractors/laravel.js +501 -0
- package/dist/core/code/engine/core/ingestion/route-extractors/middleware.js +175 -0
- package/dist/core/code/engine/core/ingestion/route-extractors/nextjs.js +81 -0
- package/dist/core/code/engine/core/ingestion/route-extractors/php.js +25 -0
- package/dist/core/code/engine/core/ingestion/route-extractors/python-const-resolver.js +307 -0
- package/dist/core/code/engine/core/ingestion/route-extractors/response-shapes.js +299 -0
- package/dist/core/code/engine/core/ingestion/route-extractors/route-path.js +71 -0
- package/dist/core/code/engine/core/ingestion/route-extractors/spring-shared.js +310 -0
- package/dist/core/code/engine/core/ingestion/route-extractors/spring.js +441 -0
- package/dist/core/code/engine/core/ingestion/scope-extractor-bridge.js +60 -0
- package/dist/core/code/engine/core/ingestion/scope-extractor.js +1373 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/contract/scope-resolver.js +282 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +72 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/graph-bridge/edges.js +194 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/graph-bridge/ids.js +472 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/graph-bridge/imports-to-edges.js +49 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/graph-bridge/method-dispatch.js +43 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +274 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +93 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/passes/callable-value-flow.js +1240 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/passes/compound-receiver.js +1174 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/passes/free-call-fallback.js +873 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/passes/imported-return-types.js +226 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/passes/mro.js +107 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/passes/overload-narrowing.js +441 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/passes/property-dispatch.js +122 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +1720 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/pipeline/phase.js +395 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +208 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/pipeline/registry.js +49 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/pipeline/run.js +632 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/pipeline/validate-bindings-immutability.js +112 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/resolution-outcome.js +41 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/scope/namespace-targets.js +81 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/scope/walkers.js +1835 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/unresolved-receivers.js +242 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/utils/definition-id.js +22 -0
- package/dist/core/code/engine/core/ingestion/scope-resolution/workspace-index.js +151 -0
- package/dist/core/code/engine/core/ingestion/structure-processor.js +40 -0
- package/dist/core/code/engine/core/ingestion/tree-sitter-queries.js +2244 -0
- package/dist/core/code/engine/core/ingestion/ts-js-hoc-utils.js +115 -0
- package/dist/core/code/engine/core/ingestion/type-env.js +1136 -0
- package/dist/core/code/engine/core/ingestion/type-extractors/c-cpp.js +555 -0
- package/dist/core/code/engine/core/ingestion/type-extractors/csharp.js +570 -0
- package/dist/core/code/engine/core/ingestion/type-extractors/dart.js +372 -0
- package/dist/core/code/engine/core/ingestion/type-extractors/go.js +508 -0
- package/dist/core/code/engine/core/ingestion/type-extractors/jvm.js +875 -0
- package/dist/core/code/engine/core/ingestion/type-extractors/php.js +537 -0
- package/dist/core/code/engine/core/ingestion/type-extractors/python.js +477 -0
- package/dist/core/code/engine/core/ingestion/type-extractors/ruby.js +380 -0
- package/dist/core/code/engine/core/ingestion/type-extractors/rust.js +502 -0
- package/dist/core/code/engine/core/ingestion/type-extractors/shared.js +843 -0
- package/dist/core/code/engine/core/ingestion/type-extractors/swift.js +490 -0
- package/dist/core/code/engine/core/ingestion/type-extractors/types.js +2 -0
- package/dist/core/code/engine/core/ingestion/type-extractors/typescript.js +690 -0
- package/dist/core/code/engine/core/ingestion/utils/ast-helpers.js +1693 -0
- package/dist/core/code/engine/core/ingestion/utils/call-analysis.js +779 -0
- package/dist/core/code/engine/core/ingestion/utils/callable-flow-captures.js +932 -0
- package/dist/core/code/engine/core/ingestion/utils/callable-labels.js +49 -0
- package/dist/core/code/engine/core/ingestion/utils/deferred-resolution-profile.js +151 -0
- package/dist/core/code/engine/core/ingestion/utils/effective-ram.js +67 -0
- package/dist/core/code/engine/core/ingestion/utils/env.js +60 -0
- package/dist/core/code/engine/core/ingestion/utils/event-loop.js +9 -0
- package/dist/core/code/engine/core/ingestion/utils/graph-sort.js +103 -0
- package/dist/core/code/engine/core/ingestion/utils/heap-probe.js +45 -0
- package/dist/core/code/engine/core/ingestion/utils/heritage-marker.js +47 -0
- package/dist/core/code/engine/core/ingestion/utils/line-base.js +24 -0
- package/dist/core/code/engine/core/ingestion/utils/max-file-size.js +59 -0
- package/dist/core/code/engine/core/ingestion/utils/method-props.js +198 -0
- package/dist/core/code/engine/core/ingestion/utils/qualified-name.js +73 -0
- package/dist/core/code/engine/core/ingestion/utils/receiver-chain-captures.js +60 -0
- package/dist/core/code/engine/core/ingestion/utils/receiver-chain-codec.js +188 -0
- package/dist/core/code/engine/core/ingestion/utils/scope-tree-walk.js +36 -0
- package/dist/core/code/engine/core/ingestion/utils/symbol-labels.js +48 -0
- package/dist/core/code/engine/core/ingestion/utils/template-arguments.js +187 -0
- package/dist/core/code/engine/core/ingestion/utils/type-parameters.js +209 -0
- package/dist/core/code/engine/core/ingestion/utils/verbose.js +6 -0
- package/dist/core/code/engine/core/ingestion/variable-extractors/configs/c-cpp.js +133 -0
- package/dist/core/code/engine/core/ingestion/variable-extractors/configs/csharp.js +66 -0
- package/dist/core/code/engine/core/ingestion/variable-extractors/configs/dart.js +111 -0
- package/dist/core/code/engine/core/ingestion/variable-extractors/configs/go.js +153 -0
- package/dist/core/code/engine/core/ingestion/variable-extractors/configs/jvm.js +145 -0
- package/dist/core/code/engine/core/ingestion/variable-extractors/configs/php.js +61 -0
- package/dist/core/code/engine/core/ingestion/variable-extractors/configs/python.js +104 -0
- package/dist/core/code/engine/core/ingestion/variable-extractors/configs/ruby.js +55 -0
- package/dist/core/code/engine/core/ingestion/variable-extractors/configs/rust.js +79 -0
- package/dist/core/code/engine/core/ingestion/variable-extractors/configs/swift.js +91 -0
- package/dist/core/code/engine/core/ingestion/variable-extractors/configs/typescript-javascript.js +86 -0
- package/dist/core/code/engine/core/ingestion/variable-extractors/generic.js +111 -0
- package/dist/core/code/engine/core/ingestion/variable-types.js +3 -0
- package/dist/core/code/engine/core/ingestion/vue-sfc-extractor.js +544 -0
- package/dist/core/code/engine/core/ingestion/workers/callable-id.js +122 -0
- package/dist/core/code/engine/core/ingestion/workers/clone-safety.js +470 -0
- package/dist/core/code/engine/core/ingestion/workers/parse-worker.js +2456 -0
- package/dist/core/code/engine/core/ingestion/workers/post-result.js +90 -0
- package/dist/core/code/engine/core/ingestion/workers/quarantine.js +41 -0
- package/dist/core/code/engine/core/ingestion/workers/result-merge.js +59 -0
- package/dist/core/code/engine/core/ingestion/workers/worker-pool.js +1725 -0
- package/dist/core/code/engine/core/ingestion/workspace-config.js +139 -0
- package/dist/core/code/engine/core/lbug/conn-lock.js +72 -0
- package/dist/core/code/engine/core/lbug/csv-generator.js +710 -0
- package/dist/core/code/engine/core/lbug/cypher-escape.js +24 -0
- package/dist/core/code/engine/core/lbug/extension-load-error.js +339 -0
- package/dist/core/code/engine/core/lbug/extension-loader.js +261 -0
- package/dist/core/code/engine/core/lbug/graph-emit-sink.js +584 -0
- package/dist/core/code/engine/core/lbug/lbug-adapter.js +2703 -0
- package/dist/core/code/engine/core/lbug/lbug-config.js +1029 -0
- package/dist/core/code/engine/core/lbug/native-check.js +435 -0
- package/dist/core/code/engine/core/lbug/pool-adapter.js +973 -0
- package/dist/core/code/engine/core/lbug/query-params.js +25 -0
- package/dist/core/code/engine/core/lbug/query-result-utils.js +31 -0
- package/dist/core/code/engine/core/lbug/rel-pair-routing.js +373 -0
- package/dist/core/code/engine/core/lbug/schema.js +771 -0
- package/dist/core/code/engine/core/lbug/shutdown-helpers.js +40 -0
- package/dist/core/code/engine/core/lbug/sidecar-recovery.js +716 -0
- package/dist/core/code/engine/core/lbug/stdio-capture.js +49 -0
- package/dist/core/code/engine/core/lbug/sync-csv-writer.js +115 -0
- package/dist/core/code/engine/core/lbug/wal-checkpoint-driver.js +215 -0
- package/dist/core/code/engine/core/lbug/wal-driver-state.js +28 -0
- package/dist/core/code/engine/core/logger.js +339 -0
- package/dist/core/code/engine/core/platform/capabilities.js +91 -0
- package/dist/core/code/engine/core/run-analyze.js +1098 -0
- package/dist/core/code/engine/core/tree-sitter/parser-loader.js +281 -0
- package/dist/core/code/engine/core/tree-sitter/safe-parse.js +258 -0
- package/dist/core/code/engine/core/tree-sitter/vendored-grammars.js +64 -0
- package/dist/core/code/engine/lib/utils.js +121 -0
- package/dist/core/code/engine/mcp/core/lbug-adapter.js +27 -0
- package/dist/core/code/engine/mcp/local/aop-metadata.js +230 -0
- package/dist/core/code/engine/mcp/local/bean-metadata.js +49 -0
- package/dist/core/code/engine/mcp/local/limits.js +15 -0
- package/dist/core/code/engine/mcp/local/line-display.js +6 -0
- package/dist/core/code/engine/mcp/local/local-backend.js +4142 -0
- package/dist/core/code/engine/storage/branch-index.js +72 -0
- package/dist/core/code/engine/storage/file-hash.js +95 -0
- package/dist/core/code/engine/storage/fs-atomic.js +34 -0
- package/dist/core/code/engine/storage/git.js +555 -0
- package/dist/core/code/engine/storage/index-lock.js +664 -0
- package/dist/core/code/engine/storage/parse-cache.js +650 -0
- package/dist/core/code/engine/storage/parsedfile-store.js +620 -0
- package/dist/core/code/engine/storage/repo-manager.js +1061 -0
- package/dist/core/code/engine/storage/scope-index-store.js +247 -0
- package/dist/core/code/engine/types/pipeline.js +2 -0
- package/dist/core/code/scripts/install-duckdb-extension.mjs +125 -0
- package/dist/core/code/scripts/resolve-analyze-cmd.cjs +346 -0
- package/dist/core/code/shared/graph/types.js +8 -0
- package/dist/core/code/shared/index.js +105 -0
- package/dist/core/code/shared/integrations/circuit-breaker.js +242 -0
- package/dist/core/code/shared/integrations/resilient-fetch.js +224 -0
- package/dist/core/code/shared/integrations/retry.js +70 -0
- package/dist/core/code/shared/integrations/understand-quickly.js +145 -0
- package/dist/core/code/shared/language-detection.js +162 -0
- package/dist/core/code/shared/languages.js +27 -0
- package/dist/core/code/shared/lbug/schema-constants.js +98 -0
- package/dist/core/code/shared/mro-strategy.js +2 -0
- package/dist/core/code/shared/pipeline.js +5 -0
- package/dist/core/code/shared/scope-resolution/callable-flow-site.js +11 -0
- package/dist/core/code/shared/scope-resolution/def-index.js +53 -0
- package/dist/core/code/shared/scope-resolution/evidence-weights.js +87 -0
- package/dist/core/code/shared/scope-resolution/finalize-algorithm.js +807 -0
- package/dist/core/code/shared/scope-resolution/language-classification.js +46 -0
- package/dist/core/code/shared/scope-resolution/method-dispatch-index.js +100 -0
- package/dist/core/code/shared/scope-resolution/module-scope-index.js +59 -0
- package/dist/core/code/shared/scope-resolution/origin-priority.js +23 -0
- package/dist/core/code/shared/scope-resolution/parsed-file.js +54 -0
- package/dist/core/code/shared/scope-resolution/position-index.js +136 -0
- package/dist/core/code/shared/scope-resolution/qualified-name-index.js +77 -0
- package/dist/core/code/shared/scope-resolution/reference-site.js +24 -0
- package/dist/core/code/shared/scope-resolution/registries/class-registry.js +32 -0
- package/dist/core/code/shared/scope-resolution/registries/context.js +52 -0
- package/dist/core/code/shared/scope-resolution/registries/evidence.js +152 -0
- package/dist/core/code/shared/scope-resolution/registries/field-registry.js +33 -0
- package/dist/core/code/shared/scope-resolution/registries/lookup-core.js +392 -0
- package/dist/core/code/shared/scope-resolution/registries/lookup-qualified.js +58 -0
- package/dist/core/code/shared/scope-resolution/registries/macro-registry.js +34 -0
- package/dist/core/code/shared/scope-resolution/registries/method-registry.js +34 -0
- package/dist/core/code/shared/scope-resolution/registries/tie-breaks.js +63 -0
- package/dist/core/code/shared/scope-resolution/resolve-type-ref.js +128 -0
- package/dist/core/code/shared/scope-resolution/scope-id.js +49 -0
- package/dist/core/code/shared/scope-resolution/scope-tree.js +225 -0
- package/dist/core/code/shared/scope-resolution/symbol-definition.js +12 -0
- package/dist/core/code/shared/scope-resolution/types.js +25 -0
- package/dist/core/code/shared/test-helpers.js +17 -0
- package/dist/core/code/vendor/leiden/index.cjs +355 -0
- package/dist/core/code/vendor/leiden/utils.cjs +419 -0
- package/dist/core/dbquery/cli.d.ts.map +1 -1
- package/dist/core/dbquery/cli.js +5 -4
- package/dist/core/dbquery/cli.js.map +1 -1
- package/dist/core/dbquery/config.d.ts.map +1 -1
- package/dist/core/dbquery/config.js +39 -3
- package/dist/core/dbquery/config.js.map +1 -1
- package/dist/core/dbquery/drivers/http.d.ts +23 -0
- package/dist/core/dbquery/drivers/http.d.ts.map +1 -0
- package/dist/core/dbquery/drivers/http.js +125 -0
- package/dist/core/dbquery/drivers/http.js.map +1 -0
- package/dist/core/dbquery/drivers/mysql.js +1 -1
- package/dist/core/dbquery/drivers/mysql.js.map +1 -1
- package/dist/core/dbquery/drivers/postgres.js +1 -1
- package/dist/core/dbquery/drivers/postgres.js.map +1 -1
- package/dist/core/dbquery/executor.d.ts.map +1 -1
- package/dist/core/dbquery/executor.js +46 -12
- package/dist/core/dbquery/executor.js.map +1 -1
- package/dist/core/dbquery/format.d.ts.map +1 -1
- package/dist/core/dbquery/format.js +2 -1
- package/dist/core/dbquery/format.js.map +1 -1
- package/dist/core/dbquery/init.d.ts.map +1 -1
- package/dist/core/dbquery/init.js +7 -0
- package/dist/core/dbquery/init.js.map +1 -1
- package/dist/core/dbquery/types.d.ts +55 -7
- package/dist/core/dbquery/types.d.ts.map +1 -1
- package/dist/core/directory.d.ts +17 -0
- package/dist/core/directory.d.ts.map +1 -1
- package/dist/core/directory.js +37 -0
- package/dist/core/directory.js.map +1 -1
- package/dist/core/features/cli.d.ts.map +1 -1
- package/dist/core/features/cli.js +3 -2
- package/dist/core/features/cli.js.map +1 -1
- package/dist/core/features/scanner.d.ts +1 -1
- package/dist/core/features/scanner.d.ts.map +1 -1
- package/dist/core/features/scanner.js +20 -4
- package/dist/core/features/scanner.js.map +1 -1
- package/dist/core/markdown/cli.d.ts.map +1 -1
- package/dist/core/markdown/cli.js +9 -8
- package/dist/core/markdown/cli.js.map +1 -1
- package/dist/core/timeline/cli.d.ts.map +1 -1
- package/dist/core/timeline/cli.js +13 -6
- package/dist/core/timeline/cli.js.map +1 -1
- package/dist/core/timeline/debris.d.ts +20 -0
- package/dist/core/timeline/debris.d.ts.map +1 -0
- package/dist/core/timeline/debris.js +124 -0
- package/dist/core/timeline/debris.js.map +1 -0
- package/dist/core/timeline/hook-runner.d.ts.map +1 -1
- package/dist/core/timeline/hook-runner.js +3 -1
- package/dist/core/timeline/hook-runner.js.map +1 -1
- package/dist/core/timeline/hooks.d.ts.map +1 -1
- package/dist/core/timeline/hooks.js +13 -5
- package/dist/core/timeline/hooks.js.map +1 -1
- package/dist/core/timeline/installer.d.ts +2 -0
- package/dist/core/timeline/installer.d.ts.map +1 -1
- package/dist/core/timeline/installer.js +12 -0
- package/dist/core/timeline/installer.js.map +1 -1
- package/dist/core/timeline/project-root.d.ts +21 -0
- package/dist/core/timeline/project-root.d.ts.map +1 -0
- package/dist/core/timeline/project-root.js +83 -0
- package/dist/core/timeline/project-root.js.map +1 -0
- package/dist/web/register.d.ts.map +1 -1
- package/dist/web/register.js +2 -1
- package/dist/web/register.js.map +1 -1
- package/package.json +1 -1
- package/dist/.claude-template/.cgraphx/timeline-current-turn.json +0 -1
- package/dist/.claude-template/skills/.cgraphx/timeline-current-turn.json +0 -1
- package/dist/.claude-template/skills/handdrawn-deck/.cgraphx/timeline-current-turn.json +0 -1
- package/dist/.claude-template/skills/handdrawn-deck/references/.cgraphx/timeline-current-turn.json +0 -1
- package/dist/.claude-template/skills/handdrawn-sketch-engine/.cgraphx/timeline-current-turn.json +0 -1
- /package/dist/.claude-template/skills/run-api-test/assets/{template-test-verify.jsonl → template-/346/216/245/345/217/243/346/265/213/350/257/225/351/252/214/350/257/201.jsonl"} +0 -0
|
@@ -0,0 +1,2171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Reproducible analyzer identity stamped into RepoMeta after a successful run.
|
|
4
|
+
*
|
|
5
|
+
* Schema v4 uses length-prefixed canonical frames. Build files and runtime
|
|
6
|
+
* artifacts contribute SHA-256 payload digests, so a validated stat inventory
|
|
7
|
+
* can safely reuse those expensive per-file digests across short-lived CLI and
|
|
8
|
+
* server-worker processes. The cache is only an optimization: malformed,
|
|
9
|
+
* mismatched, or missing entries are rehashed, and every identity calculation
|
|
10
|
+
* performs a final return-boundary inventory before returning.
|
|
11
|
+
*
|
|
12
|
+
* `invokedArtifact` remains in the receipt for diagnostics, but is deliberately
|
|
13
|
+
* excluded from semantic freshness. The CLI and the server analyze worker are
|
|
14
|
+
* different entry files inside the same build tree; alternating between them
|
|
15
|
+
* must not make an otherwise identical index stale.
|
|
16
|
+
*/
|
|
17
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
18
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports._hasNodeModulesSegmentForTests = exports._isInsideForTests = exports._hashAnalyzerIdentityFramesForTests = exports.ANALYZER_RUNNER_IDENTITY_SCHEMA_VERSION = void 0;
|
|
22
|
+
exports.normalizeAnalyzerRootPath = normalizeAnalyzerRootPath;
|
|
23
|
+
exports._clearAnalyzerIdentityProcessCacheForTests = _clearAnalyzerIdentityProcessCacheForTests;
|
|
24
|
+
exports.normalizeAnalyzerRunnerIdentityForComparison = normalizeAnalyzerRunnerIdentityForComparison;
|
|
25
|
+
exports.resolveAnalyzerRunnerIdentity = resolveAnalyzerRunnerIdentity;
|
|
26
|
+
exports.analyzerRunnerIdentitiesEqual = analyzerRunnerIdentitiesEqual;
|
|
27
|
+
exports.captureAnalyzerIdentityBeforeLoad = captureAnalyzerIdentityBeforeLoad;
|
|
28
|
+
exports.finalizeAnalyzerRunnerIdentity = finalizeAnalyzerRunnerIdentity;
|
|
29
|
+
const node_fs_1 = require("node:fs");
|
|
30
|
+
const node_crypto_1 = require("node:crypto");
|
|
31
|
+
const node_child_process_1 = require("node:child_process");
|
|
32
|
+
const node_util_1 = require("node:util");
|
|
33
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
34
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
35
|
+
const node_url_1 = require("node:url");
|
|
36
|
+
exports.ANALYZER_RUNNER_IDENTITY_SCHEMA_VERSION = 4;
|
|
37
|
+
const BUILD_CANONICALIZATION = 'cgraph-analyzer-build-v2';
|
|
38
|
+
const DEPENDENCY_RUNTIME_CANONICALIZATION = 'cgraph-analyzer-dependency-runtime-v4';
|
|
39
|
+
const IDENTITY_CACHE_SCHEMA_VERSION = 6;
|
|
40
|
+
const MAX_CACHE_ENTRIES = 100_000;
|
|
41
|
+
const MAX_CACHE_FILE_BYTES = 64 * 1024 * 1024;
|
|
42
|
+
const HASH_BUFFER_BYTES = 256 * 1024;
|
|
43
|
+
const SHA256_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
44
|
+
const DEFAULT_TRAVERSAL_LIMITS = {
|
|
45
|
+
buildEntries: 100_000,
|
|
46
|
+
buildDepth: 128,
|
|
47
|
+
buildBytes: 512 * 1024 * 1024,
|
|
48
|
+
runtimePackages: 10_000,
|
|
49
|
+
runtimeEdges: 100_000,
|
|
50
|
+
runtimeEntries: 250_000,
|
|
51
|
+
runtimeDepth: 64,
|
|
52
|
+
runtimePayloads: 100_000,
|
|
53
|
+
runtimeBytes: 2 * 1024 * 1024 * 1024,
|
|
54
|
+
resolutionAncestors: 256,
|
|
55
|
+
};
|
|
56
|
+
function toBuffer(value) {
|
|
57
|
+
return Buffer.isBuffer(value) ? value : Buffer.from(value);
|
|
58
|
+
}
|
|
59
|
+
/** Update a hash with one unambiguous, length-prefixed canonical record. */
|
|
60
|
+
function updateCanonicalFrame(hash, fields) {
|
|
61
|
+
const fieldCount = Buffer.allocUnsafe(4);
|
|
62
|
+
fieldCount.writeUInt32BE(fields.length);
|
|
63
|
+
hash.update(fieldCount);
|
|
64
|
+
for (const field of fields) {
|
|
65
|
+
const bytes = toBuffer(field);
|
|
66
|
+
const length = Buffer.allocUnsafe(8);
|
|
67
|
+
length.writeBigUInt64BE(BigInt(bytes.length));
|
|
68
|
+
hash.update(length);
|
|
69
|
+
hash.update(bytes);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function hashCanonicalFrames(frames) {
|
|
73
|
+
const hash = (0, node_crypto_1.createHash)('sha256');
|
|
74
|
+
for (const frame of frames)
|
|
75
|
+
updateCanonicalFrame(hash, frame);
|
|
76
|
+
return `sha256:${hash.digest('hex')}`;
|
|
77
|
+
}
|
|
78
|
+
/** @internal Regression seam for adversarial canonical-framing tests. */
|
|
79
|
+
exports._hashAnalyzerIdentityFramesForTests = hashCanonicalFrames;
|
|
80
|
+
function sha256(payload) {
|
|
81
|
+
return `sha256:${(0, node_crypto_1.createHash)('sha256').update(payload).digest('hex')}`;
|
|
82
|
+
}
|
|
83
|
+
function digestBytes(digest) {
|
|
84
|
+
if (!SHA256_PATTERN.test(digest))
|
|
85
|
+
throw new Error(`Invalid SHA-256 digest: ${digest}`);
|
|
86
|
+
return Buffer.from(digest.slice('sha256:'.length), 'hex');
|
|
87
|
+
}
|
|
88
|
+
function statState(stat) {
|
|
89
|
+
const bigintStat = stat;
|
|
90
|
+
return {
|
|
91
|
+
dev: String(bigintStat.dev),
|
|
92
|
+
ino: String(bigintStat.ino),
|
|
93
|
+
mode: String(bigintStat.mode),
|
|
94
|
+
nlink: String(bigintStat.nlink),
|
|
95
|
+
size: String(bigintStat.size),
|
|
96
|
+
mtimeNs: String(bigintStat.mtimeNs),
|
|
97
|
+
ctimeNs: String(bigintStat.ctimeNs),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function resolveTraversalLimits(options) {
|
|
101
|
+
const overrides = options.traversalLimits ?? {};
|
|
102
|
+
const resolved = { ...DEFAULT_TRAVERSAL_LIMITS };
|
|
103
|
+
for (const key of Object.keys(DEFAULT_TRAVERSAL_LIMITS)) {
|
|
104
|
+
const value = overrides[key];
|
|
105
|
+
if (value === undefined)
|
|
106
|
+
continue;
|
|
107
|
+
if (!Number.isSafeInteger(value) || value < 1) {
|
|
108
|
+
throw new Error(`Analyzer identity traversal limit ${key} must be a positive safe integer`);
|
|
109
|
+
}
|
|
110
|
+
resolved[key] = Math.min(value, DEFAULT_TRAVERSAL_LIMITS[key]);
|
|
111
|
+
}
|
|
112
|
+
return resolved;
|
|
113
|
+
}
|
|
114
|
+
function stateSize(state, label) {
|
|
115
|
+
const value = BigInt(state.size);
|
|
116
|
+
if (value < 0n || value > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
117
|
+
throw new Error(`Analyzer identity input has an unsupported size: ${label}`);
|
|
118
|
+
}
|
|
119
|
+
return Number(value);
|
|
120
|
+
}
|
|
121
|
+
function detectLibcVariant() {
|
|
122
|
+
if (process.platform !== 'linux')
|
|
123
|
+
return 'not-applicable';
|
|
124
|
+
try {
|
|
125
|
+
const report = process.report?.getReport();
|
|
126
|
+
const glibc = report?.header?.glibcVersionRuntime;
|
|
127
|
+
if (typeof glibc === 'string' && glibc.length > 0)
|
|
128
|
+
return `glibc:${glibc}`;
|
|
129
|
+
if (Array.isArray(report?.sharedObjects)) {
|
|
130
|
+
const musl = report.sharedObjects.find((entry) => typeof entry === 'string' && /(?:^|[/\\])(?:ld-)?musl[^/\\]*\.so/i.test(entry));
|
|
131
|
+
if (musl)
|
|
132
|
+
return `musl:${node_path_1.default.basename(musl)}`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// Runtime reporting is optional on some embedded Node builds. Unknown is
|
|
137
|
+
// still a distinct, fail-closed variant rather than being conflated with
|
|
138
|
+
// glibc or a known musl loader.
|
|
139
|
+
}
|
|
140
|
+
return 'linux-libc:unknown';
|
|
141
|
+
}
|
|
142
|
+
const LIBC_VARIANT = detectLibcVariant();
|
|
143
|
+
function resolveRuntimeVariant() {
|
|
144
|
+
return {
|
|
145
|
+
// Normalized like build.rootPath (#2668): executablePath is a compared
|
|
146
|
+
// identity field (only invokedArtifact is stripped in the comparison), and
|
|
147
|
+
// process.execPath carries the same Windows drive-letter case ambiguity —
|
|
148
|
+
// so leaving it un-normalized would reintroduce the false-stale via runtime.
|
|
149
|
+
executablePath: normalizeAnalyzerRootPath(resolveExistingPath(process.execPath), process.platform),
|
|
150
|
+
nodeVersion: process.version,
|
|
151
|
+
platform: process.platform,
|
|
152
|
+
architecture: process.arch,
|
|
153
|
+
endianness: node_os_1.default.endianness(),
|
|
154
|
+
modulesAbi: process.versions.modules ?? 'unknown',
|
|
155
|
+
napiAbi: process.versions.napi ?? 'unknown',
|
|
156
|
+
libc: LIBC_VARIANT,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function snapshotReadableFile(candidate) {
|
|
160
|
+
const link = (0, node_fs_1.lstatSync)(candidate, { bigint: true });
|
|
161
|
+
const target = (0, node_fs_1.statSync)(candidate, { bigint: true });
|
|
162
|
+
if (!target.isFile())
|
|
163
|
+
throw new Error(`Analyzer identity input is not a file: ${candidate}`);
|
|
164
|
+
return {
|
|
165
|
+
link: statState(link),
|
|
166
|
+
target: statState(target),
|
|
167
|
+
...(link.isSymbolicLink() ? { symlinkTarget: (0, node_fs_1.readlinkSync)(candidate) } : {}),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Snapshot a symbolic link without resolving it. Unlike
|
|
172
|
+
* {@link snapshotReadableFile} this never stats the target, so it is total over
|
|
173
|
+
* linked directories, dangling links, and links to device nodes — the inputs
|
|
174
|
+
* that make the readable-file snapshot throw.
|
|
175
|
+
*/
|
|
176
|
+
function snapshotSymlinkArtifact(candidate) {
|
|
177
|
+
const link = (0, node_fs_1.lstatSync)(candidate, { bigint: true });
|
|
178
|
+
if (!link.isSymbolicLink()) {
|
|
179
|
+
throw new Error(`Analyzer identity input is not a symbolic link: ${candidate}`);
|
|
180
|
+
}
|
|
181
|
+
return { link: statState(link), symlinkTarget: (0, node_fs_1.readlinkSync)(candidate) };
|
|
182
|
+
}
|
|
183
|
+
function snapshotRuntimeArtifact(artifact) {
|
|
184
|
+
return artifact.kind === 'unfollowed-symlink'
|
|
185
|
+
? snapshotSymlinkArtifact(artifact.absolutePath)
|
|
186
|
+
: snapshotReadableFile(artifact.absolutePath);
|
|
187
|
+
}
|
|
188
|
+
function snapshotDirectory(candidate) {
|
|
189
|
+
const stat = (0, node_fs_1.lstatSync)(candidate, { bigint: true });
|
|
190
|
+
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
191
|
+
throw new Error(`Analyzer identity input is not a directory: ${candidate}`);
|
|
192
|
+
}
|
|
193
|
+
return statState(stat);
|
|
194
|
+
}
|
|
195
|
+
function readDirectory(candidate, options) {
|
|
196
|
+
options.onCacheMissWork?.({ kind: 'directory-walk', path: candidate });
|
|
197
|
+
return (0, node_fs_1.readdirSync)(candidate, { withFileTypes: true });
|
|
198
|
+
}
|
|
199
|
+
function directoryEntriesDigestFrom(entriesInput) {
|
|
200
|
+
const entries = entriesInput
|
|
201
|
+
.map((entry) => ({
|
|
202
|
+
name: entry.name,
|
|
203
|
+
kind: entry.isDirectory()
|
|
204
|
+
? 'directory'
|
|
205
|
+
: entry.isFile()
|
|
206
|
+
? 'file'
|
|
207
|
+
: entry.isSymbolicLink()
|
|
208
|
+
? 'symlink'
|
|
209
|
+
: 'other',
|
|
210
|
+
}))
|
|
211
|
+
.sort((a, b) => compareBytes(a.name, b.name));
|
|
212
|
+
const hash = (0, node_crypto_1.createHash)('sha256');
|
|
213
|
+
updateCanonicalFrame(hash, ['directory-entries-v1']);
|
|
214
|
+
for (const entry of entries)
|
|
215
|
+
updateCanonicalFrame(hash, [entry.name, entry.kind]);
|
|
216
|
+
return `sha256:${hash.digest('hex')}`;
|
|
217
|
+
}
|
|
218
|
+
function directoryEntriesDigest(candidate) {
|
|
219
|
+
return directoryEntriesDigestFrom((0, node_fs_1.readdirSync)(candidate, { withFileTypes: true }));
|
|
220
|
+
}
|
|
221
|
+
function snapshotDirectoryInventory(candidate) {
|
|
222
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
223
|
+
const before = snapshotDirectory(candidate);
|
|
224
|
+
const entriesDigest = directoryEntriesDigest(candidate);
|
|
225
|
+
const after = snapshotDirectory(candidate);
|
|
226
|
+
if ((0, node_util_1.isDeepStrictEqual)(before, after))
|
|
227
|
+
return { state: after, entriesDigest };
|
|
228
|
+
}
|
|
229
|
+
throw new Error(`Analyzer identity directory changed while it was read: ${candidate}`);
|
|
230
|
+
}
|
|
231
|
+
function readStableFile(candidate) {
|
|
232
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
233
|
+
const before = snapshotReadableFile(candidate);
|
|
234
|
+
const bytes = (0, node_fs_1.readFileSync)(candidate);
|
|
235
|
+
const after = snapshotReadableFile(candidate);
|
|
236
|
+
if ((0, node_util_1.isDeepStrictEqual)(before, after))
|
|
237
|
+
return { bytes, state: after };
|
|
238
|
+
}
|
|
239
|
+
throw new Error(`Analyzer identity input changed while it was being read: ${candidate}`);
|
|
240
|
+
}
|
|
241
|
+
function readStableFileWithinBudget(candidate, budget, maxBytes) {
|
|
242
|
+
const before = snapshotReadableFile(candidate);
|
|
243
|
+
const bytes = stateSize(before.target, candidate);
|
|
244
|
+
if (budget.bytes + bytes > maxBytes) {
|
|
245
|
+
throw new Error(`Analyzer runtime scan exceeded ${maxBytes} bytes: ${candidate}`);
|
|
246
|
+
}
|
|
247
|
+
const stable = readStableFile(candidate);
|
|
248
|
+
budget.bytes += stable.bytes.length;
|
|
249
|
+
return stable;
|
|
250
|
+
}
|
|
251
|
+
/** Hash a stable file through a fixed-size buffer instead of materializing it. */
|
|
252
|
+
function hashStableFile(candidate) {
|
|
253
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
254
|
+
const before = snapshotReadableFile(candidate);
|
|
255
|
+
const expectedBytes = stateSize(before.target, candidate);
|
|
256
|
+
let descriptor = null;
|
|
257
|
+
try {
|
|
258
|
+
descriptor = (0, node_fs_1.openSync)(candidate, node_fs_1.constants.O_RDONLY);
|
|
259
|
+
const openedBefore = statState((0, node_fs_1.fstatSync)(descriptor, { bigint: true }));
|
|
260
|
+
if (!(0, node_util_1.isDeepStrictEqual)(openedBefore, before.target))
|
|
261
|
+
continue;
|
|
262
|
+
const hash = (0, node_crypto_1.createHash)('sha256');
|
|
263
|
+
const buffer = Buffer.allocUnsafe(HASH_BUFFER_BYTES);
|
|
264
|
+
let bytes = 0;
|
|
265
|
+
while (true) {
|
|
266
|
+
const read = (0, node_fs_1.readSync)(descriptor, buffer, 0, buffer.length, null);
|
|
267
|
+
if (read === 0)
|
|
268
|
+
break;
|
|
269
|
+
hash.update(buffer.subarray(0, read));
|
|
270
|
+
bytes += read;
|
|
271
|
+
if (bytes > expectedBytes)
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
const openedAfter = statState((0, node_fs_1.fstatSync)(descriptor, { bigint: true }));
|
|
275
|
+
(0, node_fs_1.closeSync)(descriptor);
|
|
276
|
+
descriptor = null;
|
|
277
|
+
const after = snapshotReadableFile(candidate);
|
|
278
|
+
if (bytes === expectedBytes &&
|
|
279
|
+
(0, node_util_1.isDeepStrictEqual)(openedBefore, openedAfter) &&
|
|
280
|
+
(0, node_util_1.isDeepStrictEqual)(before, after)) {
|
|
281
|
+
return { digest: `sha256:${hash.digest('hex')}`, state: after, bytes };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
finally {
|
|
285
|
+
if (descriptor !== null)
|
|
286
|
+
(0, node_fs_1.closeSync)(descriptor);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
throw new Error(`Analyzer identity input changed while it was being hashed: ${candidate}`);
|
|
290
|
+
}
|
|
291
|
+
function resolveExistingPath(candidate) {
|
|
292
|
+
return node_fs_1.realpathSync.native(node_path_1.default.resolve(candidate));
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Case-stabilize a path's Windows drive letter so two processes that observed
|
|
296
|
+
* the same directory under different drive-letter casing (`c:\…` vs `C:\…`)
|
|
297
|
+
* produce byte-identical analyzer-identity path fields (#2668).
|
|
298
|
+
*
|
|
299
|
+
* `realpathSync.native` canonicalizes 8.3 short names and symlinks but does not
|
|
300
|
+
* guarantee the drive-letter case it returns — it can preserve whatever casing
|
|
301
|
+
* the caller's path carried, and `import.meta.url` casing depends on how each
|
|
302
|
+
* entry process (CLI shim vs `npx`/npm wrapper vs server worker) was launched.
|
|
303
|
+
* When `analyze` stamps `build.rootPath` under one casing and `status`
|
|
304
|
+
* recomputes it under another, `analyzerRunnerIdentitiesEqual` deep-compares
|
|
305
|
+
* unequal and `status` reports a freshly-analyzed, untouched repo as stale.
|
|
306
|
+
* Uppercasing the drive letter (drive letters are case-insensitive; uppercase
|
|
307
|
+
* is the conventional form) collapses that variance. POSIX paths are returned
|
|
308
|
+
* unchanged. `platform` is explicit so the transform is unit-testable off
|
|
309
|
+
* Windows.
|
|
310
|
+
*
|
|
311
|
+
* The optional `\\?\` extended-length prefix is preserved and the drive letter
|
|
312
|
+
* after it is still normalized; UNC paths (`\\server\share`, `\\?\UNC\...`)
|
|
313
|
+
* have no drive letter and are left untouched.
|
|
314
|
+
*
|
|
315
|
+
* That optional group is defensive, not a case `realpathSync.native` produces:
|
|
316
|
+
* libuv's `fs__realpath_handle` strips `\\?\` (and rewrites `\\?\UNC\` back to
|
|
317
|
+
* `\\`) before returning, so the prefix can only reach here from caller-supplied
|
|
318
|
+
* input, which `path.resolve` preserves (#2667).
|
|
319
|
+
*
|
|
320
|
+
* Preserving it is load-bearing. The roots this normalizes are not just compared —
|
|
321
|
+
* they are READ FROM: `resolveBuildRoot` joins `package.json` onto `packageRoot`,
|
|
322
|
+
* `collectBuildEntries` walks `buildRoot`, and the lockfile lookup walks
|
|
323
|
+
* `packageRoot`'s ancestors. Node does not re-add `\\?\` for over-MAX_PATH paths,
|
|
324
|
+
* so stripping here would break analyzer-identity resolution on a deep checkout
|
|
325
|
+
* exactly as it would at any other filesystem boundary. (These fields are also
|
|
326
|
+
* compared between an `analyze` and a later `status` run, so a shape change would
|
|
327
|
+
* additionally risk the #2668 false-stale class — but the filesystem reads are the
|
|
328
|
+
* reason that matters.)
|
|
329
|
+
*
|
|
330
|
+
* Registry-style path COMPARISON is a different domain, never opens what it
|
|
331
|
+
* canonicalizes, and does normalize the prefix away: see
|
|
332
|
+
* `stripWindowsLongPathPrefix` in `src/lib/utils.ts` and its use in
|
|
333
|
+
* `canonicalizePath`.
|
|
334
|
+
*/
|
|
335
|
+
function normalizeAnalyzerRootPath(p, platform) {
|
|
336
|
+
if (platform !== 'win32')
|
|
337
|
+
return p;
|
|
338
|
+
return p.replace(/^(\\\\\?\\)?([a-z]):/, (_match, prefix, drive) => `${prefix ?? ''}${drive.toUpperCase()}:`);
|
|
339
|
+
}
|
|
340
|
+
function isFile(candidate) {
|
|
341
|
+
try {
|
|
342
|
+
return (0, node_fs_1.statSync)(candidate).isFile();
|
|
343
|
+
}
|
|
344
|
+
catch {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
function readManifest(manifestPath, options, budget, limits) {
|
|
349
|
+
options.onCacheMissWork?.({ kind: 'manifest-read', path: manifestPath });
|
|
350
|
+
const { bytes, state } = readStableFileWithinBudget(manifestPath, budget, limits.runtimeBytes);
|
|
351
|
+
return { bytes, state, manifest: JSON.parse(bytes.toString('utf8')) };
|
|
352
|
+
}
|
|
353
|
+
function manifestLabel(manifest) {
|
|
354
|
+
const name = typeof manifest.name === 'string' ? manifest.name : '<unnamed>';
|
|
355
|
+
const version = typeof manifest.version === 'string' ? manifest.version : '<unversioned>';
|
|
356
|
+
return `${name}@${version}`;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Whether `candidate` is `parent` itself or lives beneath it.
|
|
360
|
+
*
|
|
361
|
+
* The absolute-result rejection is load-bearing on Windows: `path.relative`
|
|
362
|
+
* cannot express a relative path between two different drives, so it returns the
|
|
363
|
+
* absolute target instead — `path.win32.relative('C:\\parent', 'D:\\other')` is
|
|
364
|
+
* `'D:\\other'`. That string does not start with `..`, so the `..` checks alone
|
|
365
|
+
* would report an unrelated drive as *inside* the parent. This mirrors the
|
|
366
|
+
* containment guards elsewhere in the repo (`server/api.ts`,
|
|
367
|
+
* `server/git-clone.ts`, `group/extractors/fs-utils.ts`), which all pair the
|
|
368
|
+
* `..` check with `path.isAbsolute`.
|
|
369
|
+
*
|
|
370
|
+
* `pathApi` is injectable so the win32 semantics are unit-testable from a POSIX
|
|
371
|
+
* runner; production callers always use the platform-bound `path`.
|
|
372
|
+
*/
|
|
373
|
+
function isInside(parent, candidate, pathApi = node_path_1.default) {
|
|
374
|
+
const relative = pathApi.relative(parent, candidate);
|
|
375
|
+
if (pathApi.isAbsolute(relative))
|
|
376
|
+
return false;
|
|
377
|
+
return relative === '' || (!relative.startsWith(`..${pathApi.sep}`) && relative !== '..');
|
|
378
|
+
}
|
|
379
|
+
/** Test seam for {@link isInside} (see `_hashAnalyzerIdentityFramesForTests`). */
|
|
380
|
+
exports._isInsideForTests = isInside;
|
|
381
|
+
function resolveBuildRoot(analyzerModulePath) {
|
|
382
|
+
let cursor = node_path_1.default.dirname(analyzerModulePath);
|
|
383
|
+
while (true) {
|
|
384
|
+
const base = node_path_1.default.basename(cursor);
|
|
385
|
+
if (base === 'src' || base === 'dist') {
|
|
386
|
+
const packageRoot = node_path_1.default.dirname(cursor);
|
|
387
|
+
const packageJson = node_path_1.default.join(packageRoot, 'package.json');
|
|
388
|
+
if ((0, node_fs_1.lstatSync)(packageJson).isFile()) {
|
|
389
|
+
// Normalize the drive-letter case at this single upstream source so
|
|
390
|
+
// every derived identity path field — build.rootPath, identityCacheKey,
|
|
391
|
+
// and (via collectDependencyInputs) dependencyRuntime.manifestPath /
|
|
392
|
+
// lockfilePath — inherits a case-stable root and analyze-stamp equals
|
|
393
|
+
// status-recompute regardless of launch-path casing (#2668).
|
|
394
|
+
// Migration: a Windows index stamped before this fix carries the old,
|
|
395
|
+
// un-normalized casing, so the first post-upgrade `status` sees one
|
|
396
|
+
// spurious "stale" flip — self-healing on the next `analyze`, which
|
|
397
|
+
// re-stamps the normalized (idempotent) form.
|
|
398
|
+
return {
|
|
399
|
+
packageRoot: normalizeAnalyzerRootPath(packageRoot, process.platform),
|
|
400
|
+
buildRoot: normalizeAnalyzerRootPath(cursor, process.platform),
|
|
401
|
+
kind: base === 'src' ? 'source' : 'distribution',
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const parent = node_path_1.default.dirname(cursor);
|
|
406
|
+
if (parent === cursor)
|
|
407
|
+
break;
|
|
408
|
+
cursor = parent;
|
|
409
|
+
}
|
|
410
|
+
throw new Error(`Cannot resolve GitNexus package root from analyzer module: ${analyzerModulePath}`);
|
|
411
|
+
}
|
|
412
|
+
function compareBytes(a, b) {
|
|
413
|
+
return Buffer.compare(Buffer.from(a), Buffer.from(b));
|
|
414
|
+
}
|
|
415
|
+
function collectBuildEntries(buildRoot, options, limits) {
|
|
416
|
+
const entries = [];
|
|
417
|
+
const pending = [
|
|
418
|
+
{ absoluteDir: buildRoot, depth: 0 },
|
|
419
|
+
];
|
|
420
|
+
let scannedEntries = 0;
|
|
421
|
+
let scannedBytes = 0;
|
|
422
|
+
while (pending.length > 0) {
|
|
423
|
+
const next = pending.pop();
|
|
424
|
+
if (!next)
|
|
425
|
+
break;
|
|
426
|
+
const { absoluteDir, depth } = next;
|
|
427
|
+
const directoryEntries = readDirectory(absoluteDir, options);
|
|
428
|
+
scannedEntries += directoryEntries.length;
|
|
429
|
+
if (scannedEntries > limits.buildEntries) {
|
|
430
|
+
throw new Error(`Analyzer build scan exceeded ${limits.buildEntries} entries: ${buildRoot}`);
|
|
431
|
+
}
|
|
432
|
+
for (const entry of directoryEntries) {
|
|
433
|
+
const absolutePath = node_path_1.default.join(absoluteDir, entry.name);
|
|
434
|
+
const relativePath = node_path_1.default.relative(buildRoot, absolutePath).split(node_path_1.default.sep).join('/');
|
|
435
|
+
const link = (0, node_fs_1.lstatSync)(absolutePath, { bigint: true });
|
|
436
|
+
if (link.isDirectory()) {
|
|
437
|
+
entries.push({
|
|
438
|
+
absolutePath,
|
|
439
|
+
relativePath,
|
|
440
|
+
kind: 'directory',
|
|
441
|
+
state: statState(link),
|
|
442
|
+
});
|
|
443
|
+
if (depth >= limits.buildDepth) {
|
|
444
|
+
throw new Error(`Analyzer build scan exceeded depth ${limits.buildDepth}: ${absolutePath}`);
|
|
445
|
+
}
|
|
446
|
+
pending.push({ absoluteDir: absolutePath, depth: depth + 1 });
|
|
447
|
+
}
|
|
448
|
+
else if (link.isFile()) {
|
|
449
|
+
const state = statState(link);
|
|
450
|
+
scannedBytes += stateSize(state, absolutePath);
|
|
451
|
+
if (scannedBytes > limits.buildBytes) {
|
|
452
|
+
throw new Error(`Analyzer build scan exceeded ${limits.buildBytes} bytes: ${buildRoot}`);
|
|
453
|
+
}
|
|
454
|
+
entries.push({ absolutePath, relativePath, kind: 'file', state });
|
|
455
|
+
}
|
|
456
|
+
else if (link.isSymbolicLink()) {
|
|
457
|
+
entries.push({ absolutePath, relativePath, kind: 'symlink', state: statState(link) });
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
throw new Error(`Unsupported analyzer build entry: ${absolutePath}`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
entries.sort((a, b) => compareBytes(a.relativePath, b.relativePath));
|
|
465
|
+
return entries;
|
|
466
|
+
}
|
|
467
|
+
function buildSnapshot(entries) {
|
|
468
|
+
return entries.map(({ relativePath, kind, state }) => ({ relativePath, kind, state }));
|
|
469
|
+
}
|
|
470
|
+
function buildCacheKey(entry) {
|
|
471
|
+
return JSON.stringify([entry.kind, entry.relativePath]);
|
|
472
|
+
}
|
|
473
|
+
function hashBuildTree(buildRoot, cache, options, limits) {
|
|
474
|
+
const entries = collectBuildEntries(buildRoot, options, limits);
|
|
475
|
+
const cachedEntries = new Map((cache?.buildEntries ?? []).map((entry) => [buildCacheKey(entry), entry]));
|
|
476
|
+
const nextEntries = [];
|
|
477
|
+
const hash = (0, node_crypto_1.createHash)('sha256');
|
|
478
|
+
updateCanonicalFrame(hash, ['domain', BUILD_CANONICALIZATION]);
|
|
479
|
+
for (const entry of entries) {
|
|
480
|
+
const cached = cachedEntries.get(buildCacheKey(entry));
|
|
481
|
+
let digest;
|
|
482
|
+
if (entry.kind !== 'directory' &&
|
|
483
|
+
cached?.digest &&
|
|
484
|
+
SHA256_PATTERN.test(cached.digest) &&
|
|
485
|
+
(0, node_util_1.isDeepStrictEqual)(cached.state, entry.state)) {
|
|
486
|
+
digest = cached.digest;
|
|
487
|
+
}
|
|
488
|
+
else if (entry.kind === 'file') {
|
|
489
|
+
const stable = hashStableFile(entry.absolutePath);
|
|
490
|
+
entry.state = stable.state.link;
|
|
491
|
+
digest = stable.digest;
|
|
492
|
+
options.onHashedInput?.({
|
|
493
|
+
kind: 'build',
|
|
494
|
+
path: entry.absolutePath,
|
|
495
|
+
bytes: stable.bytes,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
else if (entry.kind === 'symlink') {
|
|
499
|
+
// Imported files and directories are resolved through symlinks by Node.
|
|
500
|
+
// Hashing only link text would let bytes outside buildRoot change without
|
|
501
|
+
// changing the receipt. Reject them instead of inventing an incomplete
|
|
502
|
+
// recursive trust boundary (target containment, cycles, and TOCTOU).
|
|
503
|
+
throw new Error(`Analyzer build symbolic links are not supported; materialize the build tree: ${entry.absolutePath}`);
|
|
504
|
+
}
|
|
505
|
+
updateCanonicalFrame(hash, [
|
|
506
|
+
'build-entry',
|
|
507
|
+
entry.relativePath,
|
|
508
|
+
entry.kind,
|
|
509
|
+
digest ? digestBytes(digest) : Buffer.alloc(0),
|
|
510
|
+
]);
|
|
511
|
+
nextEntries.push({
|
|
512
|
+
relativePath: entry.relativePath,
|
|
513
|
+
kind: entry.kind,
|
|
514
|
+
state: entry.state,
|
|
515
|
+
...(digest ? { digest } : {}),
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
const directoryGuards = [
|
|
519
|
+
{ relativePath: '', state: snapshotDirectory(buildRoot), entriesDigest: '' },
|
|
520
|
+
...nextEntries
|
|
521
|
+
.filter((entry) => entry.kind === 'directory')
|
|
522
|
+
.map((entry) => ({
|
|
523
|
+
relativePath: entry.relativePath,
|
|
524
|
+
state: entry.state,
|
|
525
|
+
entriesDigest: '',
|
|
526
|
+
})),
|
|
527
|
+
].map((guard) => {
|
|
528
|
+
const absolutePath = guard.relativePath
|
|
529
|
+
? node_path_1.default.join(buildRoot, ...guard.relativePath.split('/'))
|
|
530
|
+
: buildRoot;
|
|
531
|
+
const inventory = snapshotDirectoryInventory(absolutePath);
|
|
532
|
+
return {
|
|
533
|
+
relativePath: guard.relativePath,
|
|
534
|
+
state: inventory.state,
|
|
535
|
+
entriesDigest: inventory.entriesDigest,
|
|
536
|
+
};
|
|
537
|
+
});
|
|
538
|
+
return {
|
|
539
|
+
digest: `sha256:${hash.digest('hex')}`,
|
|
540
|
+
entries: nextEntries,
|
|
541
|
+
snapshot: buildSnapshot(entries),
|
|
542
|
+
rootState: directoryGuards[0].state,
|
|
543
|
+
directoryGuards,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function recordDirectoryGuard(guards, candidate) {
|
|
547
|
+
if (guards.has(candidate))
|
|
548
|
+
return true;
|
|
549
|
+
try {
|
|
550
|
+
guards.set(candidate, snapshotDirectoryInventory(candidate));
|
|
551
|
+
return true;
|
|
552
|
+
}
|
|
553
|
+
catch {
|
|
554
|
+
return false;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
function snapshotDependencyPathGuard(candidate) {
|
|
558
|
+
try {
|
|
559
|
+
const stat = (0, node_fs_1.lstatSync)(candidate, { bigint: true });
|
|
560
|
+
const type = stat.isDirectory()
|
|
561
|
+
? 'directory'
|
|
562
|
+
: stat.isFile()
|
|
563
|
+
? 'file'
|
|
564
|
+
: stat.isSymbolicLink()
|
|
565
|
+
? 'symlink'
|
|
566
|
+
: 'other';
|
|
567
|
+
return {
|
|
568
|
+
type,
|
|
569
|
+
state: statState(stat),
|
|
570
|
+
...(type === 'symlink' ? { symlinkTarget: (0, node_fs_1.readlinkSync)(candidate) } : {}),
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
catch {
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
function recordDependencyPathGuard(guards, candidate) {
|
|
578
|
+
if (guards.has(candidate))
|
|
579
|
+
return guards.get(candidate) ?? null;
|
|
580
|
+
const result = snapshotDependencyPathGuard(candidate);
|
|
581
|
+
guards.set(candidate, result);
|
|
582
|
+
return result;
|
|
583
|
+
}
|
|
584
|
+
function findNearestPackageLock(packageRoot, pathGuards, limits) {
|
|
585
|
+
let cursor = packageRoot;
|
|
586
|
+
let ancestors = 0;
|
|
587
|
+
while (true) {
|
|
588
|
+
ancestors += 1;
|
|
589
|
+
if (ancestors > limits.resolutionAncestors) {
|
|
590
|
+
throw new Error(`Analyzer package-lock lookup exceeded ${limits.resolutionAncestors} ancestors: ${packageRoot}`);
|
|
591
|
+
}
|
|
592
|
+
const candidate = node_path_1.default.join(cursor, 'package-lock.json');
|
|
593
|
+
recordDependencyPathGuard(pathGuards, candidate);
|
|
594
|
+
// Preserve the link path so ReadableFileState guards both the link and its
|
|
595
|
+
// resolved target. Realpathing here would miss a later retarget while the
|
|
596
|
+
// old target remained unchanged.
|
|
597
|
+
try {
|
|
598
|
+
const link = (0, node_fs_1.lstatSync)(candidate);
|
|
599
|
+
if (link.isFile())
|
|
600
|
+
return node_path_1.default.resolve(candidate);
|
|
601
|
+
if (link.isSymbolicLink()) {
|
|
602
|
+
try {
|
|
603
|
+
const target = (0, node_fs_1.statSync)(candidate);
|
|
604
|
+
if (!target.isFile()) {
|
|
605
|
+
throw new Error(`Analyzer package lock symbolic link does not resolve to a file: ${candidate}`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
catch {
|
|
609
|
+
throw new Error(`Analyzer package lock symbolic link does not resolve to a file: ${candidate}`);
|
|
610
|
+
}
|
|
611
|
+
return node_path_1.default.resolve(candidate);
|
|
612
|
+
}
|
|
613
|
+
throw new Error(`Analyzer package lock is not a regular file: ${candidate}`);
|
|
614
|
+
}
|
|
615
|
+
catch (error) {
|
|
616
|
+
if (error.code !== 'ENOENT')
|
|
617
|
+
throw error;
|
|
618
|
+
}
|
|
619
|
+
const parent = node_path_1.default.dirname(cursor);
|
|
620
|
+
if (parent === cursor)
|
|
621
|
+
return null;
|
|
622
|
+
cursor = parent;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
function runtimePackageLocator(packageRoot, runtimeRoot) {
|
|
626
|
+
if (runtimeRoot === packageRoot)
|
|
627
|
+
return 'root:.';
|
|
628
|
+
const relative = node_path_1.default.relative(packageRoot, runtimeRoot).split(node_path_1.default.sep).join('/');
|
|
629
|
+
return `relative:${relative}`;
|
|
630
|
+
}
|
|
631
|
+
/** Protocols that name a checkout-local package instead of a registry tarball. */
|
|
632
|
+
const LOCAL_LINK_PROTOCOL_PATTERN = /^(?:file|link|workspace|portal):/;
|
|
633
|
+
/** npm's bare local-path shorthands: `./x`, `../x`, `/x`, `~/x`, `C:\x`. */
|
|
634
|
+
const LOCAL_LINK_PATH_PATTERN = /^(?:\.\.?[/\\]|~[/\\]|[/\\]|[A-Za-z]:)/;
|
|
635
|
+
function isLocallyLinkedSpecifier(specifier) {
|
|
636
|
+
if (typeof specifier !== 'string')
|
|
637
|
+
return false;
|
|
638
|
+
const value = specifier.trim();
|
|
639
|
+
return LOCAL_LINK_PROTOCOL_PATTERN.test(value) || LOCAL_LINK_PATH_PATTERN.test(value);
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Whether a REALPATH'd package root lives inside some installed dependency
|
|
643
|
+
* tree. Used as the resolved-location half of "is this dependency a checkout
|
|
644
|
+
* this repository owns?" (see {@link undeclaredLocalDevDependencyNames}).
|
|
645
|
+
*
|
|
646
|
+
* The input must already be realpath'd: `resolveDependencyPackageRoot` returns
|
|
647
|
+
* `realpathSync.native`, so a package reached through a link out of
|
|
648
|
+
* `node_modules` reports its checkout location and a package that merely lives
|
|
649
|
+
* in `node_modules` reports a path that still carries the segment.
|
|
650
|
+
*
|
|
651
|
+
* `pathApi` is injectable so the Windows separator handling is unit-testable
|
|
652
|
+
* from a POSIX runner, exactly as {@link isInside} does. The separator sets
|
|
653
|
+
* differ deliberately: `\` is a legal filename character on POSIX, so only
|
|
654
|
+
* win32 may treat it as a boundary.
|
|
655
|
+
*/
|
|
656
|
+
function hasNodeModulesSegment(candidate, pathApi = node_path_1.default) {
|
|
657
|
+
const segments = pathApi.sep === '\\' ? candidate.split(/[\\/]+/) : candidate.split('/');
|
|
658
|
+
return segments.includes('node_modules');
|
|
659
|
+
}
|
|
660
|
+
/** Test seam for {@link hasNodeModulesSegment} (see {@link _isInsideForTests}). */
|
|
661
|
+
exports._hasNodeModulesSegmentForTests = hasNodeModulesSegment;
|
|
662
|
+
/**
|
|
663
|
+
* How many dev dependencies may be admitted by RESOLVED LOCATION alone before
|
|
664
|
+
* the whole resolved-location channel is treated as untrustworthy and disabled.
|
|
665
|
+
*
|
|
666
|
+
* "Realpath carries no `node_modules` segment" is a proxy for "checkout-local",
|
|
667
|
+
* and a layout that materializes packages outside `node_modules` — pnpm with a
|
|
668
|
+
* relocated `virtual-store-dir`, a custom linker — makes every dev dependency
|
|
669
|
+
* pass it. Folding an entire dev tree into the receipt is not a graceful
|
|
670
|
+
* degradation: `runtimePackages`/`runtimeEntries`/`runtimeBytes` THROW, so a
|
|
671
|
+
* mis-fired proxy on a legitimate install would abort analyze outright.
|
|
672
|
+
*
|
|
673
|
+
* The bound is therefore on ADMISSIONS, and overflow admits NONE of them rather
|
|
674
|
+
* than an arbitrary prefix. A prefix would not bound the failure — the abort
|
|
675
|
+
* comes from the transitive payload of whichever trees get folded in — and it
|
|
676
|
+
* would make the receipt depend on an arbitrary slice of a sorted name list.
|
|
677
|
+
* Dropping the channel wholesale falls back to the specifier-only receipt,
|
|
678
|
+
* which is the behaviour that ships today and is known not to abort, and leaves
|
|
679
|
+
* the declared-intent half in {@link dependencyNames} untouched.
|
|
680
|
+
*
|
|
681
|
+
* Four is measured, not guessed. Monorepo and workspace links are declared
|
|
682
|
+
* (`file:`/`link:`/`workspace:`) and travel the uncapped declared half, so this
|
|
683
|
+
* channel only ever carries UNDECLARED `npm link <pkg>` — a manual, per-package
|
|
684
|
+
* developer action, in practice one or two packages. A mis-fire admits the
|
|
685
|
+
* entire dev-only set instead: 13 names in this repository's own install, tens
|
|
686
|
+
* in a typical application. The cap sits an order of magnitude below the
|
|
687
|
+
* mis-fire population and comfortably above realistic link counts.
|
|
688
|
+
*/
|
|
689
|
+
const MAX_UNDECLARED_LOCAL_DEV_DEPENDENCIES = 4;
|
|
690
|
+
/**
|
|
691
|
+
* Dependency names whose resolved packages can contribute analyzer semantics.
|
|
692
|
+
*
|
|
693
|
+
* The three runtime sections are enumerated wholesale. `devDependencies` are
|
|
694
|
+
* deliberately not: a registry dev tool (vitest, eslint, typescript) is never
|
|
695
|
+
* loaded by the analyzer, and folding the dev tree into the receipt would churn
|
|
696
|
+
* `dependencyRuntime.digest` — and force a full re-analysis — on every unrelated
|
|
697
|
+
* devDependency bump.
|
|
698
|
+
*
|
|
699
|
+
* Locally linked dev dependencies are the exception. A `file:`/`link:`/
|
|
700
|
+
* `workspace:` sibling is part of this checkout and ships code the analyzer
|
|
701
|
+
* imports at runtime: GitNexus links `cgraph-shared`, whose schema constants
|
|
702
|
+
* feed `RELATION_SCHEMA`/`NODE_SCHEMA_QUERIES`. In `kind: 'source'` runs that
|
|
703
|
+
* sibling sits outside `buildRoot`, so leaving it out let a semantic change
|
|
704
|
+
* there alter analyzer behaviour while moving neither `build.digest` nor
|
|
705
|
+
* `dependencyRuntime.digest` — DDL-affecting edits were still caught by the
|
|
706
|
+
* schema fingerprint, semantics-only edits by nothing.
|
|
707
|
+
*
|
|
708
|
+
* An unresolvable link (a published install, where the sibling checkout does not
|
|
709
|
+
* exist) still contributes its `<missing>` edge, so the linked package appearing
|
|
710
|
+
* or disappearing remains a receipt change rather than a silent one. That is why
|
|
711
|
+
* the specifier check cannot be replaced by resolution: resolution returns
|
|
712
|
+
* `null` for an absent linked checkout exactly as it does for an uninstalled
|
|
713
|
+
* registry dev tool, and the two must not be conflated.
|
|
714
|
+
*
|
|
715
|
+
* This function is the DECLARED-INTENT half and is enumerated for every package
|
|
716
|
+
* in the dependency BFS, so it must stay a pure function of the manifest. The
|
|
717
|
+
* RESOLVED-LOCATION half — `npm link <pkg>`, which leaves the specifier a
|
|
718
|
+
* registry range — lives in {@link undeclaredLocalDevDependencyNames} and is
|
|
719
|
+
* applied to the root package only.
|
|
720
|
+
*/
|
|
721
|
+
function dependencyNames(manifest) {
|
|
722
|
+
const names = new Set();
|
|
723
|
+
for (const section of [
|
|
724
|
+
manifest.dependencies,
|
|
725
|
+
manifest.optionalDependencies,
|
|
726
|
+
manifest.peerDependencies,
|
|
727
|
+
]) {
|
|
728
|
+
if (!section || typeof section !== 'object')
|
|
729
|
+
continue;
|
|
730
|
+
for (const name of Object.keys(section))
|
|
731
|
+
names.add(name);
|
|
732
|
+
}
|
|
733
|
+
const development = manifest.devDependencies;
|
|
734
|
+
if (development && typeof development === 'object') {
|
|
735
|
+
for (const [name, specifier] of Object.entries(development)) {
|
|
736
|
+
if (isLocallyLinkedSpecifier(specifier))
|
|
737
|
+
names.add(name);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return [...names].sort(compareBytes);
|
|
741
|
+
}
|
|
742
|
+
function resolveDependencyPackageRoot(fromRoot, packageName, pathGuards, limits) {
|
|
743
|
+
let cursor = fromRoot;
|
|
744
|
+
const segments = packageName.split('/');
|
|
745
|
+
let ancestors = 0;
|
|
746
|
+
while (true) {
|
|
747
|
+
ancestors += 1;
|
|
748
|
+
if (ancestors > limits.resolutionAncestors) {
|
|
749
|
+
throw new Error(`Analyzer dependency resolution exceeded ${limits.resolutionAncestors} ancestors: ${packageName}`);
|
|
750
|
+
}
|
|
751
|
+
const nodeModulesRoot = node_path_1.default.join(cursor, 'node_modules');
|
|
752
|
+
recordDependencyPathGuard(pathGuards, nodeModulesRoot);
|
|
753
|
+
let candidateParent = nodeModulesRoot;
|
|
754
|
+
for (const segment of segments) {
|
|
755
|
+
candidateParent = node_path_1.default.join(candidateParent, segment);
|
|
756
|
+
// Guard every lexical hop, not only the final manifest. Package
|
|
757
|
+
// managers commonly expose packages through symlinks; a retarget can
|
|
758
|
+
// otherwise preserve a hard-linked manifest's stat identity while
|
|
759
|
+
// changing the runtime payload tree selected by Node.
|
|
760
|
+
recordDependencyPathGuard(pathGuards, candidateParent);
|
|
761
|
+
}
|
|
762
|
+
const manifestPath = node_path_1.default.join(candidateParent, 'package.json');
|
|
763
|
+
recordDependencyPathGuard(pathGuards, manifestPath);
|
|
764
|
+
if (isFile(manifestPath))
|
|
765
|
+
return resolveExistingPath(node_path_1.default.dirname(manifestPath));
|
|
766
|
+
const parent = node_path_1.default.dirname(cursor);
|
|
767
|
+
if (parent === cursor)
|
|
768
|
+
return null;
|
|
769
|
+
cursor = parent;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
/**
|
|
773
|
+
* Dev dependencies that are locally linked by INSTALLED LOCATION rather than by
|
|
774
|
+
* declared specifier — the `npm link <pkg>` shape, where the manifest still
|
|
775
|
+
* carries a registry range while `node_modules/<pkg>` is a symlink into a
|
|
776
|
+
* working checkout. {@link isLocallyLinkedSpecifier} is blind to those, yet the
|
|
777
|
+
* linked code is exactly as load-bearing for analyzer semantics as a declared
|
|
778
|
+
* `file:` sibling, so a semantic-only edit there would move neither digest.
|
|
779
|
+
*
|
|
780
|
+
* The resolver already knows: {@link resolveDependencyPackageRoot} returns a
|
|
781
|
+
* realpath, so a linked package reports a root outside every `node_modules`
|
|
782
|
+
* tree while an ordinary installed package cannot.
|
|
783
|
+
*
|
|
784
|
+
* Two properties are load-bearing and must not be relaxed:
|
|
785
|
+
*
|
|
786
|
+
* 1. ROOT ONLY. {@link dependencyNames} runs for every package in the BFS, and
|
|
787
|
+
* published tarballs keep their `devDependencies`, so probing dev-only names
|
|
788
|
+
* everywhere costs 1998 resolutions rather than the ~13 this manifest
|
|
789
|
+
* declares — measured on this install, with 0 true positives. Persisted
|
|
790
|
+
* `dependencyPathGuards` grow 2220 → 11049, and every guard is re-probed on
|
|
791
|
+
* each warm validation, so the cost is recurring and on the `status` path.
|
|
792
|
+
* Root-only costs 13 resolutions and ~29 guards.
|
|
793
|
+
* 2. An unresolvable name is NEVER admitted. `null` here means "uninstalled
|
|
794
|
+
* registry dev tool" far more often than "broken link", and admitting it
|
|
795
|
+
* would emit a `<missing>` edge for every dev tool absent from a published
|
|
796
|
+
* install. Declared links keep that edge through {@link dependencyNames};
|
|
797
|
+
* undeclared ones have no declaration to honour.
|
|
798
|
+
*
|
|
799
|
+
* The admission count is bounded by {@link MAX_UNDECLARED_LOCAL_DEV_DEPENDENCIES}.
|
|
800
|
+
*/
|
|
801
|
+
function undeclaredLocalDevDependencyNames(rootPackage, pathGuards, limits) {
|
|
802
|
+
const development = rootPackage.manifest.devDependencies;
|
|
803
|
+
if (!development || typeof development !== 'object')
|
|
804
|
+
return [];
|
|
805
|
+
const admitted = [];
|
|
806
|
+
for (const [name, specifier] of Object.entries(development)) {
|
|
807
|
+
// Already carried by the declared half; resolving again would only add
|
|
808
|
+
// guards. Its `<missing>` edge is that half's responsibility.
|
|
809
|
+
if (isLocallyLinkedSpecifier(specifier))
|
|
810
|
+
continue;
|
|
811
|
+
const resolved = resolveDependencyPackageRoot(rootPackage.root, name, pathGuards, limits);
|
|
812
|
+
if (resolved !== null && !hasNodeModulesSegment(resolved))
|
|
813
|
+
admitted.push(name);
|
|
814
|
+
}
|
|
815
|
+
return admitted.length <= MAX_UNDECLARED_LOCAL_DEV_DEPENDENCIES ? admitted : [];
|
|
816
|
+
}
|
|
817
|
+
function collectRuntimePackages(packageRoot, directoryGuards, pathGuards, options, budget, limits) {
|
|
818
|
+
const rootManifestPath = node_path_1.default.join(packageRoot, 'package.json');
|
|
819
|
+
recordDirectoryGuard(directoryGuards, packageRoot);
|
|
820
|
+
const rootRead = readManifest(rootManifestPath, options, budget, limits);
|
|
821
|
+
const rootPackage = {
|
|
822
|
+
root: packageRoot,
|
|
823
|
+
locator: runtimePackageLocator(packageRoot, packageRoot),
|
|
824
|
+
manifestPath: rootManifestPath,
|
|
825
|
+
manifestBytes: rootRead.bytes,
|
|
826
|
+
manifestState: rootRead.state,
|
|
827
|
+
manifest: rootRead.manifest,
|
|
828
|
+
label: manifestLabel(rootRead.manifest),
|
|
829
|
+
};
|
|
830
|
+
const queue = [rootPackage];
|
|
831
|
+
const packages = new Map([[packageRoot, rootPackage]]);
|
|
832
|
+
const edges = [];
|
|
833
|
+
budget.packages = 1;
|
|
834
|
+
for (let index = 0; index < queue.length; index += 1) {
|
|
835
|
+
const parent = queue[index];
|
|
836
|
+
// The declared half is enumerated for every package; the resolved-location
|
|
837
|
+
// half is scoped to the root package, where the 1998-resolution /
|
|
838
|
+
// 8829-extra-guard blow-up documented on
|
|
839
|
+
// `undeclaredLocalDevDependencyNames` cannot occur. Dropping this scope is
|
|
840
|
+
// the expensive regression, so it is pinned by a guard-count test.
|
|
841
|
+
const dependencies = parent.root === packageRoot
|
|
842
|
+
? [
|
|
843
|
+
...new Set([
|
|
844
|
+
...dependencyNames(parent.manifest),
|
|
845
|
+
...undeclaredLocalDevDependencyNames(parent, pathGuards, limits),
|
|
846
|
+
]),
|
|
847
|
+
].sort(compareBytes)
|
|
848
|
+
: dependencyNames(parent.manifest);
|
|
849
|
+
for (const dependencyName of dependencies) {
|
|
850
|
+
budget.edges += 1;
|
|
851
|
+
if (budget.edges > limits.runtimeEdges) {
|
|
852
|
+
throw new Error(`Analyzer dependency graph exceeded ${limits.runtimeEdges} edges: ${packageRoot}`);
|
|
853
|
+
}
|
|
854
|
+
const childRoot = resolveDependencyPackageRoot(parent.root, dependencyName, pathGuards, limits);
|
|
855
|
+
if (!childRoot) {
|
|
856
|
+
edges.push({
|
|
857
|
+
parentLocator: parent.locator,
|
|
858
|
+
parentLabel: parent.label,
|
|
859
|
+
dependencyName,
|
|
860
|
+
childLocator: '<missing>',
|
|
861
|
+
childLabel: '<missing>',
|
|
862
|
+
});
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
let child = packages.get(childRoot);
|
|
866
|
+
if (!child) {
|
|
867
|
+
budget.packages += 1;
|
|
868
|
+
if (budget.packages > limits.runtimePackages) {
|
|
869
|
+
throw new Error(`Analyzer dependency graph exceeded ${limits.runtimePackages} packages: ${packageRoot}`);
|
|
870
|
+
}
|
|
871
|
+
const manifestPath = node_path_1.default.join(childRoot, 'package.json');
|
|
872
|
+
recordDirectoryGuard(directoryGuards, childRoot);
|
|
873
|
+
const read = readManifest(manifestPath, options, budget, limits);
|
|
874
|
+
child = {
|
|
875
|
+
root: childRoot,
|
|
876
|
+
locator: runtimePackageLocator(packageRoot, childRoot),
|
|
877
|
+
manifestPath,
|
|
878
|
+
manifestBytes: read.bytes,
|
|
879
|
+
manifestState: read.state,
|
|
880
|
+
manifest: read.manifest,
|
|
881
|
+
label: manifestLabel(read.manifest),
|
|
882
|
+
};
|
|
883
|
+
packages.set(childRoot, child);
|
|
884
|
+
queue.push(child);
|
|
885
|
+
}
|
|
886
|
+
edges.push({
|
|
887
|
+
parentLocator: parent.locator,
|
|
888
|
+
parentLabel: parent.label,
|
|
889
|
+
dependencyName,
|
|
890
|
+
childLocator: child.locator,
|
|
891
|
+
childLabel: child.label,
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
return { packages: [...packages.values()], edges };
|
|
896
|
+
}
|
|
897
|
+
const PRUNED_RUNTIME_DIRECTORIES = new Set(['node_modules', '.git', '.hg', '.svn']);
|
|
898
|
+
function shouldHashRuntimePayload(relativePath) {
|
|
899
|
+
const lower = relativePath.toLowerCase();
|
|
900
|
+
// Each package manifest is already hashed as a separately framed dependency
|
|
901
|
+
// input. Avoid counting/reading it twice while still hashing every other
|
|
902
|
+
// package payload: JavaScript modules, JSON data, native/Wasm binaries,
|
|
903
|
+
// extensionless exports, and files loaded explicitly through fs APIs. File
|
|
904
|
+
// and directory names are not authoritative platform-selection metadata:
|
|
905
|
+
// loaders may select or read a payload whose name mentions another target.
|
|
906
|
+
return lower !== 'package.json';
|
|
907
|
+
}
|
|
908
|
+
function collectArtifacts(root, canonicalPrefix, directoryGuards, options, budget, limits) {
|
|
909
|
+
const artifacts = [];
|
|
910
|
+
const pending = [{ absoluteDir: root, depth: 0 }];
|
|
911
|
+
while (pending.length > 0) {
|
|
912
|
+
const next = pending.pop();
|
|
913
|
+
if (!next)
|
|
914
|
+
break;
|
|
915
|
+
const { absoluteDir, depth } = next;
|
|
916
|
+
if (!recordDirectoryGuard(directoryGuards, absoluteDir)) {
|
|
917
|
+
throw new Error(`Analyzer runtime payload directory is unavailable: ${absoluteDir}`);
|
|
918
|
+
}
|
|
919
|
+
const entries = readDirectory(absoluteDir, options);
|
|
920
|
+
budget.entries += entries.length;
|
|
921
|
+
if (budget.entries > limits.runtimeEntries) {
|
|
922
|
+
throw new Error(`Analyzer runtime payload scan exceeded ${limits.runtimeEntries} entries: ${root}`);
|
|
923
|
+
}
|
|
924
|
+
for (const entry of entries) {
|
|
925
|
+
const absolutePath = node_path_1.default.join(absoluteDir, entry.name);
|
|
926
|
+
const relativePath = node_path_1.default.relative(root, absolutePath).split(node_path_1.default.sep).join('/');
|
|
927
|
+
const stat = (0, node_fs_1.lstatSync)(absolutePath);
|
|
928
|
+
// Nested dependencies are collected from their manifests as separate
|
|
929
|
+
// packages. Only prune those separately traversed trees and VCS
|
|
930
|
+
// metadata; generic cache/model directories can contain loadable code,
|
|
931
|
+
// native addons, Wasm modules, or data consumed by the runtime.
|
|
932
|
+
//
|
|
933
|
+
// Pruning is decided by NAME alone. These four names never carry analyzer
|
|
934
|
+
// payload in any form: `node_modules` is traversed separately through
|
|
935
|
+
// `resolveDependencyPackageRoot` (which follows links and guards each
|
|
936
|
+
// hop), and a `.git`/`.hg`/`.svn` entry is VCS metadata whether it is a
|
|
937
|
+
// directory, a symbolic link into a shared store, or — inside a submodule
|
|
938
|
+
// or linked worktree checkout — a regular file holding a gitdir pointer.
|
|
939
|
+
// Hashing that pointer would make analyzer identity depend on where the
|
|
940
|
+
// checkout happens to live, which is a false-stale source, not a
|
|
941
|
+
// semantic input.
|
|
942
|
+
if (PRUNED_RUNTIME_DIRECTORIES.has(entry.name))
|
|
943
|
+
continue;
|
|
944
|
+
if (stat.isDirectory()) {
|
|
945
|
+
if (depth >= limits.runtimeDepth) {
|
|
946
|
+
throw new Error(`Analyzer runtime payload scan exceeded depth ${limits.runtimeDepth}: ${absolutePath}`);
|
|
947
|
+
}
|
|
948
|
+
pending.push({ absoluteDir: absolutePath, depth: depth + 1 });
|
|
949
|
+
}
|
|
950
|
+
else if (stat.isSymbolicLink() && !isFile(absolutePath)) {
|
|
951
|
+
// A symbolic link that does not resolve to a regular file must never
|
|
952
|
+
// reach the payload branch below: `snapshotReadableFile` stats the
|
|
953
|
+
// target, and a directory (or a dangling link) makes it throw, aborting
|
|
954
|
+
// the entire analyze. Workspace-linked checkouts made this reachable
|
|
955
|
+
// for every name, not just the pruned four — `dist -> build`, a
|
|
956
|
+
// vendored-grammar link, anything a sibling checkout ships.
|
|
957
|
+
//
|
|
958
|
+
// Such links are RECORDED by their link text rather than followed.
|
|
959
|
+
// Following them would (a) recurse without cycle protection — this
|
|
960
|
+
// traversal has none, so `self -> .` would ride the depth limit, which
|
|
961
|
+
// THROWS, trading one hard abort for another; (b) re-scan trees already
|
|
962
|
+
// reached by their real path, inflating the entry/byte budgets that
|
|
963
|
+
// also throw; and (c) need a whole containment/TOCTOU trust boundary
|
|
964
|
+
// for targets outside the package. Recording the text is cycle-free,
|
|
965
|
+
// costs one `readlink`, and still moves the receipt when the link is
|
|
966
|
+
// retargeted. The trade-off is that a link's target contributes no
|
|
967
|
+
// content of its own: when it points outside the package, only the
|
|
968
|
+
// link text is covered. Links that DO resolve to a regular file keep
|
|
969
|
+
// their content digest below, unchanged.
|
|
970
|
+
if (shouldHashRuntimePayload(relativePath)) {
|
|
971
|
+
budget.artifacts += 1;
|
|
972
|
+
if (budget.artifacts > limits.runtimePayloads) {
|
|
973
|
+
throw new Error(`Analyzer runtime payload scan exceeded ${limits.runtimePayloads} payloads: ${root}`);
|
|
974
|
+
}
|
|
975
|
+
artifacts.push({
|
|
976
|
+
absolutePath,
|
|
977
|
+
canonicalPath: `${canonicalPrefix}/${relativePath}`,
|
|
978
|
+
kind: 'unfollowed-symlink',
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
else if ((stat.isFile() || stat.isSymbolicLink()) &&
|
|
983
|
+
shouldHashRuntimePayload(relativePath)) {
|
|
984
|
+
const readableState = snapshotReadableFile(absolutePath);
|
|
985
|
+
const payloadBytes = stateSize(readableState.target, absolutePath);
|
|
986
|
+
budget.artifacts += 1;
|
|
987
|
+
if (budget.artifacts > limits.runtimePayloads) {
|
|
988
|
+
throw new Error(`Analyzer runtime payload scan exceeded ${limits.runtimePayloads} payloads: ${root}`);
|
|
989
|
+
}
|
|
990
|
+
if (budget.bytes + payloadBytes > limits.runtimeBytes) {
|
|
991
|
+
throw new Error(`Analyzer runtime scan exceeded ${limits.runtimeBytes} bytes: ${absolutePath}`);
|
|
992
|
+
}
|
|
993
|
+
budget.bytes += payloadBytes;
|
|
994
|
+
artifacts.push({
|
|
995
|
+
absolutePath,
|
|
996
|
+
canonicalPath: `${canonicalPrefix}/${relativePath}`,
|
|
997
|
+
kind: stat.isSymbolicLink() ? 'symlink' : 'file',
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
else if (!stat.isFile() && !stat.isSymbolicLink()) {
|
|
1001
|
+
throw new Error(`Unsupported analyzer runtime payload entry: ${absolutePath}`);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return artifacts;
|
|
1006
|
+
}
|
|
1007
|
+
function collectVendoredGrammarInputs(packageRoot, directoryGuards, options, budget, limits) {
|
|
1008
|
+
const vendorRoot = node_path_1.default.join(packageRoot, 'vendor');
|
|
1009
|
+
if (!(0, node_fs_1.existsSync)(vendorRoot) || !(0, node_fs_1.lstatSync)(vendorRoot).isDirectory()) {
|
|
1010
|
+
recordDirectoryGuard(directoryGuards, packageRoot);
|
|
1011
|
+
return { manifests: [], artifacts: [] };
|
|
1012
|
+
}
|
|
1013
|
+
const manifests = [];
|
|
1014
|
+
const artifacts = [];
|
|
1015
|
+
if (!recordDirectoryGuard(directoryGuards, vendorRoot)) {
|
|
1016
|
+
throw new Error(`Analyzer vendored runtime directory is unavailable: ${vendorRoot}`);
|
|
1017
|
+
}
|
|
1018
|
+
const vendorEntries = readDirectory(vendorRoot, options);
|
|
1019
|
+
budget.entries += vendorEntries.length;
|
|
1020
|
+
if (budget.entries > limits.runtimeEntries) {
|
|
1021
|
+
throw new Error(`Analyzer runtime payload scan exceeded ${limits.runtimeEntries} entries: ${vendorRoot}`);
|
|
1022
|
+
}
|
|
1023
|
+
for (const entry of vendorEntries) {
|
|
1024
|
+
if (!entry.isDirectory() || !entry.name.startsWith('tree-sitter-'))
|
|
1025
|
+
continue;
|
|
1026
|
+
const grammarRoot = node_path_1.default.join(vendorRoot, entry.name);
|
|
1027
|
+
const manifestPath = node_path_1.default.join(grammarRoot, 'package.json');
|
|
1028
|
+
if (isFile(manifestPath)) {
|
|
1029
|
+
options.onCacheMissWork?.({ kind: 'manifest-read', path: manifestPath });
|
|
1030
|
+
const read = readStableFileWithinBudget(manifestPath, budget, limits.runtimeBytes);
|
|
1031
|
+
manifests.push({
|
|
1032
|
+
canonicalPath: `vendor:${entry.name}/package.json`,
|
|
1033
|
+
absolutePath: manifestPath,
|
|
1034
|
+
bytes: read.bytes,
|
|
1035
|
+
state: read.state,
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
artifacts.push(...collectArtifacts(grammarRoot, `vendor:${entry.name}`, directoryGuards, options, budget, limits));
|
|
1039
|
+
}
|
|
1040
|
+
return { manifests, artifacts };
|
|
1041
|
+
}
|
|
1042
|
+
function collectDependencyInputs(packageRoot, options, limits) {
|
|
1043
|
+
const directoryGuards = new Map();
|
|
1044
|
+
const pathGuards = new Map();
|
|
1045
|
+
const artifactScanBudget = {
|
|
1046
|
+
entries: 0,
|
|
1047
|
+
artifacts: 0,
|
|
1048
|
+
bytes: 0,
|
|
1049
|
+
packages: 0,
|
|
1050
|
+
edges: 0,
|
|
1051
|
+
};
|
|
1052
|
+
const manifestPath = resolveExistingPath(node_path_1.default.join(packageRoot, 'package.json'));
|
|
1053
|
+
const lockfilePath = findNearestPackageLock(packageRoot, pathGuards, limits);
|
|
1054
|
+
if (lockfilePath)
|
|
1055
|
+
options.onCacheMissWork?.({ kind: 'manifest-read', path: lockfilePath });
|
|
1056
|
+
const lockfile = lockfilePath
|
|
1057
|
+
? readStableFileWithinBudget(lockfilePath, artifactScanBudget, limits.runtimeBytes)
|
|
1058
|
+
: null;
|
|
1059
|
+
const { packages, edges } = collectRuntimePackages(packageRoot, directoryGuards, pathGuards, options, artifactScanBudget, limits);
|
|
1060
|
+
const vendored = collectVendoredGrammarInputs(packageRoot, directoryGuards, options, artifactScanBudget, limits);
|
|
1061
|
+
// The root build and vendored grammars are covered separately. Every
|
|
1062
|
+
// resolved external runtime package is scanned, regardless of package name:
|
|
1063
|
+
// native loaders are not constrained to a permanent allowlist (for example,
|
|
1064
|
+
// Transformers resolves Sharp's @img platform packages).
|
|
1065
|
+
const artifacts = packages
|
|
1066
|
+
.slice(1)
|
|
1067
|
+
.flatMap((runtimePackage) => collectArtifacts(runtimePackage.root, `package:${runtimePackage.locator}`, directoryGuards, options, artifactScanBudget, limits));
|
|
1068
|
+
artifacts.push(...vendored.artifacts);
|
|
1069
|
+
artifacts.sort((a, b) => compareBytes(`${a.canonicalPath}\u0000${a.absolutePath}`, `${b.canonicalPath}\u0000${b.absolutePath}`));
|
|
1070
|
+
return {
|
|
1071
|
+
manifestPath,
|
|
1072
|
+
lockfilePath,
|
|
1073
|
+
lockfileBytes: lockfile?.bytes ?? null,
|
|
1074
|
+
lockfileState: lockfile?.state ?? null,
|
|
1075
|
+
packages,
|
|
1076
|
+
edges,
|
|
1077
|
+
vendoredManifests: vendored.manifests,
|
|
1078
|
+
artifacts,
|
|
1079
|
+
directoryGuards,
|
|
1080
|
+
pathGuards,
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
function dependencySnapshot(inputs) {
|
|
1084
|
+
const edgeKey = (edge) => JSON.stringify([
|
|
1085
|
+
edge.parentLocator,
|
|
1086
|
+
edge.parentLabel,
|
|
1087
|
+
edge.dependencyName,
|
|
1088
|
+
edge.childLocator,
|
|
1089
|
+
edge.childLabel,
|
|
1090
|
+
]);
|
|
1091
|
+
return {
|
|
1092
|
+
manifestPath: inputs.manifestPath,
|
|
1093
|
+
lockfilePath: inputs.lockfilePath,
|
|
1094
|
+
lockfileDigest: inputs.lockfileBytes ? sha256(inputs.lockfileBytes) : null,
|
|
1095
|
+
lockfileState: inputs.lockfileState,
|
|
1096
|
+
packages: inputs.packages
|
|
1097
|
+
.map((runtimePackage) => ({
|
|
1098
|
+
root: runtimePackage.root,
|
|
1099
|
+
locator: runtimePackage.locator,
|
|
1100
|
+
manifestPath: runtimePackage.manifestPath,
|
|
1101
|
+
manifestDigest: sha256(runtimePackage.manifestBytes),
|
|
1102
|
+
manifestState: runtimePackage.manifestState,
|
|
1103
|
+
label: runtimePackage.label,
|
|
1104
|
+
}))
|
|
1105
|
+
.sort((a, b) => compareBytes(a.locator, b.locator)),
|
|
1106
|
+
edges: inputs.edges.map(edgeKey).sort(compareBytes),
|
|
1107
|
+
vendoredManifests: inputs.vendoredManifests
|
|
1108
|
+
.map((entry) => ({
|
|
1109
|
+
canonicalPath: entry.canonicalPath,
|
|
1110
|
+
absolutePath: entry.absolutePath,
|
|
1111
|
+
digest: sha256(entry.bytes),
|
|
1112
|
+
state: entry.state,
|
|
1113
|
+
}))
|
|
1114
|
+
.sort((a, b) => compareBytes(a.canonicalPath, b.canonicalPath)),
|
|
1115
|
+
artifacts: inputs.artifacts.map((artifact) => ({
|
|
1116
|
+
absolutePath: artifact.absolutePath,
|
|
1117
|
+
canonicalPath: artifact.canonicalPath,
|
|
1118
|
+
kind: artifact.kind,
|
|
1119
|
+
state: snapshotRuntimeArtifact(artifact),
|
|
1120
|
+
})),
|
|
1121
|
+
directories: [...inputs.directoryGuards.entries()]
|
|
1122
|
+
.map(([absolutePath, guard]) => ({ absolutePath, ...guard }))
|
|
1123
|
+
.sort((a, b) => compareBytes(a.absolutePath, b.absolutePath)),
|
|
1124
|
+
paths: [...inputs.pathGuards.entries()]
|
|
1125
|
+
.map(([absolutePath, result]) => ({ absolutePath, result }))
|
|
1126
|
+
.sort((a, b) => compareBytes(a.absolutePath, b.absolutePath)),
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
function artifactCacheKey(artifact) {
|
|
1130
|
+
return JSON.stringify([artifact.kind, artifact.canonicalPath, artifact.absolutePath]);
|
|
1131
|
+
}
|
|
1132
|
+
function hashRuntimeArtifact(artifact, cache, options) {
|
|
1133
|
+
if (artifact.kind === 'unfollowed-symlink') {
|
|
1134
|
+
// The link text is the entire payload, so there is no file read for a
|
|
1135
|
+
// cached digest to amortize: recompute it and stay independent of the
|
|
1136
|
+
// cache's freshness. The distinct frame label keeps a link recording from
|
|
1137
|
+
// ever colliding with a content digest.
|
|
1138
|
+
const state = snapshotSymlinkArtifact(artifact.absolutePath);
|
|
1139
|
+
return {
|
|
1140
|
+
...artifact,
|
|
1141
|
+
state,
|
|
1142
|
+
digest: hashCanonicalFrames([
|
|
1143
|
+
['runtime-payload-link-v1', artifact.kind, state.symlinkTarget],
|
|
1144
|
+
]),
|
|
1145
|
+
};
|
|
1146
|
+
}
|
|
1147
|
+
const before = snapshotReadableFile(artifact.absolutePath);
|
|
1148
|
+
if (cache &&
|
|
1149
|
+
cache.kind !== 'unfollowed-symlink' &&
|
|
1150
|
+
SHA256_PATTERN.test(cache.digest) &&
|
|
1151
|
+
(0, node_util_1.isDeepStrictEqual)(cache.state, before)) {
|
|
1152
|
+
return { ...artifact, state: before, digest: cache.digest };
|
|
1153
|
+
}
|
|
1154
|
+
const stable = hashStableFile(artifact.absolutePath);
|
|
1155
|
+
const digest = hashCanonicalFrames([
|
|
1156
|
+
[
|
|
1157
|
+
'runtime-payload-content-v1',
|
|
1158
|
+
artifact.kind,
|
|
1159
|
+
stable.state.symlinkTarget ?? '',
|
|
1160
|
+
digestBytes(stable.digest),
|
|
1161
|
+
],
|
|
1162
|
+
]);
|
|
1163
|
+
options.onHashedInput?.({
|
|
1164
|
+
kind: 'runtime-artifact',
|
|
1165
|
+
path: artifact.absolutePath,
|
|
1166
|
+
bytes: stable.bytes,
|
|
1167
|
+
});
|
|
1168
|
+
return { ...artifact, state: stable.state, digest };
|
|
1169
|
+
}
|
|
1170
|
+
function compareEdges(a, b) {
|
|
1171
|
+
return compareBytes(JSON.stringify([
|
|
1172
|
+
a.parentLocator,
|
|
1173
|
+
a.parentLabel,
|
|
1174
|
+
a.dependencyName,
|
|
1175
|
+
a.childLocator,
|
|
1176
|
+
a.childLabel,
|
|
1177
|
+
]), JSON.stringify([
|
|
1178
|
+
b.parentLocator,
|
|
1179
|
+
b.parentLabel,
|
|
1180
|
+
b.dependencyName,
|
|
1181
|
+
b.childLocator,
|
|
1182
|
+
b.childLabel,
|
|
1183
|
+
]));
|
|
1184
|
+
}
|
|
1185
|
+
function hashDependencyRuntime(inputs, cache, options, runtimeVariant) {
|
|
1186
|
+
const cachedArtifacts = new Map((cache?.artifactEntries ?? []).map((entry) => [artifactCacheKey(entry), entry]));
|
|
1187
|
+
const nextArtifacts = [];
|
|
1188
|
+
const hash = (0, node_crypto_1.createHash)('sha256');
|
|
1189
|
+
updateCanonicalFrame(hash, ['domain', DEPENDENCY_RUNTIME_CANONICALIZATION]);
|
|
1190
|
+
updateCanonicalFrame(hash, [
|
|
1191
|
+
'runtime-variant',
|
|
1192
|
+
runtimeVariant.nodeVersion,
|
|
1193
|
+
runtimeVariant.platform,
|
|
1194
|
+
runtimeVariant.architecture,
|
|
1195
|
+
runtimeVariant.endianness,
|
|
1196
|
+
runtimeVariant.modulesAbi,
|
|
1197
|
+
runtimeVariant.napiAbi,
|
|
1198
|
+
runtimeVariant.libc,
|
|
1199
|
+
]);
|
|
1200
|
+
updateCanonicalFrame(hash, [
|
|
1201
|
+
'lockfile',
|
|
1202
|
+
inputs.lockfilePath ? 'present' : 'absent',
|
|
1203
|
+
inputs.lockfileBytes ?? Buffer.alloc(0),
|
|
1204
|
+
]);
|
|
1205
|
+
const packageEntries = inputs.packages
|
|
1206
|
+
.map((runtimePackage) => ({
|
|
1207
|
+
canonicalPath: `package:${runtimePackage.locator}/package.json`,
|
|
1208
|
+
bytes: runtimePackage.manifestBytes,
|
|
1209
|
+
}))
|
|
1210
|
+
.concat(inputs.vendoredManifests.map(({ canonicalPath, bytes }) => ({ canonicalPath, bytes })))
|
|
1211
|
+
.sort((a, b) => compareBytes(a.canonicalPath, b.canonicalPath));
|
|
1212
|
+
for (const entry of packageEntries) {
|
|
1213
|
+
updateCanonicalFrame(hash, ['package-manifest', entry.canonicalPath, entry.bytes]);
|
|
1214
|
+
}
|
|
1215
|
+
for (const edge of [...inputs.edges].sort(compareEdges)) {
|
|
1216
|
+
updateCanonicalFrame(hash, [
|
|
1217
|
+
'dependency-edge',
|
|
1218
|
+
edge.parentLocator,
|
|
1219
|
+
edge.parentLabel,
|
|
1220
|
+
edge.dependencyName,
|
|
1221
|
+
edge.childLocator,
|
|
1222
|
+
edge.childLabel,
|
|
1223
|
+
]);
|
|
1224
|
+
}
|
|
1225
|
+
for (const artifact of inputs.artifacts) {
|
|
1226
|
+
const hashed = hashRuntimeArtifact(artifact, cachedArtifacts.get(artifactCacheKey(artifact)), options);
|
|
1227
|
+
updateCanonicalFrame(hash, [
|
|
1228
|
+
'runtime-artifact',
|
|
1229
|
+
artifact.canonicalPath,
|
|
1230
|
+
artifact.kind,
|
|
1231
|
+
digestBytes(hashed.digest),
|
|
1232
|
+
]);
|
|
1233
|
+
nextArtifacts.push(hashed);
|
|
1234
|
+
}
|
|
1235
|
+
return {
|
|
1236
|
+
identity: {
|
|
1237
|
+
manifestPath: inputs.manifestPath,
|
|
1238
|
+
lockfilePath: inputs.lockfilePath,
|
|
1239
|
+
canonicalization: DEPENDENCY_RUNTIME_CANONICALIZATION,
|
|
1240
|
+
packageCount: inputs.packages.length,
|
|
1241
|
+
artifactCount: inputs.artifacts.length,
|
|
1242
|
+
digest: `sha256:${hash.digest('hex')}`,
|
|
1243
|
+
},
|
|
1244
|
+
entries: nextArtifacts,
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1247
|
+
function isStatState(value) {
|
|
1248
|
+
if (typeof value !== 'object' || value === null)
|
|
1249
|
+
return false;
|
|
1250
|
+
const record = value;
|
|
1251
|
+
return ['dev', 'ino', 'mode', 'nlink', 'size', 'mtimeNs', 'ctimeNs'].every((key) => typeof record[key] === 'string');
|
|
1252
|
+
}
|
|
1253
|
+
function isReadableFileState(value) {
|
|
1254
|
+
if (typeof value !== 'object' || value === null)
|
|
1255
|
+
return false;
|
|
1256
|
+
const record = value;
|
|
1257
|
+
return (isStatState(record.link) &&
|
|
1258
|
+
isStatState(record.target) &&
|
|
1259
|
+
(record.symlinkTarget === undefined || typeof record.symlinkTarget === 'string'));
|
|
1260
|
+
}
|
|
1261
|
+
function isSymlinkArtifactState(value) {
|
|
1262
|
+
if (typeof value !== 'object' || value === null)
|
|
1263
|
+
return false;
|
|
1264
|
+
const record = value;
|
|
1265
|
+
// `target === undefined` keeps a readable-file state from masquerading as an
|
|
1266
|
+
// unresolved link recording, which would otherwise be validated against the
|
|
1267
|
+
// wrong guard mode on the warm path.
|
|
1268
|
+
return (isStatState(record.link) &&
|
|
1269
|
+
typeof record.symlinkTarget === 'string' &&
|
|
1270
|
+
record.target === undefined);
|
|
1271
|
+
}
|
|
1272
|
+
function isDependencyPathGuardResult(value) {
|
|
1273
|
+
if (value === null)
|
|
1274
|
+
return true;
|
|
1275
|
+
if (typeof value !== 'object')
|
|
1276
|
+
return false;
|
|
1277
|
+
const record = value;
|
|
1278
|
+
return (['directory', 'file', 'symlink', 'other'].includes(String(record.type)) &&
|
|
1279
|
+
isStatState(record.state) &&
|
|
1280
|
+
(record.type === 'symlink'
|
|
1281
|
+
? typeof record.symlinkTarget === 'string'
|
|
1282
|
+
: record.symlinkTarget === undefined));
|
|
1283
|
+
}
|
|
1284
|
+
function isRuntimeVariant(value) {
|
|
1285
|
+
if (typeof value !== 'object' || value === null)
|
|
1286
|
+
return false;
|
|
1287
|
+
const record = value;
|
|
1288
|
+
return [
|
|
1289
|
+
'executablePath',
|
|
1290
|
+
'nodeVersion',
|
|
1291
|
+
'platform',
|
|
1292
|
+
'architecture',
|
|
1293
|
+
'endianness',
|
|
1294
|
+
'modulesAbi',
|
|
1295
|
+
'napiAbi',
|
|
1296
|
+
'libc',
|
|
1297
|
+
].every((key) => typeof record[key] === 'string' && record[key].length > 0);
|
|
1298
|
+
}
|
|
1299
|
+
function isTraversalLimits(value) {
|
|
1300
|
+
if (typeof value !== 'object' || value === null)
|
|
1301
|
+
return false;
|
|
1302
|
+
const record = value;
|
|
1303
|
+
return Object.keys(DEFAULT_TRAVERSAL_LIMITS).every((key) => Number.isSafeInteger(record[key]) &&
|
|
1304
|
+
Number(record[key]) >= 1 &&
|
|
1305
|
+
Number(record[key]) <= DEFAULT_TRAVERSAL_LIMITS[key]);
|
|
1306
|
+
}
|
|
1307
|
+
function isDependencyRuntimeIdentity(value) {
|
|
1308
|
+
if (typeof value !== 'object' || value === null)
|
|
1309
|
+
return false;
|
|
1310
|
+
const dependency = value;
|
|
1311
|
+
return (typeof dependency.manifestPath === 'string' &&
|
|
1312
|
+
dependency.manifestPath.length > 0 &&
|
|
1313
|
+
(dependency.lockfilePath === null ||
|
|
1314
|
+
(typeof dependency.lockfilePath === 'string' && dependency.lockfilePath.length > 0)) &&
|
|
1315
|
+
dependency.canonicalization === DEPENDENCY_RUNTIME_CANONICALIZATION &&
|
|
1316
|
+
Number.isSafeInteger(dependency.packageCount) &&
|
|
1317
|
+
Number(dependency.packageCount) >= 1 &&
|
|
1318
|
+
Number.isSafeInteger(dependency.artifactCount) &&
|
|
1319
|
+
Number(dependency.artifactCount) >= 0 &&
|
|
1320
|
+
typeof dependency.digest === 'string' &&
|
|
1321
|
+
SHA256_PATTERN.test(dependency.digest));
|
|
1322
|
+
}
|
|
1323
|
+
function isSafeBuildRelativePath(value) {
|
|
1324
|
+
if (typeof value !== 'string' || value.length === 0 || node_path_1.default.posix.isAbsolute(value)) {
|
|
1325
|
+
return false;
|
|
1326
|
+
}
|
|
1327
|
+
const normalized = node_path_1.default.posix.normalize(value);
|
|
1328
|
+
return normalized === value && normalized !== '..' && !normalized.startsWith('../');
|
|
1329
|
+
}
|
|
1330
|
+
function isSafeBuildDirectoryGuardPath(value) {
|
|
1331
|
+
return value === '' || isSafeBuildRelativePath(value);
|
|
1332
|
+
}
|
|
1333
|
+
function isIdentityCachePayload(value, packageRoot, buildRoot, runtimeVariant, traversalLimits) {
|
|
1334
|
+
if (typeof value !== 'object' || value === null)
|
|
1335
|
+
return false;
|
|
1336
|
+
const record = value;
|
|
1337
|
+
if (record.schemaVersion !== IDENTITY_CACHE_SCHEMA_VERSION ||
|
|
1338
|
+
record.packageRoot !== packageRoot ||
|
|
1339
|
+
record.buildRoot !== buildRoot ||
|
|
1340
|
+
typeof record.packageVersion !== 'string' ||
|
|
1341
|
+
record.packageVersion.length === 0 ||
|
|
1342
|
+
(record.buildKind !== 'source' && record.buildKind !== 'distribution') ||
|
|
1343
|
+
record.buildCanonicalization !== BUILD_CANONICALIZATION ||
|
|
1344
|
+
record.dependencyCanonicalization !== DEPENDENCY_RUNTIME_CANONICALIZATION ||
|
|
1345
|
+
!isTraversalLimits(record.traversalLimits) ||
|
|
1346
|
+
!(0, node_util_1.isDeepStrictEqual)(record.traversalLimits, traversalLimits) ||
|
|
1347
|
+
!isRuntimeVariant(record.runtimeVariant) ||
|
|
1348
|
+
!(0, node_util_1.isDeepStrictEqual)(record.runtimeVariant, runtimeVariant) ||
|
|
1349
|
+
!isStatState(record.buildRootState) ||
|
|
1350
|
+
typeof record.buildDigest !== 'string' ||
|
|
1351
|
+
!SHA256_PATTERN.test(record.buildDigest) ||
|
|
1352
|
+
!isDependencyRuntimeIdentity(record.dependencyIdentity) ||
|
|
1353
|
+
!Array.isArray(record.buildEntries) ||
|
|
1354
|
+
!Array.isArray(record.buildDirectoryGuards) ||
|
|
1355
|
+
!Array.isArray(record.dependencyFileGuards) ||
|
|
1356
|
+
!Array.isArray(record.dependencyDirectoryGuards) ||
|
|
1357
|
+
!Array.isArray(record.dependencyPathGuards) ||
|
|
1358
|
+
!Array.isArray(record.artifactEntries) ||
|
|
1359
|
+
record.buildEntries.length > MAX_CACHE_ENTRIES ||
|
|
1360
|
+
record.buildDirectoryGuards.length > MAX_CACHE_ENTRIES ||
|
|
1361
|
+
record.dependencyFileGuards.length > MAX_CACHE_ENTRIES ||
|
|
1362
|
+
record.dependencyDirectoryGuards.length > MAX_CACHE_ENTRIES ||
|
|
1363
|
+
record.dependencyPathGuards.length > MAX_CACHE_ENTRIES ||
|
|
1364
|
+
record.artifactEntries.length > MAX_CACHE_ENTRIES) {
|
|
1365
|
+
return false;
|
|
1366
|
+
}
|
|
1367
|
+
const buildEntriesValid = record.buildEntries.every((entry) => {
|
|
1368
|
+
if (typeof entry !== 'object' || entry === null)
|
|
1369
|
+
return false;
|
|
1370
|
+
const item = entry;
|
|
1371
|
+
return (isSafeBuildRelativePath(item.relativePath) &&
|
|
1372
|
+
['directory', 'file'].includes(String(item.kind)) &&
|
|
1373
|
+
isStatState(item.state) &&
|
|
1374
|
+
(item.kind === 'directory'
|
|
1375
|
+
? item.digest === undefined
|
|
1376
|
+
: typeof item.digest === 'string' && SHA256_PATTERN.test(item.digest)));
|
|
1377
|
+
});
|
|
1378
|
+
const buildDirectoryGuardsValid = record.buildDirectoryGuards.every((entry) => {
|
|
1379
|
+
if (typeof entry !== 'object' || entry === null)
|
|
1380
|
+
return false;
|
|
1381
|
+
const item = entry;
|
|
1382
|
+
return (isSafeBuildDirectoryGuardPath(item.relativePath) &&
|
|
1383
|
+
isStatState(item.state) &&
|
|
1384
|
+
typeof item.entriesDigest === 'string' &&
|
|
1385
|
+
SHA256_PATTERN.test(item.entriesDigest));
|
|
1386
|
+
});
|
|
1387
|
+
const dependencyFileGuardsValid = record.dependencyFileGuards.every((entry) => {
|
|
1388
|
+
if (typeof entry !== 'object' || entry === null)
|
|
1389
|
+
return false;
|
|
1390
|
+
const item = entry;
|
|
1391
|
+
return (typeof item.absolutePath === 'string' &&
|
|
1392
|
+
node_path_1.default.isAbsolute(item.absolutePath) &&
|
|
1393
|
+
isReadableFileState(item.state));
|
|
1394
|
+
});
|
|
1395
|
+
const dependencyDirectoryGuardsValid = record.dependencyDirectoryGuards.every((entry) => {
|
|
1396
|
+
if (typeof entry !== 'object' || entry === null)
|
|
1397
|
+
return false;
|
|
1398
|
+
const item = entry;
|
|
1399
|
+
return (typeof item.absolutePath === 'string' &&
|
|
1400
|
+
node_path_1.default.isAbsolute(item.absolutePath) &&
|
|
1401
|
+
isStatState(item.state) &&
|
|
1402
|
+
typeof item.entriesDigest === 'string' &&
|
|
1403
|
+
SHA256_PATTERN.test(item.entriesDigest));
|
|
1404
|
+
});
|
|
1405
|
+
const dependencyPathGuardsValid = record.dependencyPathGuards.every((entry) => {
|
|
1406
|
+
if (typeof entry !== 'object' || entry === null)
|
|
1407
|
+
return false;
|
|
1408
|
+
const item = entry;
|
|
1409
|
+
return (typeof item.absolutePath === 'string' &&
|
|
1410
|
+
node_path_1.default.isAbsolute(item.absolutePath) &&
|
|
1411
|
+
isDependencyPathGuardResult(item.result));
|
|
1412
|
+
});
|
|
1413
|
+
const artifactEntriesValid = record.artifactEntries.every((entry) => {
|
|
1414
|
+
if (typeof entry !== 'object' || entry === null)
|
|
1415
|
+
return false;
|
|
1416
|
+
const item = entry;
|
|
1417
|
+
if (typeof item.absolutePath !== 'string' ||
|
|
1418
|
+
!node_path_1.default.isAbsolute(item.absolutePath) ||
|
|
1419
|
+
typeof item.canonicalPath !== 'string' ||
|
|
1420
|
+
typeof item.digest !== 'string' ||
|
|
1421
|
+
!SHA256_PATTERN.test(item.digest)) {
|
|
1422
|
+
return false;
|
|
1423
|
+
}
|
|
1424
|
+
return item.kind === 'unfollowed-symlink'
|
|
1425
|
+
? isSymlinkArtifactState(item.state)
|
|
1426
|
+
: (item.kind === 'file' || item.kind === 'symlink') && isReadableFileState(item.state);
|
|
1427
|
+
});
|
|
1428
|
+
const hasBuildRootGuard = record.buildDirectoryGuards.some((entry) => typeof entry === 'object' &&
|
|
1429
|
+
entry !== null &&
|
|
1430
|
+
entry.relativePath === '');
|
|
1431
|
+
return (buildEntriesValid &&
|
|
1432
|
+
buildDirectoryGuardsValid &&
|
|
1433
|
+
hasBuildRootGuard &&
|
|
1434
|
+
dependencyFileGuardsValid &&
|
|
1435
|
+
dependencyDirectoryGuardsValid &&
|
|
1436
|
+
dependencyPathGuardsValid &&
|
|
1437
|
+
artifactEntriesValid);
|
|
1438
|
+
}
|
|
1439
|
+
function currentUid() {
|
|
1440
|
+
return typeof process.getuid === 'function' ? process.getuid() : null;
|
|
1441
|
+
}
|
|
1442
|
+
function isOwnedPrivateDirectory(candidate) {
|
|
1443
|
+
try {
|
|
1444
|
+
const stat = (0, node_fs_1.lstatSync)(candidate);
|
|
1445
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
1446
|
+
return false;
|
|
1447
|
+
const uid = currentUid();
|
|
1448
|
+
return uid !== null && stat.uid === uid && (stat.mode & 0o077) === 0;
|
|
1449
|
+
}
|
|
1450
|
+
catch {
|
|
1451
|
+
return false;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1454
|
+
function isSafeTemporaryParent(candidate) {
|
|
1455
|
+
try {
|
|
1456
|
+
const stat = (0, node_fs_1.lstatSync)(candidate);
|
|
1457
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
1458
|
+
return false;
|
|
1459
|
+
if (currentUid() === null)
|
|
1460
|
+
return false;
|
|
1461
|
+
// A shared temp root is safe only with the sticky bit: another UID then
|
|
1462
|
+
// cannot replace the private child after our atomic mkdir + owner check.
|
|
1463
|
+
const writableByOthers = (stat.mode & 0o022) !== 0;
|
|
1464
|
+
return !writableByOthers || (stat.mode & 0o1000) !== 0;
|
|
1465
|
+
}
|
|
1466
|
+
catch {
|
|
1467
|
+
return false;
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
function ensurePrivateChild(parent, childName) {
|
|
1471
|
+
let resolvedParent;
|
|
1472
|
+
try {
|
|
1473
|
+
resolvedParent = node_fs_1.realpathSync.native(parent);
|
|
1474
|
+
}
|
|
1475
|
+
catch {
|
|
1476
|
+
return null;
|
|
1477
|
+
}
|
|
1478
|
+
if (!isSafeTemporaryParent(resolvedParent))
|
|
1479
|
+
return null;
|
|
1480
|
+
const candidate = node_path_1.default.join(resolvedParent, childName);
|
|
1481
|
+
try {
|
|
1482
|
+
// Non-recursive mkdir is intentional: it cannot follow an attacker-made
|
|
1483
|
+
// intermediate symlink. EEXIST is accepted only after the ownership/mode
|
|
1484
|
+
// validation below.
|
|
1485
|
+
(0, node_fs_1.mkdirSync)(candidate, { mode: 0o700 });
|
|
1486
|
+
}
|
|
1487
|
+
catch (error) {
|
|
1488
|
+
if (error.code !== 'EEXIST')
|
|
1489
|
+
return null;
|
|
1490
|
+
}
|
|
1491
|
+
return isOwnedPrivateDirectory(candidate) ? candidate : null;
|
|
1492
|
+
}
|
|
1493
|
+
function defaultCacheDirectory() {
|
|
1494
|
+
// On platforms without POSIX ownership APIs we cannot prove that a default
|
|
1495
|
+
// cache file is private to this process's user. Persistence is therefore
|
|
1496
|
+
// disabled unless the operator supplied an explicit trusted override.
|
|
1497
|
+
const uid = currentUid();
|
|
1498
|
+
if (uid === null)
|
|
1499
|
+
return null;
|
|
1500
|
+
// XDG_RUNTIME_DIR is already per-user and normally 0700. Use it only when
|
|
1501
|
+
// that contract is true; a spoofed/insecure value falls back to the sticky
|
|
1502
|
+
// OS temp root rather than becoming a cache-poisoning surface.
|
|
1503
|
+
const runtimeDir = process.env.XDG_RUNTIME_DIR;
|
|
1504
|
+
if (runtimeDir) {
|
|
1505
|
+
try {
|
|
1506
|
+
const resolvedRuntime = node_fs_1.realpathSync.native(runtimeDir);
|
|
1507
|
+
if (isOwnedPrivateDirectory(resolvedRuntime)) {
|
|
1508
|
+
const runtimeCache = ensurePrivateChild(resolvedRuntime, 'cgraph-analyzer-identity');
|
|
1509
|
+
if (runtimeCache)
|
|
1510
|
+
return runtimeCache;
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
catch {
|
|
1514
|
+
/* fall through to the OS temp directory */
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
let tempRoot;
|
|
1518
|
+
try {
|
|
1519
|
+
tempRoot = node_fs_1.realpathSync.native(node_os_1.default.tmpdir());
|
|
1520
|
+
}
|
|
1521
|
+
catch {
|
|
1522
|
+
return null;
|
|
1523
|
+
}
|
|
1524
|
+
return ensurePrivateChild(tempRoot, `cgraph-analyzer-identity-${uid}`);
|
|
1525
|
+
}
|
|
1526
|
+
const TRUSTED_CACHE_DIRECTORY_ENV = 'CGRAPH_ANALYZER_IDENTITY_CACHE_DIR';
|
|
1527
|
+
function pathsEqual(left, right) {
|
|
1528
|
+
return process.platform === 'win32' ? left.toLowerCase() === right.toLowerCase() : left === right;
|
|
1529
|
+
}
|
|
1530
|
+
function trustedEnvironmentCacheDirectory() {
|
|
1531
|
+
const configured = process.env[TRUSTED_CACHE_DIRECTORY_ENV];
|
|
1532
|
+
if (configured === undefined)
|
|
1533
|
+
return null;
|
|
1534
|
+
if (configured.length === 0 || configured.includes('\0') || !node_path_1.default.isAbsolute(configured)) {
|
|
1535
|
+
throw new Error(`${TRUSTED_CACHE_DIRECTORY_ENV} must name an absolute protected directory`);
|
|
1536
|
+
}
|
|
1537
|
+
const normalized = node_path_1.default.normalize(configured);
|
|
1538
|
+
let resolved;
|
|
1539
|
+
try {
|
|
1540
|
+
const link = (0, node_fs_1.lstatSync)(normalized);
|
|
1541
|
+
if (!link.isDirectory() || link.isSymbolicLink()) {
|
|
1542
|
+
throw new Error('not a real directory');
|
|
1543
|
+
}
|
|
1544
|
+
resolved = node_fs_1.realpathSync.native(normalized);
|
|
1545
|
+
}
|
|
1546
|
+
catch {
|
|
1547
|
+
throw new Error(`${TRUSTED_CACHE_DIRECTORY_ENV} must name a pre-existing protected non-symlink directory`);
|
|
1548
|
+
}
|
|
1549
|
+
// Reject junctions/symlinked ancestors as well as a symlink final component.
|
|
1550
|
+
// The environment variable is an explicit trust assertion, but its spelling
|
|
1551
|
+
// must still bind exactly to the directory the cache will use.
|
|
1552
|
+
if (!pathsEqual(node_path_1.default.resolve(normalized), resolved)) {
|
|
1553
|
+
throw new Error(`${TRUSTED_CACHE_DIRECTORY_ENV} must not traverse symbolic links or junctions`);
|
|
1554
|
+
}
|
|
1555
|
+
return resolved;
|
|
1556
|
+
}
|
|
1557
|
+
function cacheDirectory(options, packageRoot, buildRoot) {
|
|
1558
|
+
// An explicit location is a trusted operator/test override and therefore
|
|
1559
|
+
// remains authoritative, including when the secure default is unavailable.
|
|
1560
|
+
if (options.cacheDirectory) {
|
|
1561
|
+
const explicit = node_path_1.default.resolve(options.cacheDirectory);
|
|
1562
|
+
try {
|
|
1563
|
+
// Create it before any build/dependency directory guards are captured.
|
|
1564
|
+
// A cache nested immediately under a package root then changes that
|
|
1565
|
+
// parent's directory state once, not after we persist the first entry.
|
|
1566
|
+
(0, node_fs_1.mkdirSync)(explicit, { recursive: true, mode: 0o700 });
|
|
1567
|
+
}
|
|
1568
|
+
catch {
|
|
1569
|
+
/* persistence remains optional and will fail closed */
|
|
1570
|
+
}
|
|
1571
|
+
return explicit;
|
|
1572
|
+
}
|
|
1573
|
+
const configured = trustedEnvironmentCacheDirectory();
|
|
1574
|
+
if (configured) {
|
|
1575
|
+
if (isInside(packageRoot, configured) || isInside(buildRoot, configured)) {
|
|
1576
|
+
throw new Error(`${TRUSTED_CACHE_DIRECTORY_ENV} must be outside the analyzer package and build roots`);
|
|
1577
|
+
}
|
|
1578
|
+
return configured;
|
|
1579
|
+
}
|
|
1580
|
+
return defaultCacheDirectory();
|
|
1581
|
+
}
|
|
1582
|
+
function hasTrustedCacheOverride(options) {
|
|
1583
|
+
return (options.cacheDirectory !== undefined || process.env[TRUSTED_CACHE_DIRECTORY_ENV] !== undefined);
|
|
1584
|
+
}
|
|
1585
|
+
function identityCacheKey(packageRoot, buildRoot, runtimeVariant, traversalLimits) {
|
|
1586
|
+
return hashCanonicalFrames([
|
|
1587
|
+
[
|
|
1588
|
+
'analyzer-identity-cache-key-v3',
|
|
1589
|
+
String(IDENTITY_CACHE_SCHEMA_VERSION),
|
|
1590
|
+
packageRoot,
|
|
1591
|
+
buildRoot,
|
|
1592
|
+
runtimeVariant.executablePath,
|
|
1593
|
+
runtimeVariant.nodeVersion,
|
|
1594
|
+
runtimeVariant.platform,
|
|
1595
|
+
runtimeVariant.architecture,
|
|
1596
|
+
runtimeVariant.endianness,
|
|
1597
|
+
runtimeVariant.modulesAbi,
|
|
1598
|
+
runtimeVariant.napiAbi,
|
|
1599
|
+
runtimeVariant.libc,
|
|
1600
|
+
JSON.stringify(traversalLimits),
|
|
1601
|
+
],
|
|
1602
|
+
]).slice('sha256:'.length);
|
|
1603
|
+
}
|
|
1604
|
+
const MAX_PROCESS_CACHE_ENTRIES = 12;
|
|
1605
|
+
const processIdentityCache = new Map();
|
|
1606
|
+
/** @internal Clear process-local reuse between simulated-process unit tests. */
|
|
1607
|
+
function _clearAnalyzerIdentityProcessCacheForTests() {
|
|
1608
|
+
processIdentityCache.clear();
|
|
1609
|
+
}
|
|
1610
|
+
function cachePathFor(packageRoot, buildRoot, runtimeVariant, traversalLimits, options) {
|
|
1611
|
+
const directory = cacheDirectory(options, packageRoot, buildRoot);
|
|
1612
|
+
if (!directory)
|
|
1613
|
+
return null;
|
|
1614
|
+
const key = identityCacheKey(packageRoot, buildRoot, runtimeVariant, traversalLimits);
|
|
1615
|
+
return node_path_1.default.join(directory, `${key}.json`);
|
|
1616
|
+
}
|
|
1617
|
+
function readCacheFile(target) {
|
|
1618
|
+
let descriptor = null;
|
|
1619
|
+
try {
|
|
1620
|
+
const noFollow = 'O_NOFOLLOW' in node_fs_1.constants ? node_fs_1.constants.O_NOFOLLOW : 0;
|
|
1621
|
+
descriptor = (0, node_fs_1.openSync)(target, node_fs_1.constants.O_RDONLY | noFollow);
|
|
1622
|
+
const stat = (0, node_fs_1.fstatSync)(descriptor);
|
|
1623
|
+
const uid = currentUid();
|
|
1624
|
+
if (!stat.isFile() ||
|
|
1625
|
+
stat.isSymbolicLink() ||
|
|
1626
|
+
stat.size > MAX_CACHE_FILE_BYTES ||
|
|
1627
|
+
(uid !== null && (stat.uid !== uid || (stat.mode & 0o077) !== 0))) {
|
|
1628
|
+
return null;
|
|
1629
|
+
}
|
|
1630
|
+
return (0, node_fs_1.readFileSync)(descriptor, 'utf8');
|
|
1631
|
+
}
|
|
1632
|
+
catch {
|
|
1633
|
+
return null;
|
|
1634
|
+
}
|
|
1635
|
+
finally {
|
|
1636
|
+
if (descriptor !== null)
|
|
1637
|
+
(0, node_fs_1.closeSync)(descriptor);
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
function loadIdentityCache(packageRoot, buildRoot, runtimeVariant, traversalLimits, options) {
|
|
1641
|
+
// Invalid explicit cache configuration is an operator error, not an optional
|
|
1642
|
+
// cache miss. Resolve it outside the best-effort envelope read below.
|
|
1643
|
+
const target = cachePathFor(packageRoot, buildRoot, runtimeVariant, traversalLimits, options);
|
|
1644
|
+
try {
|
|
1645
|
+
// Validate any operator-selected persistence location on every call, even
|
|
1646
|
+
// when the payload itself is reusable from this process's guarded LRU.
|
|
1647
|
+
const key = identityCacheKey(packageRoot, buildRoot, runtimeVariant, traversalLimits);
|
|
1648
|
+
const local = processIdentityCache.get(key);
|
|
1649
|
+
if (local)
|
|
1650
|
+
return local;
|
|
1651
|
+
if (!target)
|
|
1652
|
+
return null;
|
|
1653
|
+
const raw = readCacheFile(target);
|
|
1654
|
+
if (raw === null)
|
|
1655
|
+
return null;
|
|
1656
|
+
const parsed = JSON.parse(raw);
|
|
1657
|
+
if (typeof parsed !== 'object' ||
|
|
1658
|
+
parsed === null ||
|
|
1659
|
+
typeof parsed.checksum !== 'string' ||
|
|
1660
|
+
!isIdentityCachePayload(parsed.payload, packageRoot, buildRoot, runtimeVariant, traversalLimits) ||
|
|
1661
|
+
parsed.checksum !== sha256(JSON.stringify(parsed.payload))) {
|
|
1662
|
+
return null;
|
|
1663
|
+
}
|
|
1664
|
+
return parsed.payload;
|
|
1665
|
+
}
|
|
1666
|
+
catch {
|
|
1667
|
+
return null;
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
const CACHE_GUARD_PROBE_SCRIPT = String.raw `
|
|
1671
|
+
const fs = require('node:fs/promises');
|
|
1672
|
+
const crypto = require('node:crypto');
|
|
1673
|
+
const state = (value) => ({
|
|
1674
|
+
dev: String(value.dev), ino: String(value.ino), mode: String(value.mode),
|
|
1675
|
+
nlink: String(value.nlink), size: String(value.size),
|
|
1676
|
+
mtimeNs: String(value.mtimeNs), ctimeNs: String(value.ctimeNs),
|
|
1677
|
+
});
|
|
1678
|
+
const frame = (hash, fields) => {
|
|
1679
|
+
const fieldCount = Buffer.allocUnsafe(4);
|
|
1680
|
+
fieldCount.writeUInt32BE(fields.length);
|
|
1681
|
+
hash.update(fieldCount);
|
|
1682
|
+
for (const field of fields) {
|
|
1683
|
+
const bytes = Buffer.from(field);
|
|
1684
|
+
const length = Buffer.allocUnsafe(8);
|
|
1685
|
+
length.writeBigUInt64BE(BigInt(bytes.length));
|
|
1686
|
+
hash.update(length);
|
|
1687
|
+
hash.update(bytes);
|
|
1688
|
+
}
|
|
1689
|
+
};
|
|
1690
|
+
const inventoryDigest = (entries) => {
|
|
1691
|
+
const normalized = entries.map((entry) => ({
|
|
1692
|
+
name: entry.name,
|
|
1693
|
+
kind: entry.isDirectory() ? 'directory'
|
|
1694
|
+
: entry.isFile() ? 'file'
|
|
1695
|
+
: entry.isSymbolicLink() ? 'symlink' : 'other',
|
|
1696
|
+
})).sort((a, b) => Buffer.compare(Buffer.from(a.name), Buffer.from(b.name)));
|
|
1697
|
+
const hash = crypto.createHash('sha256');
|
|
1698
|
+
frame(hash, ['directory-entries-v1']);
|
|
1699
|
+
for (const entry of normalized) frame(hash, [entry.name, entry.kind]);
|
|
1700
|
+
return 'sha256:' + hash.digest('hex');
|
|
1701
|
+
};
|
|
1702
|
+
const probe = async (request) => {
|
|
1703
|
+
try {
|
|
1704
|
+
const link = await fs.lstat(request.absolutePath, { bigint: true });
|
|
1705
|
+
const type = link.isDirectory() ? 'directory'
|
|
1706
|
+
: link.isFile() ? 'file'
|
|
1707
|
+
: link.isSymbolicLink() ? 'symlink' : 'other';
|
|
1708
|
+
if (request.mode === 'link') return {
|
|
1709
|
+
type,
|
|
1710
|
+
state: state(link),
|
|
1711
|
+
...(type === 'symlink' ? { symlinkTarget: await fs.readlink(request.absolutePath) } : {}),
|
|
1712
|
+
};
|
|
1713
|
+
if (request.mode === 'directory-inventory') {
|
|
1714
|
+
if (!link.isDirectory() || link.isSymbolicLink()) return null;
|
|
1715
|
+
const entriesDigest = inventoryDigest(
|
|
1716
|
+
await fs.readdir(request.absolutePath, { withFileTypes: true }),
|
|
1717
|
+
);
|
|
1718
|
+
const after = await fs.lstat(request.absolutePath, { bigint: true });
|
|
1719
|
+
if (JSON.stringify(state(link)) !== JSON.stringify(state(after))) return null;
|
|
1720
|
+
return { type: 'directory-inventory', state: state(after), entriesDigest };
|
|
1721
|
+
}
|
|
1722
|
+
const target = await fs.stat(request.absolutePath, { bigint: true });
|
|
1723
|
+
if (!target.isFile()) return null;
|
|
1724
|
+
const result = { type: 'readable-file', state: { link: state(link), target: state(target) } };
|
|
1725
|
+
if (link.isSymbolicLink()) result.state.symlinkTarget = await fs.readlink(request.absolutePath);
|
|
1726
|
+
return result;
|
|
1727
|
+
} catch { return null; }
|
|
1728
|
+
};
|
|
1729
|
+
let input = '';
|
|
1730
|
+
process.stdin.setEncoding('utf8');
|
|
1731
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
1732
|
+
process.stdin.on('end', async () => {
|
|
1733
|
+
try {
|
|
1734
|
+
const requests = JSON.parse(input);
|
|
1735
|
+
const results = [];
|
|
1736
|
+
for (let offset = 0; offset < requests.length; offset += 512) {
|
|
1737
|
+
results.push(...await Promise.all(requests.slice(offset, offset + 512).map(probe)));
|
|
1738
|
+
}
|
|
1739
|
+
process.stdout.write(JSON.stringify(results));
|
|
1740
|
+
} catch { process.exitCode = 1; }
|
|
1741
|
+
});
|
|
1742
|
+
`;
|
|
1743
|
+
function snapshotCacheGuardDirect(request) {
|
|
1744
|
+
try {
|
|
1745
|
+
if (request.mode === 'readable-file') {
|
|
1746
|
+
return { type: 'readable-file', state: snapshotReadableFile(request.absolutePath) };
|
|
1747
|
+
}
|
|
1748
|
+
if (request.mode === 'directory-inventory') {
|
|
1749
|
+
const inventory = snapshotDirectoryInventory(request.absolutePath);
|
|
1750
|
+
return { type: 'directory-inventory', ...inventory };
|
|
1751
|
+
}
|
|
1752
|
+
const stat = (0, node_fs_1.lstatSync)(request.absolutePath, { bigint: true });
|
|
1753
|
+
const type = stat.isDirectory()
|
|
1754
|
+
? 'directory'
|
|
1755
|
+
: stat.isFile()
|
|
1756
|
+
? 'file'
|
|
1757
|
+
: stat.isSymbolicLink()
|
|
1758
|
+
? 'symlink'
|
|
1759
|
+
: 'other';
|
|
1760
|
+
return {
|
|
1761
|
+
type,
|
|
1762
|
+
state: statState(stat),
|
|
1763
|
+
...(type === 'symlink' ? { symlinkTarget: (0, node_fs_1.readlinkSync)(request.absolutePath) } : {}),
|
|
1764
|
+
};
|
|
1765
|
+
}
|
|
1766
|
+
catch {
|
|
1767
|
+
return null;
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
function snapshotCacheGuards(requests) {
|
|
1771
|
+
if (requests.length < 128)
|
|
1772
|
+
return requests.map(snapshotCacheGuardDirect);
|
|
1773
|
+
try {
|
|
1774
|
+
const probe = (0, node_child_process_1.spawnSync)(process.execPath, ['--input-type=commonjs', '-e', CACHE_GUARD_PROBE_SCRIPT], {
|
|
1775
|
+
input: JSON.stringify(requests),
|
|
1776
|
+
encoding: 'utf8',
|
|
1777
|
+
maxBuffer: MAX_CACHE_FILE_BYTES,
|
|
1778
|
+
timeout: 30_000,
|
|
1779
|
+
windowsHide: true,
|
|
1780
|
+
});
|
|
1781
|
+
if (probe.status === 0 && !probe.error) {
|
|
1782
|
+
const parsed = JSON.parse(probe.stdout);
|
|
1783
|
+
if (Array.isArray(parsed) && parsed.length === requests.length) {
|
|
1784
|
+
return parsed;
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
catch {
|
|
1789
|
+
/* fall through to the slower in-process validator */
|
|
1790
|
+
}
|
|
1791
|
+
return requests.map(snapshotCacheGuardDirect);
|
|
1792
|
+
}
|
|
1793
|
+
function validateIdentityCache(cache, options) {
|
|
1794
|
+
const expected = new Map();
|
|
1795
|
+
const add = (request, result) => {
|
|
1796
|
+
const key = JSON.stringify([request.mode, request.absolutePath]);
|
|
1797
|
+
const prior = expected.get(key);
|
|
1798
|
+
if (expected.has(key) && !(0, node_util_1.isDeepStrictEqual)(prior, result)) {
|
|
1799
|
+
options.onCacheValidationFailure?.({ mode: request.mode, path: request.absolutePath });
|
|
1800
|
+
return false;
|
|
1801
|
+
}
|
|
1802
|
+
expected.set(key, result);
|
|
1803
|
+
return true;
|
|
1804
|
+
};
|
|
1805
|
+
if (!add({ absolutePath: cache.buildRoot, mode: 'link' }, { type: 'directory', state: cache.buildRootState })) {
|
|
1806
|
+
return false;
|
|
1807
|
+
}
|
|
1808
|
+
for (const entry of cache.buildEntries) {
|
|
1809
|
+
const absolutePath = node_path_1.default.join(cache.buildRoot, ...entry.relativePath.split('/'));
|
|
1810
|
+
if (!isInside(cache.buildRoot, absolutePath) ||
|
|
1811
|
+
!add({ absolutePath, mode: 'link' }, { type: entry.kind, state: entry.state })) {
|
|
1812
|
+
return false;
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
for (const guard of cache.buildDirectoryGuards) {
|
|
1816
|
+
const absolutePath = guard.relativePath
|
|
1817
|
+
? node_path_1.default.join(cache.buildRoot, ...guard.relativePath.split('/'))
|
|
1818
|
+
: cache.buildRoot;
|
|
1819
|
+
if (!isInside(cache.buildRoot, absolutePath) ||
|
|
1820
|
+
!add({ absolutePath, mode: 'directory-inventory' }, {
|
|
1821
|
+
type: 'directory-inventory',
|
|
1822
|
+
state: guard.state,
|
|
1823
|
+
entriesDigest: guard.entriesDigest,
|
|
1824
|
+
})) {
|
|
1825
|
+
return false;
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
for (const guard of cache.dependencyDirectoryGuards) {
|
|
1829
|
+
if (!add({ absolutePath: guard.absolutePath, mode: 'directory-inventory' }, {
|
|
1830
|
+
type: 'directory-inventory',
|
|
1831
|
+
state: guard.state,
|
|
1832
|
+
entriesDigest: guard.entriesDigest,
|
|
1833
|
+
})) {
|
|
1834
|
+
return false;
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
for (const guard of cache.dependencyPathGuards) {
|
|
1838
|
+
if (!add({ absolutePath: guard.absolutePath, mode: 'link' }, guard.result)) {
|
|
1839
|
+
return false;
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
for (const guard of cache.dependencyFileGuards) {
|
|
1843
|
+
if (!add({ absolutePath: guard.absolutePath, mode: 'readable-file' }, { type: 'readable-file', state: guard.state })) {
|
|
1844
|
+
return false;
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
for (const artifact of cache.artifactEntries) {
|
|
1848
|
+
// A recorded link is re-probed as a link, never as a readable file: the
|
|
1849
|
+
// readable-file probe resolves the target and would report `null` for the
|
|
1850
|
+
// very inputs this kind exists to describe, failing every warm validation.
|
|
1851
|
+
const probe = artifact.kind === 'unfollowed-symlink'
|
|
1852
|
+
? {
|
|
1853
|
+
request: { absolutePath: artifact.absolutePath, mode: 'link' },
|
|
1854
|
+
expected: {
|
|
1855
|
+
type: 'symlink',
|
|
1856
|
+
state: artifact.state.link,
|
|
1857
|
+
symlinkTarget: artifact.state.symlinkTarget,
|
|
1858
|
+
},
|
|
1859
|
+
}
|
|
1860
|
+
: {
|
|
1861
|
+
request: { absolutePath: artifact.absolutePath, mode: 'readable-file' },
|
|
1862
|
+
expected: { type: 'readable-file', state: artifact.state },
|
|
1863
|
+
};
|
|
1864
|
+
if (!add(probe.request, probe.expected))
|
|
1865
|
+
return false;
|
|
1866
|
+
}
|
|
1867
|
+
const entries = [...expected.entries()];
|
|
1868
|
+
const requests = entries.map(([key]) => {
|
|
1869
|
+
const [mode, absolutePath] = JSON.parse(key);
|
|
1870
|
+
return { mode, absolutePath };
|
|
1871
|
+
});
|
|
1872
|
+
options.onCacheValidationPass?.({ guardCount: requests.length });
|
|
1873
|
+
const actual = snapshotCacheGuards(requests);
|
|
1874
|
+
const mismatch = actual.findIndex((result, index) => !(0, node_util_1.isDeepStrictEqual)(result, entries[index][1]));
|
|
1875
|
+
if (mismatch !== -1) {
|
|
1876
|
+
const [mode, absolutePath] = JSON.parse(entries[mismatch][0]);
|
|
1877
|
+
options.onCacheValidationFailure?.({ mode, path: absolutePath });
|
|
1878
|
+
return false;
|
|
1879
|
+
}
|
|
1880
|
+
return true;
|
|
1881
|
+
}
|
|
1882
|
+
function cachedBuildDigestForPath(cache, absolutePath) {
|
|
1883
|
+
if (!isInside(cache.buildRoot, absolutePath))
|
|
1884
|
+
return null;
|
|
1885
|
+
const relativePath = node_path_1.default.relative(cache.buildRoot, absolutePath).split(node_path_1.default.sep).join('/');
|
|
1886
|
+
const entry = cache.buildEntries.find((candidate) => candidate.kind === 'file' && candidate.relativePath === relativePath);
|
|
1887
|
+
return entry?.digest ?? null;
|
|
1888
|
+
}
|
|
1889
|
+
function dependencyFileGuards(inputs) {
|
|
1890
|
+
const guards = new Map();
|
|
1891
|
+
if (inputs.lockfilePath && inputs.lockfileState) {
|
|
1892
|
+
guards.set(inputs.lockfilePath, inputs.lockfileState);
|
|
1893
|
+
}
|
|
1894
|
+
for (const runtimePackage of inputs.packages) {
|
|
1895
|
+
guards.set(runtimePackage.manifestPath, runtimePackage.manifestState);
|
|
1896
|
+
}
|
|
1897
|
+
for (const manifest of inputs.vendoredManifests) {
|
|
1898
|
+
guards.set(manifest.absolutePath, manifest.state);
|
|
1899
|
+
}
|
|
1900
|
+
return [...guards.entries()]
|
|
1901
|
+
.map(([absolutePath, state]) => ({ absolutePath, state }))
|
|
1902
|
+
.sort((a, b) => compareBytes(a.absolutePath, b.absolutePath));
|
|
1903
|
+
}
|
|
1904
|
+
function dependencyDirectoryGuards(inputs) {
|
|
1905
|
+
return [...inputs.directoryGuards.entries()]
|
|
1906
|
+
.map(([absolutePath, guard]) => ({ absolutePath, ...guard }))
|
|
1907
|
+
.sort((a, b) => compareBytes(a.absolutePath, b.absolutePath));
|
|
1908
|
+
}
|
|
1909
|
+
function dependencyPathGuards(inputs) {
|
|
1910
|
+
return [...inputs.pathGuards.entries()]
|
|
1911
|
+
.map(([absolutePath, result]) => ({ absolutePath, result }))
|
|
1912
|
+
.sort((a, b) => compareBytes(a.absolutePath, b.absolutePath));
|
|
1913
|
+
}
|
|
1914
|
+
function persistIdentityCache(packageRoot, buildRoot, payload, previous, options) {
|
|
1915
|
+
if (previous && (0, node_util_1.isDeepStrictEqual)(previous, payload))
|
|
1916
|
+
return;
|
|
1917
|
+
const target = cachePathFor(packageRoot, buildRoot, payload.runtimeVariant, payload.traversalLimits, options);
|
|
1918
|
+
if (!target)
|
|
1919
|
+
return;
|
|
1920
|
+
const temporary = `${target}.${process.pid}.${(0, node_crypto_1.randomBytes)(6).toString('hex')}.tmp`;
|
|
1921
|
+
try {
|
|
1922
|
+
if (options.cacheDirectory) {
|
|
1923
|
+
(0, node_fs_1.mkdirSync)(node_path_1.default.dirname(target), { recursive: true, mode: 0o700 });
|
|
1924
|
+
}
|
|
1925
|
+
else if (!hasTrustedCacheOverride(options) &&
|
|
1926
|
+
!isOwnedPrivateDirectory(node_path_1.default.dirname(target))) {
|
|
1927
|
+
return;
|
|
1928
|
+
}
|
|
1929
|
+
const envelope = {
|
|
1930
|
+
payload,
|
|
1931
|
+
checksum: sha256(JSON.stringify(payload)),
|
|
1932
|
+
};
|
|
1933
|
+
(0, node_fs_1.writeFileSync)(temporary, `${JSON.stringify(envelope)}\n`, {
|
|
1934
|
+
encoding: 'utf8',
|
|
1935
|
+
flag: 'wx',
|
|
1936
|
+
mode: 0o600,
|
|
1937
|
+
});
|
|
1938
|
+
(0, node_fs_1.renameSync)(temporary, target);
|
|
1939
|
+
}
|
|
1940
|
+
catch {
|
|
1941
|
+
// The cache is optional (notably for read-only package/container setups).
|
|
1942
|
+
// A failed write only loses the optimization; identity remains fail-closed.
|
|
1943
|
+
try {
|
|
1944
|
+
(0, node_fs_1.unlinkSync)(temporary);
|
|
1945
|
+
}
|
|
1946
|
+
catch {
|
|
1947
|
+
/* already renamed or never created */
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
function rememberIdentityCache(payload) {
|
|
1952
|
+
const key = identityCacheKey(payload.packageRoot, payload.buildRoot, payload.runtimeVariant, payload.traversalLimits);
|
|
1953
|
+
processIdentityCache.delete(key);
|
|
1954
|
+
processIdentityCache.set(key, payload);
|
|
1955
|
+
while (processIdentityCache.size > MAX_PROCESS_CACHE_ENTRIES) {
|
|
1956
|
+
const oldest = processIdentityCache.keys().next().value;
|
|
1957
|
+
if (oldest === undefined)
|
|
1958
|
+
break;
|
|
1959
|
+
processIdentityCache.delete(oldest);
|
|
1960
|
+
}
|
|
1961
|
+
}
|
|
1962
|
+
function resolveInvokedArtifact(buildRoot, analyzerModulePath) {
|
|
1963
|
+
const argvEntry = process.argv[1];
|
|
1964
|
+
if (!argvEntry)
|
|
1965
|
+
return analyzerModulePath;
|
|
1966
|
+
try {
|
|
1967
|
+
const resolved = resolveExistingPath(argvEntry);
|
|
1968
|
+
return isInside(buildRoot, resolved) && (0, node_fs_1.lstatSync)(resolved).isFile()
|
|
1969
|
+
? resolved
|
|
1970
|
+
: analyzerModulePath;
|
|
1971
|
+
}
|
|
1972
|
+
catch {
|
|
1973
|
+
return analyzerModulePath;
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1976
|
+
function isNonEmptyString(value) {
|
|
1977
|
+
return typeof value === 'string' && value.length > 0;
|
|
1978
|
+
}
|
|
1979
|
+
function runnerRuntimeIdentity(runtimeVariant) {
|
|
1980
|
+
return {
|
|
1981
|
+
executablePath: runtimeVariant.executablePath,
|
|
1982
|
+
version: runtimeVariant.nodeVersion,
|
|
1983
|
+
platform: runtimeVariant.platform,
|
|
1984
|
+
architecture: runtimeVariant.architecture,
|
|
1985
|
+
modulesAbi: runtimeVariant.modulesAbi,
|
|
1986
|
+
libc: runtimeVariant.libc,
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1989
|
+
function isAnalyzerRunnerIdentity(value) {
|
|
1990
|
+
if (typeof value !== 'object' || value === null)
|
|
1991
|
+
return false;
|
|
1992
|
+
const identity = value;
|
|
1993
|
+
const runtime = identity.runtime;
|
|
1994
|
+
const invoked = identity.invokedArtifact;
|
|
1995
|
+
const build = identity.build;
|
|
1996
|
+
const dependency = identity.dependencyRuntime;
|
|
1997
|
+
return (identity.schemaVersion === exports.ANALYZER_RUNNER_IDENTITY_SCHEMA_VERSION &&
|
|
1998
|
+
!!runtime &&
|
|
1999
|
+
isNonEmptyString(runtime.executablePath) &&
|
|
2000
|
+
isNonEmptyString(runtime.version) &&
|
|
2001
|
+
isNonEmptyString(runtime.platform) &&
|
|
2002
|
+
isNonEmptyString(runtime.architecture) &&
|
|
2003
|
+
isNonEmptyString(runtime.modulesAbi) &&
|
|
2004
|
+
isNonEmptyString(runtime.libc) &&
|
|
2005
|
+
isNonEmptyString(identity.cliVersion) &&
|
|
2006
|
+
!!invoked &&
|
|
2007
|
+
isNonEmptyString(invoked.path) &&
|
|
2008
|
+
typeof invoked.digest === 'string' &&
|
|
2009
|
+
SHA256_PATTERN.test(invoked.digest) &&
|
|
2010
|
+
!!build &&
|
|
2011
|
+
(build.kind === 'source' || build.kind === 'distribution') &&
|
|
2012
|
+
isNonEmptyString(build.rootPath) &&
|
|
2013
|
+
build.canonicalization === BUILD_CANONICALIZATION &&
|
|
2014
|
+
typeof build.digest === 'string' &&
|
|
2015
|
+
SHA256_PATTERN.test(build.digest) &&
|
|
2016
|
+
!!dependency &&
|
|
2017
|
+
isNonEmptyString(dependency.manifestPath) &&
|
|
2018
|
+
(dependency.lockfilePath === null || isNonEmptyString(dependency.lockfilePath)) &&
|
|
2019
|
+
dependency.canonicalization === DEPENDENCY_RUNTIME_CANONICALIZATION &&
|
|
2020
|
+
Number.isSafeInteger(dependency.packageCount) &&
|
|
2021
|
+
Number(dependency.packageCount) >= 1 &&
|
|
2022
|
+
Number.isSafeInteger(dependency.artifactCount) &&
|
|
2023
|
+
Number(dependency.artifactCount) >= 0 &&
|
|
2024
|
+
typeof dependency.digest === 'string' &&
|
|
2025
|
+
SHA256_PATTERN.test(dependency.digest));
|
|
2026
|
+
}
|
|
2027
|
+
/**
|
|
2028
|
+
* Normalize a raw diagnostic receipt for freshness comparison. The entrypoint
|
|
2029
|
+
* is the only excluded field; malformed/legacy receipts never compare equal.
|
|
2030
|
+
*/
|
|
2031
|
+
function normalizeAnalyzerRunnerIdentityForComparison(identity) {
|
|
2032
|
+
if (!isAnalyzerRunnerIdentity(identity))
|
|
2033
|
+
return null;
|
|
2034
|
+
const { invokedArtifact: _diagnosticEntrypoint, ...semantic } = identity;
|
|
2035
|
+
return semantic;
|
|
2036
|
+
}
|
|
2037
|
+
/** Resolve the identity of the analyzer build and runtime executing now. */
|
|
2038
|
+
function resolveAnalyzerRunnerIdentity(analyzerModuleUrl, options = {}) {
|
|
2039
|
+
const analyzerModulePath = resolveExistingPath((0, node_url_1.fileURLToPath)(analyzerModuleUrl));
|
|
2040
|
+
const { packageRoot, buildRoot, kind } = resolveBuildRoot(analyzerModulePath);
|
|
2041
|
+
const runtimeVariant = resolveRuntimeVariant();
|
|
2042
|
+
const traversalLimits = resolveTraversalLimits(options);
|
|
2043
|
+
const previousCache = loadIdentityCache(packageRoot, buildRoot, runtimeVariant, traversalLimits, options);
|
|
2044
|
+
const invokedArtifactPath = resolveInvokedArtifact(buildRoot, analyzerModulePath);
|
|
2045
|
+
if (previousCache?.buildKind === kind) {
|
|
2046
|
+
const invokedDigest = cachedBuildDigestForPath(previousCache, invokedArtifactPath);
|
|
2047
|
+
if (invokedDigest) {
|
|
2048
|
+
const cachedIdentity = {
|
|
2049
|
+
schemaVersion: exports.ANALYZER_RUNNER_IDENTITY_SCHEMA_VERSION,
|
|
2050
|
+
runtime: runnerRuntimeIdentity(runtimeVariant),
|
|
2051
|
+
cliVersion: previousCache.packageVersion,
|
|
2052
|
+
invokedArtifact: { path: invokedArtifactPath, digest: invokedDigest },
|
|
2053
|
+
build: {
|
|
2054
|
+
kind,
|
|
2055
|
+
rootPath: buildRoot,
|
|
2056
|
+
canonicalization: BUILD_CANONICALIZATION,
|
|
2057
|
+
digest: previousCache.buildDigest,
|
|
2058
|
+
},
|
|
2059
|
+
dependencyRuntime: previousCache.dependencyIdentity,
|
|
2060
|
+
};
|
|
2061
|
+
// One batched pass is deliberately the last filesystem observation on
|
|
2062
|
+
// a warm hit. Repeating the complete inventory both doubles status
|
|
2063
|
+
// latency and still cannot close the post-check scheduling window.
|
|
2064
|
+
if (validateIdentityCache(previousCache, options)) {
|
|
2065
|
+
rememberIdentityCache(previousCache);
|
|
2066
|
+
return cachedIdentity;
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
const build = hashBuildTree(buildRoot, previousCache, options, traversalLimits);
|
|
2071
|
+
const dependencyInputs = collectDependencyInputs(packageRoot, options, traversalLimits);
|
|
2072
|
+
const dependencySnapshotBefore = dependencySnapshot(dependencyInputs);
|
|
2073
|
+
const dependency = hashDependencyRuntime(dependencyInputs, previousCache, options, runtimeVariant);
|
|
2074
|
+
const packageVersion = dependencyInputs.packages[0]?.manifest.version;
|
|
2075
|
+
if (typeof packageVersion !== 'string' || packageVersion.trim() === '') {
|
|
2076
|
+
throw new Error(`GitNexus package version is unavailable in ${packageRoot}`);
|
|
2077
|
+
}
|
|
2078
|
+
const buildSnapshotAfter = buildSnapshot(collectBuildEntries(buildRoot, options, traversalLimits));
|
|
2079
|
+
if (!(0, node_util_1.isDeepStrictEqual)(build.snapshot, buildSnapshotAfter)) {
|
|
2080
|
+
throw new Error(`Analyzer build changed while its identity was being computed: ${buildRoot}`);
|
|
2081
|
+
}
|
|
2082
|
+
const dependencySnapshotAfter = dependencySnapshot(collectDependencyInputs(packageRoot, options, traversalLimits));
|
|
2083
|
+
if (!(0, node_util_1.isDeepStrictEqual)(dependencySnapshotBefore, dependencySnapshotAfter)) {
|
|
2084
|
+
throw new Error(`Analyzer dependency runtime changed while its identity was being computed: ${packageRoot}`);
|
|
2085
|
+
}
|
|
2086
|
+
const nextCache = {
|
|
2087
|
+
schemaVersion: IDENTITY_CACHE_SCHEMA_VERSION,
|
|
2088
|
+
packageRoot,
|
|
2089
|
+
buildRoot,
|
|
2090
|
+
packageVersion,
|
|
2091
|
+
buildKind: kind,
|
|
2092
|
+
buildCanonicalization: BUILD_CANONICALIZATION,
|
|
2093
|
+
dependencyCanonicalization: DEPENDENCY_RUNTIME_CANONICALIZATION,
|
|
2094
|
+
traversalLimits,
|
|
2095
|
+
runtimeVariant,
|
|
2096
|
+
buildRootState: build.rootState,
|
|
2097
|
+
buildDigest: build.digest,
|
|
2098
|
+
buildEntries: build.entries,
|
|
2099
|
+
buildDirectoryGuards: build.directoryGuards,
|
|
2100
|
+
dependencyIdentity: dependency.identity,
|
|
2101
|
+
dependencyFileGuards: dependencyFileGuards(dependencyInputs),
|
|
2102
|
+
dependencyDirectoryGuards: dependencyDirectoryGuards(dependencyInputs),
|
|
2103
|
+
dependencyPathGuards: dependencyPathGuards(dependencyInputs),
|
|
2104
|
+
artifactEntries: dependency.entries,
|
|
2105
|
+
};
|
|
2106
|
+
const invokedDigest = cachedBuildDigestForPath(nextCache, invokedArtifactPath);
|
|
2107
|
+
if (!invokedDigest) {
|
|
2108
|
+
throw new Error(`Invoked analyzer artifact is absent from the validated build: ${invokedArtifactPath}`);
|
|
2109
|
+
}
|
|
2110
|
+
const identity = {
|
|
2111
|
+
schemaVersion: exports.ANALYZER_RUNNER_IDENTITY_SCHEMA_VERSION,
|
|
2112
|
+
runtime: runnerRuntimeIdentity(runtimeVariant),
|
|
2113
|
+
cliVersion: packageVersion,
|
|
2114
|
+
invokedArtifact: {
|
|
2115
|
+
path: invokedArtifactPath,
|
|
2116
|
+
digest: invokedDigest,
|
|
2117
|
+
},
|
|
2118
|
+
build: {
|
|
2119
|
+
kind,
|
|
2120
|
+
rootPath: buildRoot,
|
|
2121
|
+
canonicalization: BUILD_CANONICALIZATION,
|
|
2122
|
+
digest: build.digest,
|
|
2123
|
+
},
|
|
2124
|
+
dependencyRuntime: dependency.identity,
|
|
2125
|
+
};
|
|
2126
|
+
let validationFailure;
|
|
2127
|
+
const validationOptions = {
|
|
2128
|
+
...options,
|
|
2129
|
+
onCacheValidationFailure: (failure) => {
|
|
2130
|
+
validationFailure = failure;
|
|
2131
|
+
options.onCacheValidationFailure?.(failure);
|
|
2132
|
+
},
|
|
2133
|
+
};
|
|
2134
|
+
if (!validateIdentityCache(nextCache, validationOptions)) {
|
|
2135
|
+
const mismatch = validationFailure
|
|
2136
|
+
? ` (failed ${validationFailure.mode} guard: ${validationFailure.path})`
|
|
2137
|
+
: '';
|
|
2138
|
+
throw new Error(`Analyzer build or dependency runtime changed while its identity was being computed: ${packageRoot}${mismatch}`);
|
|
2139
|
+
}
|
|
2140
|
+
rememberIdentityCache(nextCache);
|
|
2141
|
+
persistIdentityCache(packageRoot, buildRoot, nextCache, previousCache, options);
|
|
2142
|
+
return identity;
|
|
2143
|
+
}
|
|
2144
|
+
/**
|
|
2145
|
+
* Semantic freshness comparison. Both receipts must be well-formed schema-v4
|
|
2146
|
+
* values; only the diagnostic entrypoint field is normalized away.
|
|
2147
|
+
*/
|
|
2148
|
+
function analyzerRunnerIdentitiesEqual(indexedIdentity, currentIdentity) {
|
|
2149
|
+
const indexed = normalizeAnalyzerRunnerIdentityForComparison(indexedIdentity);
|
|
2150
|
+
const current = normalizeAnalyzerRunnerIdentityForComparison(currentIdentity);
|
|
2151
|
+
return indexed !== null && current !== null && (0, node_util_1.isDeepStrictEqual)(indexed, current);
|
|
2152
|
+
}
|
|
2153
|
+
/**
|
|
2154
|
+
* Capture analyzer identity before invoking a loader that may evaluate the
|
|
2155
|
+
* analyzer module graph. The explicit receipt is then threaded into analysis
|
|
2156
|
+
* and checked again immediately before metadata commit.
|
|
2157
|
+
*/
|
|
2158
|
+
async function captureAnalyzerIdentityBeforeLoad(analyzerModuleUrl, loader, options = {}) {
|
|
2159
|
+
const runnerIdentity = resolveAnalyzerRunnerIdentity(analyzerModuleUrl, options);
|
|
2160
|
+
const loaded = await loader();
|
|
2161
|
+
return { runnerIdentity, loaded };
|
|
2162
|
+
}
|
|
2163
|
+
/** Re-resolve immediately before commit and reject analyzer mutation mid-run. */
|
|
2164
|
+
function finalizeAnalyzerRunnerIdentity(analyzerModuleUrl, startedWith, options = {}) {
|
|
2165
|
+
const finalIdentity = resolveAnalyzerRunnerIdentity(analyzerModuleUrl, options);
|
|
2166
|
+
if (!analyzerRunnerIdentitiesEqual(startedWith, finalIdentity)) {
|
|
2167
|
+
throw new Error('Analyzer build or dependency runtime changed during analysis; refusing to stamp metadata. ' +
|
|
2168
|
+
'Retry with a stable GitNexus installation.');
|
|
2169
|
+
}
|
|
2170
|
+
return finalIdentity;
|
|
2171
|
+
}
|