@duckmind/dm-linux-x64 0.66.0 → 0.66.2
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/dm +2 -2
- package/extensions/.dm-extensions.json +12 -546
- package/extensions/dm-lens/clients/dispatch/runners/utils/runner-helpers.js +1 -1
- package/extensions/dm-mcp-adapter/mcp-keyring-helper.cjs +1 -1
- package/extensions/dm-ponytail/hooks/ponytail-activate.js +1 -1
- package/extensions/dm-ponytail/hooks/ponytail-instructions.js +1 -1
- package/extensions/dm-subagents/src/runs/background/runner-aliases.js +2 -2
- package/extensions/dm-subagents/src/runs/foreground/prompt-audit.js +4 -1
- package/extensions/dm-subagents/src/runs/shared/child-session.js +8 -6
- package/extensions/dm-subagents/src/watchdog/permission-arbiter.js +9 -1
- package/extensions/dm-subagents/src/watchdog/review.js +4 -1
- package/package.json +1 -1
- package/extensions/dm-lens/dist/clients/actionable-warnings-logger.js +0 -24
- package/extensions/dm-lens/dist/clients/actionable-warnings.js +0 -1014
- package/extensions/dm-lens/dist/clients/advisory-provenance.js +0 -385
- package/extensions/dm-lens/dist/clients/agent-behavior-client.js +0 -94
- package/extensions/dm-lens/dist/clients/agent-nudge.js +0 -151
- package/extensions/dm-lens/dist/clients/analysed-root.js +0 -1
- package/extensions/dm-lens/dist/clients/ast-grep-client.js +0 -576
- package/extensions/dm-lens/dist/clients/ast-grep-rule-manager.js +0 -114
- package/extensions/dm-lens/dist/clients/ast-grep-tool-logger.js +0 -85
- package/extensions/dm-lens/dist/clients/ast-grep-types.js +0 -1
- package/extensions/dm-lens/dist/clients/ast-grep-yaml-synth.js +0 -109
- package/extensions/dm-lens/dist/clients/atomic-write-staging.js +0 -66
- package/extensions/dm-lens/dist/clients/atomic-write.js +0 -37
- package/extensions/dm-lens/dist/clients/availability-probe-flight.js +0 -11
- package/extensions/dm-lens/dist/clients/bash-file-access.js +0 -830
- package/extensions/dm-lens/dist/clients/biome-client.js +0 -221
- package/extensions/dm-lens/dist/clients/blocker-freshness.js +0 -385
- package/extensions/dm-lens/dist/clients/blocker-past-eof.js +0 -49
- package/extensions/dm-lens/dist/clients/bootstrap.js +0 -246
- package/extensions/dm-lens/dist/clients/bounded-cache.js +0 -105
- package/extensions/dm-lens/dist/clients/bounded-pid-file-lock.js +0 -173
- package/extensions/dm-lens/dist/clients/bounded-telemetry.js +0 -104
- package/extensions/dm-lens/dist/clients/build-identity.js +0 -45
- package/extensions/dm-lens/dist/clients/bundled-resource-health.js +0 -41
- package/extensions/dm-lens/dist/clients/bus-events-logger.js +0 -48
- package/extensions/dm-lens/dist/clients/bus-publish.js +0 -117
- package/extensions/dm-lens/dist/clients/cache/rule-cache.js +0 -103
- package/extensions/dm-lens/dist/clients/cache-manager.js +0 -340
- package/extensions/dm-lens/dist/clients/cache-observability.js +0 -847
- package/extensions/dm-lens/dist/clients/call-graph.js +0 -628
- package/extensions/dm-lens/dist/clients/cargo-manifest.js +0 -151
- package/extensions/dm-lens/dist/clients/cascade-budget.js +0 -52
- package/extensions/dm-lens/dist/clients/cascade-format.js +0 -160
- package/extensions/dm-lens/dist/clients/cascade-logger.js +0 -22
- package/extensions/dm-lens/dist/clients/cascade-types.js +0 -1
- package/extensions/dm-lens/dist/clients/child-unref.js +0 -59
- package/extensions/dm-lens/dist/clients/code-quality-warnings.js +0 -192
- package/extensions/dm-lens/dist/clients/codebase-model.js +0 -110
- package/extensions/dm-lens/dist/clients/collateral-test-role.js +0 -8
- package/extensions/dm-lens/dist/clients/complexity-client.js +0 -398
- package/extensions/dm-lens/dist/clients/config-core/deny.js +0 -117
- package/extensions/dm-lens/dist/clients/config-core/index.js +0 -2
- package/extensions/dm-lens/dist/clients/config-core/merge.js +0 -228
- package/extensions/dm-lens/dist/clients/config-core/normalize.js +0 -217
- package/extensions/dm-lens/dist/clients/config-core/process-spec.js +0 -147
- package/extensions/dm-lens/dist/clients/config-core/provenance.js +0 -54
- package/extensions/dm-lens/dist/clients/config-core/records.js +0 -79
- package/extensions/dm-lens/dist/clients/config-core/resolve.js +0 -48
- package/extensions/dm-lens/dist/clients/config-core/safe-object.js +0 -22
- package/extensions/dm-lens/dist/clients/config-core/schema.js +0 -90
- package/extensions/dm-lens/dist/clients/config-diagnostic-codes.js +0 -90
- package/extensions/dm-lens/dist/clients/config-locations.js +0 -175
- package/extensions/dm-lens/dist/clients/config-resolve.js +0 -359
- package/extensions/dm-lens/dist/clients/config-schema.js +0 -128
- package/extensions/dm-lens/dist/clients/config-warn.js +0 -146
- package/extensions/dm-lens/dist/clients/console-guard-install.js +0 -4
- package/extensions/dm-lens/dist/clients/cooperative-budget.js +0 -36
- package/extensions/dm-lens/dist/clients/dead-code-client.js +0 -342
- package/extensions/dm-lens/dist/clients/dead-code-logger.js +0 -18
- package/extensions/dm-lens/dist/clients/deadline-utils.js +0 -117
- package/extensions/dm-lens/dist/clients/debug-handles.js +0 -121
- package/extensions/dm-lens/dist/clients/debug-heap.js +0 -80
- package/extensions/dm-lens/dist/clients/deferred-lsp-work.js +0 -33
- package/extensions/dm-lens/dist/clients/degradation-ledger.js +0 -272
- package/extensions/dm-lens/dist/clients/demoted-finding-render.js +0 -61
- package/extensions/dm-lens/dist/clients/dependency-checker.js +0 -525
- package/extensions/dm-lens/dist/clients/deps/ast-grep-napi.js +0 -17
- package/extensions/dm-lens/dist/clients/deps/dm-host-deps.js +0 -10
- package/extensions/dm-lens/dist/clients/deps/dm-tui-compat.js +0 -58
- package/extensions/dm-lens/dist/clients/deps/js-yaml.js +0 -7
- package/extensions/dm-lens/dist/clients/deps/minimatch.js +0 -6
- package/extensions/dm-lens/dist/clients/deps/pidusage.js +0 -5
- package/extensions/dm-lens/dist/clients/deps/typebox.js +0 -5
- package/extensions/dm-lens/dist/clients/deps/vscode-jsonrpc.js +0 -8
- package/extensions/dm-lens/dist/clients/deps/web-tree-sitter.js +0 -11
- package/extensions/dm-lens/dist/clients/diagnostic-dispositions.js +0 -318
- package/extensions/dm-lens/dist/clients/diagnostic-line-freshness.js +0 -144
- package/extensions/dm-lens/dist/clients/diagnostic-logger.js +0 -60
- package/extensions/dm-lens/dist/clients/diagnostic-tracker.js +0 -89
- package/extensions/dm-lens/dist/clients/diagnostics-publish.js +0 -134
- package/extensions/dm-lens/dist/clients/dispatch/ast-grep-catalog.js +0 -16
- package/extensions/dm-lens/dist/clients/dispatch/auxiliary-lsp.js +0 -158
- package/extensions/dm-lens/dist/clients/dispatch/collect-later-tier.js +0 -25
- package/extensions/dm-lens/dist/clients/dispatch/diagnostic-taxonomy.js +0 -58
- package/extensions/dm-lens/dist/clients/dispatch/dispatcher.js +0 -953
- package/extensions/dm-lens/dist/clients/dispatch/fact-provider-types.js +0 -1
- package/extensions/dm-lens/dist/clients/dispatch/fact-rule-runner.js +0 -17
- package/extensions/dm-lens/dist/clients/dispatch/fact-runner.js +0 -18
- package/extensions/dm-lens/dist/clients/dispatch/fact-scheduler.js +0 -52
- package/extensions/dm-lens/dist/clients/dispatch/fact-store.js +0 -199
- package/extensions/dm-lens/dist/clients/dispatch/facts/comment-facts.js +0 -20
- package/extensions/dm-lens/dist/clients/dispatch/facts/file-content.js +0 -18
- package/extensions/dm-lens/dist/clients/dispatch/facts/function-facts.js +0 -281
- package/extensions/dm-lens/dist/clients/dispatch/facts/import-facts.js +0 -169
- package/extensions/dm-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +0 -19
- package/extensions/dm-lens/dist/clients/dispatch/facts/try-catch-facts.js +0 -95
- package/extensions/dm-lens/dist/clients/dispatch/finding-policy.js +0 -159
- package/extensions/dm-lens/dist/clients/dispatch/indent-detect.js +0 -227
- package/extensions/dm-lens/dist/clients/dispatch/inline-suppressions.js +0 -52
- package/extensions/dm-lens/dist/clients/dispatch/integration.js +0 -1463
- package/extensions/dm-lens/dist/clients/dispatch/lazy.js +0 -8
- package/extensions/dm-lens/dist/clients/dispatch/pending-runner-findings.js +0 -75
- package/extensions/dm-lens/dist/clients/dispatch/plan.js +0 -278
- package/extensions/dm-lens/dist/clients/dispatch/priorities.js +0 -14
- package/extensions/dm-lens/dist/clients/dispatch/rule-id-normalize.js +0 -42
- package/extensions/dm-lens/dist/clients/dispatch/rule-ignores.js +0 -21
- package/extensions/dm-lens/dist/clients/dispatch/rule-policy.js +0 -73
- package/extensions/dm-lens/dist/clients/dispatch/rules/async-noise.js +0 -31
- package/extensions/dm-lens/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +0 -27
- package/extensions/dm-lens/dist/clients/dispatch/rules/cors-wildcard.js +0 -35
- package/extensions/dm-lens/dist/clients/dispatch/rules/error-obscuring.js +0 -29
- package/extensions/dm-lens/dist/clients/dispatch/rules/error-swallowing.js +0 -29
- package/extensions/dm-lens/dist/clients/dispatch/rules/framework-call-noise.js +0 -31
- package/extensions/dm-lens/dist/clients/dispatch/rules/high-complexity.js +0 -56
- package/extensions/dm-lens/dist/clients/dispatch/rules/high-fan-out.js +0 -45
- package/extensions/dm-lens/dist/clients/dispatch/rules/high-import-coupling.js +0 -31
- package/extensions/dm-lens/dist/clients/dispatch/rules/missing-error-propagation.js +0 -45
- package/extensions/dm-lens/dist/clients/dispatch/rules/no-commented-credentials.js +0 -46
- package/extensions/dm-lens/dist/clients/dispatch/rules/pass-through-wrappers.js +0 -38
- package/extensions/dm-lens/dist/clients/dispatch/rules/placeholder-comments.js +0 -40
- package/extensions/dm-lens/dist/clients/dispatch/rules/unsafe-boundary.js +0 -101
- package/extensions/dm-lens/dist/clients/dispatch/runner-context.js +0 -46
- package/extensions/dm-lens/dist/clients/dispatch/runners/actionlint.js +0 -98
- package/extensions/dm-lens/dist/clients/dispatch/runners/ast-grep-napi.js +0 -690
- package/extensions/dm-lens/dist/clients/dispatch/runners/biome-check.js +0 -202
- package/extensions/dm-lens/dist/clients/dispatch/runners/cpp-check.js +0 -190
- package/extensions/dm-lens/dist/clients/dispatch/runners/credo.js +0 -66
- package/extensions/dm-lens/dist/clients/dispatch/runners/cue-vet.js +0 -167
- package/extensions/dm-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -151
- package/extensions/dm-lens/dist/clients/dispatch/runners/detekt.js +0 -157
- package/extensions/dm-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -157
- package/extensions/dm-lens/dist/clients/dispatch/runners/elixir-check.js +0 -153
- package/extensions/dm-lens/dist/clients/dispatch/runners/eslint.js +0 -99
- package/extensions/dm-lens/dist/clients/dispatch/runners/fact-rules.js +0 -20
- package/extensions/dm-lens/dist/clients/dispatch/runners/fish-indent.js +0 -65
- package/extensions/dm-lens/dist/clients/dispatch/runners/gleam-check.js +0 -107
- package/extensions/dm-lens/dist/clients/dispatch/runners/go-vet.js +0 -51
- package/extensions/dm-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -89
- package/extensions/dm-lens/dist/clients/dispatch/runners/hadolint.js +0 -83
- package/extensions/dm-lens/dist/clients/dispatch/runners/helm-lint.js +0 -144
- package/extensions/dm-lens/dist/clients/dispatch/runners/helm-render.js +0 -593
- package/extensions/dm-lens/dist/clients/dispatch/runners/htmlhint.js +0 -103
- package/extensions/dm-lens/dist/clients/dispatch/runners/index.js +0 -100
- package/extensions/dm-lens/dist/clients/dispatch/runners/javac.js +0 -82
- package/extensions/dm-lens/dist/clients/dispatch/runners/ktlint.js +0 -93
- package/extensions/dm-lens/dist/clients/dispatch/runners/lsp.js +0 -206
- package/extensions/dm-lens/dist/clients/dispatch/runners/markdownlint.js +0 -129
- package/extensions/dm-lens/dist/clients/dispatch/runners/mypy.js +0 -70
- package/extensions/dm-lens/dist/clients/dispatch/runners/oxlint.js +0 -305
- package/extensions/dm-lens/dist/clients/dispatch/runners/php-lint.js +0 -70
- package/extensions/dm-lens/dist/clients/dispatch/runners/phpstan.js +0 -90
- package/extensions/dm-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -56
- package/extensions/dm-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -307
- package/extensions/dm-lens/dist/clients/dispatch/runners/pyright.js +0 -118
- package/extensions/dm-lens/dist/clients/dispatch/runners/rubocop.js +0 -79
- package/extensions/dm-lens/dist/clients/dispatch/runners/ruff.js +0 -69
- package/extensions/dm-lens/dist/clients/dispatch/runners/rust-clippy.js +0 -147
- package/extensions/dm-lens/dist/clients/dispatch/runners/shellcheck.js +0 -111
- package/extensions/dm-lens/dist/clients/dispatch/runners/shfmt.js +0 -81
- package/extensions/dm-lens/dist/clients/dispatch/runners/spellcheck.js +0 -74
- package/extensions/dm-lens/dist/clients/dispatch/runners/spotbugs.js +0 -208
- package/extensions/dm-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -136
- package/extensions/dm-lens/dist/clients/dispatch/runners/stylelint.js +0 -118
- package/extensions/dm-lens/dist/clients/dispatch/runners/swiftlint.js +0 -143
- package/extensions/dm-lens/dist/clients/dispatch/runners/taplo.js +0 -97
- package/extensions/dm-lens/dist/clients/dispatch/runners/terragrunt.js +0 -110
- package/extensions/dm-lens/dist/clients/dispatch/runners/tflint.js +0 -104
- package/extensions/dm-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -516
- package/extensions/dm-lens/dist/clients/dispatch/runners/trivy-config.js +0 -155
- package/extensions/dm-lens/dist/clients/dispatch/runners/utils/availability-policy.js +0 -285
- package/extensions/dm-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +0 -61
- package/extensions/dm-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -61
- package/extensions/dm-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +0 -130
- package/extensions/dm-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +0 -1067
- package/extensions/dm-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +0 -40
- package/extensions/dm-lens/dist/clients/dispatch/runners/utils/tool-failure.js +0 -117
- package/extensions/dm-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +0 -88
- package/extensions/dm-lens/dist/clients/dispatch/runners/utils.js +0 -8
- package/extensions/dm-lens/dist/clients/dispatch/runners/vale.js +0 -106
- package/extensions/dm-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +0 -163
- package/extensions/dm-lens/dist/clients/dispatch/runners/yamllint.js +0 -83
- package/extensions/dm-lens/dist/clients/dispatch/runners/zig-check.js +0 -72
- package/extensions/dm-lens/dist/clients/dispatch/suppress-writer.js +0 -57
- package/extensions/dm-lens/dist/clients/dispatch/tool-profile.js +0 -30
- package/extensions/dm-lens/dist/clients/dispatch/types.js +0 -7
- package/extensions/dm-lens/dist/clients/dispatch/utils/format-utils.js +0 -45
- package/extensions/dm-lens/dist/clients/dispatch/utils/lsp-diagnostics.js +0 -31
- package/extensions/dm-lens/dist/clients/disposition-logger.js +0 -19
- package/extensions/dm-lens/dist/clients/disposition-publish.js +0 -88
- package/extensions/dm-lens/dist/clients/durable-store.js +0 -77
- package/extensions/dm-lens/dist/clients/effective-config.js +0 -160
- package/extensions/dm-lens/dist/clients/env-utils.js +0 -26
- package/extensions/dm-lens/dist/clients/error-class.js +0 -3
- package/extensions/dm-lens/dist/clients/eval-timestamp.js +0 -2
- package/extensions/dm-lens/dist/clients/event-loop-hold.js +0 -119
- package/extensions/dm-lens/dist/clients/event-loop-monitor.js +0 -84
- package/extensions/dm-lens/dist/clients/extension-log.js +0 -230
- package/extensions/dm-lens/dist/clients/extension-mode.js +0 -20
- package/extensions/dm-lens/dist/clients/feature-hints.js +0 -88
- package/extensions/dm-lens/dist/clients/file-kinds.js +0 -304
- package/extensions/dm-lens/dist/clients/file-role.js +0 -50
- package/extensions/dm-lens/dist/clients/file-time.js +0 -112
- package/extensions/dm-lens/dist/clients/file-utils.js +0 -630
- package/extensions/dm-lens/dist/clients/finding-delivery-gate.js +0 -129
- package/extensions/dm-lens/dist/clients/finding-identity.js +0 -23
- package/extensions/dm-lens/dist/clients/fix-worklog.js +0 -75
- package/extensions/dm-lens/dist/clients/format-events-publish.js +0 -230
- package/extensions/dm-lens/dist/clients/format-service.js +0 -154
- package/extensions/dm-lens/dist/clients/formatters-lazy.js +0 -8
- package/extensions/dm-lens/dist/clients/formatters.js +0 -1351
- package/extensions/dm-lens/dist/clients/freshness-cadence.js +0 -1
- package/extensions/dm-lens/dist/clients/freshness.js +0 -8
- package/extensions/dm-lens/dist/clients/generated-artifacts.js +0 -209
- package/extensions/dm-lens/dist/clients/generation-guard.js +0 -105
- package/extensions/dm-lens/dist/clients/git-guard.js +0 -770
- package/extensions/dm-lens/dist/clients/git-tracked-ignore.js +0 -120
- package/extensions/dm-lens/dist/clients/gitleaks-client.js +0 -282
- package/extensions/dm-lens/dist/clients/go-client.js +0 -47
- package/extensions/dm-lens/dist/clients/govulncheck-client.js +0 -411
- package/extensions/dm-lens/dist/clients/gradle-ktfmt-style.js +0 -177
- package/extensions/dm-lens/dist/clients/grammar-source.js +0 -224
- package/extensions/dm-lens/dist/clients/gzip-stage-write.js +0 -85
- package/extensions/dm-lens/dist/clients/hashline-anchor.js +0 -222
- package/extensions/dm-lens/dist/clients/hook-budgets.js +0 -16
- package/extensions/dm-lens/dist/clients/host-edit-normalize.js +0 -87
- package/extensions/dm-lens/dist/clients/host-ports.js +0 -35
- package/extensions/dm-lens/dist/clients/indent-retarget.js +0 -102
- package/extensions/dm-lens/dist/clients/inline-blocker-dispositions.js +0 -103
- package/extensions/dm-lens/dist/clients/install-diagnostics.js +0 -123
- package/extensions/dm-lens/dist/clients/installer/index.js +0 -3770
- package/extensions/dm-lens/dist/clients/installer/managed-tool-refresh-session.js +0 -17
- package/extensions/dm-lens/dist/clients/installer/managed-tool-refresh.js +0 -522
- package/extensions/dm-lens/dist/clients/instance-reaper-state.js +0 -3
- package/extensions/dm-lens/dist/clients/instance-reaper.js +0 -577
- package/extensions/dm-lens/dist/clients/instance-registry-lock.js +0 -131
- package/extensions/dm-lens/dist/clients/instance-registry.js +0 -452
- package/extensions/dm-lens/dist/clients/jscpd-client.js +0 -252
- package/extensions/dm-lens/dist/clients/json-cache-read.js +0 -25
- package/extensions/dm-lens/dist/clients/knip-client.js +0 -472
- package/extensions/dm-lens/dist/clients/language-policy.js +0 -242
- package/extensions/dm-lens/dist/clients/language-profile.js +0 -261
- package/extensions/dm-lens/dist/clients/language-registry.js +0 -424
- package/extensions/dm-lens/dist/clients/latency-logger.js +0 -192
- package/extensions/dm-lens/dist/clients/lazy-import.js +0 -14
- package/extensions/dm-lens/dist/clients/ledger-bounds.js +0 -21
- package/extensions/dm-lens/dist/clients/lens-config.js +0 -237
- package/extensions/dm-lens/dist/clients/lens-engine.js +0 -181
- package/extensions/dm-lens/dist/clients/lens-events.js +0 -105
- package/extensions/dm-lens/dist/clients/lens-flag-registry.js +0 -302
- package/extensions/dm-lens/dist/clients/lens-map.js +0 -980
- package/extensions/dm-lens/dist/clients/live-bus-emitter.js +0 -53
- package/extensions/dm-lens/dist/clients/log-cleanup.js +0 -138
- package/extensions/dm-lens/dist/clients/lsp/aggregation.js +0 -88
- package/extensions/dm-lens/dist/clients/lsp/cascade-tier.js +0 -203
- package/extensions/dm-lens/dist/clients/lsp/client.js +0 -2709
- package/extensions/dm-lens/dist/clients/lsp/config.js +0 -252
- package/extensions/dm-lens/dist/clients/lsp/content-limits.js +0 -21
- package/extensions/dm-lens/dist/clients/lsp/diagnostic-binding.js +0 -79
- package/extensions/dm-lens/dist/clients/lsp/document-drift.js +0 -312
- package/extensions/dm-lens/dist/clients/lsp/edits.js +0 -930
- package/extensions/dm-lens/dist/clients/lsp/index.js +0 -4448
- package/extensions/dm-lens/dist/clients/lsp/inferred-project.js +0 -121
- package/extensions/dm-lens/dist/clients/lsp/jvm-runtime.js +0 -194
- package/extensions/dm-lens/dist/clients/lsp/language.js +0 -23
- package/extensions/dm-lens/dist/clients/lsp/launch.js +0 -468
- package/extensions/dm-lens/dist/clients/lsp/lombok.js +0 -146
- package/extensions/dm-lens/dist/clients/lsp/path-utils.js +0 -1
- package/extensions/dm-lens/dist/clients/lsp/pending-aux-coverage.js +0 -90
- package/extensions/dm-lens/dist/clients/lsp/position-encoding.js +0 -28
- package/extensions/dm-lens/dist/clients/lsp/ruby-drive-dirs.js +0 -42
- package/extensions/dm-lens/dist/clients/lsp/server.js +0 -2422
- package/extensions/dm-lens/dist/clients/lsp/session-roots.js +0 -52
- package/extensions/dm-lens/dist/clients/lsp/spawn-history.js +0 -15
- package/extensions/dm-lens/dist/clients/lsp/sync-kind.js +0 -30
- package/extensions/dm-lens/dist/clients/lsp/tsserver-sync.js +0 -156
- package/extensions/dm-lens/dist/clients/lsp/wait-policy/capability-snapshot.js +0 -1
- package/extensions/dm-lens/dist/clients/lsp/wait-policy/classification.js +0 -36
- package/extensions/dm-lens/dist/clients/lsp/wait-policy/index.js +0 -3
- package/extensions/dm-lens/dist/clients/lsp/wait-policy/strategies.js +0 -175
- package/extensions/dm-lens/dist/clients/lsp/watch-queue.js +0 -40
- package/extensions/dm-lens/dist/clients/lsp/workspace-diagnostics-cache.js +0 -267
- package/extensions/dm-lens/dist/clients/lsp/workspace-diagnostics-session.js +0 -7
- package/extensions/dm-lens/dist/clients/lsp/workspace-sweep-hold.js +0 -93
- package/extensions/dm-lens/dist/clients/lsp-budget.js +0 -83
- package/extensions/dm-lens/dist/clients/lsp-document-symbols.js +0 -93
- package/extensions/dm-lens/dist/clients/lsp-lazy.js +0 -8
- package/extensions/dm-lens/dist/clients/lsp-mutation.js +0 -273
- package/extensions/dm-lens/dist/clients/lsp-status.js +0 -37
- package/extensions/dm-lens/dist/clients/map-with-concurrency.js +0 -20
- package/extensions/dm-lens/dist/clients/mcp/analyze.js +0 -251
- package/extensions/dm-lens/dist/clients/mcp/host-shim.js +0 -14
- package/extensions/dm-lens/dist/clients/mcp/ipc.js +0 -302
- package/extensions/dm-lens/dist/clients/mcp/review.js +0 -137
- package/extensions/dm-lens/dist/clients/mcp/session.js +0 -305
- package/extensions/dm-lens/dist/clients/memory-sampler.js +0 -107
- package/extensions/dm-lens/dist/clients/message-end-attribution.js +0 -18
- package/extensions/dm-lens/dist/clients/metrics-client.js +0 -80
- package/extensions/dm-lens/dist/clients/metrics-history.js +0 -255
- package/extensions/dm-lens/dist/clients/middle-man-analysis.js +0 -161
- package/extensions/dm-lens/dist/clients/model-provider.js +0 -31
- package/extensions/dm-lens/dist/clients/module-report-lsp.js +0 -140
- package/extensions/dm-lens/dist/clients/module-report.js +0 -1642
- package/extensions/dm-lens/dist/clients/mutating-tool.js +0 -377
- package/extensions/dm-lens/dist/clients/mutation-attribution.js +0 -168
- package/extensions/dm-lens/dist/clients/mutation-bridge.js +0 -102
- package/extensions/dm-lens/dist/clients/ndjson-logger.js +0 -471
- package/extensions/dm-lens/dist/clients/observed-mutation-sources.js +0 -43
- package/extensions/dm-lens/dist/clients/observed-mutation.js +0 -709
- package/extensions/dm-lens/dist/clients/oldtext-autopatch.js +0 -100
- package/extensions/dm-lens/dist/clients/opaque-mutation-scan.js +0 -313
- package/extensions/dm-lens/dist/clients/opengrep-client.js +0 -216
- package/extensions/dm-lens/dist/clients/opengrep-config.js +0 -52
- package/extensions/dm-lens/dist/clients/package-manager.js +0 -303
- package/extensions/dm-lens/dist/clients/package-root.js +0 -25
- package/extensions/dm-lens/dist/clients/partial-edit-apply.js +0 -337
- package/extensions/dm-lens/dist/clients/path-attribution-telemetry.js +0 -31
- package/extensions/dm-lens/dist/clients/path-keyed-map.js +0 -57
- package/extensions/dm-lens/dist/clients/path-utils.js +0 -476
- package/extensions/dm-lens/dist/clients/performance-report.js +0 -291
- package/extensions/dm-lens/dist/clients/persist-debounce.js +0 -37
- package/extensions/dm-lens/dist/clients/persistent-reverify.js +0 -165
- package/extensions/dm-lens/dist/clients/php-cs-fixer-config.js +0 -15
- package/extensions/dm-lens/dist/clients/pipeline.js +0 -1091
- package/extensions/dm-lens/dist/clients/post-autofix-notice.js +0 -19
- package/extensions/dm-lens/dist/clients/print-mode.js +0 -3
- package/extensions/dm-lens/dist/clients/probe-home-state.js +0 -62
- package/extensions/dm-lens/dist/clients/process-bridge.js +0 -20
- package/extensions/dm-lens/dist/clients/process-singletons.js +0 -69
- package/extensions/dm-lens/dist/clients/process-snapshot.js +0 -22
- package/extensions/dm-lens/dist/clients/project-changes.js +0 -112
- package/extensions/dm-lens/dist/clients/project-conventions.js +0 -152
- package/extensions/dm-lens/dist/clients/project-diagnostics/cache.js +0 -91
- package/extensions/dm-lens/dist/clients/project-diagnostics/extractors.js +0 -53
- package/extensions/dm-lens/dist/clients/project-diagnostics/fresh-fetch.js +0 -408
- package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +0 -64
- package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +0 -38
- package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +0 -21
- package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +0 -23
- package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +0 -28
- package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +0 -45
- package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +0 -34
- package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +0 -32
- package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +0 -79
- package/extensions/dm-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +0 -41
- package/extensions/dm-lens/dist/clients/project-diagnostics/scanner.js +0 -349
- package/extensions/dm-lens/dist/clients/project-diagnostics/types.js +0 -1
- package/extensions/dm-lens/dist/clients/project-lens-config.js +0 -437
- package/extensions/dm-lens/dist/clients/project-report.js +0 -527
- package/extensions/dm-lens/dist/clients/project-scale.js +0 -60
- package/extensions/dm-lens/dist/clients/project-scan-policy.js +0 -4
- package/extensions/dm-lens/dist/clients/project-snapshot-fingerprint.js +0 -39
- package/extensions/dm-lens/dist/clients/project-snapshot-persist-worker.js +0 -14
- package/extensions/dm-lens/dist/clients/project-snapshot.js +0 -1178
- package/extensions/dm-lens/dist/clients/project-trust.js +0 -104
- package/extensions/dm-lens/dist/clients/python-environment.js +0 -120
- package/extensions/dm-lens/dist/clients/python-provenance.js +0 -551
- package/extensions/dm-lens/dist/clients/quiet-window-config.js +0 -16
- package/extensions/dm-lens/dist/clients/quiet-window.js +0 -126
- package/extensions/dm-lens/dist/clients/read-bridge.js +0 -46
- package/extensions/dm-lens/dist/clients/read-expansion.js +0 -218
- package/extensions/dm-lens/dist/clients/read-guard-logger.js +0 -178
- package/extensions/dm-lens/dist/clients/read-guard-tool-lines.js +0 -1042
- package/extensions/dm-lens/dist/clients/read-guard.js +0 -1096
- package/extensions/dm-lens/dist/clients/recent-touches.js +0 -126
- package/extensions/dm-lens/dist/clients/redact/secrets.js +0 -386
- package/extensions/dm-lens/dist/clients/resource-sampler.js +0 -344
- package/extensions/dm-lens/dist/clients/reverse-deps.js +0 -227
- package/extensions/dm-lens/dist/clients/review-graph/builder.js +0 -3635
- package/extensions/dm-lens/dist/clients/review-graph/format.js +0 -30
- package/extensions/dm-lens/dist/clients/review-graph/git-identity.js +0 -115
- package/extensions/dm-lens/dist/clients/review-graph/import-resolvers.js +0 -325
- package/extensions/dm-lens/dist/clients/review-graph/persist-worker.js +0 -8
- package/extensions/dm-lens/dist/clients/review-graph/query.js +0 -195
- package/extensions/dm-lens/dist/clients/review-graph/revision-drift.js +0 -3
- package/extensions/dm-lens/dist/clients/review-graph/service.js +0 -60
- package/extensions/dm-lens/dist/clients/review-graph/shared-extraction-ir.js +0 -54
- package/extensions/dm-lens/dist/clients/review-graph/symbol-id.js +0 -29
- package/extensions/dm-lens/dist/clients/review-graph/tsconfig-paths.js +0 -253
- package/extensions/dm-lens/dist/clients/review-graph/types.js +0 -1
- package/extensions/dm-lens/dist/clients/review-graph/workspace-modules.js +0 -374
- package/extensions/dm-lens/dist/clients/review-graph-logger.js +0 -51
- package/extensions/dm-lens/dist/clients/ruff-client.js +0 -109
- package/extensions/dm-lens/dist/clients/rules-scanner.js +0 -113
- package/extensions/dm-lens/dist/clients/run-duration.js +0 -9
- package/extensions/dm-lens/dist/clients/runtime-agent-end.js +0 -608
- package/extensions/dm-lens/dist/clients/runtime-config.js +0 -39
- package/extensions/dm-lens/dist/clients/runtime-context.js +0 -155
- package/extensions/dm-lens/dist/clients/runtime-coordinator.js +0 -817
- package/extensions/dm-lens/dist/clients/runtime-session.js +0 -1625
- package/extensions/dm-lens/dist/clients/runtime-tool-call.js +0 -868
- package/extensions/dm-lens/dist/clients/runtime-tool-result.js +0 -1317
- package/extensions/dm-lens/dist/clients/runtime-turn.js +0 -2333
- package/extensions/dm-lens/dist/clients/rust-client.js +0 -48
- package/extensions/dm-lens/dist/clients/safe-spawn.js +0 -1267
- package/extensions/dm-lens/dist/clients/sanitize.js +0 -174
- package/extensions/dm-lens/dist/clients/scan-utils.js +0 -7
- package/extensions/dm-lens/dist/clients/scratch-tree-policy.js +0 -57
- package/extensions/dm-lens/dist/clients/search-read-registration.js +0 -59
- package/extensions/dm-lens/dist/clients/secret-findings.js +0 -69
- package/extensions/dm-lens/dist/clients/security-scan-client.js +0 -208
- package/extensions/dm-lens/dist/clients/session-event-guard.js +0 -111
- package/extensions/dm-lens/dist/clients/session-lifecycle.js +0 -173
- package/extensions/dm-lens/dist/clients/session-start-observability.js +0 -47
- package/extensions/dm-lens/dist/clients/session-state-store.js +0 -63
- package/extensions/dm-lens/dist/clients/session-summary.js +0 -8
- package/extensions/dm-lens/dist/clients/sessionstart-logger.js +0 -22
- package/extensions/dm-lens/dist/clients/sg-runner.js +0 -627
- package/extensions/dm-lens/dist/clients/sgconfig.js +0 -280
- package/extensions/dm-lens/dist/clients/shared-checkout-guard.js +0 -177
- package/extensions/dm-lens/dist/clients/single-flight.js +0 -52
- package/extensions/dm-lens/dist/clients/situational-tool-telemetry.js +0 -85
- package/extensions/dm-lens/dist/clients/skills-resolver.js +0 -38
- package/extensions/dm-lens/dist/clients/slow-fs.js +0 -77
- package/extensions/dm-lens/dist/clients/smells-rollup.js +0 -118
- package/extensions/dm-lens/dist/clients/source-filter.js +0 -320
- package/extensions/dm-lens/dist/clients/source-walker.js +0 -96
- package/extensions/dm-lens/dist/clients/spawn-output-cap.js +0 -35
- package/extensions/dm-lens/dist/clients/spawn-timeout-cooldown.js +0 -51
- package/extensions/dm-lens/dist/clients/stale-marker.js +0 -1
- package/extensions/dm-lens/dist/clients/startup-scan.js +0 -222
- package/extensions/dm-lens/dist/clients/startup-timing.js +0 -67
- package/extensions/dm-lens/dist/clients/string-utils.js +0 -6
- package/extensions/dm-lens/dist/clients/subagent-mode.js +0 -40
- package/extensions/dm-lens/dist/clients/symbol-containment.js +0 -16
- package/extensions/dm-lens/dist/clients/symbol-types.js +0 -1
- package/extensions/dm-lens/dist/clients/test-runner-client.js +0 -1504
- package/extensions/dm-lens/dist/clients/test-runner-delivery.js +0 -260
- package/extensions/dm-lens/dist/clients/todo-scanner.js +0 -164
- package/extensions/dm-lens/dist/clients/tool-agreement.js +0 -377
- package/extensions/dm-lens/dist/clients/tool-config.js +0 -218
- package/extensions/dm-lens/dist/clients/tool-cwd.js +0 -235
- package/extensions/dm-lens/dist/clients/tool-definition.js +0 -22
- package/extensions/dm-lens/dist/clients/tool-event.js +0 -18
- package/extensions/dm-lens/dist/clients/tool-policy.js +0 -2393
- package/extensions/dm-lens/dist/clients/tool-probe.js +0 -5
- package/extensions/dm-lens/dist/clients/tool-render.js +0 -79
- package/extensions/dm-lens/dist/clients/tool-set-policy.js +0 -70
- package/extensions/dm-lens/dist/clients/tree-sitter-cache.js +0 -206
- package/extensions/dm-lens/dist/clients/tree-sitter-client.js +0 -2787
- package/extensions/dm-lens/dist/clients/tree-sitter-logger.js +0 -77
- package/extensions/dm-lens/dist/clients/tree-sitter-navigator.js +0 -198
- package/extensions/dm-lens/dist/clients/tree-sitter-query-loader.js +0 -318
- package/extensions/dm-lens/dist/clients/tree-sitter-shared.js +0 -87
- package/extensions/dm-lens/dist/clients/tree-sitter-symbol-extractor.js +0 -957
- package/extensions/dm-lens/dist/clients/trivy-client.js +0 -305
- package/extensions/dm-lens/dist/clients/tui-fit.js +0 -26
- package/extensions/dm-lens/dist/clients/turn-context.js +0 -48
- package/extensions/dm-lens/dist/clients/turn-end/lane.js +0 -1
- package/extensions/dm-lens/dist/clients/turn-end/lanes/govulncheck.js +0 -54
- package/extensions/dm-lens/dist/clients/turn-end/lanes/secrets.js +0 -131
- package/extensions/dm-lens/dist/clients/turn-summary-render.js +0 -56
- package/extensions/dm-lens/dist/clients/turn-summary.js +0 -84
- package/extensions/dm-lens/dist/clients/typos-config.js +0 -9
- package/extensions/dm-lens/dist/clients/user-notify.js +0 -15
- package/extensions/dm-lens/dist/clients/vanished-instance-marker.js +0 -18
- package/extensions/dm-lens/dist/clients/warm-attach.js +0 -210
- package/extensions/dm-lens/dist/clients/widget-state.js +0 -963
- package/extensions/dm-lens/dist/clients/word-index-logger.js +0 -21
- package/extensions/dm-lens/dist/clients/word-index-store.js +0 -222
- package/extensions/dm-lens/dist/clients/word-index.js +0 -1426
- package/extensions/dm-lens/dist/clients/workspace-topology.js +0 -261
- package/extensions/dm-lens/dist/clients/write-ordering-guard.js +0 -24
- package/extensions/dm-lens/dist/clients/zizmor-config.js +0 -160
- package/extensions/dm-lens/dist/i18n.js +0 -67
- package/extensions/dm-lens/dist/index.js +0 -1782
- package/extensions/dm-lens/dist/mcp/analyze-cli.js +0 -178
- package/extensions/dm-lens/dist/mcp/build-staleness.js +0 -55
- package/extensions/dm-lens/dist/mcp/cli.js +0 -78
- package/extensions/dm-lens/dist/mcp/server.js +0 -1208
- package/extensions/dm-lens/dist/mcp/worker.js +0 -33
- package/extensions/dm-lens/dist/scripts/lib/process-scan.mjs +0 -256
- package/extensions/dm-lens/dist/scripts/lib/skills-predicate.mjs +0 -53
- package/extensions/dm-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +0 -45
- package/extensions/dm-lens/dist/tools/activate-tools.js +0 -61
- package/extensions/dm-lens/dist/tools/ast-grep-outline.js +0 -127
- package/extensions/dm-lens/dist/tools/ast-grep-replace.js +0 -226
- package/extensions/dm-lens/dist/tools/ast-grep-search.js +0 -687
- package/extensions/dm-lens/dist/tools/effective-config.js +0 -59
- package/extensions/dm-lens/dist/tools/lens-diagnostic-mark.js +0 -240
- package/extensions/dm-lens/dist/tools/lens-diagnostics.js +0 -1484
- package/extensions/dm-lens/dist/tools/lsp-diagnostics.js +0 -1088
- package/extensions/dm-lens/dist/tools/lsp-navigation.js +0 -1194
- package/extensions/dm-lens/dist/tools/lsp-structured-output.js +0 -277
- package/extensions/dm-lens/dist/tools/module-report.js +0 -332
- package/extensions/dm-lens/dist/tools/project-report.js +0 -72
- package/extensions/dm-lens/dist/tools/render-compact.js +0 -272
- package/extensions/dm-lens/dist/tools/scan-progress.js +0 -31
- package/extensions/dm-lens/dist/tools/shared.js +0 -26
- package/extensions/dm-lens/dist/tools/symbol-search.js +0 -133
- package/extensions/dm-mcp-adapter/dist/abort.js +0 -35
- package/extensions/dm-mcp-adapter/dist/agent-dir.js +0 -45
- package/extensions/dm-mcp-adapter/dist/agent-plugin-loader.js +0 -392
- package/extensions/dm-mcp-adapter/dist/agent-plugin-provenance.js +0 -26
- package/extensions/dm-mcp-adapter/dist/bearer-command-resolver.js +0 -182
- package/extensions/dm-mcp-adapter/dist/claude-plugin-loader.js +0 -350
- package/extensions/dm-mcp-adapter/dist/config.js +0 -1310
- package/extensions/dm-mcp-adapter/dist/consent-manager.js +0 -84
- package/extensions/dm-mcp-adapter/dist/elicitation-handler.js +0 -308
- package/extensions/dm-mcp-adapter/dist/errors.js +0 -225
- package/extensions/dm-mcp-adapter/dist/http-ca.js +0 -71
- package/extensions/dm-mcp-adapter/dist/jev-client.js +0 -359
- package/extensions/dm-mcp-adapter/dist/jev-contracts.js +0 -1
- package/extensions/dm-mcp-adapter/dist/jev-key-store.js +0 -176
- package/extensions/dm-mcp-adapter/dist/json-schema-validator.js +0 -70
- package/extensions/dm-mcp-adapter/dist/lifecycle.js +0 -387
- package/extensions/dm-mcp-adapter/dist/logger.js +0 -115
- package/extensions/dm-mcp-adapter/dist/mcp-auth-fetch.js +0 -86
- package/extensions/dm-mcp-adapter/dist/mcp-auth-flow.js +0 -949
- package/extensions/dm-mcp-adapter/dist/mcp-auth.js +0 -997
- package/extensions/dm-mcp-adapter/dist/mcp-bearer-store.js +0 -182
- package/extensions/dm-mcp-adapter/dist/mcp-callback-server.js +0 -381
- package/extensions/dm-mcp-adapter/dist/mcp-oauth-provider.js +0 -524
- package/extensions/dm-mcp-adapter/dist/mcp-probe.js +0 -151
- package/extensions/dm-mcp-adapter/dist/mcp-tasks.js +0 -300
- package/extensions/dm-mcp-adapter/dist/mcp-trace.js +0 -215
- package/extensions/dm-mcp-adapter/dist/metadata-cache.js +0 -267
- package/extensions/dm-mcp-adapter/dist/npx-resolver.js +0 -465
- package/extensions/dm-mcp-adapter/dist/package-mcp-loader.js +0 -135
- package/extensions/dm-mcp-adapter/dist/request-headers-command.js +0 -294
- package/extensions/dm-mcp-adapter/dist/resource-tools.js +0 -7
- package/extensions/dm-mcp-adapter/dist/runtime-owner.js +0 -94
- package/extensions/dm-mcp-adapter/dist/sampling-handler.js +0 -213
- package/extensions/dm-mcp-adapter/dist/secure-keyring.js +0 -191
- package/extensions/dm-mcp-adapter/dist/server-manager.js +0 -1514
- package/extensions/dm-mcp-adapter/dist/session-approvals.js +0 -127
- package/extensions/dm-mcp-adapter/dist/session-recovery.js +0 -82
- package/extensions/dm-mcp-adapter/dist/state.js +0 -1
- package/extensions/dm-mcp-adapter/dist/types.js +0 -286
- package/extensions/dm-mcp-adapter/dist/ui-app-bridge-helpers.js +0 -36
- package/extensions/dm-mcp-adapter/dist/ui-resource-handler.js +0 -204
- package/extensions/dm-mcp-adapter/dist/ui-stream-types.js +0 -58
- package/extensions/dm-mcp-adapter/dist/ui-tool-visibility.js +0 -26
- package/extensions/dm-mcp-adapter/dist/unix-socket-transport.js +0 -83
- package/extensions/dm-mcp-adapter/dist/utils.js +0 -382
|
@@ -1,3770 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { existsSync, statSync, unlinkSync } from "node:fs";
|
|
3
|
-
import fs from "node:fs/promises";
|
|
4
|
-
import https from "node:https";
|
|
5
|
-
import { createRequire } from "node:module";
|
|
6
|
-
import os from "node:os";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
import { writeFileAtomicAsync } from "../atomic-write.js";
|
|
9
|
-
import { BoundedLruCache } from "../bounded-cache.js";
|
|
10
|
-
import { logExtension } from "../extension-log.js";
|
|
11
|
-
import { isFullyQualified } from "../path-utils.js";
|
|
12
|
-
import { assertInstallAllowed, projectTrustDenialReason } from "../project-trust.js";
|
|
13
|
-
const _installerRequire = createRequire(import.meta.url);
|
|
14
|
-
import { createGunzip } from "node:zlib";
|
|
15
|
-
import { TRANSIENT_MAX_COOLDOWN_MS } from "../dispatch/runners/utils/availability-policy.js";
|
|
16
|
-
import { getDegradationLedgerGeneration, recordDegradationOnce } from "../degradation-ledger.js";
|
|
17
|
-
import { commitDurableStoreAsync } from "../durable-store.js";
|
|
18
|
-
import { getGlobalDmLensDir } from "../file-utils.js";
|
|
19
|
-
import { createGenerationMap } from "../generation-guard.js";
|
|
20
|
-
import { resolveToolCwd } from "../tool-cwd.js";
|
|
21
|
-
import { allAvailableGlobalBinDirs, installArgs, pmBinary, resolveNodePackageManager } from "../package-manager.js";
|
|
22
|
-
import { resetSafeSpawnWindowsCommandCache, safeSpawnAsync } from "../safe-spawn.js";
|
|
23
|
-
import { createBoundedOutputSink, DEFAULT_MAX_OUTPUT_BYTES } from "../spawn-output-cap.js";
|
|
24
|
-
import { probeToolAsync } from "../tool-probe.js";
|
|
25
|
-
import { logSessionStart } from "../sessionstart-logger.js";
|
|
26
|
-
import { resolveGitHubToken } from "../zizmor-config.js";
|
|
27
|
-
const TOOLS_DIR = path.join(getGlobalDmLensDir(), "tools");
|
|
28
|
-
const INSTALL_LOCK_PATH = path.join(TOOLS_DIR, ".install.lock");
|
|
29
|
-
const activeInstallLocks = new Set;
|
|
30
|
-
let installLockExitCleanupRegistered = false;
|
|
31
|
-
export function getManagedToolsDir() {
|
|
32
|
-
return TOOLS_DIR;
|
|
33
|
-
}
|
|
34
|
-
function isProcessAlive(pid) {
|
|
35
|
-
try {
|
|
36
|
-
process.kill(pid, 0);
|
|
37
|
-
return true;
|
|
38
|
-
} catch (error) {
|
|
39
|
-
return error.code === "EPERM";
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
async function acquireInstallLock() {
|
|
43
|
-
await fs.mkdir(TOOLS_DIR, { recursive: true });
|
|
44
|
-
const timeoutMs = Number(process.env.DM_LENS_INSTALL_LOCK_TIMEOUT_MS) || 150000;
|
|
45
|
-
const deadline = Date.now() + timeoutMs;
|
|
46
|
-
let lastOwner = "unknown owner";
|
|
47
|
-
while (Date.now() < deadline) {
|
|
48
|
-
try {
|
|
49
|
-
const handle = await fs.open(INSTALL_LOCK_PATH, "wx");
|
|
50
|
-
await handle.writeFile(JSON.stringify({ pid: process.pid, createdAt: Date.now() }));
|
|
51
|
-
await handle.close();
|
|
52
|
-
activeInstallLocks.add(INSTALL_LOCK_PATH);
|
|
53
|
-
if (!installLockExitCleanupRegistered) {
|
|
54
|
-
installLockExitCleanupRegistered = true;
|
|
55
|
-
process.once("exit", () => {
|
|
56
|
-
for (const lockPath of activeInstallLocks) {
|
|
57
|
-
try {
|
|
58
|
-
unlinkSync(lockPath);
|
|
59
|
-
} catch {}
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
let released = false;
|
|
64
|
-
return {
|
|
65
|
-
release: async () => {
|
|
66
|
-
if (released)
|
|
67
|
-
return;
|
|
68
|
-
released = true;
|
|
69
|
-
activeInstallLocks.delete(INSTALL_LOCK_PATH);
|
|
70
|
-
await fs.rm(INSTALL_LOCK_PATH, { force: true });
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
} catch (error) {
|
|
74
|
-
if (error.code !== "EEXIST")
|
|
75
|
-
throw error;
|
|
76
|
-
try {
|
|
77
|
-
const owner = JSON.parse(await fs.readFile(INSTALL_LOCK_PATH, "utf8"));
|
|
78
|
-
lastOwner = `pid=${owner.pid} createdAt=${owner.createdAt}`;
|
|
79
|
-
const maxAgeMs = (Number(process.env.DM_LENS_INSTALL_TIMEOUT_MS) || 120000) + 60000;
|
|
80
|
-
const expired = Number.isFinite(owner.createdAt) && Date.now() - owner.createdAt > maxAgeMs;
|
|
81
|
-
if (expired || Number.isInteger(owner.pid) && owner.pid > 0 && !isProcessAlive(owner.pid)) {
|
|
82
|
-
await fs.rm(INSTALL_LOCK_PATH, { force: true });
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
} catch (readError) {
|
|
86
|
-
lastOwner = "unreadable owner";
|
|
87
|
-
try {
|
|
88
|
-
const stat = await fs.stat(INSTALL_LOCK_PATH);
|
|
89
|
-
const maxAgeMs = (Number(process.env.DM_LENS_INSTALL_TIMEOUT_MS) || 120000) + 60000;
|
|
90
|
-
if (Date.now() - stat.mtimeMs > maxAgeMs) {
|
|
91
|
-
await fs.rm(INSTALL_LOCK_PATH, { force: true });
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
} catch {}
|
|
95
|
-
}
|
|
96
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
return {
|
|
100
|
-
reason: `timed out after ${timeoutMs}ms waiting for shared tools install lock (${lastOwner})`
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
const GITHUB_BIN_DIR = path.join(getGlobalDmLensDir(), "bin");
|
|
104
|
-
const DEBUG = process.env.DM_LENS_DEBUG === "1" || process.argv.includes("--debug");
|
|
105
|
-
function installerPlatform() {
|
|
106
|
-
const override = process.env.DM_LENS_TEST_PLATFORM;
|
|
107
|
-
if (override === "win32" || override === "linux") {
|
|
108
|
-
return override;
|
|
109
|
-
}
|
|
110
|
-
return process.platform;
|
|
111
|
-
}
|
|
112
|
-
function debugLog(...args) {
|
|
113
|
-
if (DEBUG) {
|
|
114
|
-
logExtension({
|
|
115
|
-
subsystem: "auto-install",
|
|
116
|
-
level: "debug",
|
|
117
|
-
message: args.map((arg) => typeof arg === "string" ? arg : JSON.stringify(arg)).join(" ")
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
function archAssetMatch(table) {
|
|
122
|
-
return (platform, arch) => {
|
|
123
|
-
if (arch !== "x64" && arch !== "arm64")
|
|
124
|
-
return;
|
|
125
|
-
return table[platform]?.[arch];
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
const OS_ARCH_ZIP_ASSETS = {
|
|
129
|
-
linux: { x64: "linux_amd64.zip", arm64: "linux_arm64.zip" },
|
|
130
|
-
darwin: { x64: "darwin_amd64.zip", arm64: "darwin_arm64.zip" },
|
|
131
|
-
win32: { x64: "windows_amd64.zip", arm64: "windows_arm64.zip" }
|
|
132
|
-
};
|
|
133
|
-
function managedPackageFormatterTool(spec) {
|
|
134
|
-
return {
|
|
135
|
-
id: spec.id,
|
|
136
|
-
name: spec.name,
|
|
137
|
-
checkCommand: spec.id,
|
|
138
|
-
checkArgs: ["--version"],
|
|
139
|
-
installStrategy: spec.installStrategy,
|
|
140
|
-
packageName: spec.packageName,
|
|
141
|
-
binaryName: spec.id
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
const MANAGED_PACKAGE_FORMATTERS = [
|
|
145
|
-
{ id: "black", name: "Black", installStrategy: "pip", packageName: "black" },
|
|
146
|
-
{
|
|
147
|
-
id: "cmake-format",
|
|
148
|
-
name: "cmake-format",
|
|
149
|
-
installStrategy: "pip",
|
|
150
|
-
packageName: "cmakelang[yaml]"
|
|
151
|
-
},
|
|
152
|
-
{ id: "oxfmt", name: "oxfmt", installStrategy: "npm", packageName: "oxfmt" }
|
|
153
|
-
];
|
|
154
|
-
function managedGitHubFormatterTool(spec) {
|
|
155
|
-
return {
|
|
156
|
-
id: spec.id,
|
|
157
|
-
name: spec.name,
|
|
158
|
-
checkCommand: spec.id,
|
|
159
|
-
checkArgs: ["--version"],
|
|
160
|
-
installStrategy: "github",
|
|
161
|
-
binaryName: spec.id,
|
|
162
|
-
github: {
|
|
163
|
-
repo: `${spec.owner}/${spec.repo}`,
|
|
164
|
-
assetMatch: spec.assetPattern,
|
|
165
|
-
...spec.binaryInArchive && { binaryInArchive: spec.binaryInArchive },
|
|
166
|
-
...spec.kind === "phar" && { launcher: "php" },
|
|
167
|
-
...spec.kind === "jar" && { launcher: "java" }
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
const MANAGED_GITHUB_FORMATTERS = [
|
|
172
|
-
{
|
|
173
|
-
id: "typstyle",
|
|
174
|
-
name: "typstyle",
|
|
175
|
-
owner: "typstyle-rs",
|
|
176
|
-
repo: "typstyle",
|
|
177
|
-
assetPattern: archAssetMatch({
|
|
178
|
-
linux: {
|
|
179
|
-
x64: "typstyle-x86_64-unknown-linux-gnu",
|
|
180
|
-
arm64: "typstyle-aarch64-unknown-linux-gnu"
|
|
181
|
-
},
|
|
182
|
-
darwin: {
|
|
183
|
-
x64: "typstyle-x86_64-apple-darwin",
|
|
184
|
-
arm64: "typstyle-aarch64-apple-darwin"
|
|
185
|
-
},
|
|
186
|
-
win32: {
|
|
187
|
-
x64: "typstyle-x86_64-pc-windows-msvc.exe",
|
|
188
|
-
arm64: "typstyle-aarch64-pc-windows-msvc.exe"
|
|
189
|
-
}
|
|
190
|
-
}),
|
|
191
|
-
kind: "binary"
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
id: "stylua",
|
|
195
|
-
name: "StyLua",
|
|
196
|
-
owner: "JohnnyMorganz",
|
|
197
|
-
repo: "StyLua",
|
|
198
|
-
assetPattern: archAssetMatch({
|
|
199
|
-
linux: { x64: "linux-x86_64.zip", arm64: "linux-aarch64.zip" },
|
|
200
|
-
darwin: { x64: "macos-x86_64.zip", arm64: "macos-aarch64.zip" },
|
|
201
|
-
win32: { x64: "windows-x86_64.zip" }
|
|
202
|
-
}),
|
|
203
|
-
kind: "binary",
|
|
204
|
-
binaryInArchive: "stylua"
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
id: "php-cs-fixer",
|
|
208
|
-
name: "PHP CS Fixer",
|
|
209
|
-
owner: "PHP-CS-Fixer",
|
|
210
|
-
repo: "PHP-CS-Fixer",
|
|
211
|
-
assetPattern: (platform) => platform === "linux" || platform === "darwin" || platform === "win32" ? "php-cs-fixer.phar" : undefined,
|
|
212
|
-
kind: "phar"
|
|
213
|
-
},
|
|
214
|
-
{
|
|
215
|
-
id: "cljfmt",
|
|
216
|
-
name: "cljfmt",
|
|
217
|
-
owner: "weavejester",
|
|
218
|
-
repo: "cljfmt",
|
|
219
|
-
assetPattern: (platform, arch) => {
|
|
220
|
-
if (platform === "linux")
|
|
221
|
-
return arch === "arm64" ? "standalone.jar" : "linux-amd64-static.tar.gz";
|
|
222
|
-
if (platform === "darwin")
|
|
223
|
-
return "standalone.jar";
|
|
224
|
-
if (platform === "win32")
|
|
225
|
-
return "win-amd64.zip";
|
|
226
|
-
return;
|
|
227
|
-
},
|
|
228
|
-
kind: "jar",
|
|
229
|
-
binaryInArchive: "cljfmt"
|
|
230
|
-
}
|
|
231
|
-
];
|
|
232
|
-
const MANAGED_MAVEN_FORMATTERS = [
|
|
233
|
-
{
|
|
234
|
-
id: "google-java-format",
|
|
235
|
-
name: "google-java-format",
|
|
236
|
-
groupId: "com.google.googlejavaformat",
|
|
237
|
-
artifactId: "google-java-format",
|
|
238
|
-
version: "1.27.0",
|
|
239
|
-
classifier: "all-deps"
|
|
240
|
-
}
|
|
241
|
-
];
|
|
242
|
-
function managedMavenFormatterTool(spec) {
|
|
243
|
-
return {
|
|
244
|
-
id: spec.id,
|
|
245
|
-
name: spec.name,
|
|
246
|
-
checkCommand: spec.id,
|
|
247
|
-
checkArgs: ["--version"],
|
|
248
|
-
installStrategy: "maven",
|
|
249
|
-
binaryName: spec.id,
|
|
250
|
-
maven: {
|
|
251
|
-
groupId: spec.groupId,
|
|
252
|
-
artifactId: spec.artifactId,
|
|
253
|
-
version: spec.version,
|
|
254
|
-
classifier: spec.classifier
|
|
255
|
-
}
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
export const TOOLS = [
|
|
259
|
-
{
|
|
260
|
-
id: "typescript-language-server",
|
|
261
|
-
name: "TypeScript Language Server",
|
|
262
|
-
checkCommand: "typescript-language-server",
|
|
263
|
-
checkArgs: ["--version"],
|
|
264
|
-
installStrategy: "npm",
|
|
265
|
-
packageName: "typescript-language-server@5.3.0",
|
|
266
|
-
binaryName: "typescript-language-server"
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
id: "typescript",
|
|
270
|
-
name: "TypeScript",
|
|
271
|
-
checkCommand: "tsc",
|
|
272
|
-
checkArgs: ["--version"],
|
|
273
|
-
installStrategy: "npm",
|
|
274
|
-
packageName: "typescript@5.9.3",
|
|
275
|
-
binaryName: "tsc"
|
|
276
|
-
},
|
|
277
|
-
{
|
|
278
|
-
id: "pyright",
|
|
279
|
-
name: "Pyright",
|
|
280
|
-
checkCommand: "pyright",
|
|
281
|
-
checkArgs: ["--version"],
|
|
282
|
-
installStrategy: "npm",
|
|
283
|
-
packageName: "pyright",
|
|
284
|
-
binaryName: "pyright"
|
|
285
|
-
},
|
|
286
|
-
{
|
|
287
|
-
id: "prettier",
|
|
288
|
-
name: "Prettier",
|
|
289
|
-
checkCommand: "prettier",
|
|
290
|
-
checkArgs: ["--version"],
|
|
291
|
-
installStrategy: "npm",
|
|
292
|
-
packageName: "prettier",
|
|
293
|
-
binaryName: "prettier"
|
|
294
|
-
},
|
|
295
|
-
...MANAGED_PACKAGE_FORMATTERS.map(managedPackageFormatterTool),
|
|
296
|
-
{
|
|
297
|
-
id: "ruff",
|
|
298
|
-
name: "Ruff",
|
|
299
|
-
checkCommand: "ruff",
|
|
300
|
-
checkArgs: ["--version"],
|
|
301
|
-
installStrategy: "pip",
|
|
302
|
-
packageName: "ruff",
|
|
303
|
-
binaryName: "ruff"
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
id: "jedi-language-server",
|
|
307
|
-
name: "Jedi Language Server",
|
|
308
|
-
checkCommand: "jedi-language-server",
|
|
309
|
-
checkArgs: ["--version"],
|
|
310
|
-
installStrategy: "pip",
|
|
311
|
-
packageName: "jedi-language-server",
|
|
312
|
-
binaryName: "jedi-language-server"
|
|
313
|
-
},
|
|
314
|
-
{
|
|
315
|
-
id: "biome",
|
|
316
|
-
name: "Biome",
|
|
317
|
-
checkCommand: "biome",
|
|
318
|
-
checkArgs: ["--version"],
|
|
319
|
-
installStrategy: "npm",
|
|
320
|
-
packageName: "@biomejs/biome",
|
|
321
|
-
binaryName: "biome",
|
|
322
|
-
platformPackage: {
|
|
323
|
-
base: "@biomejs/cli",
|
|
324
|
-
suffixes: {
|
|
325
|
-
"linux-x64": "linux-x64",
|
|
326
|
-
"linux-arm64": "linux-arm64",
|
|
327
|
-
"darwin-x64": "darwin-x64",
|
|
328
|
-
"darwin-arm64": "darwin-arm64",
|
|
329
|
-
"win32-x64": "win32-x64",
|
|
330
|
-
"win32-arm64": "win32-arm64"
|
|
331
|
-
},
|
|
332
|
-
binaries: ["biome"]
|
|
333
|
-
}
|
|
334
|
-
},
|
|
335
|
-
{
|
|
336
|
-
id: "madge",
|
|
337
|
-
name: "Madge",
|
|
338
|
-
checkCommand: "madge",
|
|
339
|
-
checkArgs: ["--version"],
|
|
340
|
-
installStrategy: "npm",
|
|
341
|
-
packageName: "madge",
|
|
342
|
-
binaryName: "madge"
|
|
343
|
-
},
|
|
344
|
-
{
|
|
345
|
-
id: "jscpd",
|
|
346
|
-
name: "jscpd",
|
|
347
|
-
checkCommand: "jscpd",
|
|
348
|
-
checkArgs: ["--version"],
|
|
349
|
-
installStrategy: "npm",
|
|
350
|
-
packageName: "jscpd@5.3.0",
|
|
351
|
-
binaryName: "jscpd"
|
|
352
|
-
},
|
|
353
|
-
{
|
|
354
|
-
id: "ast-grep",
|
|
355
|
-
name: "ast-grep CLI",
|
|
356
|
-
checkCommand: "ast-grep",
|
|
357
|
-
checkArgs: ["--version"],
|
|
358
|
-
installStrategy: "npm",
|
|
359
|
-
packageName: "@ast-grep/cli",
|
|
360
|
-
binaryName: "ast-grep",
|
|
361
|
-
platformPackage: {
|
|
362
|
-
suffixes: {
|
|
363
|
-
"linux-x64": "linux-x64-gnu",
|
|
364
|
-
"linux-arm64": "linux-arm64-gnu",
|
|
365
|
-
"darwin-x64": "darwin-x64",
|
|
366
|
-
"darwin-arm64": "darwin-arm64",
|
|
367
|
-
"win32-x64": "win32-x64-msvc",
|
|
368
|
-
"win32-arm64": "win32-arm64-msvc",
|
|
369
|
-
"win32-ia32": "win32-ia32-msvc"
|
|
370
|
-
},
|
|
371
|
-
binaries: ["ast-grep", "sg"]
|
|
372
|
-
}
|
|
373
|
-
},
|
|
374
|
-
{
|
|
375
|
-
id: "knip",
|
|
376
|
-
name: "Knip",
|
|
377
|
-
checkCommand: "knip",
|
|
378
|
-
checkArgs: ["--version"],
|
|
379
|
-
installStrategy: "npm",
|
|
380
|
-
packageName: "knip",
|
|
381
|
-
binaryName: "knip"
|
|
382
|
-
},
|
|
383
|
-
{
|
|
384
|
-
id: "yamllint",
|
|
385
|
-
name: "yamllint",
|
|
386
|
-
checkCommand: "yamllint",
|
|
387
|
-
checkArgs: ["--version"],
|
|
388
|
-
installStrategy: "pip",
|
|
389
|
-
packageName: "yamllint",
|
|
390
|
-
binaryName: "yamllint"
|
|
391
|
-
},
|
|
392
|
-
{
|
|
393
|
-
id: "sqlfluff",
|
|
394
|
-
name: "sqlfluff",
|
|
395
|
-
checkCommand: "sqlfluff",
|
|
396
|
-
checkArgs: ["--version"],
|
|
397
|
-
installStrategy: "pip",
|
|
398
|
-
packageName: "sqlfluff",
|
|
399
|
-
binaryName: "sqlfluff"
|
|
400
|
-
},
|
|
401
|
-
{
|
|
402
|
-
id: "bash-language-server",
|
|
403
|
-
name: "Bash Language Server",
|
|
404
|
-
checkCommand: "bash-language-server",
|
|
405
|
-
checkArgs: ["--version"],
|
|
406
|
-
verificationTimeoutMs: 20000,
|
|
407
|
-
installStrategy: "npm",
|
|
408
|
-
packageName: "bash-language-server",
|
|
409
|
-
binaryName: "bash-language-server"
|
|
410
|
-
},
|
|
411
|
-
{
|
|
412
|
-
id: "fish-lsp",
|
|
413
|
-
name: "Fish Language Server",
|
|
414
|
-
checkCommand: "fish-lsp",
|
|
415
|
-
checkArgs: ["--version"],
|
|
416
|
-
installStrategy: "npm",
|
|
417
|
-
packageName: "fish-lsp",
|
|
418
|
-
binaryName: "fish-lsp"
|
|
419
|
-
},
|
|
420
|
-
{
|
|
421
|
-
id: "cmake-language-server",
|
|
422
|
-
name: "CMake Language Server",
|
|
423
|
-
checkCommand: "cmake-language-server",
|
|
424
|
-
checkArgs: ["--version"],
|
|
425
|
-
installStrategy: "pip",
|
|
426
|
-
packageName: "cmake-language-server",
|
|
427
|
-
binaryName: "cmake-language-server",
|
|
428
|
-
pipConstraints: ["pygls<2"]
|
|
429
|
-
},
|
|
430
|
-
{
|
|
431
|
-
id: "yaml-language-server",
|
|
432
|
-
name: "YAML Language Server",
|
|
433
|
-
checkCommand: "yaml-language-server",
|
|
434
|
-
checkArgs: ["--version"],
|
|
435
|
-
installStrategy: "npm",
|
|
436
|
-
packageName: "yaml-language-server",
|
|
437
|
-
binaryName: "yaml-language-server"
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
id: "vscode-json-language-server",
|
|
441
|
-
name: "VSCode JSON Language Server",
|
|
442
|
-
checkCommand: "vscode-json-language-server",
|
|
443
|
-
checkArgs: ["--version"],
|
|
444
|
-
verificationTimeoutMs: 20000,
|
|
445
|
-
installStrategy: "npm",
|
|
446
|
-
packageName: "vscode-langservers-extracted",
|
|
447
|
-
binaryName: "vscode-json-language-server"
|
|
448
|
-
},
|
|
449
|
-
{
|
|
450
|
-
id: "vscode-html-languageserver-bin",
|
|
451
|
-
name: "VSCode HTML Language Server",
|
|
452
|
-
checkCommand: "vscode-html-language-server",
|
|
453
|
-
checkArgs: ["--version"],
|
|
454
|
-
installStrategy: "npm",
|
|
455
|
-
packageName: "vscode-langservers-extracted",
|
|
456
|
-
binaryName: "vscode-html-language-server"
|
|
457
|
-
},
|
|
458
|
-
{
|
|
459
|
-
id: "htmlhint",
|
|
460
|
-
name: "HTMLHint",
|
|
461
|
-
checkCommand: "htmlhint",
|
|
462
|
-
checkArgs: ["--version"],
|
|
463
|
-
installStrategy: "npm",
|
|
464
|
-
packageName: "htmlhint",
|
|
465
|
-
binaryName: "htmlhint"
|
|
466
|
-
},
|
|
467
|
-
{
|
|
468
|
-
id: "hadolint",
|
|
469
|
-
name: "Hadolint",
|
|
470
|
-
checkCommand: "hadolint",
|
|
471
|
-
checkArgs: ["--version"],
|
|
472
|
-
installStrategy: "github",
|
|
473
|
-
binaryName: "hadolint",
|
|
474
|
-
github: {
|
|
475
|
-
repo: "hadolint/hadolint",
|
|
476
|
-
assetMatch: (platform, arch) => {
|
|
477
|
-
if (platform === "linux")
|
|
478
|
-
return arch === "arm64" ? "linux.aarch64" : "linux.x86_64";
|
|
479
|
-
if (platform === "darwin")
|
|
480
|
-
return arch === "arm64" ? "macos-arm64" : "macos-x86_64";
|
|
481
|
-
if (platform === "win32")
|
|
482
|
-
return "windows-x86_64.exe";
|
|
483
|
-
return;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
},
|
|
487
|
-
{
|
|
488
|
-
id: "helm",
|
|
489
|
-
name: "Helm",
|
|
490
|
-
checkCommand: "helm",
|
|
491
|
-
checkArgs: ["--version"],
|
|
492
|
-
installStrategy: "github",
|
|
493
|
-
binaryName: "helm",
|
|
494
|
-
github: {
|
|
495
|
-
repo: "helm/helm",
|
|
496
|
-
assetMatch: (platform, arch) => {
|
|
497
|
-
const cpu = arch === "arm64" ? "arm64" : "amd64";
|
|
498
|
-
if (platform === "linux")
|
|
499
|
-
return `linux-${cpu}.tar.gz`;
|
|
500
|
-
if (platform === "darwin")
|
|
501
|
-
return `darwin-${cpu}.tar.gz`;
|
|
502
|
-
if (platform === "win32")
|
|
503
|
-
return `windows-${cpu}.zip`;
|
|
504
|
-
return;
|
|
505
|
-
},
|
|
506
|
-
binaryInArchive: "helm"
|
|
507
|
-
}
|
|
508
|
-
},
|
|
509
|
-
{
|
|
510
|
-
id: "opengrep",
|
|
511
|
-
name: "Opengrep",
|
|
512
|
-
checkCommand: "opengrep",
|
|
513
|
-
checkArgs: ["--version"],
|
|
514
|
-
installStrategy: "github",
|
|
515
|
-
binaryName: "opengrep",
|
|
516
|
-
github: {
|
|
517
|
-
repo: "opengrep/opengrep",
|
|
518
|
-
assetMatch: (platform, arch) => {
|
|
519
|
-
if (platform === "linux")
|
|
520
|
-
return arch === "arm64" ? "opengrep_manylinux_aarch64" : "opengrep_manylinux_x86";
|
|
521
|
-
if (platform === "darwin")
|
|
522
|
-
return arch === "arm64" ? "opengrep_osx_arm64" : "opengrep_osx_x86";
|
|
523
|
-
if (platform === "win32")
|
|
524
|
-
return "opengrep_windows_x86.exe";
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
},
|
|
529
|
-
{
|
|
530
|
-
id: "vscode-css-languageserver",
|
|
531
|
-
name: "VSCode CSS Language Server",
|
|
532
|
-
checkCommand: "vscode-css-language-server",
|
|
533
|
-
checkArgs: ["--version"],
|
|
534
|
-
installStrategy: "npm",
|
|
535
|
-
packageName: "vscode-langservers-extracted",
|
|
536
|
-
binaryName: "vscode-css-language-server"
|
|
537
|
-
},
|
|
538
|
-
{
|
|
539
|
-
id: "dockerfile-language-server-nodejs",
|
|
540
|
-
name: "Dockerfile Language Server",
|
|
541
|
-
checkCommand: "docker-langserver",
|
|
542
|
-
checkArgs: ["--version"],
|
|
543
|
-
installStrategy: "npm",
|
|
544
|
-
packageName: "dockerfile-language-server-nodejs",
|
|
545
|
-
binaryName: "docker-langserver"
|
|
546
|
-
},
|
|
547
|
-
{
|
|
548
|
-
id: "intelephense",
|
|
549
|
-
name: "Intelephense",
|
|
550
|
-
checkCommand: "intelephense",
|
|
551
|
-
checkArgs: ["--version"],
|
|
552
|
-
verification: "package-entry",
|
|
553
|
-
installStrategy: "npm",
|
|
554
|
-
packageName: "intelephense",
|
|
555
|
-
binaryName: "intelephense"
|
|
556
|
-
},
|
|
557
|
-
{
|
|
558
|
-
id: "@prisma/language-server",
|
|
559
|
-
name: "Prisma Language Server",
|
|
560
|
-
checkCommand: "prisma-language-server",
|
|
561
|
-
checkArgs: ["--version"],
|
|
562
|
-
verificationTimeoutMs: 40000,
|
|
563
|
-
installStrategy: "npm",
|
|
564
|
-
packageName: "@prisma/language-server",
|
|
565
|
-
binaryName: "prisma-language-server"
|
|
566
|
-
},
|
|
567
|
-
{
|
|
568
|
-
id: "@vue/language-server",
|
|
569
|
-
name: "Vue Language Server",
|
|
570
|
-
checkCommand: "vue-language-server",
|
|
571
|
-
checkArgs: ["--version"],
|
|
572
|
-
verificationTimeoutMs: 30000,
|
|
573
|
-
installStrategy: "npm",
|
|
574
|
-
packageName: "@vue/language-server",
|
|
575
|
-
binaryName: "vue-language-server"
|
|
576
|
-
},
|
|
577
|
-
{
|
|
578
|
-
id: "svelte-language-server",
|
|
579
|
-
name: "Svelte Language Server",
|
|
580
|
-
checkCommand: "svelteserver",
|
|
581
|
-
checkArgs: ["--version"],
|
|
582
|
-
verificationTimeoutMs: 20000,
|
|
583
|
-
installStrategy: "npm",
|
|
584
|
-
packageName: "svelte-language-server",
|
|
585
|
-
binaryName: "svelteserver"
|
|
586
|
-
},
|
|
587
|
-
{
|
|
588
|
-
id: "markdownlint",
|
|
589
|
-
name: "markdownlint-cli2",
|
|
590
|
-
checkCommand: "markdownlint-cli2",
|
|
591
|
-
checkArgs: ["--no-globs", "-"],
|
|
592
|
-
installStrategy: "npm",
|
|
593
|
-
packageName: "markdownlint-cli2",
|
|
594
|
-
binaryName: "markdownlint-cli2"
|
|
595
|
-
},
|
|
596
|
-
{
|
|
597
|
-
id: "mypy",
|
|
598
|
-
name: "mypy",
|
|
599
|
-
checkCommand: "mypy",
|
|
600
|
-
checkArgs: ["--version"],
|
|
601
|
-
installStrategy: "pip",
|
|
602
|
-
packageName: "mypy",
|
|
603
|
-
binaryName: "mypy"
|
|
604
|
-
},
|
|
605
|
-
{
|
|
606
|
-
id: "rubocop",
|
|
607
|
-
name: "RuboCop",
|
|
608
|
-
checkCommand: "rubocop",
|
|
609
|
-
checkArgs: ["--version"],
|
|
610
|
-
installStrategy: "gem",
|
|
611
|
-
packageName: "rubocop",
|
|
612
|
-
binaryName: "rubocop"
|
|
613
|
-
},
|
|
614
|
-
{
|
|
615
|
-
id: "stylelint",
|
|
616
|
-
name: "Stylelint",
|
|
617
|
-
checkCommand: "stylelint",
|
|
618
|
-
checkArgs: ["--version"],
|
|
619
|
-
installStrategy: "npm",
|
|
620
|
-
packageName: "stylelint",
|
|
621
|
-
binaryName: "stylelint"
|
|
622
|
-
},
|
|
623
|
-
{
|
|
624
|
-
id: "oxlint",
|
|
625
|
-
name: "Oxlint",
|
|
626
|
-
checkCommand: "oxlint",
|
|
627
|
-
checkArgs: ["--version"],
|
|
628
|
-
installStrategy: "npm",
|
|
629
|
-
packageName: "oxlint",
|
|
630
|
-
binaryName: "oxlint"
|
|
631
|
-
},
|
|
632
|
-
{
|
|
633
|
-
id: "shellcheck",
|
|
634
|
-
name: "ShellCheck",
|
|
635
|
-
checkCommand: "shellcheck",
|
|
636
|
-
checkArgs: ["--version"],
|
|
637
|
-
installStrategy: "github",
|
|
638
|
-
binaryName: "shellcheck",
|
|
639
|
-
github: {
|
|
640
|
-
repo: "koalaman/shellcheck",
|
|
641
|
-
assetMatch: (platform, arch) => {
|
|
642
|
-
if (platform === "linux")
|
|
643
|
-
return arch === "arm64" ? "linux.aarch64.tar.xz" : "linux.x86_64.tar.xz";
|
|
644
|
-
if (platform === "darwin")
|
|
645
|
-
return arch === "arm64" ? "darwin.aarch64.tar.xz" : "darwin.x86_64.tar.xz";
|
|
646
|
-
if (platform === "win32")
|
|
647
|
-
return "zip";
|
|
648
|
-
return;
|
|
649
|
-
},
|
|
650
|
-
binaryInArchive: "shellcheck"
|
|
651
|
-
}
|
|
652
|
-
},
|
|
653
|
-
{
|
|
654
|
-
id: "shfmt",
|
|
655
|
-
name: "shfmt",
|
|
656
|
-
checkCommand: "shfmt",
|
|
657
|
-
checkArgs: ["--version"],
|
|
658
|
-
installStrategy: "github",
|
|
659
|
-
binaryName: "shfmt",
|
|
660
|
-
github: {
|
|
661
|
-
repo: "mvdan/sh",
|
|
662
|
-
assetMatch: (platform, arch) => {
|
|
663
|
-
if (platform === "linux")
|
|
664
|
-
return arch === "arm64" ? "linux_arm64" : "linux_amd64";
|
|
665
|
-
if (platform === "darwin")
|
|
666
|
-
return arch === "arm64" ? "darwin_arm64" : "darwin_amd64";
|
|
667
|
-
if (platform === "win32")
|
|
668
|
-
return arch === "arm64" ? "windows_arm64.exe" : "windows_amd64.exe";
|
|
669
|
-
return;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
},
|
|
673
|
-
...MANAGED_GITHUB_FORMATTERS.map(managedGitHubFormatterTool),
|
|
674
|
-
...MANAGED_MAVEN_FORMATTERS.map(managedMavenFormatterTool),
|
|
675
|
-
{
|
|
676
|
-
id: "rust-analyzer",
|
|
677
|
-
name: "rust-analyzer",
|
|
678
|
-
checkCommand: "rust-analyzer",
|
|
679
|
-
checkArgs: ["--version"],
|
|
680
|
-
installStrategy: "github",
|
|
681
|
-
binaryName: "rust-analyzer",
|
|
682
|
-
github: {
|
|
683
|
-
repo: "rust-lang/rust-analyzer",
|
|
684
|
-
assetMatch: (platform, arch) => {
|
|
685
|
-
if (platform === "linux")
|
|
686
|
-
return arch === "arm64" ? "aarch64-unknown-linux-gnu.gz" : "x86_64-unknown-linux-gnu.gz";
|
|
687
|
-
if (platform === "darwin")
|
|
688
|
-
return arch === "arm64" ? "aarch64-apple-darwin.gz" : "x86_64-apple-darwin.gz";
|
|
689
|
-
if (platform === "win32")
|
|
690
|
-
return "x86_64-pc-windows-msvc.zip";
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
},
|
|
695
|
-
{
|
|
696
|
-
id: "deno",
|
|
697
|
-
name: "Deno",
|
|
698
|
-
checkCommand: "deno",
|
|
699
|
-
checkArgs: ["--version"],
|
|
700
|
-
installStrategy: "github",
|
|
701
|
-
binaryName: "deno",
|
|
702
|
-
github: {
|
|
703
|
-
repo: "denoland/deno",
|
|
704
|
-
assetMatch: (platform, arch) => {
|
|
705
|
-
if (platform === "linux")
|
|
706
|
-
return arch === "arm64" ? "deno-aarch64-unknown-linux-gnu.zip" : "deno-x86_64-unknown-linux-gnu.zip";
|
|
707
|
-
if (platform === "darwin")
|
|
708
|
-
return arch === "arm64" ? "deno-aarch64-apple-darwin.zip" : "deno-x86_64-apple-darwin.zip";
|
|
709
|
-
if (platform === "win32")
|
|
710
|
-
return "deno-x86_64-pc-windows-msvc.zip";
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
},
|
|
715
|
-
{
|
|
716
|
-
id: "golangci-lint",
|
|
717
|
-
name: "golangci-lint",
|
|
718
|
-
checkCommand: "golangci-lint",
|
|
719
|
-
checkArgs: ["--version"],
|
|
720
|
-
installStrategy: "github",
|
|
721
|
-
binaryName: "golangci-lint",
|
|
722
|
-
github: {
|
|
723
|
-
repo: "golangci/golangci-lint",
|
|
724
|
-
assetMatch: (platform, arch) => {
|
|
725
|
-
if (platform === "linux")
|
|
726
|
-
return arch === "arm64" ? "linux-arm64.tar.gz" : "linux-amd64.tar.gz";
|
|
727
|
-
if (platform === "darwin")
|
|
728
|
-
return arch === "arm64" ? "darwin-arm64.tar.gz" : "darwin-amd64.tar.gz";
|
|
729
|
-
if (platform === "win32")
|
|
730
|
-
return arch === "arm64" ? "windows-arm64.zip" : "windows-amd64.zip";
|
|
731
|
-
return;
|
|
732
|
-
},
|
|
733
|
-
binaryInArchive: "golangci-lint"
|
|
734
|
-
}
|
|
735
|
-
},
|
|
736
|
-
{
|
|
737
|
-
id: "ktlint",
|
|
738
|
-
name: "ktlint",
|
|
739
|
-
checkCommand: "ktlint",
|
|
740
|
-
checkArgs: ["--version"],
|
|
741
|
-
installStrategy: "github",
|
|
742
|
-
binaryName: "ktlint",
|
|
743
|
-
github: {
|
|
744
|
-
repo: "pinterest/ktlint",
|
|
745
|
-
assetMatch: (platform, _arch) => {
|
|
746
|
-
if (platform === "linux")
|
|
747
|
-
return "ktlint";
|
|
748
|
-
if (platform === "darwin")
|
|
749
|
-
return "ktlint";
|
|
750
|
-
if (platform === "win32")
|
|
751
|
-
return "ktlint.bat";
|
|
752
|
-
return;
|
|
753
|
-
},
|
|
754
|
-
extraAssets: (platform) => platform === "win32" ? ["ktlint"] : []
|
|
755
|
-
}
|
|
756
|
-
},
|
|
757
|
-
{
|
|
758
|
-
id: "ktfmt",
|
|
759
|
-
name: "ktfmt",
|
|
760
|
-
checkCommand: "ktfmt",
|
|
761
|
-
checkArgs: ["--version"],
|
|
762
|
-
installStrategy: "maven",
|
|
763
|
-
binaryName: "ktfmt",
|
|
764
|
-
maven: {
|
|
765
|
-
groupId: "com.facebook",
|
|
766
|
-
artifactId: "ktfmt",
|
|
767
|
-
version: "0.63",
|
|
768
|
-
classifier: "with-dependencies"
|
|
769
|
-
}
|
|
770
|
-
},
|
|
771
|
-
{
|
|
772
|
-
id: "spotbugs",
|
|
773
|
-
name: "SpotBugs",
|
|
774
|
-
checkCommand: "spotbugs",
|
|
775
|
-
checkArgs: ["--version"],
|
|
776
|
-
installStrategy: "archive",
|
|
777
|
-
binaryName: "spotbugs",
|
|
778
|
-
archive: {
|
|
779
|
-
url: "https://github.com/spotbugs/spotbugs/releases/download/4.10.2/spotbugs-4.10.2.tgz",
|
|
780
|
-
kind: "tgz",
|
|
781
|
-
launcher: "bin/spotbugs"
|
|
782
|
-
}
|
|
783
|
-
},
|
|
784
|
-
{
|
|
785
|
-
id: "powershell-editor-services",
|
|
786
|
-
name: "PowerShell Editor Services",
|
|
787
|
-
checkCommand: "pwsh",
|
|
788
|
-
checkArgs: [
|
|
789
|
-
"-NoProfile",
|
|
790
|
-
"-Command",
|
|
791
|
-
"$PSVersionTable.PSVersion.ToString()"
|
|
792
|
-
],
|
|
793
|
-
installStrategy: "archive",
|
|
794
|
-
binaryName: "powershell-editor-services",
|
|
795
|
-
archive: {
|
|
796
|
-
url: "https://github.com/PowerShell/PowerShellEditorServices/releases/download/v4.6.0/PowerShellEditorServices.zip",
|
|
797
|
-
kind: "zip",
|
|
798
|
-
stripComponents: 0,
|
|
799
|
-
treeMarker: "PowerShellEditorServices/Start-EditorServices.ps1"
|
|
800
|
-
}
|
|
801
|
-
},
|
|
802
|
-
{
|
|
803
|
-
id: "clangd",
|
|
804
|
-
name: "clangd",
|
|
805
|
-
checkCommand: "clangd",
|
|
806
|
-
checkArgs: ["--version"],
|
|
807
|
-
installStrategy: "archive",
|
|
808
|
-
binaryName: "clangd",
|
|
809
|
-
archive: {
|
|
810
|
-
url: (platform, arch) => {
|
|
811
|
-
const version = "22.1.0";
|
|
812
|
-
const base = `https://github.com/clangd/clangd/releases/download/${version}`;
|
|
813
|
-
if (platform === "linux")
|
|
814
|
-
return arch === "x64" ? `${base}/clangd-linux-${version}.zip` : undefined;
|
|
815
|
-
if (platform === "darwin")
|
|
816
|
-
return `${base}/clangd-mac-${version}.zip`;
|
|
817
|
-
if (platform === "win32")
|
|
818
|
-
return `${base}/clangd-windows-${version}.zip`;
|
|
819
|
-
return;
|
|
820
|
-
},
|
|
821
|
-
kind: "zip",
|
|
822
|
-
stripComponents: 1,
|
|
823
|
-
treeMarker: "bin"
|
|
824
|
-
}
|
|
825
|
-
},
|
|
826
|
-
{
|
|
827
|
-
id: "lua-language-server",
|
|
828
|
-
name: "lua-language-server",
|
|
829
|
-
checkCommand: "lua-language-server",
|
|
830
|
-
checkArgs: ["--version"],
|
|
831
|
-
installStrategy: "archive",
|
|
832
|
-
binaryName: "lua-language-server",
|
|
833
|
-
archive: {
|
|
834
|
-
url: (platform, arch) => {
|
|
835
|
-
const version = "3.18.2";
|
|
836
|
-
const base = `https://github.com/LuaLS/lua-language-server/releases/download/${version}`;
|
|
837
|
-
if (platform === "linux")
|
|
838
|
-
return arch === "arm64" ? `${base}/lua-language-server-${version}-linux-arm64.tar.gz` : `${base}/lua-language-server-${version}-linux-x64.tar.gz`;
|
|
839
|
-
if (platform === "darwin")
|
|
840
|
-
return arch === "arm64" ? `${base}/lua-language-server-${version}-darwin-arm64.tar.gz` : `${base}/lua-language-server-${version}-darwin-x64.tar.gz`;
|
|
841
|
-
if (platform === "win32")
|
|
842
|
-
return arch === "arm64" ? undefined : `${base}/lua-language-server-${version}-win32-x64.zip`;
|
|
843
|
-
return;
|
|
844
|
-
},
|
|
845
|
-
kind: (platform) => platform === "win32" ? "zip" : "tgz",
|
|
846
|
-
stripComponents: 0,
|
|
847
|
-
treeMarker: "bin"
|
|
848
|
-
}
|
|
849
|
-
},
|
|
850
|
-
{
|
|
851
|
-
id: "actionlint",
|
|
852
|
-
name: "actionlint",
|
|
853
|
-
checkCommand: "actionlint",
|
|
854
|
-
checkArgs: ["--version"],
|
|
855
|
-
installStrategy: "github",
|
|
856
|
-
binaryName: "actionlint",
|
|
857
|
-
github: {
|
|
858
|
-
repo: "rhysd/actionlint",
|
|
859
|
-
assetMatch: (platform, arch) => {
|
|
860
|
-
if (platform === "linux")
|
|
861
|
-
return arch === "arm64" ? "linux_arm64.tar.gz" : "linux_amd64.tar.gz";
|
|
862
|
-
if (platform === "darwin")
|
|
863
|
-
return arch === "arm64" ? "darwin_arm64.tar.gz" : "darwin_amd64.tar.gz";
|
|
864
|
-
if (platform === "win32")
|
|
865
|
-
return arch === "arm64" ? "windows_arm64.zip" : "windows_amd64.zip";
|
|
866
|
-
return;
|
|
867
|
-
},
|
|
868
|
-
binaryInArchive: "actionlint"
|
|
869
|
-
}
|
|
870
|
-
},
|
|
871
|
-
{
|
|
872
|
-
id: "zizmor",
|
|
873
|
-
name: "zizmor",
|
|
874
|
-
checkCommand: "zizmor",
|
|
875
|
-
checkArgs: ["--version"],
|
|
876
|
-
installStrategy: "github",
|
|
877
|
-
binaryName: "zizmor",
|
|
878
|
-
github: {
|
|
879
|
-
repo: "zizmorcore/zizmor",
|
|
880
|
-
assetMatch: (platform, arch) => {
|
|
881
|
-
if (platform === "linux")
|
|
882
|
-
return arch === "arm64" ? "aarch64-unknown-linux-gnu.tar.gz" : "x86_64-unknown-linux-gnu.tar.gz";
|
|
883
|
-
if (platform === "darwin")
|
|
884
|
-
return arch === "arm64" ? "aarch64-apple-darwin.tar.gz" : "x86_64-apple-darwin.tar.gz";
|
|
885
|
-
if (platform === "win32")
|
|
886
|
-
return "x86_64-pc-windows-msvc.zip";
|
|
887
|
-
return;
|
|
888
|
-
},
|
|
889
|
-
binaryInArchive: "zizmor"
|
|
890
|
-
}
|
|
891
|
-
},
|
|
892
|
-
{
|
|
893
|
-
id: "typos-lsp",
|
|
894
|
-
name: "typos-lsp",
|
|
895
|
-
checkCommand: "typos-lsp",
|
|
896
|
-
checkArgs: ["--version"],
|
|
897
|
-
installStrategy: "github",
|
|
898
|
-
binaryName: "typos-lsp",
|
|
899
|
-
github: {
|
|
900
|
-
repo: "tekumara/typos-lsp",
|
|
901
|
-
assetMatch: (platform, arch) => {
|
|
902
|
-
if (platform === "linux")
|
|
903
|
-
return arch === "arm64" ? "aarch64-unknown-linux-gnu.tar.gz" : "x86_64-unknown-linux-gnu.tar.gz";
|
|
904
|
-
if (platform === "darwin")
|
|
905
|
-
return arch === "arm64" ? "aarch64-apple-darwin.tar.gz" : "x86_64-apple-darwin.tar.gz";
|
|
906
|
-
if (platform === "win32")
|
|
907
|
-
return arch === "arm64" ? "aarch64-pc-windows-msvc.zip" : "x86_64-pc-windows-msvc.zip";
|
|
908
|
-
return;
|
|
909
|
-
},
|
|
910
|
-
binaryInArchive: "typos-lsp"
|
|
911
|
-
}
|
|
912
|
-
},
|
|
913
|
-
{
|
|
914
|
-
id: "tflint",
|
|
915
|
-
name: "tflint",
|
|
916
|
-
checkCommand: "tflint",
|
|
917
|
-
checkArgs: ["--version"],
|
|
918
|
-
installStrategy: "github",
|
|
919
|
-
binaryName: "tflint",
|
|
920
|
-
github: {
|
|
921
|
-
repo: "terraform-linters/tflint",
|
|
922
|
-
assetMatch: archAssetMatch(OS_ARCH_ZIP_ASSETS),
|
|
923
|
-
binaryInArchive: "tflint"
|
|
924
|
-
}
|
|
925
|
-
},
|
|
926
|
-
{
|
|
927
|
-
id: "terragrunt",
|
|
928
|
-
name: "terragrunt",
|
|
929
|
-
checkCommand: "terragrunt",
|
|
930
|
-
checkArgs: ["--version"],
|
|
931
|
-
installStrategy: "github",
|
|
932
|
-
binaryName: "terragrunt",
|
|
933
|
-
github: {
|
|
934
|
-
repo: "gruntwork-io/terragrunt",
|
|
935
|
-
assetMatch: archAssetMatch({
|
|
936
|
-
linux: {
|
|
937
|
-
x64: "terragrunt_linux_amd64",
|
|
938
|
-
arm64: "terragrunt_linux_arm64"
|
|
939
|
-
},
|
|
940
|
-
darwin: {
|
|
941
|
-
x64: "terragrunt_darwin_amd64",
|
|
942
|
-
arm64: "terragrunt_darwin_arm64"
|
|
943
|
-
},
|
|
944
|
-
win32: {
|
|
945
|
-
x64: "terragrunt_windows_amd64.exe",
|
|
946
|
-
arm64: "terragrunt_windows_amd64.exe"
|
|
947
|
-
}
|
|
948
|
-
})
|
|
949
|
-
}
|
|
950
|
-
},
|
|
951
|
-
{
|
|
952
|
-
id: "gitleaks",
|
|
953
|
-
name: "gitleaks",
|
|
954
|
-
checkCommand: "gitleaks",
|
|
955
|
-
checkArgs: ["--version"],
|
|
956
|
-
installStrategy: "github",
|
|
957
|
-
binaryName: "gitleaks",
|
|
958
|
-
github: {
|
|
959
|
-
repo: "gitleaks/gitleaks",
|
|
960
|
-
assetMatch: archAssetMatch({
|
|
961
|
-
linux: { x64: "linux_x64.tar.gz", arm64: "linux_arm64.tar.gz" },
|
|
962
|
-
darwin: { x64: "darwin_x64.tar.gz", arm64: "darwin_arm64.tar.gz" },
|
|
963
|
-
win32: { x64: "windows_x64.zip", arm64: "windows_arm64.zip" }
|
|
964
|
-
}),
|
|
965
|
-
binaryInArchive: "gitleaks"
|
|
966
|
-
}
|
|
967
|
-
},
|
|
968
|
-
{
|
|
969
|
-
id: "trivy",
|
|
970
|
-
name: "Trivy",
|
|
971
|
-
checkCommand: "trivy",
|
|
972
|
-
checkArgs: ["--version"],
|
|
973
|
-
installStrategy: "github",
|
|
974
|
-
binaryName: "trivy",
|
|
975
|
-
github: {
|
|
976
|
-
repo: "aquasecurity/trivy",
|
|
977
|
-
assetMatch: archAssetMatch({
|
|
978
|
-
linux: { x64: "Linux-64bit.tar.gz", arm64: "Linux-ARM64.tar.gz" },
|
|
979
|
-
darwin: { x64: "macOS-64bit.tar.gz", arm64: "macOS-ARM64.tar.gz" },
|
|
980
|
-
win32: { x64: "windows-64bit.zip" }
|
|
981
|
-
}),
|
|
982
|
-
binaryInArchive: "trivy"
|
|
983
|
-
}
|
|
984
|
-
},
|
|
985
|
-
{
|
|
986
|
-
id: "swiftlint",
|
|
987
|
-
name: "SwiftLint",
|
|
988
|
-
checkCommand: "swiftlint",
|
|
989
|
-
checkArgs: ["--version"],
|
|
990
|
-
installStrategy: "github",
|
|
991
|
-
binaryName: "swiftlint",
|
|
992
|
-
github: {
|
|
993
|
-
repo: "realm/SwiftLint",
|
|
994
|
-
assetMatch: (platform, arch) => {
|
|
995
|
-
if (platform === "darwin")
|
|
996
|
-
return "portable_swiftlint.zip";
|
|
997
|
-
if (platform === "linux")
|
|
998
|
-
return arch === "arm64" ? "swiftlint_linux_arm64.zip" : "swiftlint_linux_amd64.zip";
|
|
999
|
-
return;
|
|
1000
|
-
},
|
|
1001
|
-
binaryInArchive: "swiftlint"
|
|
1002
|
-
}
|
|
1003
|
-
},
|
|
1004
|
-
{
|
|
1005
|
-
id: "taplo",
|
|
1006
|
-
name: "taplo",
|
|
1007
|
-
checkCommand: "taplo",
|
|
1008
|
-
checkArgs: ["--version"],
|
|
1009
|
-
installStrategy: "github",
|
|
1010
|
-
binaryName: "taplo",
|
|
1011
|
-
github: {
|
|
1012
|
-
repo: "tamasfe/taplo",
|
|
1013
|
-
assetMatch: (platform, arch) => {
|
|
1014
|
-
if (platform === "linux")
|
|
1015
|
-
return arch === "arm64" ? "taplo-linux-aarch64.gz" : "taplo-linux-x86_64.gz";
|
|
1016
|
-
if (platform === "darwin")
|
|
1017
|
-
return arch === "arm64" ? "taplo-darwin-aarch64.gz" : "taplo-darwin-x86_64.gz";
|
|
1018
|
-
if (platform === "win32")
|
|
1019
|
-
return "taplo-windows-x86_64.gz";
|
|
1020
|
-
return;
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
},
|
|
1024
|
-
{
|
|
1025
|
-
id: "vale",
|
|
1026
|
-
name: "Vale",
|
|
1027
|
-
checkCommand: "vale",
|
|
1028
|
-
checkArgs: ["--version"],
|
|
1029
|
-
installStrategy: "github",
|
|
1030
|
-
binaryName: "vale",
|
|
1031
|
-
github: {
|
|
1032
|
-
repo: "vale-cli/vale",
|
|
1033
|
-
assetMatch: (platform, arch) => {
|
|
1034
|
-
const version = "3.14.2";
|
|
1035
|
-
if (platform === "linux")
|
|
1036
|
-
return arch === "arm64" ? `vale_${version}_Linux_arm64.tar.gz` : `vale_${version}_Linux_64-bit.tar.gz`;
|
|
1037
|
-
if (platform === "darwin")
|
|
1038
|
-
return arch === "arm64" ? `vale_${version}_macOS_arm64.tar.gz` : `vale_${version}_macOS_64-bit.tar.gz`;
|
|
1039
|
-
if (platform === "win32")
|
|
1040
|
-
return `vale_${version}_Windows_64-bit.zip`;
|
|
1041
|
-
return;
|
|
1042
|
-
},
|
|
1043
|
-
binaryInArchive: "vale"
|
|
1044
|
-
}
|
|
1045
|
-
},
|
|
1046
|
-
{
|
|
1047
|
-
id: "terraform-ls",
|
|
1048
|
-
name: "terraform-ls",
|
|
1049
|
-
checkCommand: "terraform-ls",
|
|
1050
|
-
checkArgs: ["--version"],
|
|
1051
|
-
installStrategy: "github",
|
|
1052
|
-
binaryName: "terraform-ls",
|
|
1053
|
-
github: {
|
|
1054
|
-
repo: "hashicorp/terraform-ls",
|
|
1055
|
-
hashiCorpReleaseProduct: "terraform-ls",
|
|
1056
|
-
assetMatch: archAssetMatch(OS_ARCH_ZIP_ASSETS),
|
|
1057
|
-
binaryInArchive: "terraform-ls"
|
|
1058
|
-
}
|
|
1059
|
-
},
|
|
1060
|
-
{
|
|
1061
|
-
id: "zls",
|
|
1062
|
-
name: "zls",
|
|
1063
|
-
checkCommand: "zls",
|
|
1064
|
-
checkArgs: ["--version"],
|
|
1065
|
-
installStrategy: "github",
|
|
1066
|
-
binaryName: "zls",
|
|
1067
|
-
github: {
|
|
1068
|
-
repo: "zigtools/zls",
|
|
1069
|
-
assetMatch: (platform, arch) => {
|
|
1070
|
-
if (platform === "linux")
|
|
1071
|
-
return arch === "arm64" ? "aarch64-linux.tar.xz" : "x86_64-linux.tar.xz";
|
|
1072
|
-
if (platform === "darwin")
|
|
1073
|
-
return arch === "arm64" ? "aarch64-macos.tar.xz" : "x86_64-macos.tar.xz";
|
|
1074
|
-
if (platform === "win32")
|
|
1075
|
-
return arch === "arm64" ? "aarch64-windows.zip" : "x86_64-windows.zip";
|
|
1076
|
-
return;
|
|
1077
|
-
},
|
|
1078
|
-
binaryInArchive: "zls"
|
|
1079
|
-
}
|
|
1080
|
-
},
|
|
1081
|
-
{
|
|
1082
|
-
id: "clojure-lsp",
|
|
1083
|
-
name: "clojure-lsp",
|
|
1084
|
-
checkCommand: "clojure-lsp",
|
|
1085
|
-
checkArgs: ["--version"],
|
|
1086
|
-
installStrategy: "github",
|
|
1087
|
-
binaryName: "clojure-lsp",
|
|
1088
|
-
github: {
|
|
1089
|
-
repo: "clojure-lsp/clojure-lsp",
|
|
1090
|
-
assetMatch: (platform, arch) => {
|
|
1091
|
-
if (platform === "linux")
|
|
1092
|
-
return arch === "arm64" ? "native-linux-aarch64.zip" : "native-linux-amd64.zip";
|
|
1093
|
-
if (platform === "darwin")
|
|
1094
|
-
return arch === "arm64" ? "native-macos-aarch64.zip" : "native-macos-amd64.zip";
|
|
1095
|
-
if (platform === "win32")
|
|
1096
|
-
return "native-windows-amd64.zip";
|
|
1097
|
-
return;
|
|
1098
|
-
},
|
|
1099
|
-
binaryInArchive: "clojure-lsp"
|
|
1100
|
-
}
|
|
1101
|
-
},
|
|
1102
|
-
{
|
|
1103
|
-
id: "cue",
|
|
1104
|
-
name: "CUE",
|
|
1105
|
-
checkCommand: "cue",
|
|
1106
|
-
checkArgs: ["version"],
|
|
1107
|
-
installStrategy: "github",
|
|
1108
|
-
binaryName: "cue",
|
|
1109
|
-
github: {
|
|
1110
|
-
repo: "cue-lang/cue",
|
|
1111
|
-
assetMatch: (platform, arch) => {
|
|
1112
|
-
if (platform === "linux")
|
|
1113
|
-
return arch === "arm64" ? "linux_arm64.tar.gz" : "linux_amd64.tar.gz";
|
|
1114
|
-
if (platform === "darwin")
|
|
1115
|
-
return arch === "arm64" ? "darwin_arm64.tar.gz" : "darwin_amd64.tar.gz";
|
|
1116
|
-
if (platform === "win32")
|
|
1117
|
-
return arch === "arm64" ? "windows_arm64.zip" : "windows_amd64.zip";
|
|
1118
|
-
return;
|
|
1119
|
-
},
|
|
1120
|
-
binaryInArchive: "cue"
|
|
1121
|
-
}
|
|
1122
|
-
},
|
|
1123
|
-
{
|
|
1124
|
-
id: "gleam",
|
|
1125
|
-
name: "Gleam",
|
|
1126
|
-
checkCommand: "gleam",
|
|
1127
|
-
checkArgs: ["--version"],
|
|
1128
|
-
installStrategy: "github",
|
|
1129
|
-
binaryName: "gleam",
|
|
1130
|
-
github: {
|
|
1131
|
-
repo: "gleam-lang/gleam",
|
|
1132
|
-
assetMatch: (platform, arch) => {
|
|
1133
|
-
if (platform === "linux")
|
|
1134
|
-
return arch === "arm64" ? "aarch64-unknown-linux-musl.tar.gz" : "x86_64-unknown-linux-musl.tar.gz";
|
|
1135
|
-
if (platform === "darwin")
|
|
1136
|
-
return arch === "arm64" ? "aarch64-apple-darwin.tar.gz" : "x86_64-apple-darwin.tar.gz";
|
|
1137
|
-
if (platform === "win32")
|
|
1138
|
-
return arch === "arm64" ? "aarch64-pc-windows-msvc.zip" : "x86_64-pc-windows-msvc.zip";
|
|
1139
|
-
return;
|
|
1140
|
-
},
|
|
1141
|
-
binaryInArchive: "gleam"
|
|
1142
|
-
}
|
|
1143
|
-
},
|
|
1144
|
-
{
|
|
1145
|
-
id: "tinymist",
|
|
1146
|
-
name: "Tinymist",
|
|
1147
|
-
checkCommand: "tinymist",
|
|
1148
|
-
checkArgs: ["--version"],
|
|
1149
|
-
installStrategy: "github",
|
|
1150
|
-
binaryName: "tinymist",
|
|
1151
|
-
github: {
|
|
1152
|
-
repo: "Myriad-Dreamin/tinymist",
|
|
1153
|
-
assetMatch: archAssetMatch({
|
|
1154
|
-
linux: {
|
|
1155
|
-
x64: "tinymist-x86_64-unknown-linux-gnu.tar.gz",
|
|
1156
|
-
arm64: "tinymist-aarch64-unknown-linux-gnu.tar.gz"
|
|
1157
|
-
},
|
|
1158
|
-
darwin: {
|
|
1159
|
-
x64: "tinymist-x86_64-apple-darwin.tar.gz",
|
|
1160
|
-
arm64: "tinymist-aarch64-apple-darwin.tar.gz"
|
|
1161
|
-
},
|
|
1162
|
-
win32: {
|
|
1163
|
-
x64: "tinymist-x86_64-pc-windows-msvc.zip",
|
|
1164
|
-
arm64: "tinymist-aarch64-pc-windows-msvc.zip"
|
|
1165
|
-
}
|
|
1166
|
-
}),
|
|
1167
|
-
binaryInArchive: "tinymist"
|
|
1168
|
-
}
|
|
1169
|
-
},
|
|
1170
|
-
{
|
|
1171
|
-
id: "marksman",
|
|
1172
|
-
name: "Marksman",
|
|
1173
|
-
checkCommand: "marksman",
|
|
1174
|
-
checkArgs: ["--version"],
|
|
1175
|
-
installStrategy: "github",
|
|
1176
|
-
binaryName: "marksman",
|
|
1177
|
-
github: {
|
|
1178
|
-
repo: "artempyanykh/marksman",
|
|
1179
|
-
assetMatch: (platform, arch) => {
|
|
1180
|
-
if (platform === "linux")
|
|
1181
|
-
return arch === "arm64" ? "marksman-linux-arm64" : "marksman-linux-x64";
|
|
1182
|
-
if (platform === "darwin")
|
|
1183
|
-
return "marksman-macos";
|
|
1184
|
-
if (platform === "win32")
|
|
1185
|
-
return "marksman.exe";
|
|
1186
|
-
return;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
},
|
|
1190
|
-
{
|
|
1191
|
-
id: "expert",
|
|
1192
|
-
name: "Expert",
|
|
1193
|
-
checkCommand: "expert",
|
|
1194
|
-
checkArgs: ["--version"],
|
|
1195
|
-
installStrategy: "github",
|
|
1196
|
-
binaryName: "expert",
|
|
1197
|
-
github: {
|
|
1198
|
-
repo: "expert-lsp/expert",
|
|
1199
|
-
assetMatch: (platform, arch) => {
|
|
1200
|
-
if (arch !== "x64" && arch !== "arm64")
|
|
1201
|
-
return;
|
|
1202
|
-
if (platform === "linux")
|
|
1203
|
-
return arch === "arm64" ? "expert_linux_arm64" : "expert_linux_amd64";
|
|
1204
|
-
if (platform === "darwin")
|
|
1205
|
-
return arch === "arm64" ? "expert_darwin_arm64" : "expert_darwin_amd64";
|
|
1206
|
-
if (platform === "win32")
|
|
1207
|
-
return "expert_windows_amd64.exe";
|
|
1208
|
-
return;
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
];
|
|
1213
|
-
export function getToolVerificationTimeout(tool) {
|
|
1214
|
-
return tool.verificationTimeoutMs ?? 1e4;
|
|
1215
|
-
}
|
|
1216
|
-
const ensureInFlight = new Map;
|
|
1217
|
-
const installFailureReasons = new Map;
|
|
1218
|
-
export function getInstallFailureReason(toolId) {
|
|
1219
|
-
return installFailureReasons.get(toolId);
|
|
1220
|
-
}
|
|
1221
|
-
const installAttempts = new Map;
|
|
1222
|
-
function noteInstallAttempt(toolId, outcome, reason) {
|
|
1223
|
-
installAttempts.set(toolId, { outcome, reason, at: Date.now() });
|
|
1224
|
-
}
|
|
1225
|
-
function noteInstallAttemptIfUnrecorded(toolId, outcome, reason) {
|
|
1226
|
-
if (installAttempts.has(toolId))
|
|
1227
|
-
return;
|
|
1228
|
-
noteInstallAttempt(toolId, outcome, reason);
|
|
1229
|
-
}
|
|
1230
|
-
export function getInstallAttempt(toolId) {
|
|
1231
|
-
return installAttempts.get(toolId);
|
|
1232
|
-
}
|
|
1233
|
-
const lastEnsureResolutionSource = new Map;
|
|
1234
|
-
export function getLastEnsureResolutionSource(toolId) {
|
|
1235
|
-
return lastEnsureResolutionSource.get(toolId);
|
|
1236
|
-
}
|
|
1237
|
-
const resolvedPathCache = new BoundedLruCache(256);
|
|
1238
|
-
export function resetResolvedPathCache() {
|
|
1239
|
-
resolvedPathCache.clear();
|
|
1240
|
-
}
|
|
1241
|
-
const PROBE_CACHE_PATH = path.join(getGlobalDmLensDir(), "probe-cache.json");
|
|
1242
|
-
const PROBE_CACHE_LOCK_STALE_MS = 180000;
|
|
1243
|
-
const PROBE_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
1244
|
-
const PROBE_CACHE_TRANSIENT_COOLDOWN_MS = TRANSIENT_MAX_COOLDOWN_MS;
|
|
1245
|
-
const PROBE_CACHE_FLUSH_LOCK_WAIT_MS = 250;
|
|
1246
|
-
const PROBE_CACHE_FLUSH_RETRY_DELAY_MS = 300;
|
|
1247
|
-
const PROBE_CACHE_FLUSH_RETRY_MAX_DELAY_MS = 30000;
|
|
1248
|
-
let _probeCache = null;
|
|
1249
|
-
let _probeCacheDirty = false;
|
|
1250
|
-
let _probeCacheFlushTimer = null;
|
|
1251
|
-
let _probeCacheWriteInFlight = null;
|
|
1252
|
-
let _probeCacheRetryAttempt = 0;
|
|
1253
|
-
let _probeCacheChangeGeneration = 0;
|
|
1254
|
-
const _probeCacheChanges = new Map;
|
|
1255
|
-
const _probeCacheChangeVersions = new Map;
|
|
1256
|
-
async function readProbeCache() {
|
|
1257
|
-
if (_probeCache !== null)
|
|
1258
|
-
return _probeCache;
|
|
1259
|
-
try {
|
|
1260
|
-
const raw = await fs.readFile(PROBE_CACHE_PATH, "utf-8");
|
|
1261
|
-
const parsed = JSON.parse(raw);
|
|
1262
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1263
|
-
throw new Error("probe-cache root is not an object");
|
|
1264
|
-
}
|
|
1265
|
-
_probeCache = parsed;
|
|
1266
|
-
} catch (err) {
|
|
1267
|
-
const code = err?.code;
|
|
1268
|
-
if (code !== "ENOENT") {
|
|
1269
|
-
logSessionStart(`auto-install probe-cache: read failed (${code ?? "invalid"}); treating cache as unavailable`);
|
|
1270
|
-
}
|
|
1271
|
-
_probeCache = {};
|
|
1272
|
-
}
|
|
1273
|
-
return _probeCache;
|
|
1274
|
-
}
|
|
1275
|
-
function markProbeCacheChange(toolId, entry) {
|
|
1276
|
-
_probeCacheChanges.set(toolId, entry);
|
|
1277
|
-
_probeCacheChangeVersions.set(toolId, ++_probeCacheChangeGeneration);
|
|
1278
|
-
_probeCacheDirty = true;
|
|
1279
|
-
scheduleProbeFlush();
|
|
1280
|
-
}
|
|
1281
|
-
function scheduleProbeFlush(delayMs = PROBE_CACHE_FLUSH_RETRY_DELAY_MS) {
|
|
1282
|
-
if (_probeCacheFlushTimer !== null)
|
|
1283
|
-
return;
|
|
1284
|
-
_probeCacheFlushTimer = setTimeout(() => {
|
|
1285
|
-
flushProbeCache();
|
|
1286
|
-
}, delayMs);
|
|
1287
|
-
_probeCacheFlushTimer.unref?.();
|
|
1288
|
-
}
|
|
1289
|
-
function scheduleProbeFlushRetry() {
|
|
1290
|
-
const delay = Math.min(PROBE_CACHE_FLUSH_RETRY_MAX_DELAY_MS, PROBE_CACHE_FLUSH_RETRY_DELAY_MS * 2 ** Math.min(_probeCacheRetryAttempt, 6));
|
|
1291
|
-
_probeCacheRetryAttempt += 1;
|
|
1292
|
-
scheduleProbeFlush(delay);
|
|
1293
|
-
}
|
|
1294
|
-
function snapshotProbeCacheChanges() {
|
|
1295
|
-
return {
|
|
1296
|
-
changes: new Map(_probeCacheChanges),
|
|
1297
|
-
versions: new Map(_probeCacheChangeVersions)
|
|
1298
|
-
};
|
|
1299
|
-
}
|
|
1300
|
-
function deserializeProbeCache(contents) {
|
|
1301
|
-
if (contents === undefined)
|
|
1302
|
-
return {};
|
|
1303
|
-
try {
|
|
1304
|
-
const parsed = JSON.parse(contents);
|
|
1305
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1306
|
-
throw new Error("probe-cache root is not an object");
|
|
1307
|
-
}
|
|
1308
|
-
return parsed;
|
|
1309
|
-
} catch (err) {
|
|
1310
|
-
logSessionStart(`auto-install probe-cache: write-side read was corrupt (${err.message}); recovering as empty`);
|
|
1311
|
-
return {};
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
function applyProbeCacheChanges(disk, changes) {
|
|
1315
|
-
for (const [toolId, entry] of changes) {
|
|
1316
|
-
if (entry === null)
|
|
1317
|
-
delete disk[toolId];
|
|
1318
|
-
else
|
|
1319
|
-
disk[toolId] = entry;
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
function ageProbeCache(disk) {
|
|
1323
|
-
const now = Date.now();
|
|
1324
|
-
for (const [toolId, entry] of Object.entries(disk)) {
|
|
1325
|
-
const ttl = entry.transient ? PROBE_CACHE_TRANSIENT_COOLDOWN_MS : PROBE_CACHE_TTL_MS;
|
|
1326
|
-
if (!Number.isFinite(entry.cachedAt) || entry.cachedAt < now - ttl) {
|
|
1327
|
-
delete disk[toolId];
|
|
1328
|
-
}
|
|
1329
|
-
}
|
|
1330
|
-
}
|
|
1331
|
-
function publishProbeCacheWrite(disk, snapshotVersions) {
|
|
1332
|
-
const pendingAfterWrite = new Map(_probeCacheChanges);
|
|
1333
|
-
_probeCache = disk;
|
|
1334
|
-
for (const [toolId, entry] of pendingAfterWrite) {
|
|
1335
|
-
if (entry === null)
|
|
1336
|
-
delete _probeCache[toolId];
|
|
1337
|
-
else
|
|
1338
|
-
_probeCache[toolId] = entry;
|
|
1339
|
-
}
|
|
1340
|
-
for (const [toolId, version] of snapshotVersions) {
|
|
1341
|
-
if (_probeCacheChangeVersions.get(toolId) === version) {
|
|
1342
|
-
_probeCacheChanges.delete(toolId);
|
|
1343
|
-
_probeCacheChangeVersions.delete(toolId);
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
_probeCacheDirty = _probeCacheChanges.size > 0;
|
|
1347
|
-
_probeCacheRetryAttempt = 0;
|
|
1348
|
-
return _probeCacheDirty ? "written-with-pending" : "written";
|
|
1349
|
-
}
|
|
1350
|
-
async function writeProbeCache() {
|
|
1351
|
-
try {
|
|
1352
|
-
const { changes, versions } = snapshotProbeCacheChanges();
|
|
1353
|
-
let result = "written";
|
|
1354
|
-
const committed = await commitDurableStoreAsync({
|
|
1355
|
-
path: PROBE_CACHE_PATH,
|
|
1356
|
-
deserialize: deserializeProbeCache,
|
|
1357
|
-
merge: (disk) => {
|
|
1358
|
-
ageProbeCache(disk);
|
|
1359
|
-
applyProbeCacheChanges(disk, changes);
|
|
1360
|
-
return disk;
|
|
1361
|
-
},
|
|
1362
|
-
serialize: (disk) => JSON.stringify(disk, null, 2),
|
|
1363
|
-
waitMs: PROBE_CACHE_FLUSH_LOCK_WAIT_MS,
|
|
1364
|
-
retryMs: 25,
|
|
1365
|
-
staleMs: PROBE_CACHE_LOCK_STALE_MS,
|
|
1366
|
-
timeoutMessage: "Timed out waiting for probe-cache lock",
|
|
1367
|
-
onContention: "skip-log",
|
|
1368
|
-
logContention: () => {
|
|
1369
|
-
logSessionStart("auto-install probe-cache: flush deferred because another process owns the lock");
|
|
1370
|
-
},
|
|
1371
|
-
afterWriteLocked: (disk) => {
|
|
1372
|
-
result = publishProbeCacheWrite(disk, versions);
|
|
1373
|
-
}
|
|
1374
|
-
});
|
|
1375
|
-
if (committed === undefined) {
|
|
1376
|
-
scheduleProbeFlushRetry();
|
|
1377
|
-
return "deferred";
|
|
1378
|
-
}
|
|
1379
|
-
return result;
|
|
1380
|
-
} catch (err) {
|
|
1381
|
-
logSessionStart(`auto-install probe-cache: flush failed (${err?.code ?? "write error"}); pending update retained`);
|
|
1382
|
-
scheduleProbeFlushRetry();
|
|
1383
|
-
return "failed";
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
export async function flushProbeCache() {
|
|
1387
|
-
if (_probeCacheFlushTimer !== null) {
|
|
1388
|
-
clearTimeout(_probeCacheFlushTimer);
|
|
1389
|
-
_probeCacheFlushTimer = null;
|
|
1390
|
-
}
|
|
1391
|
-
if (_probeCacheWriteInFlight) {
|
|
1392
|
-
await _probeCacheWriteInFlight;
|
|
1393
|
-
if (!_probeCacheDirty)
|
|
1394
|
-
return "written";
|
|
1395
|
-
}
|
|
1396
|
-
if (!_probeCacheDirty || _probeCache === null)
|
|
1397
|
-
return "idle";
|
|
1398
|
-
const write = writeProbeCache();
|
|
1399
|
-
_probeCacheWriteInFlight = write;
|
|
1400
|
-
try {
|
|
1401
|
-
return await write;
|
|
1402
|
-
} finally {
|
|
1403
|
-
if (_probeCacheWriteInFlight === write)
|
|
1404
|
-
_probeCacheWriteInFlight = null;
|
|
1405
|
-
}
|
|
1406
|
-
}
|
|
1407
|
-
function isAstGrepVersionOutput(output) {
|
|
1408
|
-
return /\bast[- ]grep\b/i.test(output);
|
|
1409
|
-
}
|
|
1410
|
-
async function verifyAstGrepProbePath(binPath) {
|
|
1411
|
-
const result = await probeToolAsync(binPath, ["--version"], {
|
|
1412
|
-
timeout: 5000
|
|
1413
|
-
});
|
|
1414
|
-
return !result.error && result.status === 0 && isAstGrepVersionOutput(`${result.stdout}${result.stderr}`);
|
|
1415
|
-
}
|
|
1416
|
-
export async function checkProbeCache(toolId) {
|
|
1417
|
-
const cache = await readProbeCache();
|
|
1418
|
-
const entry = cache[toolId];
|
|
1419
|
-
if (!entry)
|
|
1420
|
-
return;
|
|
1421
|
-
const ttl = entry.transient ? PROBE_CACHE_TRANSIENT_COOLDOWN_MS : PROBE_CACHE_TTL_MS;
|
|
1422
|
-
if (Date.now() - entry.cachedAt > ttl) {
|
|
1423
|
-
logSessionStart(`auto-install probe-cache ${toolId}: miss (${entry.transient ? "transient cooldown" : "ttl"} expired)`);
|
|
1424
|
-
delete cache[toolId];
|
|
1425
|
-
markProbeCacheChange(toolId, null);
|
|
1426
|
-
return;
|
|
1427
|
-
}
|
|
1428
|
-
try {
|
|
1429
|
-
await fs.access(entry.path);
|
|
1430
|
-
const stat = await fs.stat(entry.path);
|
|
1431
|
-
if (stat.mtimeMs !== entry.mtimeMs || entry.sizeBytes !== undefined && stat.size !== entry.sizeBytes) {
|
|
1432
|
-
logSessionStart(`auto-install probe-cache ${toolId}: miss (mtime/size changed)`);
|
|
1433
|
-
delete cache[toolId];
|
|
1434
|
-
markProbeCacheChange(toolId, null);
|
|
1435
|
-
return;
|
|
1436
|
-
}
|
|
1437
|
-
if (toolId === "ast-grep" && !await verifyAstGrepProbePath(entry.path)) {
|
|
1438
|
-
logSessionStart(`auto-install probe-cache ${toolId}: miss (not ast-grep: ${entry.path})`);
|
|
1439
|
-
delete cache[toolId];
|
|
1440
|
-
markProbeCacheChange(toolId, null);
|
|
1441
|
-
return;
|
|
1442
|
-
}
|
|
1443
|
-
return entry.path;
|
|
1444
|
-
} catch {
|
|
1445
|
-
logSessionStart(`auto-install probe-cache ${toolId}: miss (gone: ${entry.path})`);
|
|
1446
|
-
delete cache[toolId];
|
|
1447
|
-
markProbeCacheChange(toolId, null);
|
|
1448
|
-
return;
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
export async function updateProbeCache(toolId, resolvedPath, transient = false) {
|
|
1452
|
-
try {
|
|
1453
|
-
const stat = await fs.stat(resolvedPath);
|
|
1454
|
-
const cache = await readProbeCache();
|
|
1455
|
-
const entry = {
|
|
1456
|
-
path: resolvedPath,
|
|
1457
|
-
mtimeMs: stat.mtimeMs,
|
|
1458
|
-
sizeBytes: stat.size,
|
|
1459
|
-
cachedAt: Date.now(),
|
|
1460
|
-
...transient && { transient: true }
|
|
1461
|
-
};
|
|
1462
|
-
cache[toolId] = entry;
|
|
1463
|
-
markProbeCacheChange(toolId, entry);
|
|
1464
|
-
} catch {}
|
|
1465
|
-
}
|
|
1466
|
-
export function resetProbeCacheStateForTesting() {
|
|
1467
|
-
_probeCache = null;
|
|
1468
|
-
_probeCacheDirty = false;
|
|
1469
|
-
_probeCacheChanges.clear();
|
|
1470
|
-
_probeCacheChangeVersions.clear();
|
|
1471
|
-
_probeCacheChangeGeneration = 0;
|
|
1472
|
-
_probeCacheRetryAttempt = 0;
|
|
1473
|
-
resetResolvedPathCache();
|
|
1474
|
-
ensureInFlight.clear();
|
|
1475
|
-
installFailureReasons.clear();
|
|
1476
|
-
installAttempts.clear();
|
|
1477
|
-
lastEnsureResolutionSource.clear();
|
|
1478
|
-
lastManagedInstallVersion.clear();
|
|
1479
|
-
lastResolveTransient.clear();
|
|
1480
|
-
resetPathWalkMemo();
|
|
1481
|
-
if (_probeCacheFlushTimer !== null) {
|
|
1482
|
-
clearTimeout(_probeCacheFlushTimer);
|
|
1483
|
-
_probeCacheFlushTimer = null;
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
export function _peekEnsureInFlightForTesting() {
|
|
1487
|
-
return ensureInFlight;
|
|
1488
|
-
}
|
|
1489
|
-
const pathWalkMemo = new Map;
|
|
1490
|
-
function hashSync(value) {
|
|
1491
|
-
let hash = 2166136261;
|
|
1492
|
-
for (let i = 0;i < value.length; i += 1) {
|
|
1493
|
-
hash ^= value.charCodeAt(i);
|
|
1494
|
-
hash = Math.imul(hash, 16777619);
|
|
1495
|
-
}
|
|
1496
|
-
return (hash >>> 0).toString(16);
|
|
1497
|
-
}
|
|
1498
|
-
export function resetPathWalkMemo() {
|
|
1499
|
-
pathWalkMemo.clear();
|
|
1500
|
-
}
|
|
1501
|
-
export async function isCommandAvailable(command, _args) {
|
|
1502
|
-
const isWindows = installerPlatform() === "win32";
|
|
1503
|
-
const pathEnv = process.env.PATH || process.env.Path || process.env.path || "";
|
|
1504
|
-
const dirs = pathEnv.split(path.delimiter);
|
|
1505
|
-
const names = isWindows ? [command, `${command}.exe`, `${command}.cmd`, `${command}.bat`] : [command];
|
|
1506
|
-
for (const dir of dirs) {
|
|
1507
|
-
if (!dir)
|
|
1508
|
-
continue;
|
|
1509
|
-
for (const name of names) {
|
|
1510
|
-
const candidate = path.join(dir, name);
|
|
1511
|
-
try {
|
|
1512
|
-
const stat = statSync(candidate);
|
|
1513
|
-
if (stat.isFile() && stat.size > 0) {
|
|
1514
|
-
return true;
|
|
1515
|
-
}
|
|
1516
|
-
} catch {}
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
return false;
|
|
1520
|
-
}
|
|
1521
|
-
export async function isSpawnableCommand(command) {
|
|
1522
|
-
if (/[\\/]/.test(command)) {
|
|
1523
|
-
try {
|
|
1524
|
-
const stat = statSync(command);
|
|
1525
|
-
return stat.isFile() && stat.size > 0;
|
|
1526
|
-
} catch {
|
|
1527
|
-
return false;
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
const memoKey = `${command}:${hashSync(process.env.PATH || "")}`;
|
|
1531
|
-
if (pathWalkMemo.has(memoKey))
|
|
1532
|
-
return pathWalkMemo.get(memoKey) ?? false;
|
|
1533
|
-
const isSpawnable = await isCommandAvailable(command);
|
|
1534
|
-
pathWalkMemo.set(memoKey, isSpawnable);
|
|
1535
|
-
return isSpawnable;
|
|
1536
|
-
}
|
|
1537
|
-
export function isLspTransportRequiredError(output) {
|
|
1538
|
-
return /Connection (?:input|output) stream is not set|Use arguments of createConnection/i.test(output);
|
|
1539
|
-
}
|
|
1540
|
-
const lspTransportRequiredMatcher = /Connection (?:input|output) stream is not set|Use arguments of createConnection/i;
|
|
1541
|
-
export function parsePinnedVersion(packageName) {
|
|
1542
|
-
const at = packageName.lastIndexOf("@");
|
|
1543
|
-
if (at <= 0)
|
|
1544
|
-
return;
|
|
1545
|
-
return packageName.slice(at + 1) || undefined;
|
|
1546
|
-
}
|
|
1547
|
-
function extractVersionToken(output) {
|
|
1548
|
-
return output.match(/\d+\.\d+\.\d+(?:[-+][\w.]+)?/)?.[0];
|
|
1549
|
-
}
|
|
1550
|
-
const lastManagedInstallVersion = new Map;
|
|
1551
|
-
export function packageEntryVerification(tool) {
|
|
1552
|
-
return tool.verification === "package-entry" ? tool.packageName : undefined;
|
|
1553
|
-
}
|
|
1554
|
-
export async function verifyNpmPackageEntry(binPath, packageName) {
|
|
1555
|
-
const nodeModulesDir = path.dirname(path.dirname(binPath));
|
|
1556
|
-
const pinned = parsePinnedVersion(packageName);
|
|
1557
|
-
const bareName = pinned ? packageName.slice(0, -(pinned.length + 1)) : packageName;
|
|
1558
|
-
const packageDir = path.join(nodeModulesDir, ...bareName.split("/"));
|
|
1559
|
-
const shimName = path.basename(binPath).replace(/\.(cmd|exe|ps1|bat)$/i, "").toLowerCase();
|
|
1560
|
-
const fail = (reason) => {
|
|
1561
|
-
logSessionStart(`auto-install verify: failed for ${binPath} (check=package-entry, kind=${reason})`);
|
|
1562
|
-
return false;
|
|
1563
|
-
};
|
|
1564
|
-
try {
|
|
1565
|
-
const shim = statSync(binPath);
|
|
1566
|
-
if (!shim.isFile() || shim.size === 0)
|
|
1567
|
-
return fail("shim-not-a-file");
|
|
1568
|
-
} catch {
|
|
1569
|
-
return fail("shim-missing");
|
|
1570
|
-
}
|
|
1571
|
-
let manifest;
|
|
1572
|
-
try {
|
|
1573
|
-
manifest = JSON.parse(await fs.readFile(path.join(packageDir, "package.json"), "utf8"));
|
|
1574
|
-
} catch {
|
|
1575
|
-
return fail("package-json-unreadable");
|
|
1576
|
-
}
|
|
1577
|
-
if (typeof manifest.version !== "string" || !manifest.version) {
|
|
1578
|
-
return fail("package-json-no-version");
|
|
1579
|
-
}
|
|
1580
|
-
const bin = manifest.bin;
|
|
1581
|
-
const entry = typeof bin === "string" ? bin : bin && typeof bin === "object" ? Object.entries(bin).find(([name]) => name.toLowerCase() === shimName)?.[1] : undefined;
|
|
1582
|
-
if (typeof entry !== "string" || !entry) {
|
|
1583
|
-
return fail("package-json-no-entry");
|
|
1584
|
-
}
|
|
1585
|
-
try {
|
|
1586
|
-
const stat = statSync(path.join(packageDir, entry));
|
|
1587
|
-
if (!stat.isFile() || stat.size === 0)
|
|
1588
|
-
return fail("entry-not-a-file");
|
|
1589
|
-
} catch {
|
|
1590
|
-
return fail("entry-missing");
|
|
1591
|
-
}
|
|
1592
|
-
logSessionStart(`auto-install verify: succeeded for ${binPath} (check=package-entry, version=${manifest.version}, entry=${entry})`);
|
|
1593
|
-
return true;
|
|
1594
|
-
}
|
|
1595
|
-
export async function verifyToolBinary(binPath, onVersionOutput, onTransient, timeoutMs = 1e4, verificationArgs = ["--version"], packageEntryOf, onInconclusive) {
|
|
1596
|
-
if (packageEntryOf !== undefined) {
|
|
1597
|
-
return verifyNpmPackageEntry(binPath, packageEntryOf);
|
|
1598
|
-
}
|
|
1599
|
-
const isWindows = installerPlatform() === "win32";
|
|
1600
|
-
const hasKnownWindowsExt = /\.(cmd|exe|ps1)$/i.test(binPath);
|
|
1601
|
-
let execPath = isWindows && !hasKnownWindowsExt ? `${binPath}.cmd` : binPath;
|
|
1602
|
-
if (isWindows && /\.ps1$/i.test(execPath)) {
|
|
1603
|
-
const cmdSibling = `${execPath.slice(0, -4)}.cmd`;
|
|
1604
|
-
if (require("node:fs").existsSync(cmdSibling)) {
|
|
1605
|
-
execPath = cmdSibling;
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
|
-
const useShell = isWindows && /\.(cmd|bat)$/i.test(execPath);
|
|
1609
|
-
try {
|
|
1610
|
-
const result = await safeSpawnAsync(execPath, verificationArgs, {
|
|
1611
|
-
timeout: timeoutMs,
|
|
1612
|
-
input: "",
|
|
1613
|
-
maxOutputBytes: 64 * 1024,
|
|
1614
|
-
matchWhileStreaming: lspTransportRequiredMatcher
|
|
1615
|
-
});
|
|
1616
|
-
const output = `${result.stdout}
|
|
1617
|
-
${result.stderr}`;
|
|
1618
|
-
if (result.outputTruncated) {
|
|
1619
|
-
recordDegradationOnce({
|
|
1620
|
-
kind: "installer-verification-output-truncated",
|
|
1621
|
-
subject: binPath,
|
|
1622
|
-
reason: `verification output exceeded 65536 bytes (${verificationArgs.join(" ")})`
|
|
1623
|
-
});
|
|
1624
|
-
}
|
|
1625
|
-
if (result.status === 0 && !result.error) {
|
|
1626
|
-
debugLog(`Verified: ${binPath} (${verificationArgs.join(" ")}: ${result.stdout.trim()})`);
|
|
1627
|
-
onVersionOutput?.(result.stdout);
|
|
1628
|
-
return true;
|
|
1629
|
-
}
|
|
1630
|
-
if (result.streamingMatch || isLspTransportRequiredError(output)) {
|
|
1631
|
-
debugLog(`Verified (stdio LSP, transport-required): ${binPath}`);
|
|
1632
|
-
return true;
|
|
1633
|
-
}
|
|
1634
|
-
if (result.signal !== undefined || result.spawnFailure)
|
|
1635
|
-
onTransient?.();
|
|
1636
|
-
if (result.outputTruncated && result.signal === undefined && !result.spawnFailure) {
|
|
1637
|
-
onInconclusive?.();
|
|
1638
|
-
recordDegradationOnce({
|
|
1639
|
-
kind: "installer-verification-inconclusive",
|
|
1640
|
-
subject: binPath,
|
|
1641
|
-
reason: `transport-required marker unresolved in truncated output (${verificationArgs.join(" ")})`
|
|
1642
|
-
});
|
|
1643
|
-
}
|
|
1644
|
-
const kind = result.spawnFailure?.kind ?? (result.signal ? `killed-signal=${result.signal}` : result.error ? "spawn-error" : "exit-nonzero");
|
|
1645
|
-
logSessionStart(`auto-install verify: failed for ${binPath} (check=${verificationArgs.join(" ")}, kind=${kind}${result.status !== null ? `, exit=${result.status}` : ""})`);
|
|
1646
|
-
return false;
|
|
1647
|
-
} catch (err) {
|
|
1648
|
-
logSessionStart(`auto-install verify: spawn threw for ${binPath} (${err instanceof Error ? err.message : String(err)})`);
|
|
1649
|
-
onTransient?.();
|
|
1650
|
-
return false;
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
export async function getAllToolStatuses() {
|
|
1654
|
-
const statuses = [];
|
|
1655
|
-
for (const tool of TOOLS) {
|
|
1656
|
-
const status = {
|
|
1657
|
-
id: tool.id,
|
|
1658
|
-
name: tool.name,
|
|
1659
|
-
installed: false,
|
|
1660
|
-
source: "not-installed",
|
|
1661
|
-
strategy: tool.installStrategy
|
|
1662
|
-
};
|
|
1663
|
-
if (tool.installStrategy === "archive" && !tool.archive?.launcher) {
|
|
1664
|
-
const bundleDir = await getArchiveTreeBundlePath(tool);
|
|
1665
|
-
if (bundleDir) {
|
|
1666
|
-
status.installed = true;
|
|
1667
|
-
status.source = "archive-dist";
|
|
1668
|
-
status.path = bundleDir;
|
|
1669
|
-
}
|
|
1670
|
-
statuses.push(status);
|
|
1671
|
-
continue;
|
|
1672
|
-
}
|
|
1673
|
-
if (await isCommandAvailable(tool.checkCommand, tool.checkArgs)) {
|
|
1674
|
-
status.installed = true;
|
|
1675
|
-
status.source = "global-path";
|
|
1676
|
-
status.path = tool.checkCommand;
|
|
1677
|
-
const probe = await probeToolAsync(tool.checkCommand, ["--version"], {
|
|
1678
|
-
timeout: 5000
|
|
1679
|
-
});
|
|
1680
|
-
status.version = `${probe.stdout}${probe.stderr}`.trim().split(`
|
|
1681
|
-
`)[0]?.slice(0, 30) || undefined;
|
|
1682
|
-
statuses.push(status);
|
|
1683
|
-
continue;
|
|
1684
|
-
}
|
|
1685
|
-
if (tool.installStrategy === "npm") {
|
|
1686
|
-
const npmPath = await findNpmGlobalToolPath(tool.binaryName || tool.id, undefined, tool.checkArgs, getToolVerificationTimeout(tool));
|
|
1687
|
-
if (npmPath) {
|
|
1688
|
-
status.installed = true;
|
|
1689
|
-
status.source = "npm-global";
|
|
1690
|
-
status.path = npmPath;
|
|
1691
|
-
statuses.push(status);
|
|
1692
|
-
continue;
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
if (tool.installStrategy === "pip") {
|
|
1696
|
-
const pipPath = await findPipUserToolPath(tool.binaryName || tool.id, undefined, tool.checkArgs, getToolVerificationTimeout(tool));
|
|
1697
|
-
if (pipPath) {
|
|
1698
|
-
status.installed = true;
|
|
1699
|
-
status.source = "pip-user";
|
|
1700
|
-
status.path = pipPath;
|
|
1701
|
-
statuses.push(status);
|
|
1702
|
-
continue;
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
if (tool.installStrategy === "github" || tool.installStrategy === "maven" || tool.installStrategy === "archive") {
|
|
1706
|
-
const githubPath = await findGitHubToolPath(tool.binaryName || tool.id);
|
|
1707
|
-
if (githubPath) {
|
|
1708
|
-
status.installed = true;
|
|
1709
|
-
status.source = tool.installStrategy === "maven" ? "maven-jar" : tool.installStrategy === "archive" ? "archive-dist" : "github-release";
|
|
1710
|
-
status.path = githubPath;
|
|
1711
|
-
statuses.push(status);
|
|
1712
|
-
continue;
|
|
1713
|
-
}
|
|
1714
|
-
}
|
|
1715
|
-
const localBase = path.join(TOOLS_DIR, "node_modules", ".bin", tool.binaryName || tool.id);
|
|
1716
|
-
const localPath = installerPlatform() === "win32" ? `${localBase}.cmd` : localBase;
|
|
1717
|
-
try {
|
|
1718
|
-
await fs.access(localPath);
|
|
1719
|
-
if (await verifyToolBinary(localPath, undefined, undefined, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
|
|
1720
|
-
status.installed = true;
|
|
1721
|
-
status.source = "dm-lens-auto";
|
|
1722
|
-
status.path = localPath;
|
|
1723
|
-
statuses.push(status);
|
|
1724
|
-
continue;
|
|
1725
|
-
}
|
|
1726
|
-
} catch {}
|
|
1727
|
-
if (tool.installStrategy === "npm") {
|
|
1728
|
-
status.source = "npx-fallback";
|
|
1729
|
-
}
|
|
1730
|
-
statuses.push(status);
|
|
1731
|
-
}
|
|
1732
|
-
return statuses;
|
|
1733
|
-
}
|
|
1734
|
-
export async function isToolInstalled(toolId) {
|
|
1735
|
-
return await getToolPath(toolId) !== undefined;
|
|
1736
|
-
}
|
|
1737
|
-
async function getArchiveTreeBundlePath(tool) {
|
|
1738
|
-
if (tool.installStrategy !== "archive" || tool.archive?.launcher) {
|
|
1739
|
-
return;
|
|
1740
|
-
}
|
|
1741
|
-
const extractDir = path.join(TOOLS_DIR, tool.id);
|
|
1742
|
-
const marker = tool.archive?.treeMarker ? path.join(extractDir, ...tool.archive.treeMarker.split("/")) : extractDir;
|
|
1743
|
-
try {
|
|
1744
|
-
await fs.access(marker);
|
|
1745
|
-
return extractDir;
|
|
1746
|
-
} catch {
|
|
1747
|
-
return;
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
export function resolvePlatformPackageBinary(tool) {
|
|
1751
|
-
const spec = tool.platformPackage;
|
|
1752
|
-
if (!spec || !tool.packageName)
|
|
1753
|
-
return;
|
|
1754
|
-
const suffix = spec.suffixes[`${installerPlatform()}-${process.arch}`];
|
|
1755
|
-
if (!suffix)
|
|
1756
|
-
return;
|
|
1757
|
-
const platformPkg = `${spec.base ?? tool.packageName}-${suffix}`;
|
|
1758
|
-
try {
|
|
1759
|
-
const mainPkgJson = _installerRequire.resolve(`${tool.packageName}/package.json`);
|
|
1760
|
-
const fromMain = createRequire(mainPkgJson);
|
|
1761
|
-
let pkgDir;
|
|
1762
|
-
try {
|
|
1763
|
-
pkgDir = path.dirname(fromMain.resolve(`${platformPkg}/package.json`));
|
|
1764
|
-
} catch {
|
|
1765
|
-
pkgDir = path.dirname(_installerRequire.resolve(`${platformPkg}/package.json`));
|
|
1766
|
-
}
|
|
1767
|
-
const isWin = installerPlatform() === "win32";
|
|
1768
|
-
for (const bin of spec.binaries) {
|
|
1769
|
-
for (const name of isWin ? [`${bin}.exe`, bin] : [bin]) {
|
|
1770
|
-
const candidate = path.join(pkgDir, name);
|
|
1771
|
-
if (existsSync(candidate))
|
|
1772
|
-
return candidate;
|
|
1773
|
-
}
|
|
1774
|
-
}
|
|
1775
|
-
} catch {}
|
|
1776
|
-
return;
|
|
1777
|
-
}
|
|
1778
|
-
const lastResolveTransient = new Map;
|
|
1779
|
-
const lastInstallResolutionId = new Map;
|
|
1780
|
-
export function wasLastResolveTransient(toolId) {
|
|
1781
|
-
return lastResolveTransient.get(toolId) ?? false;
|
|
1782
|
-
}
|
|
1783
|
-
export async function getToolPath(toolId) {
|
|
1784
|
-
let sawTransient = false;
|
|
1785
|
-
const markTransient = () => {
|
|
1786
|
-
sawTransient = true;
|
|
1787
|
-
};
|
|
1788
|
-
const result = await getToolPathResolved(toolId, markTransient);
|
|
1789
|
-
lastResolveTransient.set(toolId, sawTransient);
|
|
1790
|
-
return result;
|
|
1791
|
-
}
|
|
1792
|
-
async function getToolPathResolved(toolId, onTransient) {
|
|
1793
|
-
const tool = TOOLS.find((t) => t.id === toolId);
|
|
1794
|
-
if (!tool)
|
|
1795
|
-
return;
|
|
1796
|
-
if (tool.installStrategy === "archive" && !tool.archive?.launcher) {
|
|
1797
|
-
return getArchiveTreeBundlePath(tool);
|
|
1798
|
-
}
|
|
1799
|
-
const pinnedVersion = tool.installStrategy === "npm" && tool.packageName ? parsePinnedVersion(tool.packageName) : undefined;
|
|
1800
|
-
if (pinnedVersion)
|
|
1801
|
-
lastManagedInstallVersion.delete(toolId);
|
|
1802
|
-
const recordVersion = pinnedVersion ? (output) => {
|
|
1803
|
-
const seen = extractVersionToken(output);
|
|
1804
|
-
if (seen)
|
|
1805
|
-
lastManagedInstallVersion.set(toolId, seen);
|
|
1806
|
-
} : undefined;
|
|
1807
|
-
const localBase = path.join(TOOLS_DIR, "node_modules", ".bin", tool.binaryName || tool.id);
|
|
1808
|
-
if (installerPlatform() === "win32") {
|
|
1809
|
-
const cmdPath = `${localBase}.cmd`;
|
|
1810
|
-
try {
|
|
1811
|
-
await fs.access(cmdPath);
|
|
1812
|
-
if (await verifyToolBinary(cmdPath, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
|
|
1813
|
-
return cmdPath;
|
|
1814
|
-
}
|
|
1815
|
-
logSessionStart(`auto-install verify: ${cmdPath} exists but is broken, will reinstall`);
|
|
1816
|
-
} catch {}
|
|
1817
|
-
const exePath = `${localBase}.exe`;
|
|
1818
|
-
try {
|
|
1819
|
-
await fs.access(exePath);
|
|
1820
|
-
if (await verifyToolBinary(exePath, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
|
|
1821
|
-
return exePath;
|
|
1822
|
-
}
|
|
1823
|
-
logSessionStart(`auto-install verify: ${exePath} exists but is broken, will reinstall`);
|
|
1824
|
-
} catch {}
|
|
1825
|
-
}
|
|
1826
|
-
if (installerPlatform() !== "win32") {
|
|
1827
|
-
try {
|
|
1828
|
-
await fs.access(localBase);
|
|
1829
|
-
if (await verifyToolBinary(localBase, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
|
|
1830
|
-
return localBase;
|
|
1831
|
-
}
|
|
1832
|
-
logSessionStart(`auto-install verify: ${localBase} exists but is broken, will reinstall`);
|
|
1833
|
-
} catch {}
|
|
1834
|
-
}
|
|
1835
|
-
if (tool.platformPackage) {
|
|
1836
|
-
const platformBin = resolvePlatformPackageBinary(tool);
|
|
1837
|
-
if (platformBin && await verifyToolBinary(platformBin, undefined, onTransient, getToolVerificationTimeout(tool), tool.checkArgs)) {
|
|
1838
|
-
logSessionStart(`auto-install ${toolId}: resolved platform-package binary at ${platformBin}`);
|
|
1839
|
-
return platformBin;
|
|
1840
|
-
}
|
|
1841
|
-
logSessionStart(`auto-install ${toolId}: platform-package binary not resolved (${process.platform}-${process.arch}, base=${tool.platformPackage.base ?? tool.packageName}) — falling back to PATH/managed install`);
|
|
1842
|
-
}
|
|
1843
|
-
if (tool.installStrategy === "github" || tool.installStrategy === "maven" || tool.installStrategy === "archive") {
|
|
1844
|
-
const githubPath = await findGitHubToolPath(tool.binaryName || tool.id);
|
|
1845
|
-
if (githubPath)
|
|
1846
|
-
return githubPath;
|
|
1847
|
-
}
|
|
1848
|
-
if (await isCommandAvailable(tool.checkCommand)) {
|
|
1849
|
-
if (packageEntryVerification(tool) !== undefined)
|
|
1850
|
-
return tool.checkCommand;
|
|
1851
|
-
let probeStalled = false;
|
|
1852
|
-
const verified = await verifyToolBinary(tool.checkCommand, undefined, () => {
|
|
1853
|
-
probeStalled = true;
|
|
1854
|
-
onTransient();
|
|
1855
|
-
}, getToolVerificationTimeout(tool), tool.checkArgs, undefined, () => {
|
|
1856
|
-
probeStalled = true;
|
|
1857
|
-
});
|
|
1858
|
-
if (verified || probeStalled)
|
|
1859
|
-
return tool.checkCommand;
|
|
1860
|
-
recordDegradationOnce({
|
|
1861
|
-
kind: "installer-path-candidate-unrunnable",
|
|
1862
|
-
subject: toolId,
|
|
1863
|
-
reason: `${tool.checkCommand} on PATH failed its own check (${tool.checkArgs.join(" ")}); ignoring PATH for this tool`
|
|
1864
|
-
});
|
|
1865
|
-
logSessionStart(`auto-install ${toolId}: PATH candidate ${tool.checkCommand} failed ${tool.checkArgs.join(" ")} — ignoring PATH, trying managed install`);
|
|
1866
|
-
}
|
|
1867
|
-
if (tool.installStrategy === "npm") {
|
|
1868
|
-
const npmPath = await findNpmGlobalToolPath(tool.binaryName || tool.id, onTransient, tool.checkArgs, getToolVerificationTimeout(tool));
|
|
1869
|
-
if (npmPath) {
|
|
1870
|
-
return npmPath;
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
if (tool.installStrategy === "pip") {
|
|
1874
|
-
const pipPath = await findPipUserToolPath(tool.binaryName || tool.id, onTransient, tool.checkArgs, getToolVerificationTimeout(tool));
|
|
1875
|
-
if (pipPath) {
|
|
1876
|
-
return pipPath;
|
|
1877
|
-
}
|
|
1878
|
-
}
|
|
1879
|
-
return;
|
|
1880
|
-
}
|
|
1881
|
-
async function findGitHubToolPath(binaryName) {
|
|
1882
|
-
const isWindows = process.platform === "win32";
|
|
1883
|
-
const candidates = isWindows ? [
|
|
1884
|
-
path.join(GITHUB_BIN_DIR, `${binaryName}.exe`),
|
|
1885
|
-
path.join(GITHUB_BIN_DIR, `${binaryName}.bat`),
|
|
1886
|
-
path.join(GITHUB_BIN_DIR, `${binaryName}.cmd`),
|
|
1887
|
-
path.join(GITHUB_BIN_DIR, binaryName)
|
|
1888
|
-
] : [path.join(GITHUB_BIN_DIR, binaryName)];
|
|
1889
|
-
for (const candidate of candidates) {
|
|
1890
|
-
try {
|
|
1891
|
-
await fs.access(candidate);
|
|
1892
|
-
return candidate;
|
|
1893
|
-
} catch {}
|
|
1894
|
-
}
|
|
1895
|
-
return;
|
|
1896
|
-
}
|
|
1897
|
-
export async function findManagedToolBinary(toolId) {
|
|
1898
|
-
const tool = TOOLS.find((candidate) => candidate.id === toolId);
|
|
1899
|
-
if (!tool)
|
|
1900
|
-
return;
|
|
1901
|
-
if (tool.installStrategy !== "github" && tool.installStrategy !== "maven" && tool.installStrategy !== "archive") {
|
|
1902
|
-
return;
|
|
1903
|
-
}
|
|
1904
|
-
return findGitHubToolPath(tool.binaryName || tool.id);
|
|
1905
|
-
}
|
|
1906
|
-
function hasExecutableExtension(name) {
|
|
1907
|
-
return /\.(exe|bat|cmd|ps1)$/i.test(name);
|
|
1908
|
-
}
|
|
1909
|
-
function getGitHubInstalledBinaryName(binaryName, platform, assetName) {
|
|
1910
|
-
if (platform !== "win32")
|
|
1911
|
-
return binaryName;
|
|
1912
|
-
if (hasExecutableExtension(binaryName))
|
|
1913
|
-
return binaryName;
|
|
1914
|
-
if (assetName.endsWith(".bat"))
|
|
1915
|
-
return `${binaryName}.bat`;
|
|
1916
|
-
if (assetName.endsWith(".cmd"))
|
|
1917
|
-
return `${binaryName}.cmd`;
|
|
1918
|
-
return `${binaryName}.exe`;
|
|
1919
|
-
}
|
|
1920
|
-
function launcherForGitHubAsset(spec, assetName) {
|
|
1921
|
-
if (spec.launcher === "php" && assetName.endsWith(".phar"))
|
|
1922
|
-
return "php";
|
|
1923
|
-
if (spec.launcher === "java" && assetName.endsWith(".jar"))
|
|
1924
|
-
return "java";
|
|
1925
|
-
return;
|
|
1926
|
-
}
|
|
1927
|
-
function getArchiveBinaryCandidates(binaryName, platform, assetName) {
|
|
1928
|
-
if (platform !== "win32")
|
|
1929
|
-
return [binaryName];
|
|
1930
|
-
if (hasExecutableExtension(binaryName))
|
|
1931
|
-
return [binaryName];
|
|
1932
|
-
const candidates = new Set;
|
|
1933
|
-
if (assetName.endsWith(".bat"))
|
|
1934
|
-
candidates.add(`${binaryName}.bat`);
|
|
1935
|
-
if (assetName.endsWith(".cmd"))
|
|
1936
|
-
candidates.add(`${binaryName}.cmd`);
|
|
1937
|
-
candidates.add(`${binaryName}.exe`);
|
|
1938
|
-
candidates.add(binaryName);
|
|
1939
|
-
candidates.add(`${binaryName}.bat`);
|
|
1940
|
-
candidates.add(`${binaryName}.cmd`);
|
|
1941
|
-
return [...candidates];
|
|
1942
|
-
}
|
|
1943
|
-
async function findNpmGlobalToolPath(binaryName, onTransient, verificationArgs = ["--version"], verificationTimeoutMs = 1e4) {
|
|
1944
|
-
const isWindows = process.platform === "win32";
|
|
1945
|
-
const binDirs = await getNpmGlobalBinCandidates(onTransient);
|
|
1946
|
-
for (const dir of binDirs) {
|
|
1947
|
-
const candidates = isWindows ? [
|
|
1948
|
-
path.join(dir, `${binaryName}.cmd`),
|
|
1949
|
-
path.join(dir, `${binaryName}.exe`)
|
|
1950
|
-
] : [path.join(dir, binaryName)];
|
|
1951
|
-
for (const candidate of candidates) {
|
|
1952
|
-
try {
|
|
1953
|
-
await fs.access(candidate);
|
|
1954
|
-
if (await verifyToolBinary(candidate, undefined, onTransient, verificationTimeoutMs, verificationArgs)) {
|
|
1955
|
-
return candidate;
|
|
1956
|
-
}
|
|
1957
|
-
} catch {}
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
return;
|
|
1961
|
-
}
|
|
1962
|
-
async function getNpmGlobalBinCandidates(onTransient) {
|
|
1963
|
-
const dirs = [];
|
|
1964
|
-
const seen = new Set;
|
|
1965
|
-
const add = (value) => {
|
|
1966
|
-
if (!value)
|
|
1967
|
-
return;
|
|
1968
|
-
const normalized = path.resolve(value.trim());
|
|
1969
|
-
if (!normalized)
|
|
1970
|
-
return;
|
|
1971
|
-
if (seen.has(normalized))
|
|
1972
|
-
return;
|
|
1973
|
-
seen.add(normalized);
|
|
1974
|
-
dirs.push(normalized);
|
|
1975
|
-
};
|
|
1976
|
-
if (process.platform === "win32") {
|
|
1977
|
-
add(path.join(process.env.APPDATA || "", "npm"));
|
|
1978
|
-
} else {
|
|
1979
|
-
add(path.join(os.homedir(), ".npm-global", "bin"));
|
|
1980
|
-
}
|
|
1981
|
-
for (const dir of await allAvailableGlobalBinDirs(onTransient)) {
|
|
1982
|
-
add(dir);
|
|
1983
|
-
}
|
|
1984
|
-
return dirs;
|
|
1985
|
-
}
|
|
1986
|
-
async function findPipUserToolPath(binaryName, onTransient, verificationArgs = ["--version"], verificationTimeoutMs = 1e4) {
|
|
1987
|
-
const isWindows = installerPlatform() === "win32";
|
|
1988
|
-
const userBaseCandidates = [
|
|
1989
|
-
path.join(getGlobalDmLensDir(), "pip-tools"),
|
|
1990
|
-
path.join(getGlobalDmLensDir(), "pip-user"),
|
|
1991
|
-
...await getPythonUserBaseCandidates()
|
|
1992
|
-
];
|
|
1993
|
-
for (const userBase of userBaseCandidates) {
|
|
1994
|
-
const scriptDirs = [pipScriptsDir(userBase, installerPlatform())];
|
|
1995
|
-
if (isWindows) {
|
|
1996
|
-
try {
|
|
1997
|
-
const children = await fs.readdir(userBase, { withFileTypes: true });
|
|
1998
|
-
for (const entry of children) {
|
|
1999
|
-
if (!entry.isDirectory())
|
|
2000
|
-
continue;
|
|
2001
|
-
if (!/^python\d+$/i.test(entry.name))
|
|
2002
|
-
continue;
|
|
2003
|
-
scriptDirs.push(path.join(userBase, entry.name, "Scripts"));
|
|
2004
|
-
}
|
|
2005
|
-
} catch {}
|
|
2006
|
-
}
|
|
2007
|
-
for (const dir of scriptDirs) {
|
|
2008
|
-
const candidates = isWindows ? [
|
|
2009
|
-
path.join(dir, `${binaryName}.exe`),
|
|
2010
|
-
path.join(dir, `${binaryName}.cmd`),
|
|
2011
|
-
path.join(dir, binaryName)
|
|
2012
|
-
] : [path.join(dir, binaryName)];
|
|
2013
|
-
for (const candidate of candidates) {
|
|
2014
|
-
try {
|
|
2015
|
-
await fs.access(candidate);
|
|
2016
|
-
if (await verifyToolBinary(candidate, undefined, onTransient, verificationTimeoutMs, verificationArgs)) {
|
|
2017
|
-
return candidate;
|
|
2018
|
-
}
|
|
2019
|
-
} catch {}
|
|
2020
|
-
}
|
|
2021
|
-
}
|
|
2022
|
-
}
|
|
2023
|
-
return;
|
|
2024
|
-
}
|
|
2025
|
-
export function userBaseProbeResult(command, code, stdout) {
|
|
2026
|
-
if (stdout.truncated) {
|
|
2027
|
-
recordDegradationOnce({
|
|
2028
|
-
kind: "spawn-output-cap-truncated",
|
|
2029
|
-
subject: `user-base-probe:${command}`,
|
|
2030
|
-
reason: `\`${command} -m site --user-base\` reached the ${DEFAULT_MAX_OUTPUT_BYTES}-byte default cap after ${stdout.observedBytes} bytes; the probed user base is unusable`,
|
|
2031
|
-
metadata: {
|
|
2032
|
-
capBytes: DEFAULT_MAX_OUTPUT_BYTES,
|
|
2033
|
-
capSource: "default",
|
|
2034
|
-
observedBytes: stdout.observedBytes,
|
|
2035
|
-
killed: false
|
|
2036
|
-
}
|
|
2037
|
-
});
|
|
2038
|
-
return "";
|
|
2039
|
-
}
|
|
2040
|
-
return code === 0 ? stdout.text.trim() : "";
|
|
2041
|
-
}
|
|
2042
|
-
async function getPythonUserBaseCandidates() {
|
|
2043
|
-
const candidates = [];
|
|
2044
|
-
const seen = new Set;
|
|
2045
|
-
const add = (value) => {
|
|
2046
|
-
if (!value)
|
|
2047
|
-
return;
|
|
2048
|
-
const normalized = value.trim();
|
|
2049
|
-
if (!normalized)
|
|
2050
|
-
return;
|
|
2051
|
-
if (seen.has(normalized))
|
|
2052
|
-
return;
|
|
2053
|
-
seen.add(normalized);
|
|
2054
|
-
candidates.push(normalized);
|
|
2055
|
-
};
|
|
2056
|
-
if (process.platform === "win32") {
|
|
2057
|
-
add(path.join(process.env.APPDATA || "", "Python"));
|
|
2058
|
-
}
|
|
2059
|
-
const probes = process.platform === "win32" ? [
|
|
2060
|
-
{ command: "py", args: ["-m", "site", "--user-base"] },
|
|
2061
|
-
{ command: "python", args: ["-m", "site", "--user-base"] }
|
|
2062
|
-
] : [
|
|
2063
|
-
{ command: "python3", args: ["-m", "site", "--user-base"] },
|
|
2064
|
-
{ command: "python", args: ["-m", "site", "--user-base"] }
|
|
2065
|
-
];
|
|
2066
|
-
for (const probe of probes) {
|
|
2067
|
-
const userBase = await new Promise((resolve) => {
|
|
2068
|
-
const isWin = process.platform === "win32";
|
|
2069
|
-
const spawnCmd = isWin ? [probe.command, ...probe.args].join(" ") : probe.command;
|
|
2070
|
-
let proc;
|
|
2071
|
-
try {
|
|
2072
|
-
proc = spawn(spawnCmd, isWin ? [] : probe.args, {
|
|
2073
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
2074
|
-
shell: isWin
|
|
2075
|
-
});
|
|
2076
|
-
} catch {
|
|
2077
|
-
resolve("");
|
|
2078
|
-
return;
|
|
2079
|
-
}
|
|
2080
|
-
const stdout = createBoundedOutputSink();
|
|
2081
|
-
proc.stdout?.on("data", (data) => stdout.append(data));
|
|
2082
|
-
proc.on("exit", (code) => resolve(userBaseProbeResult(probe.command, code, stdout)));
|
|
2083
|
-
proc.on("error", () => resolve(""));
|
|
2084
|
-
});
|
|
2085
|
-
add(userBase);
|
|
2086
|
-
}
|
|
2087
|
-
return candidates;
|
|
2088
|
-
}
|
|
2089
|
-
async function githubApiAuthHeaders() {
|
|
2090
|
-
const token = await resolveGitHubToken();
|
|
2091
|
-
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
2092
|
-
}
|
|
2093
|
-
|
|
2094
|
-
class HttpStatusError extends Error {
|
|
2095
|
-
statusCode;
|
|
2096
|
-
constructor(statusCode, url) {
|
|
2097
|
-
super(url ? `HTTP ${statusCode} for ${url}` : `HTTP ${statusCode}`);
|
|
2098
|
-
this.name = "HttpStatusError";
|
|
2099
|
-
this.statusCode = statusCode;
|
|
2100
|
-
}
|
|
2101
|
-
}
|
|
2102
|
-
|
|
2103
|
-
class GitHubHttpError extends HttpStatusError {
|
|
2104
|
-
anonymousRateLimitExhausted;
|
|
2105
|
-
constructor(statusCode, requestHeaders, responseHeaders) {
|
|
2106
|
-
super(statusCode);
|
|
2107
|
-
this.name = "GitHubHttpError";
|
|
2108
|
-
this.message = `GitHub API HTTP ${statusCode}`;
|
|
2109
|
-
this.anonymousRateLimitExhausted = statusCode === 403 && !Object.keys(requestHeaders).some((key) => key.toLowerCase() === "authorization") && readHeader(responseHeaders, "x-ratelimit-remaining") === "0";
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
function readHeader(headers, name) {
|
|
2113
|
-
const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
|
|
2114
|
-
return Array.isArray(entry) ? entry[0]?.trim() : entry?.trim();
|
|
2115
|
-
}
|
|
2116
|
-
function isGitHubApiUrl(url) {
|
|
2117
|
-
try {
|
|
2118
|
-
return new URL(url).host.toLowerCase() === "api.github.com";
|
|
2119
|
-
} catch {
|
|
2120
|
-
return false;
|
|
2121
|
-
}
|
|
2122
|
-
}
|
|
2123
|
-
function sameHost(a, b) {
|
|
2124
|
-
try {
|
|
2125
|
-
return new URL(a).host === new URL(b).host;
|
|
2126
|
-
} catch {
|
|
2127
|
-
return false;
|
|
2128
|
-
}
|
|
2129
|
-
}
|
|
2130
|
-
function httpsGetWithMeta(url, maxRedirects = 5, headers = {}) {
|
|
2131
|
-
return new Promise((resolve, reject) => {
|
|
2132
|
-
https.get(url, { headers: { "User-Agent": "dm-lens/1.0", ...headers } }, (res) => {
|
|
2133
|
-
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
2134
|
-
if (maxRedirects === 0)
|
|
2135
|
-
return reject(new Error("Too many redirects"));
|
|
2136
|
-
const location = res.headers.location;
|
|
2137
|
-
const nextHeaders = sameHost(url, location) ? headers : (() => {
|
|
2138
|
-
const { Authorization: _drop, ...rest } = headers;
|
|
2139
|
-
return rest;
|
|
2140
|
-
})();
|
|
2141
|
-
return resolve(httpsGetWithMeta(location, maxRedirects - 1, nextHeaders));
|
|
2142
|
-
}
|
|
2143
|
-
const etag = typeof res.headers.etag === "string" ? res.headers.etag : undefined;
|
|
2144
|
-
if (res.statusCode === 304) {
|
|
2145
|
-
res.resume();
|
|
2146
|
-
return resolve({ statusCode: 304, body: Buffer.alloc(0), etag });
|
|
2147
|
-
}
|
|
2148
|
-
if (res.statusCode !== 200) {
|
|
2149
|
-
res.resume();
|
|
2150
|
-
return reject(isGitHubApiUrl(url) ? new GitHubHttpError(res.statusCode ?? 0, headers, res.headers) : new HttpStatusError(res.statusCode ?? 0, url));
|
|
2151
|
-
}
|
|
2152
|
-
const chunks = [];
|
|
2153
|
-
res.on("data", (chunk) => chunks.push(chunk));
|
|
2154
|
-
res.on("end", () => resolve({ statusCode: 200, body: Buffer.concat(chunks), etag }));
|
|
2155
|
-
res.on("error", reject);
|
|
2156
|
-
}).on("error", reject);
|
|
2157
|
-
});
|
|
2158
|
-
}
|
|
2159
|
-
async function httpsGet(url, maxRedirects = 5, headers = {}) {
|
|
2160
|
-
const response = await httpsGetWithMeta(url, maxRedirects, headers);
|
|
2161
|
-
if (response.statusCode !== 200) {
|
|
2162
|
-
throw new Error(`HTTP ${response.statusCode} for ${url}`);
|
|
2163
|
-
}
|
|
2164
|
-
return response.body;
|
|
2165
|
-
}
|
|
2166
|
-
function runCommand(command, args, cwd) {
|
|
2167
|
-
return new Promise((resolve) => {
|
|
2168
|
-
let proc;
|
|
2169
|
-
try {
|
|
2170
|
-
proc = spawn(command, args, {
|
|
2171
|
-
cwd,
|
|
2172
|
-
stdio: "ignore",
|
|
2173
|
-
shell: process.platform === "win32"
|
|
2174
|
-
});
|
|
2175
|
-
} catch {
|
|
2176
|
-
resolve(false);
|
|
2177
|
-
return;
|
|
2178
|
-
}
|
|
2179
|
-
proc.on("exit", (code) => resolve(code === 0));
|
|
2180
|
-
proc.on("error", () => resolve(false));
|
|
2181
|
-
});
|
|
2182
|
-
}
|
|
2183
|
-
async function installGitHubTool(tool, prefetchedRelease) {
|
|
2184
|
-
const spec = tool.github;
|
|
2185
|
-
if (!spec)
|
|
2186
|
-
return;
|
|
2187
|
-
const platform = process.platform;
|
|
2188
|
-
const arch = process.arch;
|
|
2189
|
-
const assetSubstring = spec.assetMatch(platform, arch);
|
|
2190
|
-
if (!assetSubstring) {
|
|
2191
|
-
logSessionStart(`github-install ${tool.id}: unsupported platform=${platform} arch=${arch}`);
|
|
2192
|
-
return;
|
|
2193
|
-
}
|
|
2194
|
-
let releaseJson;
|
|
2195
|
-
if (prefetchedRelease) {
|
|
2196
|
-
releaseJson = prefetchedRelease;
|
|
2197
|
-
logSessionStart(`github-install ${tool.id}: using caller-supplied release metadata for ${spec.repo} (${prefetchedRelease.tag_name ?? "untagged"})`);
|
|
2198
|
-
} else {
|
|
2199
|
-
logSessionStart(`github-install ${tool.id}: fetching release metadata from ${spec.repo}`);
|
|
2200
|
-
try {
|
|
2201
|
-
const body = await httpsGet(`https://api.github.com/repos/${spec.repo}/releases/latest`, 5, await githubApiAuthHeaders());
|
|
2202
|
-
releaseJson = JSON.parse(body.toString("utf8"));
|
|
2203
|
-
} catch (err) {
|
|
2204
|
-
const reason = err instanceof GitHubHttpError && err.anonymousRateLimitExhausted ? "GitHub API rate limit exhausted; authenticate with `gh auth login` or set GITHUB_TOKEN/GH_TOKEN and retry" : `release fetch failed: ${err.message}`;
|
|
2205
|
-
if (err instanceof GitHubHttpError && err.anonymousRateLimitExhausted) {
|
|
2206
|
-
recordDegradationOnce({
|
|
2207
|
-
kind: "github-api-rate-limit",
|
|
2208
|
-
subject: tool.id,
|
|
2209
|
-
reason
|
|
2210
|
-
});
|
|
2211
|
-
}
|
|
2212
|
-
logSessionStart(`github-install ${tool.id}: ${reason}`);
|
|
2213
|
-
return;
|
|
2214
|
-
}
|
|
2215
|
-
}
|
|
2216
|
-
if (releaseJson.tag_name) {
|
|
2217
|
-
lastInstallResolutionId.set(tool.id, releaseJson.tag_name);
|
|
2218
|
-
}
|
|
2219
|
-
if (!Array.isArray(releaseJson.assets)) {
|
|
2220
|
-
logSessionStart(`github-install ${tool.id}: release metadata has no assets array`);
|
|
2221
|
-
return;
|
|
2222
|
-
}
|
|
2223
|
-
const asset = pickReleaseAsset(releaseJson.assets, assetSubstring) ?? deriveHashiCorpReleaseAsset(tool, releaseJson.tag_name, assetSubstring);
|
|
2224
|
-
if (!asset) {
|
|
2225
|
-
logSessionStart(`github-install ${tool.id}: no asset matched "${assetSubstring}"`);
|
|
2226
|
-
return;
|
|
2227
|
-
}
|
|
2228
|
-
logSessionStart(`github-install ${tool.id}: downloading ${asset.name}`);
|
|
2229
|
-
debugLog(`[github] downloading ${asset.name} from ${asset.browser_download_url}`);
|
|
2230
|
-
const downloadStart = Date.now();
|
|
2231
|
-
let assetBuffer;
|
|
2232
|
-
try {
|
|
2233
|
-
assetBuffer = await httpsGet(asset.browser_download_url);
|
|
2234
|
-
logSessionStart(`github-install ${tool.id}: downloaded ${asset.name} (${assetBuffer.length} bytes, ${Date.now() - downloadStart}ms)`);
|
|
2235
|
-
} catch (err) {
|
|
2236
|
-
logSessionStart(`github-install ${tool.id}: download failed: ${err.message}`);
|
|
2237
|
-
return;
|
|
2238
|
-
}
|
|
2239
|
-
await fs.mkdir(GITHUB_BIN_DIR, { recursive: true });
|
|
2240
|
-
const binaryName = tool.binaryName ?? tool.id;
|
|
2241
|
-
const isWindows = platform === "win32";
|
|
2242
|
-
const finalBinaryName = getGitHubInstalledBinaryName(binaryName, platform, asset.name);
|
|
2243
|
-
let destPath = path.join(GITHUB_BIN_DIR, finalBinaryName);
|
|
2244
|
-
const assetName = asset.name;
|
|
2245
|
-
const launcherRuntime = launcherForGitHubAsset(spec, assetName);
|
|
2246
|
-
if (launcherRuntime && !await isCommandAvailable(launcherRuntime, ["--version"])) {
|
|
2247
|
-
logSessionStart(`github-install ${tool.id}: ${launcherRuntime} not found — asset requires a runtime launcher`);
|
|
2248
|
-
return;
|
|
2249
|
-
}
|
|
2250
|
-
try {
|
|
2251
|
-
if (launcherRuntime) {
|
|
2252
|
-
const assetPath = path.join(GITHUB_BIN_DIR, `${tool.id}${assetName.endsWith(".phar") ? ".phar" : ".jar"}`);
|
|
2253
|
-
await writeFileAtomicAsync(assetPath, assetBuffer, {
|
|
2254
|
-
bestEffort: false,
|
|
2255
|
-
mode: 488
|
|
2256
|
-
});
|
|
2257
|
-
const launcherName = isWindows ? `${binaryName}.bat` : binaryName;
|
|
2258
|
-
destPath = path.join(GITHUB_BIN_DIR, launcherName);
|
|
2259
|
-
const runtimeTarget = assetName.endsWith(".phar") ? `${tool.id}.phar` : `${tool.id}.jar`;
|
|
2260
|
-
const command = isWindows ? `@echo off\r
|
|
2261
|
-
${launcherRuntime} "%~dp0${runtimeTarget}" %*\r
|
|
2262
|
-
` : `#!/bin/sh
|
|
2263
|
-
exec ${launcherRuntime} "$(dirname "$0")/${runtimeTarget}" "$@"
|
|
2264
|
-
`;
|
|
2265
|
-
await writeFileAtomicAsync(destPath, command, {
|
|
2266
|
-
bestEffort: false,
|
|
2267
|
-
mode: isWindows ? undefined : 488
|
|
2268
|
-
});
|
|
2269
|
-
} else if (assetName.endsWith(".gz") && !assetName.endsWith(".tar.gz")) {
|
|
2270
|
-
const decompressed = await new Promise((resolve, reject) => {
|
|
2271
|
-
const gunzip = createGunzip();
|
|
2272
|
-
const chunks = [];
|
|
2273
|
-
gunzip.on("data", (chunk) => chunks.push(chunk));
|
|
2274
|
-
gunzip.on("end", () => resolve(Buffer.concat(chunks)));
|
|
2275
|
-
gunzip.on("error", reject);
|
|
2276
|
-
gunzip.end(assetBuffer);
|
|
2277
|
-
});
|
|
2278
|
-
await writeFileAtomicAsync(destPath, decompressed, {
|
|
2279
|
-
bestEffort: false,
|
|
2280
|
-
mode: 488
|
|
2281
|
-
});
|
|
2282
|
-
} else if (assetName.endsWith(".tar.gz") || assetName.endsWith(".tar.xz")) {
|
|
2283
|
-
const tmpArchive = path.join(GITHUB_BIN_DIR, `_tmp_${assetName}`);
|
|
2284
|
-
await fs.writeFile(tmpArchive, assetBuffer);
|
|
2285
|
-
const tmpDir = path.join(GITHUB_BIN_DIR, `_tmp_extract_${tool.id}`);
|
|
2286
|
-
await fs.mkdir(tmpDir, { recursive: true });
|
|
2287
|
-
const extracted = await runCommand("tar", ["xf", tmpArchive, "-C", tmpDir], GITHUB_BIN_DIR);
|
|
2288
|
-
await fs.rm(tmpArchive, { force: true });
|
|
2289
|
-
if (!extracted) {
|
|
2290
|
-
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
2291
|
-
logSessionStart(`github-install ${tool.id}: tar extraction failed for ${assetName}`);
|
|
2292
|
-
return;
|
|
2293
|
-
}
|
|
2294
|
-
const tarBinaryName = spec.binaryInArchive ?? binaryName;
|
|
2295
|
-
const tarSrcBinary = await findFirstFileRecursive(tmpDir, getArchiveBinaryCandidates(tarBinaryName, platform, assetName));
|
|
2296
|
-
if (!tarSrcBinary) {
|
|
2297
|
-
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
2298
|
-
logSessionStart(`github-install ${tool.id}: binary candidates ${JSON.stringify(getArchiveBinaryCandidates(tarBinaryName, platform, assetName))} not found in tar ${assetName}`);
|
|
2299
|
-
return;
|
|
2300
|
-
}
|
|
2301
|
-
await fs.rename(tarSrcBinary, destPath);
|
|
2302
|
-
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
2303
|
-
if (!isWindows)
|
|
2304
|
-
await fs.chmod(destPath, 488);
|
|
2305
|
-
} else if (assetName.endsWith(".zip")) {
|
|
2306
|
-
const tmpArchive = path.join(GITHUB_BIN_DIR, `_tmp_${assetName}`);
|
|
2307
|
-
await fs.writeFile(tmpArchive, assetBuffer);
|
|
2308
|
-
const tmpDir = path.join(GITHUB_BIN_DIR, `_tmp_extract_${tool.id}`);
|
|
2309
|
-
await fs.mkdir(tmpDir, { recursive: true });
|
|
2310
|
-
const extracted = isWindows ? await runCommand("powershell", [
|
|
2311
|
-
"-NoProfile",
|
|
2312
|
-
"-Command",
|
|
2313
|
-
`Expand-Archive -LiteralPath '${tmpArchive}' -DestinationPath '${tmpDir}' -Force`
|
|
2314
|
-
], GITHUB_BIN_DIR) : await runCommand("unzip", ["-q", "-o", tmpArchive, "-d", tmpDir], GITHUB_BIN_DIR);
|
|
2315
|
-
await fs.rm(tmpArchive, { force: true });
|
|
2316
|
-
if (!extracted) {
|
|
2317
|
-
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
2318
|
-
logSessionStart(`github-install ${tool.id}: zip extraction failed for ${assetName}`);
|
|
2319
|
-
return;
|
|
2320
|
-
}
|
|
2321
|
-
const archiveBinaryName = spec.binaryInArchive ?? binaryName;
|
|
2322
|
-
const srcBinary = await findFirstFileRecursive(tmpDir, getArchiveBinaryCandidates(archiveBinaryName, platform, assetName));
|
|
2323
|
-
if (!srcBinary) {
|
|
2324
|
-
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
2325
|
-
logSessionStart(`github-install ${tool.id}: binary candidates ${JSON.stringify(getArchiveBinaryCandidates(archiveBinaryName, platform, assetName))} not found in zip ${assetName}`);
|
|
2326
|
-
return;
|
|
2327
|
-
}
|
|
2328
|
-
await fs.rename(srcBinary, destPath);
|
|
2329
|
-
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
2330
|
-
if (!isWindows)
|
|
2331
|
-
await fs.chmod(destPath, 488);
|
|
2332
|
-
} else {
|
|
2333
|
-
await writeFileAtomicAsync(destPath, assetBuffer, {
|
|
2334
|
-
bestEffort: false,
|
|
2335
|
-
mode: 488
|
|
2336
|
-
});
|
|
2337
|
-
}
|
|
2338
|
-
} catch (err) {
|
|
2339
|
-
logSessionStart(`github-install ${tool.id}: install failed: ${err.message}`);
|
|
2340
|
-
return;
|
|
2341
|
-
}
|
|
2342
|
-
for (const extraName of spec.extraAssets?.(platform, arch) ?? []) {
|
|
2343
|
-
const extraAsset = releaseJson.assets.find((a) => a.name === extraName);
|
|
2344
|
-
if (!extraAsset) {
|
|
2345
|
-
logSessionStart(`github-install ${tool.id}: required extra asset "${extraName}" not found`);
|
|
2346
|
-
return;
|
|
2347
|
-
}
|
|
2348
|
-
try {
|
|
2349
|
-
const extraBuffer = await httpsGet(extraAsset.browser_download_url);
|
|
2350
|
-
await writeFileAtomicAsync(path.join(GITHUB_BIN_DIR, extraName), extraBuffer, { bestEffort: false, mode: 488 });
|
|
2351
|
-
logSessionStart(`github-install ${tool.id}: installed extra asset ${extraName} (${extraBuffer.length} bytes)`);
|
|
2352
|
-
} catch (err) {
|
|
2353
|
-
logSessionStart(`github-install ${tool.id}: extra asset ${extraName} download failed: ${err.message}`);
|
|
2354
|
-
return;
|
|
2355
|
-
}
|
|
2356
|
-
}
|
|
2357
|
-
debugLog(`[github] installed ${tool.name} → ${destPath}`);
|
|
2358
|
-
logSessionStart(`github-install ${tool.id}: installed → ${destPath}`);
|
|
2359
|
-
return destPath;
|
|
2360
|
-
}
|
|
2361
|
-
async function findFirstFileRecursive(dir, names) {
|
|
2362
|
-
const wanted = new Set(names.map((name) => name.toLowerCase()));
|
|
2363
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
2364
|
-
for (const entry of entries) {
|
|
2365
|
-
const full = path.join(dir, entry.name);
|
|
2366
|
-
if (entry.isDirectory()) {
|
|
2367
|
-
const found = await findFirstFileRecursive(full, names);
|
|
2368
|
-
if (found)
|
|
2369
|
-
return found;
|
|
2370
|
-
} else if (wanted.has(entry.name.toLowerCase())) {
|
|
2371
|
-
return full;
|
|
2372
|
-
}
|
|
2373
|
-
}
|
|
2374
|
-
return;
|
|
2375
|
-
}
|
|
2376
|
-
const NEEDS_POSTINSTALL = new Set([
|
|
2377
|
-
"@biomejs/biome",
|
|
2378
|
-
"@ast-grep/cli",
|
|
2379
|
-
"@ast-grep/napi",
|
|
2380
|
-
"esbuild",
|
|
2381
|
-
"intelephense"
|
|
2382
|
-
]);
|
|
2383
|
-
export function npmToolNeedsPostinstall(packageName) {
|
|
2384
|
-
return NEEDS_POSTINSTALL.has(packageName);
|
|
2385
|
-
}
|
|
2386
|
-
export function getRefreshableManagedNpmTools() {
|
|
2387
|
-
const seenPackages = new Set;
|
|
2388
|
-
return getRefreshableManagedTools().filter((candidate) => candidate.strategy === "npm").map((candidate) => ({
|
|
2389
|
-
toolId: candidate.toolId,
|
|
2390
|
-
packageName: candidate.packageName,
|
|
2391
|
-
binaryName: candidate.binaryName
|
|
2392
|
-
})).filter((candidate) => {
|
|
2393
|
-
if (seenPackages.has(candidate.packageName))
|
|
2394
|
-
return false;
|
|
2395
|
-
seenPackages.add(candidate.packageName);
|
|
2396
|
-
return true;
|
|
2397
|
-
});
|
|
2398
|
-
}
|
|
2399
|
-
function mavenCoordinate(spec) {
|
|
2400
|
-
return [
|
|
2401
|
-
spec.repoBaseUrl ?? MAVEN_CENTRAL_BASE,
|
|
2402
|
-
spec.groupId,
|
|
2403
|
-
spec.artifactId,
|
|
2404
|
-
spec.version,
|
|
2405
|
-
spec.classifier ?? ""
|
|
2406
|
-
].join(":");
|
|
2407
|
-
}
|
|
2408
|
-
export function getRefreshableManagedTools() {
|
|
2409
|
-
const refreshable = [];
|
|
2410
|
-
for (const tool of TOOLS) {
|
|
2411
|
-
switch (tool.installStrategy) {
|
|
2412
|
-
case "npm": {
|
|
2413
|
-
if (!tool.packageName)
|
|
2414
|
-
continue;
|
|
2415
|
-
if (parsePinnedVersion(tool.packageName) !== undefined)
|
|
2416
|
-
continue;
|
|
2417
|
-
if (!tool.binaryName)
|
|
2418
|
-
continue;
|
|
2419
|
-
refreshable.push({
|
|
2420
|
-
toolId: tool.id,
|
|
2421
|
-
strategy: "npm",
|
|
2422
|
-
checkArgs: tool.checkArgs,
|
|
2423
|
-
packageName: tool.packageName,
|
|
2424
|
-
binaryName: tool.binaryName,
|
|
2425
|
-
verificationTimeoutMs: tool.verificationTimeoutMs,
|
|
2426
|
-
packageEntryOf: packageEntryVerification(tool)
|
|
2427
|
-
});
|
|
2428
|
-
break;
|
|
2429
|
-
}
|
|
2430
|
-
case "pip":
|
|
2431
|
-
case "gem": {
|
|
2432
|
-
if (!tool.packageName)
|
|
2433
|
-
continue;
|
|
2434
|
-
refreshable.push({
|
|
2435
|
-
toolId: tool.id,
|
|
2436
|
-
strategy: tool.installStrategy,
|
|
2437
|
-
checkArgs: tool.checkArgs,
|
|
2438
|
-
packageName: tool.packageName
|
|
2439
|
-
});
|
|
2440
|
-
break;
|
|
2441
|
-
}
|
|
2442
|
-
case "github": {
|
|
2443
|
-
if (!tool.github)
|
|
2444
|
-
continue;
|
|
2445
|
-
refreshable.push({
|
|
2446
|
-
toolId: tool.id,
|
|
2447
|
-
strategy: "github",
|
|
2448
|
-
checkArgs: tool.checkArgs
|
|
2449
|
-
});
|
|
2450
|
-
break;
|
|
2451
|
-
}
|
|
2452
|
-
case "maven": {
|
|
2453
|
-
if (!tool.maven)
|
|
2454
|
-
continue;
|
|
2455
|
-
refreshable.push({
|
|
2456
|
-
toolId: tool.id,
|
|
2457
|
-
strategy: "maven",
|
|
2458
|
-
checkArgs: tool.checkArgs,
|
|
2459
|
-
pinnedCoordinate: mavenCoordinate(tool.maven)
|
|
2460
|
-
});
|
|
2461
|
-
break;
|
|
2462
|
-
}
|
|
2463
|
-
case "archive": {
|
|
2464
|
-
if (!tool.archive)
|
|
2465
|
-
continue;
|
|
2466
|
-
const url = resolveArchiveUrl(tool.archive);
|
|
2467
|
-
if (!url)
|
|
2468
|
-
continue;
|
|
2469
|
-
refreshable.push({
|
|
2470
|
-
toolId: tool.id,
|
|
2471
|
-
strategy: "archive",
|
|
2472
|
-
checkArgs: tool.checkArgs,
|
|
2473
|
-
pinnedCoordinate: url
|
|
2474
|
-
});
|
|
2475
|
-
break;
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
}
|
|
2479
|
-
return refreshable;
|
|
2480
|
-
}
|
|
2481
|
-
export async function isManagedToolPresent(toolId) {
|
|
2482
|
-
const tool = TOOLS.find((t) => t.id === toolId);
|
|
2483
|
-
if (!tool)
|
|
2484
|
-
return false;
|
|
2485
|
-
switch (tool.installStrategy) {
|
|
2486
|
-
case "npm": {
|
|
2487
|
-
if (!tool.packageName)
|
|
2488
|
-
return false;
|
|
2489
|
-
try {
|
|
2490
|
-
await fs.access(path.join(TOOLS_DIR, "node_modules", tool.packageName, "package.json"));
|
|
2491
|
-
return true;
|
|
2492
|
-
} catch {
|
|
2493
|
-
return false;
|
|
2494
|
-
}
|
|
2495
|
-
}
|
|
2496
|
-
case "github":
|
|
2497
|
-
case "maven":
|
|
2498
|
-
return await findGitHubToolPath(tool.binaryName ?? tool.id) !== undefined;
|
|
2499
|
-
case "archive":
|
|
2500
|
-
return await findGitHubToolPath(tool.binaryName ?? tool.id) !== undefined || await getArchiveTreeBundlePath(tool) !== undefined;
|
|
2501
|
-
case "pip":
|
|
2502
|
-
case "gem": {
|
|
2503
|
-
const cached = (await readProbeCache())[toolId];
|
|
2504
|
-
return cached?.path !== undefined && existsSync(cached.path);
|
|
2505
|
-
}
|
|
2506
|
-
default:
|
|
2507
|
-
return false;
|
|
2508
|
-
}
|
|
2509
|
-
}
|
|
2510
|
-
async function probeManagedToolVersion(tool) {
|
|
2511
|
-
const cached = (await readProbeCache())[tool.id];
|
|
2512
|
-
if (!cached?.path || !existsSync(cached.path))
|
|
2513
|
-
return;
|
|
2514
|
-
try {
|
|
2515
|
-
const result = await probeToolAsync(cached.path, tool.checkArgs, {
|
|
2516
|
-
timeout: getToolVerificationTimeout(tool),
|
|
2517
|
-
input: "",
|
|
2518
|
-
ignoreAmbientSignal: true,
|
|
2519
|
-
resourceLabel: `tool-refresh-version:${tool.id}`
|
|
2520
|
-
});
|
|
2521
|
-
if (result.status !== 0)
|
|
2522
|
-
return;
|
|
2523
|
-
return extractVersionToken(`${result.stdout}
|
|
2524
|
-
${result.stderr}`);
|
|
2525
|
-
} catch {
|
|
2526
|
-
return;
|
|
2527
|
-
}
|
|
2528
|
-
}
|
|
2529
|
-
export async function acquireManagedInstallGate(context) {
|
|
2530
|
-
if (process.env.DM_LENS_DISABLE_TOOL_INSTALL === "1") {
|
|
2531
|
-
return {
|
|
2532
|
-
ok: false,
|
|
2533
|
-
reason: "installation disabled by DM_LENS_DISABLE_TOOL_INSTALL=1"
|
|
2534
|
-
};
|
|
2535
|
-
}
|
|
2536
|
-
if (!assertInstallAllowed(context)) {
|
|
2537
|
-
return {
|
|
2538
|
-
ok: false,
|
|
2539
|
-
reason: `project trust: ${projectTrustDenialReason()}`
|
|
2540
|
-
};
|
|
2541
|
-
}
|
|
2542
|
-
const lock = await acquireInstallLock();
|
|
2543
|
-
if (!lock.release) {
|
|
2544
|
-
return { ok: false, reason: lock.reason ?? "install lock held" };
|
|
2545
|
-
}
|
|
2546
|
-
return { ok: true, release: lock.release };
|
|
2547
|
-
}
|
|
2548
|
-
export async function refreshManagedTool(toolId, known = {}) {
|
|
2549
|
-
const tool = TOOLS.find((t) => t.id === toolId);
|
|
2550
|
-
if (!tool) {
|
|
2551
|
-
return { ok: false, unchanged: true, reason: "unknown tool id" };
|
|
2552
|
-
}
|
|
2553
|
-
if (tool.installStrategy !== "github" && tool.installStrategy !== "pip" && tool.installStrategy !== "gem" && tool.installStrategy !== "maven" && tool.installStrategy !== "archive") {
|
|
2554
|
-
return {
|
|
2555
|
-
ok: false,
|
|
2556
|
-
unchanged: true,
|
|
2557
|
-
reason: `strategy ${tool.installStrategy} is not refreshable here`
|
|
2558
|
-
};
|
|
2559
|
-
}
|
|
2560
|
-
const strategy = tool.installStrategy;
|
|
2561
|
-
const gate = await acquireManagedInstallGate(`managed tool refresh: ${toolId}`);
|
|
2562
|
-
if (!gate.ok) {
|
|
2563
|
-
return { ok: false, unchanged: true, declined: true, reason: gate.reason };
|
|
2564
|
-
}
|
|
2565
|
-
try {
|
|
2566
|
-
switch (strategy) {
|
|
2567
|
-
case "github":
|
|
2568
|
-
return await refreshGitHubManagedTool(tool, known);
|
|
2569
|
-
case "pip":
|
|
2570
|
-
case "gem":
|
|
2571
|
-
return await refreshPackageManagerManagedTool(tool);
|
|
2572
|
-
case "maven":
|
|
2573
|
-
case "archive":
|
|
2574
|
-
return await refreshPinnedManagedTool(tool, known);
|
|
2575
|
-
default:
|
|
2576
|
-
return {
|
|
2577
|
-
ok: false,
|
|
2578
|
-
unchanged: true,
|
|
2579
|
-
reason: `strategy ${strategy} is not refreshable here`
|
|
2580
|
-
};
|
|
2581
|
-
}
|
|
2582
|
-
} finally {
|
|
2583
|
-
await gate.release?.();
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
2586
|
-
async function refreshGitHubManagedTool(tool, known) {
|
|
2587
|
-
const spec = tool.github;
|
|
2588
|
-
if (!spec)
|
|
2589
|
-
return { ok: false, unchanged: true, reason: "no github spec" };
|
|
2590
|
-
let response;
|
|
2591
|
-
try {
|
|
2592
|
-
response = await httpsGetWithMeta(`https://api.github.com/repos/${spec.repo}/releases/latest`, 5, {
|
|
2593
|
-
...await githubApiAuthHeaders(),
|
|
2594
|
-
...known.etag ? { "If-None-Match": known.etag } : {}
|
|
2595
|
-
});
|
|
2596
|
-
} catch (err) {
|
|
2597
|
-
const reason = err instanceof GitHubHttpError && err.anonymousRateLimitExhausted ? "GitHub API rate limit exhausted; authenticate with `gh auth login` or set GITHUB_TOKEN/GH_TOKEN and retry" : `release query failed: ${err.message}`;
|
|
2598
|
-
if (err instanceof GitHubHttpError && err.anonymousRateLimitExhausted) {
|
|
2599
|
-
recordDegradationOnce({
|
|
2600
|
-
kind: "github-api-rate-limit",
|
|
2601
|
-
subject: tool.id,
|
|
2602
|
-
reason
|
|
2603
|
-
});
|
|
2604
|
-
}
|
|
2605
|
-
return {
|
|
2606
|
-
ok: false,
|
|
2607
|
-
unchanged: true,
|
|
2608
|
-
reason
|
|
2609
|
-
};
|
|
2610
|
-
}
|
|
2611
|
-
if (response.statusCode === 304) {
|
|
2612
|
-
return {
|
|
2613
|
-
ok: true,
|
|
2614
|
-
unchanged: true,
|
|
2615
|
-
resolutionId: known.resolutionId,
|
|
2616
|
-
etag: known.etag,
|
|
2617
|
-
version: known.version
|
|
2618
|
-
};
|
|
2619
|
-
}
|
|
2620
|
-
let release;
|
|
2621
|
-
try {
|
|
2622
|
-
const parsed = JSON.parse(response.body.toString("utf8"));
|
|
2623
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2624
|
-
throw new Error("release metadata is not an object");
|
|
2625
|
-
}
|
|
2626
|
-
release = parsed;
|
|
2627
|
-
} catch (err) {
|
|
2628
|
-
return {
|
|
2629
|
-
ok: false,
|
|
2630
|
-
unchanged: true,
|
|
2631
|
-
reason: `release metadata unparseable: ${err.message}`
|
|
2632
|
-
};
|
|
2633
|
-
}
|
|
2634
|
-
const tag = release.tag_name;
|
|
2635
|
-
if (!tag) {
|
|
2636
|
-
return { ok: false, unchanged: true, reason: "release has no tag_name" };
|
|
2637
|
-
}
|
|
2638
|
-
if (known.resolutionId === tag) {
|
|
2639
|
-
return {
|
|
2640
|
-
ok: true,
|
|
2641
|
-
unchanged: true,
|
|
2642
|
-
resolutionId: tag,
|
|
2643
|
-
etag: response.etag ?? known.etag,
|
|
2644
|
-
version: known.version ?? tag
|
|
2645
|
-
};
|
|
2646
|
-
}
|
|
2647
|
-
const installed = await installGitHubTool(tool, release);
|
|
2648
|
-
if (!installed) {
|
|
2649
|
-
return {
|
|
2650
|
-
ok: false,
|
|
2651
|
-
unchanged: true,
|
|
2652
|
-
resolutionId: known.resolutionId,
|
|
2653
|
-
version: known.version,
|
|
2654
|
-
reason: `install from release ${tag} failed`
|
|
2655
|
-
};
|
|
2656
|
-
}
|
|
2657
|
-
const verified = await verifyRefreshedArtifact(tool, installed);
|
|
2658
|
-
if (!verified) {
|
|
2659
|
-
return {
|
|
2660
|
-
ok: false,
|
|
2661
|
-
unchanged: false,
|
|
2662
|
-
resolutionId: known.resolutionId,
|
|
2663
|
-
version: known.version,
|
|
2664
|
-
reason: `release ${tag} installed but its binary does not run`
|
|
2665
|
-
};
|
|
2666
|
-
}
|
|
2667
|
-
return {
|
|
2668
|
-
ok: true,
|
|
2669
|
-
unchanged: false,
|
|
2670
|
-
resolutionId: tag,
|
|
2671
|
-
etag: response.etag,
|
|
2672
|
-
version: tag
|
|
2673
|
-
};
|
|
2674
|
-
}
|
|
2675
|
-
async function verifyRefreshedArtifact(tool, installedPath) {
|
|
2676
|
-
if (tool.installStrategy === "archive" && !tool.archive?.launcher) {
|
|
2677
|
-
await updateProbeCache(tool.id, installedPath);
|
|
2678
|
-
return true;
|
|
2679
|
-
}
|
|
2680
|
-
if (!await verifyToolBinary(installedPath, undefined, undefined, getToolVerificationTimeout(tool), tool.checkArgs)) {
|
|
2681
|
-
logSessionStart(`managed-tool-refresh ${tool.id}: refreshed artifact at ${installedPath} failed its verification check`);
|
|
2682
|
-
return false;
|
|
2683
|
-
}
|
|
2684
|
-
await updateProbeCache(tool.id, installedPath);
|
|
2685
|
-
return true;
|
|
2686
|
-
}
|
|
2687
|
-
async function refreshPackageManagerManagedTool(tool) {
|
|
2688
|
-
if (!tool.packageName) {
|
|
2689
|
-
return { ok: false, unchanged: true, reason: "no package name" };
|
|
2690
|
-
}
|
|
2691
|
-
const previous = await probeManagedToolVersion(tool);
|
|
2692
|
-
const installed = tool.installStrategy === "pip" ? await installPipTool(tool.id, tool.packageName, tool.binaryName ?? tool.id, {
|
|
2693
|
-
upgrade: true
|
|
2694
|
-
}) : await installGemTool(tool.id, tool.packageName);
|
|
2695
|
-
if (!installed) {
|
|
2696
|
-
return {
|
|
2697
|
-
ok: false,
|
|
2698
|
-
unchanged: true,
|
|
2699
|
-
version: previous,
|
|
2700
|
-
reason: `${tool.installStrategy} upgrade failed`
|
|
2701
|
-
};
|
|
2702
|
-
}
|
|
2703
|
-
const current = await probeManagedToolVersion(tool);
|
|
2704
|
-
if (previous !== undefined && current === undefined) {
|
|
2705
|
-
return {
|
|
2706
|
-
ok: false,
|
|
2707
|
-
unchanged: false,
|
|
2708
|
-
version: previous,
|
|
2709
|
-
reason: `${tool.installStrategy} upgrade left the binary unable to report a version`
|
|
2710
|
-
};
|
|
2711
|
-
}
|
|
2712
|
-
return {
|
|
2713
|
-
ok: true,
|
|
2714
|
-
unchanged: previous === undefined || current === undefined || previous === current,
|
|
2715
|
-
version: current ?? previous
|
|
2716
|
-
};
|
|
2717
|
-
}
|
|
2718
|
-
async function refreshPinnedManagedTool(tool, known) {
|
|
2719
|
-
const coordinate = tool.installStrategy === "maven" ? tool.maven && mavenCoordinate(tool.maven) : tool.archive && resolveArchiveUrl(tool.archive);
|
|
2720
|
-
if (!coordinate) {
|
|
2721
|
-
return {
|
|
2722
|
-
ok: false,
|
|
2723
|
-
unchanged: true,
|
|
2724
|
-
reason: "no coordinate for this platform"
|
|
2725
|
-
};
|
|
2726
|
-
}
|
|
2727
|
-
if (known.resolutionId === coordinate) {
|
|
2728
|
-
return {
|
|
2729
|
-
ok: true,
|
|
2730
|
-
unchanged: true,
|
|
2731
|
-
resolutionId: coordinate,
|
|
2732
|
-
version: known.version
|
|
2733
|
-
};
|
|
2734
|
-
}
|
|
2735
|
-
const installed = tool.installStrategy === "maven" ? await installMavenTool(tool) : await installArchiveTool(tool);
|
|
2736
|
-
if (!installed) {
|
|
2737
|
-
return {
|
|
2738
|
-
ok: false,
|
|
2739
|
-
unchanged: true,
|
|
2740
|
-
resolutionId: known.resolutionId,
|
|
2741
|
-
version: known.version,
|
|
2742
|
-
reason: `reinstall from ${coordinate} failed`
|
|
2743
|
-
};
|
|
2744
|
-
}
|
|
2745
|
-
if (!await verifyRefreshedArtifact(tool, installed)) {
|
|
2746
|
-
return {
|
|
2747
|
-
ok: false,
|
|
2748
|
-
unchanged: false,
|
|
2749
|
-
resolutionId: known.resolutionId,
|
|
2750
|
-
version: known.version,
|
|
2751
|
-
reason: `reinstall from ${coordinate} produced an artifact that does not run`
|
|
2752
|
-
};
|
|
2753
|
-
}
|
|
2754
|
-
return {
|
|
2755
|
-
ok: true,
|
|
2756
|
-
unchanged: known.resolutionId === undefined,
|
|
2757
|
-
resolutionId: coordinate,
|
|
2758
|
-
version: coordinate
|
|
2759
|
-
};
|
|
2760
|
-
}
|
|
2761
|
-
export function resolveManagedNpmBinPath(binaryName) {
|
|
2762
|
-
const binBase = path.join(TOOLS_DIR, "node_modules", ".bin", binaryName);
|
|
2763
|
-
return installerPlatform() === "win32" ? `${binBase}.cmd` : binBase;
|
|
2764
|
-
}
|
|
2765
|
-
export function invalidateManagedToolResolution(toolId) {
|
|
2766
|
-
resolvedPathCache.delete(toolId);
|
|
2767
|
-
if (_probeCache !== null)
|
|
2768
|
-
delete _probeCache[toolId];
|
|
2769
|
-
markProbeCacheChange(toolId, null);
|
|
2770
|
-
}
|
|
2771
|
-
const MAVEN_CENTRAL_BASE = "https://repo1.maven.org/maven2";
|
|
2772
|
-
async function installMavenTool(tool) {
|
|
2773
|
-
const spec = tool.maven;
|
|
2774
|
-
if (!spec)
|
|
2775
|
-
return;
|
|
2776
|
-
const binaryName = tool.binaryName ?? tool.id;
|
|
2777
|
-
const isWindows = process.platform === "win32";
|
|
2778
|
-
if (!await isCommandAvailable("java", ["-version"])) {
|
|
2779
|
-
logSessionStart(`maven-install ${tool.id}: java not found — a JAR tool can't run without a JRE`);
|
|
2780
|
-
return;
|
|
2781
|
-
}
|
|
2782
|
-
let base = spec.repoBaseUrl ?? MAVEN_CENTRAL_BASE;
|
|
2783
|
-
while (base.endsWith("/"))
|
|
2784
|
-
base = base.slice(0, -1);
|
|
2785
|
-
const groupPath = spec.groupId.replace(/\./g, "/");
|
|
2786
|
-
const jarFile = `${spec.artifactId}-${spec.version}${spec.classifier ? `-${spec.classifier}` : ""}.jar`;
|
|
2787
|
-
const url = `${base}/${groupPath}/${spec.artifactId}/${spec.version}/${jarFile}`;
|
|
2788
|
-
lastInstallResolutionId.set(tool.id, mavenCoordinate(spec));
|
|
2789
|
-
logSessionStart(`maven-install ${tool.id}: downloading ${url}`);
|
|
2790
|
-
let jarBuffer;
|
|
2791
|
-
try {
|
|
2792
|
-
jarBuffer = await httpsGet(url);
|
|
2793
|
-
} catch (err) {
|
|
2794
|
-
logSessionStart(`maven-install ${tool.id}: download failed: ${err.message}`);
|
|
2795
|
-
return;
|
|
2796
|
-
}
|
|
2797
|
-
try {
|
|
2798
|
-
await fs.mkdir(GITHUB_BIN_DIR, { recursive: true });
|
|
2799
|
-
const jarPath = path.join(GITHUB_BIN_DIR, `${tool.id}.jar`);
|
|
2800
|
-
await writeFileAtomicAsync(jarPath, jarBuffer, { bestEffort: false });
|
|
2801
|
-
const launcherName = isWindows ? `${binaryName}.bat` : binaryName;
|
|
2802
|
-
const launcherPath = path.join(GITHUB_BIN_DIR, launcherName);
|
|
2803
|
-
if (isWindows) {
|
|
2804
|
-
await writeFileAtomicAsync(launcherPath, `@echo off\r
|
|
2805
|
-
java -jar "%~dp0${tool.id}.jar" %*\r
|
|
2806
|
-
`, { bestEffort: false });
|
|
2807
|
-
} else {
|
|
2808
|
-
await writeFileAtomicAsync(launcherPath, `#!/bin/sh
|
|
2809
|
-
exec java -jar "$(dirname "$0")/${tool.id}.jar" "$@"
|
|
2810
|
-
`, { bestEffort: false, mode: 488 });
|
|
2811
|
-
}
|
|
2812
|
-
logSessionStart(`maven-install ${tool.id}: installed → ${launcherPath} (${jarBuffer.length} bytes)`);
|
|
2813
|
-
debugLog(`[maven] installed ${tool.name} → ${launcherPath}`);
|
|
2814
|
-
return launcherPath;
|
|
2815
|
-
} catch (err) {
|
|
2816
|
-
logSessionStart(`maven-install ${tool.id}: install failed: ${err.message}`);
|
|
2817
|
-
return;
|
|
2818
|
-
}
|
|
2819
|
-
}
|
|
2820
|
-
export function resolveArchiveUrl(spec, platform = installerPlatform(), arch = process.arch) {
|
|
2821
|
-
return typeof spec.url === "function" ? spec.url(platform, arch) : spec.url;
|
|
2822
|
-
}
|
|
2823
|
-
export function resolveArchiveKind(spec, platform = installerPlatform(), arch = process.arch) {
|
|
2824
|
-
return typeof spec.kind === "function" ? spec.kind(platform, arch) : spec.kind;
|
|
2825
|
-
}
|
|
2826
|
-
function recordArchiveExtractionDegradation(toolId, format, reason) {
|
|
2827
|
-
recordDegradationOnce({
|
|
2828
|
-
kind: "managed-tool-install",
|
|
2829
|
-
subject: `${toolId}:${format}`,
|
|
2830
|
-
reason: `archive extraction ${reason}`
|
|
2831
|
-
});
|
|
2832
|
-
}
|
|
2833
|
-
async function stripExtractedArchiveRoot(dir, components) {
|
|
2834
|
-
for (let i = 0;i < components; i++) {
|
|
2835
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
2836
|
-
const [entry] = entries;
|
|
2837
|
-
if (entries.length !== 1 || !entry?.isDirectory())
|
|
2838
|
-
return false;
|
|
2839
|
-
const root = path.join(dir, entry.name);
|
|
2840
|
-
for (const child of await fs.readdir(root))
|
|
2841
|
-
await fs.rename(path.join(root, child), path.join(dir, child));
|
|
2842
|
-
await fs.rm(root, { recursive: true, force: true });
|
|
2843
|
-
}
|
|
2844
|
-
return true;
|
|
2845
|
-
}
|
|
2846
|
-
export async function swapExtractedDir(toolId, tmpDir, finalDir) {
|
|
2847
|
-
const backupDir = `${finalDir}.rollback`;
|
|
2848
|
-
await fs.rm(backupDir, { recursive: true, force: true });
|
|
2849
|
-
let hadPrevious = false;
|
|
2850
|
-
try {
|
|
2851
|
-
await fs.rename(finalDir, backupDir);
|
|
2852
|
-
hadPrevious = true;
|
|
2853
|
-
} catch (err) {
|
|
2854
|
-
if (err.code !== "ENOENT")
|
|
2855
|
-
throw err;
|
|
2856
|
-
}
|
|
2857
|
-
try {
|
|
2858
|
-
await fs.rename(tmpDir, finalDir);
|
|
2859
|
-
} catch (err) {
|
|
2860
|
-
if (hadPrevious) {
|
|
2861
|
-
try {
|
|
2862
|
-
await fs.rename(backupDir, finalDir);
|
|
2863
|
-
} catch (rollbackErr) {
|
|
2864
|
-
recordDegradationOnce({
|
|
2865
|
-
kind: "managed-tool-refresh",
|
|
2866
|
-
subject: toolId,
|
|
2867
|
-
reason: `swap rollback failed after a failed install: working copy orphaned at ${backupDir} (${rollbackErr.message})`
|
|
2868
|
-
});
|
|
2869
|
-
logSessionStart(`archive-install ${toolId}: swap rollback failed — working copy orphaned at ${backupDir} (${rollbackErr.message})`);
|
|
2870
|
-
}
|
|
2871
|
-
}
|
|
2872
|
-
throw err;
|
|
2873
|
-
}
|
|
2874
|
-
if (hadPrevious) {
|
|
2875
|
-
await fs.rm(backupDir, { recursive: true, force: true }).catch(() => {});
|
|
2876
|
-
}
|
|
2877
|
-
}
|
|
2878
|
-
async function installArchiveTool(tool) {
|
|
2879
|
-
const spec = tool.archive;
|
|
2880
|
-
if (!spec)
|
|
2881
|
-
return;
|
|
2882
|
-
const binaryName = tool.binaryName ?? tool.id;
|
|
2883
|
-
const platform = installerPlatform();
|
|
2884
|
-
const isWindows = platform === "win32";
|
|
2885
|
-
const archiveKind = resolveArchiveKind(spec, platform, process.arch);
|
|
2886
|
-
const url = resolveArchiveUrl(spec, platform, process.arch);
|
|
2887
|
-
if (!url) {
|
|
2888
|
-
logSessionStart(`archive-install ${tool.id}: no archive for ${process.platform}/${process.arch} — unsupported, skipping`);
|
|
2889
|
-
return;
|
|
2890
|
-
}
|
|
2891
|
-
lastInstallResolutionId.set(tool.id, url);
|
|
2892
|
-
logSessionStart(`archive-install ${tool.id}: downloading ${url}`);
|
|
2893
|
-
let archiveBuffer;
|
|
2894
|
-
try {
|
|
2895
|
-
archiveBuffer = await httpsGet(url);
|
|
2896
|
-
} catch (err) {
|
|
2897
|
-
logSessionStart(`archive-install ${tool.id}: download failed: ${err.message}`);
|
|
2898
|
-
return;
|
|
2899
|
-
}
|
|
2900
|
-
const extractName = tool.id;
|
|
2901
|
-
const tmpExtractName = `${extractName}.refresh-tmp`;
|
|
2902
|
-
const archiveName = `${tool.id}.download.${archiveKind === "zip" ? "zip" : "tgz"}`;
|
|
2903
|
-
const extractDir = path.join(TOOLS_DIR, extractName);
|
|
2904
|
-
const tmpExtractDir = path.join(TOOLS_DIR, tmpExtractName);
|
|
2905
|
-
const tmpArchive = path.join(TOOLS_DIR, archiveName);
|
|
2906
|
-
try {
|
|
2907
|
-
await fs.mkdir(TOOLS_DIR, { recursive: true });
|
|
2908
|
-
await fs.rm(tmpExtractDir, { recursive: true, force: true });
|
|
2909
|
-
await fs.mkdir(tmpExtractDir, { recursive: true });
|
|
2910
|
-
await fs.writeFile(tmpArchive, archiveBuffer);
|
|
2911
|
-
const stripComponents = spec.stripComponents ?? 1;
|
|
2912
|
-
const extractionArgs = archiveKind === "zip" && isWindows ? [
|
|
2913
|
-
"-NoProfile",
|
|
2914
|
-
"-Command",
|
|
2915
|
-
`Expand-Archive -LiteralPath '${archiveName}' -DestinationPath '${tmpExtractName}' -Force`
|
|
2916
|
-
] : archiveKind === "zip" ? ["-q", "-o", archiveName, "-d", tmpExtractName] : [
|
|
2917
|
-
"-xzf",
|
|
2918
|
-
archiveName,
|
|
2919
|
-
"-C",
|
|
2920
|
-
tmpExtractName,
|
|
2921
|
-
...stripComponents > 0 ? [`--strip-components=${stripComponents}`] : []
|
|
2922
|
-
];
|
|
2923
|
-
const extractor = archiveKind === "zip" && isWindows ? `${process.env.SystemRoot ?? "C:\\Windows"}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe` : archiveKind === "zip" ? "unzip" : isWindows ? `${process.env.SystemRoot ?? "C:\\Windows"}\\System32\\tar.exe` : "tar";
|
|
2924
|
-
const extractionResult = await safeSpawnAsync(extractor, extractionArgs, {
|
|
2925
|
-
cwd: TOOLS_DIR,
|
|
2926
|
-
timeout: 120000,
|
|
2927
|
-
ignoreAmbientSignal: true,
|
|
2928
|
-
lifetimeCoupled: true
|
|
2929
|
-
});
|
|
2930
|
-
const extracted = {
|
|
2931
|
-
ok: extractionResult.status === 0,
|
|
2932
|
-
reason: extractionResult.spawnFailure?.kind === "tool-not-found" ? "extractor-unavailable" : extractionResult.spawnFailure ? "extractor-error" : "extractor-exit"
|
|
2933
|
-
};
|
|
2934
|
-
await fs.rm(tmpArchive, { force: true });
|
|
2935
|
-
if (!extracted.ok) {
|
|
2936
|
-
recordArchiveExtractionDegradation(tool.id, archiveKind, extracted.reason);
|
|
2937
|
-
logSessionStart(`archive-install ${tool.id}: ${archiveKind} extraction failed (${extracted.reason}) — keeping installed version`);
|
|
2938
|
-
await fs.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {});
|
|
2939
|
-
return;
|
|
2940
|
-
}
|
|
2941
|
-
if (archiveKind === "zip" && !await stripExtractedArchiveRoot(tmpExtractDir, stripComponents)) {
|
|
2942
|
-
recordArchiveExtractionDegradation(tool.id, archiveKind, "layout-invalid");
|
|
2943
|
-
logSessionStart(`archive-install ${tool.id}: ${archiveKind} layout invalid — keeping installed version`);
|
|
2944
|
-
await fs.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {});
|
|
2945
|
-
return;
|
|
2946
|
-
}
|
|
2947
|
-
if (!spec.launcher) {
|
|
2948
|
-
const tmpMarker = spec.treeMarker ? path.join(tmpExtractDir, ...spec.treeMarker.split("/")) : tmpExtractDir;
|
|
2949
|
-
try {
|
|
2950
|
-
await fs.access(tmpMarker);
|
|
2951
|
-
} catch {
|
|
2952
|
-
recordArchiveExtractionDegradation(tool.id, archiveKind, "marker-missing");
|
|
2953
|
-
logSessionStart(`archive-install ${tool.id}: ${archiveKind} marker missing after extraction — keeping installed version`);
|
|
2954
|
-
await fs.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {});
|
|
2955
|
-
return;
|
|
2956
|
-
}
|
|
2957
|
-
await swapExtractedDir(tool.id, tmpExtractDir, extractDir);
|
|
2958
|
-
logSessionStart(`archive-install ${tool.id}: installed tree bundle → ${extractDir} (extracted ${archiveBuffer.length} bytes)`);
|
|
2959
|
-
debugLog(`[archive] installed ${tool.name} bundle → ${extractDir}`);
|
|
2960
|
-
return extractDir;
|
|
2961
|
-
}
|
|
2962
|
-
const launcherParts = spec.launcher.split("/").map((p) => p);
|
|
2963
|
-
const tmpInnerLauncher = path.join(tmpExtractDir, ...launcherParts);
|
|
2964
|
-
const tmpResolvedInner = isWindows ? `${tmpInnerLauncher}.bat` : tmpInnerLauncher;
|
|
2965
|
-
try {
|
|
2966
|
-
await fs.access(tmpResolvedInner);
|
|
2967
|
-
} catch {
|
|
2968
|
-
recordArchiveExtractionDegradation(tool.id, archiveKind, "launcher-missing");
|
|
2969
|
-
logSessionStart(`archive-install ${tool.id}: ${archiveKind} launcher missing after extraction — keeping installed version`);
|
|
2970
|
-
await fs.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {});
|
|
2971
|
-
return;
|
|
2972
|
-
}
|
|
2973
|
-
if (!isWindows)
|
|
2974
|
-
await fs.chmod(tmpResolvedInner, 488).catch(() => {});
|
|
2975
|
-
await swapExtractedDir(tool.id, tmpExtractDir, extractDir);
|
|
2976
|
-
const innerLauncher = path.join(extractDir, ...launcherParts);
|
|
2977
|
-
const resolvedInner = isWindows ? `${innerLauncher}.bat` : innerLauncher;
|
|
2978
|
-
await fs.mkdir(GITHUB_BIN_DIR, { recursive: true });
|
|
2979
|
-
const launcherName = isWindows ? `${binaryName}.bat` : binaryName;
|
|
2980
|
-
const shimPath = path.join(GITHUB_BIN_DIR, launcherName);
|
|
2981
|
-
if (isWindows) {
|
|
2982
|
-
await writeFileAtomicAsync(shimPath, `@echo off\r
|
|
2983
|
-
call "${resolvedInner}" %*\r
|
|
2984
|
-
`, { bestEffort: false });
|
|
2985
|
-
} else {
|
|
2986
|
-
await writeFileAtomicAsync(shimPath, `#!/bin/sh
|
|
2987
|
-
exec "${resolvedInner}" "$@"
|
|
2988
|
-
`, { bestEffort: false, mode: 488 });
|
|
2989
|
-
}
|
|
2990
|
-
logSessionStart(`archive-install ${tool.id}: installed → ${shimPath} (extracted ${archiveBuffer.length} bytes)`);
|
|
2991
|
-
debugLog(`[archive] installed ${tool.name} → ${shimPath}`);
|
|
2992
|
-
return shimPath;
|
|
2993
|
-
} catch (err) {
|
|
2994
|
-
await fs.rm(tmpArchive, { force: true }).catch(() => {});
|
|
2995
|
-
await fs.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {});
|
|
2996
|
-
logSessionStart(`archive-install ${tool.id}: install failed: ${err.message} — keeping installed version`);
|
|
2997
|
-
return;
|
|
2998
|
-
}
|
|
2999
|
-
}
|
|
3000
|
-
function recordPackageManagerInstallException(toolId, strategyLabel, packageName, err) {
|
|
3001
|
-
const message = boundInstallError(err.message);
|
|
3002
|
-
logSessionStart(`auto-install ${strategyLabel} ${packageName}: exception: ${message}`);
|
|
3003
|
-
installFailureReasons.set(toolId, message);
|
|
3004
|
-
return;
|
|
3005
|
-
}
|
|
3006
|
-
const INSTALL_ERROR_LINE_LIMIT = 1000;
|
|
3007
|
-
const INSTALL_CANDIDATE_ERROR_LIMIT = 200;
|
|
3008
|
-
function boundInstallError(value, limit = INSTALL_ERROR_LINE_LIMIT) {
|
|
3009
|
-
const line = value.replace(/[\r\n]+/g, " ").trim();
|
|
3010
|
-
return line.length > limit ? `${line.slice(0, limit - 3)}...` : line;
|
|
3011
|
-
}
|
|
3012
|
-
async function installNpmTool(toolId, packageName, binaryName, verificationArgs = ["--version"], verificationTimeoutMs = 1e4, packageEntryOf) {
|
|
3013
|
-
try {
|
|
3014
|
-
await fs.mkdir(TOOLS_DIR, { recursive: true });
|
|
3015
|
-
const packageJsonPath = path.join(TOOLS_DIR, "package.json");
|
|
3016
|
-
try {
|
|
3017
|
-
await fs.access(packageJsonPath);
|
|
3018
|
-
} catch {
|
|
3019
|
-
await writeFileAtomicAsync(packageJsonPath, JSON.stringify({ name: "dm-lens-tools", version: "1.0.0" }, null, 2), { bestEffort: false });
|
|
3020
|
-
}
|
|
3021
|
-
const isWindows = installerPlatform() === "win32";
|
|
3022
|
-
const pm = await resolveNodePackageManager(TOOLS_DIR);
|
|
3023
|
-
const testNpmScript = process.env.DM_LENS_TEST_MODE === "1" ? process.env.DM_LENS_TEST_NPM_SCRIPT : undefined;
|
|
3024
|
-
const pmCommand = testNpmScript ? process.execPath : pmBinary(pm);
|
|
3025
|
-
const needsScripts = NEEDS_POSTINSTALL.has(packageName);
|
|
3026
|
-
const baseInstallArgs = installArgs(pm, packageName, {
|
|
3027
|
-
ignoreScripts: !needsScripts
|
|
3028
|
-
});
|
|
3029
|
-
const INSTALL_TIMEOUT_MS = Number(process.env.DM_LENS_INSTALL_TIMEOUT_MS) || 120000;
|
|
3030
|
-
const runInstallAttempt = async (args) => {
|
|
3031
|
-
const result = await safeSpawnAsync(pmCommand, args, {
|
|
3032
|
-
cwd: TOOLS_DIR,
|
|
3033
|
-
timeout: INSTALL_TIMEOUT_MS,
|
|
3034
|
-
ignoreAmbientSignal: true,
|
|
3035
|
-
lifetimeCoupled: true
|
|
3036
|
-
});
|
|
3037
|
-
return {
|
|
3038
|
-
ok: result.status === 0,
|
|
3039
|
-
stderr: result.error?.message ?? result.stderr
|
|
3040
|
-
};
|
|
3041
|
-
};
|
|
3042
|
-
let outcome = await runInstallAttempt([
|
|
3043
|
-
...testNpmScript ? [testNpmScript] : [],
|
|
3044
|
-
...baseInstallArgs
|
|
3045
|
-
]);
|
|
3046
|
-
const erResolve = outcome.ok === false && /npm\s+error\s+ERESOLVE|\bERESOLVE\b|could not resolve/i.test(outcome.stderr);
|
|
3047
|
-
if (pm === "npm" && erResolve) {
|
|
3048
|
-
const retryArgs = installArgs(pm, packageName, {
|
|
3049
|
-
ignoreScripts: !needsScripts,
|
|
3050
|
-
legacyPeerDeps: true
|
|
3051
|
-
});
|
|
3052
|
-
logSessionStart(`auto-install npm ${packageName}: retry with --legacy-peer-deps after ERESOLVE`);
|
|
3053
|
-
outcome = await runInstallAttempt([
|
|
3054
|
-
...testNpmScript ? [testNpmScript] : [],
|
|
3055
|
-
...retryArgs
|
|
3056
|
-
]);
|
|
3057
|
-
}
|
|
3058
|
-
if (!outcome.ok) {
|
|
3059
|
-
throw new Error(`Failed to install ${packageName}: ${outcome.stderr}`);
|
|
3060
|
-
}
|
|
3061
|
-
const binBase = path.join(TOOLS_DIR, "node_modules", ".bin", binaryName);
|
|
3062
|
-
const binPath = installerPlatform() === "win32" ? `${binBase}.cmd` : binBase;
|
|
3063
|
-
if (installerPlatform() !== "win32") {
|
|
3064
|
-
try {
|
|
3065
|
-
await fs.chmod(binPath, 488);
|
|
3066
|
-
} catch {}
|
|
3067
|
-
}
|
|
3068
|
-
await new Promise((r) => setTimeout(r, 500));
|
|
3069
|
-
debugLog(`Verifying ${binaryName}...`);
|
|
3070
|
-
let isValid = false;
|
|
3071
|
-
let lastAttemptTransient = false;
|
|
3072
|
-
let lastAttemptInconclusive = false;
|
|
3073
|
-
for (let attempt = 1;attempt <= 3; attempt++) {
|
|
3074
|
-
lastAttemptTransient = false;
|
|
3075
|
-
lastAttemptInconclusive = false;
|
|
3076
|
-
isValid = await verifyToolBinary(binPath, undefined, () => {
|
|
3077
|
-
lastAttemptTransient = true;
|
|
3078
|
-
}, verificationTimeoutMs, verificationArgs, packageEntryOf, () => {
|
|
3079
|
-
lastAttemptInconclusive = true;
|
|
3080
|
-
});
|
|
3081
|
-
if (isValid)
|
|
3082
|
-
break;
|
|
3083
|
-
if (attempt < 3) {
|
|
3084
|
-
logSessionStart(`auto-install verify ${binaryName}: attempt ${attempt} failed, retrying in ${attempt}s`);
|
|
3085
|
-
await new Promise((r) => setTimeout(r, 1000 * attempt));
|
|
3086
|
-
}
|
|
3087
|
-
}
|
|
3088
|
-
if (!isValid && lastAttemptInconclusive) {
|
|
3089
|
-
if (await verifyNpmPackageEntry(binPath, packageName)) {
|
|
3090
|
-
logSessionStart(`auto-install ${packageName}: verification inconclusive (output truncated before the transport-required marker) but the installed tree is intact; keeping installation for re-probe`);
|
|
3091
|
-
return;
|
|
3092
|
-
}
|
|
3093
|
-
logSessionStart(`auto-install ${packageName}: verification inconclusive AND the installed tree is incomplete; cleaning up so the next install can repair it`);
|
|
3094
|
-
}
|
|
3095
|
-
if (!isValid && lastAttemptTransient) {
|
|
3096
|
-
logSessionStart(`auto-install ${packageName}: verification inconclusive (transient); keeping installation for re-probe`);
|
|
3097
|
-
return;
|
|
3098
|
-
}
|
|
3099
|
-
if (!isValid) {
|
|
3100
|
-
logSessionStart(`auto-install ${packageName}: installed but verification failed, cleaning up`);
|
|
3101
|
-
try {
|
|
3102
|
-
const packagePath = path.join(TOOLS_DIR, "node_modules", packageName);
|
|
3103
|
-
await fs.rm(packagePath, { recursive: true, force: true });
|
|
3104
|
-
await fs.rm(binBase, { force: true });
|
|
3105
|
-
if (isWindows) {
|
|
3106
|
-
await fs.rm(`${binBase}.cmd`, { force: true });
|
|
3107
|
-
await fs.rm(`${binBase}.ps1`, { force: true });
|
|
3108
|
-
}
|
|
3109
|
-
} catch {}
|
|
3110
|
-
return;
|
|
3111
|
-
}
|
|
3112
|
-
return binPath;
|
|
3113
|
-
} catch (err) {
|
|
3114
|
-
return recordPackageManagerInstallException(toolId, "npm", packageName, err);
|
|
3115
|
-
}
|
|
3116
|
-
}
|
|
3117
|
-
export function pipCommandCandidates() {
|
|
3118
|
-
return process.platform === "win32" ? ["pip", "py", "python"] : ["pip3", "pip", "python3", "python"];
|
|
3119
|
-
}
|
|
3120
|
-
export function pipScriptsDir(base, platform = installerPlatform()) {
|
|
3121
|
-
return path.join(base, platform === "win32" ? "Scripts" : "bin");
|
|
3122
|
-
}
|
|
3123
|
-
const pipPep668LoggedRefusals = createGenerationMap("installer-pep668-log");
|
|
3124
|
-
async function pipConstraintEnvFor(toolId) {
|
|
3125
|
-
const constraints = TOOLS.find((t) => t.id === toolId)?.pipConstraints;
|
|
3126
|
-
if (!constraints || constraints.length === 0)
|
|
3127
|
-
return {};
|
|
3128
|
-
const directories = [
|
|
3129
|
-
path.join(getGlobalDmLensDir(), "pip-constraints"),
|
|
3130
|
-
path.join(os.tmpdir(), "dm-lens-pip-constraints")
|
|
3131
|
-
];
|
|
3132
|
-
const directory = directories.find((candidate) => !/\s/.test(candidate));
|
|
3133
|
-
if (!directory) {
|
|
3134
|
-
recordDegradationOnce({
|
|
3135
|
-
kind: "pip-constraint-path-unusable",
|
|
3136
|
-
subject: toolId,
|
|
3137
|
-
reason: `no whitespace-free directory for the constraints file (tried ${directories.join(", ")}); installing unconstrained`
|
|
3138
|
-
});
|
|
3139
|
-
return {};
|
|
3140
|
-
}
|
|
3141
|
-
const file = path.join(directory, `${toolId}.txt`);
|
|
3142
|
-
try {
|
|
3143
|
-
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
3144
|
-
await writeFileAtomicAsync(file, `${constraints.join(`
|
|
3145
|
-
`)}
|
|
3146
|
-
`, {
|
|
3147
|
-
bestEffort: false
|
|
3148
|
-
});
|
|
3149
|
-
} catch (err) {
|
|
3150
|
-
recordDegradationOnce({
|
|
3151
|
-
kind: "pip-constraint-file-unwritable",
|
|
3152
|
-
subject: toolId,
|
|
3153
|
-
reason: `${file}: ${err instanceof Error ? err.message : String(err)}`
|
|
3154
|
-
});
|
|
3155
|
-
return {};
|
|
3156
|
-
}
|
|
3157
|
-
logSessionStart(`auto-install pip ${toolId}: constraining resolution with ${constraints.join(", ")} (${file})`);
|
|
3158
|
-
return { PIP_CONSTRAINT: file, UV_CONSTRAINT: file };
|
|
3159
|
-
}
|
|
3160
|
-
async function installPipTool(toolId, packageName, binaryName, options = {}) {
|
|
3161
|
-
try {
|
|
3162
|
-
const isWindows = installerPlatform() === "win32";
|
|
3163
|
-
const verb = options.upgrade ? ["install", "-U"] : ["install"];
|
|
3164
|
-
const pipConstraintEnv = await pipConstraintEnvFor(toolId);
|
|
3165
|
-
const pipCandidates = pipCommandCandidates().map((command) => ({
|
|
3166
|
-
command,
|
|
3167
|
-
args: command === "pip" || command === "pip3" ? [...verb, packageName] : ["-m", "pip", ...verb, packageName]
|
|
3168
|
-
}));
|
|
3169
|
-
const pep668 = /externally-managed-environment/i;
|
|
3170
|
-
const refuse = (strategy, reason) => {
|
|
3171
|
-
if (!pep668.test(reason))
|
|
3172
|
-
return;
|
|
3173
|
-
const subject = `${toolId}:${strategy}`;
|
|
3174
|
-
recordDegradationOnce({
|
|
3175
|
-
kind: "pip-pep668-strategy-refused",
|
|
3176
|
-
subject,
|
|
3177
|
-
reason
|
|
3178
|
-
});
|
|
3179
|
-
const logKey = `${getDegradationLedgerGeneration()}:${subject}`;
|
|
3180
|
-
if (pipPep668LoggedRefusals.current(logKey) === 0) {
|
|
3181
|
-
pipPep668LoggedRefusals.bump(logKey);
|
|
3182
|
-
logSessionStart(`auto-install pip ${packageName}: ${strategy} refused by PEP 668 (${boundInstallError(reason)})`);
|
|
3183
|
-
}
|
|
3184
|
-
};
|
|
3185
|
-
const succeeded = (strategy, binaryPath) => {
|
|
3186
|
-
recordDegradationOnce({
|
|
3187
|
-
kind: "pip-install-strategy-succeeded",
|
|
3188
|
-
subject: `${toolId}:${strategy}`,
|
|
3189
|
-
reason: binaryPath
|
|
3190
|
-
});
|
|
3191
|
-
return binaryPath;
|
|
3192
|
-
};
|
|
3193
|
-
const run = (command, args, env) => {
|
|
3194
|
-
const spawnEnv = Object.keys(pipConstraintEnv).length > 0 ? { ...env ?? process.env, ...pipConstraintEnv } : env;
|
|
3195
|
-
return safeSpawnAsync(command, args, {
|
|
3196
|
-
timeout: 120000,
|
|
3197
|
-
ignoreAmbientSignal: true,
|
|
3198
|
-
lifetimeCoupled: true,
|
|
3199
|
-
cwd: resolveToolCwd("runner", toolId, getGlobalDmLensDir(), {
|
|
3200
|
-
cwd: getGlobalDmLensDir(),
|
|
3201
|
-
suppressTelemetry: true
|
|
3202
|
-
}).cwd,
|
|
3203
|
-
...spawnEnv ? { env: spawnEnv } : {}
|
|
3204
|
-
});
|
|
3205
|
-
};
|
|
3206
|
-
const addBinToPath = async (binDir) => {
|
|
3207
|
-
try {
|
|
3208
|
-
await fs.access(binDir);
|
|
3209
|
-
} catch {
|
|
3210
|
-
return;
|
|
3211
|
-
}
|
|
3212
|
-
const currentPath = process.env.PATH || process.env.Path || "";
|
|
3213
|
-
const separator = isWindows ? ";" : path.delimiter;
|
|
3214
|
-
if (!currentPath.toLowerCase().split(separator).includes(binDir.toLowerCase())) {
|
|
3215
|
-
const updatedPath = `${binDir}${separator}${currentPath}`;
|
|
3216
|
-
process.env.PATH = updatedPath;
|
|
3217
|
-
if (isWindows)
|
|
3218
|
-
process.env.Path = updatedPath;
|
|
3219
|
-
}
|
|
3220
|
-
const names = isWindows ? [`${binaryName}.exe`, `${binaryName}.cmd`, binaryName] : [binaryName];
|
|
3221
|
-
for (const name of names) {
|
|
3222
|
-
const candidate = path.join(binDir, name);
|
|
3223
|
-
try {
|
|
3224
|
-
await fs.access(candidate);
|
|
3225
|
-
return candidate;
|
|
3226
|
-
} catch {}
|
|
3227
|
-
}
|
|
3228
|
-
return;
|
|
3229
|
-
};
|
|
3230
|
-
if (await isCommandAvailable("pipx")) {
|
|
3231
|
-
const result = await run("pipx", options.upgrade ? ["upgrade", packageName] : ["install", "--force", packageName]);
|
|
3232
|
-
const error = (result.error?.message ?? result.stderr).trim();
|
|
3233
|
-
if (result.status === 0) {
|
|
3234
|
-
const location = await run("pipx", [
|
|
3235
|
-
"environment",
|
|
3236
|
-
"--value",
|
|
3237
|
-
"PIPX_BIN_DIR"
|
|
3238
|
-
]);
|
|
3239
|
-
const binDir = location.status === 0 && location.stdout.trim() ? location.stdout.trim() : path.join(os.homedir(), ".local", "bin");
|
|
3240
|
-
const binaryPath = await addBinToPath(binDir);
|
|
3241
|
-
if (binaryPath)
|
|
3242
|
-
return succeeded("pipx", binaryPath);
|
|
3243
|
-
throw new Error(`pipx installed ${packageName} but ${binaryName} is not resolvable`);
|
|
3244
|
-
}
|
|
3245
|
-
refuse("pipx", error);
|
|
3246
|
-
}
|
|
3247
|
-
const pythonCandidates = pipCandidates.filter(({ command }) => command === "python3" || command === "python" || command === "py");
|
|
3248
|
-
const pythonAvailability = await Promise.all(pythonCandidates.map(({ command }) => isCommandAvailable(command)));
|
|
3249
|
-
const availablePythonCandidates = pythonCandidates.filter((_, index) => pythonAvailability[index]);
|
|
3250
|
-
const venvRoot = path.join(getGlobalDmLensDir(), "pip-tools");
|
|
3251
|
-
for (const candidate of availablePythonCandidates) {
|
|
3252
|
-
const venvBin = pipScriptsDir(venvRoot, installerPlatform());
|
|
3253
|
-
let venvPip = path.join(venvBin, isWindows ? "pip.exe" : "pip");
|
|
3254
|
-
try {
|
|
3255
|
-
await fs.access(venvPip);
|
|
3256
|
-
} catch {
|
|
3257
|
-
const created = await run(candidate.command, ["-m", "venv", venvRoot]);
|
|
3258
|
-
const error = (created.error?.message ?? created.stderr).trim();
|
|
3259
|
-
if (created.status !== 0) {
|
|
3260
|
-
refuse("venv", error || "python venv module unavailable");
|
|
3261
|
-
continue;
|
|
3262
|
-
}
|
|
3263
|
-
}
|
|
3264
|
-
try {
|
|
3265
|
-
await fs.access(venvPip);
|
|
3266
|
-
} catch {
|
|
3267
|
-
venvPip = path.join(venvBin, isWindows ? "pip.cmd" : "pip3");
|
|
3268
|
-
try {
|
|
3269
|
-
await fs.access(venvPip);
|
|
3270
|
-
} catch {
|
|
3271
|
-
continue;
|
|
3272
|
-
}
|
|
3273
|
-
}
|
|
3274
|
-
const result = await run(venvPip, [...verb, packageName]);
|
|
3275
|
-
const error = (result.error?.message ?? result.stderr).trim();
|
|
3276
|
-
if (result.status === 0) {
|
|
3277
|
-
const binaryPath = await addBinToPath(venvBin);
|
|
3278
|
-
if (binaryPath)
|
|
3279
|
-
return succeeded("venv", binaryPath);
|
|
3280
|
-
throw new Error(`venv installed ${packageName} but ${binaryName} is not resolvable`);
|
|
3281
|
-
}
|
|
3282
|
-
refuse("venv", error);
|
|
3283
|
-
}
|
|
3284
|
-
const candidateErrors = [];
|
|
3285
|
-
let userRefused = false;
|
|
3286
|
-
for (const candidate of pipCandidates) {
|
|
3287
|
-
const args = candidate.command === "pip" || candidate.command === "pip3" ? [...verb, "--user", packageName] : ["-m", "pip", ...verb, "--user", packageName];
|
|
3288
|
-
const result = await run(candidate.command, args);
|
|
3289
|
-
const error = (result.error?.message ?? result.stderr).trim();
|
|
3290
|
-
if (result.status === 0) {
|
|
3291
|
-
const base = await new Promise((resolve) => {
|
|
3292
|
-
let probe;
|
|
3293
|
-
try {
|
|
3294
|
-
probe = spawn(candidate.command, ["-m", "site", "--user-base"], {
|
|
3295
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
3296
|
-
shell: isWindows
|
|
3297
|
-
});
|
|
3298
|
-
} catch {
|
|
3299
|
-
resolve("");
|
|
3300
|
-
return;
|
|
3301
|
-
}
|
|
3302
|
-
const stdout = createBoundedOutputSink();
|
|
3303
|
-
probe.stdout?.on("data", (data) => stdout.append(data));
|
|
3304
|
-
probe.on("exit", (code) => resolve(userBaseProbeResult(candidate.command, code, stdout)));
|
|
3305
|
-
probe.on("error", () => resolve(""));
|
|
3306
|
-
});
|
|
3307
|
-
const binaryPath = base ? await addBinToPath(pipScriptsDir(base, installerPlatform())) : undefined;
|
|
3308
|
-
return succeeded("user", binaryPath ?? packageName);
|
|
3309
|
-
}
|
|
3310
|
-
const candidateError = `${candidate.command} ${args.join(" ")}: ${boundInstallError(error, INSTALL_CANDIDATE_ERROR_LIMIT)}`;
|
|
3311
|
-
candidateErrors.push(candidateError);
|
|
3312
|
-
if (pep668.test(error)) {
|
|
3313
|
-
userRefused = true;
|
|
3314
|
-
refuse("user", error);
|
|
3315
|
-
}
|
|
3316
|
-
}
|
|
3317
|
-
if (!userRefused)
|
|
3318
|
-
throw new Error(`pip install failed: ${candidateErrors.join(" | ") || "unknown error"}`);
|
|
3319
|
-
const privateBase = path.join(getGlobalDmLensDir(), "pip-user");
|
|
3320
|
-
const privateEnv = { ...process.env, PYTHONUSERBASE: privateBase };
|
|
3321
|
-
for (const candidate of pipCandidates) {
|
|
3322
|
-
const args = candidate.command === "pip" || candidate.command === "pip3" ? [...verb, "--user", "--break-system-packages", packageName] : [
|
|
3323
|
-
"-m",
|
|
3324
|
-
"pip",
|
|
3325
|
-
...verb,
|
|
3326
|
-
"--user",
|
|
3327
|
-
"--break-system-packages",
|
|
3328
|
-
packageName
|
|
3329
|
-
];
|
|
3330
|
-
const result = await run(candidate.command, args, privateEnv);
|
|
3331
|
-
const error = (result.error?.message ?? result.stderr).trim();
|
|
3332
|
-
if (result.status !== 0) {
|
|
3333
|
-
const candidateError = `${candidate.command} ${args.join(" ")}: ${boundInstallError(error, INSTALL_CANDIDATE_ERROR_LIMIT)}`;
|
|
3334
|
-
candidateErrors.push(candidateError);
|
|
3335
|
-
continue;
|
|
3336
|
-
}
|
|
3337
|
-
const binaryPath = await addBinToPath(pipScriptsDir(privateBase, installerPlatform()));
|
|
3338
|
-
if (binaryPath)
|
|
3339
|
-
return succeeded("private-prefix", binaryPath);
|
|
3340
|
-
throw new Error(`private-prefix pip installed ${packageName} but ${binaryName} is not resolvable`);
|
|
3341
|
-
}
|
|
3342
|
-
throw new Error(`pip install failed: ${candidateErrors.join(" | ") || "unknown error"}`);
|
|
3343
|
-
} catch (err) {
|
|
3344
|
-
return recordPackageManagerInstallException(toolId, "pip", packageName, err);
|
|
3345
|
-
}
|
|
3346
|
-
}
|
|
3347
|
-
async function installGemTool(toolId, packageName) {
|
|
3348
|
-
try {
|
|
3349
|
-
const gemResult = await safeSpawnAsync("gem", ["install", packageName, "--no-document"], {
|
|
3350
|
-
timeout: 120000,
|
|
3351
|
-
ignoreAmbientSignal: true,
|
|
3352
|
-
lifetimeCoupled: true
|
|
3353
|
-
});
|
|
3354
|
-
const outcome = {
|
|
3355
|
-
ok: gemResult.status === 0,
|
|
3356
|
-
error: (gemResult.error?.message ?? gemResult.stderr).trim()
|
|
3357
|
-
};
|
|
3358
|
-
if (!outcome.ok) {
|
|
3359
|
-
throw new Error(`Failed to install ${packageName} via gem: ${outcome.error}`);
|
|
3360
|
-
}
|
|
3361
|
-
return packageName;
|
|
3362
|
-
} catch (err) {
|
|
3363
|
-
return recordPackageManagerInstallException(toolId, "gem", packageName, err);
|
|
3364
|
-
}
|
|
3365
|
-
}
|
|
3366
|
-
async function stampInstallResolution(toolId) {
|
|
3367
|
-
const tool = TOOLS.find((t) => t.id === toolId);
|
|
3368
|
-
if (tool?.installStrategy === "npm")
|
|
3369
|
-
return;
|
|
3370
|
-
try {
|
|
3371
|
-
const resolutionId = lastInstallResolutionId.get(toolId);
|
|
3372
|
-
const refresh = await import("./managed-tool-refresh.js");
|
|
3373
|
-
await refresh.stampManagedToolInstall(toolId, resolutionId);
|
|
3374
|
-
} catch {}
|
|
3375
|
-
}
|
|
3376
|
-
async function finishInstallAttempt(toolId, ok, startedAt) {
|
|
3377
|
-
logSessionStart(`auto-install ${toolId}: ${ok ? "success" : "failed"} (${Date.now() - startedAt}ms)`);
|
|
3378
|
-
if (ok)
|
|
3379
|
-
await stampInstallResolution(toolId);
|
|
3380
|
-
noteInstallAttempt(toolId, ok ? "succeeded" : "failed", ok ? undefined : installFailureReasons.get(toolId) ?? "install failed");
|
|
3381
|
-
if (ok) {
|
|
3382
|
-
resetSafeSpawnWindowsCommandCache();
|
|
3383
|
-
try {
|
|
3384
|
-
const { resetMadgeManagedPathMemo } = await import("../dependency-checker.js");
|
|
3385
|
-
resetMadgeManagedPathMemo();
|
|
3386
|
-
} catch (err) {
|
|
3387
|
-
logSessionStart(`auto-install ${toolId}: madge memo reset failed: ${err.message}`);
|
|
3388
|
-
}
|
|
3389
|
-
}
|
|
3390
|
-
return ok;
|
|
3391
|
-
}
|
|
3392
|
-
export async function installTool(toolId) {
|
|
3393
|
-
if (process.env.DM_LENS_DISABLE_TOOL_INSTALL === "1") {
|
|
3394
|
-
installFailureReasons.set(toolId, "installation disabled by DM_LENS_DISABLE_TOOL_INSTALL=1");
|
|
3395
|
-
noteInstallAttempt(toolId, "declined", "installation disabled by DM_LENS_DISABLE_TOOL_INSTALL=1");
|
|
3396
|
-
logSessionStart(`auto-install ${toolId}: refused — DM_LENS_DISABLE_TOOL_INSTALL=1`);
|
|
3397
|
-
return false;
|
|
3398
|
-
}
|
|
3399
|
-
const tool = TOOLS.find((t) => t.id === toolId);
|
|
3400
|
-
if (!tool) {
|
|
3401
|
-
noteInstallAttempt(toolId, "declined", "unknown tool id");
|
|
3402
|
-
logSessionStart(`auto-install ${toolId}: unknown tool id`);
|
|
3403
|
-
return false;
|
|
3404
|
-
}
|
|
3405
|
-
const startedAt = Date.now();
|
|
3406
|
-
logSessionStart(`auto-install ${tool.id}: start strategy=${tool.installStrategy} package=${tool.packageName ?? "n/a"}`);
|
|
3407
|
-
try {
|
|
3408
|
-
switch (tool.installStrategy) {
|
|
3409
|
-
case "npm": {
|
|
3410
|
-
if (!tool.packageName || !tool.binaryName)
|
|
3411
|
-
return false;
|
|
3412
|
-
const npmPath = await installNpmTool(tool.id, tool.packageName, tool.binaryName, tool.checkArgs, getToolVerificationTimeout(tool), packageEntryVerification(tool));
|
|
3413
|
-
if (npmPath !== undefined) {
|
|
3414
|
-
await import("./managed-tool-refresh.js").then((m) => m.stampManagedToolInstalled(tool.id, tool.packageName)).catch(() => {});
|
|
3415
|
-
}
|
|
3416
|
-
return finishInstallAttempt(tool.id, npmPath !== undefined, startedAt);
|
|
3417
|
-
}
|
|
3418
|
-
case "pip": {
|
|
3419
|
-
if (!tool.packageName)
|
|
3420
|
-
return false;
|
|
3421
|
-
const pipPath = await installPipTool(tool.id, tool.packageName, tool.binaryName ?? tool.id);
|
|
3422
|
-
return finishInstallAttempt(tool.id, pipPath !== undefined, startedAt);
|
|
3423
|
-
}
|
|
3424
|
-
case "gem": {
|
|
3425
|
-
if (!tool.packageName)
|
|
3426
|
-
return false;
|
|
3427
|
-
const gemPath = await installGemTool(tool.id, tool.packageName);
|
|
3428
|
-
return finishInstallAttempt(tool.id, gemPath !== undefined, startedAt);
|
|
3429
|
-
}
|
|
3430
|
-
case "github": {
|
|
3431
|
-
if (!tool.github)
|
|
3432
|
-
return false;
|
|
3433
|
-
if (!tool.github.assetMatch(process.platform, process.arch)) {
|
|
3434
|
-
const reason = `unsupported platform=${process.platform} arch=${process.arch}`;
|
|
3435
|
-
noteInstallAttempt(tool.id, "unavailable", reason);
|
|
3436
|
-
logSessionStart(`auto-install ${tool.id}: ${reason}`);
|
|
3437
|
-
return false;
|
|
3438
|
-
}
|
|
3439
|
-
const ghPath = await installGitHubTool(tool);
|
|
3440
|
-
return finishInstallAttempt(tool.id, ghPath !== undefined, startedAt);
|
|
3441
|
-
}
|
|
3442
|
-
case "maven": {
|
|
3443
|
-
if (!tool.maven)
|
|
3444
|
-
return false;
|
|
3445
|
-
const mavenPath = await installMavenTool(tool);
|
|
3446
|
-
return finishInstallAttempt(tool.id, mavenPath !== undefined, startedAt);
|
|
3447
|
-
}
|
|
3448
|
-
case "archive": {
|
|
3449
|
-
if (!tool.archive)
|
|
3450
|
-
return false;
|
|
3451
|
-
if (!resolveArchiveUrl(tool.archive)) {
|
|
3452
|
-
const reason = `unsupported platform=${process.platform} arch=${process.arch}`;
|
|
3453
|
-
noteInstallAttempt(tool.id, "unavailable", reason);
|
|
3454
|
-
logSessionStart(`auto-install ${tool.id}: ${reason}`);
|
|
3455
|
-
return false;
|
|
3456
|
-
}
|
|
3457
|
-
const archivePath = await installArchiveTool(tool);
|
|
3458
|
-
return finishInstallAttempt(tool.id, archivePath !== undefined, startedAt);
|
|
3459
|
-
}
|
|
3460
|
-
default:
|
|
3461
|
-
logSessionStart(`auto-install ${tool.id}: unsupported strategy`);
|
|
3462
|
-
return false;
|
|
3463
|
-
}
|
|
3464
|
-
} catch (err) {
|
|
3465
|
-
logSessionStart(`auto-install ${tool.id}: exception ${err.message} (${Date.now() - startedAt}ms)`);
|
|
3466
|
-
return false;
|
|
3467
|
-
}
|
|
3468
|
-
}
|
|
3469
|
-
export async function ensureTool(toolId, opts) {
|
|
3470
|
-
if (opts?.allowInstall !== false && !assertInstallAllowed(`managed tool ensure: ${toolId}`)) {
|
|
3471
|
-
logSessionStart(`auto-install ensure ${toolId}: install gated — ${projectTrustDenialReason()}; discovery only`);
|
|
3472
|
-
const denialReason = projectTrustDenialReason();
|
|
3473
|
-
const discovered = await ensureToolResolved(toolId, {
|
|
3474
|
-
...opts,
|
|
3475
|
-
allowInstall: false
|
|
3476
|
-
});
|
|
3477
|
-
noteInstallAttempt(toolId, "declined", `project trust: ${denialReason}`);
|
|
3478
|
-
return discovered;
|
|
3479
|
-
}
|
|
3480
|
-
return ensureToolResolved(toolId, opts);
|
|
3481
|
-
}
|
|
3482
|
-
async function ensureToolResolved(toolId, opts) {
|
|
3483
|
-
installFailureReasons.delete(toolId);
|
|
3484
|
-
installAttempts.delete(toolId);
|
|
3485
|
-
lastEnsureResolutionSource.delete(toolId);
|
|
3486
|
-
const cacheResolvedPath = (result) => {
|
|
3487
|
-
if (result) {
|
|
3488
|
-
resolvedPathCache.set(toolId, result);
|
|
3489
|
-
updateProbeCache(toolId, result, wasLastResolveTransient(toolId));
|
|
3490
|
-
}
|
|
3491
|
-
return result;
|
|
3492
|
-
};
|
|
3493
|
-
if (opts?.forceReinstall) {
|
|
3494
|
-
const ensureStartMs = Date.now();
|
|
3495
|
-
logSessionStart(`auto-install ensure ${toolId}: force reinstall — clearing caches`);
|
|
3496
|
-
resolvedPathCache.delete(toolId);
|
|
3497
|
-
try {
|
|
3498
|
-
const probeCache = await readProbeCache();
|
|
3499
|
-
delete probeCache[toolId];
|
|
3500
|
-
markProbeCacheChange(toolId, null);
|
|
3501
|
-
} catch {}
|
|
3502
|
-
if (opts.allowInstall === false) {
|
|
3503
|
-
noteInstallAttempt(toolId, "declined", "install disabled by caller");
|
|
3504
|
-
logSessionStart(`auto-install ensure ${toolId}: force reinstall blocked — install disabled, discovery only (${Date.now() - ensureStartMs}ms)`);
|
|
3505
|
-
return cacheResolvedPath(await getToolPath(toolId));
|
|
3506
|
-
}
|
|
3507
|
-
if (process.env.DM_LENS_DISABLE_TOOL_INSTALL === "1") {
|
|
3508
|
-
installFailureReasons.set(toolId, "installation disabled by DM_LENS_DISABLE_TOOL_INSTALL=1");
|
|
3509
|
-
noteInstallAttempt(toolId, "declined", "installation disabled by DM_LENS_DISABLE_TOOL_INSTALL=1");
|
|
3510
|
-
logSessionStart(`auto-install ensure ${toolId}: refused — DM_LENS_DISABLE_TOOL_INSTALL=1`);
|
|
3511
|
-
return;
|
|
3512
|
-
}
|
|
3513
|
-
const lock = await acquireInstallLock();
|
|
3514
|
-
if (!lock.release) {
|
|
3515
|
-
noteInstallAttempt(toolId, "skipped", lock.reason ?? "install lock held");
|
|
3516
|
-
logSessionStart(`auto-install ensure ${toolId}: ${lock.reason}`);
|
|
3517
|
-
return;
|
|
3518
|
-
}
|
|
3519
|
-
let installed;
|
|
3520
|
-
try {
|
|
3521
|
-
installed = await installTool(toolId);
|
|
3522
|
-
} finally {
|
|
3523
|
-
await lock.release();
|
|
3524
|
-
}
|
|
3525
|
-
if (!installed) {
|
|
3526
|
-
noteInstallAttemptIfUnrecorded(toolId, "failed", "install failed");
|
|
3527
|
-
logSessionStart(`auto-install ensure ${toolId}: force reinstall failed (${Date.now() - ensureStartMs}ms)`);
|
|
3528
|
-
return;
|
|
3529
|
-
}
|
|
3530
|
-
const result = cacheResolvedPath(await getToolPath(toolId));
|
|
3531
|
-
if (result) {
|
|
3532
|
-
logSessionStart(`auto-install ensure ${toolId}: force reinstall success at ${result} (${Date.now() - ensureStartMs}ms)`);
|
|
3533
|
-
}
|
|
3534
|
-
return result;
|
|
3535
|
-
}
|
|
3536
|
-
const cached = resolvedPathCache.get(toolId);
|
|
3537
|
-
if (cached) {
|
|
3538
|
-
if (!isFullyQualified(cached)) {
|
|
3539
|
-
lastEnsureResolutionSource.set(toolId, "session-cache");
|
|
3540
|
-
return cached;
|
|
3541
|
-
}
|
|
3542
|
-
try {
|
|
3543
|
-
await fs.access(cached);
|
|
3544
|
-
lastEnsureResolutionSource.set(toolId, "session-cache");
|
|
3545
|
-
return cached;
|
|
3546
|
-
} catch {}
|
|
3547
|
-
resolvedPathCache.delete(toolId);
|
|
3548
|
-
const probeCache = await readProbeCache();
|
|
3549
|
-
delete probeCache[toolId];
|
|
3550
|
-
markProbeCacheChange(toolId, null);
|
|
3551
|
-
logSessionStart(`auto-install ensure ${toolId}: cached path disappeared; re-probing`);
|
|
3552
|
-
}
|
|
3553
|
-
const diskCached = await checkProbeCache(toolId);
|
|
3554
|
-
if (diskCached) {
|
|
3555
|
-
resolvedPathCache.set(toolId, diskCached);
|
|
3556
|
-
lastEnsureResolutionSource.set(toolId, "probe-cache");
|
|
3557
|
-
logSessionStart(`auto-install ensure ${toolId}: probe cache hit → ${diskCached}`);
|
|
3558
|
-
return diskCached;
|
|
3559
|
-
}
|
|
3560
|
-
const inFlightKey = opts?.allowInstall === false ? `${toolId}:discovery-only` : toolId;
|
|
3561
|
-
const inFlight = ensureInFlight.get(inFlightKey);
|
|
3562
|
-
if (inFlight) {
|
|
3563
|
-
logSessionStart(`auto-install ensure ${toolId}: waiting for in-flight ensure (${inFlightKey})`);
|
|
3564
|
-
return inFlight;
|
|
3565
|
-
}
|
|
3566
|
-
const ensureStartMs = Date.now();
|
|
3567
|
-
const ensurePromise = (async () => {
|
|
3568
|
-
logSessionStart(`auto-install ensure ${toolId}: start`);
|
|
3569
|
-
const existingPath = await getToolPath(toolId);
|
|
3570
|
-
if (existingPath) {
|
|
3571
|
-
const tool = TOOLS.find((t) => t.id === toolId);
|
|
3572
|
-
const pinnedVersion = tool?.installStrategy === "npm" && tool.packageName ? parsePinnedVersion(tool.packageName) : undefined;
|
|
3573
|
-
if (pinnedVersion) {
|
|
3574
|
-
const seenVersion = lastManagedInstallVersion.get(toolId);
|
|
3575
|
-
if (seenVersion && seenVersion !== pinnedVersion) {
|
|
3576
|
-
lastManagedInstallVersion.delete(toolId);
|
|
3577
|
-
logSessionStart(`auto-install ensure ${toolId}: version drift (installed ${seenVersion} != pinned ${pinnedVersion}) — forcing reinstall (${Date.now() - ensureStartMs}ms)`);
|
|
3578
|
-
return ensureTool(toolId, {
|
|
3579
|
-
forceReinstall: true,
|
|
3580
|
-
allowInstall: opts?.allowInstall
|
|
3581
|
-
});
|
|
3582
|
-
}
|
|
3583
|
-
}
|
|
3584
|
-
resolvedPathCache.set(toolId, existingPath);
|
|
3585
|
-
updateProbeCache(toolId, existingPath, wasLastResolveTransient(toolId));
|
|
3586
|
-
lastEnsureResolutionSource.set(toolId, "path");
|
|
3587
|
-
logSessionStart(`auto-install ensure ${toolId}: already available at ${existingPath} (${Date.now() - ensureStartMs}ms)`);
|
|
3588
|
-
return existingPath;
|
|
3589
|
-
}
|
|
3590
|
-
if (opts?.allowInstall === false) {
|
|
3591
|
-
noteInstallAttempt(toolId, "declined", "install disabled by caller");
|
|
3592
|
-
logSessionStart(`auto-install ensure ${toolId}: install disabled — discovery only, not found (${Date.now() - ensureStartMs}ms)`);
|
|
3593
|
-
return;
|
|
3594
|
-
}
|
|
3595
|
-
if (process.env.DM_LENS_DISABLE_TOOL_INSTALL === "1") {
|
|
3596
|
-
installFailureReasons.set(toolId, "installation disabled by DM_LENS_DISABLE_TOOL_INSTALL=1");
|
|
3597
|
-
noteInstallAttempt(toolId, "declined", "installation disabled by DM_LENS_DISABLE_TOOL_INSTALL=1");
|
|
3598
|
-
logSessionStart(`auto-install ensure ${toolId}: refused — DM_LENS_DISABLE_TOOL_INSTALL=1 (${Date.now() - ensureStartMs}ms)`);
|
|
3599
|
-
return;
|
|
3600
|
-
}
|
|
3601
|
-
const lock = await acquireInstallLock();
|
|
3602
|
-
if (!lock.release) {
|
|
3603
|
-
installFailureReasons.set(toolId, lock.reason ?? "install lock failed");
|
|
3604
|
-
noteInstallAttempt(toolId, "skipped", lock.reason ?? "install lock held");
|
|
3605
|
-
logSessionStart(`auto-install ensure ${toolId}: ${lock.reason}`);
|
|
3606
|
-
return;
|
|
3607
|
-
}
|
|
3608
|
-
let installed;
|
|
3609
|
-
try {
|
|
3610
|
-
const installedByPeer = await getToolPath(toolId);
|
|
3611
|
-
if (installedByPeer) {
|
|
3612
|
-
noteInstallAttempt(toolId, "succeeded", "installed by a concurrent process");
|
|
3613
|
-
resolvedPathCache.set(toolId, installedByPeer);
|
|
3614
|
-
updateProbeCache(toolId, installedByPeer, wasLastResolveTransient(toolId));
|
|
3615
|
-
return installedByPeer;
|
|
3616
|
-
}
|
|
3617
|
-
installed = await installTool(toolId);
|
|
3618
|
-
} finally {
|
|
3619
|
-
await lock.release();
|
|
3620
|
-
}
|
|
3621
|
-
if (!installed) {
|
|
3622
|
-
noteInstallAttemptIfUnrecorded(toolId, "failed", "install failed");
|
|
3623
|
-
logSessionStart(`auto-install ensure ${toolId}: unavailable (${Date.now() - ensureStartMs}ms)`);
|
|
3624
|
-
return;
|
|
3625
|
-
}
|
|
3626
|
-
const result = await getToolPath(toolId);
|
|
3627
|
-
if (result) {
|
|
3628
|
-
resolvedPathCache.set(toolId, result);
|
|
3629
|
-
updateProbeCache(toolId, result, wasLastResolveTransient(toolId));
|
|
3630
|
-
logSessionStart(`auto-install ensure ${toolId}: success at ${result} (${Date.now() - ensureStartMs}ms)`);
|
|
3631
|
-
} else {
|
|
3632
|
-
logSessionStart(`auto-install ensure ${toolId}: unavailable (${Date.now() - ensureStartMs}ms)`);
|
|
3633
|
-
}
|
|
3634
|
-
return result;
|
|
3635
|
-
})();
|
|
3636
|
-
ensureInFlight.set(inFlightKey, ensurePromise);
|
|
3637
|
-
try {
|
|
3638
|
-
return await ensurePromise;
|
|
3639
|
-
} finally {
|
|
3640
|
-
if (ensureInFlight.get(inFlightKey) === ensurePromise) {
|
|
3641
|
-
ensureInFlight.delete(inFlightKey);
|
|
3642
|
-
}
|
|
3643
|
-
}
|
|
3644
|
-
}
|
|
3645
|
-
export async function getToolEnvironment() {
|
|
3646
|
-
const localBin = path.join(TOOLS_DIR, "node_modules", ".bin");
|
|
3647
|
-
const currentPath = process.env.PATH || process.env.Path || process.env.path || "";
|
|
3648
|
-
const separator = process.platform === "win32" ? ";" : ":";
|
|
3649
|
-
const nodeDir = path.dirname(process.execPath);
|
|
3650
|
-
const withNode = nodeDir ? `${nodeDir}${separator}${currentPath}` : currentPath;
|
|
3651
|
-
const augmentedPath = `${GITHUB_BIN_DIR}${separator}${localBin}${separator}${withNode}`;
|
|
3652
|
-
const env = {
|
|
3653
|
-
...process.env,
|
|
3654
|
-
PATH: augmentedPath
|
|
3655
|
-
};
|
|
3656
|
-
if (process.platform === "win32") {
|
|
3657
|
-
env.Path = augmentedPath;
|
|
3658
|
-
}
|
|
3659
|
-
return env;
|
|
3660
|
-
}
|
|
3661
|
-
export async function checkAllTools() {
|
|
3662
|
-
const results = [];
|
|
3663
|
-
for (const tool of TOOLS) {
|
|
3664
|
-
const path = await getToolPath(tool.id);
|
|
3665
|
-
results.push({
|
|
3666
|
-
id: tool.id,
|
|
3667
|
-
name: tool.name,
|
|
3668
|
-
installed: path !== undefined,
|
|
3669
|
-
path
|
|
3670
|
-
});
|
|
3671
|
-
}
|
|
3672
|
-
return results;
|
|
3673
|
-
}
|
|
3674
|
-
export function isKnownToolId(toolId) {
|
|
3675
|
-
return TOOLS.some((tool) => tool.id === toolId);
|
|
3676
|
-
}
|
|
3677
|
-
export function getToolInstallStrategy(toolId) {
|
|
3678
|
-
return TOOLS.find((tool) => tool.id === toolId)?.installStrategy;
|
|
3679
|
-
}
|
|
3680
|
-
export const GITHUB_TOOLS = [
|
|
3681
|
-
"cljfmt",
|
|
3682
|
-
"php-cs-fixer",
|
|
3683
|
-
"shellcheck",
|
|
3684
|
-
"shfmt",
|
|
3685
|
-
"rust-analyzer",
|
|
3686
|
-
"golangci-lint",
|
|
3687
|
-
"ktlint",
|
|
3688
|
-
"actionlint",
|
|
3689
|
-
"zizmor",
|
|
3690
|
-
"typos-lsp",
|
|
3691
|
-
"tflint",
|
|
3692
|
-
"terragrunt",
|
|
3693
|
-
"terraform-ls",
|
|
3694
|
-
"zls",
|
|
3695
|
-
"hadolint",
|
|
3696
|
-
"helm",
|
|
3697
|
-
"gitleaks",
|
|
3698
|
-
"taplo",
|
|
3699
|
-
"vale",
|
|
3700
|
-
"opengrep",
|
|
3701
|
-
"deno",
|
|
3702
|
-
"clojure-lsp",
|
|
3703
|
-
"cue",
|
|
3704
|
-
"gleam",
|
|
3705
|
-
"typstyle",
|
|
3706
|
-
"tinymist",
|
|
3707
|
-
"marksman",
|
|
3708
|
-
"expert"
|
|
3709
|
-
];
|
|
3710
|
-
export function resolveGitHubAsset(toolId, platform, arch) {
|
|
3711
|
-
const tool = TOOLS.find((t) => t.id === toolId);
|
|
3712
|
-
return tool?.github?.assetMatch(platform, arch);
|
|
3713
|
-
}
|
|
3714
|
-
export function resolveGitHubAssetLauncher(toolId, _platform, assetName) {
|
|
3715
|
-
const tool = TOOLS.find((t) => t.id === toolId);
|
|
3716
|
-
return tool?.github ? launcherForGitHubAsset(tool.github, assetName) : undefined;
|
|
3717
|
-
}
|
|
3718
|
-
export function resolveGitHubInstalledBinaryName(toolId, platform, assetName) {
|
|
3719
|
-
const tool = TOOLS.find((t) => t.id === toolId);
|
|
3720
|
-
if (!tool)
|
|
3721
|
-
return;
|
|
3722
|
-
return getGitHubInstalledBinaryName(tool.binaryName ?? tool.id, platform, assetName);
|
|
3723
|
-
}
|
|
3724
|
-
export function resolveGitHubArchiveBinaryCandidates(toolId, platform, assetName) {
|
|
3725
|
-
const tool = TOOLS.find((t) => t.id === toolId);
|
|
3726
|
-
if (!tool)
|
|
3727
|
-
return;
|
|
3728
|
-
const binaryName = tool.github?.binaryInArchive ?? tool.binaryName ?? tool.id;
|
|
3729
|
-
return getArchiveBinaryCandidates(binaryName, platform, assetName);
|
|
3730
|
-
}
|
|
3731
|
-
const ASSET_SIDECAR_SUFFIXES = [
|
|
3732
|
-
".asc",
|
|
3733
|
-
".sig",
|
|
3734
|
-
".minisig",
|
|
3735
|
-
".pem",
|
|
3736
|
-
".cert",
|
|
3737
|
-
".sbom",
|
|
3738
|
-
".sha256",
|
|
3739
|
-
".sha256sum",
|
|
3740
|
-
".md5"
|
|
3741
|
-
];
|
|
3742
|
-
function isAssetSidecar(name) {
|
|
3743
|
-
const lower = name.toLowerCase();
|
|
3744
|
-
return ASSET_SIDECAR_SUFFIXES.some((suffix) => lower.endsWith(suffix));
|
|
3745
|
-
}
|
|
3746
|
-
export function pickReleaseAsset(assets, assetSubstring) {
|
|
3747
|
-
return assets.find((a) => a.name === assetSubstring) ?? assets.find((a) => a.name.includes(assetSubstring) && !isAssetSidecar(a.name));
|
|
3748
|
-
}
|
|
3749
|
-
function deriveHashiCorpReleaseAsset(tool, tagName, assetSubstring) {
|
|
3750
|
-
const product = tool.github?.hashiCorpReleaseProduct;
|
|
3751
|
-
if (!product || !tagName)
|
|
3752
|
-
return;
|
|
3753
|
-
const version = tagName.replace(/^v/, "").trim();
|
|
3754
|
-
if (!version)
|
|
3755
|
-
return;
|
|
3756
|
-
const assetName = `${product}_${version}_${assetSubstring}`;
|
|
3757
|
-
return {
|
|
3758
|
-
name: assetName,
|
|
3759
|
-
browser_download_url: `https://releases.hashicorp.com/${product}/${version}/${assetName}`
|
|
3760
|
-
};
|
|
3761
|
-
}
|
|
3762
|
-
export function resolveDerivedHashiCorpReleaseAsset(toolId, tagName, platform, arch) {
|
|
3763
|
-
const tool = TOOLS.find((t) => t.id === toolId);
|
|
3764
|
-
if (!tool)
|
|
3765
|
-
return;
|
|
3766
|
-
const assetSubstring = tool.github?.assetMatch(platform, arch);
|
|
3767
|
-
if (!assetSubstring)
|
|
3768
|
-
return;
|
|
3769
|
-
return deriveHashiCorpReleaseAsset(tool, tagName, assetSubstring);
|
|
3770
|
-
}
|