@qwen-code/qwen-code 0.19.6-nightly.20260706.47f62a466 → 0.19.6-preview.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/bundled/qc-helper/docs/features/channels/_meta.ts +1 -0
- package/bundled/qc-helper/docs/features/channels/overview.md +17 -9
- package/bundled/qc-helper/docs/features/channels/wecom.md +111 -0
- package/bundled/qc-helper/docs/features/code-review.md +16 -6
- package/bundled/qc-helper/docs/features/hooks.md +6 -0
- package/bundled/qc-helper/docs/integration-github-action.md +1 -1
- package/bundled/review/DESIGN.md +27 -18
- package/bundled/review/SKILL.md +37 -4
- package/chunks/{MaxSizedBox-JCOL4CAA.js → MaxSizedBox-2JOX4MMR.js} +11 -10
- package/chunks/{StandaloneSessionPicker-BLFN63BV.js → StandaloneSessionPicker-Z2G37ETA.js} +24 -22
- package/chunks/{acpAgent-3KQMM3LN.js → acpAgent-MQJT2H4Y.js} +103 -61
- package/chunks/{agent-3SQT4Q6F.js → agent-HEVJ76EO.js} +6 -7
- package/chunks/{agent-headless-XMYIIWGF.js → agent-headless-UNB5SWYC.js} +6 -7
- package/chunks/{anthropicContentGenerator-L7J7PVYL.js → anthropicContentGenerator-AKYPSZIV.js} +3 -3
- package/chunks/{artifact-tool-H2TNPNP5.js → artifact-tool-D233YJFH.js} +25 -6
- package/chunks/{bridge-VL53WCNP.js → bridge-GX2LGQDS.js} +11 -10
- package/chunks/{channel-worker-supervisor-B7MSMB5K.js → channel-worker-supervisor-CRCVYQIE.js} +1 -1
- package/chunks/{chunk-6T6SXQNR.js → chunk-27QGW3BK.js} +5 -5
- package/chunks/{chunk-YMVK4LS2.js → chunk-2D5Q33PT.js} +3 -3
- package/chunks/{chunk-IKCFWN3Y.js → chunk-2MDHTI6A.js} +2 -2
- package/chunks/{chunk-BBKXTYL6.js → chunk-2MJAXDFK.js} +6 -6
- package/chunks/{chunk-WSVZFWN4.js → chunk-2NPS2UDH.js} +305 -97
- package/chunks/{chunk-HALA655G.js → chunk-2X3ZVAAT.js} +2 -2
- package/chunks/chunk-332PWN27.js +19 -0
- package/chunks/{chunk-ACD5DFUY.js → chunk-3B26UVCX.js} +130 -24
- package/chunks/{chunk-Z7RPIVIC.js → chunk-43W4YSO6.js} +1 -12
- package/chunks/{chunk-3QAQS4IM.js → chunk-44NROQYV.js} +4 -2
- package/chunks/{chunk-N7K5TYQW.js → chunk-4XV2MRNK.js} +1 -1
- package/chunks/{chunk-VCHDUDLM.js → chunk-5HMMLTUD.js} +11 -11
- package/chunks/{chunk-JZLDAWNW.js → chunk-5IGNZP2J.js} +1 -1
- package/chunks/{chunk-NCFVPWRR.js → chunk-6GHEZDDX.js} +1 -1
- package/chunks/{chunk-AQOYX6KA.js → chunk-7O6DD2ES.js} +1 -1
- package/chunks/{chunk-SN3W7PFN.js → chunk-7R62CLMI.js} +4 -4
- package/chunks/{chunk-FB75L4ZI.js → chunk-7SAH6M3Y.js} +3 -3
- package/chunks/{chunk-RMIXI7TN.js → chunk-7VCI24VZ.js} +4 -107
- package/chunks/chunk-A2X3KLC3.js +3807 -0
- package/chunks/chunk-A3ZQMFO6.js +949 -0
- package/chunks/{chunk-B7E6VU5A.js → chunk-A7UKINBT.js} +7 -7
- package/chunks/{chunk-VDTH6RD3.js → chunk-ADO6IDOZ.js} +15 -9
- package/chunks/{chunk-FDVILPGP.js → chunk-AJ3CEJRJ.js} +10 -5
- package/chunks/{chunk-NCFY7PWS.js → chunk-CCGOFQV3.js} +51 -2
- package/chunks/{chunk-HSO66R46.js → chunk-DB7CZERZ.js} +1 -1
- package/chunks/{chunk-LAW3LHN6.js → chunk-DF74IL3R.js} +67 -42
- package/chunks/{chunk-U3RZKQXS.js → chunk-FCEHOKYP.js} +5 -5
- package/chunks/{chunk-OQLSVZAA.js → chunk-FRR5I3SQ.js} +1 -1
- package/chunks/{chunk-NFE3DQ4R.js → chunk-FV7425LN.js} +36 -1
- package/chunks/{chunk-NOAYCIDS.js → chunk-GPLKAQUX.js} +8 -8
- package/chunks/{chunk-VS64QG5I.js → chunk-GV2S4F6Q.js} +12 -4
- package/chunks/{chunk-ZNUVV3IQ.js → chunk-GW67O5SO.js} +1 -1
- package/chunks/{chunk-6QXGBCAH.js → chunk-HW4N45EE.js} +1 -45
- package/chunks/{chunk-OGC64RKC.js → chunk-IFDJAMJW.js} +484 -1222
- package/chunks/{chunk-7AEMHW7Y.js → chunk-ILFQAFWH.js} +1 -1
- package/chunks/chunk-JYFZFWSL.js +167 -0
- package/chunks/{chunk-JZGYH2ST.js → chunk-K56RTEBN.js} +1 -1
- package/chunks/chunk-LGFAYIKX.js +115 -0
- package/chunks/{chunk-QR2KK6CG.js → chunk-LLTFLVFK.js} +4 -4
- package/chunks/{chunk-654I2NBH.js → chunk-MZWODTEA.js} +5 -13
- package/chunks/{chunk-FCCBH56M.js → chunk-N33JAGJ7.js} +812 -78
- package/chunks/{chunk-K2KVPZM5.js → chunk-NVQVIJMO.js} +112 -42
- package/chunks/{chunk-LH6D765T.js → chunk-O6T7PBUM.js} +30 -6
- package/chunks/{chunk-QOKVIOLW.js → chunk-P452E6XF.js} +2 -2
- package/chunks/chunk-PS2NG7KU.js +262 -0
- package/chunks/{chunk-RG6ZAMMW.js → chunk-QM4FKX7H.js} +2 -2
- package/chunks/chunk-RODQXLGZ.js +28 -0
- package/chunks/{chunk-KSPVDFE4.js → chunk-SAIILPQ3.js} +0 -3
- package/chunks/{chunk-5QNTAU44.js → chunk-SJ3QRPN2.js} +14 -8
- package/chunks/{chunk-FTXYSZOJ.js → chunk-SKO6VJHG.js} +16 -4
- package/chunks/{chunk-66KNL43D.js → chunk-TBAZMRW3.js} +1 -1
- package/chunks/{chunk-KDCCKYPM.js → chunk-TPTG52XG.js} +1 -1
- package/chunks/chunk-UIDXQNMV.js +13 -0
- package/chunks/chunk-VP2FB7OC.js +8223 -0
- package/chunks/{chunk-QLEBY26M.js → chunk-WKBA74DD.js} +1 -1
- package/chunks/{chunk-XRKPKL3R.js → chunk-WKTLDGD6.js} +1 -1
- package/chunks/{chunk-O2I6MGPK.js → chunk-WRLMLPWI.js} +3 -3
- package/chunks/{chunk-PRO3AHV3.js → chunk-XXICIIS5.js} +5 -5
- package/chunks/{chunk-GIXLNFID.js → chunk-Y3QQV7WE.js} +2 -2
- package/chunks/{chunk-LTDPXBIJ.js → chunk-ZKX7IBXU.js} +4 -4
- package/chunks/{chunk-J7JIBDUN.js → chunk-ZOSL7K3L.js} +221 -42
- package/chunks/{chunk-4VVDPKQ6.js → chunk-ZWFS7YCB.js} +4941 -3908
- package/chunks/{computer-use-JZO5THQ4.js → computer-use-IQ46C3YP.js} +6 -7
- package/chunks/{contextCommand-B3US3GS2.js → contextCommand-TTT6AP2W.js} +10 -10
- package/chunks/{cron-create-O6ZHD3JN.js → cron-create-JSXEIPVS.js} +1 -1
- package/chunks/{cron-delete-BFD5B2XX.js → cron-delete-OIFVEPGU.js} +1 -1
- package/chunks/{cron-list-KI6FV4KN.js → cron-list-D4ZX4IOE.js} +8 -4
- package/chunks/{daemon-WQXSK7GF.js → daemon-F3I2RXP6.js} +59 -3
- package/chunks/{daemon-status-provider-FYMIAJ74.js → daemon-status-provider-5GAWK65R.js} +13 -13
- package/chunks/dist-573HNSVO.js +3217 -0
- package/chunks/{dist-VHBP2PIG.js → dist-FQ5WRJPL.js} +2 -2
- package/chunks/{dist-LPVU3L2M.js → dist-IMFFBMAT.js} +6 -3792
- package/chunks/{dist-7GU63KI5.js → dist-OT7G2WNM.js} +1 -1
- package/chunks/{dist-H4HHZYCI.js → dist-PKFTNUHO.js} +2 -2
- package/chunks/{dist-3UDX5T4T.js → dist-YTRKAVUB.js} +1 -1
- package/chunks/{earlyInputCapture-5GY56LUS.js → earlyInputCapture-LAGZUGGV.js} +6 -7
- package/chunks/{edit-A26XF3GQ.js → edit-Q5BUQC2S.js} +6 -7
- package/chunks/{en-AXARKOWO.js → en-GA2RMXY5.js} +5 -1
- package/chunks/{enter-worktree-LXWZ7LPU.js → enter-worktree-DC4FHMVF.js} +6 -7
- package/chunks/{enterPlanMode-OZPTJ4LY.js → enterPlanMode-5BGJBWIY.js} +6 -7
- package/chunks/{errors-HC6JMZ7A.js → errors-EGX5DECS.js} +8 -9
- package/chunks/{exit-worktree-5SMO7MH5.js → exit-worktree-2EOSZNZI.js} +6 -7
- package/chunks/{exitPlanMode-EFPLGAND.js → exitPlanMode-JAQ35ELJ.js} +6 -7
- package/chunks/{fast-path-TF2IF5WZ.js → fast-path-QEWDA6O6.js} +5 -2
- package/chunks/{fr-MBMNQSQ6.js → fr-GWNQG4XH.js} +4 -0
- package/chunks/{gemini-XTISXYIY.js → gemini-TQ5XANRS.js} +64 -188
- package/chunks/{geminiContentGenerator-5W62YCSD.js → geminiContentGenerator-DCM2XGDQ.js} +1 -1
- package/chunks/{glob-YVWLTWR4.js → glob-7JLTQ4IQ.js} +6 -7
- package/chunks/{grep-TMLRZONJ.js → grep-CAUXEO7Q.js} +6 -7
- package/chunks/handleAutoUpdate-XKXULZQX.js +65 -0
- package/chunks/{initializer-7SJAWILK.js → initializer-3NRWU5JV.js} +12 -11
- package/chunks/{ja-HFV5QCHR.js → ja-2HOTA47X.js} +4 -0
- package/chunks/{list-JE74WAG2.js → list-NWO6QBDQ.js} +12 -13
- package/chunks/{loadedSettingsAdapter-R3DCJQ5W.js → loadedSettingsAdapter-PJ77LGFS.js} +10 -11
- package/chunks/{loop-wakeup-CUVIY2JY.js → loop-wakeup-OHWEMFP5.js} +2 -2
- package/chunks/{mcp-AJ2ZHF2N.js → mcp-VFEHVF5A.js} +10 -11
- package/chunks/{monitor-UH6ORENE.js → monitor-FY2ITVSC.js} +14 -10
- package/chunks/{nonInteractiveCli-CCYM24DQ.js → nonInteractiveCli-KPCXTVWY.js} +41 -37
- package/chunks/{notebook-edit-VW6QKDC6.js → notebook-edit-BSJMXIWX.js} +6 -7
- package/chunks/{openaiContentGenerator-2HIY7ZUY.js → openaiContentGenerator-BA6NWY4S.js} +5 -6
- package/chunks/{pidfile-QGNMZ5SP.js → pidfile-WXMRN6QF.js} +6 -7
- package/chunks/{qwenContentGenerator-JFQNSKW4.js → qwenContentGenerator-U4ZSLGFN.js} +8 -9
- package/chunks/{read-file-LTJRKWK2.js → read-file-VMVHNOMA.js} +2 -2
- package/chunks/{ripGrep-X65NYSFJ.js → ripGrep-NC6MUWXN.js} +6 -7
- package/chunks/{run-qwen-serve-CLVQV6WU.js → run-qwen-serve-NQH6LQTH.js} +144 -27
- package/chunks/{scheduler-NUTJ4FRU.js → scheduler-35T3E2UQ.js} +6 -7
- package/chunks/{serve-4DVF5UCI.js → serve-C36LOB5N.js} +10 -11
- package/chunks/{server-ZFRDBIFR.js → server-TDFPIDC3.js} +1342 -160
- package/chunks/{session-YQPMXZCZ.js → session-STHNQGRJ.js} +42 -38
- package/chunks/{settings-2P6SMCR5.js → settings-GIE22AYF.js} +9 -10
- package/chunks/{shell-5V6CJVMZ.js → shell-JD52AIMU.js} +6 -7
- package/chunks/{skill-YCDQKIQQ.js → skill-L4IFKRQ5.js} +14 -4
- package/chunks/{spawnChannel-NEOZMKQL.js → spawnChannel-HGMOEPM7.js} +8 -9
- package/chunks/{src-OSBARWA7.js → src-3SNF3G3V.js} +40 -17
- package/chunks/{startInteractiveUI-YKNL25BC.js → startInteractiveUI-C7DCDHGF.js} +5011 -13181
- package/chunks/{team-create-DWR5UZIT.js → team-create-NYL7KAMA.js} +6 -7
- package/chunks/{team-plan-approval-IVROZF6V.js → team-plan-approval-LH65JBEH.js} +6 -7
- package/chunks/{theme-manager-C5E2HDJG.js → theme-manager-ANKQO5DR.js} +6 -7
- package/chunks/{tool-search-YX2YOBFJ.js → tool-search-WZEGJFO4.js} +4 -21
- package/chunks/{trustedFolders-7KI7ETLR.js → trustedFolders-HECAG3S3.js} +7 -8
- package/chunks/updateCheck-72BBAE5V.js +65 -0
- package/chunks/{validateNonInterActiveAuth-JUTUFRSI.js → validateNonInterActiveAuth-PDQCPSNA.js} +38 -34
- package/chunks/{version-4DEOOFHV.js → version-VSEUTPNM.js} +2 -1
- package/chunks/{workflow-FSJVAP5Q.js → workflow-DPF2CL7U.js} +7 -8
- package/chunks/{workspace-providers-status-YBIB2D77.js → workspace-providers-status-TQ23DQ2D.js} +12 -13
- package/chunks/{workspace-service-K27FZHY6.js → workspace-service-D34UDMEY.js} +14 -15
- package/chunks/{workspace-skills-status-QKXCNLVT.js → workspace-skills-status-4DR5UNNC.js} +11 -12
- package/chunks/{write-file-7XMET6EL.js → write-file-VFNAKGYM.js} +6 -7
- package/chunks/{zh-RS3HZHFH.js → zh-7XVVRTDH.js} +5 -1
- package/chunks/{zh-TW-L5GRQ4S4.js → zh-TW-DPH554OG.js} +5 -1
- package/cli.js +11 -11
- package/locales/en.js +8 -2
- package/locales/fr.js +6 -0
- package/locales/ja.js +6 -0
- package/locales/zh-TW.js +8 -2
- package/locales/zh.js +8 -2
- package/node_modules/@qwen-code/audio-capture/package.json +1 -1
- package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-arm64/@qwen-code+audio-capture.node +0 -0
- package/node_modules/@qwen-code/audio-capture/prebuilds/darwin-x64/@qwen-code+audio-capture.node +0 -0
- package/node_modules/@qwen-code/audio-capture/prebuilds/win32-x64/@qwen-code+audio-capture.node +0 -0
- package/package.json +3 -3
- package/web-shell/assets/{arc-DsvupRmw.js → arc-C7hjaa7g.js} +1 -1
- package/web-shell/assets/{architectureDiagram-3BPJPVTR-BwogiDi8.js → architectureDiagram-3BPJPVTR-pm9KtMZY.js} +1 -1
- package/web-shell/assets/{blockDiagram-GPEHLZMM-BibxECWL.js → blockDiagram-GPEHLZMM-CApElKsU.js} +1 -1
- package/web-shell/assets/{c4Diagram-AAUBKEIU-7cCkrqli.js → c4Diagram-AAUBKEIU-BeDe0-9N.js} +1 -1
- package/web-shell/assets/channel-BNf1dW-j.js +1 -0
- package/web-shell/assets/{chunk-2J33WTMH-DVJvJrzC.js → chunk-2J33WTMH-DJEkmLdl.js} +1 -1
- package/web-shell/assets/{chunk-4BX2VUAB-BFd1gVwi.js → chunk-4BX2VUAB-DXX8gdpe.js} +1 -1
- package/web-shell/assets/{chunk-55IACEB6-BW2NnZiy.js → chunk-55IACEB6-DORmk1kQ.js} +1 -1
- package/web-shell/assets/{chunk-727SXJPM-FUpKPObH.js → chunk-727SXJPM-DWmO4mCZ.js} +1 -1
- package/web-shell/assets/{chunk-AQP2D5EJ-DqoSTkq2.js → chunk-AQP2D5EJ-C7LRaYw2.js} +1 -1
- package/web-shell/assets/{chunk-FMBD7UC4-D6Tt9URv.js → chunk-FMBD7UC4-CCdXgyC5.js} +1 -1
- package/web-shell/assets/{chunk-ND2GUHAM-B9pTpaYB.js → chunk-ND2GUHAM-SyL_xvWc.js} +1 -1
- package/web-shell/assets/{chunk-QZHKN3VN-KJs617M2.js → chunk-QZHKN3VN-BzOuCKxa.js} +1 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-uOIGlzgi.js +1 -0
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-uOIGlzgi.js +1 -0
- package/web-shell/assets/{cose-bilkent-S5V4N54A-B-pCB_lI.js → cose-bilkent-S5V4N54A-BMPWyDKc.js} +1 -1
- package/web-shell/assets/{dagre-BM42HDAG-pCqrm_dE.js → dagre-BM42HDAG-B_GnPWik.js} +1 -1
- package/web-shell/assets/{diagram-2AECGRRQ-CJd4RXQc.js → diagram-2AECGRRQ-fJqMc3S_.js} +1 -1
- package/web-shell/assets/{diagram-5GNKFQAL-CP0ntTEt.js → diagram-5GNKFQAL-ximV3O9D.js} +1 -1
- package/web-shell/assets/{diagram-KO2AKTUF-HKMjC8U_.js → diagram-KO2AKTUF-2vVyjzqw.js} +1 -1
- package/web-shell/assets/{diagram-LMA3HP47-BnUMTzxK.js → diagram-LMA3HP47-Dckcx1sT.js} +1 -1
- package/web-shell/assets/{diagram-OG6HWLK6-CY2kjke3.js → diagram-OG6HWLK6-BAVv47ga.js} +1 -1
- package/web-shell/assets/{erDiagram-TEJ5UH35-Q-P2C_2c.js → erDiagram-TEJ5UH35-Bn3nf5J0.js} +1 -1
- package/web-shell/assets/{flowDiagram-I6XJVG4X-D5dDqv6k.js → flowDiagram-I6XJVG4X-BIbGF9JT.js} +1 -1
- package/web-shell/assets/{ganttDiagram-6RSMTGT7-D6e_nLNG.js → ganttDiagram-6RSMTGT7-CDbhb3AH.js} +1 -1
- package/web-shell/assets/{gitGraphDiagram-PVQCEYII-Cagi_aG2.js → gitGraphDiagram-PVQCEYII-CbKG1a_w.js} +1 -1
- package/web-shell/assets/index-CnE2ur2L.js +779 -0
- package/web-shell/assets/index-hSKXrlSE.css +5 -0
- package/web-shell/assets/{infoDiagram-5YYISTIA-CCRsrfey.js → infoDiagram-5YYISTIA-DUFxTy8m.js} +1 -1
- package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-o1nEXhOa.js → ishikawaDiagram-YF4QCWOH-D69hrBG8.js} +1 -1
- package/web-shell/assets/{journeyDiagram-JHISSGLW-yNY7DUDz.js → journeyDiagram-JHISSGLW-DsvEw51Y.js} +1 -1
- package/web-shell/assets/{kanban-definition-UN3LZRKU-C2CG5vmP.js → kanban-definition-UN3LZRKU-lFFjZEYd.js} +1 -1
- package/web-shell/assets/{linear-Jsjhveg2.js → linear-Bo5bWzvo.js} +1 -1
- package/web-shell/assets/{mermaid.core-CCrKRcoG.js → mermaid.core-C_HKMJx5.js} +5 -5
- package/web-shell/assets/{mindmap-definition-RKZ34NQL-C8yHT5I5.js → mindmap-definition-RKZ34NQL-DlV-NUTs.js} +1 -1
- package/web-shell/assets/{pieDiagram-4H26LBE5-BnVzUzO5.js → pieDiagram-4H26LBE5-B2e6FH2a.js} +1 -1
- package/web-shell/assets/{quadrantDiagram-W4KKPZXB-EP9K_nK2.js → quadrantDiagram-W4KKPZXB-D75kSk-b.js} +1 -1
- package/web-shell/assets/{requirementDiagram-4Y6WPE33-D_Q9FvKR.js → requirementDiagram-4Y6WPE33-CQ6xakhM.js} +1 -1
- package/web-shell/assets/{sankeyDiagram-5OEKKPKP-Bh08id0J.js → sankeyDiagram-5OEKKPKP-J_XTKrcY.js} +1 -1
- package/web-shell/assets/{sequenceDiagram-3UESZ5HK-Btm9ZGN_.js → sequenceDiagram-3UESZ5HK-D7jTcBtf.js} +1 -1
- package/web-shell/assets/{stateDiagram-AJRCARHV-CR9Ym-Fy.js → stateDiagram-AJRCARHV-CrlM_OT3.js} +1 -1
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-Bs4TK_84.js +1 -0
- package/web-shell/assets/{timeline-definition-PNZ67QCA-TUsY0ZLs.js → timeline-definition-PNZ67QCA-5oZi76Ef.js} +1 -1
- package/web-shell/assets/{vennDiagram-CIIHVFJN-DqzVmJAq.js → vennDiagram-CIIHVFJN-DO5YptPK.js} +1 -1
- package/web-shell/assets/{wardley-L42UT6IY-DlUQaEG_.js → wardley-L42UT6IY-4t1eJDvM.js} +1 -1
- package/web-shell/assets/{wardleyDiagram-YWT4CUSO-mvwPkaJp.js → wardleyDiagram-YWT4CUSO-DKZc661S.js} +1 -1
- package/web-shell/assets/{xychartDiagram-2RQKCTM6-CJfXdkkx.js → xychartDiagram-2RQKCTM6-su9hp08G.js} +1 -1
- package/web-shell/index.html +2 -2
- package/chunks/chunk-QT7XKZT7.js +0 -293
- package/web-shell/assets/channel-Bhs5kHEh.js +0 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-BGHJQZmo.js +0 -1
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-BGHJQZmo.js +0 -1
- package/web-shell/assets/index-DoGSgv5C.css +0 -5
- package/web-shell/assets/index-lWWJkqt9.js +0 -754
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-6aAMKcly.js +0 -1
|
@@ -0,0 +1,949 @@
|
|
|
1
|
+
// Force strict mode and setup for ESM
|
|
2
|
+
"use strict";
|
|
3
|
+
import {
|
|
4
|
+
require_undici
|
|
5
|
+
} from "./chunk-54YZOP7P.js";
|
|
6
|
+
import {
|
|
7
|
+
So,
|
|
8
|
+
isGitRepository
|
|
9
|
+
} from "./chunk-ZWFS7YCB.js";
|
|
10
|
+
import {
|
|
11
|
+
createDebugLogger
|
|
12
|
+
} from "./chunk-22MNNNXE.js";
|
|
13
|
+
import {
|
|
14
|
+
init_esbuild_shims
|
|
15
|
+
} from "./chunk-A4BMJM77.js";
|
|
16
|
+
import {
|
|
17
|
+
__name,
|
|
18
|
+
__toESM
|
|
19
|
+
} from "./chunk-J2S4EL5Y.js";
|
|
20
|
+
|
|
21
|
+
// packages/cli/src/utils/installationInfo.ts
|
|
22
|
+
init_esbuild_shims();
|
|
23
|
+
import * as fs from "node:fs";
|
|
24
|
+
import * as path from "node:path";
|
|
25
|
+
import * as childProcess from "node:child_process";
|
|
26
|
+
var debugLogger = createDebugLogger("INSTALLATION_INFO");
|
|
27
|
+
var STANDALONE_UNIX_INSTALLER = "https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen-standalone.sh";
|
|
28
|
+
var STANDALONE_WINDOWS_INSTALLER = "https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/installation/install-qwen-standalone.ps1";
|
|
29
|
+
function getInstallationInfo(projectRoot, isAutoUpdateEnabled) {
|
|
30
|
+
const cliPath = process.argv[1];
|
|
31
|
+
if (!cliPath) {
|
|
32
|
+
return { packageManager: "unknown" /* UNKNOWN */, isGlobal: false };
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
const realPath = fs.realpathSync(cliPath).replace(/\\/g, "/");
|
|
36
|
+
const normalizedProjectRoot = projectRoot?.replace(/\\/g, "/");
|
|
37
|
+
const isGit = isGitRepository(process.cwd());
|
|
38
|
+
if (isGit && normalizedProjectRoot && isSamePathOrInside(realPath, normalizedProjectRoot) && !realPath.includes("/node_modules/")) {
|
|
39
|
+
return {
|
|
40
|
+
packageManager: "unknown" /* UNKNOWN */,
|
|
41
|
+
// Not managed by a package manager in this sense
|
|
42
|
+
isGlobal: false,
|
|
43
|
+
updateMessage: 'Running from a local git clone. Please update with "git pull".'
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (realPath.includes("/.npm/_npx") || realPath.includes("/npm/_npx")) {
|
|
47
|
+
return {
|
|
48
|
+
packageManager: "npx" /* NPX */,
|
|
49
|
+
isGlobal: false,
|
|
50
|
+
updateMessage: "Running via npx, update not applicable."
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
if (realPath.includes("/.pnpm/_pnpx")) {
|
|
54
|
+
return {
|
|
55
|
+
packageManager: "pnpx" /* PNPX */,
|
|
56
|
+
isGlobal: false,
|
|
57
|
+
updateMessage: "Running via pnpx, update not applicable."
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const standaloneInfo = getStandaloneInstallInfo(
|
|
61
|
+
realPath,
|
|
62
|
+
isAutoUpdateEnabled
|
|
63
|
+
);
|
|
64
|
+
if (standaloneInfo) {
|
|
65
|
+
return standaloneInfo;
|
|
66
|
+
}
|
|
67
|
+
if (process.platform === "darwin") {
|
|
68
|
+
try {
|
|
69
|
+
childProcess.execSync('brew list -1 | grep -q "^qwen-code$"', {
|
|
70
|
+
stdio: "ignore"
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
packageManager: "homebrew" /* HOMEBREW */,
|
|
74
|
+
isGlobal: true,
|
|
75
|
+
updateMessage: 'Installed via Homebrew. Please update with "brew upgrade".'
|
|
76
|
+
};
|
|
77
|
+
} catch (_error) {
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (realPath.includes("/.pnpm/global")) {
|
|
81
|
+
const updateCommand2 = "pnpm add -g @qwen-code/qwen-code@latest";
|
|
82
|
+
return {
|
|
83
|
+
packageManager: "pnpm" /* PNPM */,
|
|
84
|
+
isGlobal: true,
|
|
85
|
+
updateCommand: updateCommand2,
|
|
86
|
+
updateMessage: isAutoUpdateEnabled ? "Installed with pnpm. Attempting to automatically update now..." : `Please run ${updateCommand2} to update`
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (realPath.includes("/.yarn/global")) {
|
|
90
|
+
const updateCommand2 = "yarn global add @qwen-code/qwen-code@latest";
|
|
91
|
+
return {
|
|
92
|
+
packageManager: "yarn" /* YARN */,
|
|
93
|
+
isGlobal: true,
|
|
94
|
+
updateCommand: updateCommand2,
|
|
95
|
+
updateMessage: isAutoUpdateEnabled ? "Installed with yarn. Attempting to automatically update now..." : `Please run ${updateCommand2} to update`
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
if (realPath.includes("/.bun/install/cache")) {
|
|
99
|
+
return {
|
|
100
|
+
packageManager: "bunx" /* BUNX */,
|
|
101
|
+
isGlobal: false,
|
|
102
|
+
updateMessage: "Running via bunx, update not applicable."
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
if (realPath.includes("/.bun/bin")) {
|
|
106
|
+
const updateCommand2 = "bun add -g @qwen-code/qwen-code@latest";
|
|
107
|
+
return {
|
|
108
|
+
packageManager: "bun" /* BUN */,
|
|
109
|
+
isGlobal: true,
|
|
110
|
+
updateCommand: updateCommand2,
|
|
111
|
+
updateMessage: isAutoUpdateEnabled ? "Installed with bun. Attempting to automatically update now..." : `Please run ${updateCommand2} to update`
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (normalizedProjectRoot && isSamePathOrInside(realPath, `${normalizedProjectRoot}/node_modules`)) {
|
|
115
|
+
let pm = "npm" /* NPM */;
|
|
116
|
+
if (fs.existsSync(path.join(projectRoot, "yarn.lock"))) {
|
|
117
|
+
pm = "yarn" /* YARN */;
|
|
118
|
+
} else if (fs.existsSync(path.join(projectRoot, "pnpm-lock.yaml"))) {
|
|
119
|
+
pm = "pnpm" /* PNPM */;
|
|
120
|
+
} else if (fs.existsSync(path.join(projectRoot, "bun.lockb"))) {
|
|
121
|
+
pm = "bun" /* BUN */;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
packageManager: pm,
|
|
125
|
+
isGlobal: false,
|
|
126
|
+
updateMessage: "Locally installed. Please update via your project's package.json."
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const npmPackageDir = path.dirname(path.dirname(realPath));
|
|
130
|
+
let npmPrefixWritable = false;
|
|
131
|
+
try {
|
|
132
|
+
fs.accessSync(npmPackageDir, fs.constants.W_OK);
|
|
133
|
+
npmPrefixWritable = true;
|
|
134
|
+
} catch {
|
|
135
|
+
}
|
|
136
|
+
if (!npmPrefixWritable) {
|
|
137
|
+
return {
|
|
138
|
+
packageManager: "npm" /* NPM */,
|
|
139
|
+
isGlobal: true,
|
|
140
|
+
updateMessage: "Update requires sudo. Please run: sudo npm install -g @qwen-code/qwen-code@latest"
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const updateCommand = "npm install -g @qwen-code/qwen-code@latest";
|
|
144
|
+
return {
|
|
145
|
+
packageManager: "npm" /* NPM */,
|
|
146
|
+
isGlobal: true,
|
|
147
|
+
updateCommand,
|
|
148
|
+
updateMessage: isAutoUpdateEnabled ? "Installed with npm. Attempting to automatically update now..." : `Please run ${updateCommand} to update`
|
|
149
|
+
};
|
|
150
|
+
} catch (error) {
|
|
151
|
+
debugLogger.error("Failed to detect installation info:", error);
|
|
152
|
+
return { packageManager: "unknown" /* UNKNOWN */, isGlobal: false };
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
__name(getInstallationInfo, "getInstallationInfo");
|
|
156
|
+
function stripTrailingSlashes(value) {
|
|
157
|
+
return value.replace(/\/+$/, "") || "/";
|
|
158
|
+
}
|
|
159
|
+
__name(stripTrailingSlashes, "stripTrailingSlashes");
|
|
160
|
+
function isSamePathOrInside(candidate, parent) {
|
|
161
|
+
const normalizedCandidate = stripTrailingSlashes(candidate);
|
|
162
|
+
const normalizedParent = stripTrailingSlashes(parent);
|
|
163
|
+
if (normalizedParent === "/") {
|
|
164
|
+
return normalizedCandidate === "/" || normalizedCandidate.startsWith("/");
|
|
165
|
+
}
|
|
166
|
+
return normalizedCandidate === normalizedParent || normalizedCandidate.startsWith(`${normalizedParent}/`);
|
|
167
|
+
}
|
|
168
|
+
__name(isSamePathOrInside, "isSamePathOrInside");
|
|
169
|
+
function getStandaloneInstallInfo(realPath, isAutoUpdateEnabled) {
|
|
170
|
+
const installDir = standaloneInstallDirForCliPath(realPath);
|
|
171
|
+
if (!installDir || !isStandaloneInstallDir(installDir)) {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const updateCommand = process.platform === "win32" ? `powershell -ExecutionPolicy Bypass -c "irm ${STANDALONE_WINDOWS_INSTALLER} | iex"` : `curl -fsSL ${STANDALONE_UNIX_INSTALLER} | bash`;
|
|
175
|
+
return {
|
|
176
|
+
packageManager: "standalone" /* STANDALONE */,
|
|
177
|
+
isGlobal: true,
|
|
178
|
+
isStandalone: true,
|
|
179
|
+
standaloneDir: installDir,
|
|
180
|
+
updateMessage: isAutoUpdateEnabled ? "Standalone install detected. Attempting to automatically update now..." : `Standalone install detected. Please rerun the standalone installer to update: ${updateCommand}`
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
__name(getStandaloneInstallInfo, "getStandaloneInstallInfo");
|
|
184
|
+
function standaloneInstallDirForCliPath(realPath) {
|
|
185
|
+
const normalized = realPath.replace(/\\/g, "/");
|
|
186
|
+
const suffix = "/lib/cli.js";
|
|
187
|
+
if (!normalized.endsWith(suffix)) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
return realPath.slice(0, -suffix.length);
|
|
191
|
+
}
|
|
192
|
+
__name(standaloneInstallDirForCliPath, "standaloneInstallDirForCliPath");
|
|
193
|
+
function isStandaloneInstallDir(installDir) {
|
|
194
|
+
try {
|
|
195
|
+
const manifestPath = path.join(installDir, "manifest.json");
|
|
196
|
+
if (!fs.existsSync(manifestPath)) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
200
|
+
if (manifest.name !== "@qwen-code/qwen-code" || typeof manifest.target !== "string" || !isStandaloneTargetForCurrentPlatform(manifest.target)) {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
const qwenBin = process.platform === "win32" ? path.join(installDir, "bin", "qwen.cmd") : path.join(installDir, "bin", "qwen");
|
|
204
|
+
const nodeBin = process.platform === "win32" ? path.join(installDir, "node", "node.exe") : path.join(installDir, "node", "bin", "node");
|
|
205
|
+
return fs.existsSync(qwenBin) && fs.existsSync(nodeBin) && isStandaloneRuntimeFile(qwenBin) && isStandaloneRuntimeFile(nodeBin);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
debugLogger.error("Standalone detection failed:", installDir, err);
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
__name(isStandaloneInstallDir, "isStandaloneInstallDir");
|
|
212
|
+
function isStandaloneTargetForCurrentPlatform(target) {
|
|
213
|
+
switch (process.platform) {
|
|
214
|
+
case "darwin":
|
|
215
|
+
return /^darwin-(arm64|x64)$/.test(target);
|
|
216
|
+
case "linux":
|
|
217
|
+
return /^linux-(arm64|x64)$/.test(target);
|
|
218
|
+
case "win32":
|
|
219
|
+
return /^win-(arm64|x64)$/.test(target);
|
|
220
|
+
default:
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
__name(isStandaloneTargetForCurrentPlatform, "isStandaloneTargetForCurrentPlatform");
|
|
225
|
+
function isStandaloneRuntimeFile(filePath) {
|
|
226
|
+
const stats = fs.lstatSync(filePath);
|
|
227
|
+
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
return process.platform === "win32" || (stats.mode & 73) !== 0;
|
|
231
|
+
}
|
|
232
|
+
__name(isStandaloneRuntimeFile, "isStandaloneRuntimeFile");
|
|
233
|
+
|
|
234
|
+
// packages/cli/src/utils/standalone-update.ts
|
|
235
|
+
init_esbuild_shims();
|
|
236
|
+
var import_undici = __toESM(require_undici(), 1);
|
|
237
|
+
import { createHash } from "node:crypto";
|
|
238
|
+
import * as fs2 from "node:fs";
|
|
239
|
+
import * as os from "node:os";
|
|
240
|
+
import * as path2 from "node:path";
|
|
241
|
+
import { Readable, Transform } from "node:stream";
|
|
242
|
+
import { spawn, execFile } from "node:child_process";
|
|
243
|
+
import { pipeline } from "node:stream/promises";
|
|
244
|
+
|
|
245
|
+
// packages/cli/src/utils/standalone-update-verify.ts
|
|
246
|
+
init_esbuild_shims();
|
|
247
|
+
import { createPublicKey, verify } from "node:crypto";
|
|
248
|
+
var RELEASE_PUBLIC_KEY_DER_B64 = "MCowBQYDK2VwAyEAr9WRFLDauibZQKCe1oKfuFZn6zRMaEkD5+KVqN6mKM4=";
|
|
249
|
+
function verifySignature(sha256sumsContent, signatureBase64) {
|
|
250
|
+
const key = createPublicKey({
|
|
251
|
+
key: Buffer.from(RELEASE_PUBLIC_KEY_DER_B64, "base64"),
|
|
252
|
+
format: "der",
|
|
253
|
+
type: "spki"
|
|
254
|
+
});
|
|
255
|
+
const signature = Buffer.from(signatureBase64, "base64");
|
|
256
|
+
if (signature.length !== 64) {
|
|
257
|
+
throw new Error(
|
|
258
|
+
`Invalid signature length: expected 64 bytes, got ${signature.length}`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
const valid = verify(null, Buffer.from(sha256sumsContent), key, signature);
|
|
262
|
+
if (!valid) {
|
|
263
|
+
throw new Error(
|
|
264
|
+
"SHA256SUMS signature verification failed \u2014 possible tampering detected"
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
__name(verifySignature, "verifySignature");
|
|
269
|
+
|
|
270
|
+
// packages/cli/src/utils/standalone-update.ts
|
|
271
|
+
var debugLogger2 = createDebugLogger("STANDALONE_UPDATE");
|
|
272
|
+
var OSS_BASE = "https://qwen-code-assets.oss-cn-hangzhou.aliyuncs.com/releases/qwen-code";
|
|
273
|
+
var GITHUB_BASE = "https://github.com/QwenLM/qwen-code/releases/download";
|
|
274
|
+
var FETCH_TIMEOUT_MS = 3e4;
|
|
275
|
+
var ARCHIVE_TIMEOUT_MS = 3e5;
|
|
276
|
+
var VALID_TARGETS = /* @__PURE__ */ new Set([
|
|
277
|
+
"darwin-arm64",
|
|
278
|
+
"darwin-x64",
|
|
279
|
+
"linux-arm64",
|
|
280
|
+
"linux-x64",
|
|
281
|
+
"win-x64"
|
|
282
|
+
]);
|
|
283
|
+
var SEMVER_RE = /^v?\d+\.\d+\.\d+(-[\w.]+)?$/;
|
|
284
|
+
function normalizeVersion(version) {
|
|
285
|
+
if (!SEMVER_RE.test(version)) {
|
|
286
|
+
throw new Error(`Invalid version format: ${version}`);
|
|
287
|
+
}
|
|
288
|
+
return version.startsWith("v") ? version : `v${version}`;
|
|
289
|
+
}
|
|
290
|
+
__name(normalizeVersion, "normalizeVersion");
|
|
291
|
+
function validateTarget(target) {
|
|
292
|
+
if (!VALID_TARGETS.has(target)) {
|
|
293
|
+
throw new Error(`Unknown target: ${target}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
__name(validateTarget, "validateTarget");
|
|
297
|
+
function archiveFilename(target) {
|
|
298
|
+
const ext = target.startsWith("win") ? "zip" : "tar.gz";
|
|
299
|
+
return `qwen-code-${target}.${ext}`;
|
|
300
|
+
}
|
|
301
|
+
__name(archiveFilename, "archiveFilename");
|
|
302
|
+
function escapePS(s) {
|
|
303
|
+
return s.replace(/'/g, "''");
|
|
304
|
+
}
|
|
305
|
+
__name(escapePS, "escapePS");
|
|
306
|
+
async function tryFetch(url, timeoutMs = FETCH_TIMEOUT_MS) {
|
|
307
|
+
try {
|
|
308
|
+
const res = await (0, import_undici.fetch)(url, {
|
|
309
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
310
|
+
});
|
|
311
|
+
if (res.ok) return { response: res };
|
|
312
|
+
await res.body?.cancel().catch(() => {
|
|
313
|
+
});
|
|
314
|
+
return { error: new Error(`HTTP ${res.status} ${res.statusText}`) };
|
|
315
|
+
} catch (err) {
|
|
316
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
317
|
+
debugLogger2.debug(`Fetch failed for ${url}: ${error.message}`);
|
|
318
|
+
return { error };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
__name(tryFetch, "tryFetch");
|
|
322
|
+
async function downloadWithFallback(versionPath, filename, timeoutMs = FETCH_TIMEOUT_MS) {
|
|
323
|
+
const ossUrl = `${OSS_BASE}/${versionPath}/${filename}`;
|
|
324
|
+
const ossResult = await tryFetch(ossUrl, timeoutMs);
|
|
325
|
+
if (ossResult.response) return ossResult.response;
|
|
326
|
+
const ghUrl = `${GITHUB_BASE}/${versionPath}/${filename}`;
|
|
327
|
+
const ghResult = await tryFetch(ghUrl, timeoutMs);
|
|
328
|
+
if (ghResult.response) return ghResult.response;
|
|
329
|
+
throw new Error(
|
|
330
|
+
`Failed to download ${filename}: OSS (${ossResult.error?.message ?? "unknown"}), GitHub (${ghResult.error?.message ?? "unknown"})`
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
__name(downloadWithFallback, "downloadWithFallback");
|
|
334
|
+
async function verifyChecksum(actualHash, filename, versionPath) {
|
|
335
|
+
const response = await downloadWithFallback(versionPath, "SHA256SUMS");
|
|
336
|
+
const text = await response.text();
|
|
337
|
+
const requireSig = process.env["QWEN_REQUIRE_SIGNATURE"] === "1";
|
|
338
|
+
let sigResponse;
|
|
339
|
+
try {
|
|
340
|
+
sigResponse = await downloadWithFallback(versionPath, "SHA256SUMS.sig");
|
|
341
|
+
} catch (err) {
|
|
342
|
+
debugLogger2.debug("SHA256SUMS.sig not available:", err);
|
|
343
|
+
}
|
|
344
|
+
if (sigResponse) {
|
|
345
|
+
const sigContent = await sigResponse.text();
|
|
346
|
+
verifySignature(text, sigContent.trim());
|
|
347
|
+
debugLogger2.info("SHA256SUMS signature verified.");
|
|
348
|
+
} else if (requireSig) {
|
|
349
|
+
throw new Error(
|
|
350
|
+
"SHA256SUMS.sig not found and QWEN_REQUIRE_SIGNATURE=1 is set"
|
|
351
|
+
);
|
|
352
|
+
} else {
|
|
353
|
+
debugLogger2.warn(
|
|
354
|
+
"SHA256SUMS.sig not available \u2014 update integrity relies on SHA256 checksum only. Set QWEN_REQUIRE_SIGNATURE=1 to enforce signature verification."
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
const expectedLine = text.split("\n").find((line) => {
|
|
358
|
+
const parts = line.trim().split(/\s+/);
|
|
359
|
+
if (parts.length < 2) return false;
|
|
360
|
+
const name = parts[parts.length - 1].replace(/^\*/, "");
|
|
361
|
+
return name === filename;
|
|
362
|
+
});
|
|
363
|
+
if (!expectedLine) {
|
|
364
|
+
throw new Error(`No checksum found for ${filename} in SHA256SUMS`);
|
|
365
|
+
}
|
|
366
|
+
const expectedHash = expectedLine.trim().split(/\s+/)[0];
|
|
367
|
+
if (actualHash !== expectedHash) {
|
|
368
|
+
throw new Error(
|
|
369
|
+
`Checksum mismatch: expected ${expectedHash}, got ${actualHash}`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
__name(verifyChecksum, "verifyChecksum");
|
|
374
|
+
var MAX_DOWNLOAD_BYTES = 512 * 1024 * 1024;
|
|
375
|
+
async function downloadToFile(versionPath, filename, destPath) {
|
|
376
|
+
const response = await downloadWithFallback(
|
|
377
|
+
versionPath,
|
|
378
|
+
filename,
|
|
379
|
+
ARCHIVE_TIMEOUT_MS
|
|
380
|
+
);
|
|
381
|
+
const body = response.body;
|
|
382
|
+
if (!body) throw new Error("Empty response body");
|
|
383
|
+
const contentLength = response.headers.get("content-length");
|
|
384
|
+
if (contentLength && parseInt(contentLength, 10) > MAX_DOWNLOAD_BYTES) {
|
|
385
|
+
await body.cancel().catch(() => {
|
|
386
|
+
});
|
|
387
|
+
throw new Error(
|
|
388
|
+
`Download too large: ${contentLength} bytes exceeds ${MAX_DOWNLOAD_BYTES} limit`
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
const hash = createHash("sha256");
|
|
392
|
+
let bytesWritten = 0;
|
|
393
|
+
const dest = fs2.createWriteStream(destPath);
|
|
394
|
+
const sizeGuard = new Transform({
|
|
395
|
+
transform(chunk, _encoding, callback) {
|
|
396
|
+
bytesWritten += chunk.length;
|
|
397
|
+
if (bytesWritten > MAX_DOWNLOAD_BYTES) {
|
|
398
|
+
callback(
|
|
399
|
+
new Error(`Download exceeded ${MAX_DOWNLOAD_BYTES} byte limit`)
|
|
400
|
+
);
|
|
401
|
+
} else {
|
|
402
|
+
hash.update(chunk);
|
|
403
|
+
callback(null, chunk);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
});
|
|
407
|
+
await pipeline(Readable.fromWeb(body), sizeGuard, dest);
|
|
408
|
+
return hash.digest("hex");
|
|
409
|
+
}
|
|
410
|
+
__name(downloadToFile, "downloadToFile");
|
|
411
|
+
function validateExtractedPaths(resolvedDest) {
|
|
412
|
+
const entries = fs2.readdirSync(resolvedDest, {
|
|
413
|
+
recursive: true,
|
|
414
|
+
withFileTypes: true
|
|
415
|
+
});
|
|
416
|
+
for (const entry of entries) {
|
|
417
|
+
const fullPath = path2.join(
|
|
418
|
+
String(entry.parentPath || entry.path),
|
|
419
|
+
entry.name
|
|
420
|
+
);
|
|
421
|
+
const resolved = fs2.realpathSync(fullPath);
|
|
422
|
+
if (!resolved.startsWith(resolvedDest + path2.sep) && resolved !== resolvedDest) {
|
|
423
|
+
fs2.rmSync(resolvedDest, { recursive: true, force: true });
|
|
424
|
+
throw new Error(
|
|
425
|
+
`Path traversal detected in archive: ${entry.name} resolves to ${resolved}`
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
__name(validateExtractedPaths, "validateExtractedPaths");
|
|
431
|
+
function isSafeTarEntryPath(entryPath) {
|
|
432
|
+
if (entryPath.length === 0) return false;
|
|
433
|
+
if (path2.posix.isAbsolute(entryPath) || path2.win32.isAbsolute(entryPath)) {
|
|
434
|
+
return false;
|
|
435
|
+
}
|
|
436
|
+
return !entryPath.split(/[\\/]+/).includes("..");
|
|
437
|
+
}
|
|
438
|
+
__name(isSafeTarEntryPath, "isSafeTarEntryPath");
|
|
439
|
+
async function extractArchive(archivePath, destDir, target) {
|
|
440
|
+
fs2.mkdirSync(destDir, { recursive: true });
|
|
441
|
+
if (target.startsWith("win")) {
|
|
442
|
+
await new Promise((resolve2, reject) => {
|
|
443
|
+
const ps = spawn(
|
|
444
|
+
"powershell.exe",
|
|
445
|
+
[
|
|
446
|
+
"-NoProfile",
|
|
447
|
+
"-Command",
|
|
448
|
+
`Expand-Archive -Path '${escapePS(archivePath)}' -DestinationPath '${escapePS(destDir)}' -Force`
|
|
449
|
+
],
|
|
450
|
+
{ stdio: "ignore" }
|
|
451
|
+
);
|
|
452
|
+
ps.on(
|
|
453
|
+
"close",
|
|
454
|
+
(code) => code === 0 ? resolve2() : reject(new Error(`Expand-Archive exited with code ${code}`))
|
|
455
|
+
);
|
|
456
|
+
ps.on("error", reject);
|
|
457
|
+
});
|
|
458
|
+
const resolvedDest = fs2.realpathSync(destDir);
|
|
459
|
+
validateExtractedPaths(resolvedDest);
|
|
460
|
+
} else {
|
|
461
|
+
const resolvedDest = path2.resolve(destDir);
|
|
462
|
+
await So({
|
|
463
|
+
file: archivePath,
|
|
464
|
+
cwd: destDir,
|
|
465
|
+
preservePaths: false,
|
|
466
|
+
filter: /* @__PURE__ */ __name((p, entry) => {
|
|
467
|
+
if (!isSafeTarEntryPath(p)) return false;
|
|
468
|
+
if ("type" in entry && entry.type === "SymbolicLink" && "linkpath" in entry) {
|
|
469
|
+
const linkTarget = path2.resolve(
|
|
470
|
+
resolvedDest,
|
|
471
|
+
path2.dirname(p),
|
|
472
|
+
String(entry.linkpath)
|
|
473
|
+
);
|
|
474
|
+
if (!linkTarget.startsWith(resolvedDest + path2.sep) && linkTarget !== resolvedDest) {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return true;
|
|
479
|
+
}, "filter")
|
|
480
|
+
});
|
|
481
|
+
validateExtractedPaths(fs2.realpathSync(destDir));
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
__name(extractArchive, "extractArchive");
|
|
485
|
+
function spawnAndCapture(command, args, timeoutMs) {
|
|
486
|
+
return new Promise((resolve2, reject) => {
|
|
487
|
+
let settled = false;
|
|
488
|
+
const child = execFile(
|
|
489
|
+
command,
|
|
490
|
+
args,
|
|
491
|
+
{ timeout: timeoutMs },
|
|
492
|
+
(err, out, stderr) => {
|
|
493
|
+
if (settled) return;
|
|
494
|
+
settled = true;
|
|
495
|
+
if (err && ("killed" in err && err.killed || "signal" in err && err.signal)) {
|
|
496
|
+
reject(new Error("Smoke test timed out"));
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (err) {
|
|
500
|
+
const exitCode = "code" in err && typeof err.code === "number" ? err.code : 1;
|
|
501
|
+
resolve2({ exitCode, stdout: out || "", stderr: stderr || "" });
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
resolve2({ exitCode: 0, stdout: out || "", stderr: stderr || "" });
|
|
505
|
+
}
|
|
506
|
+
);
|
|
507
|
+
child.on("error", (e) => {
|
|
508
|
+
if (settled) return;
|
|
509
|
+
settled = true;
|
|
510
|
+
reject(e);
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
__name(spawnAndCapture, "spawnAndCapture");
|
|
515
|
+
async function smokeTest(newInstallDir, target) {
|
|
516
|
+
const resolvedInstallDir = path2.resolve(newInstallDir);
|
|
517
|
+
const nodeBin = target.startsWith("win") ? path2.join(resolvedInstallDir, "node", "node.exe") : path2.join(resolvedInstallDir, "node", "bin", "node");
|
|
518
|
+
const cliBin = path2.join(resolvedInstallDir, "lib", "cli.js");
|
|
519
|
+
if (!fs2.existsSync(nodeBin)) {
|
|
520
|
+
throw new Error(`Smoke test failed: node binary not found at ${nodeBin}`);
|
|
521
|
+
}
|
|
522
|
+
if (!fs2.existsSync(cliBin)) {
|
|
523
|
+
throw new Error(`Smoke test failed: cli.js not found at ${cliBin}`);
|
|
524
|
+
}
|
|
525
|
+
const { exitCode, stdout, stderr } = await spawnAndCapture(
|
|
526
|
+
nodeBin,
|
|
527
|
+
[cliBin, "--version"],
|
|
528
|
+
1e4
|
|
529
|
+
);
|
|
530
|
+
if (exitCode !== 0) {
|
|
531
|
+
const detail = stderr.trim() ? `: ${stderr.trim()}` : "";
|
|
532
|
+
throw new Error(
|
|
533
|
+
`Smoke test failed: new binary exited with code ${exitCode}${detail}`
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
const version = stdout.trim();
|
|
537
|
+
if (!SEMVER_RE.test(version)) {
|
|
538
|
+
throw new Error(
|
|
539
|
+
`Smoke test failed: unexpected version output "${version}"`
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
debugLogger2.info(`Smoke test passed: ${version}`);
|
|
543
|
+
}
|
|
544
|
+
__name(smokeTest, "smokeTest");
|
|
545
|
+
function acquireLock(lockPath) {
|
|
546
|
+
try {
|
|
547
|
+
fs2.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
548
|
+
return true;
|
|
549
|
+
} catch {
|
|
550
|
+
try {
|
|
551
|
+
const pidStr = fs2.readFileSync(lockPath, "utf-8").trim();
|
|
552
|
+
const pid = parseInt(pidStr, 10);
|
|
553
|
+
if (Number.isNaN(pid) || !isProcessAlive(pid)) {
|
|
554
|
+
fs2.unlinkSync(lockPath);
|
|
555
|
+
try {
|
|
556
|
+
fs2.writeFileSync(lockPath, String(process.pid), { flag: "wx" });
|
|
557
|
+
return true;
|
|
558
|
+
} catch {
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
} catch {
|
|
563
|
+
}
|
|
564
|
+
return false;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
__name(acquireLock, "acquireLock");
|
|
568
|
+
function releaseLock(lockPath) {
|
|
569
|
+
try {
|
|
570
|
+
fs2.unlinkSync(lockPath);
|
|
571
|
+
} catch {
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
__name(releaseLock, "releaseLock");
|
|
575
|
+
function cleanupEmptyStandaloneDir(standaloneDir) {
|
|
576
|
+
const manifestPath = path2.join(standaloneDir, "manifest.json");
|
|
577
|
+
if (fs2.existsSync(standaloneDir) && !fs2.existsSync(manifestPath)) {
|
|
578
|
+
fs2.rmSync(standaloneDir, { recursive: true, force: true });
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
__name(cleanupEmptyStandaloneDir, "cleanupEmptyStandaloneDir");
|
|
582
|
+
var UNSAFE_SHELL_CHARS = /["`$\\;\n\r]/;
|
|
583
|
+
var UNSAFE_CMD_CHARS = /[&|<>^%!"`\n\r]/;
|
|
584
|
+
function assertSafeForShellEmbed(p, context) {
|
|
585
|
+
if (UNSAFE_SHELL_CHARS.test(p)) {
|
|
586
|
+
throw new Error(
|
|
587
|
+
`${context} contains characters unsafe for shell embedding: ${p}`
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
__name(assertSafeForShellEmbed, "assertSafeForShellEmbed");
|
|
592
|
+
function isProcessAlive(pid) {
|
|
593
|
+
try {
|
|
594
|
+
process.kill(pid, 0);
|
|
595
|
+
return true;
|
|
596
|
+
} catch {
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
__name(isProcessAlive, "isProcessAlive");
|
|
601
|
+
function atomicReplace(standaloneDir, newDir, lockPath) {
|
|
602
|
+
const oldDir = `${standaloneDir}.old`;
|
|
603
|
+
const pendingDir = `${standaloneDir}.new`;
|
|
604
|
+
if (fs2.existsSync(oldDir)) {
|
|
605
|
+
fs2.rmSync(oldDir, { recursive: true, force: true });
|
|
606
|
+
}
|
|
607
|
+
if (os.platform() === "win32") {
|
|
608
|
+
if (UNSAFE_CMD_CHARS.test(standaloneDir) || UNSAFE_CMD_CHARS.test(oldDir) || UNSAFE_CMD_CHARS.test(pendingDir)) {
|
|
609
|
+
throw new Error(
|
|
610
|
+
"Installation path contains characters unsafe for deferred update script"
|
|
611
|
+
);
|
|
612
|
+
}
|
|
613
|
+
if (fs2.existsSync(pendingDir)) {
|
|
614
|
+
fs2.rmSync(pendingDir, { recursive: true, force: true });
|
|
615
|
+
}
|
|
616
|
+
fs2.renameSync(newDir, pendingDir);
|
|
617
|
+
const lockFile = lockPath;
|
|
618
|
+
const logFile = path2.join(path2.dirname(standaloneDir), "qwen-update.log");
|
|
619
|
+
const script = [
|
|
620
|
+
"@echo off",
|
|
621
|
+
"set /a TRIES=0",
|
|
622
|
+
":wait",
|
|
623
|
+
"set /a TRIES+=1",
|
|
624
|
+
"if %TRIES% GTR 30 goto proceed",
|
|
625
|
+
`tasklist /FI "PID eq ${process.pid}" 2>nul | find "${process.pid}" >nul && (timeout /t 1 >nul & goto wait)`,
|
|
626
|
+
":proceed",
|
|
627
|
+
`echo [%DATE% %TIME%] starting swap >> "${logFile}"`,
|
|
628
|
+
`move /Y "${standaloneDir}" "${oldDir}"`,
|
|
629
|
+
"if errorlevel 1 goto move1_failed",
|
|
630
|
+
`move /Y "${pendingDir}" "${standaloneDir}"`,
|
|
631
|
+
"if errorlevel 1 goto move2_failed",
|
|
632
|
+
`echo [%DATE% %TIME%] swap completed >> "${logFile}"`,
|
|
633
|
+
"goto cleanup",
|
|
634
|
+
":move1_failed",
|
|
635
|
+
`echo [%DATE% %TIME%] ERROR: failed to rename install to .old (errorlevel %errorlevel%) >> "${logFile}"`,
|
|
636
|
+
"goto cleanup",
|
|
637
|
+
":move2_failed",
|
|
638
|
+
`echo [%DATE% %TIME%] ERROR: failed to promote .new; rolling back >> "${logFile}"`,
|
|
639
|
+
`move /Y "${oldDir}" "${standaloneDir}"`,
|
|
640
|
+
"if errorlevel 1 (",
|
|
641
|
+
` echo [%DATE% %TIME%] CRITICAL: rollback also failed; manual recovery: move "${oldDir}" "${standaloneDir}" >> "${logFile}"`,
|
|
642
|
+
") else (",
|
|
643
|
+
` echo [%DATE% %TIME%] rollback succeeded >> "${logFile}"`,
|
|
644
|
+
")",
|
|
645
|
+
":cleanup",
|
|
646
|
+
`del /F /Q "${lockFile}" 2>nul`,
|
|
647
|
+
`del "%~f0"`
|
|
648
|
+
].join("\r\n");
|
|
649
|
+
const scriptPath = path2.join(
|
|
650
|
+
path2.dirname(standaloneDir),
|
|
651
|
+
"qwen-update.bat"
|
|
652
|
+
);
|
|
653
|
+
fs2.writeFileSync(scriptPath, script);
|
|
654
|
+
spawn("cmd.exe", ["/c", scriptPath], {
|
|
655
|
+
detached: true,
|
|
656
|
+
stdio: "ignore",
|
|
657
|
+
windowsHide: true
|
|
658
|
+
}).unref();
|
|
659
|
+
return "deferred";
|
|
660
|
+
} else {
|
|
661
|
+
fs2.renameSync(standaloneDir, oldDir);
|
|
662
|
+
try {
|
|
663
|
+
fs2.renameSync(newDir, standaloneDir);
|
|
664
|
+
} catch (promoteErr) {
|
|
665
|
+
try {
|
|
666
|
+
fs2.renameSync(oldDir, standaloneDir);
|
|
667
|
+
} catch (rollbackErr) {
|
|
668
|
+
const detail = `Standalone update failed AND rollback failed.
|
|
669
|
+
Original error: ${promoteErr.message}
|
|
670
|
+
Rollback error: ${rollbackErr.message}
|
|
671
|
+
Manual recovery: mv "${oldDir}" "${standaloneDir}"`;
|
|
672
|
+
throw new Error(detail);
|
|
673
|
+
}
|
|
674
|
+
throw promoteErr;
|
|
675
|
+
}
|
|
676
|
+
return "done";
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
__name(atomicReplace, "atomicReplace");
|
|
680
|
+
function ensureBinWrapper(standaloneDir, target) {
|
|
681
|
+
const binDir = path2.join(path2.dirname(standaloneDir), "..", "bin");
|
|
682
|
+
try {
|
|
683
|
+
fs2.mkdirSync(binDir, { recursive: true });
|
|
684
|
+
if (target.startsWith("win")) {
|
|
685
|
+
if (UNSAFE_CMD_CHARS.test(standaloneDir)) {
|
|
686
|
+
throw new Error(
|
|
687
|
+
"standaloneDir contains characters unsafe for cmd.exe wrapper"
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
const wrapperPath = path2.join(binDir, "qwen.cmd");
|
|
691
|
+
if (!fs2.existsSync(wrapperPath)) {
|
|
692
|
+
const content = `@echo off\r
|
|
693
|
+
call "${standaloneDir}\\bin\\qwen.cmd" %*\r
|
|
694
|
+
`;
|
|
695
|
+
fs2.writeFileSync(wrapperPath, content);
|
|
696
|
+
}
|
|
697
|
+
} else {
|
|
698
|
+
assertSafeForShellEmbed(standaloneDir, "standaloneDir");
|
|
699
|
+
const wrapperPath = path2.join(binDir, "qwen");
|
|
700
|
+
if (!fs2.existsSync(wrapperPath)) {
|
|
701
|
+
const content = `#!/bin/sh
|
|
702
|
+
exec "${standaloneDir}/bin/qwen" "$@"
|
|
703
|
+
`;
|
|
704
|
+
fs2.writeFileSync(wrapperPath, content, { mode: 493 });
|
|
705
|
+
}
|
|
706
|
+
ensurePathInShellRc(binDir);
|
|
707
|
+
}
|
|
708
|
+
} catch (err) {
|
|
709
|
+
debugLogger2.warn("Failed to create bin wrapper:", err);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
__name(ensureBinWrapper, "ensureBinWrapper");
|
|
713
|
+
function ensurePathInShellRc(binDir) {
|
|
714
|
+
assertSafeForShellEmbed(binDir, "binDir");
|
|
715
|
+
const shell = process.env["SHELL"] || "";
|
|
716
|
+
let rcFile = null;
|
|
717
|
+
const home = process.env["HOME"] || os.homedir();
|
|
718
|
+
if (shell.endsWith("/zsh")) {
|
|
719
|
+
rcFile = path2.join(home, ".zshrc");
|
|
720
|
+
} else if (shell.endsWith("/bash")) {
|
|
721
|
+
const bashrc = path2.join(home, ".bashrc");
|
|
722
|
+
const profile = path2.join(home, ".bash_profile");
|
|
723
|
+
if (os.platform() === "darwin") {
|
|
724
|
+
rcFile = fs2.existsSync(profile) ? profile : bashrc;
|
|
725
|
+
} else {
|
|
726
|
+
rcFile = bashrc;
|
|
727
|
+
}
|
|
728
|
+
} else if (shell.endsWith("/fish")) {
|
|
729
|
+
rcFile = path2.join(home, ".config", "fish", "config.fish");
|
|
730
|
+
}
|
|
731
|
+
if (!rcFile) return;
|
|
732
|
+
try {
|
|
733
|
+
const content = fs2.existsSync(rcFile) ? fs2.readFileSync(rcFile, "utf-8") : "";
|
|
734
|
+
const marker = "# Added by Qwen Code standalone installer";
|
|
735
|
+
if (content.includes(marker)) return;
|
|
736
|
+
const exportLine = shell.endsWith("/fish") ? `
|
|
737
|
+
${marker}
|
|
738
|
+
fish_add_path "${binDir}"
|
|
739
|
+
` : `
|
|
740
|
+
${marker}
|
|
741
|
+
export PATH="${binDir}:$PATH"
|
|
742
|
+
`;
|
|
743
|
+
fs2.appendFileSync(rcFile, exportLine);
|
|
744
|
+
debugLogger2.info(`Added ${binDir} to ${rcFile}`);
|
|
745
|
+
} catch (err) {
|
|
746
|
+
debugLogger2.debug("Failed to update shell rc:", err);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
__name(ensurePathInShellRc, "ensurePathInShellRc");
|
|
750
|
+
function detectTarget() {
|
|
751
|
+
const platform2 = os.platform();
|
|
752
|
+
const arch2 = os.arch();
|
|
753
|
+
if (platform2 === "darwin") {
|
|
754
|
+
return arch2 === "arm64" ? "darwin-arm64" : "darwin-x64";
|
|
755
|
+
}
|
|
756
|
+
if (platform2 === "win32") return "win-x64";
|
|
757
|
+
if (platform2 === "linux") {
|
|
758
|
+
if (arch2 === "arm64") return "linux-arm64";
|
|
759
|
+
if (arch2 === "x64") return "linux-x64";
|
|
760
|
+
}
|
|
761
|
+
throw new Error(`Unsupported platform: ${platform2}-${arch2}`);
|
|
762
|
+
}
|
|
763
|
+
__name(detectTarget, "detectTarget");
|
|
764
|
+
async function performStandaloneUpdate(standaloneDir, newVersion) {
|
|
765
|
+
const versionPath = normalizeVersion(newVersion);
|
|
766
|
+
let target;
|
|
767
|
+
let isFirstTimeMigration = false;
|
|
768
|
+
const manifestPath = path2.join(standaloneDir, "manifest.json");
|
|
769
|
+
if (fs2.existsSync(manifestPath)) {
|
|
770
|
+
const manifestRaw = fs2.readFileSync(manifestPath, "utf-8");
|
|
771
|
+
const manifest = JSON.parse(manifestRaw);
|
|
772
|
+
target = manifest.target ?? detectTarget();
|
|
773
|
+
} else if (fs2.existsSync(standaloneDir)) {
|
|
774
|
+
throw new Error(
|
|
775
|
+
`${standaloneDir} exists but is not a Qwen Code standalone install. Remove it manually to proceed.`
|
|
776
|
+
);
|
|
777
|
+
} else {
|
|
778
|
+
target = detectTarget();
|
|
779
|
+
isFirstTimeMigration = true;
|
|
780
|
+
}
|
|
781
|
+
validateTarget(target);
|
|
782
|
+
const filename = archiveFilename(target);
|
|
783
|
+
const parentDir = path2.dirname(standaloneDir);
|
|
784
|
+
fs2.mkdirSync(parentDir, { recursive: true });
|
|
785
|
+
const lockPath = path2.join(parentDir, ".qwen-update.lock");
|
|
786
|
+
if (!acquireLock(lockPath)) {
|
|
787
|
+
throw new Error("Another update is already in progress");
|
|
788
|
+
}
|
|
789
|
+
if (isFirstTimeMigration) {
|
|
790
|
+
fs2.mkdirSync(standaloneDir, { recursive: true });
|
|
791
|
+
}
|
|
792
|
+
const tempDir = fs2.mkdtempSync(path2.join(os.tmpdir(), "qwen-code-update-"));
|
|
793
|
+
let extractDir;
|
|
794
|
+
let updateResult;
|
|
795
|
+
try {
|
|
796
|
+
extractDir = fs2.mkdtempSync(path2.join(parentDir, ".qwen-code-update-"));
|
|
797
|
+
} catch (err) {
|
|
798
|
+
fs2.rmSync(tempDir, { recursive: true, force: true });
|
|
799
|
+
releaseLock(lockPath);
|
|
800
|
+
throw err;
|
|
801
|
+
}
|
|
802
|
+
try {
|
|
803
|
+
const archivePath = path2.join(tempDir, filename);
|
|
804
|
+
debugLogger2.info(`Downloading ${filename} (${versionPath})...`);
|
|
805
|
+
const archiveHash = await downloadToFile(
|
|
806
|
+
versionPath,
|
|
807
|
+
filename,
|
|
808
|
+
archivePath
|
|
809
|
+
);
|
|
810
|
+
debugLogger2.info("Verifying checksum...");
|
|
811
|
+
await verifyChecksum(archiveHash, filename, versionPath);
|
|
812
|
+
debugLogger2.info("Extracting archive...");
|
|
813
|
+
await extractArchive(archivePath, extractDir, target);
|
|
814
|
+
const newInstallDir = path2.join(extractDir, "qwen-code");
|
|
815
|
+
if (!fs2.existsSync(path2.join(newInstallDir, "manifest.json"))) {
|
|
816
|
+
throw new Error(
|
|
817
|
+
"Extracted archive does not contain expected qwen-code directory"
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
debugLogger2.info("Running smoke test...");
|
|
821
|
+
await smokeTest(newInstallDir, target);
|
|
822
|
+
debugLogger2.info("Replacing installation...");
|
|
823
|
+
updateResult = atomicReplace(standaloneDir, newInstallDir, lockPath);
|
|
824
|
+
const oldDir = `${standaloneDir}.old`;
|
|
825
|
+
if (fs2.existsSync(oldDir)) {
|
|
826
|
+
if (isFirstTimeMigration) {
|
|
827
|
+
fs2.rmSync(oldDir, { recursive: true, force: true });
|
|
828
|
+
} else {
|
|
829
|
+
try {
|
|
830
|
+
const oldManifestPath = path2.join(oldDir, "manifest.json");
|
|
831
|
+
let oldVersion = "unknown";
|
|
832
|
+
if (fs2.existsSync(oldManifestPath)) {
|
|
833
|
+
const oldManifest = JSON.parse(
|
|
834
|
+
fs2.readFileSync(oldManifestPath, "utf-8")
|
|
835
|
+
);
|
|
836
|
+
oldVersion = oldManifest.version || "unknown";
|
|
837
|
+
}
|
|
838
|
+
const rollbackInfo = {
|
|
839
|
+
preservedVersion: oldVersion,
|
|
840
|
+
updatedTo: versionPath,
|
|
841
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
842
|
+
reason: "auto-update"
|
|
843
|
+
};
|
|
844
|
+
fs2.writeFileSync(
|
|
845
|
+
path2.join(oldDir, ".qwen-rollback-info.json"),
|
|
846
|
+
JSON.stringify(rollbackInfo, null, 2)
|
|
847
|
+
);
|
|
848
|
+
} catch {
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
ensureBinWrapper(standaloneDir, target);
|
|
853
|
+
debugLogger2.info("Standalone update complete.");
|
|
854
|
+
return updateResult;
|
|
855
|
+
} catch (err) {
|
|
856
|
+
const pendingDir = `${standaloneDir}.new`;
|
|
857
|
+
if (fs2.existsSync(pendingDir)) {
|
|
858
|
+
fs2.rmSync(pendingDir, { recursive: true, force: true });
|
|
859
|
+
}
|
|
860
|
+
cleanupEmptyStandaloneDir(standaloneDir);
|
|
861
|
+
throw err;
|
|
862
|
+
} finally {
|
|
863
|
+
if (updateResult !== "deferred") {
|
|
864
|
+
releaseLock(lockPath);
|
|
865
|
+
}
|
|
866
|
+
try {
|
|
867
|
+
fs2.rmSync(tempDir, { recursive: true, force: true });
|
|
868
|
+
} catch {
|
|
869
|
+
}
|
|
870
|
+
try {
|
|
871
|
+
fs2.rmSync(extractDir, { recursive: true, force: true });
|
|
872
|
+
} catch {
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
__name(performStandaloneUpdate, "performStandaloneUpdate");
|
|
877
|
+
function rollbackStandaloneUpdate(standaloneDir) {
|
|
878
|
+
const lockPath = path2.join(path2.dirname(standaloneDir), ".qwen-update.lock");
|
|
879
|
+
try {
|
|
880
|
+
const pidStr = fs2.readFileSync(lockPath, "utf-8").trim();
|
|
881
|
+
const pid = parseInt(pidStr, 10);
|
|
882
|
+
if (!Number.isNaN(pid) && isProcessAlive(pid)) {
|
|
883
|
+
return {
|
|
884
|
+
ok: false,
|
|
885
|
+
reason: "rename-failed",
|
|
886
|
+
detail: "An auto-update is currently in progress. Wait for it to finish before rolling back."
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
} catch {
|
|
890
|
+
}
|
|
891
|
+
const oldDir = `${standaloneDir}.old`;
|
|
892
|
+
if (!fs2.existsSync(oldDir)) {
|
|
893
|
+
return { ok: false, reason: "no-old", detail: `${oldDir} does not exist` };
|
|
894
|
+
}
|
|
895
|
+
const oldManifest = path2.join(oldDir, "manifest.json");
|
|
896
|
+
if (!fs2.existsSync(oldManifest)) {
|
|
897
|
+
debugLogger2.error("Rollback failed: .old directory has no manifest.json");
|
|
898
|
+
return {
|
|
899
|
+
ok: false,
|
|
900
|
+
reason: "no-manifest",
|
|
901
|
+
detail: `${oldDir}/manifest.json missing \u2014 .old may be corrupt`
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
const failedDir = `${standaloneDir}.failed`;
|
|
905
|
+
try {
|
|
906
|
+
if (fs2.existsSync(failedDir)) {
|
|
907
|
+
fs2.rmSync(failedDir, { recursive: true, force: true });
|
|
908
|
+
}
|
|
909
|
+
fs2.renameSync(standaloneDir, failedDir);
|
|
910
|
+
fs2.renameSync(oldDir, standaloneDir);
|
|
911
|
+
debugLogger2.info("Rollback successful.");
|
|
912
|
+
try {
|
|
913
|
+
fs2.rmSync(failedDir, { recursive: true, force: true });
|
|
914
|
+
} catch {
|
|
915
|
+
debugLogger2.debug(`Leftover .failed dir at ${failedDir}, safe to delete`);
|
|
916
|
+
}
|
|
917
|
+
return { ok: true };
|
|
918
|
+
} catch (err) {
|
|
919
|
+
debugLogger2.error("Rollback failed:", err);
|
|
920
|
+
if (!fs2.existsSync(standaloneDir) && fs2.existsSync(failedDir)) {
|
|
921
|
+
try {
|
|
922
|
+
fs2.renameSync(failedDir, standaloneDir);
|
|
923
|
+
return {
|
|
924
|
+
ok: false,
|
|
925
|
+
reason: "rename-failed",
|
|
926
|
+
detail: `Filesystem error: ${err.message}. Current installation was restored automatically.`
|
|
927
|
+
};
|
|
928
|
+
} catch {
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return {
|
|
932
|
+
ok: false,
|
|
933
|
+
reason: "rename-failed",
|
|
934
|
+
detail: `Filesystem error: ${err.message}. Manual recovery: mv "${oldDir}" "${standaloneDir}"`
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
__name(rollbackStandaloneUpdate, "rollbackStandaloneUpdate");
|
|
939
|
+
|
|
940
|
+
export {
|
|
941
|
+
getInstallationInfo,
|
|
942
|
+
performStandaloneUpdate,
|
|
943
|
+
rollbackStandaloneUpdate
|
|
944
|
+
};
|
|
945
|
+
/**
|
|
946
|
+
* @license
|
|
947
|
+
* Copyright 2025 Google LLC
|
|
948
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
949
|
+
*/
|