blun-king-cli 9.0.0 → 9.1.0
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/LIESMICH.txt +32 -13
- package/README.md +39 -20
- package/bin/blun.js +248 -160
- package/bin/core-bootstrap.js +47 -0
- package/bin/king.js +277 -1
- package/bin/launcher-mode.js +15 -3
- package/bin/launcher-runtime.js +293 -0
- package/bin/plugin-bootstrap.js +11 -4
- package/bin/private-paths.js +1577 -10
- package/bin/update-lease.js +399 -0
- package/bin/update-notice.js +1245 -0
- package/blun.mjs +26606 -25239
- package/dist-web/assets/{CodeBlockNode-Bsvp6ZEy.js → CodeBlockNode-CXnTLMNr.js} +1 -1
- package/dist-web/assets/{CodeBlockShell.vue_vue_type_style_index_0_lang-ta7hLjia.js → CodeBlockShell.vue_vue_type_style_index_0_lang-CCF8Ntnq.js} +1 -1
- package/dist-web/assets/{DesignSystemView-BmDDe28A.js → DesignSystemView-BbMOu-XO.js} +1 -1
- package/dist-web/assets/{Tooltip-nEEfaYVY.js → Tooltip-CFQEzqC-.js} +1 -1
- package/dist-web/assets/abnfDiagram-N423BO3Z-CsByy-GW.js +1 -0
- package/dist-web/assets/abnfDiagram-N423BO3Z-Z1APZG5m.js +1 -0
- package/dist-web/assets/arc-C-LXCaja.js +1 -0
- package/dist-web/assets/arc-C4VyWloT.js +1 -0
- package/dist-web/assets/architectureDiagram-T3A2C74G-DZq4L18e.js +36 -0
- package/dist-web/assets/architectureDiagram-T3A2C74G-DpddbUcM.js +36 -0
- package/dist-web/assets/blockDiagram-VBNYF7ZC-BV4sIf98.js +132 -0
- package/dist-web/assets/blockDiagram-VBNYF7ZC-BqccOSMr.js +132 -0
- package/dist-web/assets/{c4Diagram-AAUBKEIU-DtzacMnD.js → c4Diagram-5PPSVZJV-0MRkZlgP.js} +1 -1
- package/dist-web/assets/{c4Diagram-AAUBKEIU-D1VdMx8d.js → c4Diagram-5PPSVZJV-OHAcJrTy.js} +1 -1
- package/dist-web/assets/channel-B2VOQOI-.js +1 -0
- package/dist-web/assets/channel-DKjwM05G.js +1 -0
- package/dist-web/assets/{chunk-ND2GUHAM-DJoY9wNJ.js → chunk-2GRJ4B5K-DqJHs8Fw.js} +1 -1
- package/dist-web/assets/{chunk-ND2GUHAM-C4-rwdcv.js → chunk-2GRJ4B5K-oO6nqeLA.js} +1 -1
- package/dist-web/assets/{chunk-QZHKN3VN-C7YGaShq.js → chunk-2Q5K7J3B-B1nCd91o.js} +1 -1
- package/dist-web/assets/{chunk-QZHKN3VN-_Rz9_NuS.js → chunk-2Q5K7J3B-BC0Hsfqy.js} +1 -1
- package/dist-web/assets/chunk-5RXB4S5H-BfZa4yDH.js +231 -0
- package/dist-web/assets/chunk-5RXB4S5H-neG70kZx.js +231 -0
- package/dist-web/assets/{chunk-FMBD7UC4-DlLNDUx6.js → chunk-5VM5RSS4-l7biGqeH.js} +1 -1
- package/dist-web/assets/{chunk-FMBD7UC4-B2zs_Y-d.js → chunk-5VM5RSS4-u00AcpiJ.js} +1 -1
- package/dist-web/assets/chunk-6Q2QTUOP-BO-F11Gs.js +88 -0
- package/dist-web/assets/chunk-6Q2QTUOP-RUVRPBzG.js +88 -0
- package/dist-web/assets/{chunk-727SXJPM-Dn5wkDO_.js → chunk-GF5L2VYU-Ba-86KTd.js} +5 -5
- package/dist-web/assets/{chunk-727SXJPM-Dk2Xa3bA.js → chunk-GF5L2VYU-BtC69fw0.js} +5 -5
- package/dist-web/assets/{chunk-4BX2VUAB-DxZViaHS.js → chunk-JWPE2WC7-DIOubEqr.js} +1 -1
- package/dist-web/assets/{chunk-4BX2VUAB-WqrE2gaw.js → chunk-JWPE2WC7-u8Pcfhwh.js} +1 -1
- package/dist-web/assets/{chunk-2J33WTMH-Dm_s0_4f.js → chunk-KBJHAD2P-DDUeH_Ba.js} +1 -1
- package/dist-web/assets/{chunk-2J33WTMH-w4sdiKFO.js → chunk-KBJHAD2P-DS3BL64y.js} +1 -1
- package/dist-web/assets/chunk-RYQCIY6F-B9gFMVbS.js +1 -0
- package/dist-web/assets/chunk-RYQCIY6F-xg3EO0le.js +1 -0
- package/dist-web/assets/{chunk-55IACEB6-B5dE1-Um.js → chunk-XXDRQBXY-B60H0rRC.js} +1 -1
- package/dist-web/assets/{chunk-55IACEB6-B12zGIFz.js → chunk-XXDRQBXY-Bn1Xz6TQ.js} +1 -1
- package/dist-web/assets/classDiagram-JCYQIIEL-BA1TjAxj.js +1 -0
- package/dist-web/assets/classDiagram-JCYQIIEL-BNU7Qn4k.js +1 -0
- package/dist-web/assets/classDiagram-v2-OCEON4UE-BA1TjAxj.js +1 -0
- package/dist-web/assets/classDiagram-v2-OCEON4UE-BNU7Qn4k.js +1 -0
- package/dist-web/assets/{cose-bilkent-S5V4N54A-Bi19n8m-.js → cose-bilkent-JH36ORCC-4Ybddg08.js} +1 -1
- package/dist-web/assets/{cose-bilkent-S5V4N54A-c8o8NFp5.js → cose-bilkent-JH36ORCC-DqigO3T4.js} +1 -1
- package/dist-web/assets/cynefin-VYW2F7L2-C-EfYT07.js +178 -0
- package/dist-web/assets/cynefin-VYW2F7L2-rpD93tQ-.js +178 -0
- package/dist-web/assets/cynefinDiagram-MW4NZA55-DWZPLJcD.js +62 -0
- package/dist-web/assets/cynefinDiagram-MW4NZA55-DbTqnkVJ.js +62 -0
- package/dist-web/assets/dagre-VZM6K2ZE-DcM8aEVY.js +4 -0
- package/dist-web/assets/dagre-VZM6K2ZE-M_HVNtqP.js +4 -0
- package/dist-web/assets/diagram-7IWD3JNH-DoNximNF.js +30 -0
- package/dist-web/assets/diagram-7IWD3JNH-UymgsBNa.js +30 -0
- package/dist-web/assets/{diagram-KO2AKTUF-CFQs13i5.js → diagram-B4RE2ZJO-BVA2EhnC.js} +3 -3
- package/dist-web/assets/{diagram-KO2AKTUF-agFIuPJW.js → diagram-B4RE2ZJO-BiYu5WPF.js} +3 -3
- package/dist-web/assets/{diagram-LMA3HP47-TMIXizxR.js → diagram-LBJQPF4R-BD3kkgOO.js} +2 -2
- package/dist-web/assets/{diagram-LMA3HP47-He5zlyMt.js → diagram-LBJQPF4R-m3a1Euj3.js} +2 -2
- package/dist-web/assets/diagram-Q27KOJAE-D1mYQ_cI.js +24 -0
- package/dist-web/assets/diagram-Q27KOJAE-hy_T73PK.js +24 -0
- package/dist-web/assets/diagram-UB23O5K3-BWkoQZ4A.js +41 -0
- package/dist-web/assets/diagram-UB23O5K3-BtX8u6HG.js +41 -0
- package/dist-web/assets/ebnfDiagram-BXEA7PRR-BKs9JUHW.js +1 -0
- package/dist-web/assets/ebnfDiagram-BXEA7PRR-CBAnFkRF.js +1 -0
- package/dist-web/assets/erDiagram-JOGREHBK-3vKcWsxm.js +85 -0
- package/dist-web/assets/erDiagram-JOGREHBK-BIpd6dTf.js +85 -0
- package/dist-web/assets/flowDiagram-UKHOOZJN-D9wBbHm2.js +156 -0
- package/dist-web/assets/flowDiagram-UKHOOZJN-Z83i2UWI.js +156 -0
- package/dist-web/assets/ganttDiagram-PKOTCBZU-Cd_dViF-.js +292 -0
- package/dist-web/assets/ganttDiagram-PKOTCBZU-DFrb_S1d.js +292 -0
- package/dist-web/assets/{gitGraphDiagram-PVQCEYII-BSDFfwdY.js → gitGraphDiagram-DS77QQ5N-BiEEtTo7.js} +3 -3
- package/dist-web/assets/{gitGraphDiagram-PVQCEYII-DMbTATyw.js → gitGraphDiagram-DS77QQ5N-Dp3Ar-hi.js} +3 -3
- package/dist-web/assets/{graph-BwjfAU3j.js → graph-CAUpWmoK.js} +1 -1
- package/dist-web/assets/{graph--OzhPTMs.js → graph-DOmOIIwC.js} +1 -1
- package/dist-web/assets/{index-W6VnSrkB.js → index-Ba1OvLjf.js} +2 -2
- package/dist-web/assets/{index-CkVgGDpx.js → index-CiPA8Kex.js} +4 -4
- package/dist-web/assets/{index-BzK4vJBu.js → index-DdEakFJM.js} +1 -1
- package/dist-web/assets/{index10-Czg_oF-W.js → index10-CNr4U-Nw.js} +1 -1
- package/dist-web/assets/{index11-BjzCZuCr.js → index11-Dto_yZNz.js} +1 -1
- package/dist-web/assets/{index5-DlVoNkCD.js → index5-B2PldaO2.js} +1 -1
- package/dist-web/assets/{index6-BxiXA2yo.js → index6-CBdZmmyx.js} +1 -1
- package/dist-web/assets/{index7-BPdza6d1.js → index7-Dz9oCsUF.js} +1 -1
- package/dist-web/assets/{index8-Q_XzGC1Z.js → index8-B6k25jtU.js} +2 -2
- package/dist-web/assets/{infoDiagram-5YYISTIA-2JF3XEdD.js → infoDiagram-6WML65LV-BSa2g04G.js} +1 -1
- package/dist-web/assets/{infoDiagram-5YYISTIA-UrQusrJq.js → infoDiagram-6WML65LV-D4ph2sIH.js} +1 -1
- package/dist-web/assets/{ishikawaDiagram-YF4QCWOH-7RVUL-vB.js → ishikawaDiagram-WSZJBQD7-DjKjbMm9.js} +5 -5
- package/dist-web/assets/{ishikawaDiagram-YF4QCWOH-C5OstRVV.js → ishikawaDiagram-WSZJBQD7-Hm36UOQe.js} +5 -5
- package/dist-web/assets/{journeyDiagram-JHISSGLW-B34fCMu9.js → journeyDiagram-NVQOT4AX-DttHDfPQ.js} +1 -1
- package/dist-web/assets/{journeyDiagram-JHISSGLW-yctdo4bX.js → journeyDiagram-NVQOT4AX-MvWWPNDV.js} +1 -1
- package/dist-web/assets/{kanban-definition-UN3LZRKU-BS4FBZsQ.js → kanban-definition-27J2QSJJ-C5gB-W0d.js} +7 -7
- package/dist-web/assets/{kanban-definition-UN3LZRKU-CxBCv27E.js → kanban-definition-27J2QSJJ-Cu-E_cXA.js} +5 -5
- package/dist-web/assets/layout-CvP6o9AK.js +1 -0
- package/dist-web/assets/layout-D-LzfAck.js +1 -0
- package/dist-web/assets/{linear-3mB6q2-g.js → linear-B3HWgQI2.js} +1 -1
- package/dist-web/assets/{linear-BKCnUo31.js → linear-BRGe4-d6.js} +1 -1
- package/dist-web/assets/map-CWKZqZjr.js +1 -0
- package/dist-web/assets/map-DxJ2ADlA.js +1 -0
- package/dist-web/assets/mermaid.core-D-9Sx1No.js +314 -0
- package/dist-web/assets/mermaidParser.worker-PTFfzpoB.js +318 -0
- package/dist-web/assets/{mindmap-definition-RKZ34NQL-BmJZpjh6.js → mindmap-definition-FAOFIHXS-C4vnms3i.js} +2 -2
- package/dist-web/assets/{mindmap-definition-RKZ34NQL-1mjWUcf6.js → mindmap-definition-FAOFIHXS-Cg_hoVIq.js} +1 -1
- package/dist-web/assets/pegDiagram-VL7TDLO6-B2fFEFUH.js +1 -0
- package/dist-web/assets/pegDiagram-VL7TDLO6-CSX9hitD.js +1 -0
- package/dist-web/assets/pieDiagram-7S7Q4E2Y-0L0mxahZ.js +39 -0
- package/dist-web/assets/pieDiagram-7S7Q4E2Y-C7T0Cmw1.js +39 -0
- package/dist-web/assets/{quadrantDiagram-W4KKPZXB-BmT_qM09.js → quadrantDiagram-CIZ2JOQS-BBy7w4kD.js} +3 -3
- package/dist-web/assets/{quadrantDiagram-W4KKPZXB-C0kPuyNP.js → quadrantDiagram-CIZ2JOQS-U483z0er.js} +3 -3
- package/dist-web/assets/railroadDiagram-AXF67PYL-D80a2_DJ.js +1 -0
- package/dist-web/assets/railroadDiagram-AXF67PYL-DX8y0MzW.js +1 -0
- package/dist-web/assets/{requirementDiagram-4Y6WPE33-C0YP_Amt.js → requirementDiagram-LRYGKXZP-CeyHfq8t.js} +3 -3
- package/dist-web/assets/{requirementDiagram-4Y6WPE33-CZrxH1Y2.js → requirementDiagram-LRYGKXZP-DGD5J_4k.js} +3 -3
- package/dist-web/assets/sankeyDiagram-W5VNT64P-CCISkkrR.js +40 -0
- package/dist-web/assets/sankeyDiagram-W5VNT64P-CT5PdZz2.js +40 -0
- package/dist-web/assets/sequenceDiagram-SI44F4Z6-BeMzHWXI.js +162 -0
- package/dist-web/assets/sequenceDiagram-SI44F4Z6-jQfm2XS7.js +162 -0
- package/dist-web/assets/sizeCapture-X5ZJPWSS-BQsTdtrv.js +1 -0
- package/dist-web/assets/sizeCapture-X5ZJPWSS-lRZsmCoS.js +1 -0
- package/dist-web/assets/stateDiagram-OKZ733FA-ClCgTdUm.js +1 -0
- package/dist-web/assets/stateDiagram-OKZ733FA-Cv59z5YT.js +1 -0
- package/dist-web/assets/stateDiagram-v2-UEYNNEHI-R8jTc--F.js +1 -0
- package/dist-web/assets/stateDiagram-v2-UEYNNEHI-fi_lrTAx.js +1 -0
- package/dist-web/assets/swimlanes-SLNWSIFB-BCdOUN05.js +2 -0
- package/dist-web/assets/swimlanes-SLNWSIFB-Cu5l7SuR.js +1 -0
- package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-BUd_uYK3.js +8 -0
- package/dist-web/assets/swimlanesDiagram-ULZ7WXOC-CalrsgPK.js +8 -0
- package/dist-web/assets/{timeline-definition-PNZ67QCA-DsVZdrZa.js → timeline-definition-Z64GVDOM-CMjnh4bu.js} +4 -4
- package/dist-web/assets/{timeline-definition-PNZ67QCA-DnB6Usl0.js → timeline-definition-Z64GVDOM-DpWutSKa.js} +4 -4
- package/dist-web/assets/vennDiagram-T6HMQDX7-DKJW_8oU.js +34 -0
- package/dist-web/assets/vennDiagram-T6HMQDX7-DVC5JVsc.js +34 -0
- package/dist-web/assets/{vue.runtime.esm-bundler-pgHGheCW.js → vue.runtime.esm-bundler-BwwRtQwQ.js} +1 -1
- package/dist-web/assets/{wardleyDiagram-YWT4CUSO-oBauTCEw.js → wardleyDiagram-T6FBY63Y-BK2mrq7L.js} +3 -3
- package/dist-web/assets/{wardleyDiagram-YWT4CUSO-DCiyDQNB.js → wardleyDiagram-T6FBY63Y-oUvgepFn.js} +3 -3
- package/dist-web/assets/xychartDiagram-ELKLHX3M-DI6n79q0.js +7 -0
- package/dist-web/assets/xychartDiagram-ELKLHX3M-DUJu9F7-.js +7 -0
- package/dist-web/index.html +1 -1
- package/dist-web/vis/index.html.gz +0 -0
- package/native/win32/prebuilds/win32-arm64/win32-console-mode.node +0 -0
- package/native/win32/prebuilds/win32-x64/win32-console-mode.node +0 -0
- package/package.json +9 -11
- package/skills/screenshot-lesen/SKILL.md +0 -1
- package/skills/web-lesen/SKILL.md +0 -1
- package/telegram-plugin/dist/bridge.mjs +7 -25
- package/telegram-plugin/dist/mcp-server.mjs +236 -71
- package/dist-web/assets/arc-4xPfymbf.js +0 -1
- package/dist-web/assets/arc-Doj0wRZ0.js +0 -1
- package/dist-web/assets/architectureDiagram-3BPJPVTR-BGih283a.js +0 -36
- package/dist-web/assets/architectureDiagram-3BPJPVTR-CQ09RrbH.js +0 -36
- package/dist-web/assets/blockDiagram-GPEHLZMM-3ShMf1gu.js +0 -132
- package/dist-web/assets/blockDiagram-GPEHLZMM-CAtDjkJt.js +0 -132
- package/dist-web/assets/channel-DNkUo9e6.js +0 -1
- package/dist-web/assets/channel-DcnUTIGB.js +0 -1
- package/dist-web/assets/chunk-AQP2D5EJ-FS8-f8lF.js +0 -231
- package/dist-web/assets/chunk-AQP2D5EJ-UKhb4V4r.js +0 -231
- package/dist-web/assets/classDiagram-4FO5ZUOK-B1ZO8EbE.js +0 -1
- package/dist-web/assets/classDiagram-4FO5ZUOK-xk6cM9Kq.js +0 -1
- package/dist-web/assets/classDiagram-v2-Q7XG4LA2-B1ZO8EbE.js +0 -1
- package/dist-web/assets/classDiagram-v2-Q7XG4LA2-xk6cM9Kq.js +0 -1
- package/dist-web/assets/dagre-BM42HDAG-CmUO9qKN.js +0 -4
- package/dist-web/assets/dagre-BM42HDAG-DLMfLCoV.js +0 -4
- package/dist-web/assets/diagram-2AECGRRQ-DQTst0OH.js +0 -43
- package/dist-web/assets/diagram-2AECGRRQ-L74iLMqd.js +0 -43
- package/dist-web/assets/diagram-5GNKFQAL-BiBm7CJR.js +0 -10
- package/dist-web/assets/diagram-5GNKFQAL-DoE6q7H1.js +0 -10
- package/dist-web/assets/diagram-OG6HWLK6-BHCt1E0n.js +0 -24
- package/dist-web/assets/diagram-OG6HWLK6-DRyxjFMa.js +0 -24
- package/dist-web/assets/erDiagram-TEJ5UH35-BoDczjGG.js +0 -85
- package/dist-web/assets/erDiagram-TEJ5UH35-yd6SOv_7.js +0 -85
- package/dist-web/assets/flowDiagram-I6XJVG4X-DHoceI7r.js +0 -162
- package/dist-web/assets/flowDiagram-I6XJVG4X-DkYNQv0R.js +0 -162
- package/dist-web/assets/ganttDiagram-6RSMTGT7-Cg1TQBc0.js +0 -292
- package/dist-web/assets/ganttDiagram-6RSMTGT7-Z6klo7KK.js +0 -292
- package/dist-web/assets/layout-C1ojF0zw.js +0 -1
- package/dist-web/assets/layout-SsrduOYp.js +0 -1
- package/dist-web/assets/mermaid.core-D_lKT0dR.js +0 -309
- package/dist-web/assets/mermaidParser.worker-Dx4jPi9z.js +0 -313
- package/dist-web/assets/pieDiagram-4H26LBE5-BU41t2A_.js +0 -30
- package/dist-web/assets/pieDiagram-4H26LBE5-BkwDAvbt.js +0 -30
- package/dist-web/assets/sankeyDiagram-5OEKKPKP-De_o7hDr.js +0 -40
- package/dist-web/assets/sankeyDiagram-5OEKKPKP-Dvuj6bMA.js +0 -40
- package/dist-web/assets/sequenceDiagram-3UESZ5HK-DuCrgPyA.js +0 -162
- package/dist-web/assets/sequenceDiagram-3UESZ5HK-VAxffBe7.js +0 -162
- package/dist-web/assets/stateDiagram-AJRCARHV-CYLupxM7.js +0 -1
- package/dist-web/assets/stateDiagram-AJRCARHV-nDExQydZ.js +0 -1
- package/dist-web/assets/stateDiagram-v2-BHNVJYJU-BhEc7Thx.js +0 -1
- package/dist-web/assets/stateDiagram-v2-BHNVJYJU-BzwD_BIl.js +0 -1
- package/dist-web/assets/vennDiagram-CIIHVFJN-BBHQmCFf.js +0 -34
- package/dist-web/assets/vennDiagram-CIIHVFJN-Cfj-K9NW.js +0 -34
- package/dist-web/assets/wardley-L42UT6IY-BJFn8eDD.js +0 -173
- package/dist-web/assets/wardley-L42UT6IY-BSu8-4hg.js +0 -173
- package/dist-web/assets/xychartDiagram-2RQKCTM6-C2iZpl9I.js +0 -7
- package/dist-web/assets/xychartDiagram-2RQKCTM6-Db_VoDBV.js +0 -7
- package/mnemo/access_routes.js +0 -692
- package/mnemo/agent_governance.js +0 -4242
- package/mnemo/agent_mail.js +0 -901
- package/mnemo/bootstrap_auto.js +0 -137
- package/mnemo/brief_coordination.js +0 -226
- package/mnemo/code_read_tools.js +0 -375
- package/mnemo/context_preview_tools.js +0 -603
- package/mnemo/embeddings.js +0 -66
- package/mnemo/external_repo_ops.js +0 -575
- package/mnemo/facts/example-project-rules.json +0 -90
- package/mnemo/facts/example.json +0 -34
- package/mnemo/identity_schema.sql +0 -139
- package/mnemo/journal_schema.js +0 -561
- package/mnemo/loop_doctor_tools.js +0 -661
- package/mnemo/mail_secret_refs.js +0 -150
- package/mnemo/mcp.js +0 -9309
- package/mnemo/memory_consolidation.js +0 -1914
- package/mnemo/memory_health_tools.js +0 -165
- package/mnemo/package.json +0 -79
- package/mnemo/protected_scope_gate.js +0 -627
- package/mnemo/resource_access_control.js +0 -684
- package/mnemo/runtime_governance.js +0 -1256
- package/mnemo/runtime_turn_gate.js +0 -862
- package/mnemo/sandbox.js +0 -143
- package/mnemo/schema.sql +0 -389
- package/mnemo/shared_utils.js +0 -763
- package/mnemo/skills/agent-auto-resume/SKILL.md +0 -56
- package/mnemo/skills/agent_hand/SKILL.md +0 -43
- package/mnemo/skills/agent_hand/run.js +0 -63
- package/mnemo/skills/book_flight/SKILL.md +0 -34
- package/mnemo/skills/external_repo_review/SKILL.md +0 -43
- package/mnemo/skills/external_repo_review/run.js +0 -73
- package/mnemo/skills/pay_invoice/SKILL.md +0 -34
- package/mnemo/team_quality_ops.js +0 -944
- package/mnemo/timeline_report_tools.js +0 -810
- package/mnemo/write_gate_risk.js +0 -80
- package/mnemo/writer_health.js +0 -152
- package/skills/doku-ingestion/SKILL.md +0 -48
- package/skills/doku-ingestion/ingest_docs.py +0 -133
|
@@ -0,0 +1,1245 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { randomUUID } = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const https = require('node:https');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { fork } = require('node:child_process');
|
|
8
|
+
const { StringDecoder } = require('node:string_decoder');
|
|
9
|
+
const { types } = require('node:util');
|
|
10
|
+
|
|
11
|
+
const { tryAcquireUpdateLease } = require('./update-lease');
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
ensurePrivateDirectory,
|
|
15
|
+
securePrivateFile,
|
|
16
|
+
writePrivateFile,
|
|
17
|
+
} = require('./private-paths');
|
|
18
|
+
|
|
19
|
+
const PACKAGE_NAME = 'blun-king-cli';
|
|
20
|
+
const REGISTRY_URL = 'https://registry.npmjs.org/blun-king-cli';
|
|
21
|
+
const INSTALL_REGISTRY_URL = 'https://registry.npmjs.org/';
|
|
22
|
+
const FALLBACK_MANIFEST_URL = 'https://chat.blun.ai/blun-code-version.json';
|
|
23
|
+
const SNOOZE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
24
|
+
const REQUEST_TIMEOUT_MS = 2_500;
|
|
25
|
+
const STATE_FILE = 'update-notice.json';
|
|
26
|
+
const UPDATE_DIRECTORY = 'updates';
|
|
27
|
+
const UPDATE_KEY_ESCAPE_TIMEOUT_MS = 500;
|
|
28
|
+
const UPDATE_SIGNAL_EXIT_CODES = Object.freeze({
|
|
29
|
+
SIGHUP: 129,
|
|
30
|
+
SIGINT: 130,
|
|
31
|
+
SIGTERM: 143,
|
|
32
|
+
});
|
|
33
|
+
const SEMVER_IDENTIFIER = '(?:0|[1-9]\\d*|\\d*[A-Za-z-][0-9A-Za-z-]*)';
|
|
34
|
+
const SEMVER_PATTERN = new RegExp(
|
|
35
|
+
`^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)`
|
|
36
|
+
+ `(?:-(${SEMVER_IDENTIFIER}(?:\\.${SEMVER_IDENTIFIER})*))?`
|
|
37
|
+
+ '(?:\\+([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?$',
|
|
38
|
+
);
|
|
39
|
+
const MANIFEST_KEYS = Object.freeze([
|
|
40
|
+
'install',
|
|
41
|
+
'latest',
|
|
42
|
+
'minSupported',
|
|
43
|
+
'name',
|
|
44
|
+
'notesUrl',
|
|
45
|
+
'releasedAt',
|
|
46
|
+
]);
|
|
47
|
+
const ENDPOINT_LIMITS = new Map([
|
|
48
|
+
[REGISTRY_URL, 1024 * 1024],
|
|
49
|
+
[FALLBACK_MANIFEST_URL, 64 * 1024],
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
function parseSemver(value) {
|
|
53
|
+
if (typeof value !== 'string' || value.length > 128) return undefined;
|
|
54
|
+
const match = SEMVER_PATTERN.exec(value);
|
|
55
|
+
if (!match) return undefined;
|
|
56
|
+
return {
|
|
57
|
+
raw: value,
|
|
58
|
+
core: [match[1], match[2], match[3]],
|
|
59
|
+
prerelease: match[4] === undefined ? undefined : match[4].split('.'),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function compareNumericIdentifier(left, right) {
|
|
64
|
+
if (left.length !== right.length) return left.length < right.length ? -1 : 1;
|
|
65
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function comparePrerelease(left, right) {
|
|
69
|
+
if (left === undefined && right === undefined) return 0;
|
|
70
|
+
if (left === undefined) return 1;
|
|
71
|
+
if (right === undefined) return -1;
|
|
72
|
+
const length = Math.max(left.length, right.length);
|
|
73
|
+
for (let index = 0; index < length; index += 1) {
|
|
74
|
+
if (left[index] === undefined) return -1;
|
|
75
|
+
if (right[index] === undefined) return 1;
|
|
76
|
+
const leftNumeric = /^\d+$/.test(left[index]);
|
|
77
|
+
const rightNumeric = /^\d+$/.test(right[index]);
|
|
78
|
+
if (leftNumeric && rightNumeric) {
|
|
79
|
+
const comparison = compareNumericIdentifier(left[index], right[index]);
|
|
80
|
+
if (comparison !== 0) return comparison;
|
|
81
|
+
} else if (leftNumeric !== rightNumeric) {
|
|
82
|
+
return leftNumeric ? -1 : 1;
|
|
83
|
+
} else if (left[index] !== right[index]) {
|
|
84
|
+
return left[index] < right[index] ? -1 : 1;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return 0;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function compareSemver(leftValue, rightValue) {
|
|
91
|
+
const left = parseSemver(leftValue);
|
|
92
|
+
const right = parseSemver(rightValue);
|
|
93
|
+
if (!left || !right) return undefined;
|
|
94
|
+
for (let index = 0; index < left.core.length; index += 1) {
|
|
95
|
+
const comparison = compareNumericIdentifier(left.core[index], right.core[index]);
|
|
96
|
+
if (comparison !== 0) return comparison;
|
|
97
|
+
}
|
|
98
|
+
return comparePrerelease(left.prerelease, right.prerelease);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function exactDataRecord(value, expectedKeys) {
|
|
102
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value) || types.isProxy(value)) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
const prototype = Object.getPrototypeOf(value);
|
|
106
|
+
if (prototype !== Object.prototype && prototype !== null) return undefined;
|
|
107
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
108
|
+
const keys = Object.keys(descriptors).toSorted();
|
|
109
|
+
if (keys.length !== expectedKeys.length
|
|
110
|
+
|| keys.some((key, index) => key !== expectedKeys[index])) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
for (const key of keys) {
|
|
114
|
+
if (!Object.hasOwn(descriptors[key], 'value') || descriptors[key].enumerable !== true) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return Object.fromEntries(keys.map((key) => [key, descriptors[key].value]));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function readDataProperty(value, key) {
|
|
122
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value) || types.isProxy(value)) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
const prototype = Object.getPrototypeOf(value);
|
|
126
|
+
if (prototype !== Object.prototype && prototype !== null) return undefined;
|
|
127
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
128
|
+
if (!descriptor || !Object.hasOwn(descriptor, 'value') || descriptor.enumerable !== true) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
return descriptor.value;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function parseRegistryDocument(value) {
|
|
135
|
+
const tags = readDataProperty(value, 'dist-tags');
|
|
136
|
+
const latest = readDataProperty(tags, 'latest');
|
|
137
|
+
if (!parseSemver(latest)) return undefined;
|
|
138
|
+
const parsed = { latest };
|
|
139
|
+
const next = readDataProperty(tags, 'next');
|
|
140
|
+
if (parseSemver(next)) parsed.next = next;
|
|
141
|
+
return Object.freeze(parsed);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function isBoundedText(value, maxBytes) {
|
|
145
|
+
return typeof value === 'string'
|
|
146
|
+
&& value.length > 0
|
|
147
|
+
&& Buffer.byteLength(value, 'utf8') <= maxBytes
|
|
148
|
+
&& !/[\u0000-\u001F\u007F-\u009F\u061C\u200B-\u200F\u202A-\u202E\u2060-\u206F\uFEFF]/u.test(value);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function canonicalOfficialNotesUrl(value) {
|
|
152
|
+
if (!isBoundedText(value, 2_048)) return undefined;
|
|
153
|
+
try {
|
|
154
|
+
const url = new URL(value);
|
|
155
|
+
const officialHost = url.hostname === 'blun.ai' || url.hostname.endsWith('.blun.ai');
|
|
156
|
+
if (url.protocol !== 'https:'
|
|
157
|
+
|| !officialHost
|
|
158
|
+
|| url.username !== ''
|
|
159
|
+
|| url.password !== ''
|
|
160
|
+
|| url.port !== '') {
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
return url.href;
|
|
164
|
+
} catch {
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function isIsoTimestamp(value) {
|
|
170
|
+
if (typeof value !== 'string') return false;
|
|
171
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,3})?Z$/.exec(value);
|
|
172
|
+
if (!match) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
const timestamp = Date.parse(value);
|
|
176
|
+
if (!Number.isFinite(timestamp)) return false;
|
|
177
|
+
const date = new Date(timestamp);
|
|
178
|
+
return date.getUTCFullYear() === Number(match[1])
|
|
179
|
+
&& date.getUTCMonth() + 1 === Number(match[2])
|
|
180
|
+
&& date.getUTCDate() === Number(match[3])
|
|
181
|
+
&& date.getUTCHours() === Number(match[4])
|
|
182
|
+
&& date.getUTCMinutes() === Number(match[5])
|
|
183
|
+
&& date.getUTCSeconds() === Number(match[6]);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function parseFallbackManifest(value) {
|
|
187
|
+
const record = exactDataRecord(value, MANIFEST_KEYS);
|
|
188
|
+
const notesUrl = record ? canonicalOfficialNotesUrl(record.notesUrl) : undefined;
|
|
189
|
+
if (!record
|
|
190
|
+
|| record.name !== PACKAGE_NAME
|
|
191
|
+
|| !parseSemver(record.latest)
|
|
192
|
+
|| !parseSemver(record.minSupported)
|
|
193
|
+
|| compareSemver(record.minSupported, record.latest) === 1
|
|
194
|
+
|| !isIsoTimestamp(record.releasedAt)
|
|
195
|
+
|| !notesUrl
|
|
196
|
+
|| !isBoundedText(record.install, 256)) {
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
return Object.freeze({
|
|
200
|
+
name: record.name,
|
|
201
|
+
latest: record.latest,
|
|
202
|
+
minSupported: record.minSupported,
|
|
203
|
+
releasedAt: record.releasedAt,
|
|
204
|
+
notesUrl,
|
|
205
|
+
install: record.install,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function resolveEffectiveRelease(currentVersion, registryTags, fallbackManifest) {
|
|
210
|
+
if (!parseSemver(currentVersion)) return undefined;
|
|
211
|
+
const registryLatest = readDataProperty(registryTags, 'latest');
|
|
212
|
+
const validRegistryLatest = parseSemver(registryLatest) ? registryLatest : undefined;
|
|
213
|
+
const registryNext = readDataProperty(registryTags, 'next');
|
|
214
|
+
const validRegistryNext = parseSemver(registryNext) ? registryNext : undefined;
|
|
215
|
+
const validFallback = parseFallbackManifest(fallbackManifest);
|
|
216
|
+
if (!validRegistryLatest && !validFallback) return undefined;
|
|
217
|
+
|
|
218
|
+
if (validRegistryLatest && compareSemver(currentVersion, validRegistryLatest) > 0) {
|
|
219
|
+
if (!validRegistryNext || compareSemver(validRegistryNext, currentVersion) <= 0) {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
return { channel: 'next', version: validRegistryNext };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let version;
|
|
226
|
+
if (validRegistryLatest && validFallback) {
|
|
227
|
+
version = compareSemver(validRegistryLatest, validFallback.latest) <= 0
|
|
228
|
+
? validRegistryLatest
|
|
229
|
+
: validFallback.latest;
|
|
230
|
+
} else {
|
|
231
|
+
version = validRegistryLatest || validFallback.latest;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (compareSemver(version, currentVersion) <= 0) return undefined;
|
|
235
|
+
|
|
236
|
+
if (validFallback && compareSemver(validFallback.latest, version) === 0) {
|
|
237
|
+
return { channel: 'stable', version, notesUrl: validFallback.notesUrl };
|
|
238
|
+
}
|
|
239
|
+
return { channel: 'stable', version };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function requestTrustedJson(url, options = {}) {
|
|
243
|
+
const maxBytes = ENDPOINT_LIMITS.get(url);
|
|
244
|
+
if (maxBytes === undefined) {
|
|
245
|
+
return Promise.reject(new Error('UNTRUSTED_UPDATE_ENDPOINT'));
|
|
246
|
+
}
|
|
247
|
+
const httpsImpl = options.httpsImpl || https;
|
|
248
|
+
const timeoutMs = options.timeoutMs || REQUEST_TIMEOUT_MS;
|
|
249
|
+
|
|
250
|
+
return new Promise((resolve, reject) => {
|
|
251
|
+
let request;
|
|
252
|
+
let response;
|
|
253
|
+
let settled = false;
|
|
254
|
+
const finish = (error, value) => {
|
|
255
|
+
if (settled) return;
|
|
256
|
+
settled = true;
|
|
257
|
+
clearTimeout(timer);
|
|
258
|
+
if (error) reject(error);
|
|
259
|
+
else resolve(value);
|
|
260
|
+
};
|
|
261
|
+
const timer = setTimeout(() => {
|
|
262
|
+
const error = new Error('UPDATE_REQUEST_TIMEOUT');
|
|
263
|
+
finish(error);
|
|
264
|
+
if (response && typeof response.destroy === 'function') response.destroy();
|
|
265
|
+
if (request && typeof request.destroy === 'function') request.destroy();
|
|
266
|
+
}, timeoutMs);
|
|
267
|
+
|
|
268
|
+
try {
|
|
269
|
+
request = httpsImpl.get(url, {
|
|
270
|
+
headers: { accept: 'application/vnd.npm.install-v1+json, application/json' },
|
|
271
|
+
}, (incoming) => {
|
|
272
|
+
response = incoming;
|
|
273
|
+
if (incoming.statusCode !== 200) {
|
|
274
|
+
finish(new Error(`UPDATE_HTTP_STATUS_${incoming.statusCode || 0}`));
|
|
275
|
+
if (typeof incoming.destroy === 'function') incoming.destroy();
|
|
276
|
+
if (request && typeof request.destroy === 'function') request.destroy();
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const rawLength = incoming.headers && incoming.headers['content-length'];
|
|
281
|
+
const declaredLength = typeof rawLength === 'string' && /^\d+$/.test(rawLength)
|
|
282
|
+
? Number(rawLength)
|
|
283
|
+
: undefined;
|
|
284
|
+
if (declaredLength !== undefined && declaredLength > maxBytes) {
|
|
285
|
+
finish(new Error('UPDATE_BODY_TOO_LARGE'));
|
|
286
|
+
if (typeof incoming.destroy === 'function') incoming.destroy();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const chunks = [];
|
|
291
|
+
let total = 0;
|
|
292
|
+
incoming.on('data', (chunk) => {
|
|
293
|
+
if (settled) return;
|
|
294
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
295
|
+
total += buffer.length;
|
|
296
|
+
if (total > maxBytes) {
|
|
297
|
+
finish(new Error('UPDATE_BODY_TOO_LARGE'));
|
|
298
|
+
if (typeof incoming.destroy === 'function') incoming.destroy();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
chunks.push(buffer);
|
|
302
|
+
});
|
|
303
|
+
incoming.on('error', (error) => finish(error));
|
|
304
|
+
incoming.on('end', () => {
|
|
305
|
+
if (settled) return;
|
|
306
|
+
try {
|
|
307
|
+
finish(undefined, JSON.parse(Buffer.concat(chunks).toString('utf8')));
|
|
308
|
+
} catch {
|
|
309
|
+
finish(new Error('UPDATE_INVALID_JSON'));
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
request.on('error', (error) => finish(error));
|
|
314
|
+
} catch (error) {
|
|
315
|
+
finish(error);
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
async function loadReleaseSources() {
|
|
321
|
+
const [registry, fallback] = await Promise.allSettled([
|
|
322
|
+
requestTrustedJson(REGISTRY_URL),
|
|
323
|
+
requestTrustedJson(FALLBACK_MANIFEST_URL),
|
|
324
|
+
]);
|
|
325
|
+
return {
|
|
326
|
+
registryDocument: registry.status === 'fulfilled' ? registry.value : undefined,
|
|
327
|
+
fallbackDocument: fallback.status === 'fulfilled' ? fallback.value : undefined,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function statePath(blunDir) {
|
|
332
|
+
return path.join(blunDir, STATE_FILE);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function readSnoozeState(blunDir, now = Date.now()) {
|
|
336
|
+
try {
|
|
337
|
+
const filePath = statePath(blunDir);
|
|
338
|
+
if (!fs.existsSync(filePath)) return false;
|
|
339
|
+
securePrivateFile(filePath);
|
|
340
|
+
const stat = fs.lstatSync(filePath);
|
|
341
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1_024) return false;
|
|
342
|
+
const record = exactDataRecord(JSON.parse(fs.readFileSync(filePath, 'utf8')), [
|
|
343
|
+
'snoozedUntil',
|
|
344
|
+
'version',
|
|
345
|
+
]);
|
|
346
|
+
if (!record
|
|
347
|
+
|| record.version !== 1
|
|
348
|
+
|| !Number.isSafeInteger(record.snoozedUntil)
|
|
349
|
+
|| record.snoozedUntil <= now
|
|
350
|
+
|| record.snoozedUntil > now + SNOOZE_MS) {
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
return true;
|
|
354
|
+
} catch {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function writeSnoozeState(blunDir, now = Date.now()) {
|
|
360
|
+
ensurePrivateDirectory(blunDir);
|
|
361
|
+
const filePath = statePath(blunDir);
|
|
362
|
+
const temporaryPath = path.join(
|
|
363
|
+
blunDir,
|
|
364
|
+
`.${STATE_FILE}.${process.pid}.${randomUUID()}.tmp`,
|
|
365
|
+
);
|
|
366
|
+
try {
|
|
367
|
+
writePrivateFile(temporaryPath, `${JSON.stringify({
|
|
368
|
+
version: 1,
|
|
369
|
+
snoozedUntil: now + SNOOZE_MS,
|
|
370
|
+
})}\n`);
|
|
371
|
+
const handle = fs.openSync(temporaryPath, 'r+');
|
|
372
|
+
try {
|
|
373
|
+
fs.fsyncSync(handle);
|
|
374
|
+
} finally {
|
|
375
|
+
fs.closeSync(handle);
|
|
376
|
+
}
|
|
377
|
+
fs.renameSync(temporaryPath, filePath);
|
|
378
|
+
securePrivateFile(filePath);
|
|
379
|
+
} catch (error) {
|
|
380
|
+
fs.rmSync(temporaryPath, { force: true });
|
|
381
|
+
throw error;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function isInteractiveUpdateStart({ argv, env, stdin, stdout }) {
|
|
386
|
+
return Array.isArray(argv)
|
|
387
|
+
&& argv.length === 0
|
|
388
|
+
&& (!env || env.BLUN_NO_AUTO_UPDATE !== '1')
|
|
389
|
+
&& stdin?.isTTY === true
|
|
390
|
+
&& stdout?.isTTY === true;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
function installCommandText(version) {
|
|
394
|
+
return `npm i -g ${PACKAGE_NAME}@${version}`;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function promptUpdateAction({ currentVersion, release, input, output, processRef = process }) {
|
|
398
|
+
return new Promise((resolve, reject) => {
|
|
399
|
+
let selected = 1;
|
|
400
|
+
let settled = false;
|
|
401
|
+
let cursorHidden = false;
|
|
402
|
+
let escapeTimer;
|
|
403
|
+
let pendingInput = '';
|
|
404
|
+
const decoder = new StringDecoder('utf8');
|
|
405
|
+
const wasRaw = input.isRaw === true;
|
|
406
|
+
const actions = ['Herunterladen und installieren', 'Aussetzen'];
|
|
407
|
+
const signalHandlers = new Map();
|
|
408
|
+
|
|
409
|
+
const renderActions = (refresh) => {
|
|
410
|
+
if (refresh) output.write('\u001B[2F');
|
|
411
|
+
for (let index = 0; index < actions.length; index += 1) {
|
|
412
|
+
output.write(`\r\u001B[2K ${selected === index ? '>' : ' '} ${actions[index]}\n`);
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
const cleanup = () => {
|
|
416
|
+
if (escapeTimer !== undefined) clearTimeout(escapeTimer);
|
|
417
|
+
input.removeListener('data', onData);
|
|
418
|
+
input.removeListener('end', postpone);
|
|
419
|
+
input.removeListener('close', postpone);
|
|
420
|
+
input.removeListener('error', postpone);
|
|
421
|
+
for (const [signal, handler] of signalHandlers) {
|
|
422
|
+
processRef.removeListener(signal, handler);
|
|
423
|
+
}
|
|
424
|
+
signalHandlers.clear();
|
|
425
|
+
try {
|
|
426
|
+
if (typeof input.setRawMode === 'function') input.setRawMode(wasRaw);
|
|
427
|
+
} catch {}
|
|
428
|
+
try {
|
|
429
|
+
if (typeof input.pause === 'function') input.pause();
|
|
430
|
+
} catch {}
|
|
431
|
+
if (cursorHidden) {
|
|
432
|
+
try {
|
|
433
|
+
output.write('\u001B[?25h');
|
|
434
|
+
} catch {}
|
|
435
|
+
cursorHidden = false;
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
const finish = (choice) => {
|
|
439
|
+
if (settled) return;
|
|
440
|
+
settled = true;
|
|
441
|
+
cleanup();
|
|
442
|
+
try {
|
|
443
|
+
output.write('\n');
|
|
444
|
+
} catch {}
|
|
445
|
+
resolve(choice);
|
|
446
|
+
};
|
|
447
|
+
const fail = (error) => {
|
|
448
|
+
if (settled) return;
|
|
449
|
+
settled = true;
|
|
450
|
+
cleanup();
|
|
451
|
+
reject(error);
|
|
452
|
+
};
|
|
453
|
+
const handleSignal = (signal) => {
|
|
454
|
+
if (settled) return;
|
|
455
|
+
settled = true;
|
|
456
|
+
cleanup();
|
|
457
|
+
try {
|
|
458
|
+
output.write('\n');
|
|
459
|
+
} catch {}
|
|
460
|
+
const error = new Error(`UPDATE_PROMPT_${signal}`);
|
|
461
|
+
error.code = `UPDATE_PROMPT_${signal}`;
|
|
462
|
+
const exitCode = UPDATE_SIGNAL_EXIT_CODES[signal];
|
|
463
|
+
processRef.exitCode = exitCode;
|
|
464
|
+
if (typeof processRef.exit === 'function') processRef.exit(exitCode);
|
|
465
|
+
reject(error);
|
|
466
|
+
};
|
|
467
|
+
function postpone() {
|
|
468
|
+
finish('postpone');
|
|
469
|
+
}
|
|
470
|
+
const waitForEscapeSuffix = () => {
|
|
471
|
+
if (escapeTimer !== undefined) return;
|
|
472
|
+
escapeTimer = setTimeout(() => {
|
|
473
|
+
escapeTimer = undefined;
|
|
474
|
+
postpone();
|
|
475
|
+
}, UPDATE_KEY_ESCAPE_TIMEOUT_MS);
|
|
476
|
+
};
|
|
477
|
+
const clearEscapeTimer = () => {
|
|
478
|
+
if (escapeTimer === undefined) return;
|
|
479
|
+
clearTimeout(escapeTimer);
|
|
480
|
+
escapeTimer = undefined;
|
|
481
|
+
};
|
|
482
|
+
const readEscapeSequence = () => {
|
|
483
|
+
if (pendingInput.length < 2) return undefined;
|
|
484
|
+
if (pendingInput[1] === '[') {
|
|
485
|
+
for (let index = 2; index < pendingInput.length; index += 1) {
|
|
486
|
+
const code = pendingInput.codePointAt(index);
|
|
487
|
+
if (code >= 0x40 && code <= 0x7e) return pendingInput.slice(0, index + 1);
|
|
488
|
+
const isParameter = code >= 0x30 && code <= 0x3f;
|
|
489
|
+
const isIntermediate = code >= 0x20 && code <= 0x2f;
|
|
490
|
+
if (!isParameter && !isIntermediate) return pendingInput.slice(0, index + 1);
|
|
491
|
+
}
|
|
492
|
+
return pendingInput.length > 64 ? pendingInput : undefined;
|
|
493
|
+
}
|
|
494
|
+
if (pendingInput[1] === 'O') {
|
|
495
|
+
return pendingInput.length >= 3 ? pendingInput.slice(0, 3) : undefined;
|
|
496
|
+
}
|
|
497
|
+
return pendingInput.slice(0, 2);
|
|
498
|
+
};
|
|
499
|
+
const processPendingInput = () => {
|
|
500
|
+
while (pendingInput.length > 0) {
|
|
501
|
+
const first = pendingInput[0];
|
|
502
|
+
if (first === '\u0003') {
|
|
503
|
+
postpone();
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (first === '\r' || first === '\n') {
|
|
507
|
+
pendingInput = pendingInput.slice(1);
|
|
508
|
+
finish(selected === 0 ? 'install' : 'postpone');
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (first !== '\u001B') {
|
|
512
|
+
pendingInput = pendingInput.slice(1);
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
if (pendingInput.length > 1
|
|
516
|
+
&& (pendingInput[1] === '\r'
|
|
517
|
+
|| pendingInput[1] === '\n'
|
|
518
|
+
|| pendingInput[1] === '\u0003')) {
|
|
519
|
+
postpone();
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
const sequence = readEscapeSequence();
|
|
523
|
+
if (sequence === undefined) {
|
|
524
|
+
waitForEscapeSuffix();
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
clearEscapeTimer();
|
|
528
|
+
if (sequence === '\u001B[A' || sequence === '\u001B[D') {
|
|
529
|
+
selected = 0;
|
|
530
|
+
pendingInput = pendingInput.slice(sequence.length);
|
|
531
|
+
renderActions(true);
|
|
532
|
+
continue;
|
|
533
|
+
}
|
|
534
|
+
if (sequence === '\u001B[B' || sequence === '\u001B[C') {
|
|
535
|
+
selected = 1;
|
|
536
|
+
pendingInput = pendingInput.slice(sequence.length);
|
|
537
|
+
renderActions(true);
|
|
538
|
+
continue;
|
|
539
|
+
}
|
|
540
|
+
pendingInput = pendingInput.slice(sequence.length);
|
|
541
|
+
}
|
|
542
|
+
};
|
|
543
|
+
const onData = (chunk) => {
|
|
544
|
+
try {
|
|
545
|
+
pendingInput += Buffer.isBuffer(chunk) ? decoder.write(chunk) : String(chunk);
|
|
546
|
+
processPendingInput();
|
|
547
|
+
} catch (error) {
|
|
548
|
+
fail(error);
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
try {
|
|
553
|
+
const supportedSignals = processRef.platform === 'win32'
|
|
554
|
+
? ['SIGINT', 'SIGTERM']
|
|
555
|
+
: Object.keys(UPDATE_SIGNAL_EXIT_CODES);
|
|
556
|
+
for (const signal of supportedSignals) {
|
|
557
|
+
const handler = () => handleSignal(signal);
|
|
558
|
+
signalHandlers.set(signal, handler);
|
|
559
|
+
processRef.once(signal, handler);
|
|
560
|
+
}
|
|
561
|
+
output.write(`\nNeues Update verfügbar (${currentVersion} -> ${release.version})\n`);
|
|
562
|
+
if (release.notesUrl) output.write(`Versionshinweise: ${release.notesUrl}\n`);
|
|
563
|
+
output.write(`Installationsbefehl: ${installCommandText(release.version)}\n\n`);
|
|
564
|
+
cursorHidden = true;
|
|
565
|
+
output.write('\u001B[?25l');
|
|
566
|
+
renderActions(false);
|
|
567
|
+
input.on('data', onData);
|
|
568
|
+
input.once('end', postpone);
|
|
569
|
+
input.once('close', postpone);
|
|
570
|
+
input.once('error', postpone);
|
|
571
|
+
if (typeof input.setRawMode === 'function') input.setRawMode(true);
|
|
572
|
+
if (typeof input.resume === 'function') input.resume();
|
|
573
|
+
} catch (error) {
|
|
574
|
+
fail(error);
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
function resolveTrustedNpmCliPath(options = {}) {
|
|
580
|
+
const execPath = path.resolve(options.execPath || process.execPath);
|
|
581
|
+
const executableDirectory = path.dirname(execPath);
|
|
582
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
583
|
+
const candidates = options.npmCliPath ? [options.npmCliPath] : [
|
|
584
|
+
path.join(executableDirectory, 'node_modules', 'npm', 'bin', 'npm-cli.js'),
|
|
585
|
+
path.resolve(executableDirectory, '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
|
|
586
|
+
path.resolve(executableDirectory, '..', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
|
|
587
|
+
path.resolve(packageRoot, '..', 'npm', 'bin', 'npm-cli.js'),
|
|
588
|
+
];
|
|
589
|
+
for (const candidate of candidates) {
|
|
590
|
+
try {
|
|
591
|
+
if (!path.isAbsolute(candidate)) continue;
|
|
592
|
+
const resolved = fs.realpathSync(candidate);
|
|
593
|
+
const parts = resolved.replaceAll('\\', '/').split('/');
|
|
594
|
+
const npmIndex = parts.length - 3;
|
|
595
|
+
if (parts.at(-1) !== 'npm-cli.js'
|
|
596
|
+
|| parts.at(-2) !== 'bin'
|
|
597
|
+
|| parts[npmIndex]?.toLowerCase() !== 'npm'
|
|
598
|
+
|| !fs.statSync(resolved).isFile()) {
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
601
|
+
return resolved;
|
|
602
|
+
} catch {}
|
|
603
|
+
}
|
|
604
|
+
throw new Error('TRUSTED_NPM_CLI_NOT_FOUND');
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function createInstallInvocation(version, options = {}) {
|
|
608
|
+
if (!parseSemver(version)) throw new Error('INVALID_UPDATE_VERSION');
|
|
609
|
+
const packageSpec = `${PACKAGE_NAME}@${version}`;
|
|
610
|
+
const execPath = fs.realpathSync(path.resolve(options.execPath || process.execPath));
|
|
611
|
+
if (!fs.statSync(execPath).isFile()) throw new Error('INVALID_NODE_EXECUTABLE');
|
|
612
|
+
const npmCliPath = resolveTrustedNpmCliPath(options);
|
|
613
|
+
const packageRoot = fs.realpathSync(path.resolve(options.packageRoot));
|
|
614
|
+
const modulesRoot = path.dirname(packageRoot);
|
|
615
|
+
if (path.basename(modulesRoot).toLowerCase() !== 'node_modules') {
|
|
616
|
+
throw new Error('INVALID_GLOBAL_PACKAGE_ROOT');
|
|
617
|
+
}
|
|
618
|
+
let installPrefix = path.dirname(modulesRoot);
|
|
619
|
+
const platform = options.platform || process.platform;
|
|
620
|
+
if (platform !== 'win32' && path.basename(installPrefix) === 'lib') {
|
|
621
|
+
installPrefix = path.dirname(installPrefix);
|
|
622
|
+
}
|
|
623
|
+
let npmConfigPath;
|
|
624
|
+
if (options.npmConfigPath) {
|
|
625
|
+
npmConfigPath = fs.realpathSync(path.resolve(options.npmConfigPath));
|
|
626
|
+
if (!fs.statSync(npmConfigPath).isFile()) throw new Error('INVALID_NPM_CONFIG_PATH');
|
|
627
|
+
}
|
|
628
|
+
return {
|
|
629
|
+
command: execPath,
|
|
630
|
+
args: [
|
|
631
|
+
npmCliPath,
|
|
632
|
+
'i',
|
|
633
|
+
'-g',
|
|
634
|
+
packageSpec,
|
|
635
|
+
`--registry=${INSTALL_REGISTRY_URL}`,
|
|
636
|
+
'--strict-ssl=true',
|
|
637
|
+
'--dry-run=false',
|
|
638
|
+
'--ignore-scripts=false',
|
|
639
|
+
'--package-lock-only=false',
|
|
640
|
+
`--prefix=${installPrefix}`,
|
|
641
|
+
...(npmConfigPath ? [
|
|
642
|
+
`--userconfig=${npmConfigPath}`,
|
|
643
|
+
`--globalconfig=${npmConfigPath}`,
|
|
644
|
+
] : []),
|
|
645
|
+
],
|
|
646
|
+
windowsVerbatimArguments: false,
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function createInstallEnvironment(sourceEnv, installDirectory, npmConfigPath) {
|
|
651
|
+
const allowedKeys = new Set([
|
|
652
|
+
'appdata',
|
|
653
|
+
'comspec',
|
|
654
|
+
'home',
|
|
655
|
+
'homedrive',
|
|
656
|
+
'homepath',
|
|
657
|
+
'lang',
|
|
658
|
+
'lc_all',
|
|
659
|
+
'lc_ctype',
|
|
660
|
+
'localappdata',
|
|
661
|
+
'logname',
|
|
662
|
+
'path',
|
|
663
|
+
'pathext',
|
|
664
|
+
'programdata',
|
|
665
|
+
'programfiles',
|
|
666
|
+
'programfiles(x86)',
|
|
667
|
+
'systemroot',
|
|
668
|
+
'term',
|
|
669
|
+
'tz',
|
|
670
|
+
'user',
|
|
671
|
+
'userprofile',
|
|
672
|
+
'windir',
|
|
673
|
+
]);
|
|
674
|
+
const result = {};
|
|
675
|
+
for (const [key, value] of Object.entries(sourceEnv || {})) {
|
|
676
|
+
const normalized = key.toLowerCase();
|
|
677
|
+
if (!allowedKeys.has(normalized) && !key.startsWith('BLUN_TEST_')) continue;
|
|
678
|
+
result[key] = value;
|
|
679
|
+
}
|
|
680
|
+
result.TEMP = installDirectory;
|
|
681
|
+
result.TMP = installDirectory;
|
|
682
|
+
result.TMPDIR = installDirectory;
|
|
683
|
+
result.npm_config_registry = INSTALL_REGISTRY_URL;
|
|
684
|
+
result.npm_config_strict_ssl = 'true';
|
|
685
|
+
result.npm_config_dry_run = 'false';
|
|
686
|
+
result.npm_config_ignore_scripts = 'false';
|
|
687
|
+
result.npm_config_package_lock_only = 'false';
|
|
688
|
+
result.npm_config_userconfig = npmConfigPath;
|
|
689
|
+
result.npm_config_globalconfig = npmConfigPath;
|
|
690
|
+
return result;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function pathIsWithin(root, candidate) {
|
|
694
|
+
const relative = path.relative(root, candidate);
|
|
695
|
+
return relative === '' || (!relative.startsWith(`..${path.sep}`)
|
|
696
|
+
&& relative !== '..'
|
|
697
|
+
&& !path.isAbsolute(relative));
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function resolveInstallTempRoot(options) {
|
|
701
|
+
if (typeof options.blunDir !== 'string' || options.blunDir.trim().length === 0) {
|
|
702
|
+
throw new Error('INVALID_UPDATE_BLUN_HOME');
|
|
703
|
+
}
|
|
704
|
+
const updateRoot = path.join(path.resolve(options.blunDir), UPDATE_DIRECTORY);
|
|
705
|
+
if (options.tempRoot === undefined) return updateRoot;
|
|
706
|
+
const explicitRoot = path.resolve(options.tempRoot);
|
|
707
|
+
if (!pathIsWithin(updateRoot, explicitRoot)) {
|
|
708
|
+
throw new Error('INVALID_UPDATE_TEMP_ROOT');
|
|
709
|
+
}
|
|
710
|
+
return explicitRoot;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function createPrivateInstallDirectory(tempRoot, blunDir) {
|
|
714
|
+
const updateRoot = path.join(path.resolve(blunDir), UPDATE_DIRECTORY);
|
|
715
|
+
let current = updateRoot;
|
|
716
|
+
ensurePrivateDirectory(current);
|
|
717
|
+
const relative = path.relative(updateRoot, tempRoot);
|
|
718
|
+
if (relative !== '') {
|
|
719
|
+
for (const segment of relative.split(path.sep)) {
|
|
720
|
+
current = path.join(current, segment);
|
|
721
|
+
ensurePrivateDirectory(current);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
let directory;
|
|
725
|
+
try {
|
|
726
|
+
directory = fs.mkdtempSync(path.join(tempRoot, 'blun-update-'));
|
|
727
|
+
ensurePrivateDirectory(directory);
|
|
728
|
+
return directory;
|
|
729
|
+
} catch (error) {
|
|
730
|
+
if (directory) {
|
|
731
|
+
try {
|
|
732
|
+
fs.rmSync(directory, { recursive: true, force: true });
|
|
733
|
+
} catch {}
|
|
734
|
+
}
|
|
735
|
+
throw error;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
function verifyInstalledPackageVersion(packageRoot, version) {
|
|
740
|
+
try {
|
|
741
|
+
const manifestPath = path.join(packageRoot, 'package.json');
|
|
742
|
+
const stat = fs.lstatSync(manifestPath);
|
|
743
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 64 * 1024) return false;
|
|
744
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
745
|
+
return readDataProperty(manifest, 'name') === PACKAGE_NAME
|
|
746
|
+
&& readDataProperty(manifest, 'version') === version;
|
|
747
|
+
} catch {
|
|
748
|
+
return false;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function preparePinnedInstaller(version, options = {}) {
|
|
753
|
+
const forkImpl = options.forkImpl || fork;
|
|
754
|
+
const preparation = Promise.withResolvers();
|
|
755
|
+
const resolvePreparation = preparation.resolve;
|
|
756
|
+
let child;
|
|
757
|
+
let invocation;
|
|
758
|
+
let leaseBusy = false;
|
|
759
|
+
let leaseUnavailable = false;
|
|
760
|
+
let sendError;
|
|
761
|
+
let childError;
|
|
762
|
+
let preparationSettled = false;
|
|
763
|
+
let closeSettled = false;
|
|
764
|
+
let selectedAction;
|
|
765
|
+
let installDirectory;
|
|
766
|
+
let npmConfigPath;
|
|
767
|
+
let disconnectTimer;
|
|
768
|
+
const close = Promise.withResolvers();
|
|
769
|
+
const closePromise = close.promise;
|
|
770
|
+
const resolveClose = close.resolve;
|
|
771
|
+
const cleanupDirectory = () => {
|
|
772
|
+
try {
|
|
773
|
+
if (installDirectory) {
|
|
774
|
+
(options.removeDirectory || fs.rmSync)(installDirectory, {
|
|
775
|
+
recursive: true,
|
|
776
|
+
force: true,
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
} catch {
|
|
780
|
+
// Cleanup cannot change a completed npm result.
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
const settlePreparation = (result) => {
|
|
784
|
+
if (preparationSettled) return;
|
|
785
|
+
preparationSettled = true;
|
|
786
|
+
clearTimeout(prepareTimeout);
|
|
787
|
+
resolvePreparation(result);
|
|
788
|
+
};
|
|
789
|
+
const finishClose = (result) => {
|
|
790
|
+
if (closeSettled) return;
|
|
791
|
+
closeSettled = true;
|
|
792
|
+
clearTimeout(disconnectTimer);
|
|
793
|
+
cleanupDirectory();
|
|
794
|
+
resolveClose(result);
|
|
795
|
+
if (!preparationSettled) {
|
|
796
|
+
settlePreparation({
|
|
797
|
+
ready: false,
|
|
798
|
+
reason: leaseBusy
|
|
799
|
+
? 'busy'
|
|
800
|
+
: leaseUnavailable
|
|
801
|
+
? 'unavailable'
|
|
802
|
+
: result.code === 75 || result.unknownState === true
|
|
803
|
+
? 'unknown'
|
|
804
|
+
: 'failed',
|
|
805
|
+
result,
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
const finishDisconnected = () => {
|
|
810
|
+
if (closeSettled) return;
|
|
811
|
+
closeSettled = true;
|
|
812
|
+
const result = { code: undefined, error: childError, unknownState: true };
|
|
813
|
+
resolveClose(result);
|
|
814
|
+
if (!preparationSettled) {
|
|
815
|
+
settlePreparation({ ready: false, reason: 'unknown', result });
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
const scheduleUnknownState = (error) => {
|
|
819
|
+
if (error && childError === undefined) childError = error;
|
|
820
|
+
if (disconnectTimer !== undefined) return;
|
|
821
|
+
disconnectTimer = setTimeout(
|
|
822
|
+
finishDisconnected,
|
|
823
|
+
options.disconnectTimeoutMs || 1_000,
|
|
824
|
+
);
|
|
825
|
+
};
|
|
826
|
+
const sendControl = (type) => {
|
|
827
|
+
if (selectedAction) return closePromise;
|
|
828
|
+
selectedAction = type;
|
|
829
|
+
try {
|
|
830
|
+
child.send({ type }, (error) => {
|
|
831
|
+
if (!error) return;
|
|
832
|
+
sendError = error;
|
|
833
|
+
try {
|
|
834
|
+
child.kill();
|
|
835
|
+
} catch {}
|
|
836
|
+
});
|
|
837
|
+
} catch (error) {
|
|
838
|
+
sendError = error;
|
|
839
|
+
try {
|
|
840
|
+
child.kill();
|
|
841
|
+
} catch {}
|
|
842
|
+
}
|
|
843
|
+
return closePromise;
|
|
844
|
+
};
|
|
845
|
+
let prepareTimeout;
|
|
846
|
+
try {
|
|
847
|
+
installDirectory = createPrivateInstallDirectory(
|
|
848
|
+
resolveInstallTempRoot(options),
|
|
849
|
+
options.blunDir,
|
|
850
|
+
);
|
|
851
|
+
npmConfigPath = path.join(installDirectory, 'npmrc');
|
|
852
|
+
writePrivateFile(npmConfigPath, '');
|
|
853
|
+
invocation = createInstallInvocation(version, { ...options, npmConfigPath });
|
|
854
|
+
child = forkImpl(path.join(__dirname, 'update-lease.js'), ['--installer-worker'], {
|
|
855
|
+
detached: true,
|
|
856
|
+
execPath: invocation.command,
|
|
857
|
+
cwd: installDirectory,
|
|
858
|
+
env: createInstallEnvironment(
|
|
859
|
+
options.env || process.env,
|
|
860
|
+
installDirectory,
|
|
861
|
+
npmConfigPath,
|
|
862
|
+
),
|
|
863
|
+
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
|
864
|
+
windowsHide: true,
|
|
865
|
+
});
|
|
866
|
+
} catch (error) {
|
|
867
|
+
cleanupDirectory();
|
|
868
|
+
settlePreparation({ ready: false, reason: 'failed', result: { error } });
|
|
869
|
+
return preparation.promise;
|
|
870
|
+
}
|
|
871
|
+
prepareTimeout = setTimeout(() => {
|
|
872
|
+
sendError = new Error('UPDATE_INSTALLER_PREPARE_TIMEOUT');
|
|
873
|
+
settlePreparation({ ready: false, reason: 'unknown', result: { error: sendError } });
|
|
874
|
+
try {
|
|
875
|
+
child.kill();
|
|
876
|
+
} catch {}
|
|
877
|
+
}, options.prepareTimeoutMs || 5_000);
|
|
878
|
+
prepareTimeout.unref?.();
|
|
879
|
+
child.once('error', (error) => {
|
|
880
|
+
if (!preparationSettled && child.pid === undefined) {
|
|
881
|
+
finishClose({ code: undefined, error });
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
scheduleUnknownState(error);
|
|
885
|
+
});
|
|
886
|
+
child.on('message', (message) => {
|
|
887
|
+
if (message?.type === 'blun-update-lease-busy') leaseBusy = true;
|
|
888
|
+
if (message?.type === 'blun-update-lease-unavailable') leaseUnavailable = true;
|
|
889
|
+
if (message?.type === 'blun-update-lease-adopted') {
|
|
890
|
+
settlePreparation(Object.freeze({
|
|
891
|
+
ready: true,
|
|
892
|
+
commit: () => sendControl('blun-update-lease-commit'),
|
|
893
|
+
cancel: () => sendControl('blun-update-lease-cancel'),
|
|
894
|
+
}));
|
|
895
|
+
}
|
|
896
|
+
});
|
|
897
|
+
const finishInstallerProcess = (code, signal) => {
|
|
898
|
+
if (sendError || childError) {
|
|
899
|
+
finishClose({
|
|
900
|
+
code: undefined,
|
|
901
|
+
error: sendError || childError,
|
|
902
|
+
unknownState: true,
|
|
903
|
+
});
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
finishClose({
|
|
907
|
+
code: Number.isInteger(code) ? code : undefined,
|
|
908
|
+
signal: typeof signal === 'string' ? signal : undefined,
|
|
909
|
+
leaseBusy,
|
|
910
|
+
leaseUnavailable,
|
|
911
|
+
unknownState: !Number.isInteger(code),
|
|
912
|
+
verified: !leaseBusy
|
|
913
|
+
&& !leaseUnavailable
|
|
914
|
+
&& code === 0
|
|
915
|
+
&& verifyInstalledPackageVersion(options.packageRoot, version),
|
|
916
|
+
});
|
|
917
|
+
};
|
|
918
|
+
child.once('exit', finishInstallerProcess);
|
|
919
|
+
child.once('close', finishInstallerProcess);
|
|
920
|
+
child.once('disconnect', () => scheduleUnknownState());
|
|
921
|
+
try {
|
|
922
|
+
child.send({
|
|
923
|
+
type: 'blun-update-lease-handoff',
|
|
924
|
+
packageRoot: path.resolve(options.packageRoot),
|
|
925
|
+
installer: {
|
|
926
|
+
npmCliPath: invocation.args[0],
|
|
927
|
+
npmArgs: invocation.args.slice(1),
|
|
928
|
+
installCwd: installDirectory,
|
|
929
|
+
},
|
|
930
|
+
}, (error) => {
|
|
931
|
+
if (!error) return;
|
|
932
|
+
sendError = error;
|
|
933
|
+
try {
|
|
934
|
+
child.kill();
|
|
935
|
+
} catch {
|
|
936
|
+
finishClose({ code: undefined, error, unknownState: true });
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
} catch (error) {
|
|
940
|
+
sendError = error;
|
|
941
|
+
try {
|
|
942
|
+
child.kill();
|
|
943
|
+
} catch {
|
|
944
|
+
finishClose({ code: undefined, error, unknownState: true });
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
return preparation.promise;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
async function installPinnedVersion(version, options = {}) {
|
|
951
|
+
const prepared = await preparePinnedInstaller(version, options);
|
|
952
|
+
if (!prepared.ready) {
|
|
953
|
+
return {
|
|
954
|
+
...prepared.result,
|
|
955
|
+
leaseBusy: prepared.reason === 'busy',
|
|
956
|
+
leaseUnavailable: prepared.reason === 'unavailable',
|
|
957
|
+
unknownState: prepared.reason === 'unknown' || prepared.result?.unknownState === true,
|
|
958
|
+
};
|
|
959
|
+
}
|
|
960
|
+
return prepared.commit();
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
async function probeInstallLease(options, packageRoot) {
|
|
964
|
+
let result;
|
|
965
|
+
try {
|
|
966
|
+
result = await (options.acquireInstallLease || tryAcquireUpdateLease)({
|
|
967
|
+
packageRoot,
|
|
968
|
+
role: 'install',
|
|
969
|
+
});
|
|
970
|
+
} catch {
|
|
971
|
+
return 'unavailable';
|
|
972
|
+
}
|
|
973
|
+
if (!result?.acquired) return result?.reason === 'busy' ? 'busy' : 'unavailable';
|
|
974
|
+
try {
|
|
975
|
+
result.lease.assertOwned();
|
|
976
|
+
} catch {
|
|
977
|
+
return 'busy';
|
|
978
|
+
} finally {
|
|
979
|
+
try {
|
|
980
|
+
await result.lease.release();
|
|
981
|
+
} catch {}
|
|
982
|
+
}
|
|
983
|
+
return 'clear';
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
function noReleaseReason(currentVersion, registryTags, fallbackManifest) {
|
|
987
|
+
const latest = readDataProperty(registryTags, 'latest');
|
|
988
|
+
if (parseSemver(latest) && compareSemver(currentVersion, latest) > 0) {
|
|
989
|
+
const next = readDataProperty(registryTags, 'next');
|
|
990
|
+
return parseSemver(next) && compareSemver(next, currentVersion) === 0
|
|
991
|
+
? 'current'
|
|
992
|
+
: 'unavailable';
|
|
993
|
+
}
|
|
994
|
+
return parseSemver(latest) || fallbackManifest ? 'current' : 'unavailable';
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function explicitNoTarget(stdout, reason) {
|
|
998
|
+
stdout.write(reason === 'current'
|
|
999
|
+
? 'Kein Update verfügbar. BLUN Code ist auf dem aktuellen Updatekanal.\n'
|
|
1000
|
+
: 'Kein sicherer Update-Zielstand verfügbar.\n');
|
|
1001
|
+
return { kind: 'current', reason };
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
function explicitFailure(stderr, reason, exitCode) {
|
|
1005
|
+
const hasExitCode = Number.isInteger(exitCode) && exitCode !== 0;
|
|
1006
|
+
if (hasExitCode) {
|
|
1007
|
+
stderr.write(`Update fehlgeschlagen (Code ${exitCode}).\n`);
|
|
1008
|
+
} else {
|
|
1009
|
+
stderr.write('Das Update konnte nicht verifiziert werden.\n');
|
|
1010
|
+
}
|
|
1011
|
+
return {
|
|
1012
|
+
kind: 'failed',
|
|
1013
|
+
reason,
|
|
1014
|
+
exitCode: hasExitCode ? exitCode : 1,
|
|
1015
|
+
};
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
async function runUpdateFlow(options, explicitUpdate) {
|
|
1019
|
+
const {
|
|
1020
|
+
currentVersion,
|
|
1021
|
+
argv,
|
|
1022
|
+
env,
|
|
1023
|
+
stdin,
|
|
1024
|
+
stdout,
|
|
1025
|
+
stderr,
|
|
1026
|
+
blunDir,
|
|
1027
|
+
} = options;
|
|
1028
|
+
if (!explicitUpdate && !isInteractiveUpdateStart({ argv, env, stdin, stdout })) {
|
|
1029
|
+
return { kind: 'continue', reason: 'not_interactive' };
|
|
1030
|
+
}
|
|
1031
|
+
if (!parseSemver(currentVersion)) {
|
|
1032
|
+
return explicitUpdate
|
|
1033
|
+
? explicitFailure(stderr, 'invalid_current_version')
|
|
1034
|
+
: { kind: 'continue', reason: 'invalid_current_version' };
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
const now = (options.now || Date.now)();
|
|
1038
|
+
if (!explicitUpdate && readSnoozeState(blunDir, now)) {
|
|
1039
|
+
return { kind: 'continue', reason: 'snoozed' };
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
const packageRoot = path.resolve(options.packageRoot || path.resolve(__dirname, '..'));
|
|
1043
|
+
let leaseResult;
|
|
1044
|
+
const ownsNoticeLease = options.noticeLease === undefined;
|
|
1045
|
+
if (ownsNoticeLease) {
|
|
1046
|
+
try {
|
|
1047
|
+
leaseResult = await (options.acquireLease || tryAcquireUpdateLease)({ packageRoot });
|
|
1048
|
+
} catch {
|
|
1049
|
+
return explicitUpdate
|
|
1050
|
+
? explicitFailure(stderr, 'lease_unavailable')
|
|
1051
|
+
: { kind: 'continue', reason: 'lease_unavailable' };
|
|
1052
|
+
}
|
|
1053
|
+
} else {
|
|
1054
|
+
leaseResult = { acquired: true, lease: options.noticeLease };
|
|
1055
|
+
}
|
|
1056
|
+
if (!leaseResult?.acquired) {
|
|
1057
|
+
if (explicitUpdate) {
|
|
1058
|
+
return explicitFailure(
|
|
1059
|
+
stderr,
|
|
1060
|
+
leaseResult?.reason === 'busy' ? 'update_in_progress' : 'lease_unavailable',
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
return leaseResult?.reason === 'busy'
|
|
1064
|
+
? { kind: 'update_in_progress' }
|
|
1065
|
+
: { kind: 'continue', reason: 'lease_unavailable' };
|
|
1066
|
+
}
|
|
1067
|
+
const { lease } = leaseResult;
|
|
1068
|
+
let installerSession;
|
|
1069
|
+
|
|
1070
|
+
try {
|
|
1071
|
+
if (!explicitUpdate && readSnoozeState(blunDir, (options.now || Date.now)())) {
|
|
1072
|
+
return { kind: 'continue', reason: 'snoozed' };
|
|
1073
|
+
}
|
|
1074
|
+
const initialInstallLease = await probeInstallLease(options, packageRoot);
|
|
1075
|
+
if (initialInstallLease === 'busy') {
|
|
1076
|
+
return explicitUpdate
|
|
1077
|
+
? explicitFailure(stderr, 'update_in_progress')
|
|
1078
|
+
: { kind: 'update_in_progress' };
|
|
1079
|
+
}
|
|
1080
|
+
if (initialInstallLease !== 'clear') {
|
|
1081
|
+
return explicitUpdate
|
|
1082
|
+
? explicitFailure(stderr, 'lease_unavailable')
|
|
1083
|
+
: { kind: 'continue', reason: 'lease_unavailable' };
|
|
1084
|
+
}
|
|
1085
|
+
let sources;
|
|
1086
|
+
try {
|
|
1087
|
+
sources = await (options.loadReleaseSources || loadReleaseSources)();
|
|
1088
|
+
} catch {
|
|
1089
|
+
return explicitUpdate
|
|
1090
|
+
? explicitNoTarget(stdout, 'unavailable')
|
|
1091
|
+
: { kind: 'continue', reason: 'unavailable' };
|
|
1092
|
+
}
|
|
1093
|
+
const registryTags = parseRegistryDocument(sources?.registryDocument);
|
|
1094
|
+
const fallbackManifest = parseFallbackManifest(sources?.fallbackDocument);
|
|
1095
|
+
const release = resolveEffectiveRelease(currentVersion, registryTags, fallbackManifest);
|
|
1096
|
+
if (!release) {
|
|
1097
|
+
const reason = noReleaseReason(currentVersion, registryTags, fallbackManifest);
|
|
1098
|
+
return explicitUpdate
|
|
1099
|
+
? explicitNoTarget(stdout, reason)
|
|
1100
|
+
: { kind: 'continue', reason };
|
|
1101
|
+
}
|
|
1102
|
+
if (explicitUpdate) {
|
|
1103
|
+
try {
|
|
1104
|
+
ensurePrivateDirectory(blunDir);
|
|
1105
|
+
} catch {
|
|
1106
|
+
return explicitFailure(stderr, 'private_home_unavailable');
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
try {
|
|
1110
|
+
lease.assertOwned();
|
|
1111
|
+
} catch {
|
|
1112
|
+
return explicitUpdate
|
|
1113
|
+
? explicitFailure(stderr, 'update_in_progress')
|
|
1114
|
+
: { kind: 'update_in_progress' };
|
|
1115
|
+
}
|
|
1116
|
+
const prePromptInstallLease = await probeInstallLease(options, packageRoot);
|
|
1117
|
+
if (prePromptInstallLease === 'busy') {
|
|
1118
|
+
return explicitUpdate
|
|
1119
|
+
? explicitFailure(stderr, 'update_in_progress')
|
|
1120
|
+
: { kind: 'update_in_progress' };
|
|
1121
|
+
}
|
|
1122
|
+
if (prePromptInstallLease !== 'clear') {
|
|
1123
|
+
return explicitUpdate
|
|
1124
|
+
? explicitFailure(stderr, 'lease_unavailable')
|
|
1125
|
+
: { kind: 'continue', reason: 'lease_unavailable' };
|
|
1126
|
+
}
|
|
1127
|
+
if (!options.installVersion) {
|
|
1128
|
+
installerSession = await (options.prepareInstaller || preparePinnedInstaller)(
|
|
1129
|
+
release.version,
|
|
1130
|
+
{ blunDir, packageRoot },
|
|
1131
|
+
);
|
|
1132
|
+
if (!installerSession?.ready) {
|
|
1133
|
+
if (explicitUpdate) {
|
|
1134
|
+
return explicitFailure(stderr, 'installer_unavailable');
|
|
1135
|
+
}
|
|
1136
|
+
return installerSession?.reason === 'failed'
|
|
1137
|
+
? { kind: 'continue', reason: 'installer_unavailable' }
|
|
1138
|
+
: { kind: 'update_in_progress' };
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
let action;
|
|
1142
|
+
if (explicitUpdate) {
|
|
1143
|
+
action = 'install';
|
|
1144
|
+
} else {
|
|
1145
|
+
try {
|
|
1146
|
+
action = await (options.promptAction || promptUpdateAction)({
|
|
1147
|
+
currentVersion,
|
|
1148
|
+
release,
|
|
1149
|
+
input: stdin,
|
|
1150
|
+
output: stdout,
|
|
1151
|
+
});
|
|
1152
|
+
} catch {
|
|
1153
|
+
return { kind: 'continue', reason: 'prompt_failed' };
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
if (action !== 'install') {
|
|
1157
|
+
if (installerSession?.ready) await installerSession.cancel();
|
|
1158
|
+
try {
|
|
1159
|
+
writeSnoozeState(blunDir, (options.now || Date.now)());
|
|
1160
|
+
} catch {
|
|
1161
|
+
// A failed preference write must not block the existing console.
|
|
1162
|
+
}
|
|
1163
|
+
return { kind: 'continue', reason: 'postponed' };
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
try {
|
|
1167
|
+
lease.assertOwned();
|
|
1168
|
+
} catch {
|
|
1169
|
+
return explicitUpdate
|
|
1170
|
+
? explicitFailure(stderr, 'update_in_progress')
|
|
1171
|
+
: { kind: 'update_in_progress' };
|
|
1172
|
+
}
|
|
1173
|
+
let installResult;
|
|
1174
|
+
try {
|
|
1175
|
+
installResult = options.installVersion
|
|
1176
|
+
? await options.installVersion(release.version)
|
|
1177
|
+
: await installerSession.commit();
|
|
1178
|
+
} catch {
|
|
1179
|
+
installResult = { code: undefined };
|
|
1180
|
+
}
|
|
1181
|
+
if (installResult?.leaseBusy === true
|
|
1182
|
+
|| installResult?.leaseUnavailable === true
|
|
1183
|
+
|| installResult?.unknownState === true) {
|
|
1184
|
+
return explicitUpdate
|
|
1185
|
+
? explicitFailure(stderr, 'update_in_progress')
|
|
1186
|
+
: { kind: 'update_in_progress' };
|
|
1187
|
+
}
|
|
1188
|
+
if (installResult?.code === 0 && installResult?.verified === true) {
|
|
1189
|
+
stdout.write('Update erfolgreich installiert. Starte BLUN Code neu, um die neue Version zu verwenden.\n');
|
|
1190
|
+
return { kind: 'installed', version: release.version };
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
if (Number.isInteger(installResult?.code) && installResult.code !== 0) {
|
|
1194
|
+
if (explicitUpdate) {
|
|
1195
|
+
return explicitFailure(stderr, 'install_failed', installResult.code);
|
|
1196
|
+
}
|
|
1197
|
+
stderr.write(`Update fehlgeschlagen (Code ${installResult.code}). Die vorhandene Version wird gestartet.\n`);
|
|
1198
|
+
return { kind: 'continue', reason: 'install_failed', exitCode: installResult.code };
|
|
1199
|
+
}
|
|
1200
|
+
if (explicitUpdate) return explicitFailure(stderr, 'verification_failed');
|
|
1201
|
+
stderr.write('Update konnte nicht verifiziert werden. Starte BLUN Code neu.\n');
|
|
1202
|
+
return { kind: 'update_in_progress' };
|
|
1203
|
+
} finally {
|
|
1204
|
+
if (installerSession?.ready) {
|
|
1205
|
+
try {
|
|
1206
|
+
await installerSession.cancel();
|
|
1207
|
+
} catch {}
|
|
1208
|
+
}
|
|
1209
|
+
if (ownsNoticeLease) {
|
|
1210
|
+
try {
|
|
1211
|
+
await lease.release();
|
|
1212
|
+
} catch {}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
function runExplicitUpdate(options) {
|
|
1218
|
+
return runUpdateFlow(options, true);
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function runUpdateNotice(options) {
|
|
1222
|
+
return runUpdateFlow(options, false);
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
module.exports = {
|
|
1226
|
+
FALLBACK_MANIFEST_URL,
|
|
1227
|
+
PACKAGE_NAME,
|
|
1228
|
+
REGISTRY_URL,
|
|
1229
|
+
SNOOZE_MS,
|
|
1230
|
+
compareSemver,
|
|
1231
|
+
createInstallInvocation,
|
|
1232
|
+
installPinnedVersion,
|
|
1233
|
+
preparePinnedInstaller,
|
|
1234
|
+
isInteractiveUpdateStart,
|
|
1235
|
+
loadReleaseSources,
|
|
1236
|
+
parseFallbackManifest,
|
|
1237
|
+
parseRegistryDocument,
|
|
1238
|
+
promptUpdateAction,
|
|
1239
|
+
readSnoozeState,
|
|
1240
|
+
requestTrustedJson,
|
|
1241
|
+
resolveEffectiveRelease,
|
|
1242
|
+
runExplicitUpdate,
|
|
1243
|
+
runUpdateNotice,
|
|
1244
|
+
writeSnoozeState,
|
|
1245
|
+
};
|