@herbertgao/pi-extensions 2026.10.0 → 2026.10.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/README.md +16 -12
- package/THIRD_PARTY_NOTICES.md +3 -3
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +2 -2
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +2 -2
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +1 -1
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-help.md +1 -1
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +2 -2
- package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +1 -1
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/index.js +4 -0
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail-frontmatter.cjs +43 -3
- package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail.mjs +142 -59
- package/node_modules/@dietrichgebert/ponytail/.qoder/rules/ponytail.md +21 -21
- package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +1 -1
- package/node_modules/@dietrichgebert/ponytail/AGENTS.md +21 -21
- package/node_modules/@dietrichgebert/ponytail/README.md +52 -242
- package/node_modules/@dietrichgebert/ponytail/hooks/claude-codex-hooks.json +3 -3
- package/node_modules/@dietrichgebert/ponytail/hooks/copilot-hooks.json +4 -4
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +70 -16
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-config.js +8 -7
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-instructions.js +30 -30
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-map.js +103 -0
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-mode-tracker.js +46 -23
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-runtime.js +39 -7
- package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-subagent.js +20 -3
- package/node_modules/@dietrichgebert/ponytail/hooks/qoder-hooks.json +3 -3
- package/node_modules/@dietrichgebert/ponytail/package.json +3 -3
- package/node_modules/@dietrichgebert/ponytail/pi-extension/index.js +47 -17
- package/node_modules/@dietrichgebert/ponytail/scripts/uninstall.js +21 -7
- package/node_modules/@dietrichgebert/ponytail/skills/plugin.json +5 -0
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail/SKILL.md +33 -101
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +89 -27
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +13 -12
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +21 -18
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +12 -13
- package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +96 -52
- package/node_modules/@herbertgao/pi-agency/LICENSE +21 -0
- package/node_modules/@herbertgao/pi-agency/README.md +40 -0
- package/node_modules/@herbertgao/pi-agency/package.json +53 -0
- package/node_modules/@herbertgao/pi-agency/src/index.ts +441 -0
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +12 -12
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +30 -6
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/default-mode.ts +15 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse/interaction.ts +27 -9
- package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool/codemode.ts +4 -1
- package/node_modules/@herbertgao/pi-cc-extensions/package.json +2 -2
- package/node_modules/@narumitw/pi-caffeinate/package.json +4 -4
- package/node_modules/@tifan/pi-mermaid-open/package.json +5 -1
- package/node_modules/@tifan/pi-mermaid-open/src/index.ts +26 -9
- package/node_modules/@tifan/pi-recap/package.json +1 -1
- package/node_modules/@tifan/pi-recap/src/index.ts +104 -20
- package/node_modules/@tifan/pi-recap/src/models.ts +46 -4
- package/node_modules/pi-antigravity/README.md +23 -2
- package/node_modules/pi-antigravity/package.json +5 -3
- package/node_modules/pi-antigravity/src/diagnostics/diagnostics.ts +3 -2
- package/node_modules/pi-antigravity/src/features/generate-image.ts +72 -0
- package/node_modules/pi-antigravity/src/features/google-search.ts +71 -0
- package/node_modules/pi-antigravity/src/image/image.ts +86 -47
- package/node_modules/pi-antigravity/src/image/native.ts +69 -0
- package/node_modules/pi-antigravity/src/index.ts +52 -137
- package/node_modules/pi-antigravity/src/search/search.ts +130 -31
- package/node_modules/pi-antigravity/src/stream/stream.ts +162 -113
- package/node_modules/pi-antigravity/src/types/enums.ts +1 -0
- package/node_modules/pi-antigravity/src/types/types.ts +1 -1
- package/node_modules/pi-antigravity/src/utils/security.ts +47 -1
- package/node_modules/pi-antigravity/src/utils/util.ts +38 -6
- package/node_modules/pi-lens/CHANGELOG.md +1064 -0
- package/node_modules/pi-lens/README.md +115 -25
- package/node_modules/pi-lens/config/dependency-cruiser-eager-allowlist.json +6 -2
- package/node_modules/pi-lens/dist/chunk-22AVIGXS.js +560 -0
- package/node_modules/pi-lens/dist/chunk-27BBHSZC.js +106 -0
- package/node_modules/pi-lens/dist/chunk-2BEMIWEW.js +51 -0
- package/node_modules/pi-lens/dist/chunk-2EECBNC5.js +89 -0
- package/node_modules/pi-lens/dist/chunk-34U7PLGE.js +23 -0
- package/node_modules/pi-lens/dist/chunk-3I5QFT3N.js +181 -0
- package/node_modules/pi-lens/dist/chunk-3MED75HT.js +37 -0
- package/node_modules/pi-lens/dist/chunk-47D2BO4S.js +87 -0
- package/node_modules/pi-lens/dist/chunk-4RERDEXR.js +348 -0
- package/node_modules/pi-lens/dist/chunk-4W2PLC4J.js +27 -0
- package/node_modules/pi-lens/dist/chunk-52H5B5TC.js +202 -0
- package/node_modules/pi-lens/dist/chunk-54B42YRX.js +27 -0
- package/node_modules/pi-lens/dist/chunk-5RXBNSW2.js +266 -0
- package/node_modules/pi-lens/dist/chunk-5THXD6X5.js +201 -0
- package/node_modules/pi-lens/dist/chunk-5W3R6LFT.js +24 -0
- package/node_modules/pi-lens/dist/chunk-5ZEJHV35.js +341 -0
- package/node_modules/pi-lens/dist/chunk-6P62QVEA.js +118 -0
- package/node_modules/pi-lens/dist/chunk-6QIUX4EP.js +459 -0
- package/node_modules/pi-lens/dist/chunk-6VCD4VKZ.js +78 -0
- package/node_modules/pi-lens/dist/chunk-7F72PXY2.js +326 -0
- package/node_modules/pi-lens/dist/chunk-7FBF7NPR.js +998 -0
- package/node_modules/pi-lens/dist/chunk-7U2IDNXN.js +37 -0
- package/node_modules/pi-lens/dist/chunk-ABBOA7UD.js +157 -0
- package/node_modules/pi-lens/dist/chunk-AHDW3GPE.js +50 -0
- package/node_modules/pi-lens/dist/chunk-AJ3QXTRQ.js +756 -0
- package/node_modules/pi-lens/dist/chunk-AJVLWNFN.js +1610 -0
- package/node_modules/pi-lens/dist/chunk-AJXODZY6.js +112 -0
- package/node_modules/pi-lens/dist/chunk-ARFCXG7S.js +84 -0
- package/node_modules/pi-lens/dist/chunk-AS44CFQ4.js +708 -0
- package/node_modules/pi-lens/dist/chunk-AWE5TMPO.js +38 -0
- package/node_modules/pi-lens/dist/chunk-B3I2UUT4.js +4692 -0
- package/node_modules/pi-lens/dist/chunk-BRJKOBZP.js +331 -0
- package/node_modules/pi-lens/dist/chunk-BW5KFIQC.js +59 -0
- package/node_modules/pi-lens/dist/chunk-C2AUYXQH.js +149 -0
- package/node_modules/pi-lens/dist/chunk-CGGAIP7J.js +29 -0
- package/node_modules/pi-lens/dist/chunk-D6QS3U6M.js +30 -0
- package/node_modules/pi-lens/dist/chunk-DFVMIKMQ.js +349 -0
- package/node_modules/pi-lens/dist/chunk-DIULIWKU.js +169 -0
- package/node_modules/pi-lens/dist/chunk-DQ2NZ7C4.js +2317 -0
- package/node_modules/pi-lens/dist/chunk-E2WFBRHM.js +213 -0
- package/node_modules/pi-lens/dist/chunk-E4MAEDPM.js +50 -0
- package/node_modules/pi-lens/dist/chunk-EOKRQ3SA.js +30 -0
- package/node_modules/pi-lens/dist/chunk-F3AU2TMM.js +2626 -0
- package/node_modules/pi-lens/dist/chunk-F3P7VQ7H.js +170 -0
- package/node_modules/pi-lens/dist/chunk-FC4RP4I2.js +12 -0
- package/node_modules/pi-lens/dist/chunk-FJNKANOS.js +32 -0
- package/node_modules/pi-lens/dist/chunk-GUOGVTCQ.js +146 -0
- package/node_modules/pi-lens/dist/chunk-GVXA6CCE.js +18 -0
- package/node_modules/pi-lens/dist/chunk-HHYAELLJ.js +2334 -0
- package/node_modules/pi-lens/dist/chunk-HMFXILPV.js +83 -0
- package/node_modules/pi-lens/dist/chunk-HVUZXSWK.js +421 -0
- package/node_modules/pi-lens/dist/chunk-HWBZIGMJ.js +62 -0
- package/node_modules/pi-lens/dist/chunk-I3R7GKDR.js +17 -0
- package/node_modules/pi-lens/dist/chunk-I7SPGOWI.js +55 -0
- package/node_modules/pi-lens/dist/chunk-IIXOAGHR.js +38 -0
- package/node_modules/pi-lens/dist/chunk-IVP5KAB7.js +2250 -0
- package/node_modules/pi-lens/dist/chunk-IXEBJSUE.js +22 -0
- package/node_modules/pi-lens/dist/chunk-JOLITGCC.js +27 -0
- package/node_modules/pi-lens/dist/chunk-K6VYLB2U.js +145 -0
- package/node_modules/pi-lens/dist/chunk-LJDODBBZ.js +78 -0
- package/node_modules/pi-lens/dist/chunk-LNAKEO63.js +78 -0
- package/node_modules/pi-lens/dist/chunk-LSMJ6KBE.js +4139 -0
- package/node_modules/pi-lens/dist/chunk-LW6XKDZQ.js +5565 -0
- package/node_modules/pi-lens/dist/chunk-N3YQJI6O.js +899 -0
- package/node_modules/pi-lens/dist/chunk-NABLC7W3.js +148 -0
- package/node_modules/pi-lens/dist/chunk-NKOUFPQF.js +659 -0
- package/node_modules/pi-lens/dist/chunk-NL5GLMFI.js +211 -0
- package/node_modules/pi-lens/dist/chunk-NMABWBZN.js +327 -0
- package/node_modules/pi-lens/dist/chunk-NTHH2J74.js +27 -0
- package/node_modules/pi-lens/dist/chunk-NTUJEGDZ.js +59 -0
- package/node_modules/pi-lens/dist/chunk-NXL4FFQQ.js +47 -0
- package/node_modules/pi-lens/dist/chunk-NXPS2L6U.js +466 -0
- package/node_modules/pi-lens/dist/chunk-O5WILGMI.js +42 -0
- package/node_modules/pi-lens/dist/chunk-O6TQT6RI.js +48 -0
- package/node_modules/pi-lens/dist/chunk-OAXNDZJ7.js +323 -0
- package/node_modules/pi-lens/dist/chunk-ODEEOXPH.js +44 -0
- package/node_modules/pi-lens/dist/chunk-OQGFCZJ4.js +4533 -0
- package/node_modules/pi-lens/dist/chunk-ORTMY4Q4.js +282 -0
- package/node_modules/pi-lens/dist/chunk-OS32RNR2.js +442 -0
- package/node_modules/pi-lens/dist/chunk-PAK6IDME.js +189 -0
- package/node_modules/pi-lens/dist/chunk-PWFHBTSF.js +406 -0
- package/node_modules/pi-lens/dist/chunk-Q5U6FMDI.js +2352 -0
- package/node_modules/pi-lens/dist/chunk-QOZXJ4NA.js +3364 -0
- package/node_modules/pi-lens/dist/chunk-QYF5S6P6.js +45 -0
- package/node_modules/pi-lens/dist/chunk-RIISMHKN.js +36 -0
- package/node_modules/pi-lens/dist/chunk-RPD4ZSB7.js +25 -0
- package/node_modules/pi-lens/dist/chunk-RPD5K5NC.js +79 -0
- package/node_modules/pi-lens/dist/chunk-RUOG5D2C.js +262 -0
- package/node_modules/pi-lens/dist/chunk-T4ERT73Y.js +599 -0
- package/node_modules/pi-lens/dist/chunk-T6D4Q4AQ.js +54 -0
- package/node_modules/pi-lens/dist/chunk-TGPXLY2J.js +391 -0
- package/node_modules/pi-lens/dist/chunk-TLGFBM3F.js +12116 -0
- package/node_modules/pi-lens/dist/chunk-TO6WL3GM.js +88 -0
- package/node_modules/pi-lens/dist/chunk-TT22N74J.js +517 -0
- package/node_modules/pi-lens/dist/chunk-TVN5N6GY.js +21216 -0
- package/node_modules/pi-lens/dist/chunk-U3VGR4VU.js +308 -0
- package/node_modules/pi-lens/dist/chunk-UK3CMEAL.js +1250 -0
- package/node_modules/pi-lens/dist/chunk-UOPVJQ4N.js +103 -0
- package/node_modules/pi-lens/dist/chunk-UXVNL3D7.js +23 -0
- package/node_modules/pi-lens/dist/chunk-UY752QSN.js +45 -0
- package/node_modules/pi-lens/dist/chunk-V5PXFSZK.js +188 -0
- package/node_modules/pi-lens/dist/chunk-V7R425XG.js +398 -0
- package/node_modules/pi-lens/dist/chunk-VCSRSYLF.js +81 -0
- package/node_modules/pi-lens/dist/chunk-VD4JQCQC.js +162 -0
- package/node_modules/pi-lens/dist/chunk-VDBPVIKD.js +62 -0
- package/node_modules/pi-lens/dist/chunk-VHQLPIRJ.js +114 -0
- package/node_modules/pi-lens/dist/chunk-VN2AXLEX.js +5414 -0
- package/node_modules/pi-lens/dist/chunk-VNMT7C64.js +478 -0
- package/node_modules/pi-lens/dist/chunk-WONERJTP.js +309 -0
- package/node_modules/pi-lens/dist/chunk-XECLWQZN.js +85 -0
- package/node_modules/pi-lens/dist/chunk-XIMYZ3JA.js +27 -0
- package/node_modules/pi-lens/dist/chunk-XK4CIILI.js +144 -0
- package/node_modules/pi-lens/dist/chunk-Y4T27COV.js +562 -0
- package/node_modules/pi-lens/dist/chunk-YGVYKNSN.js +1860 -0
- package/node_modules/pi-lens/dist/chunk-YLF3PPT5.js +308 -0
- package/node_modules/pi-lens/dist/chunk-YSNFMJKN.js +14 -0
- package/node_modules/pi-lens/dist/chunk-Z3CCVDUB.js +76 -0
- package/node_modules/pi-lens/dist/chunk-Z54ZSOBZ.js +61 -0
- package/node_modules/pi-lens/dist/chunk-ZAWW3AF6.js +97 -0
- package/node_modules/pi-lens/dist/chunk-ZMOL6RQC.js +167 -0
- package/node_modules/pi-lens/dist/index.js +16456 -8335
- package/node_modules/pi-lens/dist/mcp/analyze-cli.js +186 -232
- package/node_modules/pi-lens/dist/mcp/cli.js +137 -98
- package/node_modules/pi-lens/dist/mcp/server.js +22589 -1573
- package/node_modules/pi-lens/dist/mcp/worker.js +97 -38
- package/node_modules/pi-lens/dist/probes/installer.js +135 -0
- package/node_modules/pi-lens/dist/workers/project-snapshot-persist-worker.js +22 -0
- package/node_modules/pi-lens/dist/workers/review-graph-persist-worker.js +15 -0
- package/node_modules/pi-lens/docs/agent-guide.md +13 -4
- package/node_modules/pi-lens/docs/agent-tools.md +42 -0
- package/node_modules/pi-lens/docs/ast-grep_rules_catalog.md +3 -2
- package/node_modules/pi-lens/docs/configuration.md +20 -1
- package/node_modules/pi-lens/docs/environment-variables.md +52 -2
- package/node_modules/pi-lens/docs/features.md +8 -4
- package/node_modules/pi-lens/docs/globalconfig.md +1 -0
- package/node_modules/pi-lens/docs/io-bridge-v2-migration.md +94 -0
- package/node_modules/pi-lens/docs/lsp-capability-matrix.md +36 -4
- package/node_modules/pi-lens/docs/lsp-idle-eviction.md +124 -0
- package/node_modules/pi-lens/docs/mcp.md +21 -5
- package/node_modules/pi-lens/docs/merge-queue-rollout.md +77 -0
- package/node_modules/pi-lens/docs/pi-lens-fixer.md +156 -88
- package/node_modules/pi-lens/docs/pi-lens-investigator.md +74 -26
- package/node_modules/pi-lens/docs/pi-lens-merge-policy.md +694 -0
- package/node_modules/pi-lens/docs/pi-lens-monitor.md +73 -7
- package/node_modules/pi-lens/docs/pi-lens-retro.md +6 -4
- package/node_modules/pi-lens/docs/pi-lens-reviewer.md +188 -48
- package/node_modules/pi-lens/docs/pi-lens-subagent.md +246 -45
- package/node_modules/pi-lens/docs/pi-lens-warden.md +18 -7
- package/node_modules/pi-lens/docs/public-api-stability.md +55 -0
- package/node_modules/pi-lens/docs/release-qa-baseline.md +84 -17
- package/node_modules/pi-lens/docs/rfcs/3654-unified-io-bridge-v2.md +316 -0
- package/node_modules/pi-lens/docs/servercapabilities.md +53 -49
- package/node_modules/pi-lens/docs/settings.md +1 -0
- package/node_modules/pi-lens/docs/usage.md +5 -1
- package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm +0 -0
- package/node_modules/pi-lens/grammars/tree-sitter-bash.wasm.json +3 -3
- package/node_modules/pi-lens/package.json +18 -6
- package/node_modules/pi-lens/rules/ast-grep-rules/rule-tests/advisory-registry-subset-test.yml +41 -0
- package/node_modules/pi-lens/rules/ast-grep-rules/rules/advisory-registry-subset.yml +50 -0
- package/node_modules/pi-lens/scripts/analyze-pi-lens-logs.mjs +1275 -62
- package/node_modules/pi-lens/scripts/download-grammars.js +5 -0
- package/node_modules/pi-lens/scripts/grammars.lock.json +6 -1
- package/node_modules/pi-lens/scripts/install-selftest.mjs +33 -34
- package/node_modules/pi-lens/scripts/lib/host-provided-deps.mjs +13 -4
- package/node_modules/pi-lens/scripts/lib/web-tree-sitter-dir.mjs +18 -0
- package/node_modules/pi-typesafe/README.md +1 -1
- package/node_modules/pi-typesafe/dist/backends.d.ts +15 -5
- package/node_modules/pi-typesafe/dist/backends.js +18 -0
- package/node_modules/pi-typesafe/dist/client.d.ts +4 -3
- package/node_modules/pi-typesafe/dist/client.js +22 -7
- package/node_modules/pi-typesafe/dist/index.d.ts +2 -2
- package/node_modules/pi-typesafe/dist/index.js +1 -1
- package/node_modules/pi-typesafe/dist/usage.d.ts +14 -4
- package/node_modules/pi-typesafe/dist/usage.js +147 -42
- package/node_modules/pi-typesafe/package.json +1 -1
- package/node_modules/pi-web-access/CHANGELOG.md +48 -0
- package/node_modules/pi-web-access/README.md +57 -11
- package/node_modules/pi-web-access/brave-rate-limit.ts +8 -2
- package/node_modules/pi-web-access/curator-page.ts +5 -1
- package/node_modules/pi-web-access/curator-server.ts +2 -0
- package/node_modules/pi-web-access/declared-web-links.ts +2 -2
- package/node_modules/pi-web-access/degoog.ts +323 -0
- package/node_modules/pi-web-access/dist/index.js +3483 -2745
- package/node_modules/pi-web-access/dist/mcp-cli.js +27443 -0
- package/node_modules/pi-web-access/exa.ts +5 -1
- package/node_modules/pi-web-access/extract.ts +41 -3
- package/node_modules/pi-web-access/firecrawl.ts +35 -4
- package/node_modules/pi-web-access/gemini-search.ts +34 -11
- package/node_modules/pi-web-access/gemini-web.ts +14 -9
- package/node_modules/pi-web-access/index.ts +153 -1202
- package/node_modules/pi-web-access/keenable.ts +221 -0
- package/node_modules/pi-web-access/mcp-cli.ts +40 -0
- package/node_modules/pi-web-access/mcp-server.ts +81 -0
- package/node_modules/pi-web-access/mcp-tool-definitions.ts +117 -0
- package/node_modules/pi-web-access/openai-search.ts +34 -1
- package/node_modules/pi-web-access/package.json +23 -5
- package/node_modules/pi-web-access/perplexity.ts +64 -56
- package/node_modules/pi-web-access/tool-arguments.ts +20 -0
- package/node_modules/pi-web-access/video-extract.ts +1 -1
- package/node_modules/pi-web-access/web-tool-contract.ts +86 -0
- package/node_modules/pi-web-access/web-tool-core.ts +1502 -0
- package/node_modules/pi-web-access/youtube-extract.ts +2 -5
- package/package.json +17 -12
- package/node_modules/@dietrichgebert/ponytail/README.es.md +0 -329
- package/node_modules/@dietrichgebert/ponytail/README.ko.md +0 -346
- package/node_modules/@dietrichgebert/ponytail/assets/benchmark-3model.svg +0 -21
- package/node_modules/@dietrichgebert/ponytail/assets/benchmark-agentic.svg +0 -62
- package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.svg +0 -115
- package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt-dark.svg +0 -27
- package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt.svg +0 -27
- package/node_modules/@dietrichgebert/ponytail/assets/logo.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/retriever-icon.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/retriever-logo-dark.svg +0 -4
- package/node_modules/@dietrichgebert/ponytail/assets/retriever-logo-light.svg +0 -4
- package/node_modules/@dietrichgebert/ponytail/assets/social-preview.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-es.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-ko.png +0 -0
- package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner.png +0 -0
- package/node_modules/pi-lens/dist/clients/actionable-warnings-logger.js +0 -35
- package/node_modules/pi-lens/dist/clients/actionable-warnings.js +0 -1610
- package/node_modules/pi-lens/dist/clients/advisory-provenance.js +0 -593
- package/node_modules/pi-lens/dist/clients/agent-behavior-client.js +0 -147
- package/node_modules/pi-lens/dist/clients/agent-nudge.js +0 -325
- package/node_modules/pi-lens/dist/clients/analysed-root.js +0 -1
- package/node_modules/pi-lens/dist/clients/ast-grep-client.js +0 -731
- package/node_modules/pi-lens/dist/clients/ast-grep-rule-manager.js +0 -146
- package/node_modules/pi-lens/dist/clients/ast-grep-tool-logger.js +0 -118
- package/node_modules/pi-lens/dist/clients/ast-grep-types.js +0 -10
- package/node_modules/pi-lens/dist/clients/ast-grep-yaml-synth.js +0 -154
- package/node_modules/pi-lens/dist/clients/atomic-write-staging.js +0 -75
- package/node_modules/pi-lens/dist/clients/atomic-write.js +0 -189
- package/node_modules/pi-lens/dist/clients/availability-probe-flight.js +0 -12
- package/node_modules/pi-lens/dist/clients/bash-file-access.js +0 -1056
- package/node_modules/pi-lens/dist/clients/biome-client.js +0 -357
- package/node_modules/pi-lens/dist/clients/blocker-freshness.js +0 -837
- package/node_modules/pi-lens/dist/clients/blocker-past-eof.js +0 -129
- package/node_modules/pi-lens/dist/clients/bootstrap.js +0 -569
- package/node_modules/pi-lens/dist/clients/bounded-cache.js +0 -190
- package/node_modules/pi-lens/dist/clients/bounded-pid-file-lock.js +0 -202
- package/node_modules/pi-lens/dist/clients/bounded-telemetry.js +0 -295
- package/node_modules/pi-lens/dist/clients/build-identity.js +0 -95
- package/node_modules/pi-lens/dist/clients/bundled-resource-health.js +0 -113
- package/node_modules/pi-lens/dist/clients/bus-events-logger.js +0 -105
- package/node_modules/pi-lens/dist/clients/bus-publish.js +0 -171
- package/node_modules/pi-lens/dist/clients/cache/rule-cache.js +0 -190
- package/node_modules/pi-lens/dist/clients/cache-manager.js +0 -524
- package/node_modules/pi-lens/dist/clients/cache-observability.js +0 -1313
- package/node_modules/pi-lens/dist/clients/call-graph.js +0 -852
- package/node_modules/pi-lens/dist/clients/cargo-manifest.js +0 -422
- package/node_modules/pi-lens/dist/clients/cascade-budget.js +0 -196
- package/node_modules/pi-lens/dist/clients/cascade-format.js +0 -315
- package/node_modules/pi-lens/dist/clients/cascade-logger.js +0 -31
- package/node_modules/pi-lens/dist/clients/cascade-types.js +0 -9
- package/node_modules/pi-lens/dist/clients/child-unref.js +0 -124
- package/node_modules/pi-lens/dist/clients/code-quality-warnings.js +0 -286
- package/node_modules/pi-lens/dist/clients/codebase-model.js +0 -173
- package/node_modules/pi-lens/dist/clients/collateral-test-role.js +0 -40
- package/node_modules/pi-lens/dist/clients/complexity-client.js +0 -462
- package/node_modules/pi-lens/dist/clients/config-core/deny.js +0 -221
- package/node_modules/pi-lens/dist/clients/config-core/index.js +0 -50
- package/node_modules/pi-lens/dist/clients/config-core/merge.js +0 -357
- package/node_modules/pi-lens/dist/clients/config-core/normalize.js +0 -340
- package/node_modules/pi-lens/dist/clients/config-core/process-spec.js +0 -248
- package/node_modules/pi-lens/dist/clients/config-core/provenance.js +0 -164
- package/node_modules/pi-lens/dist/clients/config-core/records.js +0 -218
- package/node_modules/pi-lens/dist/clients/config-core/resolve.js +0 -125
- package/node_modules/pi-lens/dist/clients/config-core/safe-object.js +0 -78
- package/node_modules/pi-lens/dist/clients/config-core/schema.js +0 -165
- package/node_modules/pi-lens/dist/clients/config-diagnostic-codes.js +0 -285
- package/node_modules/pi-lens/dist/clients/config-locations.js +0 -388
- package/node_modules/pi-lens/dist/clients/config-resolve.js +0 -798
- package/node_modules/pi-lens/dist/clients/config-schema.js +0 -251
- package/node_modules/pi-lens/dist/clients/config-warn.js +0 -407
- package/node_modules/pi-lens/dist/clients/console-guard-install.js +0 -26
- package/node_modules/pi-lens/dist/clients/cooperative-budget.js +0 -42
- package/node_modules/pi-lens/dist/clients/dead-code-client.js +0 -510
- package/node_modules/pi-lens/dist/clients/dead-code-logger.js +0 -30
- package/node_modules/pi-lens/dist/clients/deadline-utils.js +0 -280
- package/node_modules/pi-lens/dist/clients/debug-handles.js +0 -211
- package/node_modules/pi-lens/dist/clients/debug-heap.js +0 -174
- package/node_modules/pi-lens/dist/clients/deferred-lsp-work.js +0 -106
- package/node_modules/pi-lens/dist/clients/degradation-ledger.js +0 -426
- package/node_modules/pi-lens/dist/clients/demoted-finding-render.js +0 -169
- package/node_modules/pi-lens/dist/clients/dependency-checker.js +0 -885
- package/node_modules/pi-lens/dist/clients/deps/ast-grep-napi.js +0 -22
- package/node_modules/pi-lens/dist/clients/deps/js-yaml.js +0 -9
- package/node_modules/pi-lens/dist/clients/deps/minimatch.js +0 -4
- package/node_modules/pi-lens/dist/clients/deps/pi-tui.js +0 -14
- package/node_modules/pi-lens/dist/clients/deps/typebox.js +0 -14
- package/node_modules/pi-lens/dist/clients/deps/vscode-jsonrpc.js +0 -1
- package/node_modules/pi-lens/dist/clients/deps/web-tree-sitter.js +0 -24
- package/node_modules/pi-lens/dist/clients/diagnostic-dispositions.js +0 -630
- package/node_modules/pi-lens/dist/clients/diagnostic-line-freshness.js +0 -305
- package/node_modules/pi-lens/dist/clients/diagnostic-logger.js +0 -73
- package/node_modules/pi-lens/dist/clients/diagnostic-tracker.js +0 -105
- package/node_modules/pi-lens/dist/clients/diagnostics-publish.js +0 -230
- package/node_modules/pi-lens/dist/clients/dispatch/ast-grep-catalog.js +0 -65
- package/node_modules/pi-lens/dist/clients/dispatch/auxiliary-lsp.js +0 -343
- package/node_modules/pi-lens/dist/clients/dispatch/collect-later-tier.js +0 -35
- package/node_modules/pi-lens/dist/clients/dispatch/diagnostic-taxonomy.js +0 -66
- package/node_modules/pi-lens/dist/clients/dispatch/dispatcher.js +0 -1269
- package/node_modules/pi-lens/dist/clients/dispatch/fact-provider-types.js +0 -1
- package/node_modules/pi-lens/dist/clients/dispatch/fact-rule-runner.js +0 -17
- package/node_modules/pi-lens/dist/clients/dispatch/fact-runner.js +0 -30
- package/node_modules/pi-lens/dist/clients/dispatch/fact-scheduler.js +0 -68
- package/node_modules/pi-lens/dist/clients/dispatch/fact-store.js +0 -377
- package/node_modules/pi-lens/dist/clients/dispatch/facts/comment-facts.js +0 -22
- package/node_modules/pi-lens/dist/clients/dispatch/facts/file-content.js +0 -19
- package/node_modules/pi-lens/dist/clients/dispatch/facts/function-facts.js +0 -348
- package/node_modules/pi-lens/dist/clients/dispatch/facts/import-facts.js +0 -204
- package/node_modules/pi-lens/dist/clients/dispatch/facts/tree-sitter-facts.js +0 -34
- package/node_modules/pi-lens/dist/clients/dispatch/facts/try-catch-facts.js +0 -119
- package/node_modules/pi-lens/dist/clients/dispatch/finding-policy.js +0 -339
- package/node_modules/pi-lens/dist/clients/dispatch/indent-detect.js +0 -417
- package/node_modules/pi-lens/dist/clients/dispatch/inline-suppressions.js +0 -89
- package/node_modules/pi-lens/dist/clients/dispatch/integration.js +0 -2377
- package/node_modules/pi-lens/dist/clients/dispatch/lazy.js +0 -11
- package/node_modules/pi-lens/dist/clients/dispatch/pending-runner-findings.js +0 -87
- package/node_modules/pi-lens/dist/clients/dispatch/plan.js +0 -278
- package/node_modules/pi-lens/dist/clients/dispatch/priorities.js +0 -20
- package/node_modules/pi-lens/dist/clients/dispatch/rule-id-normalize.js +0 -50
- package/node_modules/pi-lens/dist/clients/dispatch/rule-ignores.js +0 -69
- package/node_modules/pi-lens/dist/clients/dispatch/rule-policy.js +0 -135
- package/node_modules/pi-lens/dist/clients/dispatch/rules/async-noise.js +0 -40
- package/node_modules/pi-lens/dist/clients/dispatch/rules/async-unnecessary-wrapper.js +0 -27
- package/node_modules/pi-lens/dist/clients/dispatch/rules/cors-wildcard.js +0 -54
- package/node_modules/pi-lens/dist/clients/dispatch/rules/error-obscuring.js +0 -32
- package/node_modules/pi-lens/dist/clients/dispatch/rules/error-swallowing.js +0 -31
- package/node_modules/pi-lens/dist/clients/dispatch/rules/framework-call-noise.js +0 -67
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-complexity.js +0 -72
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-fan-out.js +0 -74
- package/node_modules/pi-lens/dist/clients/dispatch/rules/high-import-coupling.js +0 -37
- package/node_modules/pi-lens/dist/clients/dispatch/rules/missing-error-propagation.js +0 -62
- package/node_modules/pi-lens/dist/clients/dispatch/rules/no-commented-credentials.js +0 -52
- package/node_modules/pi-lens/dist/clients/dispatch/rules/pass-through-wrappers.js +0 -42
- package/node_modules/pi-lens/dist/clients/dispatch/rules/placeholder-comments.js +0 -40
- package/node_modules/pi-lens/dist/clients/dispatch/rules/unsafe-boundary.js +0 -126
- package/node_modules/pi-lens/dist/clients/dispatch/runner-context.js +0 -52
- package/node_modules/pi-lens/dist/clients/dispatch/runners/actionlint.js +0 -111
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ast-grep-napi.js +0 -1196
- package/node_modules/pi-lens/dist/clients/dispatch/runners/biome-check.js +0 -296
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cpp-check.js +0 -219
- package/node_modules/pi-lens/dist/clients/dispatch/runners/credo.js +0 -82
- package/node_modules/pi-lens/dist/clients/dispatch/runners/cue-vet.js +0 -416
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dart-analyze.js +0 -183
- package/node_modules/pi-lens/dist/clients/dispatch/runners/detekt.js +0 -176
- package/node_modules/pi-lens/dist/clients/dispatch/runners/dotnet-build.js +0 -164
- package/node_modules/pi-lens/dist/clients/dispatch/runners/elixir-check.js +0 -195
- package/node_modules/pi-lens/dist/clients/dispatch/runners/eslint.js +0 -137
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fact-rules.js +0 -29
- package/node_modules/pi-lens/dist/clients/dispatch/runners/fish-indent.js +0 -69
- package/node_modules/pi-lens/dist/clients/dispatch/runners/gleam-check.js +0 -167
- package/node_modules/pi-lens/dist/clients/dispatch/runners/go-vet.js +0 -104
- package/node_modules/pi-lens/dist/clients/dispatch/runners/golangci-lint.js +0 -135
- package/node_modules/pi-lens/dist/clients/dispatch/runners/hadolint.js +0 -91
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-lint.js +0 -171
- package/node_modules/pi-lens/dist/clients/dispatch/runners/helm-render.js +0 -940
- package/node_modules/pi-lens/dist/clients/dispatch/runners/htmlhint.js +0 -117
- package/node_modules/pi-lens/dist/clients/dispatch/runners/index.js +0 -108
- package/node_modules/pi-lens/dist/clients/dispatch/runners/javac.js +0 -99
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ktlint.js +0 -110
- package/node_modules/pi-lens/dist/clients/dispatch/runners/lsp.js +0 -379
- package/node_modules/pi-lens/dist/clients/dispatch/runners/markdownlint.js +0 -167
- package/node_modules/pi-lens/dist/clients/dispatch/runners/mypy.js +0 -102
- package/node_modules/pi-lens/dist/clients/dispatch/runners/oxlint.js +0 -414
- package/node_modules/pi-lens/dist/clients/dispatch/runners/php-lint.js +0 -85
- package/node_modules/pi-lens/dist/clients/dispatch/runners/phpstan.js +0 -120
- package/node_modules/pi-lens/dist/clients/dispatch/runners/prisma-validate.js +0 -62
- package/node_modules/pi-lens/dist/clients/dispatch/runners/psscriptanalyzer.js +0 -445
- package/node_modules/pi-lens/dist/clients/dispatch/runners/pyright.js +0 -169
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rubocop.js +0 -99
- package/node_modules/pi-lens/dist/clients/dispatch/runners/ruff.js +0 -88
- package/node_modules/pi-lens/dist/clients/dispatch/runners/rust-clippy.js +0 -256
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shellcheck.js +0 -173
- package/node_modules/pi-lens/dist/clients/dispatch/runners/shfmt.js +0 -102
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spellcheck.js +0 -128
- package/node_modules/pi-lens/dist/clients/dispatch/runners/spotbugs.js +0 -259
- package/node_modules/pi-lens/dist/clients/dispatch/runners/sqlfluff.js +0 -183
- package/node_modules/pi-lens/dist/clients/dispatch/runners/stylelint.js +0 -166
- package/node_modules/pi-lens/dist/clients/dispatch/runners/swiftlint.js +0 -163
- package/node_modules/pi-lens/dist/clients/dispatch/runners/taplo.js +0 -174
- package/node_modules/pi-lens/dist/clients/dispatch/runners/terragrunt.js +0 -168
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tflint.js +0 -125
- package/node_modules/pi-lens/dist/clients/dispatch/runners/tree-sitter.js +0 -643
- package/node_modules/pi-lens/dist/clients/dispatch/runners/trivy-config.js +0 -253
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/availability-policy.js +0 -496
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/candidate-probe.js +0 -90
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/diagnostic-parsers.js +0 -95
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/lazy-installer.js +0 -274
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/runner-helpers.js +0 -1701
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/spawn-outcome.js +0 -101
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/tool-failure.js +0 -235
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils/toolchain-availability.js +0 -169
- package/node_modules/pi-lens/dist/clients/dispatch/runners/utils.js +0 -15
- package/node_modules/pi-lens/dist/clients/dispatch/runners/vale.js +0 -128
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yaml-rule-parser.js +0 -242
- package/node_modules/pi-lens/dist/clients/dispatch/runners/yamllint.js +0 -95
- package/node_modules/pi-lens/dist/clients/dispatch/runners/zig-check.js +0 -79
- package/node_modules/pi-lens/dist/clients/dispatch/suppress-writer.js +0 -79
- package/node_modules/pi-lens/dist/clients/dispatch/tool-profile.js +0 -30
- package/node_modules/pi-lens/dist/clients/dispatch/types.js +0 -21
- package/node_modules/pi-lens/dist/clients/dispatch/utils/format-utils.js +0 -45
- package/node_modules/pi-lens/dist/clients/dispatch/utils/lsp-diagnostics.js +0 -35
- package/node_modules/pi-lens/dist/clients/disposition-logger.js +0 -32
- package/node_modules/pi-lens/dist/clients/disposition-publish.js +0 -124
- package/node_modules/pi-lens/dist/clients/durable-store.js +0 -97
- package/node_modules/pi-lens/dist/clients/effective-config.js +0 -415
- package/node_modules/pi-lens/dist/clients/env-utils.js +0 -83
- package/node_modules/pi-lens/dist/clients/error-class.js +0 -23
- package/node_modules/pi-lens/dist/clients/eval-timestamp.js +0 -17
- package/node_modules/pi-lens/dist/clients/event-loop-hold.js +0 -274
- package/node_modules/pi-lens/dist/clients/event-loop-monitor.js +0 -250
- package/node_modules/pi-lens/dist/clients/extension-log.js +0 -504
- package/node_modules/pi-lens/dist/clients/extension-mode.js +0 -73
- package/node_modules/pi-lens/dist/clients/feature-hints.js +0 -103
- package/node_modules/pi-lens/dist/clients/file-kinds.js +0 -412
- package/node_modules/pi-lens/dist/clients/file-role.js +0 -134
- package/node_modules/pi-lens/dist/clients/file-time.js +0 -160
- package/node_modules/pi-lens/dist/clients/file-utils.js +0 -1032
- package/node_modules/pi-lens/dist/clients/finding-delivery-gate.js +0 -494
- package/node_modules/pi-lens/dist/clients/finding-identity.js +0 -88
- package/node_modules/pi-lens/dist/clients/fix-worklog.js +0 -103
- package/node_modules/pi-lens/dist/clients/format-events-publish.js +0 -343
- package/node_modules/pi-lens/dist/clients/format-service.js +0 -222
- package/node_modules/pi-lens/dist/clients/formatters-lazy.js +0 -9
- package/node_modules/pi-lens/dist/clients/formatters.js +0 -2032
- package/node_modules/pi-lens/dist/clients/freshness-cadence.js +0 -17
- package/node_modules/pi-lens/dist/clients/freshness.js +0 -38
- package/node_modules/pi-lens/dist/clients/generated-artifacts.js +0 -394
- package/node_modules/pi-lens/dist/clients/generation-guard.js +0 -215
- package/node_modules/pi-lens/dist/clients/git-guard.js +0 -1136
- package/node_modules/pi-lens/dist/clients/git-tracked-ignore.js +0 -243
- package/node_modules/pi-lens/dist/clients/gitleaks-client.js +0 -509
- package/node_modules/pi-lens/dist/clients/go-client.js +0 -104
- package/node_modules/pi-lens/dist/clients/govulncheck-client.js +0 -605
- package/node_modules/pi-lens/dist/clients/gradle-ktfmt-style.js +0 -348
- package/node_modules/pi-lens/dist/clients/grammar-source.js +0 -465
- package/node_modules/pi-lens/dist/clients/gzip-stage-write.js +0 -135
- package/node_modules/pi-lens/dist/clients/hashline-anchor.js +0 -424
- package/node_modules/pi-lens/dist/clients/hook-budgets.js +0 -76
- package/node_modules/pi-lens/dist/clients/host-edit-normalize.js +0 -140
- package/node_modules/pi-lens/dist/clients/host-ports.js +0 -39
- package/node_modules/pi-lens/dist/clients/indent-retarget.js +0 -154
- package/node_modules/pi-lens/dist/clients/inline-blocker-dispositions.js +0 -186
- package/node_modules/pi-lens/dist/clients/install-diagnostics.js +0 -156
- package/node_modules/pi-lens/dist/clients/installer/index.js +0 -5416
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh-session.js +0 -58
- package/node_modules/pi-lens/dist/clients/installer/managed-tool-refresh.js +0 -783
- package/node_modules/pi-lens/dist/clients/instance-reaper-state.js +0 -7
- package/node_modules/pi-lens/dist/clients/instance-reaper.js +0 -1137
- package/node_modules/pi-lens/dist/clients/instance-registry-lock.js +0 -145
- package/node_modules/pi-lens/dist/clients/instance-registry.js +0 -773
- package/node_modules/pi-lens/dist/clients/jscpd-client.js +0 -378
- package/node_modules/pi-lens/dist/clients/json-cache-read.js +0 -65
- package/node_modules/pi-lens/dist/clients/knip-client.js +0 -788
- package/node_modules/pi-lens/dist/clients/language-policy.js +0 -252
- package/node_modules/pi-lens/dist/clients/language-profile.js +0 -361
- package/node_modules/pi-lens/dist/clients/language-registry.js +0 -603
- package/node_modules/pi-lens/dist/clients/latency-logger.js +0 -727
- package/node_modules/pi-lens/dist/clients/lazy-import.js +0 -21
- package/node_modules/pi-lens/dist/clients/ledger-bounds.js +0 -54
- package/node_modules/pi-lens/dist/clients/lens-config.js +0 -404
- package/node_modules/pi-lens/dist/clients/lens-engine.js +0 -358
- package/node_modules/pi-lens/dist/clients/lens-events.js +0 -125
- package/node_modules/pi-lens/dist/clients/lens-flag-registry.js +0 -467
- package/node_modules/pi-lens/dist/clients/lens-map.js +0 -1156
- package/node_modules/pi-lens/dist/clients/live-bus-emitter.js +0 -74
- package/node_modules/pi-lens/dist/clients/log-cleanup.js +0 -222
- package/node_modules/pi-lens/dist/clients/lsp/aggregation.js +0 -178
- package/node_modules/pi-lens/dist/clients/lsp/cascade-tier.js +0 -421
- package/node_modules/pi-lens/dist/clients/lsp/client.js +0 -4593
- package/node_modules/pi-lens/dist/clients/lsp/config.js +0 -691
- package/node_modules/pi-lens/dist/clients/lsp/content-limits.js +0 -41
- package/node_modules/pi-lens/dist/clients/lsp/diagnostic-binding.js +0 -225
- package/node_modules/pi-lens/dist/clients/lsp/document-drift.js +0 -467
- package/node_modules/pi-lens/dist/clients/lsp/edits.js +0 -1219
- package/node_modules/pi-lens/dist/clients/lsp/index.js +0 -8305
- package/node_modules/pi-lens/dist/clients/lsp/inferred-project.js +0 -216
- package/node_modules/pi-lens/dist/clients/lsp/jvm-runtime.js +0 -298
- package/node_modules/pi-lens/dist/clients/lsp/language.js +0 -63
- package/node_modules/pi-lens/dist/clients/lsp/launch.js +0 -746
- package/node_modules/pi-lens/dist/clients/lsp/lombok.js +0 -158
- package/node_modules/pi-lens/dist/clients/lsp/path-utils.js +0 -5
- package/node_modules/pi-lens/dist/clients/lsp/pending-aux-coverage.js +0 -251
- package/node_modules/pi-lens/dist/clients/lsp/position-encoding.js +0 -61
- package/node_modules/pi-lens/dist/clients/lsp/ruby-drive-dirs.js +0 -83
- package/node_modules/pi-lens/dist/clients/lsp/server.js +0 -3290
- package/node_modules/pi-lens/dist/clients/lsp/session-roots.js +0 -192
- package/node_modules/pi-lens/dist/clients/lsp/spawn-history.js +0 -17
- package/node_modules/pi-lens/dist/clients/lsp/sync-kind.js +0 -56
- package/node_modules/pi-lens/dist/clients/lsp/tsserver-sync.js +0 -332
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/capability-snapshot.js +0 -1
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/classification.js +0 -85
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/index.js +0 -10
- package/node_modules/pi-lens/dist/clients/lsp/wait-policy/strategies.js +0 -392
- package/node_modules/pi-lens/dist/clients/lsp/watch-queue.js +0 -61
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-cache.js +0 -639
- package/node_modules/pi-lens/dist/clients/lsp/workspace-diagnostics-session.js +0 -30
- package/node_modules/pi-lens/dist/clients/lsp/workspace-sweep-hold.js +0 -184
- package/node_modules/pi-lens/dist/clients/lsp-budget.js +0 -186
- package/node_modules/pi-lens/dist/clients/lsp-document-symbols.js +0 -110
- package/node_modules/pi-lens/dist/clients/lsp-lazy.js +0 -9
- package/node_modules/pi-lens/dist/clients/lsp-mutation.js +0 -396
- package/node_modules/pi-lens/dist/clients/lsp-status.js +0 -59
- package/node_modules/pi-lens/dist/clients/map-with-concurrency.js +0 -40
- package/node_modules/pi-lens/dist/clients/mcp/analyze.js +0 -420
- package/node_modules/pi-lens/dist/clients/mcp/host-shim.js +0 -31
- package/node_modules/pi-lens/dist/clients/mcp/ipc.js +0 -523
- package/node_modules/pi-lens/dist/clients/mcp/review.js +0 -197
- package/node_modules/pi-lens/dist/clients/mcp/session.js +0 -412
- package/node_modules/pi-lens/dist/clients/memory-sampler.js +0 -208
- package/node_modules/pi-lens/dist/clients/message-end-attribution.js +0 -30
- package/node_modules/pi-lens/dist/clients/metrics-client.js +0 -107
- package/node_modules/pi-lens/dist/clients/metrics-history.js +0 -327
- package/node_modules/pi-lens/dist/clients/middle-man-analysis.js +0 -263
- package/node_modules/pi-lens/dist/clients/model-provider.js +0 -53
- package/node_modules/pi-lens/dist/clients/module-report-lsp.js +0 -189
- package/node_modules/pi-lens/dist/clients/module-report.js +0 -2150
- package/node_modules/pi-lens/dist/clients/mutating-tool.js +0 -651
- package/node_modules/pi-lens/dist/clients/mutation-attribution.js +0 -368
- package/node_modules/pi-lens/dist/clients/mutation-bridge.js +0 -240
- package/node_modules/pi-lens/dist/clients/ndjson-logger.js +0 -734
- package/node_modules/pi-lens/dist/clients/observed-mutation-sources.js +0 -101
- package/node_modules/pi-lens/dist/clients/observed-mutation.js +0 -1253
- package/node_modules/pi-lens/dist/clients/oldtext-autopatch.js +0 -128
- package/node_modules/pi-lens/dist/clients/opaque-mutation-scan.js +0 -496
- package/node_modules/pi-lens/dist/clients/opengrep-client.js +0 -333
- package/node_modules/pi-lens/dist/clients/opengrep-config.js +0 -76
- package/node_modules/pi-lens/dist/clients/package-manager.js +0 -607
- package/node_modules/pi-lens/dist/clients/package-root.js +0 -38
- package/node_modules/pi-lens/dist/clients/partial-edit-apply.js +0 -439
- package/node_modules/pi-lens/dist/clients/path-attribution-telemetry.js +0 -43
- package/node_modules/pi-lens/dist/clients/path-keyed-map.js +0 -108
- package/node_modules/pi-lens/dist/clients/path-utils.js +0 -1244
- package/node_modules/pi-lens/dist/clients/performance-report.js +0 -331
- package/node_modules/pi-lens/dist/clients/persist-debounce.js +0 -70
- package/node_modules/pi-lens/dist/clients/persistent-reverify.js +0 -271
- package/node_modules/pi-lens/dist/clients/php-cs-fixer-config.js +0 -119
- package/node_modules/pi-lens/dist/clients/pipeline.js +0 -1480
- package/node_modules/pi-lens/dist/clients/post-autofix-notice.js +0 -37
- package/node_modules/pi-lens/dist/clients/print-mode.js +0 -21
- package/node_modules/pi-lens/dist/clients/probe-home-state.js +0 -228
- package/node_modules/pi-lens/dist/clients/process-bridge.js +0 -66
- package/node_modules/pi-lens/dist/clients/process-singletons.js +0 -210
- package/node_modules/pi-lens/dist/clients/process-snapshot.js +0 -68
- package/node_modules/pi-lens/dist/clients/project-changes.js +0 -181
- package/node_modules/pi-lens/dist/clients/project-conventions.js +0 -171
- package/node_modules/pi-lens/dist/clients/project-diagnostics/cache.js +0 -137
- package/node_modules/pi-lens/dist/clients/project-diagnostics/extractors.js +0 -109
- package/node_modules/pi-lens/dist/clients/project-diagnostics/fresh-fetch.js +0 -668
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/call-graph-impact.js +0 -118
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/dead-code.js +0 -47
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/gitleaks.js +0 -40
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/govulncheck.js +0 -34
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/jscpd.js +0 -38
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/knip.js +0 -47
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/madge.js +0 -48
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/opengrep.js +0 -40
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/runner-findings.js +0 -127
- package/node_modules/pi-lens/dist/clients/project-diagnostics/runner-adapters/trivy.js +0 -81
- package/node_modules/pi-lens/dist/clients/project-diagnostics/scanner.js +0 -520
- package/node_modules/pi-lens/dist/clients/project-diagnostics/types.js +0 -1
- package/node_modules/pi-lens/dist/clients/project-lens-config.js +0 -807
- package/node_modules/pi-lens/dist/clients/project-report.js +0 -701
- package/node_modules/pi-lens/dist/clients/project-scale.js +0 -237
- package/node_modules/pi-lens/dist/clients/project-scan-policy.js +0 -11
- package/node_modules/pi-lens/dist/clients/project-snapshot-fingerprint.js +0 -48
- package/node_modules/pi-lens/dist/clients/project-snapshot-persist-worker.js +0 -20
- package/node_modules/pi-lens/dist/clients/project-snapshot.js +0 -1653
- package/node_modules/pi-lens/dist/clients/project-trust.js +0 -181
- package/node_modules/pi-lens/dist/clients/python-environment.js +0 -217
- package/node_modules/pi-lens/dist/clients/python-provenance.js +0 -639
- package/node_modules/pi-lens/dist/clients/quiet-window-config.js +0 -47
- package/node_modules/pi-lens/dist/clients/quiet-window.js +0 -236
- package/node_modules/pi-lens/dist/clients/read-bridge.js +0 -139
- package/node_modules/pi-lens/dist/clients/read-expansion.js +0 -266
- package/node_modules/pi-lens/dist/clients/read-guard-logger.js +0 -251
- package/node_modules/pi-lens/dist/clients/read-guard-tool-lines.js +0 -1306
- package/node_modules/pi-lens/dist/clients/read-guard.js +0 -1561
- package/node_modules/pi-lens/dist/clients/recent-touches.js +0 -240
- package/node_modules/pi-lens/dist/clients/redact/secrets.js +0 -449
- package/node_modules/pi-lens/dist/clients/resource-sampler.js +0 -628
- package/node_modules/pi-lens/dist/clients/reverse-deps.js +0 -270
- package/node_modules/pi-lens/dist/clients/review-graph/builder.js +0 -5126
- package/node_modules/pi-lens/dist/clients/review-graph/format.js +0 -33
- package/node_modules/pi-lens/dist/clients/review-graph/git-identity.js +0 -163
- package/node_modules/pi-lens/dist/clients/review-graph/import-resolvers.js +0 -481
- package/node_modules/pi-lens/dist/clients/review-graph/persist-worker.js +0 -10
- package/node_modules/pi-lens/dist/clients/review-graph/query.js +0 -244
- package/node_modules/pi-lens/dist/clients/review-graph/revision-drift.js +0 -21
- package/node_modules/pi-lens/dist/clients/review-graph/service.js +0 -96
- package/node_modules/pi-lens/dist/clients/review-graph/shared-extraction-ir.js +0 -82
- package/node_modules/pi-lens/dist/clients/review-graph/symbol-id.js +0 -103
- package/node_modules/pi-lens/dist/clients/review-graph/tsconfig-paths.js +0 -299
- package/node_modules/pi-lens/dist/clients/review-graph/types.js +0 -1
- package/node_modules/pi-lens/dist/clients/review-graph/workspace-modules.js +0 -449
- package/node_modules/pi-lens/dist/clients/review-graph-logger.js +0 -81
- package/node_modules/pi-lens/dist/clients/ruff-client.js +0 -179
- package/node_modules/pi-lens/dist/clients/rules-scanner.js +0 -131
- package/node_modules/pi-lens/dist/clients/run-duration.js +0 -55
- package/node_modules/pi-lens/dist/clients/runtime-agent-end.js +0 -746
- package/node_modules/pi-lens/dist/clients/runtime-config.js +0 -77
- package/node_modules/pi-lens/dist/clients/runtime-context.js +0 -211
- package/node_modules/pi-lens/dist/clients/runtime-coordinator.js +0 -1324
- package/node_modules/pi-lens/dist/clients/runtime-session.js +0 -2485
- package/node_modules/pi-lens/dist/clients/runtime-tool-call.js +0 -1295
- package/node_modules/pi-lens/dist/clients/runtime-tool-result.js +0 -2049
- package/node_modules/pi-lens/dist/clients/runtime-turn.js +0 -3767
- package/node_modules/pi-lens/dist/clients/rust-client.js +0 -97
- package/node_modules/pi-lens/dist/clients/safe-spawn.js +0 -2357
- package/node_modules/pi-lens/dist/clients/sanitize.js +0 -286
- package/node_modules/pi-lens/dist/clients/scan-utils.js +0 -25
- package/node_modules/pi-lens/dist/clients/scratch-tree-policy.js +0 -165
- package/node_modules/pi-lens/dist/clients/search-read-registration.js +0 -94
- package/node_modules/pi-lens/dist/clients/secret-findings.js +0 -114
- package/node_modules/pi-lens/dist/clients/security-scan-client.js +0 -352
- package/node_modules/pi-lens/dist/clients/session-event-guard.js +0 -297
- package/node_modules/pi-lens/dist/clients/session-lifecycle.js +0 -484
- package/node_modules/pi-lens/dist/clients/session-start-observability.js +0 -94
- package/node_modules/pi-lens/dist/clients/session-state-store.js +0 -101
- package/node_modules/pi-lens/dist/clients/session-summary.js +0 -15
- package/node_modules/pi-lens/dist/clients/sessionstart-logger.js +0 -27
- package/node_modules/pi-lens/dist/clients/sg-runner.js +0 -876
- package/node_modules/pi-lens/dist/clients/sgconfig.js +0 -347
- package/node_modules/pi-lens/dist/clients/shared-checkout-guard.js +0 -338
- package/node_modules/pi-lens/dist/clients/single-flight.js +0 -158
- package/node_modules/pi-lens/dist/clients/situational-tool-telemetry.js +0 -108
- package/node_modules/pi-lens/dist/clients/skills-resolver.js +0 -141
- package/node_modules/pi-lens/dist/clients/slow-fs.js +0 -138
- package/node_modules/pi-lens/dist/clients/smells-rollup.js +0 -231
- package/node_modules/pi-lens/dist/clients/source-filter.js +0 -596
- package/node_modules/pi-lens/dist/clients/source-walker.js +0 -211
- package/node_modules/pi-lens/dist/clients/spawn-output-cap.js +0 -120
- package/node_modules/pi-lens/dist/clients/spawn-timeout-cooldown.js +0 -110
- package/node_modules/pi-lens/dist/clients/stale-marker.js +0 -17
- package/node_modules/pi-lens/dist/clients/startup-scan.js +0 -400
- package/node_modules/pi-lens/dist/clients/startup-timing.js +0 -130
- package/node_modules/pi-lens/dist/clients/string-utils.js +0 -33
- package/node_modules/pi-lens/dist/clients/subagent-mode.js +0 -111
- package/node_modules/pi-lens/dist/clients/symbol-containment.js +0 -24
- package/node_modules/pi-lens/dist/clients/symbol-types.js +0 -5
- package/node_modules/pi-lens/dist/clients/test-runner-client.js +0 -2507
- package/node_modules/pi-lens/dist/clients/test-runner-delivery.js +0 -287
- package/node_modules/pi-lens/dist/clients/todo-scanner.js +0 -229
- package/node_modules/pi-lens/dist/clients/tool-agreement.js +0 -423
- package/node_modules/pi-lens/dist/clients/tool-config.js +0 -249
- package/node_modules/pi-lens/dist/clients/tool-cwd.js +0 -270
- package/node_modules/pi-lens/dist/clients/tool-definition.js +0 -47
- package/node_modules/pi-lens/dist/clients/tool-event.js +0 -105
- package/node_modules/pi-lens/dist/clients/tool-policy.js +0 -2755
- package/node_modules/pi-lens/dist/clients/tool-probe.js +0 -53
- package/node_modules/pi-lens/dist/clients/tool-render.js +0 -156
- package/node_modules/pi-lens/dist/clients/tool-set-policy.js +0 -118
- package/node_modules/pi-lens/dist/clients/tree-sitter-cache.js +0 -364
- package/node_modules/pi-lens/dist/clients/tree-sitter-client.js +0 -4195
- package/node_modules/pi-lens/dist/clients/tree-sitter-logger.js +0 -100
- package/node_modules/pi-lens/dist/clients/tree-sitter-navigator.js +0 -269
- package/node_modules/pi-lens/dist/clients/tree-sitter-query-loader.js +0 -569
- package/node_modules/pi-lens/dist/clients/tree-sitter-shared.js +0 -158
- package/node_modules/pi-lens/dist/clients/tree-sitter-symbol-extractor.js +0 -1265
- package/node_modules/pi-lens/dist/clients/trivy-client.js +0 -467
- package/node_modules/pi-lens/dist/clients/tui-fit.js +0 -50
- package/node_modules/pi-lens/dist/clients/turn-context.js +0 -52
- package/node_modules/pi-lens/dist/clients/turn-end/lane.js +0 -51
- package/node_modules/pi-lens/dist/clients/turn-end/lanes/govulncheck.js +0 -112
- package/node_modules/pi-lens/dist/clients/turn-end/lanes/secrets.js +0 -204
- package/node_modules/pi-lens/dist/clients/turn-summary-render.js +0 -72
- package/node_modules/pi-lens/dist/clients/turn-summary.js +0 -132
- package/node_modules/pi-lens/dist/clients/typos-config.js +0 -29
- package/node_modules/pi-lens/dist/clients/user-notify.js +0 -51
- package/node_modules/pi-lens/dist/clients/vanished-instance-marker.js +0 -57
- package/node_modules/pi-lens/dist/clients/warm-attach.js +0 -305
- package/node_modules/pi-lens/dist/clients/widget-state.js +0 -1808
- package/node_modules/pi-lens/dist/clients/word-index-logger.js +0 -49
- package/node_modules/pi-lens/dist/clients/word-index-store.js +0 -459
- package/node_modules/pi-lens/dist/clients/word-index.js +0 -2080
- package/node_modules/pi-lens/dist/clients/workspace-topology.js +0 -443
- package/node_modules/pi-lens/dist/clients/write-ordering-guard.js +0 -65
- package/node_modules/pi-lens/dist/clients/zizmor-config.js +0 -340
- package/node_modules/pi-lens/dist/i18n.js +0 -69
- package/node_modules/pi-lens/dist/mcp/build-staleness.js +0 -124
- package/node_modules/pi-lens/dist/scripts/lib/process-scan.mjs +0 -583
- package/node_modules/pi-lens/dist/scripts/lib/skills-predicate.mjs +0 -129
- package/node_modules/pi-lens/dist/scripts/lib/web-tree-sitter-dir.mjs +0 -123
- package/node_modules/pi-lens/dist/tools/activate-tools.js +0 -87
- package/node_modules/pi-lens/dist/tools/ast-grep-outline.js +0 -137
- package/node_modules/pi-lens/dist/tools/ast-grep-replace.js +0 -244
- package/node_modules/pi-lens/dist/tools/ast-grep-search.js +0 -762
- package/node_modules/pi-lens/dist/tools/effective-config.js +0 -90
- package/node_modules/pi-lens/dist/tools/lens-diagnostic-mark.js +0 -375
- package/node_modules/pi-lens/dist/tools/lens-diagnostics.js +0 -2571
- package/node_modules/pi-lens/dist/tools/lsp-diagnostics.js +0 -1745
- package/node_modules/pi-lens/dist/tools/lsp-navigation.js +0 -1367
- package/node_modules/pi-lens/dist/tools/lsp-structured-output.js +0 -294
- package/node_modules/pi-lens/dist/tools/module-report.js +0 -382
- package/node_modules/pi-lens/dist/tools/project-report.js +0 -86
- package/node_modules/pi-lens/dist/tools/render-compact.js +0 -374
- package/node_modules/pi-lens/dist/tools/scan-progress.js +0 -53
- package/node_modules/pi-lens/dist/tools/shared.js +0 -29
- package/node_modules/pi-lens/dist/tools/symbol-search.js +0 -155
|
@@ -0,0 +1,4533 @@
|
|
|
1
|
+
import { createRequire as __pilensCreateRequire } from "node:module"; const require = __pilensCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
resolveToolCwd
|
|
4
|
+
} from "./chunk-RUOG5D2C.js";
|
|
5
|
+
import {
|
|
6
|
+
allAvailableGlobalBinDirs,
|
|
7
|
+
installArgs,
|
|
8
|
+
pmBinary,
|
|
9
|
+
resolveNodePackageManager
|
|
10
|
+
} from "./chunk-U3VGR4VU.js";
|
|
11
|
+
import {
|
|
12
|
+
assertInstallAllowed,
|
|
13
|
+
projectTrustDenialReason
|
|
14
|
+
} from "./chunk-NTUJEGDZ.js";
|
|
15
|
+
import {
|
|
16
|
+
probeToolAsync
|
|
17
|
+
} from "./chunk-EOKRQ3SA.js";
|
|
18
|
+
import {
|
|
19
|
+
TRANSIENT_MAX_COOLDOWN_MS,
|
|
20
|
+
classifyProbeFailure,
|
|
21
|
+
createAvailabilityLatch,
|
|
22
|
+
logAvailabilityDecision,
|
|
23
|
+
startHostStallSampler
|
|
24
|
+
} from "./chunk-WONERJTP.js";
|
|
25
|
+
import {
|
|
26
|
+
acquireBoundedPidFileLock,
|
|
27
|
+
acquireQuarantinePidFileLock,
|
|
28
|
+
createGenerationMap,
|
|
29
|
+
getGlobalPiLensDir,
|
|
30
|
+
heartbeatIntervalMs,
|
|
31
|
+
isLockContention,
|
|
32
|
+
ownsTopGeneration,
|
|
33
|
+
recordGenerationTakeover,
|
|
34
|
+
recordLegacyLockHeld,
|
|
35
|
+
releaseGeneration,
|
|
36
|
+
resetSafeSpawnWindowsCommandCache,
|
|
37
|
+
safeSpawnAsync,
|
|
38
|
+
startGenerationHeartbeat,
|
|
39
|
+
tryAcquireGeneration
|
|
40
|
+
} from "./chunk-VN2AXLEX.js";
|
|
41
|
+
import {
|
|
42
|
+
DEFAULT_MAX_OUTPUT_BYTES,
|
|
43
|
+
createBoundedOutputSink,
|
|
44
|
+
writeFileAtomic,
|
|
45
|
+
writeFileAtomicAsync
|
|
46
|
+
} from "./chunk-2EECBNC5.js";
|
|
47
|
+
import {
|
|
48
|
+
getDegradationLedgerGeneration,
|
|
49
|
+
incrementDegradationCount,
|
|
50
|
+
recordDegradationOnce
|
|
51
|
+
} from "./chunk-N3YQJI6O.js";
|
|
52
|
+
import {
|
|
53
|
+
logExtension
|
|
54
|
+
} from "./chunk-O6TQT6RI.js";
|
|
55
|
+
import {
|
|
56
|
+
createNdjsonLogger,
|
|
57
|
+
getGlobalPiLensLogDir,
|
|
58
|
+
getMaxLogSizeMB,
|
|
59
|
+
isTestMode
|
|
60
|
+
} from "./chunk-UK3CMEAL.js";
|
|
61
|
+
import {
|
|
62
|
+
BoundedLruCache
|
|
63
|
+
} from "./chunk-ABBOA7UD.js";
|
|
64
|
+
import {
|
|
65
|
+
findLocalToolConfig,
|
|
66
|
+
isFullyQualified
|
|
67
|
+
} from "./chunk-Q5U6FMDI.js";
|
|
68
|
+
import {
|
|
69
|
+
__require
|
|
70
|
+
} from "./chunk-NXL4FFQQ.js";
|
|
71
|
+
|
|
72
|
+
// dist/clients/installer/index.js
|
|
73
|
+
import { spawn } from "node:child_process";
|
|
74
|
+
import { existsSync, readFileSync as readFileSync2, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
75
|
+
import fs2 from "node:fs/promises";
|
|
76
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
77
|
+
import https from "node:https";
|
|
78
|
+
import { createRequire } from "node:module";
|
|
79
|
+
import os from "node:os";
|
|
80
|
+
import path3 from "node:path";
|
|
81
|
+
import { createGunzip } from "node:zlib";
|
|
82
|
+
|
|
83
|
+
// dist/clients/durable-store.js
|
|
84
|
+
import * as fs from "node:fs";
|
|
85
|
+
import fsp from "node:fs/promises";
|
|
86
|
+
function readLocked(path4) {
|
|
87
|
+
try {
|
|
88
|
+
return fs.readFileSync(path4, "utf8");
|
|
89
|
+
} catch {
|
|
90
|
+
return void 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function commitDurableStore(options) {
|
|
94
|
+
const release = options.onContention === "skip-log" ? acquireBoundedPidFileLock(`${options.path}.lock`, {
|
|
95
|
+
waitMs: options.waitMs,
|
|
96
|
+
retryMs: options.retryMs,
|
|
97
|
+
timeoutMessage: options.timeoutMessage,
|
|
98
|
+
onContention: "skip-log",
|
|
99
|
+
logContention: options.logContention
|
|
100
|
+
}) : acquireBoundedPidFileLock(`${options.path}.lock`, {
|
|
101
|
+
waitMs: options.waitMs,
|
|
102
|
+
retryMs: options.retryMs,
|
|
103
|
+
timeoutMessage: options.timeoutMessage,
|
|
104
|
+
onContention: "throw"
|
|
105
|
+
});
|
|
106
|
+
if (!release)
|
|
107
|
+
return void 0;
|
|
108
|
+
let committed;
|
|
109
|
+
try {
|
|
110
|
+
const current = options.deserialize(readLocked(options.path));
|
|
111
|
+
committed = options.merge(current);
|
|
112
|
+
writeFileAtomic(options.path, options.serialize(committed), {
|
|
113
|
+
bestEffort: false
|
|
114
|
+
});
|
|
115
|
+
options.afterWriteLocked?.(committed);
|
|
116
|
+
} finally {
|
|
117
|
+
release();
|
|
118
|
+
}
|
|
119
|
+
return committed;
|
|
120
|
+
}
|
|
121
|
+
async function readLockedAsync(path4) {
|
|
122
|
+
try {
|
|
123
|
+
return await fsp.readFile(path4, "utf8");
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (error.code === "ENOENT")
|
|
126
|
+
return void 0;
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
async function commitDurableStoreAsync(options) {
|
|
131
|
+
const release = options.onContention === "skip-log" ? await acquireQuarantinePidFileLock(`${options.path}.lock`, {
|
|
132
|
+
waitMs: options.waitMs,
|
|
133
|
+
retryMs: options.retryMs,
|
|
134
|
+
staleMs: options.staleMs,
|
|
135
|
+
timeoutMessage: options.timeoutMessage,
|
|
136
|
+
onContention: "skip-log",
|
|
137
|
+
logContention: options.logContention
|
|
138
|
+
}) : await acquireQuarantinePidFileLock(`${options.path}.lock`, {
|
|
139
|
+
waitMs: options.waitMs,
|
|
140
|
+
retryMs: options.retryMs,
|
|
141
|
+
staleMs: options.staleMs,
|
|
142
|
+
timeoutMessage: options.timeoutMessage,
|
|
143
|
+
onContention: "throw"
|
|
144
|
+
});
|
|
145
|
+
if (!release)
|
|
146
|
+
return void 0;
|
|
147
|
+
try {
|
|
148
|
+
const current = options.deserialize(await readLockedAsync(options.path));
|
|
149
|
+
const committed = options.merge(current);
|
|
150
|
+
await writeFileAtomicAsync(options.path, options.serialize(committed), {
|
|
151
|
+
bestEffort: false
|
|
152
|
+
});
|
|
153
|
+
await options.afterWriteLocked?.(committed);
|
|
154
|
+
return committed;
|
|
155
|
+
} finally {
|
|
156
|
+
await release();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// dist/clients/sessionstart-logger.js
|
|
161
|
+
import * as path from "node:path";
|
|
162
|
+
var SESSIONSTART_LOG_FILE = path.join(getGlobalPiLensLogDir(), "sessionstart.log");
|
|
163
|
+
var writer = createNdjsonLogger({
|
|
164
|
+
filePath: SESSIONSTART_LOG_FILE,
|
|
165
|
+
maxBytes: getMaxLogSizeMB() * 1024 * 1024,
|
|
166
|
+
backupPath: `${SESSIONSTART_LOG_FILE}.1`
|
|
167
|
+
});
|
|
168
|
+
function logSessionStart(message) {
|
|
169
|
+
if (isTestMode())
|
|
170
|
+
return;
|
|
171
|
+
writer.append(`[${(/* @__PURE__ */ new Date()).toISOString()}] ${message}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// dist/clients/zizmor-config.js
|
|
175
|
+
import * as path2 from "node:path";
|
|
176
|
+
var LOCAL_ZIZMOR_CONFIG_NAMES = [
|
|
177
|
+
path2.join(".github", "zizmor.yml"),
|
|
178
|
+
path2.join(".github", "zizmor.yaml"),
|
|
179
|
+
"zizmor.yml",
|
|
180
|
+
"zizmor.yaml"
|
|
181
|
+
];
|
|
182
|
+
function findLocalZizmorConfig(startDir) {
|
|
183
|
+
return findLocalToolConfig(startDir, LOCAL_ZIZMOR_CONFIG_NAMES);
|
|
184
|
+
}
|
|
185
|
+
function isZizmorAuditTarget(filePath) {
|
|
186
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
187
|
+
const base = path2.basename(normalized).toLowerCase();
|
|
188
|
+
if (/(^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(normalized))
|
|
189
|
+
return true;
|
|
190
|
+
if (base === "action.yml" || base === "action.yaml")
|
|
191
|
+
return true;
|
|
192
|
+
if (/(^|\/)\.github\/dependabot\.ya?ml$/i.test(normalized))
|
|
193
|
+
return true;
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
var ZIZMOR_TOKEN_MAX_COOLDOWN_MS = 12e4;
|
|
197
|
+
var ghTokenLatch = createAvailabilityLatch({
|
|
198
|
+
maxCooldownMs: ZIZMOR_TOKEN_MAX_COOLDOWN_MS
|
|
199
|
+
});
|
|
200
|
+
var cachedToken;
|
|
201
|
+
var GH_TOKEN_PROBE_TIMEOUT_MS = 5e3;
|
|
202
|
+
function resetZizmorTokenAvailability() {
|
|
203
|
+
ghTokenLatch.reset();
|
|
204
|
+
cachedToken = void 0;
|
|
205
|
+
}
|
|
206
|
+
function classifyGhTokenFailure(res, hostStallMs) {
|
|
207
|
+
const neverAnswered = res.status === null || (res.status ?? 0) < 0;
|
|
208
|
+
if (!res.error && !neverAnswered) {
|
|
209
|
+
return {
|
|
210
|
+
outcome: "non-installable",
|
|
211
|
+
cause: "probe-rejected",
|
|
212
|
+
classifiedBy: "caller"
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
if (res.spawnFailure?.kind === "tool-not-found") {
|
|
216
|
+
return { outcome: "missing", cause: "not-found", classifiedBy: "caller" };
|
|
217
|
+
}
|
|
218
|
+
const classified = classifyProbeFailure(res, { hostStallMs });
|
|
219
|
+
if (classified.outcome === "transient" || classified.outcome === "missing") {
|
|
220
|
+
return { ...classified, classifiedBy: "probe" };
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
outcome: "transient",
|
|
224
|
+
cause: classified.cause,
|
|
225
|
+
classifiedBy: "caller"
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
async function deriveGhCliToken(consumer) {
|
|
229
|
+
const sampler = startHostStallSampler();
|
|
230
|
+
const startedAt = Date.now();
|
|
231
|
+
const res = await safeSpawnAsync("gh", ["auth", "token"], {
|
|
232
|
+
timeout: GH_TOKEN_PROBE_TIMEOUT_MS,
|
|
233
|
+
ignoreAmbientSignal: true
|
|
234
|
+
});
|
|
235
|
+
const hostStallMs = sampler.stop();
|
|
236
|
+
const elapsedMs = Date.now() - startedAt;
|
|
237
|
+
if (!res.error && res.status === 0) {
|
|
238
|
+
const token = res.stdout.trim();
|
|
239
|
+
if (token.length > 0) {
|
|
240
|
+
ghTokenLatch.noteAvailable();
|
|
241
|
+
logAvailabilityDecision({
|
|
242
|
+
tool: consumer === "zizmor" ? "zizmor-gh-token" : "github-token",
|
|
243
|
+
verdict: "available",
|
|
244
|
+
outcome: "success",
|
|
245
|
+
cause: "ok",
|
|
246
|
+
elapsedMs,
|
|
247
|
+
latched: true,
|
|
248
|
+
hostStallMs,
|
|
249
|
+
budgetMs: GH_TOKEN_PROBE_TIMEOUT_MS,
|
|
250
|
+
classifiedBy: "probe"
|
|
251
|
+
});
|
|
252
|
+
return token;
|
|
253
|
+
}
|
|
254
|
+
return recordGhTokenUnavailable(consumer, {
|
|
255
|
+
outcome: "non-installable",
|
|
256
|
+
cause: "empty-result",
|
|
257
|
+
classifiedBy: "caller"
|
|
258
|
+
}, elapsedMs, hostStallMs);
|
|
259
|
+
}
|
|
260
|
+
return recordGhTokenUnavailable(consumer, classifyGhTokenFailure(res, hostStallMs), elapsedMs, hostStallMs);
|
|
261
|
+
}
|
|
262
|
+
function recordGhTokenUnavailable(consumer, { outcome, cause, classifiedBy }, elapsedMs, hostStallMs) {
|
|
263
|
+
const retryAfterMs = ghTokenLatch.noteUnavailable(outcome, cause);
|
|
264
|
+
if (consumer === "zizmor" && outcome === "transient") {
|
|
265
|
+
recordZizmorOfflineDegradation(`gh auth token probe ${cause}; running offline until the next zizmor start (retry allowed in ${Math.round(retryAfterMs / 1e3)}s)`);
|
|
266
|
+
}
|
|
267
|
+
logAvailabilityDecision({
|
|
268
|
+
tool: consumer === "zizmor" ? "zizmor-gh-token" : "github-token",
|
|
269
|
+
verdict: "unavailable",
|
|
270
|
+
outcome,
|
|
271
|
+
cause,
|
|
272
|
+
elapsedMs,
|
|
273
|
+
latched: outcome !== "transient",
|
|
274
|
+
hostStallMs,
|
|
275
|
+
...retryAfterMs > 0 && { retryAfterMs },
|
|
276
|
+
budgetMs: GH_TOKEN_PROBE_TIMEOUT_MS,
|
|
277
|
+
// Shared tail for both call paths; the caller carries whether ITS
|
|
278
|
+
// own verdict was a `classifyProbeFailure` passthrough or one of
|
|
279
|
+
// this module's own assertions (#2226 review F2).
|
|
280
|
+
classifiedBy
|
|
281
|
+
});
|
|
282
|
+
return void 0;
|
|
283
|
+
}
|
|
284
|
+
function recordZizmorOfflineDegradation(reason) {
|
|
285
|
+
incrementDegradationCount({
|
|
286
|
+
kind: "mode-suppression",
|
|
287
|
+
subject: "zizmor",
|
|
288
|
+
reason
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
async function resolveZizmorGitHubToken() {
|
|
292
|
+
if (process.env.ZIZMOR_OFFLINE)
|
|
293
|
+
return void 0;
|
|
294
|
+
return resolveGitHubToken(["ZIZMOR_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"], { consumer: "zizmor" });
|
|
295
|
+
}
|
|
296
|
+
async function resolveGitHubToken(envKeys = ["GITHUB_TOKEN", "GH_TOKEN"], options = {}) {
|
|
297
|
+
const consumer = options.consumer ?? "github-api";
|
|
298
|
+
const fromEnv = envKeys.map((key) => process.env[key]).find(Boolean);
|
|
299
|
+
if (fromEnv)
|
|
300
|
+
return fromEnv;
|
|
301
|
+
const memo = ghTokenLatch.read();
|
|
302
|
+
if (memo !== null) {
|
|
303
|
+
if (consumer === "zizmor" && memo === false && ghTokenLatch.getOutcome() === "transient") {
|
|
304
|
+
const cause = ghTokenLatch.getCause();
|
|
305
|
+
if (cause === null) {
|
|
306
|
+
throw new Error("zizmor gh-token latch: transient outcome with no cause (invariant violated)");
|
|
307
|
+
}
|
|
308
|
+
const retryAfterMs = Math.max(0, ghTokenLatch.getRetryAtMs() - Date.now());
|
|
309
|
+
recordZizmorOfflineDegradation(`gh auth token still cooling down (${cause}); serving cached offline verdict, retry allowed in ${Math.round(retryAfterMs / 1e3)}s`);
|
|
310
|
+
logAvailabilityDecision({
|
|
311
|
+
tool: "zizmor-gh-token",
|
|
312
|
+
verdict: "unavailable",
|
|
313
|
+
outcome: "transient",
|
|
314
|
+
cause,
|
|
315
|
+
elapsedMs: 0,
|
|
316
|
+
latched: false,
|
|
317
|
+
hostStallMs: 0,
|
|
318
|
+
...retryAfterMs > 0 && { retryAfterMs },
|
|
319
|
+
budgetMs: GH_TOKEN_PROBE_TIMEOUT_MS,
|
|
320
|
+
// No probe ran here: the latch's own remembered cause is replayed
|
|
321
|
+
// as-is, so the call site is the one asserting it (#2209).
|
|
322
|
+
classifiedBy: "caller"
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
return memo ? cachedToken : void 0;
|
|
326
|
+
}
|
|
327
|
+
cachedToken = await deriveGhCliToken(consumer);
|
|
328
|
+
return cachedToken;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// dist/clients/installer/index.js
|
|
332
|
+
var _installerRequire = createRequire(import.meta.url);
|
|
333
|
+
var TOOLS_DIR = path3.join(getGlobalPiLensDir(), "tools");
|
|
334
|
+
var INSTALL_LOCK_PATH = path3.join(TOOLS_DIR, ".install.lock");
|
|
335
|
+
var INSTALL_LOCK_GENERATIONS = `${INSTALL_LOCK_PATH}s`;
|
|
336
|
+
var activeInstallLocks = /* @__PURE__ */ new Set();
|
|
337
|
+
var installLockExitCleanupRegistered = false;
|
|
338
|
+
function getManagedToolsDir() {
|
|
339
|
+
return TOOLS_DIR;
|
|
340
|
+
}
|
|
341
|
+
function isProcessAlive(pid) {
|
|
342
|
+
try {
|
|
343
|
+
process.kill(pid, 0);
|
|
344
|
+
return true;
|
|
345
|
+
} catch (error) {
|
|
346
|
+
return error.code === "EPERM";
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
function installLockMaxAgeMs() {
|
|
350
|
+
return (Number(process.env.PI_LENS_INSTALL_TIMEOUT_MS) || 12e4) + 6e4;
|
|
351
|
+
}
|
|
352
|
+
function legacyInstallLockIsStale(maxAgeMs) {
|
|
353
|
+
try {
|
|
354
|
+
const owner = JSON.parse(readFileSync2(INSTALL_LOCK_PATH, "utf8"));
|
|
355
|
+
const expired = Number.isFinite(owner.createdAt) && Date.now() - owner.createdAt > maxAgeMs;
|
|
356
|
+
return expired || Number.isInteger(owner.pid) && owner.pid > 0 && !isProcessAlive(owner.pid);
|
|
357
|
+
} catch {
|
|
358
|
+
try {
|
|
359
|
+
return Date.now() - statSync(INSTALL_LOCK_PATH).mtimeMs > maxAgeMs;
|
|
360
|
+
} catch {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
function createLegacyInstallLock(token) {
|
|
366
|
+
try {
|
|
367
|
+
writeFileSync(INSTALL_LOCK_PATH, token, { flag: "wx" });
|
|
368
|
+
return true;
|
|
369
|
+
} catch (cause) {
|
|
370
|
+
if (isLockContention(cause))
|
|
371
|
+
return false;
|
|
372
|
+
throw cause;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
function takeLegacyInstallLock(maxAgeMs, token) {
|
|
376
|
+
if (createLegacyInstallLock(token))
|
|
377
|
+
return true;
|
|
378
|
+
if (!legacyInstallLockIsStale(maxAgeMs))
|
|
379
|
+
return false;
|
|
380
|
+
try {
|
|
381
|
+
unlinkSync(INSTALL_LOCK_PATH);
|
|
382
|
+
} catch {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
385
|
+
return createLegacyInstallLock(token);
|
|
386
|
+
}
|
|
387
|
+
function installLockOwner() {
|
|
388
|
+
try {
|
|
389
|
+
const owner = JSON.parse(readFileSync2(INSTALL_LOCK_PATH, "utf8"));
|
|
390
|
+
return `pid=${owner.pid} createdAt=${owner.createdAt}`;
|
|
391
|
+
} catch {
|
|
392
|
+
return "unknown owner";
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
function tryAcquireInstallLock(maxAgeMs) {
|
|
396
|
+
const hold = tryAcquireGeneration(INSTALL_LOCK_GENERATIONS, maxAgeMs);
|
|
397
|
+
if (!hold)
|
|
398
|
+
return "busy";
|
|
399
|
+
if (hold.tookOverStale)
|
|
400
|
+
recordGenerationTakeover(hold);
|
|
401
|
+
const heartbeat = startGenerationHeartbeat(hold, heartbeatIntervalMs(maxAgeMs));
|
|
402
|
+
const token = JSON.stringify({
|
|
403
|
+
pid: process.pid,
|
|
404
|
+
createdAt: Date.now(),
|
|
405
|
+
nonce: randomUUID()
|
|
406
|
+
});
|
|
407
|
+
let took;
|
|
408
|
+
try {
|
|
409
|
+
took = takeLegacyInstallLock(maxAgeMs, token);
|
|
410
|
+
} catch (cause) {
|
|
411
|
+
heartbeat.stop();
|
|
412
|
+
releaseGeneration(hold);
|
|
413
|
+
throw cause;
|
|
414
|
+
}
|
|
415
|
+
if (took)
|
|
416
|
+
return { generation: hold, token, heartbeat };
|
|
417
|
+
heartbeat.stop();
|
|
418
|
+
releaseGeneration(hold);
|
|
419
|
+
return "legacy-held";
|
|
420
|
+
}
|
|
421
|
+
function releaseInstallLock(hold) {
|
|
422
|
+
activeInstallLocks.delete(hold);
|
|
423
|
+
hold.heartbeat.stop();
|
|
424
|
+
try {
|
|
425
|
+
if (readFileSync2(INSTALL_LOCK_PATH, "utf8") === hold.token)
|
|
426
|
+
unlinkSync(INSTALL_LOCK_PATH);
|
|
427
|
+
} catch {
|
|
428
|
+
}
|
|
429
|
+
releaseGeneration(hold.generation);
|
|
430
|
+
}
|
|
431
|
+
async function acquireInstallLock() {
|
|
432
|
+
const timeoutMs = Number(process.env.PI_LENS_INSTALL_LOCK_TIMEOUT_MS) || 15e4;
|
|
433
|
+
const deadline = Date.now() + timeoutMs;
|
|
434
|
+
const maxAgeMs = installLockMaxAgeMs();
|
|
435
|
+
let legacyHeldRecorded = false;
|
|
436
|
+
while (Date.now() < deadline) {
|
|
437
|
+
const hold = tryAcquireInstallLock(maxAgeMs);
|
|
438
|
+
if (hold === "legacy-held" && !legacyHeldRecorded) {
|
|
439
|
+
legacyHeldRecorded = true;
|
|
440
|
+
recordLegacyLockHeld(INSTALL_LOCK_PATH);
|
|
441
|
+
}
|
|
442
|
+
if (typeof hold === "object") {
|
|
443
|
+
activeInstallLocks.add(hold);
|
|
444
|
+
if (!installLockExitCleanupRegistered) {
|
|
445
|
+
installLockExitCleanupRegistered = true;
|
|
446
|
+
process.once("exit", () => {
|
|
447
|
+
for (const held of activeInstallLocks)
|
|
448
|
+
releaseInstallLock(held);
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
let released = false;
|
|
452
|
+
return {
|
|
453
|
+
release: async () => {
|
|
454
|
+
if (released)
|
|
455
|
+
return;
|
|
456
|
+
released = true;
|
|
457
|
+
releaseInstallLock(hold);
|
|
458
|
+
},
|
|
459
|
+
ownsLock: () => ownsTopGeneration(hold.generation)
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
reason: `timed out after ${timeoutMs}ms waiting for shared tools install lock (${installLockOwner()})`
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
var GITHUB_BIN_DIR = path3.join(getGlobalPiLensDir(), "bin");
|
|
469
|
+
var DEBUG = process.env.PI_LENS_DEBUG === "1" || process.argv.includes("--debug");
|
|
470
|
+
function installerPlatform() {
|
|
471
|
+
const override = process.env.PI_LENS_TEST_PLATFORM;
|
|
472
|
+
if (override === "win32" || override === "linux") {
|
|
473
|
+
return override;
|
|
474
|
+
}
|
|
475
|
+
return process.platform;
|
|
476
|
+
}
|
|
477
|
+
function debugLog(...args) {
|
|
478
|
+
if (DEBUG) {
|
|
479
|
+
logExtension({
|
|
480
|
+
subsystem: "auto-install",
|
|
481
|
+
level: "debug",
|
|
482
|
+
message: args.map((arg) => typeof arg === "string" ? arg : JSON.stringify(arg)).join(" ")
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
function archAssetMatch(table) {
|
|
487
|
+
return (platform, arch) => {
|
|
488
|
+
if (arch !== "x64" && arch !== "arm64")
|
|
489
|
+
return void 0;
|
|
490
|
+
return table[platform]?.[arch];
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
var OS_ARCH_ZIP_ASSETS = {
|
|
494
|
+
linux: { x64: "linux_amd64.zip", arm64: "linux_arm64.zip" },
|
|
495
|
+
darwin: { x64: "darwin_amd64.zip", arm64: "darwin_arm64.zip" },
|
|
496
|
+
win32: { x64: "windows_amd64.zip", arm64: "windows_arm64.zip" }
|
|
497
|
+
};
|
|
498
|
+
function managedPackageFormatterTool(spec) {
|
|
499
|
+
return {
|
|
500
|
+
id: spec.id,
|
|
501
|
+
name: spec.name,
|
|
502
|
+
checkCommand: spec.id,
|
|
503
|
+
checkArgs: ["--version"],
|
|
504
|
+
installStrategy: spec.installStrategy,
|
|
505
|
+
packageName: spec.packageName,
|
|
506
|
+
binaryName: spec.id
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
var MANAGED_PACKAGE_FORMATTERS = [
|
|
510
|
+
{ id: "black", name: "Black", installStrategy: "pip", packageName: "black" },
|
|
511
|
+
{
|
|
512
|
+
id: "cmake-format",
|
|
513
|
+
name: "cmake-format",
|
|
514
|
+
installStrategy: "pip",
|
|
515
|
+
// The `yaml` EXTRA, never the bare distribution (#3312). cmakelang reads a
|
|
516
|
+
// `.cmake-format.yaml` project config through `import yaml`, and upstream
|
|
517
|
+
// puts PyYAML behind an extra: cmakelang 0.6.13's PyPI metadata declares
|
|
518
|
+
// `pyyaml (>=5.3) ; extra == 'yaml'`. A bare install still answers
|
|
519
|
+
// `cmake-format --version` with status 0 and then dies with
|
|
520
|
+
// `ModuleNotFoundError: No module named 'yaml'` on the first YAML config —
|
|
521
|
+
// the nightly's red cmake row. Every rung of the pip ladder passes this
|
|
522
|
+
// string verbatim to pip/pipx, which both accept the `pkg[extra]` spec.
|
|
523
|
+
packageName: "cmakelang[yaml]"
|
|
524
|
+
},
|
|
525
|
+
{ id: "oxfmt", name: "oxfmt", installStrategy: "npm", packageName: "oxfmt" }
|
|
526
|
+
];
|
|
527
|
+
function managedGitHubFormatterTool(spec) {
|
|
528
|
+
return {
|
|
529
|
+
id: spec.id,
|
|
530
|
+
name: spec.name,
|
|
531
|
+
checkCommand: spec.id,
|
|
532
|
+
checkArgs: ["--version"],
|
|
533
|
+
installStrategy: "github",
|
|
534
|
+
binaryName: spec.id,
|
|
535
|
+
github: {
|
|
536
|
+
repo: `${spec.owner}/${spec.repo}`,
|
|
537
|
+
assetMatch: spec.assetPattern,
|
|
538
|
+
...spec.binaryInArchive && { binaryInArchive: spec.binaryInArchive },
|
|
539
|
+
...spec.kind === "phar" && { launcher: "php" },
|
|
540
|
+
...spec.kind === "jar" && { launcher: "java" }
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
var MANAGED_GITHUB_FORMATTERS = [
|
|
545
|
+
{
|
|
546
|
+
id: "typstyle",
|
|
547
|
+
name: "typstyle",
|
|
548
|
+
owner: "typstyle-rs",
|
|
549
|
+
repo: "typstyle",
|
|
550
|
+
assetPattern: archAssetMatch({
|
|
551
|
+
linux: {
|
|
552
|
+
x64: "typstyle-x86_64-unknown-linux-gnu",
|
|
553
|
+
arm64: "typstyle-aarch64-unknown-linux-gnu"
|
|
554
|
+
},
|
|
555
|
+
darwin: {
|
|
556
|
+
x64: "typstyle-x86_64-apple-darwin",
|
|
557
|
+
arm64: "typstyle-aarch64-apple-darwin"
|
|
558
|
+
},
|
|
559
|
+
win32: {
|
|
560
|
+
x64: "typstyle-x86_64-pc-windows-msvc.exe",
|
|
561
|
+
arm64: "typstyle-aarch64-pc-windows-msvc.exe"
|
|
562
|
+
}
|
|
563
|
+
}),
|
|
564
|
+
kind: "binary"
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
id: "stylua",
|
|
568
|
+
name: "StyLua",
|
|
569
|
+
owner: "JohnnyMorganz",
|
|
570
|
+
repo: "StyLua",
|
|
571
|
+
assetPattern: archAssetMatch({
|
|
572
|
+
linux: { x64: "linux-x86_64.zip", arm64: "linux-aarch64.zip" },
|
|
573
|
+
darwin: { x64: "macos-x86_64.zip", arm64: "macos-aarch64.zip" },
|
|
574
|
+
win32: { x64: "windows-x86_64.zip" }
|
|
575
|
+
}),
|
|
576
|
+
kind: "binary",
|
|
577
|
+
binaryInArchive: "stylua"
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
id: "php-cs-fixer",
|
|
581
|
+
name: "PHP CS Fixer",
|
|
582
|
+
owner: "PHP-CS-Fixer",
|
|
583
|
+
repo: "PHP-CS-Fixer",
|
|
584
|
+
assetPattern: (platform) => platform === "linux" || platform === "darwin" || platform === "win32" ? "php-cs-fixer.phar" : void 0,
|
|
585
|
+
kind: "phar"
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
id: "cljfmt",
|
|
589
|
+
name: "cljfmt",
|
|
590
|
+
owner: "weavejester",
|
|
591
|
+
repo: "cljfmt",
|
|
592
|
+
assetPattern: (platform, arch) => {
|
|
593
|
+
if (platform === "linux")
|
|
594
|
+
return arch === "arm64" ? "standalone.jar" : "linux-amd64-static.tar.gz";
|
|
595
|
+
if (platform === "darwin")
|
|
596
|
+
return "standalone.jar";
|
|
597
|
+
if (platform === "win32")
|
|
598
|
+
return "win-amd64.zip";
|
|
599
|
+
return void 0;
|
|
600
|
+
},
|
|
601
|
+
kind: "jar",
|
|
602
|
+
binaryInArchive: "cljfmt"
|
|
603
|
+
}
|
|
604
|
+
];
|
|
605
|
+
var MANAGED_MAVEN_FORMATTERS = [
|
|
606
|
+
{
|
|
607
|
+
id: "google-java-format",
|
|
608
|
+
name: "google-java-format",
|
|
609
|
+
groupId: "com.google.googlejavaformat",
|
|
610
|
+
artifactId: "google-java-format",
|
|
611
|
+
version: "1.27.0",
|
|
612
|
+
classifier: "all-deps"
|
|
613
|
+
}
|
|
614
|
+
];
|
|
615
|
+
function managedMavenFormatterTool(spec) {
|
|
616
|
+
return {
|
|
617
|
+
id: spec.id,
|
|
618
|
+
name: spec.name,
|
|
619
|
+
checkCommand: spec.id,
|
|
620
|
+
checkArgs: ["--version"],
|
|
621
|
+
installStrategy: "maven",
|
|
622
|
+
binaryName: spec.id,
|
|
623
|
+
maven: {
|
|
624
|
+
groupId: spec.groupId,
|
|
625
|
+
artifactId: spec.artifactId,
|
|
626
|
+
version: spec.version,
|
|
627
|
+
classifier: spec.classifier
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
var TOOLS = [
|
|
632
|
+
// Core LSP servers
|
|
633
|
+
{
|
|
634
|
+
id: "typescript-language-server",
|
|
635
|
+
name: "TypeScript Language Server",
|
|
636
|
+
checkCommand: "typescript-language-server",
|
|
637
|
+
checkArgs: ["--version"],
|
|
638
|
+
installStrategy: "npm",
|
|
639
|
+
// Pinned below the package's own floor: 6.0.0 declares
|
|
640
|
+
// engines.node >=22.22.2, above the pi host's own floor (pi-lens
|
|
641
|
+
// must never require more than pi does — #2633) — an unpinned
|
|
642
|
+
// install here resolves latest and prints EBADENGINE on any pi
|
|
643
|
+
// host's supported Node. 5.3.0's own floor is engines.node >=20.
|
|
644
|
+
packageName: "typescript-language-server@5.3.0",
|
|
645
|
+
binaryName: "typescript-language-server"
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
id: "typescript",
|
|
649
|
+
name: "TypeScript",
|
|
650
|
+
checkCommand: "tsc",
|
|
651
|
+
checkArgs: ["--version"],
|
|
652
|
+
installStrategy: "npm",
|
|
653
|
+
// The managed compiler serves the classic typescript-language-server
|
|
654
|
+
// fallback. TypeScript 7 removed lib/tsserver.js and is selected only from
|
|
655
|
+
// project-local installs through the native `tsc --lsp --stdio` path.
|
|
656
|
+
// Revisit when typescript-language-server supports TS 7 — refs #1436.
|
|
657
|
+
packageName: "typescript@5.9.3",
|
|
658
|
+
binaryName: "tsc"
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
id: "pyright",
|
|
662
|
+
name: "Pyright",
|
|
663
|
+
checkCommand: "pyright",
|
|
664
|
+
checkArgs: ["--version"],
|
|
665
|
+
installStrategy: "npm",
|
|
666
|
+
packageName: "pyright",
|
|
667
|
+
binaryName: "pyright"
|
|
668
|
+
},
|
|
669
|
+
// Linting/formatting tools
|
|
670
|
+
{
|
|
671
|
+
id: "prettier",
|
|
672
|
+
name: "Prettier",
|
|
673
|
+
checkCommand: "prettier",
|
|
674
|
+
checkArgs: ["--version"],
|
|
675
|
+
installStrategy: "npm",
|
|
676
|
+
packageName: "prettier",
|
|
677
|
+
binaryName: "prettier"
|
|
678
|
+
},
|
|
679
|
+
...MANAGED_PACKAGE_FORMATTERS.map(managedPackageFormatterTool),
|
|
680
|
+
{
|
|
681
|
+
id: "ruff",
|
|
682
|
+
name: "Ruff",
|
|
683
|
+
checkCommand: "ruff",
|
|
684
|
+
checkArgs: ["--version"],
|
|
685
|
+
installStrategy: "pip",
|
|
686
|
+
packageName: "ruff",
|
|
687
|
+
binaryName: "ruff"
|
|
688
|
+
},
|
|
689
|
+
{
|
|
690
|
+
// Alternate Python LSP (fallback when pyright/the `python` server is
|
|
691
|
+
// unavailable or disabled). Used as a managedToolId by PythonJediServer.
|
|
692
|
+
id: "jedi-language-server",
|
|
693
|
+
name: "Jedi Language Server",
|
|
694
|
+
checkCommand: "jedi-language-server",
|
|
695
|
+
checkArgs: ["--version"],
|
|
696
|
+
installStrategy: "pip",
|
|
697
|
+
packageName: "jedi-language-server",
|
|
698
|
+
binaryName: "jedi-language-server"
|
|
699
|
+
},
|
|
700
|
+
{
|
|
701
|
+
id: "biome",
|
|
702
|
+
name: "Biome",
|
|
703
|
+
checkCommand: "biome",
|
|
704
|
+
checkArgs: ["--version"],
|
|
705
|
+
installStrategy: "npm",
|
|
706
|
+
packageName: "@biomejs/biome",
|
|
707
|
+
binaryName: "biome",
|
|
708
|
+
platformPackage: {
|
|
709
|
+
base: "@biomejs/cli",
|
|
710
|
+
suffixes: {
|
|
711
|
+
"linux-x64": "linux-x64",
|
|
712
|
+
"linux-arm64": "linux-arm64",
|
|
713
|
+
"darwin-x64": "darwin-x64",
|
|
714
|
+
"darwin-arm64": "darwin-arm64",
|
|
715
|
+
"win32-x64": "win32-x64",
|
|
716
|
+
"win32-arm64": "win32-arm64"
|
|
717
|
+
},
|
|
718
|
+
binaries: ["biome"]
|
|
719
|
+
}
|
|
720
|
+
},
|
|
721
|
+
// Analysis tools (run at session start / turn end)
|
|
722
|
+
{
|
|
723
|
+
id: "madge",
|
|
724
|
+
name: "Madge",
|
|
725
|
+
checkCommand: "madge",
|
|
726
|
+
checkArgs: ["--version"],
|
|
727
|
+
installStrategy: "npm",
|
|
728
|
+
packageName: "madge",
|
|
729
|
+
binaryName: "madge"
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
id: "jscpd",
|
|
733
|
+
name: "jscpd",
|
|
734
|
+
checkCommand: "jscpd",
|
|
735
|
+
checkArgs: ["--version"],
|
|
736
|
+
installStrategy: "npm",
|
|
737
|
+
packageName: "jscpd@5.4.0",
|
|
738
|
+
// v5.4.0 is the repository's exact devDependency; the v4 packaging defect that required the older v5.0.12 pin is gone, and parseReport() reads the unchanged clone-report fields.
|
|
739
|
+
binaryName: "jscpd"
|
|
740
|
+
},
|
|
741
|
+
// Structural search and dead code detection
|
|
742
|
+
{
|
|
743
|
+
id: "ast-grep",
|
|
744
|
+
name: "ast-grep CLI",
|
|
745
|
+
checkCommand: "ast-grep",
|
|
746
|
+
checkArgs: ["--version"],
|
|
747
|
+
installStrategy: "npm",
|
|
748
|
+
packageName: "@ast-grep/cli",
|
|
749
|
+
binaryName: "ast-grep",
|
|
750
|
+
platformPackage: {
|
|
751
|
+
suffixes: {
|
|
752
|
+
"linux-x64": "linux-x64-gnu",
|
|
753
|
+
"linux-arm64": "linux-arm64-gnu",
|
|
754
|
+
"darwin-x64": "darwin-x64",
|
|
755
|
+
"darwin-arm64": "darwin-arm64",
|
|
756
|
+
"win32-x64": "win32-x64-msvc",
|
|
757
|
+
"win32-arm64": "win32-arm64-msvc",
|
|
758
|
+
"win32-ia32": "win32-ia32-msvc"
|
|
759
|
+
},
|
|
760
|
+
binaries: ["ast-grep", "sg"]
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
id: "knip",
|
|
765
|
+
name: "Knip",
|
|
766
|
+
checkCommand: "knip",
|
|
767
|
+
checkArgs: ["--version"],
|
|
768
|
+
installStrategy: "npm",
|
|
769
|
+
packageName: "knip",
|
|
770
|
+
binaryName: "knip"
|
|
771
|
+
},
|
|
772
|
+
{
|
|
773
|
+
id: "yamllint",
|
|
774
|
+
name: "yamllint",
|
|
775
|
+
checkCommand: "yamllint",
|
|
776
|
+
checkArgs: ["--version"],
|
|
777
|
+
installStrategy: "pip",
|
|
778
|
+
packageName: "yamllint",
|
|
779
|
+
binaryName: "yamllint"
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
id: "sqlfluff",
|
|
783
|
+
name: "sqlfluff",
|
|
784
|
+
checkCommand: "sqlfluff",
|
|
785
|
+
checkArgs: ["--version"],
|
|
786
|
+
installStrategy: "pip",
|
|
787
|
+
packageName: "sqlfluff",
|
|
788
|
+
binaryName: "sqlfluff"
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
id: "bash-language-server",
|
|
792
|
+
name: "Bash Language Server",
|
|
793
|
+
checkCommand: "bash-language-server",
|
|
794
|
+
checkArgs: ["--version"],
|
|
795
|
+
// The #2188 sweep measured a 9,667ms cold `--version` start with closed
|
|
796
|
+
// stdin — close enough to the 10s installer default that modest host
|
|
797
|
+
// contention pushes it over and emits a false verification degradation.
|
|
798
|
+
// 20s gives the same kind of headroom Vue's 30s bound gives its own
|
|
799
|
+
// slower cold start (#2176), without inventing a shared literal (#2194).
|
|
800
|
+
verificationTimeoutMs: 2e4,
|
|
801
|
+
installStrategy: "npm",
|
|
802
|
+
packageName: "bash-language-server",
|
|
803
|
+
binaryName: "bash-language-server"
|
|
804
|
+
},
|
|
805
|
+
{
|
|
806
|
+
id: "fish-lsp",
|
|
807
|
+
name: "Fish Language Server",
|
|
808
|
+
checkCommand: "fish-lsp",
|
|
809
|
+
checkArgs: ["--version"],
|
|
810
|
+
installStrategy: "npm",
|
|
811
|
+
packageName: "fish-lsp",
|
|
812
|
+
binaryName: "fish-lsp"
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
id: "cmake-language-server",
|
|
816
|
+
name: "CMake Language Server",
|
|
817
|
+
checkCommand: "cmake-language-server",
|
|
818
|
+
checkArgs: ["--version"],
|
|
819
|
+
installStrategy: "pip",
|
|
820
|
+
packageName: "cmake-language-server",
|
|
821
|
+
binaryName: "cmake-language-server",
|
|
822
|
+
// Upstream 0.1.11 (the latest release) imports `LanguageServer` from
|
|
823
|
+
// `pygls.server`, a symbol pygls 2 removed, while declaring only
|
|
824
|
+
// `pygls>=1.1.1` — so an unconstrained install resolves pygls 2.1.1 and
|
|
825
|
+
// produces a launcher that cannot start. Measured against PyPI for the
|
|
826
|
+
// nightly's interpreter (#3311): `pip download --python-version 3.12
|
|
827
|
+
// cmake-language-server` → `pygls-2.1.1`; with this constraint →
|
|
828
|
+
// `pygls-1.3.1` + `lsprotocol-2023.0.1`, and `cmake-language-server
|
|
829
|
+
// --version` then prints `cmake-language-server 0.1.11` and exits 0.
|
|
830
|
+
pipConstraints: ["pygls<2"]
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
id: "yaml-language-server",
|
|
834
|
+
name: "YAML Language Server",
|
|
835
|
+
checkCommand: "yaml-language-server",
|
|
836
|
+
checkArgs: ["--version"],
|
|
837
|
+
installStrategy: "npm",
|
|
838
|
+
packageName: "yaml-language-server",
|
|
839
|
+
binaryName: "yaml-language-server"
|
|
840
|
+
},
|
|
841
|
+
{
|
|
842
|
+
id: "vscode-json-language-server",
|
|
843
|
+
name: "VSCode JSON Language Server",
|
|
844
|
+
checkCommand: "vscode-json-language-server",
|
|
845
|
+
checkArgs: ["--version"],
|
|
846
|
+
// The #2188 sweep measured an 11,047ms cold `--version` start with closed
|
|
847
|
+
// stdin — over the 10s installer default, so a cold or contended host can
|
|
848
|
+
// see a false verification degradation before the binary ever answers.
|
|
849
|
+
// 20s mirrors the bash-language-server bound above (#2194).
|
|
850
|
+
verificationTimeoutMs: 2e4,
|
|
851
|
+
installStrategy: "npm",
|
|
852
|
+
packageName: "vscode-langservers-extracted",
|
|
853
|
+
binaryName: "vscode-json-language-server"
|
|
854
|
+
},
|
|
855
|
+
{
|
|
856
|
+
id: "vscode-html-languageserver-bin",
|
|
857
|
+
name: "VSCode HTML Language Server",
|
|
858
|
+
checkCommand: "vscode-html-language-server",
|
|
859
|
+
checkArgs: ["--version"],
|
|
860
|
+
installStrategy: "npm",
|
|
861
|
+
packageName: "vscode-langservers-extracted",
|
|
862
|
+
binaryName: "vscode-html-language-server"
|
|
863
|
+
},
|
|
864
|
+
{
|
|
865
|
+
id: "htmlhint",
|
|
866
|
+
name: "HTMLHint",
|
|
867
|
+
checkCommand: "htmlhint",
|
|
868
|
+
checkArgs: ["--version"],
|
|
869
|
+
installStrategy: "npm",
|
|
870
|
+
packageName: "htmlhint",
|
|
871
|
+
binaryName: "htmlhint"
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
id: "hadolint",
|
|
875
|
+
name: "Hadolint",
|
|
876
|
+
checkCommand: "hadolint",
|
|
877
|
+
checkArgs: ["--version"],
|
|
878
|
+
installStrategy: "github",
|
|
879
|
+
binaryName: "hadolint",
|
|
880
|
+
github: {
|
|
881
|
+
repo: "hadolint/hadolint",
|
|
882
|
+
assetMatch: (platform, arch) => {
|
|
883
|
+
if (platform === "linux")
|
|
884
|
+
return arch === "arm64" ? "linux.aarch64" : "linux.x86_64";
|
|
885
|
+
if (platform === "darwin")
|
|
886
|
+
return arch === "arm64" ? "macos-arm64" : "macos-x86_64";
|
|
887
|
+
if (platform === "win32")
|
|
888
|
+
return "windows-x86_64.exe";
|
|
889
|
+
return void 0;
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
id: "helm",
|
|
895
|
+
name: "Helm",
|
|
896
|
+
checkCommand: "helm",
|
|
897
|
+
// intended: version --short — unverified against real binary (shape 16), do not wire until probed
|
|
898
|
+
checkArgs: ["--version"],
|
|
899
|
+
installStrategy: "github",
|
|
900
|
+
binaryName: "helm",
|
|
901
|
+
github: {
|
|
902
|
+
repo: "helm/helm",
|
|
903
|
+
assetMatch: (platform, arch) => {
|
|
904
|
+
const cpu = arch === "arm64" ? "arm64" : "amd64";
|
|
905
|
+
if (platform === "linux")
|
|
906
|
+
return `linux-${cpu}.tar.gz`;
|
|
907
|
+
if (platform === "darwin")
|
|
908
|
+
return `darwin-${cpu}.tar.gz`;
|
|
909
|
+
if (platform === "win32")
|
|
910
|
+
return `windows-${cpu}.zip`;
|
|
911
|
+
return void 0;
|
|
912
|
+
},
|
|
913
|
+
// Release archives nest the executable under an OS/arch directory;
|
|
914
|
+
// the installer searches recursively and adds the Windows suffix.
|
|
915
|
+
binaryInArchive: "helm"
|
|
916
|
+
}
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
// Opengrep: a single standalone binary per platform on GitHub releases —
|
|
920
|
+
// no login, no telemetry (the reason for switching off Semgrep, #111).
|
|
921
|
+
id: "opengrep",
|
|
922
|
+
name: "Opengrep",
|
|
923
|
+
checkCommand: "opengrep",
|
|
924
|
+
checkArgs: ["--version"],
|
|
925
|
+
installStrategy: "github",
|
|
926
|
+
binaryName: "opengrep",
|
|
927
|
+
github: {
|
|
928
|
+
repo: "opengrep/opengrep",
|
|
929
|
+
assetMatch: (platform, arch) => {
|
|
930
|
+
if (platform === "linux")
|
|
931
|
+
return arch === "arm64" ? "opengrep_manylinux_aarch64" : "opengrep_manylinux_x86";
|
|
932
|
+
if (platform === "darwin")
|
|
933
|
+
return arch === "arm64" ? "opengrep_osx_arm64" : "opengrep_osx_x86";
|
|
934
|
+
if (platform === "win32")
|
|
935
|
+
return "opengrep_windows_x86.exe";
|
|
936
|
+
return void 0;
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
},
|
|
940
|
+
{
|
|
941
|
+
id: "vscode-css-languageserver",
|
|
942
|
+
name: "VSCode CSS Language Server",
|
|
943
|
+
checkCommand: "vscode-css-language-server",
|
|
944
|
+
checkArgs: ["--version"],
|
|
945
|
+
installStrategy: "npm",
|
|
946
|
+
packageName: "vscode-langservers-extracted",
|
|
947
|
+
binaryName: "vscode-css-language-server"
|
|
948
|
+
},
|
|
949
|
+
{
|
|
950
|
+
id: "dockerfile-language-server-nodejs",
|
|
951
|
+
name: "Dockerfile Language Server",
|
|
952
|
+
checkCommand: "docker-langserver",
|
|
953
|
+
checkArgs: ["--version"],
|
|
954
|
+
installStrategy: "npm",
|
|
955
|
+
packageName: "dockerfile-language-server-nodejs",
|
|
956
|
+
binaryName: "docker-langserver"
|
|
957
|
+
},
|
|
958
|
+
{
|
|
959
|
+
// #2722: intelephense has no CLI — its entry calls createConnection()
|
|
960
|
+
// unconditionally — and Node prints the offending source line before the
|
|
961
|
+
// error. The bundle is one ~4 MB minified line, so the transport-required
|
|
962
|
+
// marker #208 rescues on lands at byte 4,154,741 of a 4,423,356-byte
|
|
963
|
+
// stderr while Node truncates piped stderr at 1 MiB on exit. Measured on
|
|
964
|
+
// intelephense@1.18.5, linux, Node v22.22.1:
|
|
965
|
+
// $ intelephense --version 2>&1 | wc -c -> 1048576
|
|
966
|
+
// $ intelephense --version 2>&1 | grep -c "Connection input stream is not set" -> 0
|
|
967
|
+
// $ intelephense --version 2>err.txt; wc -c < err.txt -> 4423356
|
|
968
|
+
// Same run over the alternatives, each with stdin closed the way
|
|
969
|
+
// verifyToolBinary closes it (`input: ""`):
|
|
970
|
+
// --help, -v, --socket=0 -> identical dump (exit 1, 4423356 bytes,
|
|
971
|
+
// marker at 4154741, 1048576 through a pipe, marker absent)
|
|
972
|
+
// --stdio -> exit 1 with ZERO bytes on either stream,
|
|
973
|
+
// so it carries no marker to rescue on either.
|
|
974
|
+
// No checkArgs value produces a verdict. Verified spawn-free instead.
|
|
975
|
+
id: "intelephense",
|
|
976
|
+
name: "Intelephense",
|
|
977
|
+
checkCommand: "intelephense",
|
|
978
|
+
checkArgs: ["--version"],
|
|
979
|
+
verification: "package-entry",
|
|
980
|
+
installStrategy: "npm",
|
|
981
|
+
packageName: "intelephense",
|
|
982
|
+
binaryName: "intelephense"
|
|
983
|
+
},
|
|
984
|
+
{
|
|
985
|
+
id: "@prisma/language-server",
|
|
986
|
+
name: "Prisma Language Server",
|
|
987
|
+
checkCommand: "prisma-language-server",
|
|
988
|
+
checkArgs: ["--version"],
|
|
989
|
+
// #2169: a real closed-stdin cold run measured 27,265ms, and a warm-cache
|
|
990
|
+
// rerun still took 9,860ms — well past the 10s installer default. 40s
|
|
991
|
+
// keeps the same margin-over-worst-observed ratio Vue's 30s bound uses
|
|
992
|
+
// (#2176) rather than trimming it for a slower binary.
|
|
993
|
+
verificationTimeoutMs: 4e4,
|
|
994
|
+
installStrategy: "npm",
|
|
995
|
+
packageName: "@prisma/language-server",
|
|
996
|
+
binaryName: "prisma-language-server"
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
id: "@vue/language-server",
|
|
1000
|
+
name: "Vue Language Server",
|
|
1001
|
+
checkCommand: "vue-language-server",
|
|
1002
|
+
checkArgs: ["--version"],
|
|
1003
|
+
// Vue's launcher loads the full language-service bundle before answering
|
|
1004
|
+
// --version. Its cold start exceeds the dispatch probe budget on some hosts
|
|
1005
|
+
// even though warm starts complete quickly (#2176).
|
|
1006
|
+
verificationTimeoutMs: 3e4,
|
|
1007
|
+
installStrategy: "npm",
|
|
1008
|
+
packageName: "@vue/language-server",
|
|
1009
|
+
binaryName: "vue-language-server"
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
id: "svelte-language-server",
|
|
1013
|
+
name: "Svelte Language Server",
|
|
1014
|
+
checkCommand: "svelteserver",
|
|
1015
|
+
checkArgs: ["--version"],
|
|
1016
|
+
// #2169: a real closed-stdin cold run measured 12,410ms — over the 10s
|
|
1017
|
+
// installer default, matching the bash/JSON class of false verification
|
|
1018
|
+
// degradation from a cold-cache host (#2194). 20s mirrors that bound.
|
|
1019
|
+
verificationTimeoutMs: 2e4,
|
|
1020
|
+
installStrategy: "npm",
|
|
1021
|
+
packageName: "svelte-language-server",
|
|
1022
|
+
binaryName: "svelteserver"
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
id: "markdownlint",
|
|
1026
|
+
name: "markdownlint-cli2",
|
|
1027
|
+
checkCommand: "markdownlint-cli2",
|
|
1028
|
+
// `--version` is interpreted as a file glob by markdownlint-cli2. Use
|
|
1029
|
+
// stdin with globs disabled so verification cannot scan the workspace.
|
|
1030
|
+
checkArgs: ["--no-globs", "-"],
|
|
1031
|
+
installStrategy: "npm",
|
|
1032
|
+
packageName: "markdownlint-cli2",
|
|
1033
|
+
binaryName: "markdownlint-cli2"
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
id: "mypy",
|
|
1037
|
+
name: "mypy",
|
|
1038
|
+
checkCommand: "mypy",
|
|
1039
|
+
checkArgs: ["--version"],
|
|
1040
|
+
installStrategy: "pip",
|
|
1041
|
+
packageName: "mypy",
|
|
1042
|
+
binaryName: "mypy"
|
|
1043
|
+
},
|
|
1044
|
+
{
|
|
1045
|
+
id: "rubocop",
|
|
1046
|
+
name: "RuboCop",
|
|
1047
|
+
checkCommand: "rubocop",
|
|
1048
|
+
checkArgs: ["--version"],
|
|
1049
|
+
installStrategy: "gem",
|
|
1050
|
+
packageName: "rubocop",
|
|
1051
|
+
binaryName: "rubocop"
|
|
1052
|
+
},
|
|
1053
|
+
{
|
|
1054
|
+
id: "stylelint",
|
|
1055
|
+
name: "Stylelint",
|
|
1056
|
+
checkCommand: "stylelint",
|
|
1057
|
+
checkArgs: ["--version"],
|
|
1058
|
+
installStrategy: "npm",
|
|
1059
|
+
packageName: "stylelint",
|
|
1060
|
+
binaryName: "stylelint"
|
|
1061
|
+
},
|
|
1062
|
+
{
|
|
1063
|
+
id: "oxlint",
|
|
1064
|
+
name: "Oxlint",
|
|
1065
|
+
checkCommand: "oxlint",
|
|
1066
|
+
checkArgs: ["--version"],
|
|
1067
|
+
installStrategy: "npm",
|
|
1068
|
+
packageName: "oxlint",
|
|
1069
|
+
binaryName: "oxlint"
|
|
1070
|
+
},
|
|
1071
|
+
// GitHub release binaries
|
|
1072
|
+
{
|
|
1073
|
+
id: "shellcheck",
|
|
1074
|
+
name: "ShellCheck",
|
|
1075
|
+
checkCommand: "shellcheck",
|
|
1076
|
+
checkArgs: ["--version"],
|
|
1077
|
+
installStrategy: "github",
|
|
1078
|
+
binaryName: "shellcheck",
|
|
1079
|
+
github: {
|
|
1080
|
+
repo: "koalaman/shellcheck",
|
|
1081
|
+
assetMatch: (platform, arch) => {
|
|
1082
|
+
if (platform === "linux")
|
|
1083
|
+
return arch === "arm64" ? "linux.aarch64.tar.xz" : "linux.x86_64.tar.xz";
|
|
1084
|
+
if (platform === "darwin")
|
|
1085
|
+
return arch === "arm64" ? "darwin.aarch64.tar.xz" : "darwin.x86_64.tar.xz";
|
|
1086
|
+
if (platform === "win32")
|
|
1087
|
+
return "zip";
|
|
1088
|
+
return void 0;
|
|
1089
|
+
},
|
|
1090
|
+
binaryInArchive: "shellcheck"
|
|
1091
|
+
}
|
|
1092
|
+
},
|
|
1093
|
+
{
|
|
1094
|
+
id: "shfmt",
|
|
1095
|
+
name: "shfmt",
|
|
1096
|
+
checkCommand: "shfmt",
|
|
1097
|
+
checkArgs: ["--version"],
|
|
1098
|
+
installStrategy: "github",
|
|
1099
|
+
binaryName: "shfmt",
|
|
1100
|
+
github: {
|
|
1101
|
+
repo: "mvdan/sh",
|
|
1102
|
+
assetMatch: (platform, arch) => {
|
|
1103
|
+
if (platform === "linux")
|
|
1104
|
+
return arch === "arm64" ? "linux_arm64" : "linux_amd64";
|
|
1105
|
+
if (platform === "darwin")
|
|
1106
|
+
return arch === "arm64" ? "darwin_arm64" : "darwin_amd64";
|
|
1107
|
+
if (platform === "win32")
|
|
1108
|
+
return arch === "arm64" ? "windows_arm64.exe" : "windows_amd64.exe";
|
|
1109
|
+
return void 0;
|
|
1110
|
+
}
|
|
1111
|
+
// bare binary, no archive
|
|
1112
|
+
}
|
|
1113
|
+
},
|
|
1114
|
+
...MANAGED_GITHUB_FORMATTERS.map(managedGitHubFormatterTool),
|
|
1115
|
+
...MANAGED_MAVEN_FORMATTERS.map(managedMavenFormatterTool),
|
|
1116
|
+
{
|
|
1117
|
+
id: "rust-analyzer",
|
|
1118
|
+
name: "rust-analyzer",
|
|
1119
|
+
checkCommand: "rust-analyzer",
|
|
1120
|
+
checkArgs: ["--version"],
|
|
1121
|
+
installStrategy: "github",
|
|
1122
|
+
binaryName: "rust-analyzer",
|
|
1123
|
+
github: {
|
|
1124
|
+
repo: "rust-lang/rust-analyzer",
|
|
1125
|
+
assetMatch: (platform, arch) => {
|
|
1126
|
+
if (platform === "linux")
|
|
1127
|
+
return arch === "arm64" ? "aarch64-unknown-linux-gnu.gz" : "x86_64-unknown-linux-gnu.gz";
|
|
1128
|
+
if (platform === "darwin")
|
|
1129
|
+
return arch === "arm64" ? "aarch64-apple-darwin.gz" : "x86_64-apple-darwin.gz";
|
|
1130
|
+
if (platform === "win32")
|
|
1131
|
+
return "x86_64-pc-windows-msvc.zip";
|
|
1132
|
+
return void 0;
|
|
1133
|
+
}
|
|
1134
|
+
// Linux/macOS: bare .gz; Windows: .zip archive containing rust-analyzer.exe
|
|
1135
|
+
}
|
|
1136
|
+
},
|
|
1137
|
+
{
|
|
1138
|
+
// Alternate JS/TS LSP (fallback when the `typescript` server is unavailable
|
|
1139
|
+
// or disabled — e.g. Deno projects). Used as a managedToolId by DenoServer.
|
|
1140
|
+
// Every platform ships a .zip containing the `deno` binary (the github
|
|
1141
|
+
// strategy extracts it, as it does for rust-analyzer's Windows .zip).
|
|
1142
|
+
id: "deno",
|
|
1143
|
+
name: "Deno",
|
|
1144
|
+
checkCommand: "deno",
|
|
1145
|
+
checkArgs: ["--version"],
|
|
1146
|
+
installStrategy: "github",
|
|
1147
|
+
binaryName: "deno",
|
|
1148
|
+
github: {
|
|
1149
|
+
repo: "denoland/deno",
|
|
1150
|
+
assetMatch: (platform, arch) => {
|
|
1151
|
+
if (platform === "linux")
|
|
1152
|
+
return arch === "arm64" ? "deno-aarch64-unknown-linux-gnu.zip" : "deno-x86_64-unknown-linux-gnu.zip";
|
|
1153
|
+
if (platform === "darwin")
|
|
1154
|
+
return arch === "arm64" ? "deno-aarch64-apple-darwin.zip" : "deno-x86_64-apple-darwin.zip";
|
|
1155
|
+
if (platform === "win32")
|
|
1156
|
+
return "deno-x86_64-pc-windows-msvc.zip";
|
|
1157
|
+
return void 0;
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
},
|
|
1161
|
+
{
|
|
1162
|
+
id: "golangci-lint",
|
|
1163
|
+
name: "golangci-lint",
|
|
1164
|
+
checkCommand: "golangci-lint",
|
|
1165
|
+
checkArgs: ["--version"],
|
|
1166
|
+
installStrategy: "github",
|
|
1167
|
+
binaryName: "golangci-lint",
|
|
1168
|
+
github: {
|
|
1169
|
+
repo: "golangci/golangci-lint",
|
|
1170
|
+
assetMatch: (platform, arch) => {
|
|
1171
|
+
if (platform === "linux")
|
|
1172
|
+
return arch === "arm64" ? "linux-arm64.tar.gz" : "linux-amd64.tar.gz";
|
|
1173
|
+
if (platform === "darwin")
|
|
1174
|
+
return arch === "arm64" ? "darwin-arm64.tar.gz" : "darwin-amd64.tar.gz";
|
|
1175
|
+
if (platform === "win32")
|
|
1176
|
+
return arch === "arm64" ? "windows-arm64.zip" : "windows-amd64.zip";
|
|
1177
|
+
return void 0;
|
|
1178
|
+
},
|
|
1179
|
+
binaryInArchive: "golangci-lint"
|
|
1180
|
+
}
|
|
1181
|
+
},
|
|
1182
|
+
{
|
|
1183
|
+
id: "ktlint",
|
|
1184
|
+
name: "ktlint",
|
|
1185
|
+
checkCommand: "ktlint",
|
|
1186
|
+
checkArgs: ["--version"],
|
|
1187
|
+
installStrategy: "github",
|
|
1188
|
+
binaryName: "ktlint",
|
|
1189
|
+
github: {
|
|
1190
|
+
// ktlint ships a self-executable `ktlint` (a JAR with a shell preamble)
|
|
1191
|
+
// for Linux/macOS, plus a `ktlint.bat` wrapper for Windows that runs
|
|
1192
|
+
// `java -jar %~dp0ktlint`. On Windows BOTH files are needed: the .bat AND
|
|
1193
|
+
// the `ktlint` jar it wraps (#218). No arm64-specific asset.
|
|
1194
|
+
repo: "pinterest/ktlint",
|
|
1195
|
+
assetMatch: (platform, _arch) => {
|
|
1196
|
+
if (platform === "linux")
|
|
1197
|
+
return "ktlint";
|
|
1198
|
+
if (platform === "darwin")
|
|
1199
|
+
return "ktlint";
|
|
1200
|
+
if (platform === "win32")
|
|
1201
|
+
return "ktlint.bat";
|
|
1202
|
+
return void 0;
|
|
1203
|
+
},
|
|
1204
|
+
extraAssets: (platform) => platform === "win32" ? ["ktlint"] : []
|
|
1205
|
+
}
|
|
1206
|
+
},
|
|
1207
|
+
{
|
|
1208
|
+
// ktfmt (Meta's opinionated Kotlin formatter) ships only as a Maven-Central
|
|
1209
|
+
// fat JAR — no native binary, no npm package — so it uses the maven strategy
|
|
1210
|
+
// (#129). Run via a `java -jar` launcher; requires a JRE.
|
|
1211
|
+
id: "ktfmt",
|
|
1212
|
+
name: "ktfmt",
|
|
1213
|
+
checkCommand: "ktfmt",
|
|
1214
|
+
checkArgs: ["--version"],
|
|
1215
|
+
installStrategy: "maven",
|
|
1216
|
+
binaryName: "ktfmt",
|
|
1217
|
+
maven: {
|
|
1218
|
+
groupId: "com.facebook",
|
|
1219
|
+
artifactId: "ktfmt",
|
|
1220
|
+
version: "0.63",
|
|
1221
|
+
classifier: "with-dependencies"
|
|
1222
|
+
}
|
|
1223
|
+
},
|
|
1224
|
+
{
|
|
1225
|
+
// SpotBugs (bytecode bug-pattern analyzer for Java/Kotlin/Scala/Groovy)
|
|
1226
|
+
// ships as a distribution archive — a lib/ of many JARs + bin/ launchers,
|
|
1227
|
+
// NOT a runnable fat JAR — so it uses the archive strategy, not maven
|
|
1228
|
+
// (refs #133). Requires a JRE (gated by the runner, not the install).
|
|
1229
|
+
id: "spotbugs",
|
|
1230
|
+
name: "SpotBugs",
|
|
1231
|
+
checkCommand: "spotbugs",
|
|
1232
|
+
// intended: -version — unverified against real binary (shape 16), do not wire until probed
|
|
1233
|
+
checkArgs: ["--version"],
|
|
1234
|
+
installStrategy: "archive",
|
|
1235
|
+
binaryName: "spotbugs",
|
|
1236
|
+
archive: {
|
|
1237
|
+
url: "https://github.com/spotbugs/spotbugs/releases/download/4.10.2/spotbugs-4.10.2.tgz",
|
|
1238
|
+
kind: "tgz",
|
|
1239
|
+
launcher: "bin/spotbugs",
|
|
1240
|
+
sha256: {
|
|
1241
|
+
"https://github.com/spotbugs/spotbugs/releases/download/4.10.2/spotbugs-4.10.2.tgz": "63d7687c35fba12cbc8e55ec2a889a2bbf1b9be299dea91f2b0d351dc285308a"
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
},
|
|
1245
|
+
{
|
|
1246
|
+
// kotlin-language-server (fwcd, #3400) — a platform-agnostic launcher ZIP
|
|
1247
|
+
// (`server/{bin,lib}`, 87 MB) whose `bin/kotlin-language-server` script
|
|
1248
|
+
// starts a JVM on ANY argument and has no stable `--version`, so it is
|
|
1249
|
+
// verified at install by manifest, not by spawn: the pinned sha256, the
|
|
1250
|
+
// launcher on disk with an exec bit, and `java` on PATH or JAVA_HOME before
|
|
1251
|
+
// the download (`verification: "tree-manifest"`). The 2025 release publishes
|
|
1252
|
+
// no digest, so its pin is trust-on-first-use (the hash of the download). The initialize handshake is the nightly
|
|
1253
|
+
// tool-smoke `kotlin` row. fwcd 1.3.13 (2025-01-18) rather than JetBrains'
|
|
1254
|
+
// kotlin-lsp (v263.4702.0): its CDN ships the macOS standalone archives as
|
|
1255
|
+
// `.sit`, which this installer's tar/unzip/Expand-Archive paths cannot
|
|
1256
|
+
// extract, and its six per-platform archives would need six pins.
|
|
1257
|
+
id: "kotlin-language-server",
|
|
1258
|
+
name: "Kotlin Language Server",
|
|
1259
|
+
checkCommand: "kotlin-language-server",
|
|
1260
|
+
checkArgs: ["--version"],
|
|
1261
|
+
installStrategy: "archive",
|
|
1262
|
+
binaryName: "kotlin-language-server",
|
|
1263
|
+
verification: "tree-manifest",
|
|
1264
|
+
archive: {
|
|
1265
|
+
url: "https://github.com/fwcd/kotlin-language-server/releases/download/1.3.13/server.zip",
|
|
1266
|
+
kind: "zip",
|
|
1267
|
+
launcher: "bin/kotlin-language-server",
|
|
1268
|
+
runtime: "java",
|
|
1269
|
+
sha256: {
|
|
1270
|
+
"https://github.com/fwcd/kotlin-language-server/releases/download/1.3.13/server.zip": "4fe7d71d087b307c7869036171bd9d8c6a4284cd7c25b89098b0a24eb2d9b6d2"
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
// PowerShell Editor Services (#278). NOT a single binary — a multi-folder
|
|
1276
|
+
// PowerShell MODULE BUNDLE launched via `pwsh Start-EditorServices.ps1
|
|
1277
|
+
// -Stdio` (see PowerShellServer.spawn). archive TREE BUNDLE: the release zip
|
|
1278
|
+
// extracts sibling module dirs (PowerShellEditorServices/, PSReadLine/,
|
|
1279
|
+
// PSScriptAnalyzer/) at the root with no wrapping dir, so stripComponents:0
|
|
1280
|
+
// + no launcher — the whole tree is kept and resolved to its extract dir.
|
|
1281
|
+
// checkCommand "pwsh" documents the runtime but is unused for resolution
|
|
1282
|
+
// (tree bundles resolve only via the extract dir + treeMarker).
|
|
1283
|
+
id: "powershell-editor-services",
|
|
1284
|
+
name: "PowerShell Editor Services",
|
|
1285
|
+
checkCommand: "pwsh",
|
|
1286
|
+
checkArgs: [
|
|
1287
|
+
"-NoProfile",
|
|
1288
|
+
"-Command",
|
|
1289
|
+
"$PSVersionTable.PSVersion.ToString()"
|
|
1290
|
+
],
|
|
1291
|
+
installStrategy: "archive",
|
|
1292
|
+
binaryName: "powershell-editor-services",
|
|
1293
|
+
archive: {
|
|
1294
|
+
url: "https://github.com/PowerShell/PowerShellEditorServices/releases/download/v4.6.0/PowerShellEditorServices.zip",
|
|
1295
|
+
kind: "zip",
|
|
1296
|
+
stripComponents: 0,
|
|
1297
|
+
treeMarker: "PowerShellEditorServices/Start-EditorServices.ps1",
|
|
1298
|
+
sha256: {
|
|
1299
|
+
"https://github.com/PowerShell/PowerShellEditorServices/releases/download/v4.6.0/PowerShellEditorServices.zip": "0d91898f73d4faeb64291336f6386f0c890a933df012827571adf7008480a04a"
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
},
|
|
1303
|
+
{
|
|
1304
|
+
// clangd (C/C++/Obj-C LSP, #241) — a self-contained native TREE BUNDLE: the
|
|
1305
|
+
// release zip wraps `clangd_<ver>/{bin,lib}` (bin/clangd[.exe] + the bundled
|
|
1306
|
+
// libclang headers under lib/), so stripComponents:1 drops the version dir and
|
|
1307
|
+
// the whole tree is kept (no launcher). Unlike PSES there is no external
|
|
1308
|
+
// runtime — CppServer launches `<bundle>/bin/clangd` directly. checkCommand
|
|
1309
|
+
// documents the binary but is unused for resolution (tree bundles resolve only
|
|
1310
|
+
// via the extract dir + treeMarker). Platform-matched url: clangd ships x64
|
|
1311
|
+
// prebuilts; arm runs the x64 build under Rosetta/emulation (darwin/win32),
|
|
1312
|
+
// while linux/arm64 has no official build → undefined (graceful unavailable).
|
|
1313
|
+
id: "clangd",
|
|
1314
|
+
name: "clangd",
|
|
1315
|
+
checkCommand: "clangd",
|
|
1316
|
+
checkArgs: ["--version"],
|
|
1317
|
+
installStrategy: "archive",
|
|
1318
|
+
binaryName: "clangd",
|
|
1319
|
+
archive: {
|
|
1320
|
+
url: (platform, arch) => {
|
|
1321
|
+
const version = "22.1.0";
|
|
1322
|
+
const base = `https://github.com/clangd/clangd/releases/download/${version}`;
|
|
1323
|
+
if (platform === "linux")
|
|
1324
|
+
return arch === "x64" ? `${base}/clangd-linux-${version}.zip` : void 0;
|
|
1325
|
+
if (platform === "darwin")
|
|
1326
|
+
return `${base}/clangd-mac-${version}.zip`;
|
|
1327
|
+
if (platform === "win32")
|
|
1328
|
+
return `${base}/clangd-windows-${version}.zip`;
|
|
1329
|
+
return void 0;
|
|
1330
|
+
},
|
|
1331
|
+
kind: "zip",
|
|
1332
|
+
stripComponents: 1,
|
|
1333
|
+
treeMarker: "bin",
|
|
1334
|
+
sha256: {
|
|
1335
|
+
"https://github.com/clangd/clangd/releases/download/22.1.0/clangd-linux-22.1.0.zip": "c54e57dbff3ccc9e8352367ddb7030ad3f624073ec58c7477424e7919f578572",
|
|
1336
|
+
"https://github.com/clangd/clangd/releases/download/22.1.0/clangd-mac-22.1.0.zip": "71eddc5303da9a5bc5e8b509488b5b2c5acf45f20e33b8394e71a12a56d67198",
|
|
1337
|
+
"https://github.com/clangd/clangd/releases/download/22.1.0/clangd-windows-22.1.0.zip": "e31e271fe11f6dcd7cf87ca74be4a12788ff8ce5a0b07762583e335c058e939a"
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
},
|
|
1341
|
+
{
|
|
1342
|
+
// lua-language-server (#564, split from #241) — same self-contained native
|
|
1343
|
+
// TREE BUNDLE shape as clangd: bin/lua-language-server[.exe] + bundled
|
|
1344
|
+
// locale/meta files, no external runtime. UNLIKE clangd, the release
|
|
1345
|
+
// archive has NO wrapping version dir (verified by inspecting the actual
|
|
1346
|
+
// 3.18.2 linux-x64 .tar.gz and win32-x64 .zip contents: `bin/`, `LICENSE`,
|
|
1347
|
+
// `locale/`, … sit at archive root) — so stripComponents:0, not 1.
|
|
1348
|
+
// LuaServer launches `<bundle>/bin/lua-language-server` directly.
|
|
1349
|
+
// checkCommand documents the binary but is unused for resolution (tree
|
|
1350
|
+
// bundles resolve only via the extract dir + treeMarker). Platform-matched
|
|
1351
|
+
// url: LuaLS publishes darwin/linux x64+arm64 and win32 x64 (no win32/arm64
|
|
1352
|
+
// build as of 3.18.2 → undefined, graceful unavailable); asset naming
|
|
1353
|
+
// verified against the live GitHub release listing, not guessed.
|
|
1354
|
+
id: "lua-language-server",
|
|
1355
|
+
name: "lua-language-server",
|
|
1356
|
+
checkCommand: "lua-language-server",
|
|
1357
|
+
checkArgs: ["--version"],
|
|
1358
|
+
installStrategy: "archive",
|
|
1359
|
+
binaryName: "lua-language-server",
|
|
1360
|
+
archive: {
|
|
1361
|
+
url: (platform, arch) => {
|
|
1362
|
+
const version = "3.18.2";
|
|
1363
|
+
const base = `https://github.com/LuaLS/lua-language-server/releases/download/${version}`;
|
|
1364
|
+
if (platform === "linux")
|
|
1365
|
+
return arch === "arm64" ? `${base}/lua-language-server-${version}-linux-arm64.tar.gz` : `${base}/lua-language-server-${version}-linux-x64.tar.gz`;
|
|
1366
|
+
if (platform === "darwin")
|
|
1367
|
+
return arch === "arm64" ? `${base}/lua-language-server-${version}-darwin-arm64.tar.gz` : `${base}/lua-language-server-${version}-darwin-x64.tar.gz`;
|
|
1368
|
+
if (platform === "win32")
|
|
1369
|
+
return arch === "arm64" ? void 0 : `${base}/lua-language-server-${version}-win32-x64.zip`;
|
|
1370
|
+
return void 0;
|
|
1371
|
+
},
|
|
1372
|
+
kind: (platform) => platform === "win32" ? "zip" : "tgz",
|
|
1373
|
+
stripComponents: 0,
|
|
1374
|
+
treeMarker: "bin",
|
|
1375
|
+
sha256: {
|
|
1376
|
+
"https://github.com/LuaLS/lua-language-server/releases/download/3.18.2/lua-language-server-3.18.2-linux-arm64.tar.gz": "273af33f26f4a1143f27c96d9f9e1188aba619c71e0807042134f66b4bd27f24",
|
|
1377
|
+
"https://github.com/LuaLS/lua-language-server/releases/download/3.18.2/lua-language-server-3.18.2-linux-x64.tar.gz": "ca71415dd19f19e30aaa35a4915aefca9fdb5fec31b98331cc3d77f778d539c5",
|
|
1378
|
+
"https://github.com/LuaLS/lua-language-server/releases/download/3.18.2/lua-language-server-3.18.2-darwin-arm64.tar.gz": "cec99d70b1f612acec4a10a79a03664e3aa0c229d4d8a586cb3f928ec37d509e",
|
|
1379
|
+
"https://github.com/LuaLS/lua-language-server/releases/download/3.18.2/lua-language-server-3.18.2-darwin-x64.tar.gz": "e26cfefe423dd7326fc7c649539e4d4aaa4f35f34d2fefd8af2ed7090b72c556",
|
|
1380
|
+
"https://github.com/LuaLS/lua-language-server/releases/download/3.18.2/lua-language-server-3.18.2-win32-x64.zip": "a4439a8f5e8e9e6505c11f045a7bf45db602124a1e246371c1dbe34924f3cf71"
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
id: "actionlint",
|
|
1386
|
+
name: "actionlint",
|
|
1387
|
+
checkCommand: "actionlint",
|
|
1388
|
+
checkArgs: ["--version"],
|
|
1389
|
+
installStrategy: "github",
|
|
1390
|
+
binaryName: "actionlint",
|
|
1391
|
+
github: {
|
|
1392
|
+
repo: "rhysd/actionlint",
|
|
1393
|
+
assetMatch: (platform, arch) => {
|
|
1394
|
+
if (platform === "linux")
|
|
1395
|
+
return arch === "arm64" ? "linux_arm64.tar.gz" : "linux_amd64.tar.gz";
|
|
1396
|
+
if (platform === "darwin")
|
|
1397
|
+
return arch === "arm64" ? "darwin_arm64.tar.gz" : "darwin_amd64.tar.gz";
|
|
1398
|
+
if (platform === "win32")
|
|
1399
|
+
return arch === "arm64" ? "windows_arm64.zip" : "windows_amd64.zip";
|
|
1400
|
+
return void 0;
|
|
1401
|
+
},
|
|
1402
|
+
binaryInArchive: "actionlint"
|
|
1403
|
+
}
|
|
1404
|
+
},
|
|
1405
|
+
{
|
|
1406
|
+
// zizmor: GitHub Actions workflow security scanner that speaks LSP (#272).
|
|
1407
|
+
// cargo-dist release archives, one per target triple, each holding a single
|
|
1408
|
+
// `zizmor` binary (extracted via the recursive binary find). Online audits
|
|
1409
|
+
// (known-vulnerable-actions, unpinned-uses, …) need a GitHub token — the LSP
|
|
1410
|
+
// spawn forwards one via resolveZizmorGitHubToken (clients/zizmor-config.ts).
|
|
1411
|
+
id: "zizmor",
|
|
1412
|
+
name: "zizmor",
|
|
1413
|
+
checkCommand: "zizmor",
|
|
1414
|
+
checkArgs: ["--version"],
|
|
1415
|
+
installStrategy: "github",
|
|
1416
|
+
binaryName: "zizmor",
|
|
1417
|
+
github: {
|
|
1418
|
+
repo: "zizmorcore/zizmor",
|
|
1419
|
+
assetMatch: (platform, arch) => {
|
|
1420
|
+
if (platform === "linux")
|
|
1421
|
+
return arch === "arm64" ? "aarch64-unknown-linux-gnu.tar.gz" : "x86_64-unknown-linux-gnu.tar.gz";
|
|
1422
|
+
if (platform === "darwin")
|
|
1423
|
+
return arch === "arm64" ? "aarch64-apple-darwin.tar.gz" : "x86_64-apple-darwin.tar.gz";
|
|
1424
|
+
if (platform === "win32")
|
|
1425
|
+
return "x86_64-pc-windows-msvc.zip";
|
|
1426
|
+
return void 0;
|
|
1427
|
+
},
|
|
1428
|
+
binaryInArchive: "zizmor"
|
|
1429
|
+
}
|
|
1430
|
+
},
|
|
1431
|
+
{
|
|
1432
|
+
// typos-lsp: source-code spell checker that speaks LSP (#283). cargo-dist
|
|
1433
|
+
// release archives, one per target triple, each holding a single `typos-lsp`
|
|
1434
|
+
// binary (extracted via the recursive binary find). NO token / network — the
|
|
1435
|
+
// dictionary is compiled in. The binary takes no `--version` (it ignores args
|
|
1436
|
+
// and serves the LSP on stdin/stdout); the PATH probe ignores checkArgs and
|
|
1437
|
+
// verifyToolBinary runs with stdin:ignore so the server gets EOF and exits.
|
|
1438
|
+
id: "typos-lsp",
|
|
1439
|
+
name: "typos-lsp",
|
|
1440
|
+
checkCommand: "typos-lsp",
|
|
1441
|
+
checkArgs: ["--version"],
|
|
1442
|
+
installStrategy: "github",
|
|
1443
|
+
binaryName: "typos-lsp",
|
|
1444
|
+
github: {
|
|
1445
|
+
repo: "tekumara/typos-lsp",
|
|
1446
|
+
assetMatch: (platform, arch) => {
|
|
1447
|
+
if (platform === "linux")
|
|
1448
|
+
return arch === "arm64" ? "aarch64-unknown-linux-gnu.tar.gz" : "x86_64-unknown-linux-gnu.tar.gz";
|
|
1449
|
+
if (platform === "darwin")
|
|
1450
|
+
return arch === "arm64" ? "aarch64-apple-darwin.tar.gz" : "x86_64-apple-darwin.tar.gz";
|
|
1451
|
+
if (platform === "win32")
|
|
1452
|
+
return arch === "arm64" ? "aarch64-pc-windows-msvc.zip" : "x86_64-pc-windows-msvc.zip";
|
|
1453
|
+
return void 0;
|
|
1454
|
+
},
|
|
1455
|
+
binaryInArchive: "typos-lsp"
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
id: "tflint",
|
|
1460
|
+
name: "tflint",
|
|
1461
|
+
checkCommand: "tflint",
|
|
1462
|
+
checkArgs: ["--version"],
|
|
1463
|
+
installStrategy: "github",
|
|
1464
|
+
binaryName: "tflint",
|
|
1465
|
+
github: {
|
|
1466
|
+
repo: "terraform-linters/tflint",
|
|
1467
|
+
assetMatch: archAssetMatch(OS_ARCH_ZIP_ASSETS),
|
|
1468
|
+
binaryInArchive: "tflint"
|
|
1469
|
+
}
|
|
1470
|
+
},
|
|
1471
|
+
{
|
|
1472
|
+
// Terragrunt ships a bare native binary per platform on GitHub releases.
|
|
1473
|
+
// Windows arm64 uses the x64 binary through Windows' built-in emulation —
|
|
1474
|
+
// there is no terragrunt_windows_arm64.exe upstream.
|
|
1475
|
+
id: "terragrunt",
|
|
1476
|
+
name: "terragrunt",
|
|
1477
|
+
checkCommand: "terragrunt",
|
|
1478
|
+
checkArgs: ["--version"],
|
|
1479
|
+
installStrategy: "github",
|
|
1480
|
+
binaryName: "terragrunt",
|
|
1481
|
+
github: {
|
|
1482
|
+
repo: "gruntwork-io/terragrunt",
|
|
1483
|
+
assetMatch: archAssetMatch({
|
|
1484
|
+
linux: {
|
|
1485
|
+
x64: "terragrunt_linux_amd64",
|
|
1486
|
+
arm64: "terragrunt_linux_arm64"
|
|
1487
|
+
},
|
|
1488
|
+
darwin: {
|
|
1489
|
+
x64: "terragrunt_darwin_amd64",
|
|
1490
|
+
arm64: "terragrunt_darwin_arm64"
|
|
1491
|
+
},
|
|
1492
|
+
win32: {
|
|
1493
|
+
x64: "terragrunt_windows_amd64.exe",
|
|
1494
|
+
arm64: "terragrunt_windows_amd64.exe"
|
|
1495
|
+
}
|
|
1496
|
+
})
|
|
1497
|
+
// bare binary — no binaryInArchive
|
|
1498
|
+
}
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
id: "gitleaks",
|
|
1502
|
+
name: "gitleaks",
|
|
1503
|
+
checkCommand: "gitleaks",
|
|
1504
|
+
// intended: version — unverified against real binary (shape 16), do not wire until probed
|
|
1505
|
+
checkArgs: ["--version"],
|
|
1506
|
+
installStrategy: "github",
|
|
1507
|
+
binaryName: "gitleaks",
|
|
1508
|
+
github: {
|
|
1509
|
+
repo: "gitleaks/gitleaks",
|
|
1510
|
+
// gitleaks asset naming uses `x64` not `amd64` (unlike most Go-built
|
|
1511
|
+
// tools). Substring match is exact-enough — release assets are
|
|
1512
|
+
// named e.g. `gitleaks_8.18.4_linux_x64.tar.gz`.
|
|
1513
|
+
assetMatch: archAssetMatch({
|
|
1514
|
+
linux: { x64: "linux_x64.tar.gz", arm64: "linux_arm64.tar.gz" },
|
|
1515
|
+
darwin: { x64: "darwin_x64.tar.gz", arm64: "darwin_arm64.tar.gz" },
|
|
1516
|
+
win32: { x64: "windows_x64.zip", arm64: "windows_arm64.zip" }
|
|
1517
|
+
}),
|
|
1518
|
+
binaryInArchive: "gitleaks"
|
|
1519
|
+
}
|
|
1520
|
+
},
|
|
1521
|
+
{
|
|
1522
|
+
id: "trivy",
|
|
1523
|
+
name: "Trivy",
|
|
1524
|
+
checkCommand: "trivy",
|
|
1525
|
+
checkArgs: ["--version"],
|
|
1526
|
+
installStrategy: "github",
|
|
1527
|
+
binaryName: "trivy",
|
|
1528
|
+
github: {
|
|
1529
|
+
repo: "aquasecurity/trivy",
|
|
1530
|
+
// Trivy asset naming is `trivy_<ver>_<OS>-<bits>.{tar.gz,zip}` with a
|
|
1531
|
+
// capitalized OS and `64bit`/`ARM64` arch tokens — e.g.
|
|
1532
|
+
// `trivy_0.71.2_Linux-64bit.tar.gz`, `trivy_0.71.2_macOS-ARM64.tar.gz`.
|
|
1533
|
+
// No windows-arm64 asset exists (win32.arm64 omitted), so (like
|
|
1534
|
+
// swiftlint) trivy is absent from GITHUB_TOOLS and covered by the
|
|
1535
|
+
// weaker "at least one platform" guard.
|
|
1536
|
+
assetMatch: archAssetMatch({
|
|
1537
|
+
linux: { x64: "Linux-64bit.tar.gz", arm64: "Linux-ARM64.tar.gz" },
|
|
1538
|
+
darwin: { x64: "macOS-64bit.tar.gz", arm64: "macOS-ARM64.tar.gz" },
|
|
1539
|
+
win32: { x64: "windows-64bit.zip" }
|
|
1540
|
+
}),
|
|
1541
|
+
binaryInArchive: "trivy"
|
|
1542
|
+
}
|
|
1543
|
+
},
|
|
1544
|
+
{
|
|
1545
|
+
id: "swiftlint",
|
|
1546
|
+
name: "SwiftLint",
|
|
1547
|
+
checkCommand: "swiftlint",
|
|
1548
|
+
checkArgs: ["--version"],
|
|
1549
|
+
installStrategy: "github",
|
|
1550
|
+
binaryName: "swiftlint",
|
|
1551
|
+
github: {
|
|
1552
|
+
repo: "realm/SwiftLint",
|
|
1553
|
+
assetMatch: (platform, arch) => {
|
|
1554
|
+
if (platform === "darwin")
|
|
1555
|
+
return "portable_swiftlint.zip";
|
|
1556
|
+
if (platform === "linux")
|
|
1557
|
+
return arch === "arm64" ? "swiftlint_linux_arm64.zip" : "swiftlint_linux_amd64.zip";
|
|
1558
|
+
return void 0;
|
|
1559
|
+
},
|
|
1560
|
+
binaryInArchive: "swiftlint"
|
|
1561
|
+
}
|
|
1562
|
+
},
|
|
1563
|
+
{
|
|
1564
|
+
id: "taplo",
|
|
1565
|
+
name: "taplo",
|
|
1566
|
+
checkCommand: "taplo",
|
|
1567
|
+
checkArgs: ["--version"],
|
|
1568
|
+
installStrategy: "github",
|
|
1569
|
+
binaryName: "taplo",
|
|
1570
|
+
github: {
|
|
1571
|
+
repo: "tamasfe/taplo",
|
|
1572
|
+
assetMatch: (platform, arch) => {
|
|
1573
|
+
if (platform === "linux")
|
|
1574
|
+
return arch === "arm64" ? "taplo-linux-aarch64.gz" : "taplo-linux-x86_64.gz";
|
|
1575
|
+
if (platform === "darwin")
|
|
1576
|
+
return arch === "arm64" ? "taplo-darwin-aarch64.gz" : "taplo-darwin-x86_64.gz";
|
|
1577
|
+
if (platform === "win32")
|
|
1578
|
+
return "taplo-windows-x86_64.gz";
|
|
1579
|
+
return void 0;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
},
|
|
1583
|
+
{
|
|
1584
|
+
id: "vale",
|
|
1585
|
+
name: "Vale",
|
|
1586
|
+
checkCommand: "vale",
|
|
1587
|
+
checkArgs: ["--version"],
|
|
1588
|
+
installStrategy: "github",
|
|
1589
|
+
binaryName: "vale",
|
|
1590
|
+
github: {
|
|
1591
|
+
repo: "vale-cli/vale",
|
|
1592
|
+
assetMatch: (platform, arch) => {
|
|
1593
|
+
const version = "3.14.2";
|
|
1594
|
+
if (platform === "linux")
|
|
1595
|
+
return arch === "arm64" ? `vale_${version}_Linux_arm64.tar.gz` : `vale_${version}_Linux_64-bit.tar.gz`;
|
|
1596
|
+
if (platform === "darwin")
|
|
1597
|
+
return arch === "arm64" ? `vale_${version}_macOS_arm64.tar.gz` : `vale_${version}_macOS_64-bit.tar.gz`;
|
|
1598
|
+
if (platform === "win32")
|
|
1599
|
+
return `vale_${version}_Windows_64-bit.zip`;
|
|
1600
|
+
return void 0;
|
|
1601
|
+
},
|
|
1602
|
+
binaryInArchive: "vale"
|
|
1603
|
+
}
|
|
1604
|
+
},
|
|
1605
|
+
{
|
|
1606
|
+
id: "terraform-ls",
|
|
1607
|
+
name: "terraform-ls",
|
|
1608
|
+
checkCommand: "terraform-ls",
|
|
1609
|
+
// intended: version — unverified against real binary (shape 16), do not wire until probed
|
|
1610
|
+
checkArgs: ["--version"],
|
|
1611
|
+
installStrategy: "github",
|
|
1612
|
+
binaryName: "terraform-ls",
|
|
1613
|
+
github: {
|
|
1614
|
+
repo: "hashicorp/terraform-ls",
|
|
1615
|
+
hashiCorpReleaseProduct: "terraform-ls",
|
|
1616
|
+
assetMatch: archAssetMatch(OS_ARCH_ZIP_ASSETS),
|
|
1617
|
+
binaryInArchive: "terraform-ls"
|
|
1618
|
+
}
|
|
1619
|
+
},
|
|
1620
|
+
{
|
|
1621
|
+
id: "zls",
|
|
1622
|
+
name: "zls",
|
|
1623
|
+
checkCommand: "zls",
|
|
1624
|
+
checkArgs: ["--version"],
|
|
1625
|
+
installStrategy: "github",
|
|
1626
|
+
binaryName: "zls",
|
|
1627
|
+
github: {
|
|
1628
|
+
repo: "zigtools/zls",
|
|
1629
|
+
assetMatch: (platform, arch) => {
|
|
1630
|
+
if (platform === "linux")
|
|
1631
|
+
return arch === "arm64" ? "aarch64-linux.tar.xz" : "x86_64-linux.tar.xz";
|
|
1632
|
+
if (platform === "darwin")
|
|
1633
|
+
return arch === "arm64" ? "aarch64-macos.tar.xz" : "x86_64-macos.tar.xz";
|
|
1634
|
+
if (platform === "win32")
|
|
1635
|
+
return arch === "arm64" ? "aarch64-windows.zip" : "x86_64-windows.zip";
|
|
1636
|
+
return void 0;
|
|
1637
|
+
},
|
|
1638
|
+
binaryInArchive: "zls"
|
|
1639
|
+
}
|
|
1640
|
+
},
|
|
1641
|
+
{
|
|
1642
|
+
// clojure-lsp ships a self-contained native (GraalVM) binary per platform
|
|
1643
|
+
// on GitHub releases — no JVM needed. Used as managedToolId by ClojureServer.
|
|
1644
|
+
// The .zip carries the bare binary (located recursively on extract).
|
|
1645
|
+
id: "clojure-lsp",
|
|
1646
|
+
name: "clojure-lsp",
|
|
1647
|
+
checkCommand: "clojure-lsp",
|
|
1648
|
+
checkArgs: ["--version"],
|
|
1649
|
+
installStrategy: "github",
|
|
1650
|
+
binaryName: "clojure-lsp",
|
|
1651
|
+
github: {
|
|
1652
|
+
repo: "clojure-lsp/clojure-lsp",
|
|
1653
|
+
assetMatch: (platform, arch) => {
|
|
1654
|
+
if (platform === "linux")
|
|
1655
|
+
return arch === "arm64" ? "native-linux-aarch64.zip" : "native-linux-amd64.zip";
|
|
1656
|
+
if (platform === "darwin")
|
|
1657
|
+
return arch === "arm64" ? "native-macos-aarch64.zip" : "native-macos-amd64.zip";
|
|
1658
|
+
if (platform === "win32")
|
|
1659
|
+
return "native-windows-amd64.zip";
|
|
1660
|
+
return void 0;
|
|
1661
|
+
},
|
|
1662
|
+
binaryInArchive: "clojure-lsp"
|
|
1663
|
+
}
|
|
1664
|
+
},
|
|
1665
|
+
{
|
|
1666
|
+
// CUE ships a single native binary per platform on GitHub releases;
|
|
1667
|
+
// the LSP runs via `cue lsp serve`. Used as managedToolId by CueServer.
|
|
1668
|
+
id: "cue",
|
|
1669
|
+
name: "CUE",
|
|
1670
|
+
checkCommand: "cue",
|
|
1671
|
+
// Probed locally with CUE v0.17.1: `cue version` exits 0.
|
|
1672
|
+
checkArgs: ["version"],
|
|
1673
|
+
installStrategy: "github",
|
|
1674
|
+
binaryName: "cue",
|
|
1675
|
+
github: {
|
|
1676
|
+
repo: "cue-lang/cue",
|
|
1677
|
+
assetMatch: (platform, arch) => {
|
|
1678
|
+
if (platform === "linux")
|
|
1679
|
+
return arch === "arm64" ? "linux_arm64.tar.gz" : "linux_amd64.tar.gz";
|
|
1680
|
+
if (platform === "darwin")
|
|
1681
|
+
return arch === "arm64" ? "darwin_arm64.tar.gz" : "darwin_amd64.tar.gz";
|
|
1682
|
+
if (platform === "win32")
|
|
1683
|
+
return arch === "arm64" ? "windows_arm64.zip" : "windows_amd64.zip";
|
|
1684
|
+
return void 0;
|
|
1685
|
+
},
|
|
1686
|
+
binaryInArchive: "cue"
|
|
1687
|
+
}
|
|
1688
|
+
},
|
|
1689
|
+
{
|
|
1690
|
+
// gleam ships a single static binary per platform on GitHub releases; the
|
|
1691
|
+
// LSP runs via `gleam lsp`. Used as managedToolId by GleamServer. The linux
|
|
1692
|
+
// build is a FLAT musl tarball (a bare `gleam`), handled by the recursive
|
|
1693
|
+
// tar-binary lookup in installGitHubTool.
|
|
1694
|
+
id: "gleam",
|
|
1695
|
+
name: "Gleam",
|
|
1696
|
+
checkCommand: "gleam",
|
|
1697
|
+
checkArgs: ["--version"],
|
|
1698
|
+
installStrategy: "github",
|
|
1699
|
+
binaryName: "gleam",
|
|
1700
|
+
github: {
|
|
1701
|
+
repo: "gleam-lang/gleam",
|
|
1702
|
+
assetMatch: (platform, arch) => {
|
|
1703
|
+
if (platform === "linux")
|
|
1704
|
+
return arch === "arm64" ? "aarch64-unknown-linux-musl.tar.gz" : "x86_64-unknown-linux-musl.tar.gz";
|
|
1705
|
+
if (platform === "darwin")
|
|
1706
|
+
return arch === "arm64" ? "aarch64-apple-darwin.tar.gz" : "x86_64-apple-darwin.tar.gz";
|
|
1707
|
+
if (platform === "win32")
|
|
1708
|
+
return arch === "arm64" ? "aarch64-pc-windows-msvc.zip" : "x86_64-pc-windows-msvc.zip";
|
|
1709
|
+
return void 0;
|
|
1710
|
+
},
|
|
1711
|
+
binaryInArchive: "gleam"
|
|
1712
|
+
}
|
|
1713
|
+
},
|
|
1714
|
+
{
|
|
1715
|
+
// Tinymist publishes cargo-dist archives containing the `tinymist` binary
|
|
1716
|
+
// for the supported desktop targets. The LSP server uses `tinymist lsp`.
|
|
1717
|
+
id: "tinymist",
|
|
1718
|
+
name: "Tinymist",
|
|
1719
|
+
checkCommand: "tinymist",
|
|
1720
|
+
checkArgs: ["--version"],
|
|
1721
|
+
installStrategy: "github",
|
|
1722
|
+
binaryName: "tinymist",
|
|
1723
|
+
github: {
|
|
1724
|
+
repo: "Myriad-Dreamin/tinymist",
|
|
1725
|
+
assetMatch: archAssetMatch({
|
|
1726
|
+
linux: {
|
|
1727
|
+
x64: "tinymist-x86_64-unknown-linux-gnu.tar.gz",
|
|
1728
|
+
arm64: "tinymist-aarch64-unknown-linux-gnu.tar.gz"
|
|
1729
|
+
},
|
|
1730
|
+
darwin: {
|
|
1731
|
+
x64: "tinymist-x86_64-apple-darwin.tar.gz",
|
|
1732
|
+
arm64: "tinymist-aarch64-apple-darwin.tar.gz"
|
|
1733
|
+
},
|
|
1734
|
+
win32: {
|
|
1735
|
+
x64: "tinymist-x86_64-pc-windows-msvc.zip",
|
|
1736
|
+
arm64: "tinymist-aarch64-pc-windows-msvc.zip"
|
|
1737
|
+
}
|
|
1738
|
+
}),
|
|
1739
|
+
binaryInArchive: "tinymist"
|
|
1740
|
+
}
|
|
1741
|
+
},
|
|
1742
|
+
{
|
|
1743
|
+
// marksman ships a single BARE (uncompressed) binary per platform on GitHub
|
|
1744
|
+
// releases — no archive, so it lands via the bare-binary branch of
|
|
1745
|
+
// installGitHubTool (the `else` that writes the asset directly, like shfmt).
|
|
1746
|
+
// Used as managedToolId by MarksmanServer; LSP entrypoint is `marksman
|
|
1747
|
+
// server` (stdio). macOS ships a universal binary; Windows has only x64
|
|
1748
|
+
// (runs on arm64 via emulation) — so all six platform/arch combos resolve.
|
|
1749
|
+
id: "marksman",
|
|
1750
|
+
name: "Marksman",
|
|
1751
|
+
checkCommand: "marksman",
|
|
1752
|
+
checkArgs: ["--version"],
|
|
1753
|
+
installStrategy: "github",
|
|
1754
|
+
binaryName: "marksman",
|
|
1755
|
+
github: {
|
|
1756
|
+
repo: "artempyanykh/marksman",
|
|
1757
|
+
assetMatch: (platform, arch) => {
|
|
1758
|
+
if (platform === "linux")
|
|
1759
|
+
return arch === "arm64" ? "marksman-linux-arm64" : "marksman-linux-x64";
|
|
1760
|
+
if (platform === "darwin")
|
|
1761
|
+
return "marksman-macos";
|
|
1762
|
+
if (platform === "win32")
|
|
1763
|
+
return "marksman.exe";
|
|
1764
|
+
return void 0;
|
|
1765
|
+
}
|
|
1766
|
+
// bare binary — no binaryInArchive
|
|
1767
|
+
}
|
|
1768
|
+
},
|
|
1769
|
+
{
|
|
1770
|
+
// Expert ships a bare native binary per platform on GitHub releases. Its
|
|
1771
|
+
// `--stdio` flag is required to start the LSP transport. Windows arm64 uses
|
|
1772
|
+
// the x64 binary through Windows' built-in x64 emulation.
|
|
1773
|
+
id: "expert",
|
|
1774
|
+
name: "Expert",
|
|
1775
|
+
checkCommand: "expert",
|
|
1776
|
+
checkArgs: ["--version"],
|
|
1777
|
+
installStrategy: "github",
|
|
1778
|
+
binaryName: "expert",
|
|
1779
|
+
github: {
|
|
1780
|
+
repo: "expert-lsp/expert",
|
|
1781
|
+
assetMatch: (platform, arch) => {
|
|
1782
|
+
if (arch !== "x64" && arch !== "arm64")
|
|
1783
|
+
return void 0;
|
|
1784
|
+
if (platform === "linux")
|
|
1785
|
+
return arch === "arm64" ? "expert_linux_arm64" : "expert_linux_amd64";
|
|
1786
|
+
if (platform === "darwin")
|
|
1787
|
+
return arch === "arm64" ? "expert_darwin_arm64" : "expert_darwin_amd64";
|
|
1788
|
+
if (platform === "win32")
|
|
1789
|
+
return "expert_windows_amd64.exe";
|
|
1790
|
+
return void 0;
|
|
1791
|
+
}
|
|
1792
|
+
// bare binary — no binaryInArchive
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
];
|
|
1796
|
+
function getToolVerificationTimeout(tool) {
|
|
1797
|
+
return tool.verificationTimeoutMs ?? 1e4;
|
|
1798
|
+
}
|
|
1799
|
+
var ensureInFlight = /* @__PURE__ */ new Map();
|
|
1800
|
+
var installFailureReasons = /* @__PURE__ */ new Map();
|
|
1801
|
+
function getInstallFailureReason(toolId) {
|
|
1802
|
+
return installFailureReasons.get(toolId);
|
|
1803
|
+
}
|
|
1804
|
+
var installAttempts = /* @__PURE__ */ new Map();
|
|
1805
|
+
function noteInstallAttempt(toolId, outcome, reason) {
|
|
1806
|
+
installAttempts.set(toolId, { outcome, reason, at: Date.now() });
|
|
1807
|
+
}
|
|
1808
|
+
function noteInstallAttemptIfUnrecorded(toolId, outcome, reason) {
|
|
1809
|
+
if (installAttempts.has(toolId))
|
|
1810
|
+
return;
|
|
1811
|
+
noteInstallAttempt(toolId, outcome, reason);
|
|
1812
|
+
}
|
|
1813
|
+
function getInstallAttempt(toolId) {
|
|
1814
|
+
return installAttempts.get(toolId);
|
|
1815
|
+
}
|
|
1816
|
+
var lastEnsureResolutionSource = /* @__PURE__ */ new Map();
|
|
1817
|
+
function getLastEnsureResolutionSource(toolId) {
|
|
1818
|
+
return lastEnsureResolutionSource.get(toolId);
|
|
1819
|
+
}
|
|
1820
|
+
var resolvedPathCache = new BoundedLruCache(256);
|
|
1821
|
+
function resetResolvedPathCache() {
|
|
1822
|
+
resolvedPathCache.clear();
|
|
1823
|
+
}
|
|
1824
|
+
var PROBE_CACHE_PATH = path3.join(getGlobalPiLensDir(), "probe-cache.json");
|
|
1825
|
+
var PROBE_CACHE_LOCK_STALE_MS = 18e4;
|
|
1826
|
+
var PROBE_CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1827
|
+
var PROBE_CACHE_TRANSIENT_COOLDOWN_MS = TRANSIENT_MAX_COOLDOWN_MS;
|
|
1828
|
+
var PROBE_CACHE_FLUSH_LOCK_WAIT_MS = 250;
|
|
1829
|
+
var PROBE_CACHE_FLUSH_RETRY_DELAY_MS = 300;
|
|
1830
|
+
var PROBE_CACHE_FLUSH_RETRY_MAX_DELAY_MS = 3e4;
|
|
1831
|
+
var _probeCache = null;
|
|
1832
|
+
var _probeCacheDirty = false;
|
|
1833
|
+
var _probeCacheFlushTimer = null;
|
|
1834
|
+
var _probeCacheWriteInFlight = null;
|
|
1835
|
+
var _probeCacheRetryAttempt = 0;
|
|
1836
|
+
var _probeCacheChangeGeneration = 0;
|
|
1837
|
+
var _probeCacheChanges = /* @__PURE__ */ new Map();
|
|
1838
|
+
var _probeCacheChangeVersions = /* @__PURE__ */ new Map();
|
|
1839
|
+
async function readProbeCache() {
|
|
1840
|
+
if (_probeCache !== null)
|
|
1841
|
+
return _probeCache;
|
|
1842
|
+
try {
|
|
1843
|
+
const raw = await fs2.readFile(PROBE_CACHE_PATH, "utf-8");
|
|
1844
|
+
const parsed = JSON.parse(raw);
|
|
1845
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1846
|
+
throw new Error("probe-cache root is not an object");
|
|
1847
|
+
}
|
|
1848
|
+
_probeCache = parsed;
|
|
1849
|
+
} catch (err) {
|
|
1850
|
+
const code = err?.code;
|
|
1851
|
+
if (code !== "ENOENT") {
|
|
1852
|
+
logSessionStart(`auto-install probe-cache: read failed (${code ?? "invalid"}); treating cache as unavailable`);
|
|
1853
|
+
}
|
|
1854
|
+
_probeCache = {};
|
|
1855
|
+
}
|
|
1856
|
+
return _probeCache;
|
|
1857
|
+
}
|
|
1858
|
+
function markProbeCacheChange(toolId, entry) {
|
|
1859
|
+
_probeCacheChanges.set(toolId, entry);
|
|
1860
|
+
_probeCacheChangeVersions.set(toolId, ++_probeCacheChangeGeneration);
|
|
1861
|
+
_probeCacheDirty = true;
|
|
1862
|
+
scheduleProbeFlush();
|
|
1863
|
+
}
|
|
1864
|
+
function scheduleProbeFlush(delayMs = PROBE_CACHE_FLUSH_RETRY_DELAY_MS) {
|
|
1865
|
+
if (_probeCacheFlushTimer !== null)
|
|
1866
|
+
return;
|
|
1867
|
+
_probeCacheFlushTimer = setTimeout(() => {
|
|
1868
|
+
void flushProbeCache();
|
|
1869
|
+
}, delayMs);
|
|
1870
|
+
_probeCacheFlushTimer.unref?.();
|
|
1871
|
+
}
|
|
1872
|
+
function scheduleProbeFlushRetry() {
|
|
1873
|
+
const delay = Math.min(PROBE_CACHE_FLUSH_RETRY_MAX_DELAY_MS, PROBE_CACHE_FLUSH_RETRY_DELAY_MS * 2 ** Math.min(_probeCacheRetryAttempt, 6));
|
|
1874
|
+
_probeCacheRetryAttempt += 1;
|
|
1875
|
+
scheduleProbeFlush(delay);
|
|
1876
|
+
}
|
|
1877
|
+
function snapshotProbeCacheChanges() {
|
|
1878
|
+
return {
|
|
1879
|
+
changes: new Map(_probeCacheChanges),
|
|
1880
|
+
versions: new Map(_probeCacheChangeVersions)
|
|
1881
|
+
};
|
|
1882
|
+
}
|
|
1883
|
+
function deserializeProbeCache(contents) {
|
|
1884
|
+
if (contents === void 0)
|
|
1885
|
+
return {};
|
|
1886
|
+
try {
|
|
1887
|
+
const parsed = JSON.parse(contents);
|
|
1888
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1889
|
+
throw new Error("probe-cache root is not an object");
|
|
1890
|
+
}
|
|
1891
|
+
return parsed;
|
|
1892
|
+
} catch (err) {
|
|
1893
|
+
logSessionStart(`auto-install probe-cache: write-side read was corrupt (${err.message}); recovering as empty`);
|
|
1894
|
+
return {};
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
function applyProbeCacheChanges(disk, changes) {
|
|
1898
|
+
for (const [toolId, entry] of changes) {
|
|
1899
|
+
if (entry === null)
|
|
1900
|
+
delete disk[toolId];
|
|
1901
|
+
else
|
|
1902
|
+
disk[toolId] = entry;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
function ageProbeCache(disk) {
|
|
1906
|
+
const now = Date.now();
|
|
1907
|
+
for (const [toolId, entry] of Object.entries(disk)) {
|
|
1908
|
+
const ttl = entry.transient ? PROBE_CACHE_TRANSIENT_COOLDOWN_MS : PROBE_CACHE_TTL_MS;
|
|
1909
|
+
if (!Number.isFinite(entry.cachedAt) || entry.cachedAt < now - ttl) {
|
|
1910
|
+
delete disk[toolId];
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
function publishProbeCacheWrite(disk, snapshotVersions) {
|
|
1915
|
+
const pendingAfterWrite = new Map(_probeCacheChanges);
|
|
1916
|
+
_probeCache = disk;
|
|
1917
|
+
for (const [toolId, entry] of pendingAfterWrite) {
|
|
1918
|
+
if (entry === null)
|
|
1919
|
+
delete _probeCache[toolId];
|
|
1920
|
+
else
|
|
1921
|
+
_probeCache[toolId] = entry;
|
|
1922
|
+
}
|
|
1923
|
+
for (const [toolId, version] of snapshotVersions) {
|
|
1924
|
+
if (_probeCacheChangeVersions.get(toolId) === version) {
|
|
1925
|
+
_probeCacheChanges.delete(toolId);
|
|
1926
|
+
_probeCacheChangeVersions.delete(toolId);
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
_probeCacheDirty = _probeCacheChanges.size > 0;
|
|
1930
|
+
_probeCacheRetryAttempt = 0;
|
|
1931
|
+
return _probeCacheDirty ? "written-with-pending" : "written";
|
|
1932
|
+
}
|
|
1933
|
+
async function writeProbeCache() {
|
|
1934
|
+
try {
|
|
1935
|
+
const { changes, versions } = snapshotProbeCacheChanges();
|
|
1936
|
+
let result = "written";
|
|
1937
|
+
const committed = await commitDurableStoreAsync({
|
|
1938
|
+
path: PROBE_CACHE_PATH,
|
|
1939
|
+
deserialize: deserializeProbeCache,
|
|
1940
|
+
merge: (disk) => {
|
|
1941
|
+
ageProbeCache(disk);
|
|
1942
|
+
applyProbeCacheChanges(disk, changes);
|
|
1943
|
+
return disk;
|
|
1944
|
+
},
|
|
1945
|
+
serialize: (disk) => JSON.stringify(disk, null, 2),
|
|
1946
|
+
waitMs: PROBE_CACHE_FLUSH_LOCK_WAIT_MS,
|
|
1947
|
+
retryMs: 25,
|
|
1948
|
+
staleMs: PROBE_CACHE_LOCK_STALE_MS,
|
|
1949
|
+
timeoutMessage: "Timed out waiting for probe-cache lock",
|
|
1950
|
+
onContention: "skip-log",
|
|
1951
|
+
logContention: () => {
|
|
1952
|
+
logSessionStart("auto-install probe-cache: flush deferred because another process owns the lock");
|
|
1953
|
+
},
|
|
1954
|
+
afterWriteLocked: (disk) => {
|
|
1955
|
+
result = publishProbeCacheWrite(disk, versions);
|
|
1956
|
+
}
|
|
1957
|
+
});
|
|
1958
|
+
if (committed === void 0) {
|
|
1959
|
+
scheduleProbeFlushRetry();
|
|
1960
|
+
return "deferred";
|
|
1961
|
+
}
|
|
1962
|
+
return result;
|
|
1963
|
+
} catch (err) {
|
|
1964
|
+
logSessionStart(`auto-install probe-cache: flush failed (${err?.code ?? "write error"}); pending update retained`);
|
|
1965
|
+
scheduleProbeFlushRetry();
|
|
1966
|
+
return "failed";
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
async function flushProbeCache() {
|
|
1970
|
+
if (_probeCacheFlushTimer !== null) {
|
|
1971
|
+
clearTimeout(_probeCacheFlushTimer);
|
|
1972
|
+
_probeCacheFlushTimer = null;
|
|
1973
|
+
}
|
|
1974
|
+
if (_probeCacheWriteInFlight) {
|
|
1975
|
+
await _probeCacheWriteInFlight;
|
|
1976
|
+
if (!_probeCacheDirty)
|
|
1977
|
+
return "written";
|
|
1978
|
+
}
|
|
1979
|
+
if (!_probeCacheDirty || _probeCache === null)
|
|
1980
|
+
return "idle";
|
|
1981
|
+
const write = writeProbeCache();
|
|
1982
|
+
_probeCacheWriteInFlight = write;
|
|
1983
|
+
try {
|
|
1984
|
+
return await write;
|
|
1985
|
+
} finally {
|
|
1986
|
+
if (_probeCacheWriteInFlight === write)
|
|
1987
|
+
_probeCacheWriteInFlight = null;
|
|
1988
|
+
}
|
|
1989
|
+
}
|
|
1990
|
+
function isAstGrepVersionOutput(output) {
|
|
1991
|
+
return /\bast[- ]grep\b/i.test(output);
|
|
1992
|
+
}
|
|
1993
|
+
async function verifyAstGrepProbePath(binPath) {
|
|
1994
|
+
const result = await probeToolAsync(binPath, ["--version"], {
|
|
1995
|
+
timeout: 5e3
|
|
1996
|
+
});
|
|
1997
|
+
return !result.error && result.status === 0 && isAstGrepVersionOutput(`${result.stdout}${result.stderr}`);
|
|
1998
|
+
}
|
|
1999
|
+
async function checkProbeCache(toolId) {
|
|
2000
|
+
const cache = await readProbeCache();
|
|
2001
|
+
const entry = cache[toolId];
|
|
2002
|
+
if (!entry)
|
|
2003
|
+
return void 0;
|
|
2004
|
+
const ttl = entry.transient ? PROBE_CACHE_TRANSIENT_COOLDOWN_MS : PROBE_CACHE_TTL_MS;
|
|
2005
|
+
if (Date.now() - entry.cachedAt > ttl) {
|
|
2006
|
+
logSessionStart(`auto-install probe-cache ${toolId}: miss (${entry.transient ? "transient cooldown" : "ttl"} expired)`);
|
|
2007
|
+
delete cache[toolId];
|
|
2008
|
+
markProbeCacheChange(toolId, null);
|
|
2009
|
+
return void 0;
|
|
2010
|
+
}
|
|
2011
|
+
try {
|
|
2012
|
+
await fs2.access(entry.path);
|
|
2013
|
+
const stat = await fs2.stat(entry.path);
|
|
2014
|
+
if (stat.mtimeMs !== entry.mtimeMs || entry.sizeBytes !== void 0 && stat.size !== entry.sizeBytes) {
|
|
2015
|
+
logSessionStart(`auto-install probe-cache ${toolId}: miss (mtime/size changed)`);
|
|
2016
|
+
delete cache[toolId];
|
|
2017
|
+
markProbeCacheChange(toolId, null);
|
|
2018
|
+
return void 0;
|
|
2019
|
+
}
|
|
2020
|
+
if (toolId === "ast-grep" && !await verifyAstGrepProbePath(entry.path)) {
|
|
2021
|
+
logSessionStart(`auto-install probe-cache ${toolId}: miss (not ast-grep: ${entry.path})`);
|
|
2022
|
+
delete cache[toolId];
|
|
2023
|
+
markProbeCacheChange(toolId, null);
|
|
2024
|
+
return void 0;
|
|
2025
|
+
}
|
|
2026
|
+
return entry.path;
|
|
2027
|
+
} catch {
|
|
2028
|
+
logSessionStart(`auto-install probe-cache ${toolId}: miss (gone: ${entry.path})`);
|
|
2029
|
+
delete cache[toolId];
|
|
2030
|
+
markProbeCacheChange(toolId, null);
|
|
2031
|
+
return void 0;
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
async function updateProbeCache(toolId, resolvedPath, transient = false) {
|
|
2035
|
+
try {
|
|
2036
|
+
const stat = await fs2.stat(resolvedPath);
|
|
2037
|
+
const cache = await readProbeCache();
|
|
2038
|
+
const entry = {
|
|
2039
|
+
path: resolvedPath,
|
|
2040
|
+
mtimeMs: stat.mtimeMs,
|
|
2041
|
+
sizeBytes: stat.size,
|
|
2042
|
+
cachedAt: Date.now(),
|
|
2043
|
+
...transient && { transient: true }
|
|
2044
|
+
};
|
|
2045
|
+
cache[toolId] = entry;
|
|
2046
|
+
markProbeCacheChange(toolId, entry);
|
|
2047
|
+
} catch {
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
function resetProbeCacheStateForTesting() {
|
|
2051
|
+
_probeCache = null;
|
|
2052
|
+
_probeCacheDirty = false;
|
|
2053
|
+
_probeCacheChanges.clear();
|
|
2054
|
+
_probeCacheChangeVersions.clear();
|
|
2055
|
+
_probeCacheChangeGeneration = 0;
|
|
2056
|
+
_probeCacheRetryAttempt = 0;
|
|
2057
|
+
resetResolvedPathCache();
|
|
2058
|
+
ensureInFlight.clear();
|
|
2059
|
+
installFailureReasons.clear();
|
|
2060
|
+
installAttempts.clear();
|
|
2061
|
+
lastEnsureResolutionSource.clear();
|
|
2062
|
+
lastManagedInstallVersion.clear();
|
|
2063
|
+
lastResolveTransient.clear();
|
|
2064
|
+
resetPathWalkMemo();
|
|
2065
|
+
if (_probeCacheFlushTimer !== null) {
|
|
2066
|
+
clearTimeout(_probeCacheFlushTimer);
|
|
2067
|
+
_probeCacheFlushTimer = null;
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
function _peekEnsureInFlightForTesting() {
|
|
2071
|
+
return ensureInFlight;
|
|
2072
|
+
}
|
|
2073
|
+
var pathWalkMemo = /* @__PURE__ */ new Map();
|
|
2074
|
+
function hashSync(value) {
|
|
2075
|
+
let hash = 2166136261;
|
|
2076
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
2077
|
+
hash ^= value.charCodeAt(i);
|
|
2078
|
+
hash = Math.imul(hash, 16777619);
|
|
2079
|
+
}
|
|
2080
|
+
return (hash >>> 0).toString(16);
|
|
2081
|
+
}
|
|
2082
|
+
function resetPathWalkMemo() {
|
|
2083
|
+
pathWalkMemo.clear();
|
|
2084
|
+
}
|
|
2085
|
+
async function isCommandAvailable(command, _args) {
|
|
2086
|
+
const isWindows = installerPlatform() === "win32";
|
|
2087
|
+
const pathEnv = process.env.PATH || process.env.Path || process.env.path || "";
|
|
2088
|
+
const dirs = pathEnv.split(path3.delimiter);
|
|
2089
|
+
const names = isWindows ? [command, `${command}.exe`, `${command}.cmd`, `${command}.bat`] : [command];
|
|
2090
|
+
for (const dir of dirs) {
|
|
2091
|
+
if (!dir)
|
|
2092
|
+
continue;
|
|
2093
|
+
for (const name of names) {
|
|
2094
|
+
const candidate = path3.join(dir, name);
|
|
2095
|
+
try {
|
|
2096
|
+
const stat = statSync(candidate);
|
|
2097
|
+
if (stat.isFile() && stat.size > 0) {
|
|
2098
|
+
return true;
|
|
2099
|
+
}
|
|
2100
|
+
} catch {
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
return false;
|
|
2105
|
+
}
|
|
2106
|
+
async function isSpawnableCommand(command) {
|
|
2107
|
+
if (/[\\/]/.test(command)) {
|
|
2108
|
+
try {
|
|
2109
|
+
const stat = statSync(command);
|
|
2110
|
+
return stat.isFile() && stat.size > 0;
|
|
2111
|
+
} catch {
|
|
2112
|
+
return false;
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
const memoKey = `${command}:${hashSync(process.env.PATH || "")}`;
|
|
2116
|
+
if (pathWalkMemo.has(memoKey))
|
|
2117
|
+
return pathWalkMemo.get(memoKey) ?? false;
|
|
2118
|
+
const isSpawnable = await isCommandAvailable(command);
|
|
2119
|
+
pathWalkMemo.set(memoKey, isSpawnable);
|
|
2120
|
+
return isSpawnable;
|
|
2121
|
+
}
|
|
2122
|
+
function isLspTransportRequiredError(output) {
|
|
2123
|
+
return /Connection (?:input|output) stream is not set|Use arguments of createConnection/i.test(output);
|
|
2124
|
+
}
|
|
2125
|
+
var lspTransportRequiredMatcher = /Connection (?:input|output) stream is not set|Use arguments of createConnection/i;
|
|
2126
|
+
function parsePinnedVersion(packageName) {
|
|
2127
|
+
const at = packageName.lastIndexOf("@");
|
|
2128
|
+
if (at <= 0)
|
|
2129
|
+
return void 0;
|
|
2130
|
+
return packageName.slice(at + 1) || void 0;
|
|
2131
|
+
}
|
|
2132
|
+
function extractVersionToken(output) {
|
|
2133
|
+
return output.match(/\d+\.\d+\.\d+(?:[-+][\w.]+)?/)?.[0];
|
|
2134
|
+
}
|
|
2135
|
+
var lastManagedInstallVersion = /* @__PURE__ */ new Map();
|
|
2136
|
+
function packageEntryVerification(tool) {
|
|
2137
|
+
return tool.verification === "package-entry" ? tool.packageName : void 0;
|
|
2138
|
+
}
|
|
2139
|
+
async function verifyNpmPackageEntry(binPath, packageName) {
|
|
2140
|
+
const nodeModulesDir = path3.dirname(path3.dirname(binPath));
|
|
2141
|
+
const pinned = parsePinnedVersion(packageName);
|
|
2142
|
+
const bareName = pinned ? packageName.slice(0, -(pinned.length + 1)) : packageName;
|
|
2143
|
+
const packageDir = path3.join(nodeModulesDir, ...bareName.split("/"));
|
|
2144
|
+
const shimName = path3.basename(binPath).replace(/\.(cmd|exe|ps1|bat)$/i, "").toLowerCase();
|
|
2145
|
+
const fail = (reason) => {
|
|
2146
|
+
logSessionStart(`auto-install verify: failed for ${binPath} (check=package-entry, kind=${reason})`);
|
|
2147
|
+
return false;
|
|
2148
|
+
};
|
|
2149
|
+
try {
|
|
2150
|
+
const shim = statSync(binPath);
|
|
2151
|
+
if (!shim.isFile() || shim.size === 0)
|
|
2152
|
+
return fail("shim-not-a-file");
|
|
2153
|
+
} catch {
|
|
2154
|
+
return fail("shim-missing");
|
|
2155
|
+
}
|
|
2156
|
+
let manifest;
|
|
2157
|
+
try {
|
|
2158
|
+
manifest = JSON.parse(await fs2.readFile(path3.join(packageDir, "package.json"), "utf8"));
|
|
2159
|
+
} catch {
|
|
2160
|
+
return fail("package-json-unreadable");
|
|
2161
|
+
}
|
|
2162
|
+
if (typeof manifest.version !== "string" || !manifest.version) {
|
|
2163
|
+
return fail("package-json-no-version");
|
|
2164
|
+
}
|
|
2165
|
+
const bin = manifest.bin;
|
|
2166
|
+
const entry = typeof bin === "string" ? bin : bin && typeof bin === "object" ? Object.entries(bin).find(
|
|
2167
|
+
// Case-folded on purpose: on a case-insensitive filesystem the
|
|
2168
|
+
// shim on disk can differ in case from the manifest key npm
|
|
2169
|
+
// wrote it from, and `path.basename` reads the disk name.
|
|
2170
|
+
([name]) => name.toLowerCase() === shimName
|
|
2171
|
+
)?.[1] : void 0;
|
|
2172
|
+
if (typeof entry !== "string" || !entry) {
|
|
2173
|
+
return fail("package-json-no-entry");
|
|
2174
|
+
}
|
|
2175
|
+
try {
|
|
2176
|
+
const stat = statSync(path3.join(packageDir, entry));
|
|
2177
|
+
if (!stat.isFile() || stat.size === 0)
|
|
2178
|
+
return fail("entry-not-a-file");
|
|
2179
|
+
} catch {
|
|
2180
|
+
return fail("entry-missing");
|
|
2181
|
+
}
|
|
2182
|
+
logSessionStart(`auto-install verify: succeeded for ${binPath} (check=package-entry, version=${manifest.version}, entry=${entry})`);
|
|
2183
|
+
return true;
|
|
2184
|
+
}
|
|
2185
|
+
async function verifyToolBinary(binPath, onVersionOutput, onTransient, timeoutMs = 1e4, verificationArgs = ["--version"], packageEntryOf, onInconclusive) {
|
|
2186
|
+
if (packageEntryOf !== void 0) {
|
|
2187
|
+
return verifyNpmPackageEntry(binPath, packageEntryOf);
|
|
2188
|
+
}
|
|
2189
|
+
const isWindows = installerPlatform() === "win32";
|
|
2190
|
+
const hasKnownWindowsExt = /\.(cmd|exe|ps1)$/i.test(binPath);
|
|
2191
|
+
let execPath = isWindows && !hasKnownWindowsExt ? `${binPath}.cmd` : binPath;
|
|
2192
|
+
if (isWindows && /\.ps1$/i.test(execPath)) {
|
|
2193
|
+
const cmdSibling = `${execPath.slice(0, -4)}.cmd`;
|
|
2194
|
+
if (__require("node:fs").existsSync(cmdSibling)) {
|
|
2195
|
+
execPath = cmdSibling;
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
const useShell = isWindows && /\.(cmd|bat)$/i.test(execPath);
|
|
2199
|
+
void useShell;
|
|
2200
|
+
try {
|
|
2201
|
+
const result = await safeSpawnAsync(execPath, verificationArgs, {
|
|
2202
|
+
timeout: timeoutMs,
|
|
2203
|
+
input: "",
|
|
2204
|
+
// A broken or version-blind language server can emit a bundled
|
|
2205
|
+
// megabytes-long diagnostic on stderr. Keep verification bounded even
|
|
2206
|
+
// when the child reaches its normal timeout first.
|
|
2207
|
+
maxOutputBytes: 64 * 1024,
|
|
2208
|
+
matchWhileStreaming: lspTransportRequiredMatcher
|
|
2209
|
+
});
|
|
2210
|
+
const output = `${result.stdout}
|
|
2211
|
+
${result.stderr}`;
|
|
2212
|
+
if (result.outputTruncated) {
|
|
2213
|
+
recordDegradationOnce({
|
|
2214
|
+
kind: "installer-verification-output-truncated",
|
|
2215
|
+
subject: binPath,
|
|
2216
|
+
reason: `verification output exceeded 65536 bytes (${verificationArgs.join(" ")})`
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2219
|
+
if (result.status === 0 && !result.error) {
|
|
2220
|
+
debugLog(`Verified: ${binPath} (${verificationArgs.join(" ")}: ${result.stdout.trim()})`);
|
|
2221
|
+
onVersionOutput?.(result.stdout);
|
|
2222
|
+
return true;
|
|
2223
|
+
}
|
|
2224
|
+
if (result.streamingMatch || isLspTransportRequiredError(output)) {
|
|
2225
|
+
debugLog(`Verified (stdio LSP, transport-required): ${binPath}`);
|
|
2226
|
+
return true;
|
|
2227
|
+
}
|
|
2228
|
+
if (result.signal !== void 0 || result.spawnFailure)
|
|
2229
|
+
onTransient?.();
|
|
2230
|
+
if (result.outputTruncated && result.signal === void 0 && !result.spawnFailure) {
|
|
2231
|
+
onInconclusive?.();
|
|
2232
|
+
recordDegradationOnce({
|
|
2233
|
+
kind: "installer-verification-inconclusive",
|
|
2234
|
+
subject: binPath,
|
|
2235
|
+
reason: `transport-required marker unresolved in truncated output (${verificationArgs.join(" ")})`
|
|
2236
|
+
});
|
|
2237
|
+
}
|
|
2238
|
+
const kind = result.spawnFailure?.kind ?? (result.signal ? `killed-signal=${result.signal}` : result.error ? "spawn-error" : "exit-nonzero");
|
|
2239
|
+
logSessionStart(`auto-install verify: failed for ${binPath} (check=${verificationArgs.join(" ")}, kind=${kind}${result.status !== null ? `, exit=${result.status}` : ""})`);
|
|
2240
|
+
return false;
|
|
2241
|
+
} catch (err) {
|
|
2242
|
+
logSessionStart(`auto-install verify: spawn threw for ${binPath} (${err instanceof Error ? err.message : String(err)})`);
|
|
2243
|
+
onTransient?.();
|
|
2244
|
+
return false;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
async function getAllToolStatuses() {
|
|
2248
|
+
const statuses = [];
|
|
2249
|
+
for (const tool of TOOLS) {
|
|
2250
|
+
const status = {
|
|
2251
|
+
id: tool.id,
|
|
2252
|
+
name: tool.name,
|
|
2253
|
+
installed: false,
|
|
2254
|
+
source: "not-installed",
|
|
2255
|
+
strategy: tool.installStrategy
|
|
2256
|
+
};
|
|
2257
|
+
if (tool.installStrategy === "archive" && !tool.archive?.launcher) {
|
|
2258
|
+
const bundleDir = await getArchiveTreeBundlePath(tool);
|
|
2259
|
+
if (bundleDir) {
|
|
2260
|
+
status.installed = true;
|
|
2261
|
+
status.source = "archive-dist";
|
|
2262
|
+
status.path = bundleDir;
|
|
2263
|
+
}
|
|
2264
|
+
statuses.push(status);
|
|
2265
|
+
continue;
|
|
2266
|
+
}
|
|
2267
|
+
if (await isCommandAvailable(tool.checkCommand, tool.checkArgs)) {
|
|
2268
|
+
status.installed = true;
|
|
2269
|
+
status.source = "global-path";
|
|
2270
|
+
status.path = tool.checkCommand;
|
|
2271
|
+
if (tool.verification !== "tree-manifest") {
|
|
2272
|
+
const probe = await probeToolAsync(tool.checkCommand, ["--version"], {
|
|
2273
|
+
timeout: 5e3
|
|
2274
|
+
});
|
|
2275
|
+
status.version = `${probe.stdout}${probe.stderr}`.trim().split("\n")[0]?.slice(0, 30) || void 0;
|
|
2276
|
+
}
|
|
2277
|
+
statuses.push(status);
|
|
2278
|
+
continue;
|
|
2279
|
+
}
|
|
2280
|
+
if (tool.installStrategy === "npm") {
|
|
2281
|
+
const npmPath = await findNpmGlobalToolPath(tool.binaryName || tool.id, void 0, tool.checkArgs, getToolVerificationTimeout(tool));
|
|
2282
|
+
if (npmPath) {
|
|
2283
|
+
status.installed = true;
|
|
2284
|
+
status.source = "npm-global";
|
|
2285
|
+
status.path = npmPath;
|
|
2286
|
+
statuses.push(status);
|
|
2287
|
+
continue;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
if (tool.installStrategy === "pip") {
|
|
2291
|
+
const pipPath = await findPipUserToolPath(tool.binaryName || tool.id, void 0, tool.checkArgs, getToolVerificationTimeout(tool));
|
|
2292
|
+
if (pipPath) {
|
|
2293
|
+
status.installed = true;
|
|
2294
|
+
status.source = "pip-user";
|
|
2295
|
+
status.path = pipPath;
|
|
2296
|
+
statuses.push(status);
|
|
2297
|
+
continue;
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
if (tool.installStrategy === "github" || tool.installStrategy === "maven" || tool.installStrategy === "archive") {
|
|
2301
|
+
const githubPath = await findGitHubToolPath(tool.binaryName || tool.id);
|
|
2302
|
+
if (githubPath) {
|
|
2303
|
+
status.installed = true;
|
|
2304
|
+
status.source = tool.installStrategy === "maven" ? "maven-jar" : tool.installStrategy === "archive" ? "archive-dist" : "github-release";
|
|
2305
|
+
status.path = githubPath;
|
|
2306
|
+
statuses.push(status);
|
|
2307
|
+
continue;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
const localBase = path3.join(TOOLS_DIR, "node_modules", ".bin", tool.binaryName || tool.id);
|
|
2311
|
+
const localPath = installerPlatform() === "win32" ? `${localBase}.cmd` : localBase;
|
|
2312
|
+
try {
|
|
2313
|
+
await fs2.access(localPath);
|
|
2314
|
+
if (await verifyToolBinary(localPath, void 0, void 0, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
|
|
2315
|
+
status.installed = true;
|
|
2316
|
+
status.source = "pi-lens-auto";
|
|
2317
|
+
status.path = localPath;
|
|
2318
|
+
statuses.push(status);
|
|
2319
|
+
continue;
|
|
2320
|
+
}
|
|
2321
|
+
} catch {
|
|
2322
|
+
}
|
|
2323
|
+
if (tool.installStrategy === "npm") {
|
|
2324
|
+
status.source = "npx-fallback";
|
|
2325
|
+
}
|
|
2326
|
+
statuses.push(status);
|
|
2327
|
+
}
|
|
2328
|
+
return statuses;
|
|
2329
|
+
}
|
|
2330
|
+
async function isToolInstalled(toolId) {
|
|
2331
|
+
return await getToolPath(toolId) !== void 0;
|
|
2332
|
+
}
|
|
2333
|
+
async function getArchiveTreeBundlePath(tool) {
|
|
2334
|
+
if (tool.installStrategy !== "archive" || tool.archive?.launcher) {
|
|
2335
|
+
return void 0;
|
|
2336
|
+
}
|
|
2337
|
+
const extractDir = path3.join(TOOLS_DIR, tool.id);
|
|
2338
|
+
const marker = tool.archive?.treeMarker ? path3.join(extractDir, ...tool.archive.treeMarker.split("/")) : extractDir;
|
|
2339
|
+
try {
|
|
2340
|
+
await fs2.access(marker);
|
|
2341
|
+
return extractDir;
|
|
2342
|
+
} catch {
|
|
2343
|
+
return void 0;
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
function resolvePlatformPackageBinary(tool) {
|
|
2347
|
+
const spec = tool.platformPackage;
|
|
2348
|
+
if (!spec || !tool.packageName)
|
|
2349
|
+
return void 0;
|
|
2350
|
+
const suffix = spec.suffixes[`${installerPlatform()}-${process.arch}`];
|
|
2351
|
+
if (!suffix)
|
|
2352
|
+
return void 0;
|
|
2353
|
+
const platformPkg = `${spec.base ?? tool.packageName}-${suffix}`;
|
|
2354
|
+
try {
|
|
2355
|
+
const mainPkgJson = _installerRequire.resolve(`${tool.packageName}/package.json`);
|
|
2356
|
+
const fromMain = createRequire(mainPkgJson);
|
|
2357
|
+
let pkgDir;
|
|
2358
|
+
try {
|
|
2359
|
+
pkgDir = path3.dirname(fromMain.resolve(`${platformPkg}/package.json`));
|
|
2360
|
+
} catch {
|
|
2361
|
+
pkgDir = path3.dirname(_installerRequire.resolve(`${platformPkg}/package.json`));
|
|
2362
|
+
}
|
|
2363
|
+
const isWin = installerPlatform() === "win32";
|
|
2364
|
+
for (const bin of spec.binaries) {
|
|
2365
|
+
for (const name of isWin ? [`${bin}.exe`, bin] : [bin]) {
|
|
2366
|
+
const candidate = path3.join(pkgDir, name);
|
|
2367
|
+
if (existsSync(candidate))
|
|
2368
|
+
return candidate;
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
} catch {
|
|
2372
|
+
}
|
|
2373
|
+
return void 0;
|
|
2374
|
+
}
|
|
2375
|
+
var lastResolveTransient = /* @__PURE__ */ new Map();
|
|
2376
|
+
var lastInstallResolutionId = /* @__PURE__ */ new Map();
|
|
2377
|
+
function wasLastResolveTransient(toolId) {
|
|
2378
|
+
return lastResolveTransient.get(toolId) ?? false;
|
|
2379
|
+
}
|
|
2380
|
+
async function getToolPath(toolId) {
|
|
2381
|
+
let sawTransient = false;
|
|
2382
|
+
const markTransient = () => {
|
|
2383
|
+
sawTransient = true;
|
|
2384
|
+
};
|
|
2385
|
+
const result = await getToolPathResolved(toolId, markTransient);
|
|
2386
|
+
lastResolveTransient.set(toolId, sawTransient);
|
|
2387
|
+
return result;
|
|
2388
|
+
}
|
|
2389
|
+
async function getToolPathResolved(toolId, onTransient) {
|
|
2390
|
+
const tool = TOOLS.find((t) => t.id === toolId);
|
|
2391
|
+
if (!tool)
|
|
2392
|
+
return void 0;
|
|
2393
|
+
if (tool.installStrategy === "archive" && !tool.archive?.launcher) {
|
|
2394
|
+
return getArchiveTreeBundlePath(tool);
|
|
2395
|
+
}
|
|
2396
|
+
const pinnedVersion = tool.installStrategy === "npm" && tool.packageName ? parsePinnedVersion(tool.packageName) : void 0;
|
|
2397
|
+
if (pinnedVersion)
|
|
2398
|
+
lastManagedInstallVersion.delete(toolId);
|
|
2399
|
+
const recordVersion = pinnedVersion ? (output) => {
|
|
2400
|
+
const seen = extractVersionToken(output);
|
|
2401
|
+
if (seen)
|
|
2402
|
+
lastManagedInstallVersion.set(toolId, seen);
|
|
2403
|
+
} : void 0;
|
|
2404
|
+
const localBase = path3.join(TOOLS_DIR, "node_modules", ".bin", tool.binaryName || tool.id);
|
|
2405
|
+
if (installerPlatform() === "win32") {
|
|
2406
|
+
const cmdPath = `${localBase}.cmd`;
|
|
2407
|
+
try {
|
|
2408
|
+
await fs2.access(cmdPath);
|
|
2409
|
+
if (await verifyToolBinary(cmdPath, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
|
|
2410
|
+
return cmdPath;
|
|
2411
|
+
}
|
|
2412
|
+
logSessionStart(`auto-install verify: ${cmdPath} exists but is broken, will reinstall`);
|
|
2413
|
+
} catch {
|
|
2414
|
+
}
|
|
2415
|
+
const exePath = `${localBase}.exe`;
|
|
2416
|
+
try {
|
|
2417
|
+
await fs2.access(exePath);
|
|
2418
|
+
if (await verifyToolBinary(exePath, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
|
|
2419
|
+
return exePath;
|
|
2420
|
+
}
|
|
2421
|
+
logSessionStart(`auto-install verify: ${exePath} exists but is broken, will reinstall`);
|
|
2422
|
+
} catch {
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
if (installerPlatform() !== "win32") {
|
|
2426
|
+
try {
|
|
2427
|
+
await fs2.access(localBase);
|
|
2428
|
+
if (await verifyToolBinary(localBase, recordVersion, onTransient, getToolVerificationTimeout(tool), tool.checkArgs, packageEntryVerification(tool))) {
|
|
2429
|
+
return localBase;
|
|
2430
|
+
}
|
|
2431
|
+
logSessionStart(`auto-install verify: ${localBase} exists but is broken, will reinstall`);
|
|
2432
|
+
} catch {
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
if (tool.platformPackage) {
|
|
2436
|
+
const platformBin = resolvePlatformPackageBinary(tool);
|
|
2437
|
+
if (platformBin && await verifyToolBinary(platformBin, void 0, onTransient, getToolVerificationTimeout(tool), tool.checkArgs)) {
|
|
2438
|
+
logSessionStart(`auto-install ${toolId}: resolved platform-package binary at ${platformBin}`);
|
|
2439
|
+
return platformBin;
|
|
2440
|
+
}
|
|
2441
|
+
logSessionStart(`auto-install ${toolId}: platform-package binary not resolved (${process.platform}-${process.arch}, base=${tool.platformPackage.base ?? tool.packageName}) \u2014 falling back to PATH/managed install`);
|
|
2442
|
+
}
|
|
2443
|
+
if (tool.installStrategy === "github" || tool.installStrategy === "maven" || tool.installStrategy === "archive") {
|
|
2444
|
+
const githubPath = await findGitHubToolPath(tool.binaryName || tool.id);
|
|
2445
|
+
if (githubPath)
|
|
2446
|
+
return githubPath;
|
|
2447
|
+
}
|
|
2448
|
+
if (await isCommandAvailable(tool.checkCommand)) {
|
|
2449
|
+
if (packageEntryVerification(tool) !== void 0)
|
|
2450
|
+
return tool.checkCommand;
|
|
2451
|
+
if (tool.verification === "tree-manifest")
|
|
2452
|
+
return tool.checkCommand;
|
|
2453
|
+
let probeStalled = false;
|
|
2454
|
+
const verified = await verifyToolBinary(tool.checkCommand, void 0, () => {
|
|
2455
|
+
probeStalled = true;
|
|
2456
|
+
onTransient();
|
|
2457
|
+
}, getToolVerificationTimeout(tool), tool.checkArgs, void 0, () => {
|
|
2458
|
+
probeStalled = true;
|
|
2459
|
+
});
|
|
2460
|
+
if (verified || probeStalled)
|
|
2461
|
+
return tool.checkCommand;
|
|
2462
|
+
recordDegradationOnce({
|
|
2463
|
+
kind: "installer-path-candidate-unrunnable",
|
|
2464
|
+
subject: toolId,
|
|
2465
|
+
reason: `${tool.checkCommand} on PATH failed its own check (${tool.checkArgs.join(" ")}); ignoring PATH for this tool`
|
|
2466
|
+
});
|
|
2467
|
+
logSessionStart(`auto-install ${toolId}: PATH candidate ${tool.checkCommand} failed ${tool.checkArgs.join(" ")} \u2014 ignoring PATH, trying managed install`);
|
|
2468
|
+
}
|
|
2469
|
+
if (tool.installStrategy === "npm") {
|
|
2470
|
+
const npmPath = await findNpmGlobalToolPath(tool.binaryName || tool.id, onTransient, tool.checkArgs, getToolVerificationTimeout(tool));
|
|
2471
|
+
if (npmPath) {
|
|
2472
|
+
return npmPath;
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
if (tool.installStrategy === "pip") {
|
|
2476
|
+
const pipPath = await findPipUserToolPath(tool.binaryName || tool.id, onTransient, tool.checkArgs, getToolVerificationTimeout(tool));
|
|
2477
|
+
if (pipPath) {
|
|
2478
|
+
return pipPath;
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
return void 0;
|
|
2482
|
+
}
|
|
2483
|
+
async function findGitHubToolPath(binaryName) {
|
|
2484
|
+
const isWindows = process.platform === "win32";
|
|
2485
|
+
const candidates = isWindows ? [
|
|
2486
|
+
path3.join(GITHUB_BIN_DIR, `${binaryName}.exe`),
|
|
2487
|
+
path3.join(GITHUB_BIN_DIR, `${binaryName}.bat`),
|
|
2488
|
+
path3.join(GITHUB_BIN_DIR, `${binaryName}.cmd`),
|
|
2489
|
+
path3.join(GITHUB_BIN_DIR, binaryName)
|
|
2490
|
+
] : [path3.join(GITHUB_BIN_DIR, binaryName)];
|
|
2491
|
+
for (const candidate of candidates) {
|
|
2492
|
+
try {
|
|
2493
|
+
await fs2.access(candidate);
|
|
2494
|
+
return candidate;
|
|
2495
|
+
} catch {
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
return void 0;
|
|
2499
|
+
}
|
|
2500
|
+
async function findManagedToolBinary(toolId) {
|
|
2501
|
+
const tool = TOOLS.find((candidate) => candidate.id === toolId);
|
|
2502
|
+
if (!tool)
|
|
2503
|
+
return void 0;
|
|
2504
|
+
if (tool.installStrategy !== "github" && tool.installStrategy !== "maven" && tool.installStrategy !== "archive") {
|
|
2505
|
+
return void 0;
|
|
2506
|
+
}
|
|
2507
|
+
return findGitHubToolPath(tool.binaryName || tool.id);
|
|
2508
|
+
}
|
|
2509
|
+
function hasExecutableExtension(name) {
|
|
2510
|
+
return /\.(exe|bat|cmd|ps1)$/i.test(name);
|
|
2511
|
+
}
|
|
2512
|
+
function getGitHubInstalledBinaryName(binaryName, platform, assetName) {
|
|
2513
|
+
if (platform !== "win32")
|
|
2514
|
+
return binaryName;
|
|
2515
|
+
if (hasExecutableExtension(binaryName))
|
|
2516
|
+
return binaryName;
|
|
2517
|
+
if (assetName.endsWith(".bat"))
|
|
2518
|
+
return `${binaryName}.bat`;
|
|
2519
|
+
if (assetName.endsWith(".cmd"))
|
|
2520
|
+
return `${binaryName}.cmd`;
|
|
2521
|
+
return `${binaryName}.exe`;
|
|
2522
|
+
}
|
|
2523
|
+
function launcherForGitHubAsset(spec, assetName) {
|
|
2524
|
+
if (spec.launcher === "php" && assetName.endsWith(".phar"))
|
|
2525
|
+
return "php";
|
|
2526
|
+
if (spec.launcher === "java" && assetName.endsWith(".jar"))
|
|
2527
|
+
return "java";
|
|
2528
|
+
return void 0;
|
|
2529
|
+
}
|
|
2530
|
+
function getArchiveBinaryCandidates(binaryName, platform, assetName) {
|
|
2531
|
+
if (platform !== "win32")
|
|
2532
|
+
return [binaryName];
|
|
2533
|
+
if (hasExecutableExtension(binaryName))
|
|
2534
|
+
return [binaryName];
|
|
2535
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
2536
|
+
if (assetName.endsWith(".bat"))
|
|
2537
|
+
candidates.add(`${binaryName}.bat`);
|
|
2538
|
+
if (assetName.endsWith(".cmd"))
|
|
2539
|
+
candidates.add(`${binaryName}.cmd`);
|
|
2540
|
+
candidates.add(`${binaryName}.exe`);
|
|
2541
|
+
candidates.add(binaryName);
|
|
2542
|
+
candidates.add(`${binaryName}.bat`);
|
|
2543
|
+
candidates.add(`${binaryName}.cmd`);
|
|
2544
|
+
return [...candidates];
|
|
2545
|
+
}
|
|
2546
|
+
async function findNpmGlobalToolPath(binaryName, onTransient, verificationArgs = ["--version"], verificationTimeoutMs = 1e4) {
|
|
2547
|
+
const isWindows = process.platform === "win32";
|
|
2548
|
+
const binDirs = await getNpmGlobalBinCandidates(onTransient);
|
|
2549
|
+
for (const dir of binDirs) {
|
|
2550
|
+
const candidates = isWindows ? [
|
|
2551
|
+
path3.join(dir, `${binaryName}.cmd`),
|
|
2552
|
+
path3.join(dir, `${binaryName}.exe`)
|
|
2553
|
+
] : [path3.join(dir, binaryName)];
|
|
2554
|
+
for (const candidate of candidates) {
|
|
2555
|
+
try {
|
|
2556
|
+
await fs2.access(candidate);
|
|
2557
|
+
if (await verifyToolBinary(candidate, void 0, onTransient, verificationTimeoutMs, verificationArgs)) {
|
|
2558
|
+
return candidate;
|
|
2559
|
+
}
|
|
2560
|
+
} catch {
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
return void 0;
|
|
2565
|
+
}
|
|
2566
|
+
async function getNpmGlobalBinCandidates(onTransient) {
|
|
2567
|
+
const dirs = [];
|
|
2568
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2569
|
+
const add = (value) => {
|
|
2570
|
+
if (!value)
|
|
2571
|
+
return;
|
|
2572
|
+
const normalized = path3.resolve(value.trim());
|
|
2573
|
+
if (!normalized)
|
|
2574
|
+
return;
|
|
2575
|
+
if (seen.has(normalized))
|
|
2576
|
+
return;
|
|
2577
|
+
seen.add(normalized);
|
|
2578
|
+
dirs.push(normalized);
|
|
2579
|
+
};
|
|
2580
|
+
if (process.platform === "win32") {
|
|
2581
|
+
add(path3.join(process.env.APPDATA || "", "npm"));
|
|
2582
|
+
} else {
|
|
2583
|
+
add(path3.join(os.homedir(), ".npm-global", "bin"));
|
|
2584
|
+
}
|
|
2585
|
+
for (const dir of await allAvailableGlobalBinDirs(onTransient)) {
|
|
2586
|
+
add(dir);
|
|
2587
|
+
}
|
|
2588
|
+
return dirs;
|
|
2589
|
+
}
|
|
2590
|
+
async function findPipUserToolPath(binaryName, onTransient, verificationArgs = ["--version"], verificationTimeoutMs = 1e4) {
|
|
2591
|
+
const isWindows = installerPlatform() === "win32";
|
|
2592
|
+
const userBaseCandidates = [
|
|
2593
|
+
path3.join(getGlobalPiLensDir(), "pip-tools"),
|
|
2594
|
+
path3.join(getGlobalPiLensDir(), "pip-user"),
|
|
2595
|
+
...await getPythonUserBaseCandidates()
|
|
2596
|
+
];
|
|
2597
|
+
for (const userBase of userBaseCandidates) {
|
|
2598
|
+
const scriptDirs = [pipScriptsDir(userBase, installerPlatform())];
|
|
2599
|
+
if (isWindows) {
|
|
2600
|
+
try {
|
|
2601
|
+
const children = await fs2.readdir(userBase, { withFileTypes: true });
|
|
2602
|
+
for (const entry of children) {
|
|
2603
|
+
if (!entry.isDirectory())
|
|
2604
|
+
continue;
|
|
2605
|
+
if (!/^python\d+$/i.test(entry.name))
|
|
2606
|
+
continue;
|
|
2607
|
+
scriptDirs.push(path3.join(userBase, entry.name, "Scripts"));
|
|
2608
|
+
}
|
|
2609
|
+
} catch {
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
for (const dir of scriptDirs) {
|
|
2613
|
+
const candidates = isWindows ? [
|
|
2614
|
+
path3.join(dir, `${binaryName}.exe`),
|
|
2615
|
+
path3.join(dir, `${binaryName}.cmd`),
|
|
2616
|
+
path3.join(dir, binaryName)
|
|
2617
|
+
] : [path3.join(dir, binaryName)];
|
|
2618
|
+
for (const candidate of candidates) {
|
|
2619
|
+
try {
|
|
2620
|
+
await fs2.access(candidate);
|
|
2621
|
+
if (await verifyToolBinary(candidate, void 0, onTransient, verificationTimeoutMs, verificationArgs)) {
|
|
2622
|
+
return candidate;
|
|
2623
|
+
}
|
|
2624
|
+
} catch {
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
return void 0;
|
|
2630
|
+
}
|
|
2631
|
+
function userBaseProbeResult(command, code, stdout) {
|
|
2632
|
+
if (stdout.truncated) {
|
|
2633
|
+
recordDegradationOnce({
|
|
2634
|
+
kind: "spawn-output-cap-truncated",
|
|
2635
|
+
subject: `user-base-probe:${command}`,
|
|
2636
|
+
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`,
|
|
2637
|
+
metadata: {
|
|
2638
|
+
capBytes: DEFAULT_MAX_OUTPUT_BYTES,
|
|
2639
|
+
capSource: "default",
|
|
2640
|
+
observedBytes: stdout.observedBytes,
|
|
2641
|
+
killed: false
|
|
2642
|
+
}
|
|
2643
|
+
});
|
|
2644
|
+
return "";
|
|
2645
|
+
}
|
|
2646
|
+
return code === 0 ? stdout.text.trim() : "";
|
|
2647
|
+
}
|
|
2648
|
+
async function getPythonUserBaseCandidates() {
|
|
2649
|
+
const candidates = [];
|
|
2650
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2651
|
+
const add = (value) => {
|
|
2652
|
+
if (!value)
|
|
2653
|
+
return;
|
|
2654
|
+
const normalized = value.trim();
|
|
2655
|
+
if (!normalized)
|
|
2656
|
+
return;
|
|
2657
|
+
if (seen.has(normalized))
|
|
2658
|
+
return;
|
|
2659
|
+
seen.add(normalized);
|
|
2660
|
+
candidates.push(normalized);
|
|
2661
|
+
};
|
|
2662
|
+
if (process.platform === "win32") {
|
|
2663
|
+
add(path3.join(process.env.APPDATA || "", "Python"));
|
|
2664
|
+
}
|
|
2665
|
+
const probes = process.platform === "win32" ? [
|
|
2666
|
+
{ command: "py", args: ["-m", "site", "--user-base"] },
|
|
2667
|
+
{ command: "python", args: ["-m", "site", "--user-base"] }
|
|
2668
|
+
] : [
|
|
2669
|
+
{ command: "python3", args: ["-m", "site", "--user-base"] },
|
|
2670
|
+
{ command: "python", args: ["-m", "site", "--user-base"] }
|
|
2671
|
+
];
|
|
2672
|
+
for (const probe of probes) {
|
|
2673
|
+
const userBase = await new Promise((resolve) => {
|
|
2674
|
+
const isWin = process.platform === "win32";
|
|
2675
|
+
const spawnCmd = isWin ? [probe.command, ...probe.args].join(" ") : probe.command;
|
|
2676
|
+
let proc;
|
|
2677
|
+
try {
|
|
2678
|
+
proc = spawn(spawnCmd, isWin ? [] : probe.args, {
|
|
2679
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2680
|
+
shell: isWin
|
|
2681
|
+
});
|
|
2682
|
+
} catch {
|
|
2683
|
+
resolve("");
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2686
|
+
const stdout = createBoundedOutputSink();
|
|
2687
|
+
proc.stdout?.on("data", (data) => stdout.append(data));
|
|
2688
|
+
proc.on("exit", (code) => resolve(userBaseProbeResult(probe.command, code, stdout)));
|
|
2689
|
+
proc.on("error", () => resolve(""));
|
|
2690
|
+
});
|
|
2691
|
+
add(userBase);
|
|
2692
|
+
}
|
|
2693
|
+
return candidates;
|
|
2694
|
+
}
|
|
2695
|
+
async function githubApiAuthHeaders() {
|
|
2696
|
+
const token = await resolveGitHubToken();
|
|
2697
|
+
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
2698
|
+
}
|
|
2699
|
+
var HttpStatusError = class extends Error {
|
|
2700
|
+
statusCode;
|
|
2701
|
+
constructor(statusCode, url) {
|
|
2702
|
+
super(url ? `HTTP ${statusCode} for ${url}` : `HTTP ${statusCode}`);
|
|
2703
|
+
this.name = "HttpStatusError";
|
|
2704
|
+
this.statusCode = statusCode;
|
|
2705
|
+
}
|
|
2706
|
+
};
|
|
2707
|
+
var GitHubHttpError = class extends HttpStatusError {
|
|
2708
|
+
anonymousRateLimitExhausted;
|
|
2709
|
+
constructor(statusCode, requestHeaders, responseHeaders) {
|
|
2710
|
+
super(statusCode);
|
|
2711
|
+
this.name = "GitHubHttpError";
|
|
2712
|
+
this.message = `GitHub API HTTP ${statusCode}`;
|
|
2713
|
+
this.anonymousRateLimitExhausted = statusCode === 403 && !Object.keys(requestHeaders).some((key) => key.toLowerCase() === "authorization") && readHeader(responseHeaders, "x-ratelimit-remaining") === "0";
|
|
2714
|
+
}
|
|
2715
|
+
};
|
|
2716
|
+
function readHeader(headers, name) {
|
|
2717
|
+
const entry = Object.entries(headers).find(([key]) => key.toLowerCase() === name.toLowerCase())?.[1];
|
|
2718
|
+
return Array.isArray(entry) ? entry[0]?.trim() : entry?.trim();
|
|
2719
|
+
}
|
|
2720
|
+
function isGitHubApiUrl(url) {
|
|
2721
|
+
try {
|
|
2722
|
+
return new URL(url).host.toLowerCase() === "api.github.com";
|
|
2723
|
+
} catch {
|
|
2724
|
+
return false;
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
function sameHost(a, b) {
|
|
2728
|
+
try {
|
|
2729
|
+
return new URL(a).host === new URL(b).host;
|
|
2730
|
+
} catch {
|
|
2731
|
+
return false;
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
function httpsGetWithMeta(url, maxRedirects = 5, headers = {}) {
|
|
2735
|
+
return new Promise((resolve, reject) => {
|
|
2736
|
+
https.get(url, { headers: { "User-Agent": "pi-lens/1.0", ...headers } }, (res) => {
|
|
2737
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
2738
|
+
if (maxRedirects === 0)
|
|
2739
|
+
return reject(new Error("Too many redirects"));
|
|
2740
|
+
const location = res.headers.location;
|
|
2741
|
+
const nextHeaders = sameHost(url, location) ? headers : (() => {
|
|
2742
|
+
const { Authorization: _drop, ...rest } = headers;
|
|
2743
|
+
return rest;
|
|
2744
|
+
})();
|
|
2745
|
+
return resolve(httpsGetWithMeta(location, maxRedirects - 1, nextHeaders));
|
|
2746
|
+
}
|
|
2747
|
+
const etag = typeof res.headers.etag === "string" ? res.headers.etag : void 0;
|
|
2748
|
+
if (res.statusCode === 304) {
|
|
2749
|
+
res.resume();
|
|
2750
|
+
return resolve({ statusCode: 304, body: Buffer.alloc(0), etag });
|
|
2751
|
+
}
|
|
2752
|
+
if (res.statusCode !== 200) {
|
|
2753
|
+
res.resume();
|
|
2754
|
+
return reject(isGitHubApiUrl(url) ? new GitHubHttpError(res.statusCode ?? 0, headers, res.headers) : new HttpStatusError(res.statusCode ?? 0, url));
|
|
2755
|
+
}
|
|
2756
|
+
const chunks = [];
|
|
2757
|
+
res.on("data", (chunk) => chunks.push(chunk));
|
|
2758
|
+
res.on("end", () => resolve({ statusCode: 200, body: Buffer.concat(chunks), etag }));
|
|
2759
|
+
res.on("error", reject);
|
|
2760
|
+
}).on("error", reject);
|
|
2761
|
+
});
|
|
2762
|
+
}
|
|
2763
|
+
async function httpsGet(url, maxRedirects = 5, headers = {}) {
|
|
2764
|
+
const response = await httpsGetWithMeta(url, maxRedirects, headers);
|
|
2765
|
+
if (response.statusCode !== 200) {
|
|
2766
|
+
throw new Error(`HTTP ${response.statusCode} for ${url}`);
|
|
2767
|
+
}
|
|
2768
|
+
return response.body;
|
|
2769
|
+
}
|
|
2770
|
+
function runCommand(command, args, cwd) {
|
|
2771
|
+
return new Promise((resolve) => {
|
|
2772
|
+
let proc;
|
|
2773
|
+
try {
|
|
2774
|
+
proc = spawn(command, args, {
|
|
2775
|
+
cwd,
|
|
2776
|
+
stdio: "ignore",
|
|
2777
|
+
shell: process.platform === "win32"
|
|
2778
|
+
});
|
|
2779
|
+
} catch {
|
|
2780
|
+
resolve(false);
|
|
2781
|
+
return;
|
|
2782
|
+
}
|
|
2783
|
+
proc.on("exit", (code) => resolve(code === 0));
|
|
2784
|
+
proc.on("error", () => resolve(false));
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
async function installGitHubTool(tool, prefetchedRelease) {
|
|
2788
|
+
const spec = tool.github;
|
|
2789
|
+
if (!spec)
|
|
2790
|
+
return void 0;
|
|
2791
|
+
const platform = process.platform;
|
|
2792
|
+
const arch = process.arch;
|
|
2793
|
+
const assetSubstring = spec.assetMatch(platform, arch);
|
|
2794
|
+
if (!assetSubstring) {
|
|
2795
|
+
logSessionStart(`github-install ${tool.id}: unsupported platform=${platform} arch=${arch}`);
|
|
2796
|
+
return void 0;
|
|
2797
|
+
}
|
|
2798
|
+
let releaseJson;
|
|
2799
|
+
if (prefetchedRelease) {
|
|
2800
|
+
releaseJson = prefetchedRelease;
|
|
2801
|
+
logSessionStart(`github-install ${tool.id}: using caller-supplied release metadata for ${spec.repo} (${prefetchedRelease.tag_name ?? "untagged"})`);
|
|
2802
|
+
} else {
|
|
2803
|
+
logSessionStart(`github-install ${tool.id}: fetching release metadata from ${spec.repo}`);
|
|
2804
|
+
try {
|
|
2805
|
+
const body = await httpsGet(`https://api.github.com/repos/${spec.repo}/releases/latest`, 5, await githubApiAuthHeaders());
|
|
2806
|
+
releaseJson = JSON.parse(body.toString("utf8"));
|
|
2807
|
+
} catch (err) {
|
|
2808
|
+
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}`;
|
|
2809
|
+
if (err instanceof GitHubHttpError && err.anonymousRateLimitExhausted) {
|
|
2810
|
+
recordDegradationOnce({
|
|
2811
|
+
kind: "github-api-rate-limit",
|
|
2812
|
+
subject: tool.id,
|
|
2813
|
+
reason
|
|
2814
|
+
});
|
|
2815
|
+
}
|
|
2816
|
+
logSessionStart(`github-install ${tool.id}: ${reason}`);
|
|
2817
|
+
return void 0;
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
if (releaseJson.tag_name) {
|
|
2821
|
+
lastInstallResolutionId.set(tool.id, releaseJson.tag_name);
|
|
2822
|
+
}
|
|
2823
|
+
if (!Array.isArray(releaseJson.assets)) {
|
|
2824
|
+
logSessionStart(`github-install ${tool.id}: release metadata has no assets array`);
|
|
2825
|
+
return void 0;
|
|
2826
|
+
}
|
|
2827
|
+
const asset = pickReleaseAsset(releaseJson.assets, assetSubstring) ?? deriveHashiCorpReleaseAsset(tool, releaseJson.tag_name, assetSubstring);
|
|
2828
|
+
if (!asset) {
|
|
2829
|
+
logSessionStart(`github-install ${tool.id}: no asset matched "${assetSubstring}"`);
|
|
2830
|
+
return void 0;
|
|
2831
|
+
}
|
|
2832
|
+
logSessionStart(`github-install ${tool.id}: downloading ${asset.name}`);
|
|
2833
|
+
debugLog(`[github] downloading ${asset.name} from ${asset.browser_download_url}`);
|
|
2834
|
+
const downloadStart = Date.now();
|
|
2835
|
+
let assetBuffer;
|
|
2836
|
+
try {
|
|
2837
|
+
assetBuffer = await httpsGet(asset.browser_download_url);
|
|
2838
|
+
logSessionStart(`github-install ${tool.id}: downloaded ${asset.name} (${assetBuffer.length} bytes, ${Date.now() - downloadStart}ms)`);
|
|
2839
|
+
} catch (err) {
|
|
2840
|
+
logSessionStart(`github-install ${tool.id}: download failed: ${err.message}`);
|
|
2841
|
+
return void 0;
|
|
2842
|
+
}
|
|
2843
|
+
await fs2.mkdir(GITHUB_BIN_DIR, { recursive: true });
|
|
2844
|
+
const binaryName = tool.binaryName ?? tool.id;
|
|
2845
|
+
const isWindows = platform === "win32";
|
|
2846
|
+
const finalBinaryName = getGitHubInstalledBinaryName(binaryName, platform, asset.name);
|
|
2847
|
+
let destPath = path3.join(GITHUB_BIN_DIR, finalBinaryName);
|
|
2848
|
+
const assetName = asset.name;
|
|
2849
|
+
const launcherRuntime = launcherForGitHubAsset(spec, assetName);
|
|
2850
|
+
if (launcherRuntime && !await isCommandAvailable(launcherRuntime, ["--version"])) {
|
|
2851
|
+
logSessionStart(`github-install ${tool.id}: ${launcherRuntime} not found \u2014 asset requires a runtime launcher`);
|
|
2852
|
+
return void 0;
|
|
2853
|
+
}
|
|
2854
|
+
try {
|
|
2855
|
+
if (launcherRuntime) {
|
|
2856
|
+
const assetPath = path3.join(GITHUB_BIN_DIR, `${tool.id}${assetName.endsWith(".phar") ? ".phar" : ".jar"}`);
|
|
2857
|
+
await writeFileAtomicAsync(assetPath, assetBuffer, {
|
|
2858
|
+
bestEffort: false,
|
|
2859
|
+
mode: 488
|
|
2860
|
+
});
|
|
2861
|
+
const launcherName = isWindows ? `${binaryName}.bat` : binaryName;
|
|
2862
|
+
destPath = path3.join(GITHUB_BIN_DIR, launcherName);
|
|
2863
|
+
const runtimeTarget = assetName.endsWith(".phar") ? `${tool.id}.phar` : `${tool.id}.jar`;
|
|
2864
|
+
const command = isWindows ? `@echo off\r
|
|
2865
|
+
${launcherRuntime} "%~dp0${runtimeTarget}" %*\r
|
|
2866
|
+
` : `#!/bin/sh
|
|
2867
|
+
exec ${launcherRuntime} "$(dirname "$0")/${runtimeTarget}" "$@"
|
|
2868
|
+
`;
|
|
2869
|
+
await writeFileAtomicAsync(destPath, command, {
|
|
2870
|
+
bestEffort: false,
|
|
2871
|
+
mode: isWindows ? void 0 : 488
|
|
2872
|
+
});
|
|
2873
|
+
} else if (assetName.endsWith(".gz") && !assetName.endsWith(".tar.gz")) {
|
|
2874
|
+
const decompressed = await new Promise((resolve, reject) => {
|
|
2875
|
+
const gunzip = createGunzip();
|
|
2876
|
+
const chunks = [];
|
|
2877
|
+
gunzip.on("data", (chunk) => chunks.push(chunk));
|
|
2878
|
+
gunzip.on("end", () => resolve(Buffer.concat(chunks)));
|
|
2879
|
+
gunzip.on("error", reject);
|
|
2880
|
+
gunzip.end(assetBuffer);
|
|
2881
|
+
});
|
|
2882
|
+
await writeFileAtomicAsync(destPath, decompressed, {
|
|
2883
|
+
bestEffort: false,
|
|
2884
|
+
mode: 488
|
|
2885
|
+
});
|
|
2886
|
+
} else if (assetName.endsWith(".tar.gz") || assetName.endsWith(".tar.xz")) {
|
|
2887
|
+
const tmpArchive = path3.join(GITHUB_BIN_DIR, `_tmp_${assetName}`);
|
|
2888
|
+
await fs2.writeFile(tmpArchive, assetBuffer);
|
|
2889
|
+
const tmpDir = path3.join(GITHUB_BIN_DIR, `_tmp_extract_${tool.id}`);
|
|
2890
|
+
await fs2.mkdir(tmpDir, { recursive: true });
|
|
2891
|
+
const extracted = await runCommand("tar", ["xf", tmpArchive, "-C", tmpDir], GITHUB_BIN_DIR);
|
|
2892
|
+
await fs2.rm(tmpArchive, { force: true });
|
|
2893
|
+
if (!extracted) {
|
|
2894
|
+
await fs2.rm(tmpDir, { recursive: true, force: true });
|
|
2895
|
+
logSessionStart(`github-install ${tool.id}: tar extraction failed for ${assetName}`);
|
|
2896
|
+
return void 0;
|
|
2897
|
+
}
|
|
2898
|
+
const tarBinaryName = spec.binaryInArchive ?? binaryName;
|
|
2899
|
+
const tarSrcBinary = await findFirstFileRecursive(tmpDir, getArchiveBinaryCandidates(tarBinaryName, platform, assetName));
|
|
2900
|
+
if (!tarSrcBinary) {
|
|
2901
|
+
await fs2.rm(tmpDir, { recursive: true, force: true });
|
|
2902
|
+
logSessionStart(`github-install ${tool.id}: binary candidates ${JSON.stringify(getArchiveBinaryCandidates(tarBinaryName, platform, assetName))} not found in tar ${assetName}`);
|
|
2903
|
+
return void 0;
|
|
2904
|
+
}
|
|
2905
|
+
await fs2.rename(tarSrcBinary, destPath);
|
|
2906
|
+
await fs2.rm(tmpDir, { recursive: true, force: true });
|
|
2907
|
+
if (!isWindows)
|
|
2908
|
+
await fs2.chmod(destPath, 488);
|
|
2909
|
+
} else if (assetName.endsWith(".zip")) {
|
|
2910
|
+
const tmpArchive = path3.join(GITHUB_BIN_DIR, `_tmp_${assetName}`);
|
|
2911
|
+
await fs2.writeFile(tmpArchive, assetBuffer);
|
|
2912
|
+
const tmpDir = path3.join(GITHUB_BIN_DIR, `_tmp_extract_${tool.id}`);
|
|
2913
|
+
await fs2.mkdir(tmpDir, { recursive: true });
|
|
2914
|
+
const extracted = isWindows ? await runCommand("powershell", [
|
|
2915
|
+
"-NoProfile",
|
|
2916
|
+
"-Command",
|
|
2917
|
+
`Expand-Archive -LiteralPath '${tmpArchive}' -DestinationPath '${tmpDir}' -Force`
|
|
2918
|
+
], GITHUB_BIN_DIR) : await runCommand("unzip", ["-q", "-o", tmpArchive, "-d", tmpDir], GITHUB_BIN_DIR);
|
|
2919
|
+
await fs2.rm(tmpArchive, { force: true });
|
|
2920
|
+
if (!extracted) {
|
|
2921
|
+
await fs2.rm(tmpDir, { recursive: true, force: true });
|
|
2922
|
+
logSessionStart(`github-install ${tool.id}: zip extraction failed for ${assetName}`);
|
|
2923
|
+
return void 0;
|
|
2924
|
+
}
|
|
2925
|
+
const archiveBinaryName = spec.binaryInArchive ?? binaryName;
|
|
2926
|
+
const srcBinary = await findFirstFileRecursive(tmpDir, getArchiveBinaryCandidates(archiveBinaryName, platform, assetName));
|
|
2927
|
+
if (!srcBinary) {
|
|
2928
|
+
await fs2.rm(tmpDir, { recursive: true, force: true });
|
|
2929
|
+
logSessionStart(`github-install ${tool.id}: binary candidates ${JSON.stringify(getArchiveBinaryCandidates(archiveBinaryName, platform, assetName))} not found in zip ${assetName}`);
|
|
2930
|
+
return void 0;
|
|
2931
|
+
}
|
|
2932
|
+
await fs2.rename(srcBinary, destPath);
|
|
2933
|
+
await fs2.rm(tmpDir, { recursive: true, force: true });
|
|
2934
|
+
if (!isWindows)
|
|
2935
|
+
await fs2.chmod(destPath, 488);
|
|
2936
|
+
} else {
|
|
2937
|
+
await writeFileAtomicAsync(destPath, assetBuffer, {
|
|
2938
|
+
bestEffort: false,
|
|
2939
|
+
mode: 488
|
|
2940
|
+
});
|
|
2941
|
+
}
|
|
2942
|
+
} catch (err) {
|
|
2943
|
+
logSessionStart(`github-install ${tool.id}: install failed: ${err.message}`);
|
|
2944
|
+
return void 0;
|
|
2945
|
+
}
|
|
2946
|
+
for (const extraName of spec.extraAssets?.(platform, arch) ?? []) {
|
|
2947
|
+
const extraAsset = releaseJson.assets.find((a) => a.name === extraName);
|
|
2948
|
+
if (!extraAsset) {
|
|
2949
|
+
logSessionStart(`github-install ${tool.id}: required extra asset "${extraName}" not found`);
|
|
2950
|
+
return void 0;
|
|
2951
|
+
}
|
|
2952
|
+
try {
|
|
2953
|
+
const extraBuffer = await httpsGet(extraAsset.browser_download_url);
|
|
2954
|
+
await writeFileAtomicAsync(path3.join(GITHUB_BIN_DIR, extraName), extraBuffer, { bestEffort: false, mode: 488 });
|
|
2955
|
+
logSessionStart(`github-install ${tool.id}: installed extra asset ${extraName} (${extraBuffer.length} bytes)`);
|
|
2956
|
+
} catch (err) {
|
|
2957
|
+
logSessionStart(`github-install ${tool.id}: extra asset ${extraName} download failed: ${err.message}`);
|
|
2958
|
+
return void 0;
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
debugLog(`[github] installed ${tool.name} \u2192 ${destPath}`);
|
|
2962
|
+
logSessionStart(`github-install ${tool.id}: installed \u2192 ${destPath}`);
|
|
2963
|
+
return destPath;
|
|
2964
|
+
}
|
|
2965
|
+
async function findFirstFileRecursive(dir, names) {
|
|
2966
|
+
const wanted = new Set(names.map((name) => name.toLowerCase()));
|
|
2967
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
2968
|
+
for (const entry of entries) {
|
|
2969
|
+
const full = path3.join(dir, entry.name);
|
|
2970
|
+
if (entry.isDirectory()) {
|
|
2971
|
+
const found = await findFirstFileRecursive(full, names);
|
|
2972
|
+
if (found)
|
|
2973
|
+
return found;
|
|
2974
|
+
} else if (wanted.has(entry.name.toLowerCase())) {
|
|
2975
|
+
return full;
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
return void 0;
|
|
2979
|
+
}
|
|
2980
|
+
var NEEDS_POSTINSTALL = /* @__PURE__ */ new Set([
|
|
2981
|
+
"@biomejs/biome",
|
|
2982
|
+
"@ast-grep/cli",
|
|
2983
|
+
// postinstall copies platform binary (ast-grep.exe/sg.exe) into place
|
|
2984
|
+
"@ast-grep/napi",
|
|
2985
|
+
"esbuild",
|
|
2986
|
+
"intelephense"
|
|
2987
|
+
// postinstall fetches platform binary; --ignore-scripts breaks install
|
|
2988
|
+
]);
|
|
2989
|
+
function npmToolNeedsPostinstall(packageName) {
|
|
2990
|
+
return NEEDS_POSTINSTALL.has(packageName);
|
|
2991
|
+
}
|
|
2992
|
+
function getRefreshableManagedNpmTools() {
|
|
2993
|
+
const seenPackages = /* @__PURE__ */ new Set();
|
|
2994
|
+
return getRefreshableManagedTools().filter((candidate) => candidate.strategy === "npm").map((candidate) => ({
|
|
2995
|
+
toolId: candidate.toolId,
|
|
2996
|
+
// The npm branch of getRefreshableManagedTools only admits entries that
|
|
2997
|
+
// have both, so these are total.
|
|
2998
|
+
packageName: candidate.packageName,
|
|
2999
|
+
binaryName: candidate.binaryName
|
|
3000
|
+
})).filter((candidate) => {
|
|
3001
|
+
if (seenPackages.has(candidate.packageName))
|
|
3002
|
+
return false;
|
|
3003
|
+
seenPackages.add(candidate.packageName);
|
|
3004
|
+
return true;
|
|
3005
|
+
});
|
|
3006
|
+
}
|
|
3007
|
+
function mavenCoordinate(spec) {
|
|
3008
|
+
return [
|
|
3009
|
+
spec.repoBaseUrl ?? MAVEN_CENTRAL_BASE,
|
|
3010
|
+
spec.groupId,
|
|
3011
|
+
spec.artifactId,
|
|
3012
|
+
spec.version,
|
|
3013
|
+
spec.classifier ?? ""
|
|
3014
|
+
].join(":");
|
|
3015
|
+
}
|
|
3016
|
+
function getRefreshableManagedTools() {
|
|
3017
|
+
const refreshable = [];
|
|
3018
|
+
for (const tool of TOOLS) {
|
|
3019
|
+
switch (tool.installStrategy) {
|
|
3020
|
+
case "npm": {
|
|
3021
|
+
if (!tool.packageName)
|
|
3022
|
+
continue;
|
|
3023
|
+
if (parsePinnedVersion(tool.packageName) !== void 0)
|
|
3024
|
+
continue;
|
|
3025
|
+
if (!tool.binaryName)
|
|
3026
|
+
continue;
|
|
3027
|
+
refreshable.push({
|
|
3028
|
+
toolId: tool.id,
|
|
3029
|
+
strategy: "npm",
|
|
3030
|
+
checkArgs: tool.checkArgs,
|
|
3031
|
+
packageName: tool.packageName,
|
|
3032
|
+
binaryName: tool.binaryName,
|
|
3033
|
+
verificationTimeoutMs: tool.verificationTimeoutMs,
|
|
3034
|
+
packageEntryOf: packageEntryVerification(tool)
|
|
3035
|
+
});
|
|
3036
|
+
break;
|
|
3037
|
+
}
|
|
3038
|
+
case "pip":
|
|
3039
|
+
case "gem": {
|
|
3040
|
+
if (!tool.packageName)
|
|
3041
|
+
continue;
|
|
3042
|
+
refreshable.push({
|
|
3043
|
+
toolId: tool.id,
|
|
3044
|
+
strategy: tool.installStrategy,
|
|
3045
|
+
checkArgs: tool.checkArgs,
|
|
3046
|
+
packageName: tool.packageName
|
|
3047
|
+
});
|
|
3048
|
+
break;
|
|
3049
|
+
}
|
|
3050
|
+
case "github": {
|
|
3051
|
+
if (!tool.github)
|
|
3052
|
+
continue;
|
|
3053
|
+
refreshable.push({
|
|
3054
|
+
toolId: tool.id,
|
|
3055
|
+
strategy: "github",
|
|
3056
|
+
checkArgs: tool.checkArgs
|
|
3057
|
+
});
|
|
3058
|
+
break;
|
|
3059
|
+
}
|
|
3060
|
+
case "maven": {
|
|
3061
|
+
if (!tool.maven)
|
|
3062
|
+
continue;
|
|
3063
|
+
refreshable.push({
|
|
3064
|
+
toolId: tool.id,
|
|
3065
|
+
strategy: "maven",
|
|
3066
|
+
checkArgs: tool.checkArgs,
|
|
3067
|
+
pinnedCoordinate: mavenCoordinate(tool.maven)
|
|
3068
|
+
});
|
|
3069
|
+
break;
|
|
3070
|
+
}
|
|
3071
|
+
case "archive": {
|
|
3072
|
+
if (!tool.archive)
|
|
3073
|
+
continue;
|
|
3074
|
+
const url = resolveArchiveUrl(tool.archive);
|
|
3075
|
+
if (!url)
|
|
3076
|
+
continue;
|
|
3077
|
+
refreshable.push({
|
|
3078
|
+
toolId: tool.id,
|
|
3079
|
+
strategy: "archive",
|
|
3080
|
+
checkArgs: tool.checkArgs,
|
|
3081
|
+
pinnedCoordinate: url
|
|
3082
|
+
});
|
|
3083
|
+
break;
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
return refreshable;
|
|
3088
|
+
}
|
|
3089
|
+
async function isManagedToolPresent(toolId) {
|
|
3090
|
+
const tool = TOOLS.find((t) => t.id === toolId);
|
|
3091
|
+
if (!tool)
|
|
3092
|
+
return false;
|
|
3093
|
+
switch (tool.installStrategy) {
|
|
3094
|
+
case "npm": {
|
|
3095
|
+
if (!tool.packageName)
|
|
3096
|
+
return false;
|
|
3097
|
+
try {
|
|
3098
|
+
await fs2.access(path3.join(TOOLS_DIR, "node_modules", tool.packageName, "package.json"));
|
|
3099
|
+
return true;
|
|
3100
|
+
} catch {
|
|
3101
|
+
return false;
|
|
3102
|
+
}
|
|
3103
|
+
}
|
|
3104
|
+
case "github":
|
|
3105
|
+
case "maven":
|
|
3106
|
+
return await findGitHubToolPath(tool.binaryName ?? tool.id) !== void 0;
|
|
3107
|
+
case "archive":
|
|
3108
|
+
return await findGitHubToolPath(tool.binaryName ?? tool.id) !== void 0 || await getArchiveTreeBundlePath(tool) !== void 0;
|
|
3109
|
+
case "pip":
|
|
3110
|
+
case "gem": {
|
|
3111
|
+
const cached = (await readProbeCache())[toolId];
|
|
3112
|
+
return cached?.path !== void 0 && existsSync(cached.path);
|
|
3113
|
+
}
|
|
3114
|
+
default:
|
|
3115
|
+
return false;
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
async function probeManagedToolVersion(tool) {
|
|
3119
|
+
const cached = (await readProbeCache())[tool.id];
|
|
3120
|
+
if (!cached?.path || !existsSync(cached.path))
|
|
3121
|
+
return void 0;
|
|
3122
|
+
try {
|
|
3123
|
+
const result = await probeToolAsync(cached.path, tool.checkArgs, {
|
|
3124
|
+
timeout: getToolVerificationTimeout(tool),
|
|
3125
|
+
input: "",
|
|
3126
|
+
ignoreAmbientSignal: true,
|
|
3127
|
+
resourceLabel: `tool-refresh-version:${tool.id}`
|
|
3128
|
+
});
|
|
3129
|
+
if (result.status !== 0)
|
|
3130
|
+
return void 0;
|
|
3131
|
+
return extractVersionToken(`${result.stdout}
|
|
3132
|
+
${result.stderr}`);
|
|
3133
|
+
} catch {
|
|
3134
|
+
return void 0;
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
async function acquireManagedInstallGate(context) {
|
|
3138
|
+
if (process.env.PI_LENS_DISABLE_TOOL_INSTALL === "1") {
|
|
3139
|
+
return {
|
|
3140
|
+
ok: false,
|
|
3141
|
+
reason: "installation disabled by PI_LENS_DISABLE_TOOL_INSTALL=1"
|
|
3142
|
+
};
|
|
3143
|
+
}
|
|
3144
|
+
if (!assertInstallAllowed(context)) {
|
|
3145
|
+
return {
|
|
3146
|
+
ok: false,
|
|
3147
|
+
reason: `project trust: ${projectTrustDenialReason()}`
|
|
3148
|
+
};
|
|
3149
|
+
}
|
|
3150
|
+
const lock = await acquireInstallLock();
|
|
3151
|
+
if (!lock.release) {
|
|
3152
|
+
return { ok: false, reason: lock.reason ?? "install lock held" };
|
|
3153
|
+
}
|
|
3154
|
+
return { ok: true, release: lock.release };
|
|
3155
|
+
}
|
|
3156
|
+
async function refreshManagedTool(toolId, known = {}) {
|
|
3157
|
+
const tool = TOOLS.find((t) => t.id === toolId);
|
|
3158
|
+
if (!tool) {
|
|
3159
|
+
return { ok: false, unchanged: true, reason: "unknown tool id" };
|
|
3160
|
+
}
|
|
3161
|
+
if (tool.installStrategy !== "github" && tool.installStrategy !== "pip" && tool.installStrategy !== "gem" && tool.installStrategy !== "maven" && tool.installStrategy !== "archive") {
|
|
3162
|
+
return {
|
|
3163
|
+
ok: false,
|
|
3164
|
+
unchanged: true,
|
|
3165
|
+
reason: `strategy ${tool.installStrategy} is not refreshable here`
|
|
3166
|
+
};
|
|
3167
|
+
}
|
|
3168
|
+
const strategy = tool.installStrategy;
|
|
3169
|
+
const gate = await acquireManagedInstallGate(`managed tool refresh: ${toolId}`);
|
|
3170
|
+
if (!gate.ok) {
|
|
3171
|
+
return { ok: false, unchanged: true, declined: true, reason: gate.reason };
|
|
3172
|
+
}
|
|
3173
|
+
try {
|
|
3174
|
+
switch (strategy) {
|
|
3175
|
+
case "github":
|
|
3176
|
+
return await refreshGitHubManagedTool(tool, known);
|
|
3177
|
+
case "pip":
|
|
3178
|
+
case "gem":
|
|
3179
|
+
return await refreshPackageManagerManagedTool(tool);
|
|
3180
|
+
case "maven":
|
|
3181
|
+
case "archive":
|
|
3182
|
+
return await refreshPinnedManagedTool(tool, known);
|
|
3183
|
+
default:
|
|
3184
|
+
return {
|
|
3185
|
+
ok: false,
|
|
3186
|
+
unchanged: true,
|
|
3187
|
+
reason: `strategy ${strategy} is not refreshable here`
|
|
3188
|
+
};
|
|
3189
|
+
}
|
|
3190
|
+
} finally {
|
|
3191
|
+
await gate.release?.();
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
async function refreshGitHubManagedTool(tool, known) {
|
|
3195
|
+
const spec = tool.github;
|
|
3196
|
+
if (!spec)
|
|
3197
|
+
return { ok: false, unchanged: true, reason: "no github spec" };
|
|
3198
|
+
let response;
|
|
3199
|
+
try {
|
|
3200
|
+
response = await httpsGetWithMeta(`https://api.github.com/repos/${spec.repo}/releases/latest`, 5, {
|
|
3201
|
+
...await githubApiAuthHeaders(),
|
|
3202
|
+
...known.etag ? { "If-None-Match": known.etag } : {}
|
|
3203
|
+
});
|
|
3204
|
+
} catch (err) {
|
|
3205
|
+
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}`;
|
|
3206
|
+
if (err instanceof GitHubHttpError && err.anonymousRateLimitExhausted) {
|
|
3207
|
+
recordDegradationOnce({
|
|
3208
|
+
kind: "github-api-rate-limit",
|
|
3209
|
+
subject: tool.id,
|
|
3210
|
+
reason
|
|
3211
|
+
});
|
|
3212
|
+
}
|
|
3213
|
+
return {
|
|
3214
|
+
ok: false,
|
|
3215
|
+
unchanged: true,
|
|
3216
|
+
reason
|
|
3217
|
+
};
|
|
3218
|
+
}
|
|
3219
|
+
if (response.statusCode === 304) {
|
|
3220
|
+
return {
|
|
3221
|
+
ok: true,
|
|
3222
|
+
unchanged: true,
|
|
3223
|
+
resolutionId: known.resolutionId,
|
|
3224
|
+
etag: known.etag,
|
|
3225
|
+
version: known.version
|
|
3226
|
+
};
|
|
3227
|
+
}
|
|
3228
|
+
let release;
|
|
3229
|
+
try {
|
|
3230
|
+
const parsed = JSON.parse(response.body.toString("utf8"));
|
|
3231
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
3232
|
+
throw new Error("release metadata is not an object");
|
|
3233
|
+
}
|
|
3234
|
+
release = parsed;
|
|
3235
|
+
} catch (err) {
|
|
3236
|
+
return {
|
|
3237
|
+
ok: false,
|
|
3238
|
+
unchanged: true,
|
|
3239
|
+
reason: `release metadata unparseable: ${err.message}`
|
|
3240
|
+
};
|
|
3241
|
+
}
|
|
3242
|
+
const tag = release.tag_name;
|
|
3243
|
+
if (!tag) {
|
|
3244
|
+
return { ok: false, unchanged: true, reason: "release has no tag_name" };
|
|
3245
|
+
}
|
|
3246
|
+
if (known.resolutionId === tag) {
|
|
3247
|
+
return {
|
|
3248
|
+
ok: true,
|
|
3249
|
+
unchanged: true,
|
|
3250
|
+
resolutionId: tag,
|
|
3251
|
+
etag: response.etag ?? known.etag,
|
|
3252
|
+
version: known.version ?? tag
|
|
3253
|
+
};
|
|
3254
|
+
}
|
|
3255
|
+
const installed = await installGitHubTool(tool, release);
|
|
3256
|
+
if (!installed) {
|
|
3257
|
+
return {
|
|
3258
|
+
ok: false,
|
|
3259
|
+
unchanged: true,
|
|
3260
|
+
// Keep the ETag off the failure stamp: replaying it would make the next
|
|
3261
|
+
// attempt a 304 and skip the download this one never completed.
|
|
3262
|
+
resolutionId: known.resolutionId,
|
|
3263
|
+
version: known.version,
|
|
3264
|
+
reason: `install from release ${tag} failed`
|
|
3265
|
+
};
|
|
3266
|
+
}
|
|
3267
|
+
const verified = await verifyRefreshedArtifact(tool, installed);
|
|
3268
|
+
if (!verified) {
|
|
3269
|
+
return {
|
|
3270
|
+
ok: false,
|
|
3271
|
+
unchanged: false,
|
|
3272
|
+
resolutionId: known.resolutionId,
|
|
3273
|
+
version: known.version,
|
|
3274
|
+
reason: `release ${tag} installed but its binary does not run`
|
|
3275
|
+
};
|
|
3276
|
+
}
|
|
3277
|
+
return {
|
|
3278
|
+
ok: true,
|
|
3279
|
+
unchanged: false,
|
|
3280
|
+
resolutionId: tag,
|
|
3281
|
+
etag: response.etag,
|
|
3282
|
+
version: tag
|
|
3283
|
+
};
|
|
3284
|
+
}
|
|
3285
|
+
async function verifyRefreshedArtifact(tool, installedPath) {
|
|
3286
|
+
if (tool.installStrategy === "archive" && (!tool.archive?.launcher || tool.verification === "tree-manifest")) {
|
|
3287
|
+
await updateProbeCache(tool.id, installedPath);
|
|
3288
|
+
return true;
|
|
3289
|
+
}
|
|
3290
|
+
if (!await verifyToolBinary(installedPath, void 0, void 0, getToolVerificationTimeout(tool), tool.checkArgs)) {
|
|
3291
|
+
logSessionStart(`managed-tool-refresh ${tool.id}: refreshed artifact at ${installedPath} failed its verification check`);
|
|
3292
|
+
return false;
|
|
3293
|
+
}
|
|
3294
|
+
await updateProbeCache(tool.id, installedPath);
|
|
3295
|
+
return true;
|
|
3296
|
+
}
|
|
3297
|
+
async function refreshPackageManagerManagedTool(tool) {
|
|
3298
|
+
if (!tool.packageName) {
|
|
3299
|
+
return { ok: false, unchanged: true, reason: "no package name" };
|
|
3300
|
+
}
|
|
3301
|
+
const previous = await probeManagedToolVersion(tool);
|
|
3302
|
+
const installed = tool.installStrategy === "pip" ? (
|
|
3303
|
+
// `-U` is the whole fix: without it pip treats the installed copy as
|
|
3304
|
+
// satisfying the requirement and the day-one version never moves.
|
|
3305
|
+
await installPipTool(tool.id, tool.packageName, tool.binaryName ?? tool.id, {
|
|
3306
|
+
upgrade: true
|
|
3307
|
+
})
|
|
3308
|
+
) : (
|
|
3309
|
+
// `gem install` always fetches the newest version that satisfies the
|
|
3310
|
+
// requirement, so the install command IS the upgrade command.
|
|
3311
|
+
await installGemTool(tool.id, tool.packageName)
|
|
3312
|
+
);
|
|
3313
|
+
if (!installed) {
|
|
3314
|
+
return {
|
|
3315
|
+
ok: false,
|
|
3316
|
+
unchanged: true,
|
|
3317
|
+
version: previous,
|
|
3318
|
+
reason: `${tool.installStrategy} upgrade failed`
|
|
3319
|
+
};
|
|
3320
|
+
}
|
|
3321
|
+
const current = await probeManagedToolVersion(tool);
|
|
3322
|
+
if (previous !== void 0 && current === void 0) {
|
|
3323
|
+
return {
|
|
3324
|
+
ok: false,
|
|
3325
|
+
unchanged: false,
|
|
3326
|
+
version: previous,
|
|
3327
|
+
reason: `${tool.installStrategy} upgrade left the binary unable to report a version`
|
|
3328
|
+
};
|
|
3329
|
+
}
|
|
3330
|
+
return {
|
|
3331
|
+
ok: true,
|
|
3332
|
+
// An unreadable version on both sides cannot prove a move, so it reads as
|
|
3333
|
+
// unchanged rather than inventing one.
|
|
3334
|
+
unchanged: previous === void 0 || current === void 0 || previous === current,
|
|
3335
|
+
version: current ?? previous
|
|
3336
|
+
};
|
|
3337
|
+
}
|
|
3338
|
+
async function refreshPinnedManagedTool(tool, known) {
|
|
3339
|
+
const coordinate = tool.installStrategy === "maven" ? tool.maven && mavenCoordinate(tool.maven) : tool.archive && resolveArchiveUrl(tool.archive);
|
|
3340
|
+
if (!coordinate) {
|
|
3341
|
+
return {
|
|
3342
|
+
ok: false,
|
|
3343
|
+
unchanged: true,
|
|
3344
|
+
reason: "no coordinate for this platform"
|
|
3345
|
+
};
|
|
3346
|
+
}
|
|
3347
|
+
if (known.resolutionId === coordinate) {
|
|
3348
|
+
return {
|
|
3349
|
+
ok: true,
|
|
3350
|
+
unchanged: true,
|
|
3351
|
+
resolutionId: coordinate,
|
|
3352
|
+
version: known.version
|
|
3353
|
+
};
|
|
3354
|
+
}
|
|
3355
|
+
const installed = tool.installStrategy === "maven" ? await installMavenTool(tool) : await installArchiveTool(tool);
|
|
3356
|
+
if (!installed) {
|
|
3357
|
+
return {
|
|
3358
|
+
ok: false,
|
|
3359
|
+
unchanged: true,
|
|
3360
|
+
resolutionId: known.resolutionId,
|
|
3361
|
+
version: known.version,
|
|
3362
|
+
reason: `reinstall from ${coordinate} failed`
|
|
3363
|
+
};
|
|
3364
|
+
}
|
|
3365
|
+
if (!await verifyRefreshedArtifact(tool, installed)) {
|
|
3366
|
+
return {
|
|
3367
|
+
ok: false,
|
|
3368
|
+
unchanged: false,
|
|
3369
|
+
resolutionId: known.resolutionId,
|
|
3370
|
+
version: known.version,
|
|
3371
|
+
reason: `reinstall from ${coordinate} produced an artifact that does not run`
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
return {
|
|
3375
|
+
ok: true,
|
|
3376
|
+
// A first-ever stamp is an adoption, not a version move: the coordinate did
|
|
3377
|
+
// not change, pi-lens simply had no record of it. Reporting it as a move
|
|
3378
|
+
// would put a false "x → y" row in the log.
|
|
3379
|
+
unchanged: known.resolutionId === void 0,
|
|
3380
|
+
resolutionId: coordinate,
|
|
3381
|
+
version: coordinate
|
|
3382
|
+
};
|
|
3383
|
+
}
|
|
3384
|
+
function resolveManagedNpmBinPath(binaryName) {
|
|
3385
|
+
const binBase = path3.join(TOOLS_DIR, "node_modules", ".bin", binaryName);
|
|
3386
|
+
return installerPlatform() === "win32" ? `${binBase}.cmd` : binBase;
|
|
3387
|
+
}
|
|
3388
|
+
function invalidateManagedToolResolution(toolId) {
|
|
3389
|
+
resolvedPathCache.delete(toolId);
|
|
3390
|
+
if (_probeCache !== null)
|
|
3391
|
+
delete _probeCache[toolId];
|
|
3392
|
+
markProbeCacheChange(toolId, null);
|
|
3393
|
+
}
|
|
3394
|
+
var MAVEN_CENTRAL_BASE = "https://repo1.maven.org/maven2";
|
|
3395
|
+
async function installMavenTool(tool) {
|
|
3396
|
+
const spec = tool.maven;
|
|
3397
|
+
if (!spec)
|
|
3398
|
+
return void 0;
|
|
3399
|
+
const binaryName = tool.binaryName ?? tool.id;
|
|
3400
|
+
const isWindows = process.platform === "win32";
|
|
3401
|
+
if (!await isCommandAvailable("java", ["-version"])) {
|
|
3402
|
+
logSessionStart(`maven-install ${tool.id}: java not found \u2014 a JAR tool can't run without a JRE`);
|
|
3403
|
+
return void 0;
|
|
3404
|
+
}
|
|
3405
|
+
let base = spec.repoBaseUrl ?? MAVEN_CENTRAL_BASE;
|
|
3406
|
+
while (base.endsWith("/"))
|
|
3407
|
+
base = base.slice(0, -1);
|
|
3408
|
+
const groupPath = spec.groupId.replace(/\./g, "/");
|
|
3409
|
+
const jarFile = `${spec.artifactId}-${spec.version}${spec.classifier ? `-${spec.classifier}` : ""}.jar`;
|
|
3410
|
+
const url = `${base}/${groupPath}/${spec.artifactId}/${spec.version}/${jarFile}`;
|
|
3411
|
+
lastInstallResolutionId.set(tool.id, mavenCoordinate(spec));
|
|
3412
|
+
logSessionStart(`maven-install ${tool.id}: downloading ${url}`);
|
|
3413
|
+
let jarBuffer;
|
|
3414
|
+
try {
|
|
3415
|
+
jarBuffer = await httpsGet(url);
|
|
3416
|
+
} catch (err) {
|
|
3417
|
+
logSessionStart(`maven-install ${tool.id}: download failed: ${err.message}`);
|
|
3418
|
+
return void 0;
|
|
3419
|
+
}
|
|
3420
|
+
try {
|
|
3421
|
+
await fs2.mkdir(GITHUB_BIN_DIR, { recursive: true });
|
|
3422
|
+
const jarPath = path3.join(GITHUB_BIN_DIR, `${tool.id}.jar`);
|
|
3423
|
+
await writeFileAtomicAsync(jarPath, jarBuffer, { bestEffort: false });
|
|
3424
|
+
const launcherName = isWindows ? `${binaryName}.bat` : binaryName;
|
|
3425
|
+
const launcherPath = path3.join(GITHUB_BIN_DIR, launcherName);
|
|
3426
|
+
if (isWindows) {
|
|
3427
|
+
await writeFileAtomicAsync(launcherPath, `@echo off\r
|
|
3428
|
+
java -jar "%~dp0${tool.id}.jar" %*\r
|
|
3429
|
+
`, { bestEffort: false });
|
|
3430
|
+
} else {
|
|
3431
|
+
await writeFileAtomicAsync(launcherPath, `#!/bin/sh
|
|
3432
|
+
exec java -jar "$(dirname "$0")/${tool.id}.jar" "$@"
|
|
3433
|
+
`, { bestEffort: false, mode: 488 });
|
|
3434
|
+
}
|
|
3435
|
+
logSessionStart(`maven-install ${tool.id}: installed \u2192 ${launcherPath} (${jarBuffer.length} bytes)`);
|
|
3436
|
+
debugLog(`[maven] installed ${tool.name} \u2192 ${launcherPath}`);
|
|
3437
|
+
return launcherPath;
|
|
3438
|
+
} catch (err) {
|
|
3439
|
+
logSessionStart(`maven-install ${tool.id}: install failed: ${err.message}`);
|
|
3440
|
+
return void 0;
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
function resolveArchiveUrl(spec, platform = installerPlatform(), arch = process.arch) {
|
|
3444
|
+
return typeof spec.url === "function" ? spec.url(platform, arch) : spec.url;
|
|
3445
|
+
}
|
|
3446
|
+
function resolveArchiveKind(spec, platform = installerPlatform(), arch = process.arch) {
|
|
3447
|
+
return typeof spec.kind === "function" ? spec.kind(platform, arch) : spec.kind;
|
|
3448
|
+
}
|
|
3449
|
+
function recordArchiveExtractionDegradation(toolId, format, reason) {
|
|
3450
|
+
recordDegradationOnce({
|
|
3451
|
+
kind: "managed-tool-install",
|
|
3452
|
+
subject: `${toolId}:${format}`,
|
|
3453
|
+
reason: `archive extraction ${reason}`
|
|
3454
|
+
});
|
|
3455
|
+
}
|
|
3456
|
+
async function stripExtractedArchiveRoot(dir, components) {
|
|
3457
|
+
for (let i = 0; i < components; i++) {
|
|
3458
|
+
const entries = await fs2.readdir(dir, { withFileTypes: true });
|
|
3459
|
+
const [entry] = entries;
|
|
3460
|
+
if (entries.length !== 1 || !entry?.isDirectory())
|
|
3461
|
+
return false;
|
|
3462
|
+
const root = path3.join(dir, entry.name);
|
|
3463
|
+
for (const child of await fs2.readdir(root))
|
|
3464
|
+
await fs2.rename(path3.join(root, child), path3.join(dir, child));
|
|
3465
|
+
await fs2.rm(root, { recursive: true, force: true });
|
|
3466
|
+
}
|
|
3467
|
+
return true;
|
|
3468
|
+
}
|
|
3469
|
+
async function swapExtractedDir(toolId, tmpDir, finalDir) {
|
|
3470
|
+
const backupDir = `${finalDir}.rollback`;
|
|
3471
|
+
await fs2.rm(backupDir, { recursive: true, force: true });
|
|
3472
|
+
let hadPrevious = false;
|
|
3473
|
+
try {
|
|
3474
|
+
await fs2.rename(finalDir, backupDir);
|
|
3475
|
+
hadPrevious = true;
|
|
3476
|
+
} catch (err) {
|
|
3477
|
+
if (err.code !== "ENOENT")
|
|
3478
|
+
throw err;
|
|
3479
|
+
}
|
|
3480
|
+
try {
|
|
3481
|
+
await fs2.rename(tmpDir, finalDir);
|
|
3482
|
+
} catch (err) {
|
|
3483
|
+
if (hadPrevious) {
|
|
3484
|
+
try {
|
|
3485
|
+
await fs2.rename(backupDir, finalDir);
|
|
3486
|
+
} catch (rollbackErr) {
|
|
3487
|
+
recordDegradationOnce({
|
|
3488
|
+
kind: "managed-tool-refresh",
|
|
3489
|
+
subject: toolId,
|
|
3490
|
+
reason: `swap rollback failed after a failed install: working copy orphaned at ${backupDir} (${rollbackErr.message})`
|
|
3491
|
+
});
|
|
3492
|
+
logSessionStart(`archive-install ${toolId}: swap rollback failed \u2014 working copy orphaned at ${backupDir} (${rollbackErr.message})`);
|
|
3493
|
+
}
|
|
3494
|
+
}
|
|
3495
|
+
throw err;
|
|
3496
|
+
}
|
|
3497
|
+
if (hadPrevious) {
|
|
3498
|
+
await fs2.rm(backupDir, { recursive: true, force: true }).catch(() => {
|
|
3499
|
+
});
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
var ARCHIVE_RUNTIME_HOMES = {
|
|
3503
|
+
java: { env: "JAVA_HOME", dir: "bin" }
|
|
3504
|
+
};
|
|
3505
|
+
function runtimeHomeVerdict(runtime) {
|
|
3506
|
+
const home = ARCHIVE_RUNTIME_HOMES[runtime];
|
|
3507
|
+
const root = home ? process.env[home.env] : void 0;
|
|
3508
|
+
if (!home || !root)
|
|
3509
|
+
return void 0;
|
|
3510
|
+
const windows = installerPlatform() === "win32";
|
|
3511
|
+
const names = windows ? [`${runtime}.exe`, runtime] : [runtime];
|
|
3512
|
+
return names.some((name) => {
|
|
3513
|
+
try {
|
|
3514
|
+
const stat = statSync(path3.join(root, home.dir, name));
|
|
3515
|
+
return stat.isFile() && stat.size > 0 && (windows || (stat.mode & 73) !== 0);
|
|
3516
|
+
} catch {
|
|
3517
|
+
return false;
|
|
3518
|
+
}
|
|
3519
|
+
});
|
|
3520
|
+
}
|
|
3521
|
+
async function installArchiveTool(tool) {
|
|
3522
|
+
const spec = tool.archive;
|
|
3523
|
+
if (!spec)
|
|
3524
|
+
return void 0;
|
|
3525
|
+
const binaryName = tool.binaryName ?? tool.id;
|
|
3526
|
+
const platform = installerPlatform();
|
|
3527
|
+
const isWindows = platform === "win32";
|
|
3528
|
+
const archiveKind = resolveArchiveKind(spec, platform, process.arch);
|
|
3529
|
+
const url = resolveArchiveUrl(spec, platform, process.arch);
|
|
3530
|
+
if (!url) {
|
|
3531
|
+
logSessionStart(`archive-install ${tool.id}: no archive for ${process.platform}/${process.arch} \u2014 unsupported, skipping`);
|
|
3532
|
+
return void 0;
|
|
3533
|
+
}
|
|
3534
|
+
lastInstallResolutionId.set(tool.id, url);
|
|
3535
|
+
logSessionStart(`archive-install ${tool.id}: downloading ${url}`);
|
|
3536
|
+
let archiveBuffer;
|
|
3537
|
+
try {
|
|
3538
|
+
archiveBuffer = await httpsGet(url);
|
|
3539
|
+
} catch (err) {
|
|
3540
|
+
logSessionStart(`archive-install ${tool.id}: download failed: ${err.message}`);
|
|
3541
|
+
return void 0;
|
|
3542
|
+
}
|
|
3543
|
+
const pinned = spec.sha256?.[url];
|
|
3544
|
+
const digest = createHash("sha256").update(archiveBuffer).digest("hex");
|
|
3545
|
+
if (digest !== pinned) {
|
|
3546
|
+
const reason = pinned === void 0 ? "sha256-unpinned" : "sha256-mismatch";
|
|
3547
|
+
recordDegradationOnce({
|
|
3548
|
+
kind: "managed-tool-install",
|
|
3549
|
+
subject: `${tool.id}:${archiveKind}:integrity`,
|
|
3550
|
+
reason: `archive integrity ${reason}`
|
|
3551
|
+
});
|
|
3552
|
+
installFailureReasons.set(tool.id, `archive integrity ${reason} (${archiveBuffer.length} bytes from ${url})`);
|
|
3553
|
+
logSessionStart(`archive-install ${tool.id}: refused ${reason} (got ${digest}, pinned ${pinned ?? "none"}) \u2014 keeping installed version`);
|
|
3554
|
+
return void 0;
|
|
3555
|
+
}
|
|
3556
|
+
const extractName = tool.id;
|
|
3557
|
+
const tmpExtractName = `${extractName}.refresh-tmp`;
|
|
3558
|
+
const archiveName = `${tool.id}.download.${archiveKind === "zip" ? "zip" : "tgz"}`;
|
|
3559
|
+
const extractDir = path3.join(TOOLS_DIR, extractName);
|
|
3560
|
+
const tmpExtractDir = path3.join(TOOLS_DIR, tmpExtractName);
|
|
3561
|
+
const tmpArchive = path3.join(TOOLS_DIR, archiveName);
|
|
3562
|
+
try {
|
|
3563
|
+
await fs2.mkdir(TOOLS_DIR, { recursive: true });
|
|
3564
|
+
await fs2.rm(tmpExtractDir, { recursive: true, force: true });
|
|
3565
|
+
await fs2.mkdir(tmpExtractDir, { recursive: true });
|
|
3566
|
+
await fs2.writeFile(tmpArchive, archiveBuffer);
|
|
3567
|
+
const stripComponents = spec.stripComponents ?? 1;
|
|
3568
|
+
const extractionArgs = archiveKind === "zip" && isWindows ? [
|
|
3569
|
+
"-NoProfile",
|
|
3570
|
+
"-Command",
|
|
3571
|
+
`Expand-Archive -LiteralPath '${archiveName}' -DestinationPath '${tmpExtractName}' -Force`
|
|
3572
|
+
] : archiveKind === "zip" ? ["-q", "-o", archiveName, "-d", tmpExtractName] : [
|
|
3573
|
+
"-xzf",
|
|
3574
|
+
archiveName,
|
|
3575
|
+
"-C",
|
|
3576
|
+
tmpExtractName,
|
|
3577
|
+
...stripComponents > 0 ? [`--strip-components=${stripComponents}`] : []
|
|
3578
|
+
];
|
|
3579
|
+
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";
|
|
3580
|
+
const extractionResult = await safeSpawnAsync(extractor, extractionArgs, {
|
|
3581
|
+
cwd: TOOLS_DIR,
|
|
3582
|
+
timeout: 12e4,
|
|
3583
|
+
ignoreAmbientSignal: true,
|
|
3584
|
+
lifetimeCoupled: true
|
|
3585
|
+
});
|
|
3586
|
+
const extracted = {
|
|
3587
|
+
ok: extractionResult.status === 0,
|
|
3588
|
+
reason: extractionResult.spawnFailure?.kind === "tool-not-found" ? "extractor-unavailable" : extractionResult.spawnFailure ? "extractor-error" : "extractor-exit"
|
|
3589
|
+
};
|
|
3590
|
+
await fs2.rm(tmpArchive, { force: true });
|
|
3591
|
+
if (!extracted.ok) {
|
|
3592
|
+
recordArchiveExtractionDegradation(tool.id, archiveKind, extracted.reason);
|
|
3593
|
+
logSessionStart(`archive-install ${tool.id}: ${archiveKind} extraction failed (${extracted.reason}) \u2014 keeping installed version`);
|
|
3594
|
+
await fs2.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {
|
|
3595
|
+
});
|
|
3596
|
+
return void 0;
|
|
3597
|
+
}
|
|
3598
|
+
if (archiveKind === "zip" && !await stripExtractedArchiveRoot(tmpExtractDir, stripComponents)) {
|
|
3599
|
+
recordArchiveExtractionDegradation(tool.id, archiveKind, "layout-invalid");
|
|
3600
|
+
logSessionStart(`archive-install ${tool.id}: ${archiveKind} layout invalid \u2014 keeping installed version`);
|
|
3601
|
+
await fs2.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {
|
|
3602
|
+
});
|
|
3603
|
+
return void 0;
|
|
3604
|
+
}
|
|
3605
|
+
if (!spec.launcher) {
|
|
3606
|
+
const tmpMarker = spec.treeMarker ? path3.join(tmpExtractDir, ...spec.treeMarker.split("/")) : tmpExtractDir;
|
|
3607
|
+
try {
|
|
3608
|
+
await fs2.access(tmpMarker);
|
|
3609
|
+
} catch {
|
|
3610
|
+
recordArchiveExtractionDegradation(tool.id, archiveKind, "marker-missing");
|
|
3611
|
+
logSessionStart(`archive-install ${tool.id}: ${archiveKind} marker missing after extraction \u2014 keeping installed version`);
|
|
3612
|
+
await fs2.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {
|
|
3613
|
+
});
|
|
3614
|
+
return void 0;
|
|
3615
|
+
}
|
|
3616
|
+
await swapExtractedDir(tool.id, tmpExtractDir, extractDir);
|
|
3617
|
+
logSessionStart(`archive-install ${tool.id}: installed tree bundle \u2192 ${extractDir} (extracted ${archiveBuffer.length} bytes, sha256 verified ${digest})`);
|
|
3618
|
+
debugLog(`[archive] installed ${tool.name} bundle \u2192 ${extractDir}`);
|
|
3619
|
+
return extractDir;
|
|
3620
|
+
}
|
|
3621
|
+
const launcherParts = spec.launcher.split("/").map((p) => p);
|
|
3622
|
+
const tmpInnerLauncher = path3.join(tmpExtractDir, ...launcherParts);
|
|
3623
|
+
const tmpResolvedInner = isWindows ? `${tmpInnerLauncher}.bat` : tmpInnerLauncher;
|
|
3624
|
+
try {
|
|
3625
|
+
await fs2.access(tmpResolvedInner);
|
|
3626
|
+
} catch {
|
|
3627
|
+
recordArchiveExtractionDegradation(tool.id, archiveKind, "launcher-missing");
|
|
3628
|
+
logSessionStart(`archive-install ${tool.id}: ${archiveKind} launcher missing after extraction \u2014 keeping installed version`);
|
|
3629
|
+
await fs2.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {
|
|
3630
|
+
});
|
|
3631
|
+
return void 0;
|
|
3632
|
+
}
|
|
3633
|
+
if (!isWindows)
|
|
3634
|
+
await fs2.chmod(tmpResolvedInner, 488).catch(() => {
|
|
3635
|
+
});
|
|
3636
|
+
const launcherStat = await fs2.stat(tmpResolvedInner).catch(() => void 0);
|
|
3637
|
+
const launcherRunnable = launcherStat?.isFile() === true && launcherStat.size > 0 && (isWindows || (launcherStat.mode & 73) !== 0);
|
|
3638
|
+
if (!launcherRunnable) {
|
|
3639
|
+
recordArchiveExtractionDegradation(tool.id, archiveKind, "launcher-invalid");
|
|
3640
|
+
logSessionStart(`archive-install ${tool.id}: ${archiveKind} launcher is empty or not executable \u2014 keeping installed version`);
|
|
3641
|
+
await fs2.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {
|
|
3642
|
+
});
|
|
3643
|
+
return void 0;
|
|
3644
|
+
}
|
|
3645
|
+
await swapExtractedDir(tool.id, tmpExtractDir, extractDir);
|
|
3646
|
+
const innerLauncher = path3.join(extractDir, ...launcherParts);
|
|
3647
|
+
const resolvedInner = isWindows ? `${innerLauncher}.bat` : innerLauncher;
|
|
3648
|
+
await fs2.mkdir(GITHUB_BIN_DIR, { recursive: true });
|
|
3649
|
+
const launcherName = isWindows ? `${binaryName}.bat` : binaryName;
|
|
3650
|
+
const shimPath = path3.join(GITHUB_BIN_DIR, launcherName);
|
|
3651
|
+
if (isWindows) {
|
|
3652
|
+
await writeFileAtomicAsync(shimPath, `@echo off\r
|
|
3653
|
+
call "${resolvedInner}" %*\r
|
|
3654
|
+
`, { bestEffort: false });
|
|
3655
|
+
} else {
|
|
3656
|
+
await writeFileAtomicAsync(shimPath, `#!/bin/sh
|
|
3657
|
+
exec "${resolvedInner}" "$@"
|
|
3658
|
+
`, { bestEffort: false, mode: 488 });
|
|
3659
|
+
}
|
|
3660
|
+
logSessionStart(`archive-install ${tool.id}: installed \u2192 ${shimPath} (extracted ${archiveBuffer.length} bytes, sha256 verified ${digest})`);
|
|
3661
|
+
debugLog(`[archive] installed ${tool.name} \u2192 ${shimPath}`);
|
|
3662
|
+
return shimPath;
|
|
3663
|
+
} catch (err) {
|
|
3664
|
+
await fs2.rm(tmpArchive, { force: true }).catch(() => {
|
|
3665
|
+
});
|
|
3666
|
+
await fs2.rm(tmpExtractDir, { recursive: true, force: true }).catch(() => {
|
|
3667
|
+
});
|
|
3668
|
+
logSessionStart(`archive-install ${tool.id}: install failed: ${err.message} \u2014 keeping installed version`);
|
|
3669
|
+
return void 0;
|
|
3670
|
+
}
|
|
3671
|
+
}
|
|
3672
|
+
function recordPackageManagerInstallException(toolId, strategyLabel, packageName, err) {
|
|
3673
|
+
const message = boundInstallError(err.message);
|
|
3674
|
+
logSessionStart(`auto-install ${strategyLabel} ${packageName}: exception: ${message}`);
|
|
3675
|
+
installFailureReasons.set(toolId, message);
|
|
3676
|
+
return void 0;
|
|
3677
|
+
}
|
|
3678
|
+
var INSTALL_ERROR_LINE_LIMIT = 1e3;
|
|
3679
|
+
var INSTALL_CANDIDATE_ERROR_LIMIT = 200;
|
|
3680
|
+
function boundInstallError(value, limit = INSTALL_ERROR_LINE_LIMIT) {
|
|
3681
|
+
const line = value.replace(/[\r\n]+/g, " ").trim();
|
|
3682
|
+
return line.length > limit ? `${line.slice(0, limit - 3)}...` : line;
|
|
3683
|
+
}
|
|
3684
|
+
var InstallLockLostError = class extends Error {
|
|
3685
|
+
};
|
|
3686
|
+
async function installNpmTool(toolId, packageName, binaryName, verificationArgs = ["--version"], verificationTimeoutMs = 1e4, packageEntryOf, ownsLock) {
|
|
3687
|
+
const assertOwnsLock = (context) => {
|
|
3688
|
+
if (ownsLock && !ownsLock()) {
|
|
3689
|
+
recordDegradationOnce({
|
|
3690
|
+
kind: "install-lock-lost-mid-install",
|
|
3691
|
+
subject: toolId,
|
|
3692
|
+
reason: `install lock generation lost before ${context}; aborting rather than risk a second writer in TOOLS_DIR`
|
|
3693
|
+
});
|
|
3694
|
+
throw new InstallLockLostError(`install lock lost before ${context} for ${packageName} (#3515)`);
|
|
3695
|
+
}
|
|
3696
|
+
};
|
|
3697
|
+
try {
|
|
3698
|
+
await fs2.mkdir(TOOLS_DIR, { recursive: true });
|
|
3699
|
+
const packageJsonPath = path3.join(TOOLS_DIR, "package.json");
|
|
3700
|
+
try {
|
|
3701
|
+
await fs2.access(packageJsonPath);
|
|
3702
|
+
} catch {
|
|
3703
|
+
await writeFileAtomicAsync(packageJsonPath, JSON.stringify({ name: "pi-lens-tools", version: "1.0.0" }, null, 2), { bestEffort: false });
|
|
3704
|
+
}
|
|
3705
|
+
const isWindows = installerPlatform() === "win32";
|
|
3706
|
+
const pm = await resolveNodePackageManager(TOOLS_DIR);
|
|
3707
|
+
const testNpmScript = process.env.PI_LENS_TEST_MODE === "1" ? process.env.PI_LENS_TEST_NPM_SCRIPT : void 0;
|
|
3708
|
+
const pmCommand = testNpmScript ? process.execPath : pmBinary(pm);
|
|
3709
|
+
const needsScripts = NEEDS_POSTINSTALL.has(packageName);
|
|
3710
|
+
const baseInstallArgs = installArgs(pm, packageName, {
|
|
3711
|
+
ignoreScripts: !needsScripts
|
|
3712
|
+
});
|
|
3713
|
+
const INSTALL_TIMEOUT_MS = Number(process.env.PI_LENS_INSTALL_TIMEOUT_MS) || 12e4;
|
|
3714
|
+
const runInstallAttempt = async (args) => {
|
|
3715
|
+
const result = await safeSpawnAsync(pmCommand, args, {
|
|
3716
|
+
cwd: TOOLS_DIR,
|
|
3717
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
3718
|
+
ignoreAmbientSignal: true,
|
|
3719
|
+
lifetimeCoupled: true
|
|
3720
|
+
});
|
|
3721
|
+
return {
|
|
3722
|
+
ok: result.status === 0,
|
|
3723
|
+
stderr: result.error?.message ?? result.stderr
|
|
3724
|
+
};
|
|
3725
|
+
};
|
|
3726
|
+
assertOwnsLock(`spawning ${pmCommand} install`);
|
|
3727
|
+
let outcome = await runInstallAttempt([
|
|
3728
|
+
...testNpmScript ? [testNpmScript] : [],
|
|
3729
|
+
...baseInstallArgs
|
|
3730
|
+
]);
|
|
3731
|
+
const erResolve = outcome.ok === false && /npm\s+error\s+ERESOLVE|\bERESOLVE\b|could not resolve/i.test(outcome.stderr);
|
|
3732
|
+
if (pm === "npm" && erResolve) {
|
|
3733
|
+
assertOwnsLock(`retrying ${pmCommand} install with --legacy-peer-deps`);
|
|
3734
|
+
const retryArgs = installArgs(pm, packageName, {
|
|
3735
|
+
ignoreScripts: !needsScripts,
|
|
3736
|
+
legacyPeerDeps: true
|
|
3737
|
+
});
|
|
3738
|
+
logSessionStart(`auto-install npm ${packageName}: retry with --legacy-peer-deps after ERESOLVE`);
|
|
3739
|
+
outcome = await runInstallAttempt([
|
|
3740
|
+
...testNpmScript ? [testNpmScript] : [],
|
|
3741
|
+
...retryArgs
|
|
3742
|
+
]);
|
|
3743
|
+
}
|
|
3744
|
+
if (!outcome.ok) {
|
|
3745
|
+
throw new Error(`Failed to install ${packageName}: ${outcome.stderr}`);
|
|
3746
|
+
}
|
|
3747
|
+
const binBase = path3.join(TOOLS_DIR, "node_modules", ".bin", binaryName);
|
|
3748
|
+
const binPath = installerPlatform() === "win32" ? `${binBase}.cmd` : binBase;
|
|
3749
|
+
if (installerPlatform() !== "win32") {
|
|
3750
|
+
try {
|
|
3751
|
+
await fs2.chmod(binPath, 488);
|
|
3752
|
+
} catch {
|
|
3753
|
+
}
|
|
3754
|
+
}
|
|
3755
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
3756
|
+
debugLog(`Verifying ${binaryName}...`);
|
|
3757
|
+
let isValid = false;
|
|
3758
|
+
let lastAttemptTransient = false;
|
|
3759
|
+
let lastAttemptInconclusive = false;
|
|
3760
|
+
for (let attempt = 1; attempt <= 3; attempt++) {
|
|
3761
|
+
lastAttemptTransient = false;
|
|
3762
|
+
lastAttemptInconclusive = false;
|
|
3763
|
+
isValid = await verifyToolBinary(binPath, void 0, () => {
|
|
3764
|
+
lastAttemptTransient = true;
|
|
3765
|
+
}, verificationTimeoutMs, verificationArgs, packageEntryOf, () => {
|
|
3766
|
+
lastAttemptInconclusive = true;
|
|
3767
|
+
});
|
|
3768
|
+
if (isValid)
|
|
3769
|
+
break;
|
|
3770
|
+
if (attempt < 3) {
|
|
3771
|
+
logSessionStart(`auto-install verify ${binaryName}: attempt ${attempt} failed, retrying in ${attempt}s`);
|
|
3772
|
+
await new Promise((r) => setTimeout(r, 1e3 * attempt));
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
if (!isValid && lastAttemptInconclusive) {
|
|
3776
|
+
if (await verifyNpmPackageEntry(binPath, packageName)) {
|
|
3777
|
+
logSessionStart(`auto-install ${packageName}: verification inconclusive (output truncated before the transport-required marker) but the installed tree is intact; keeping installation for re-probe`);
|
|
3778
|
+
return void 0;
|
|
3779
|
+
}
|
|
3780
|
+
logSessionStart(`auto-install ${packageName}: verification inconclusive AND the installed tree is incomplete; cleaning up so the next install can repair it`);
|
|
3781
|
+
}
|
|
3782
|
+
if (!isValid && lastAttemptTransient) {
|
|
3783
|
+
logSessionStart(`auto-install ${packageName}: verification inconclusive (transient); keeping installation for re-probe`);
|
|
3784
|
+
return void 0;
|
|
3785
|
+
}
|
|
3786
|
+
if (!isValid) {
|
|
3787
|
+
logSessionStart(`auto-install ${packageName}: installed but verification failed, cleaning up`);
|
|
3788
|
+
try {
|
|
3789
|
+
const packagePath = path3.join(TOOLS_DIR, "node_modules", packageName);
|
|
3790
|
+
await fs2.rm(packagePath, { recursive: true, force: true });
|
|
3791
|
+
await fs2.rm(binBase, { force: true });
|
|
3792
|
+
if (isWindows) {
|
|
3793
|
+
await fs2.rm(`${binBase}.cmd`, { force: true });
|
|
3794
|
+
await fs2.rm(`${binBase}.ps1`, { force: true });
|
|
3795
|
+
}
|
|
3796
|
+
} catch {
|
|
3797
|
+
}
|
|
3798
|
+
return void 0;
|
|
3799
|
+
}
|
|
3800
|
+
return binPath;
|
|
3801
|
+
} catch (err) {
|
|
3802
|
+
return recordPackageManagerInstallException(toolId, "npm", packageName, err);
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
function pipCommandCandidates() {
|
|
3806
|
+
return process.platform === "win32" ? ["pip", "py", "python"] : ["pip3", "pip", "python3", "python"];
|
|
3807
|
+
}
|
|
3808
|
+
function pipScriptsDir(base, platform = installerPlatform()) {
|
|
3809
|
+
return path3.join(base, platform === "win32" ? "Scripts" : "bin");
|
|
3810
|
+
}
|
|
3811
|
+
var pipPep668LoggedRefusals = createGenerationMap("installer-pep668-log");
|
|
3812
|
+
async function pipConstraintEnvFor(toolId) {
|
|
3813
|
+
const constraints = TOOLS.find((t) => t.id === toolId)?.pipConstraints;
|
|
3814
|
+
if (!constraints || constraints.length === 0)
|
|
3815
|
+
return {};
|
|
3816
|
+
const directories = [
|
|
3817
|
+
path3.join(getGlobalPiLensDir(), "pip-constraints"),
|
|
3818
|
+
path3.join(os.tmpdir(), "pi-lens-pip-constraints")
|
|
3819
|
+
];
|
|
3820
|
+
const directory = directories.find((candidate) => !/\s/.test(candidate));
|
|
3821
|
+
if (!directory) {
|
|
3822
|
+
recordDegradationOnce({
|
|
3823
|
+
kind: "pip-constraint-path-unusable",
|
|
3824
|
+
subject: toolId,
|
|
3825
|
+
reason: `no whitespace-free directory for the constraints file (tried ${directories.join(", ")}); installing unconstrained`
|
|
3826
|
+
});
|
|
3827
|
+
return {};
|
|
3828
|
+
}
|
|
3829
|
+
const file = path3.join(directory, `${toolId}.txt`);
|
|
3830
|
+
try {
|
|
3831
|
+
await fs2.mkdir(path3.dirname(file), { recursive: true });
|
|
3832
|
+
await writeFileAtomicAsync(file, `${constraints.join("\n")}
|
|
3833
|
+
`, {
|
|
3834
|
+
bestEffort: false
|
|
3835
|
+
});
|
|
3836
|
+
} catch (err) {
|
|
3837
|
+
recordDegradationOnce({
|
|
3838
|
+
kind: "pip-constraint-file-unwritable",
|
|
3839
|
+
subject: toolId,
|
|
3840
|
+
reason: `${file}: ${err instanceof Error ? err.message : String(err)}`
|
|
3841
|
+
});
|
|
3842
|
+
return {};
|
|
3843
|
+
}
|
|
3844
|
+
logSessionStart(`auto-install pip ${toolId}: constraining resolution with ${constraints.join(", ")} (${file})`);
|
|
3845
|
+
return { PIP_CONSTRAINT: file, UV_CONSTRAINT: file };
|
|
3846
|
+
}
|
|
3847
|
+
async function installPipTool(toolId, packageName, binaryName, options = {}) {
|
|
3848
|
+
try {
|
|
3849
|
+
const isWindows = installerPlatform() === "win32";
|
|
3850
|
+
const verb = options.upgrade ? ["install", "-U"] : ["install"];
|
|
3851
|
+
const pipConstraintEnv = await pipConstraintEnvFor(toolId);
|
|
3852
|
+
const pipCandidates = pipCommandCandidates().map((command) => ({
|
|
3853
|
+
command,
|
|
3854
|
+
args: command === "pip" || command === "pip3" ? [...verb, packageName] : ["-m", "pip", ...verb, packageName]
|
|
3855
|
+
}));
|
|
3856
|
+
const pep668 = /externally-managed-environment/i;
|
|
3857
|
+
const refuse = (strategy, reason) => {
|
|
3858
|
+
if (!pep668.test(reason))
|
|
3859
|
+
return;
|
|
3860
|
+
const subject = `${toolId}:${strategy}`;
|
|
3861
|
+
recordDegradationOnce({
|
|
3862
|
+
kind: "pip-pep668-strategy-refused",
|
|
3863
|
+
subject,
|
|
3864
|
+
reason
|
|
3865
|
+
});
|
|
3866
|
+
const logKey = `${getDegradationLedgerGeneration()}:${subject}`;
|
|
3867
|
+
if (pipPep668LoggedRefusals.current(logKey) === 0) {
|
|
3868
|
+
pipPep668LoggedRefusals.bump(logKey);
|
|
3869
|
+
logSessionStart(`auto-install pip ${packageName}: ${strategy} refused by PEP 668 (${boundInstallError(reason)})`);
|
|
3870
|
+
}
|
|
3871
|
+
};
|
|
3872
|
+
const succeeded = (strategy, binaryPath) => {
|
|
3873
|
+
recordDegradationOnce({
|
|
3874
|
+
kind: "pip-install-strategy-succeeded",
|
|
3875
|
+
subject: `${toolId}:${strategy}`,
|
|
3876
|
+
reason: binaryPath
|
|
3877
|
+
});
|
|
3878
|
+
return binaryPath;
|
|
3879
|
+
};
|
|
3880
|
+
const run = (command, args, env) => {
|
|
3881
|
+
const spawnEnv = Object.keys(pipConstraintEnv).length > 0 ? { ...env ?? process.env, ...pipConstraintEnv } : env;
|
|
3882
|
+
return safeSpawnAsync(command, args, {
|
|
3883
|
+
timeout: 12e4,
|
|
3884
|
+
ignoreAmbientSignal: true,
|
|
3885
|
+
lifetimeCoupled: true,
|
|
3886
|
+
cwd: resolveToolCwd("runner", toolId, getGlobalPiLensDir(), {
|
|
3887
|
+
cwd: getGlobalPiLensDir(),
|
|
3888
|
+
suppressTelemetry: true
|
|
3889
|
+
}).cwd,
|
|
3890
|
+
...spawnEnv ? { env: spawnEnv } : {}
|
|
3891
|
+
});
|
|
3892
|
+
};
|
|
3893
|
+
const addBinToPath = async (binDir) => {
|
|
3894
|
+
try {
|
|
3895
|
+
await fs2.access(binDir);
|
|
3896
|
+
} catch {
|
|
3897
|
+
return void 0;
|
|
3898
|
+
}
|
|
3899
|
+
const currentPath = process.env.PATH || process.env.Path || "";
|
|
3900
|
+
const separator = isWindows ? ";" : path3.delimiter;
|
|
3901
|
+
if (!currentPath.toLowerCase().split(separator).includes(binDir.toLowerCase())) {
|
|
3902
|
+
const updatedPath = `${binDir}${separator}${currentPath}`;
|
|
3903
|
+
process.env.PATH = updatedPath;
|
|
3904
|
+
if (isWindows)
|
|
3905
|
+
process.env.Path = updatedPath;
|
|
3906
|
+
}
|
|
3907
|
+
const names = isWindows ? [`${binaryName}.exe`, `${binaryName}.cmd`, binaryName] : [binaryName];
|
|
3908
|
+
for (const name of names) {
|
|
3909
|
+
const candidate = path3.join(binDir, name);
|
|
3910
|
+
try {
|
|
3911
|
+
await fs2.access(candidate);
|
|
3912
|
+
return candidate;
|
|
3913
|
+
} catch {
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
return void 0;
|
|
3917
|
+
};
|
|
3918
|
+
if (await isCommandAvailable("pipx")) {
|
|
3919
|
+
const result = await run("pipx", options.upgrade ? ["upgrade", packageName] : ["install", "--force", packageName]);
|
|
3920
|
+
const error = (result.error?.message ?? result.stderr).trim();
|
|
3921
|
+
if (result.status === 0) {
|
|
3922
|
+
const location = await run("pipx", [
|
|
3923
|
+
"environment",
|
|
3924
|
+
"--value",
|
|
3925
|
+
"PIPX_BIN_DIR"
|
|
3926
|
+
]);
|
|
3927
|
+
const binDir = location.status === 0 && location.stdout.trim() ? location.stdout.trim() : path3.join(os.homedir(), ".local", "bin");
|
|
3928
|
+
const binaryPath = await addBinToPath(binDir);
|
|
3929
|
+
if (binaryPath)
|
|
3930
|
+
return succeeded("pipx", binaryPath);
|
|
3931
|
+
throw new Error(`pipx installed ${packageName} but ${binaryName} is not resolvable`);
|
|
3932
|
+
}
|
|
3933
|
+
refuse("pipx", error);
|
|
3934
|
+
}
|
|
3935
|
+
const pythonCandidates = pipCandidates.filter(({ command }) => command === "python3" || command === "python" || command === "py");
|
|
3936
|
+
const pythonAvailability = await Promise.all(pythonCandidates.map(({ command }) => isCommandAvailable(command)));
|
|
3937
|
+
const availablePythonCandidates = pythonCandidates.filter((_, index) => pythonAvailability[index]);
|
|
3938
|
+
const venvRoot = path3.join(getGlobalPiLensDir(), "pip-tools");
|
|
3939
|
+
for (const candidate of availablePythonCandidates) {
|
|
3940
|
+
const venvBin = pipScriptsDir(venvRoot, installerPlatform());
|
|
3941
|
+
let venvPip = path3.join(venvBin, isWindows ? "pip.exe" : "pip");
|
|
3942
|
+
try {
|
|
3943
|
+
await fs2.access(venvPip);
|
|
3944
|
+
} catch {
|
|
3945
|
+
const created = await run(candidate.command, ["-m", "venv", venvRoot]);
|
|
3946
|
+
const error2 = (created.error?.message ?? created.stderr).trim();
|
|
3947
|
+
if (created.status !== 0) {
|
|
3948
|
+
refuse("venv", error2 || "python venv module unavailable");
|
|
3949
|
+
continue;
|
|
3950
|
+
}
|
|
3951
|
+
}
|
|
3952
|
+
try {
|
|
3953
|
+
await fs2.access(venvPip);
|
|
3954
|
+
} catch {
|
|
3955
|
+
venvPip = path3.join(venvBin, isWindows ? "pip.cmd" : "pip3");
|
|
3956
|
+
try {
|
|
3957
|
+
await fs2.access(venvPip);
|
|
3958
|
+
} catch {
|
|
3959
|
+
continue;
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
const result = await run(venvPip, [...verb, packageName]);
|
|
3963
|
+
const error = (result.error?.message ?? result.stderr).trim();
|
|
3964
|
+
if (result.status === 0) {
|
|
3965
|
+
const binaryPath = await addBinToPath(venvBin);
|
|
3966
|
+
if (binaryPath)
|
|
3967
|
+
return succeeded("venv", binaryPath);
|
|
3968
|
+
throw new Error(`venv installed ${packageName} but ${binaryName} is not resolvable`);
|
|
3969
|
+
}
|
|
3970
|
+
refuse("venv", error);
|
|
3971
|
+
}
|
|
3972
|
+
const candidateErrors = [];
|
|
3973
|
+
let userRefused = false;
|
|
3974
|
+
for (const candidate of pipCandidates) {
|
|
3975
|
+
const args = candidate.command === "pip" || candidate.command === "pip3" ? [...verb, "--user", packageName] : ["-m", "pip", ...verb, "--user", packageName];
|
|
3976
|
+
const result = await run(candidate.command, args);
|
|
3977
|
+
const error = (result.error?.message ?? result.stderr).trim();
|
|
3978
|
+
if (result.status === 0) {
|
|
3979
|
+
const base = await new Promise((resolve) => {
|
|
3980
|
+
let probe;
|
|
3981
|
+
try {
|
|
3982
|
+
probe = spawn(candidate.command, ["-m", "site", "--user-base"], {
|
|
3983
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
3984
|
+
shell: isWindows
|
|
3985
|
+
});
|
|
3986
|
+
} catch {
|
|
3987
|
+
resolve("");
|
|
3988
|
+
return;
|
|
3989
|
+
}
|
|
3990
|
+
const stdout = createBoundedOutputSink();
|
|
3991
|
+
probe.stdout?.on("data", (data) => stdout.append(data));
|
|
3992
|
+
probe.on("exit", (code) => resolve(userBaseProbeResult(candidate.command, code, stdout)));
|
|
3993
|
+
probe.on("error", () => resolve(""));
|
|
3994
|
+
});
|
|
3995
|
+
const binaryPath = base ? await addBinToPath(pipScriptsDir(base, installerPlatform())) : void 0;
|
|
3996
|
+
return succeeded("user", binaryPath ?? packageName);
|
|
3997
|
+
}
|
|
3998
|
+
const candidateError = `${candidate.command} ${args.join(" ")}: ${boundInstallError(error, INSTALL_CANDIDATE_ERROR_LIMIT)}`;
|
|
3999
|
+
candidateErrors.push(candidateError);
|
|
4000
|
+
if (pep668.test(error)) {
|
|
4001
|
+
userRefused = true;
|
|
4002
|
+
refuse("user", error);
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
if (!userRefused)
|
|
4006
|
+
throw new Error(`pip install failed: ${candidateErrors.join(" | ") || "unknown error"}`);
|
|
4007
|
+
const privateBase = path3.join(getGlobalPiLensDir(), "pip-user");
|
|
4008
|
+
const privateEnv = { ...process.env, PYTHONUSERBASE: privateBase };
|
|
4009
|
+
for (const candidate of pipCandidates) {
|
|
4010
|
+
const args = candidate.command === "pip" || candidate.command === "pip3" ? [...verb, "--user", "--break-system-packages", packageName] : [
|
|
4011
|
+
"-m",
|
|
4012
|
+
"pip",
|
|
4013
|
+
...verb,
|
|
4014
|
+
"--user",
|
|
4015
|
+
"--break-system-packages",
|
|
4016
|
+
packageName
|
|
4017
|
+
];
|
|
4018
|
+
const result = await run(candidate.command, args, privateEnv);
|
|
4019
|
+
const error = (result.error?.message ?? result.stderr).trim();
|
|
4020
|
+
if (result.status !== 0) {
|
|
4021
|
+
const candidateError = `${candidate.command} ${args.join(" ")}: ${boundInstallError(error, INSTALL_CANDIDATE_ERROR_LIMIT)}`;
|
|
4022
|
+
candidateErrors.push(candidateError);
|
|
4023
|
+
continue;
|
|
4024
|
+
}
|
|
4025
|
+
const binaryPath = await addBinToPath(pipScriptsDir(privateBase, installerPlatform()));
|
|
4026
|
+
if (binaryPath)
|
|
4027
|
+
return succeeded("private-prefix", binaryPath);
|
|
4028
|
+
throw new Error(`private-prefix pip installed ${packageName} but ${binaryName} is not resolvable`);
|
|
4029
|
+
}
|
|
4030
|
+
throw new Error(`pip install failed: ${candidateErrors.join(" | ") || "unknown error"}`);
|
|
4031
|
+
} catch (err) {
|
|
4032
|
+
return recordPackageManagerInstallException(toolId, "pip", packageName, err);
|
|
4033
|
+
}
|
|
4034
|
+
}
|
|
4035
|
+
async function installGemTool(toolId, packageName) {
|
|
4036
|
+
try {
|
|
4037
|
+
const gemResult = await safeSpawnAsync("gem", ["install", packageName, "--no-document"], {
|
|
4038
|
+
timeout: 12e4,
|
|
4039
|
+
ignoreAmbientSignal: true,
|
|
4040
|
+
lifetimeCoupled: true
|
|
4041
|
+
});
|
|
4042
|
+
const outcome = {
|
|
4043
|
+
ok: gemResult.status === 0,
|
|
4044
|
+
error: (gemResult.error?.message ?? gemResult.stderr).trim()
|
|
4045
|
+
};
|
|
4046
|
+
if (!outcome.ok) {
|
|
4047
|
+
throw new Error(`Failed to install ${packageName} via gem: ${outcome.error}`);
|
|
4048
|
+
}
|
|
4049
|
+
return packageName;
|
|
4050
|
+
} catch (err) {
|
|
4051
|
+
return recordPackageManagerInstallException(toolId, "gem", packageName, err);
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4054
|
+
async function stampInstallResolution(toolId) {
|
|
4055
|
+
const tool = TOOLS.find((t) => t.id === toolId);
|
|
4056
|
+
if (tool?.installStrategy === "npm")
|
|
4057
|
+
return;
|
|
4058
|
+
try {
|
|
4059
|
+
const resolutionId = lastInstallResolutionId.get(toolId);
|
|
4060
|
+
const refresh = await import("./chunk-T4ERT73Y.js");
|
|
4061
|
+
await refresh.stampManagedToolInstall(toolId, resolutionId);
|
|
4062
|
+
} catch {
|
|
4063
|
+
}
|
|
4064
|
+
}
|
|
4065
|
+
async function finishInstallAttempt(toolId, ok, startedAt) {
|
|
4066
|
+
logSessionStart(`auto-install ${toolId}: ${ok ? "success" : "failed"} (${Date.now() - startedAt}ms)`);
|
|
4067
|
+
if (ok)
|
|
4068
|
+
await stampInstallResolution(toolId);
|
|
4069
|
+
noteInstallAttempt(toolId, ok ? "succeeded" : "failed", ok ? void 0 : installFailureReasons.get(toolId) ?? "install failed");
|
|
4070
|
+
if (ok) {
|
|
4071
|
+
resetSafeSpawnWindowsCommandCache();
|
|
4072
|
+
try {
|
|
4073
|
+
const { resetMadgeManagedPathMemo } = await import("./chunk-T6D4Q4AQ.js");
|
|
4074
|
+
resetMadgeManagedPathMemo();
|
|
4075
|
+
} catch (err) {
|
|
4076
|
+
logSessionStart(`auto-install ${toolId}: madge memo reset failed: ${err.message}`);
|
|
4077
|
+
}
|
|
4078
|
+
}
|
|
4079
|
+
return ok;
|
|
4080
|
+
}
|
|
4081
|
+
async function installTool(toolId, ownsLock) {
|
|
4082
|
+
if (process.env.PI_LENS_DISABLE_TOOL_INSTALL === "1") {
|
|
4083
|
+
installFailureReasons.set(toolId, "installation disabled by PI_LENS_DISABLE_TOOL_INSTALL=1");
|
|
4084
|
+
noteInstallAttempt(toolId, "declined", "installation disabled by PI_LENS_DISABLE_TOOL_INSTALL=1");
|
|
4085
|
+
logSessionStart(`auto-install ${toolId}: refused \u2014 PI_LENS_DISABLE_TOOL_INSTALL=1`);
|
|
4086
|
+
return false;
|
|
4087
|
+
}
|
|
4088
|
+
const tool = TOOLS.find((t) => t.id === toolId);
|
|
4089
|
+
if (!tool) {
|
|
4090
|
+
noteInstallAttempt(toolId, "declined", "unknown tool id");
|
|
4091
|
+
logSessionStart(`auto-install ${toolId}: unknown tool id`);
|
|
4092
|
+
return false;
|
|
4093
|
+
}
|
|
4094
|
+
const startedAt = Date.now();
|
|
4095
|
+
logSessionStart(`auto-install ${tool.id}: start strategy=${tool.installStrategy} package=${tool.packageName ?? "n/a"}`);
|
|
4096
|
+
try {
|
|
4097
|
+
switch (tool.installStrategy) {
|
|
4098
|
+
case "npm": {
|
|
4099
|
+
if (!tool.packageName || !tool.binaryName)
|
|
4100
|
+
return false;
|
|
4101
|
+
const npmPath = await installNpmTool(tool.id, tool.packageName, tool.binaryName, tool.checkArgs, getToolVerificationTimeout(tool), packageEntryVerification(tool), ownsLock);
|
|
4102
|
+
if (npmPath !== void 0) {
|
|
4103
|
+
await import("./chunk-T4ERT73Y.js").then((m) => m.stampManagedToolInstalled(tool.id, tool.packageName)).catch(() => {
|
|
4104
|
+
});
|
|
4105
|
+
}
|
|
4106
|
+
return finishInstallAttempt(tool.id, npmPath !== void 0, startedAt);
|
|
4107
|
+
}
|
|
4108
|
+
case "pip": {
|
|
4109
|
+
if (!tool.packageName)
|
|
4110
|
+
return false;
|
|
4111
|
+
const pipPath = await installPipTool(tool.id, tool.packageName, tool.binaryName ?? tool.id);
|
|
4112
|
+
return finishInstallAttempt(tool.id, pipPath !== void 0, startedAt);
|
|
4113
|
+
}
|
|
4114
|
+
case "gem": {
|
|
4115
|
+
if (!tool.packageName)
|
|
4116
|
+
return false;
|
|
4117
|
+
const gemPath = await installGemTool(tool.id, tool.packageName);
|
|
4118
|
+
return finishInstallAttempt(tool.id, gemPath !== void 0, startedAt);
|
|
4119
|
+
}
|
|
4120
|
+
case "github": {
|
|
4121
|
+
if (!tool.github)
|
|
4122
|
+
return false;
|
|
4123
|
+
if (!tool.github.assetMatch(process.platform, process.arch)) {
|
|
4124
|
+
const reason = `unsupported platform=${process.platform} arch=${process.arch}`;
|
|
4125
|
+
noteInstallAttempt(tool.id, "unavailable", reason);
|
|
4126
|
+
logSessionStart(`auto-install ${tool.id}: ${reason}`);
|
|
4127
|
+
return false;
|
|
4128
|
+
}
|
|
4129
|
+
const ghPath = await installGitHubTool(tool);
|
|
4130
|
+
return finishInstallAttempt(tool.id, ghPath !== void 0, startedAt);
|
|
4131
|
+
}
|
|
4132
|
+
case "maven": {
|
|
4133
|
+
if (!tool.maven)
|
|
4134
|
+
return false;
|
|
4135
|
+
const mavenPath = await installMavenTool(tool);
|
|
4136
|
+
return finishInstallAttempt(tool.id, mavenPath !== void 0, startedAt);
|
|
4137
|
+
}
|
|
4138
|
+
case "archive": {
|
|
4139
|
+
if (!tool.archive)
|
|
4140
|
+
return false;
|
|
4141
|
+
if (!resolveArchiveUrl(tool.archive)) {
|
|
4142
|
+
const reason = `unsupported platform=${process.platform} arch=${process.arch}`;
|
|
4143
|
+
noteInstallAttempt(tool.id, "unavailable", reason);
|
|
4144
|
+
logSessionStart(`auto-install ${tool.id}: ${reason}`);
|
|
4145
|
+
return false;
|
|
4146
|
+
}
|
|
4147
|
+
const runtime = tool.archive.runtime;
|
|
4148
|
+
const viaHome = runtime ? runtimeHomeVerdict(runtime) : void 0;
|
|
4149
|
+
if (runtime && !(viaHome ?? await isCommandAvailable(runtime))) {
|
|
4150
|
+
const reason = viaHome === void 0 ? `runtime ${runtime} not found on PATH` : `runtime ${runtime} is not an executable file under ${ARCHIVE_RUNTIME_HOMES[runtime]?.env}`;
|
|
4151
|
+
noteInstallAttempt(tool.id, "unavailable", reason);
|
|
4152
|
+
logSessionStart(`auto-install ${tool.id}: ${reason}`);
|
|
4153
|
+
return false;
|
|
4154
|
+
}
|
|
4155
|
+
const archivePath = await installArchiveTool(tool);
|
|
4156
|
+
return finishInstallAttempt(tool.id, archivePath !== void 0, startedAt);
|
|
4157
|
+
}
|
|
4158
|
+
default:
|
|
4159
|
+
logSessionStart(`auto-install ${tool.id}: unsupported strategy`);
|
|
4160
|
+
return false;
|
|
4161
|
+
}
|
|
4162
|
+
} catch (err) {
|
|
4163
|
+
logSessionStart(`auto-install ${tool.id}: exception ${err.message} (${Date.now() - startedAt}ms)`);
|
|
4164
|
+
return false;
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
async function ensureTool(toolId, opts) {
|
|
4168
|
+
if (opts?.allowInstall !== false && !assertInstallAllowed(`managed tool ensure: ${toolId}`)) {
|
|
4169
|
+
logSessionStart(`auto-install ensure ${toolId}: install gated \u2014 ${projectTrustDenialReason()}; discovery only`);
|
|
4170
|
+
const denialReason = projectTrustDenialReason();
|
|
4171
|
+
const discovered = await ensureToolResolved(toolId, {
|
|
4172
|
+
...opts,
|
|
4173
|
+
allowInstall: false
|
|
4174
|
+
});
|
|
4175
|
+
noteInstallAttempt(toolId, "declined", `project trust: ${denialReason}`);
|
|
4176
|
+
return discovered;
|
|
4177
|
+
}
|
|
4178
|
+
return ensureToolResolved(toolId, opts);
|
|
4179
|
+
}
|
|
4180
|
+
async function ensureToolResolved(toolId, opts) {
|
|
4181
|
+
installFailureReasons.delete(toolId);
|
|
4182
|
+
installAttempts.delete(toolId);
|
|
4183
|
+
lastEnsureResolutionSource.delete(toolId);
|
|
4184
|
+
const cacheResolvedPath = (result) => {
|
|
4185
|
+
if (result) {
|
|
4186
|
+
resolvedPathCache.set(toolId, result);
|
|
4187
|
+
void updateProbeCache(toolId, result, wasLastResolveTransient(toolId));
|
|
4188
|
+
}
|
|
4189
|
+
return result;
|
|
4190
|
+
};
|
|
4191
|
+
if (opts?.forceReinstall) {
|
|
4192
|
+
const ensureStartMs2 = Date.now();
|
|
4193
|
+
logSessionStart(`auto-install ensure ${toolId}: force reinstall \u2014 clearing caches`);
|
|
4194
|
+
resolvedPathCache.delete(toolId);
|
|
4195
|
+
try {
|
|
4196
|
+
const probeCache = await readProbeCache();
|
|
4197
|
+
delete probeCache[toolId];
|
|
4198
|
+
markProbeCacheChange(toolId, null);
|
|
4199
|
+
} catch {
|
|
4200
|
+
}
|
|
4201
|
+
if (opts.allowInstall === false) {
|
|
4202
|
+
noteInstallAttempt(toolId, "declined", "install disabled by caller");
|
|
4203
|
+
logSessionStart(`auto-install ensure ${toolId}: force reinstall blocked \u2014 install disabled, discovery only (${Date.now() - ensureStartMs2}ms)`);
|
|
4204
|
+
return cacheResolvedPath(await getToolPath(toolId));
|
|
4205
|
+
}
|
|
4206
|
+
if (process.env.PI_LENS_DISABLE_TOOL_INSTALL === "1") {
|
|
4207
|
+
installFailureReasons.set(toolId, "installation disabled by PI_LENS_DISABLE_TOOL_INSTALL=1");
|
|
4208
|
+
noteInstallAttempt(toolId, "declined", "installation disabled by PI_LENS_DISABLE_TOOL_INSTALL=1");
|
|
4209
|
+
logSessionStart(`auto-install ensure ${toolId}: refused \u2014 PI_LENS_DISABLE_TOOL_INSTALL=1`);
|
|
4210
|
+
return void 0;
|
|
4211
|
+
}
|
|
4212
|
+
const lock = await acquireInstallLock();
|
|
4213
|
+
if (!lock.release) {
|
|
4214
|
+
noteInstallAttempt(toolId, "skipped", lock.reason ?? "install lock held");
|
|
4215
|
+
logSessionStart(`auto-install ensure ${toolId}: ${lock.reason}`);
|
|
4216
|
+
return void 0;
|
|
4217
|
+
}
|
|
4218
|
+
let installed;
|
|
4219
|
+
try {
|
|
4220
|
+
installed = await installTool(toolId, lock.ownsLock);
|
|
4221
|
+
} finally {
|
|
4222
|
+
await lock.release();
|
|
4223
|
+
}
|
|
4224
|
+
if (!installed) {
|
|
4225
|
+
noteInstallAttemptIfUnrecorded(toolId, "failed", "install failed");
|
|
4226
|
+
logSessionStart(`auto-install ensure ${toolId}: force reinstall failed (${Date.now() - ensureStartMs2}ms)`);
|
|
4227
|
+
return void 0;
|
|
4228
|
+
}
|
|
4229
|
+
const result = cacheResolvedPath(await getToolPath(toolId));
|
|
4230
|
+
if (result) {
|
|
4231
|
+
logSessionStart(`auto-install ensure ${toolId}: force reinstall success at ${result} (${Date.now() - ensureStartMs2}ms)`);
|
|
4232
|
+
}
|
|
4233
|
+
return result;
|
|
4234
|
+
}
|
|
4235
|
+
const cached = resolvedPathCache.get(toolId);
|
|
4236
|
+
if (cached) {
|
|
4237
|
+
if (!isFullyQualified(cached)) {
|
|
4238
|
+
lastEnsureResolutionSource.set(toolId, "session-cache");
|
|
4239
|
+
return cached;
|
|
4240
|
+
}
|
|
4241
|
+
try {
|
|
4242
|
+
await fs2.access(cached);
|
|
4243
|
+
lastEnsureResolutionSource.set(toolId, "session-cache");
|
|
4244
|
+
return cached;
|
|
4245
|
+
} catch {
|
|
4246
|
+
}
|
|
4247
|
+
resolvedPathCache.delete(toolId);
|
|
4248
|
+
const probeCache = await readProbeCache();
|
|
4249
|
+
delete probeCache[toolId];
|
|
4250
|
+
markProbeCacheChange(toolId, null);
|
|
4251
|
+
logSessionStart(`auto-install ensure ${toolId}: cached path disappeared; re-probing`);
|
|
4252
|
+
}
|
|
4253
|
+
const diskCached = await checkProbeCache(toolId);
|
|
4254
|
+
if (diskCached) {
|
|
4255
|
+
resolvedPathCache.set(toolId, diskCached);
|
|
4256
|
+
lastEnsureResolutionSource.set(toolId, "probe-cache");
|
|
4257
|
+
logSessionStart(`auto-install ensure ${toolId}: probe cache hit \u2192 ${diskCached}`);
|
|
4258
|
+
return diskCached;
|
|
4259
|
+
}
|
|
4260
|
+
const inFlightKey = opts?.allowInstall === false ? `${toolId}:discovery-only` : toolId;
|
|
4261
|
+
const inFlight = ensureInFlight.get(inFlightKey);
|
|
4262
|
+
if (inFlight) {
|
|
4263
|
+
logSessionStart(`auto-install ensure ${toolId}: waiting for in-flight ensure (${inFlightKey})`);
|
|
4264
|
+
return inFlight;
|
|
4265
|
+
}
|
|
4266
|
+
const ensureStartMs = Date.now();
|
|
4267
|
+
const ensurePromise = (async () => {
|
|
4268
|
+
logSessionStart(`auto-install ensure ${toolId}: start`);
|
|
4269
|
+
const existingPath = await getToolPath(toolId);
|
|
4270
|
+
if (existingPath) {
|
|
4271
|
+
const tool = TOOLS.find((t) => t.id === toolId);
|
|
4272
|
+
const pinnedVersion = tool?.installStrategy === "npm" && tool.packageName ? parsePinnedVersion(tool.packageName) : void 0;
|
|
4273
|
+
if (pinnedVersion) {
|
|
4274
|
+
const seenVersion = lastManagedInstallVersion.get(toolId);
|
|
4275
|
+
if (seenVersion && seenVersion !== pinnedVersion) {
|
|
4276
|
+
lastManagedInstallVersion.delete(toolId);
|
|
4277
|
+
logSessionStart(`auto-install ensure ${toolId}: version drift (installed ${seenVersion} != pinned ${pinnedVersion}) \u2014 forcing reinstall (${Date.now() - ensureStartMs}ms)`);
|
|
4278
|
+
return ensureTool(toolId, {
|
|
4279
|
+
forceReinstall: true,
|
|
4280
|
+
allowInstall: opts?.allowInstall
|
|
4281
|
+
});
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
resolvedPathCache.set(toolId, existingPath);
|
|
4285
|
+
void updateProbeCache(toolId, existingPath, wasLastResolveTransient(toolId));
|
|
4286
|
+
lastEnsureResolutionSource.set(toolId, "path");
|
|
4287
|
+
logSessionStart(`auto-install ensure ${toolId}: already available at ${existingPath} (${Date.now() - ensureStartMs}ms)`);
|
|
4288
|
+
return existingPath;
|
|
4289
|
+
}
|
|
4290
|
+
if (opts?.allowInstall === false) {
|
|
4291
|
+
noteInstallAttempt(toolId, "declined", "install disabled by caller");
|
|
4292
|
+
logSessionStart(`auto-install ensure ${toolId}: install disabled \u2014 discovery only, not found (${Date.now() - ensureStartMs}ms)`);
|
|
4293
|
+
return void 0;
|
|
4294
|
+
}
|
|
4295
|
+
if (process.env.PI_LENS_DISABLE_TOOL_INSTALL === "1") {
|
|
4296
|
+
installFailureReasons.set(toolId, "installation disabled by PI_LENS_DISABLE_TOOL_INSTALL=1");
|
|
4297
|
+
noteInstallAttempt(toolId, "declined", "installation disabled by PI_LENS_DISABLE_TOOL_INSTALL=1");
|
|
4298
|
+
logSessionStart(`auto-install ensure ${toolId}: refused \u2014 PI_LENS_DISABLE_TOOL_INSTALL=1 (${Date.now() - ensureStartMs}ms)`);
|
|
4299
|
+
return void 0;
|
|
4300
|
+
}
|
|
4301
|
+
const lock = await acquireInstallLock();
|
|
4302
|
+
if (!lock.release) {
|
|
4303
|
+
installFailureReasons.set(toolId, lock.reason ?? "install lock failed");
|
|
4304
|
+
noteInstallAttempt(toolId, "skipped", lock.reason ?? "install lock held");
|
|
4305
|
+
logSessionStart(`auto-install ensure ${toolId}: ${lock.reason}`);
|
|
4306
|
+
return void 0;
|
|
4307
|
+
}
|
|
4308
|
+
let installed;
|
|
4309
|
+
try {
|
|
4310
|
+
const installedByPeer = await getToolPath(toolId);
|
|
4311
|
+
if (installedByPeer) {
|
|
4312
|
+
noteInstallAttempt(toolId, "succeeded", "installed by a concurrent process");
|
|
4313
|
+
resolvedPathCache.set(toolId, installedByPeer);
|
|
4314
|
+
void updateProbeCache(toolId, installedByPeer, wasLastResolveTransient(toolId));
|
|
4315
|
+
return installedByPeer;
|
|
4316
|
+
}
|
|
4317
|
+
installed = await installTool(toolId, lock.ownsLock);
|
|
4318
|
+
} finally {
|
|
4319
|
+
await lock.release();
|
|
4320
|
+
}
|
|
4321
|
+
if (!installed) {
|
|
4322
|
+
noteInstallAttemptIfUnrecorded(toolId, "failed", "install failed");
|
|
4323
|
+
logSessionStart(`auto-install ensure ${toolId}: unavailable (${Date.now() - ensureStartMs}ms)`);
|
|
4324
|
+
return void 0;
|
|
4325
|
+
}
|
|
4326
|
+
const result = await getToolPath(toolId);
|
|
4327
|
+
if (result) {
|
|
4328
|
+
resolvedPathCache.set(toolId, result);
|
|
4329
|
+
void updateProbeCache(toolId, result, wasLastResolveTransient(toolId));
|
|
4330
|
+
logSessionStart(`auto-install ensure ${toolId}: success at ${result} (${Date.now() - ensureStartMs}ms)`);
|
|
4331
|
+
} else {
|
|
4332
|
+
logSessionStart(`auto-install ensure ${toolId}: unavailable (${Date.now() - ensureStartMs}ms)`);
|
|
4333
|
+
}
|
|
4334
|
+
return result;
|
|
4335
|
+
})();
|
|
4336
|
+
ensureInFlight.set(inFlightKey, ensurePromise);
|
|
4337
|
+
try {
|
|
4338
|
+
return await ensurePromise;
|
|
4339
|
+
} finally {
|
|
4340
|
+
if (ensureInFlight.get(inFlightKey) === ensurePromise) {
|
|
4341
|
+
ensureInFlight.delete(inFlightKey);
|
|
4342
|
+
}
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
4345
|
+
async function getToolEnvironment() {
|
|
4346
|
+
const localBin = path3.join(TOOLS_DIR, "node_modules", ".bin");
|
|
4347
|
+
const currentPath = process.env.PATH || process.env.Path || process.env.path || "";
|
|
4348
|
+
const separator = process.platform === "win32" ? ";" : ":";
|
|
4349
|
+
const nodeDir = path3.dirname(process.execPath);
|
|
4350
|
+
const withNode = nodeDir ? `${nodeDir}${separator}${currentPath}` : currentPath;
|
|
4351
|
+
const augmentedPath = `${GITHUB_BIN_DIR}${separator}${localBin}${separator}${withNode}`;
|
|
4352
|
+
const env = {
|
|
4353
|
+
...process.env,
|
|
4354
|
+
PATH: augmentedPath
|
|
4355
|
+
};
|
|
4356
|
+
if (process.platform === "win32") {
|
|
4357
|
+
env.Path = augmentedPath;
|
|
4358
|
+
}
|
|
4359
|
+
return env;
|
|
4360
|
+
}
|
|
4361
|
+
async function checkAllTools() {
|
|
4362
|
+
const results = [];
|
|
4363
|
+
for (const tool of TOOLS) {
|
|
4364
|
+
const path4 = await getToolPath(tool.id);
|
|
4365
|
+
results.push({
|
|
4366
|
+
id: tool.id,
|
|
4367
|
+
name: tool.name,
|
|
4368
|
+
installed: path4 !== void 0,
|
|
4369
|
+
path: path4
|
|
4370
|
+
});
|
|
4371
|
+
}
|
|
4372
|
+
return results;
|
|
4373
|
+
}
|
|
4374
|
+
function isKnownToolId(toolId) {
|
|
4375
|
+
return TOOLS.some((tool) => tool.id === toolId);
|
|
4376
|
+
}
|
|
4377
|
+
function getToolInstallStrategy(toolId) {
|
|
4378
|
+
return TOOLS.find((tool) => tool.id === toolId)?.installStrategy;
|
|
4379
|
+
}
|
|
4380
|
+
var GITHUB_TOOLS = [
|
|
4381
|
+
"cljfmt",
|
|
4382
|
+
"php-cs-fixer",
|
|
4383
|
+
"shellcheck",
|
|
4384
|
+
"shfmt",
|
|
4385
|
+
"rust-analyzer",
|
|
4386
|
+
"golangci-lint",
|
|
4387
|
+
"ktlint",
|
|
4388
|
+
"actionlint",
|
|
4389
|
+
"zizmor",
|
|
4390
|
+
"typos-lsp",
|
|
4391
|
+
"tflint",
|
|
4392
|
+
"terragrunt",
|
|
4393
|
+
"terraform-ls",
|
|
4394
|
+
"zls",
|
|
4395
|
+
"hadolint",
|
|
4396
|
+
"helm",
|
|
4397
|
+
"gitleaks",
|
|
4398
|
+
"taplo",
|
|
4399
|
+
"vale",
|
|
4400
|
+
"opengrep",
|
|
4401
|
+
"deno",
|
|
4402
|
+
"clojure-lsp",
|
|
4403
|
+
"cue",
|
|
4404
|
+
"gleam",
|
|
4405
|
+
"typstyle",
|
|
4406
|
+
"tinymist",
|
|
4407
|
+
"marksman",
|
|
4408
|
+
"expert"
|
|
4409
|
+
];
|
|
4410
|
+
function resolveGitHubAsset(toolId, platform, arch) {
|
|
4411
|
+
const tool = TOOLS.find((t) => t.id === toolId);
|
|
4412
|
+
return tool?.github?.assetMatch(platform, arch);
|
|
4413
|
+
}
|
|
4414
|
+
function resolveGitHubAssetLauncher(toolId, _platform, assetName) {
|
|
4415
|
+
const tool = TOOLS.find((t) => t.id === toolId);
|
|
4416
|
+
return tool?.github ? launcherForGitHubAsset(tool.github, assetName) : void 0;
|
|
4417
|
+
}
|
|
4418
|
+
function resolveGitHubInstalledBinaryName(toolId, platform, assetName) {
|
|
4419
|
+
const tool = TOOLS.find((t) => t.id === toolId);
|
|
4420
|
+
if (!tool)
|
|
4421
|
+
return void 0;
|
|
4422
|
+
return getGitHubInstalledBinaryName(tool.binaryName ?? tool.id, platform, assetName);
|
|
4423
|
+
}
|
|
4424
|
+
function resolveGitHubArchiveBinaryCandidates(toolId, platform, assetName) {
|
|
4425
|
+
const tool = TOOLS.find((t) => t.id === toolId);
|
|
4426
|
+
if (!tool)
|
|
4427
|
+
return void 0;
|
|
4428
|
+
const binaryName = tool.github?.binaryInArchive ?? tool.binaryName ?? tool.id;
|
|
4429
|
+
return getArchiveBinaryCandidates(binaryName, platform, assetName);
|
|
4430
|
+
}
|
|
4431
|
+
var ASSET_SIDECAR_SUFFIXES = [
|
|
4432
|
+
".asc",
|
|
4433
|
+
".sig",
|
|
4434
|
+
".minisig",
|
|
4435
|
+
".pem",
|
|
4436
|
+
".cert",
|
|
4437
|
+
".sbom",
|
|
4438
|
+
".sha256",
|
|
4439
|
+
".sha256sum",
|
|
4440
|
+
".md5"
|
|
4441
|
+
];
|
|
4442
|
+
function isAssetSidecar(name) {
|
|
4443
|
+
const lower = name.toLowerCase();
|
|
4444
|
+
return ASSET_SIDECAR_SUFFIXES.some((suffix) => lower.endsWith(suffix));
|
|
4445
|
+
}
|
|
4446
|
+
function pickReleaseAsset(assets, assetSubstring) {
|
|
4447
|
+
return assets.find((a) => a.name === assetSubstring) ?? assets.find((a) => a.name.includes(assetSubstring) && !isAssetSidecar(a.name));
|
|
4448
|
+
}
|
|
4449
|
+
function deriveHashiCorpReleaseAsset(tool, tagName, assetSubstring) {
|
|
4450
|
+
const product = tool.github?.hashiCorpReleaseProduct;
|
|
4451
|
+
if (!product || !tagName)
|
|
4452
|
+
return void 0;
|
|
4453
|
+
const version = tagName.replace(/^v/, "").trim();
|
|
4454
|
+
if (!version)
|
|
4455
|
+
return void 0;
|
|
4456
|
+
const assetName = `${product}_${version}_${assetSubstring}`;
|
|
4457
|
+
return {
|
|
4458
|
+
name: assetName,
|
|
4459
|
+
browser_download_url: `https://releases.hashicorp.com/${product}/${version}/${assetName}`
|
|
4460
|
+
};
|
|
4461
|
+
}
|
|
4462
|
+
function resolveDerivedHashiCorpReleaseAsset(toolId, tagName, platform, arch) {
|
|
4463
|
+
const tool = TOOLS.find((t) => t.id === toolId);
|
|
4464
|
+
if (!tool)
|
|
4465
|
+
return void 0;
|
|
4466
|
+
const assetSubstring = tool.github?.assetMatch(platform, arch);
|
|
4467
|
+
if (!assetSubstring)
|
|
4468
|
+
return void 0;
|
|
4469
|
+
return deriveHashiCorpReleaseAsset(tool, tagName, assetSubstring);
|
|
4470
|
+
}
|
|
4471
|
+
|
|
4472
|
+
export {
|
|
4473
|
+
commitDurableStore,
|
|
4474
|
+
commitDurableStoreAsync,
|
|
4475
|
+
findLocalZizmorConfig,
|
|
4476
|
+
isZizmorAuditTarget,
|
|
4477
|
+
resetZizmorTokenAvailability,
|
|
4478
|
+
resolveZizmorGitHubToken,
|
|
4479
|
+
logSessionStart,
|
|
4480
|
+
getManagedToolsDir,
|
|
4481
|
+
TOOLS,
|
|
4482
|
+
getToolVerificationTimeout,
|
|
4483
|
+
getInstallFailureReason,
|
|
4484
|
+
getInstallAttempt,
|
|
4485
|
+
getLastEnsureResolutionSource,
|
|
4486
|
+
resetResolvedPathCache,
|
|
4487
|
+
flushProbeCache,
|
|
4488
|
+
checkProbeCache,
|
|
4489
|
+
updateProbeCache,
|
|
4490
|
+
resetProbeCacheStateForTesting,
|
|
4491
|
+
_peekEnsureInFlightForTesting,
|
|
4492
|
+
resetPathWalkMemo,
|
|
4493
|
+
isCommandAvailable,
|
|
4494
|
+
isSpawnableCommand,
|
|
4495
|
+
isLspTransportRequiredError,
|
|
4496
|
+
parsePinnedVersion,
|
|
4497
|
+
packageEntryVerification,
|
|
4498
|
+
verifyNpmPackageEntry,
|
|
4499
|
+
verifyToolBinary,
|
|
4500
|
+
getAllToolStatuses,
|
|
4501
|
+
isToolInstalled,
|
|
4502
|
+
resolvePlatformPackageBinary,
|
|
4503
|
+
wasLastResolveTransient,
|
|
4504
|
+
getToolPath,
|
|
4505
|
+
findManagedToolBinary,
|
|
4506
|
+
userBaseProbeResult,
|
|
4507
|
+
npmToolNeedsPostinstall,
|
|
4508
|
+
getRefreshableManagedNpmTools,
|
|
4509
|
+
getRefreshableManagedTools,
|
|
4510
|
+
isManagedToolPresent,
|
|
4511
|
+
acquireManagedInstallGate,
|
|
4512
|
+
refreshManagedTool,
|
|
4513
|
+
resolveManagedNpmBinPath,
|
|
4514
|
+
invalidateManagedToolResolution,
|
|
4515
|
+
resolveArchiveUrl,
|
|
4516
|
+
resolveArchiveKind,
|
|
4517
|
+
swapExtractedDir,
|
|
4518
|
+
pipCommandCandidates,
|
|
4519
|
+
pipScriptsDir,
|
|
4520
|
+
installTool,
|
|
4521
|
+
ensureTool,
|
|
4522
|
+
getToolEnvironment,
|
|
4523
|
+
checkAllTools,
|
|
4524
|
+
isKnownToolId,
|
|
4525
|
+
getToolInstallStrategy,
|
|
4526
|
+
GITHUB_TOOLS,
|
|
4527
|
+
resolveGitHubAsset,
|
|
4528
|
+
resolveGitHubAssetLauncher,
|
|
4529
|
+
resolveGitHubInstalledBinaryName,
|
|
4530
|
+
resolveGitHubArchiveBinaryCandidates,
|
|
4531
|
+
pickReleaseAsset,
|
|
4532
|
+
resolveDerivedHashiCorpReleaseAsset
|
|
4533
|
+
};
|