@runcofactor/cli 0.55.0 → 0.56.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/dist/{account-RHHS7HT4.js → account-CZYZD2JS.js} +5 -5
- package/dist/{alert-B5WEOKHP.js → alert-JQ4SDEB7.js} +8 -8
- package/dist/{api-catalog-UXONX3BR.js → api-catalog-IWQC35P2.js} +5 -5
- package/dist/{api-key-J3A4TJKN.js → api-key-GUDXIIEA.js} +5 -5
- package/dist/{attachment-ZQW7DR4G.js → attachment-CPZYOBUB.js} +5 -5
- package/dist/{auth-HQGKUZBS.js → auth-DS6MBVEI.js} +3 -3
- package/dist/{benchmark-TIKJA32J.js → benchmark-6HPCIQHD.js} +5 -5
- package/dist/{blackcat-Z5LNV2GS.js → blackcat-V3L63WFE.js} +3 -3
- package/dist/{chunk-JQG3V5E3.js → chunk-2E3STBHS.js} +2 -2
- package/dist/{chunk-KL2ARGH6.js → chunk-2OHYMT3A.js} +3 -3
- package/dist/{chunk-KL2ARGH6.js.map → chunk-2OHYMT3A.js.map} +1 -1
- package/dist/{chunk-W6DL4IP6.js → chunk-3LGCHNBC.js} +2 -2
- package/dist/{chunk-BJHAVM7Z.js → chunk-4MFMPC5P.js} +46 -2
- package/dist/chunk-4MFMPC5P.js.map +1 -0
- package/dist/{chunk-MB4FS35J.js → chunk-56HFZHQM.js} +8 -8
- package/dist/{chunk-CZDQVQ57.js → chunk-5OYWNS6D.js} +20 -9
- package/dist/chunk-5OYWNS6D.js.map +1 -0
- package/dist/{chunk-YTXZUWMN.js → chunk-62EXS76C.js} +6 -6
- package/dist/{chunk-QVMGXMP5.js → chunk-6ILWJFKY.js} +368 -6
- package/dist/chunk-6ILWJFKY.js.map +1 -0
- package/dist/{chunk-6X6ZTDXP.js → chunk-BDOL622T.js} +4 -4
- package/dist/{chunk-L4JQ236V.js → chunk-C3CXVX2B.js} +4 -4
- package/dist/{chunk-33U56YGD.js → chunk-CWICLBLU.js} +2 -1
- package/dist/{chunk-33U56YGD.js.map → chunk-CWICLBLU.js.map} +1 -1
- package/dist/{chunk-XM3WTJ7Y.js → chunk-DEVD2WD2.js} +2 -2
- package/dist/{chunk-WWUEKQLR.js → chunk-EWK44F6V.js} +3 -3
- package/dist/chunk-EWK44F6V.js.map +1 -0
- package/dist/{chunk-CQSQNDMV.js → chunk-FNHPTTRF.js} +7 -5
- package/dist/chunk-FNHPTTRF.js.map +1 -0
- package/dist/{chunk-Z72GWGH2.js → chunk-GCD4EHCD.js} +6 -6
- package/dist/{chunk-3KBYI6RR.js → chunk-H6ZMKNP7.js} +3 -3
- package/dist/{chunk-5OKCT6LF.js → chunk-IEWAH7W3.js} +5 -5
- package/dist/{chunk-TGI2AAOZ.js → chunk-IV4HRGKX.js} +77 -12
- package/dist/chunk-IV4HRGKX.js.map +1 -0
- package/dist/{chunk-635IQV6B.js → chunk-J2ZQULU3.js} +2 -2
- package/dist/{chunk-ZZZ33KTO.js → chunk-K2IDR6FR.js} +3 -3
- package/dist/{chunk-YEH7UIUY.js → chunk-KTZRI27M.js} +4 -4
- package/dist/{chunk-RV7FZ4ID.js → chunk-L644NMDI.js} +3 -3
- package/dist/{chunk-RV7FZ4ID.js.map → chunk-L644NMDI.js.map} +1 -1
- package/dist/{chunk-3TZABC7D.js → chunk-LFT7GUWS.js} +63 -17
- package/dist/chunk-LFT7GUWS.js.map +1 -0
- package/dist/{chunk-QHRWXCON.js → chunk-O7B2IYCZ.js} +11 -11
- package/dist/chunk-O7B2IYCZ.js.map +1 -0
- package/dist/{chunk-U3MRXQZS.js → chunk-RHWPZKRV.js} +24 -1
- package/dist/chunk-RHWPZKRV.js.map +1 -0
- package/dist/{chunk-OU4I3MEH.js → chunk-UJSW72LG.js} +3 -3
- package/dist/{chunk-R2CRH6JQ.js → chunk-VACACCD6.js} +3 -3
- package/dist/{chunk-EGWVL5XA.js → chunk-VMAR67FF.js} +2 -2
- package/dist/{chunk-ZH7HWVEE.js → chunk-VMJ7TBVM.js} +2 -2
- package/dist/chunk-WRRA4ATV.js +332 -0
- package/dist/chunk-WRRA4ATV.js.map +1 -0
- package/dist/{chunk-XWJJYZQC.js → chunk-XHEVDABB.js} +2 -2
- package/dist/{chunk-SCQVBLNR.js → chunk-XHGF6VC6.js} +4 -4
- package/dist/{credential-2IEZDTH3.js → credential-GJW7LGYP.js} +5 -5
- package/dist/{credential-template-S5WSOI3P.js → credential-template-UWBYKKWD.js} +5 -5
- package/dist/dev-PBW7BSOD.js +605 -0
- package/dist/dev-PBW7BSOD.js.map +1 -0
- package/dist/{digest-HQGL7H6E.js → digest-XEHGCJM4.js} +8 -8
- package/dist/{docs-CJQZVERZ.js → docs-KH4BCEQF.js} +5 -5
- package/dist/{doctor-EGK5AB77.js → doctor-FOXXE5SK.js} +3 -3
- package/dist/{environment-MWVXYEDU.js → environment-EBDPQTIS.js} +7 -7
- package/dist/{eval-F4NORBQI.js → eval-QZSSEICV.js} +67 -27
- package/dist/eval-QZSSEICV.js.map +1 -0
- package/dist/{exploration-JC7R7KTJ.js → exploration-LCGKRGOQ.js} +5 -5
- package/dist/{exploratory-test-BHK5FTMJ.js → exploratory-test-SGECQGIX.js} +6 -6
- package/dist/{external-run-UL5CKG54.js → external-run-2AJAI5O7.js} +9 -9
- package/dist/{feature-flag-NMO25TZN.js → feature-flag-P7N4WQIH.js} +5 -5
- package/dist/{feedback-3JNPSUXS.js → feedback-LAT7TQ2T.js} +6 -6
- package/dist/{flow-AYE4RTZN.js → flow-NH2DC6AP.js} +5 -5
- package/dist/{github-DM2DCXRY.js → github-FCEKBLXI.js} +6 -6
- package/dist/index.js +106 -408
- package/dist/index.js.map +1 -1
- package/dist/{init-CPDT5XAU.js → init-5U5EZOAW.js} +5 -5
- package/dist/{install-JZRNCHMO.js → install-X3UNVXKY.js} +5 -5
- package/dist/{issues-GRFJB52C.js → issues-4P6I3YQW.js} +8 -8
- package/dist/{knobs-3Y4QXWAD.js → knobs-JKTWJ6ZG.js} +5 -5
- package/dist/{landing-ABYL5WVG.js → landing-6AGLKI66.js} +7 -7
- package/dist/{list-WG5QB4NK.js → list-TWP2RP33.js} +5 -5
- package/dist/{local-KX24OR5Z.js → local-D75QE5TB.js} +7 -7
- package/dist/{login-MLJQAIFK.js → login-RBET4TIP.js} +5 -5
- package/dist/{mcp-DDTO54XD.js → mcp-562SUUMK.js} +11 -11
- package/dist/{metrics-BJLLOSPQ.js → metrics-4WF2GEPA.js} +5 -5
- package/dist/{monitor-E2CKGHPM.js → monitor-WJXH54H2.js} +5 -5
- package/dist/{perf-IX7JOMUE.js → perf-C6N6P7YG.js} +12 -361
- package/dist/perf-C6N6P7YG.js.map +1 -0
- package/dist/{product-2ECWM2JN.js → product-CYEQ7HOB.js} +5 -5
- package/dist/{product-map-O542LZQL.js → product-map-MSDLIFZY.js} +7 -7
- package/dist/{prompts-Y7O4XJ5P.js → prompts-ME53WPN4.js} +5 -5
- package/dist/{proofs-TNOBLSRP.js → proofs-D3G6ZUFP.js} +11 -9
- package/dist/{property-TZCGOZQG.js → property-5GLJNAVB.js} +29 -10
- package/dist/property-5GLJNAVB.js.map +1 -0
- package/dist/{psql-M66YSUXW.js → psql-EXAUDU3W.js} +5 -5
- package/dist/{record-3JINBRAR.js → record-EE7NGNKY.js} +6 -6
- package/dist/{redis-FOIBINYA.js → redis-M4EYWKOJ.js} +5 -5
- package/dist/{release-B4VWMVEI.js → release-4KUWVWWC.js} +5 -5
- package/dist/{runbook-EYMSE37E.js → runbook-AHU77ANQ.js} +5 -5
- package/dist/{sandbox-R6EMGOKR.js → sandbox-FBLJEYA2.js} +1910 -1629
- package/dist/sandbox-FBLJEYA2.js.map +1 -0
- package/dist/{scenarios-W7YNLBVO.js → scenarios-TU656T2B.js} +5 -5
- package/dist/{schedule-EDHQJUUZ.js → schedule-DXJI6SYO.js} +8 -8
- package/dist/{session-4N62APNQ.js → session-RUFTJHF4.js} +10 -10
- package/dist/{skill-XFGMNOVM.js → skill-ZK76GQ47.js} +7 -7
- package/dist/{skill-XFGMNOVM.js.map → skill-ZK76GQ47.js.map} +1 -1
- package/dist/skills/cofactor-debug-run/SKILL.md +2 -1
- package/dist/skills/cofactor-debug-run/history/2.0.10.md +320 -0
- package/dist/skills/cofactor-debug-run/version.json +1 -1
- package/dist/{slack-RUAXG6TB.js → slack-M3OKDVMY.js} +6 -6
- package/dist/{spec-ZZKMW7ER.js → spec-5MXEBLYR.js} +5 -5
- package/dist/{src-OKSAEHQS.js → src-2X5OQRPJ.js} +4 -4
- package/dist/{src-DGIVA22K.js → src-72EFBA2R.js} +3 -3
- package/dist/{src-ECZWYNSV.js → src-KH6WSBJV.js} +5 -5
- package/dist/{src-CQ2YAW7F.js → src-VLV36QYH.js} +127 -20
- package/dist/src-VLV36QYH.js.map +1 -0
- package/dist/{start-OCZGILRL.js → start-PDYBRB5T.js} +5 -5
- package/dist/{status-YTJ6YITG.js → status-VMVPVCDA.js} +5 -5
- package/dist/{tag-KBG4SWRL.js → tag-7KTDLPVU.js} +6 -6
- package/dist/{task-O6XRMHYE.js → task-QLFYZNKR.js} +6 -6
- package/dist/{teams-NFUHS7XI.js → teams-Y7BXCY6P.js} +7 -7
- package/dist/{test-suite-6BH3DESN.js → test-suite-6U22AYFG.js} +15 -14
- package/dist/test-suite-6U22AYFG.js.map +1 -0
- package/dist/{tickets-JU2STK4B.js → tickets-CQDJDNPY.js} +5 -5
- package/dist/{trace-NMSGKASJ.js → trace-QDORWXYX.js} +8 -8
- package/dist/{triage-JGECJJU5.js → triage-GY7M3UGG.js} +6 -6
- package/dist/{trigger-OSPEVCCQ.js → trigger-DLFAORX4.js} +9 -9
- package/dist/{update-check-FNP6RFZO.js → update-check-ES6XQ7OF.js} +4 -4
- package/dist/{upgrade-3PPBXFAT.js → upgrade-WKEOW5GS.js} +5 -5
- package/dist/{usage-QMCU2JIO.js → usage-E6FDTR3W.js} +7 -7
- package/dist/{utilities-7VHWTSJT.js → utilities-4Z6YX3L5.js} +6 -6
- package/dist/{visual-WIZSPTVK.js → visual-TA6LPVDJ.js} +5 -5
- package/dist/{workflow-23HKTJLS.js → workflow-B5FXS2CJ.js} +58 -31
- package/dist/workflow-B5FXS2CJ.js.map +1 -0
- package/dist/{workflow-cache-ACGM3BOI.js → workflow-cache-MNLEMRMM.js} +16 -16
- package/dist/{workflow-cache-convergence-resolve-KEQDQMKL.js → workflow-cache-convergence-resolve-G5HJM6MF.js} +5 -5
- package/dist/{workflow-debug-TVLBNRCB.js → workflow-debug-TNBUGSBK.js} +11 -11
- package/dist/{workflow-feedback-GB5VPGE2.js → workflow-feedback-G2ZXAUGI.js} +5 -5
- package/dist/{workflow-fork-2IPJNR76.js → workflow-fork-6HHOMC2D.js} +5 -5
- package/dist/{workflow-promotion-QEQUXVUT.js → workflow-promotion-RG7L6JZT.js} +5 -5
- package/dist/{workflow-proposals-KHGTPMD4.js → workflow-proposals-ZNF2F3UE.js} +5 -5
- package/dist/{workflow-revisions-F5KQEEXB.js → workflow-revisions-TP64Z6FO.js} +5 -5
- package/dist/{workflow-status-66HC5MEA.js → workflow-status-5RG3JX56.js} +5 -5
- package/package.json +3 -2
- package/dist/chunk-3TZABC7D.js.map +0 -1
- package/dist/chunk-BJHAVM7Z.js.map +0 -1
- package/dist/chunk-CQSQNDMV.js.map +0 -1
- package/dist/chunk-CZDQVQ57.js.map +0 -1
- package/dist/chunk-QHRWXCON.js.map +0 -1
- package/dist/chunk-QVMGXMP5.js.map +0 -1
- package/dist/chunk-TGI2AAOZ.js.map +0 -1
- package/dist/chunk-U3MRXQZS.js.map +0 -1
- package/dist/chunk-WWUEKQLR.js.map +0 -1
- package/dist/eval-F4NORBQI.js.map +0 -1
- package/dist/perf-IX7JOMUE.js.map +0 -1
- package/dist/property-TZCGOZQG.js.map +0 -1
- package/dist/sandbox-R6EMGOKR.js.map +0 -1
- package/dist/src-CQ2YAW7F.js.map +0 -1
- package/dist/test-suite-6BH3DESN.js.map +0 -1
- package/dist/workflow-23HKTJLS.js.map +0 -1
- /package/dist/{account-RHHS7HT4.js.map → account-CZYZD2JS.js.map} +0 -0
- /package/dist/{alert-B5WEOKHP.js.map → alert-JQ4SDEB7.js.map} +0 -0
- /package/dist/{api-catalog-UXONX3BR.js.map → api-catalog-IWQC35P2.js.map} +0 -0
- /package/dist/{api-key-J3A4TJKN.js.map → api-key-GUDXIIEA.js.map} +0 -0
- /package/dist/{attachment-ZQW7DR4G.js.map → attachment-CPZYOBUB.js.map} +0 -0
- /package/dist/{auth-HQGKUZBS.js.map → auth-DS6MBVEI.js.map} +0 -0
- /package/dist/{benchmark-TIKJA32J.js.map → benchmark-6HPCIQHD.js.map} +0 -0
- /package/dist/{blackcat-Z5LNV2GS.js.map → blackcat-V3L63WFE.js.map} +0 -0
- /package/dist/{chunk-JQG3V5E3.js.map → chunk-2E3STBHS.js.map} +0 -0
- /package/dist/{chunk-W6DL4IP6.js.map → chunk-3LGCHNBC.js.map} +0 -0
- /package/dist/{chunk-MB4FS35J.js.map → chunk-56HFZHQM.js.map} +0 -0
- /package/dist/{chunk-YTXZUWMN.js.map → chunk-62EXS76C.js.map} +0 -0
- /package/dist/{chunk-6X6ZTDXP.js.map → chunk-BDOL622T.js.map} +0 -0
- /package/dist/{chunk-L4JQ236V.js.map → chunk-C3CXVX2B.js.map} +0 -0
- /package/dist/{chunk-XM3WTJ7Y.js.map → chunk-DEVD2WD2.js.map} +0 -0
- /package/dist/{chunk-Z72GWGH2.js.map → chunk-GCD4EHCD.js.map} +0 -0
- /package/dist/{chunk-3KBYI6RR.js.map → chunk-H6ZMKNP7.js.map} +0 -0
- /package/dist/{chunk-5OKCT6LF.js.map → chunk-IEWAH7W3.js.map} +0 -0
- /package/dist/{chunk-635IQV6B.js.map → chunk-J2ZQULU3.js.map} +0 -0
- /package/dist/{chunk-ZZZ33KTO.js.map → chunk-K2IDR6FR.js.map} +0 -0
- /package/dist/{chunk-YEH7UIUY.js.map → chunk-KTZRI27M.js.map} +0 -0
- /package/dist/{chunk-OU4I3MEH.js.map → chunk-UJSW72LG.js.map} +0 -0
- /package/dist/{chunk-R2CRH6JQ.js.map → chunk-VACACCD6.js.map} +0 -0
- /package/dist/{chunk-EGWVL5XA.js.map → chunk-VMAR67FF.js.map} +0 -0
- /package/dist/{chunk-ZH7HWVEE.js.map → chunk-VMJ7TBVM.js.map} +0 -0
- /package/dist/{chunk-XWJJYZQC.js.map → chunk-XHEVDABB.js.map} +0 -0
- /package/dist/{chunk-SCQVBLNR.js.map → chunk-XHGF6VC6.js.map} +0 -0
- /package/dist/{credential-2IEZDTH3.js.map → credential-GJW7LGYP.js.map} +0 -0
- /package/dist/{credential-template-S5WSOI3P.js.map → credential-template-UWBYKKWD.js.map} +0 -0
- /package/dist/{digest-HQGL7H6E.js.map → digest-XEHGCJM4.js.map} +0 -0
- /package/dist/{docs-CJQZVERZ.js.map → docs-KH4BCEQF.js.map} +0 -0
- /package/dist/{doctor-EGK5AB77.js.map → doctor-FOXXE5SK.js.map} +0 -0
- /package/dist/{environment-MWVXYEDU.js.map → environment-EBDPQTIS.js.map} +0 -0
- /package/dist/{exploration-JC7R7KTJ.js.map → exploration-LCGKRGOQ.js.map} +0 -0
- /package/dist/{exploratory-test-BHK5FTMJ.js.map → exploratory-test-SGECQGIX.js.map} +0 -0
- /package/dist/{external-run-UL5CKG54.js.map → external-run-2AJAI5O7.js.map} +0 -0
- /package/dist/{feature-flag-NMO25TZN.js.map → feature-flag-P7N4WQIH.js.map} +0 -0
- /package/dist/{feedback-3JNPSUXS.js.map → feedback-LAT7TQ2T.js.map} +0 -0
- /package/dist/{flow-AYE4RTZN.js.map → flow-NH2DC6AP.js.map} +0 -0
- /package/dist/{github-DM2DCXRY.js.map → github-FCEKBLXI.js.map} +0 -0
- /package/dist/{init-CPDT5XAU.js.map → init-5U5EZOAW.js.map} +0 -0
- /package/dist/{install-JZRNCHMO.js.map → install-X3UNVXKY.js.map} +0 -0
- /package/dist/{issues-GRFJB52C.js.map → issues-4P6I3YQW.js.map} +0 -0
- /package/dist/{knobs-3Y4QXWAD.js.map → knobs-JKTWJ6ZG.js.map} +0 -0
- /package/dist/{landing-ABYL5WVG.js.map → landing-6AGLKI66.js.map} +0 -0
- /package/dist/{list-WG5QB4NK.js.map → list-TWP2RP33.js.map} +0 -0
- /package/dist/{local-KX24OR5Z.js.map → local-D75QE5TB.js.map} +0 -0
- /package/dist/{login-MLJQAIFK.js.map → login-RBET4TIP.js.map} +0 -0
- /package/dist/{mcp-DDTO54XD.js.map → mcp-562SUUMK.js.map} +0 -0
- /package/dist/{metrics-BJLLOSPQ.js.map → metrics-4WF2GEPA.js.map} +0 -0
- /package/dist/{monitor-E2CKGHPM.js.map → monitor-WJXH54H2.js.map} +0 -0
- /package/dist/{product-2ECWM2JN.js.map → product-CYEQ7HOB.js.map} +0 -0
- /package/dist/{product-map-O542LZQL.js.map → product-map-MSDLIFZY.js.map} +0 -0
- /package/dist/{prompts-Y7O4XJ5P.js.map → prompts-ME53WPN4.js.map} +0 -0
- /package/dist/{proofs-TNOBLSRP.js.map → proofs-D3G6ZUFP.js.map} +0 -0
- /package/dist/{psql-M66YSUXW.js.map → psql-EXAUDU3W.js.map} +0 -0
- /package/dist/{record-3JINBRAR.js.map → record-EE7NGNKY.js.map} +0 -0
- /package/dist/{redis-FOIBINYA.js.map → redis-M4EYWKOJ.js.map} +0 -0
- /package/dist/{release-B4VWMVEI.js.map → release-4KUWVWWC.js.map} +0 -0
- /package/dist/{runbook-EYMSE37E.js.map → runbook-AHU77ANQ.js.map} +0 -0
- /package/dist/{scenarios-W7YNLBVO.js.map → scenarios-TU656T2B.js.map} +0 -0
- /package/dist/{schedule-EDHQJUUZ.js.map → schedule-DXJI6SYO.js.map} +0 -0
- /package/dist/{session-4N62APNQ.js.map → session-RUFTJHF4.js.map} +0 -0
- /package/dist/{slack-RUAXG6TB.js.map → slack-M3OKDVMY.js.map} +0 -0
- /package/dist/{spec-ZZKMW7ER.js.map → spec-5MXEBLYR.js.map} +0 -0
- /package/dist/{src-DGIVA22K.js.map → src-2X5OQRPJ.js.map} +0 -0
- /package/dist/{src-ECZWYNSV.js.map → src-72EFBA2R.js.map} +0 -0
- /package/dist/{src-OKSAEHQS.js.map → src-KH6WSBJV.js.map} +0 -0
- /package/dist/{start-OCZGILRL.js.map → start-PDYBRB5T.js.map} +0 -0
- /package/dist/{status-YTJ6YITG.js.map → status-VMVPVCDA.js.map} +0 -0
- /package/dist/{tag-KBG4SWRL.js.map → tag-7KTDLPVU.js.map} +0 -0
- /package/dist/{task-O6XRMHYE.js.map → task-QLFYZNKR.js.map} +0 -0
- /package/dist/{teams-NFUHS7XI.js.map → teams-Y7BXCY6P.js.map} +0 -0
- /package/dist/{tickets-JU2STK4B.js.map → tickets-CQDJDNPY.js.map} +0 -0
- /package/dist/{trace-NMSGKASJ.js.map → trace-QDORWXYX.js.map} +0 -0
- /package/dist/{triage-JGECJJU5.js.map → triage-GY7M3UGG.js.map} +0 -0
- /package/dist/{trigger-OSPEVCCQ.js.map → trigger-DLFAORX4.js.map} +0 -0
- /package/dist/{update-check-FNP6RFZO.js.map → update-check-ES6XQ7OF.js.map} +0 -0
- /package/dist/{upgrade-3PPBXFAT.js.map → upgrade-WKEOW5GS.js.map} +0 -0
- /package/dist/{usage-QMCU2JIO.js.map → usage-E6FDTR3W.js.map} +0 -0
- /package/dist/{utilities-7VHWTSJT.js.map → utilities-4Z6YX3L5.js.map} +0 -0
- /package/dist/{visual-WIZSPTVK.js.map → visual-TA6LPVDJ.js.map} +0 -0
- /package/dist/{workflow-cache-ACGM3BOI.js.map → workflow-cache-MNLEMRMM.js.map} +0 -0
- /package/dist/{workflow-cache-convergence-resolve-KEQDQMKL.js.map → workflow-cache-convergence-resolve-G5HJM6MF.js.map} +0 -0
- /package/dist/{workflow-debug-TVLBNRCB.js.map → workflow-debug-TNBUGSBK.js.map} +0 -0
- /package/dist/{workflow-feedback-GB5VPGE2.js.map → workflow-feedback-G2ZXAUGI.js.map} +0 -0
- /package/dist/{workflow-fork-2IPJNR76.js.map → workflow-fork-6HHOMC2D.js.map} +0 -0
- /package/dist/{workflow-promotion-QEQUXVUT.js.map → workflow-promotion-RG7L6JZT.js.map} +0 -0
- /package/dist/{workflow-proposals-KHGTPMD4.js.map → workflow-proposals-ZNF2F3UE.js.map} +0 -0
- /package/dist/{workflow-revisions-F5KQEEXB.js.map → workflow-revisions-TP64Z6FO.js.map} +0 -0
- /package/dist/{workflow-status-66HC5MEA.js.map → workflow-status-5RG3JX56.js.map} +0 -0
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
import { createRequire as __cr } from "module"; import { fileURLToPath as __fup } from "url"; import { dirname as __dn } from "path"; const require = __cr(import.meta.url); const __filename = __fup(import.meta.url); const __dirname = __dn(__filename);
|
|
2
2
|
import {
|
|
3
3
|
parseDurationSeconds
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-IV4HRGKX.js";
|
|
5
5
|
import {
|
|
6
6
|
resolveApplicationId,
|
|
7
7
|
resolveOrCreateSandboxApplication
|
|
8
|
-
} from "./chunk-
|
|
9
|
-
import "./chunk-
|
|
8
|
+
} from "./chunk-6ILWJFKY.js";
|
|
9
|
+
import "./chunk-VMJ7TBVM.js";
|
|
10
10
|
import {
|
|
11
11
|
SANDBOX_V2_GUIDE_SECTIONS,
|
|
12
12
|
printServedGuide
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-O7B2IYCZ.js";
|
|
14
14
|
import {
|
|
15
15
|
findOperation,
|
|
16
16
|
getAllPlugins,
|
|
17
17
|
getPlugin,
|
|
18
18
|
getPluginIds,
|
|
19
19
|
pluginOperations
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-CWICLBLU.js";
|
|
21
21
|
import {
|
|
22
22
|
UUID_RE,
|
|
23
23
|
apiRequest,
|
|
@@ -27,9 +27,10 @@ import {
|
|
|
27
27
|
getTargetOrg,
|
|
28
28
|
selectProperty,
|
|
29
29
|
stripOneTrailingNewline
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-EWK44F6V.js";
|
|
31
31
|
import "./chunk-S3Q7MDCS.js";
|
|
32
32
|
import {
|
|
33
|
+
activeConfigDir,
|
|
33
34
|
apiFetch,
|
|
34
35
|
decodeJwtPayload,
|
|
35
36
|
exitOnFailure,
|
|
@@ -39,33 +40,33 @@ import {
|
|
|
39
40
|
resolveConfig,
|
|
40
41
|
resolveOrgAppUrl,
|
|
41
42
|
splitAtSeparator
|
|
42
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-4MFMPC5P.js";
|
|
43
44
|
import {
|
|
44
45
|
SANDBOX_GUIDE_SECTIONS,
|
|
45
46
|
writeRawOutput
|
|
46
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-2OHYMT3A.js";
|
|
47
48
|
import {
|
|
48
49
|
INSTANCE_SIZES,
|
|
49
50
|
INSTANCE_SIZE_SPEC,
|
|
50
51
|
SANDBOX_EGRESS_MODES,
|
|
51
52
|
parseInstanceSize,
|
|
52
53
|
sandboxPreviewPagePath
|
|
53
|
-
} from "./chunk-
|
|
54
|
+
} from "./chunk-RHWPZKRV.js";
|
|
54
55
|
import "./chunk-TOWZAG3I.js";
|
|
55
56
|
|
|
56
57
|
// src/sandbox.ts
|
|
57
|
-
import
|
|
58
|
-
import { readFileSync as
|
|
58
|
+
import process11 from "process";
|
|
59
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
59
60
|
|
|
60
61
|
// src/sandbox-emulation.ts
|
|
61
|
-
import
|
|
62
|
+
import process2 from "process";
|
|
62
63
|
import { readFileSync } from "fs";
|
|
63
64
|
var pad = (value, width) => value.padEnd(width);
|
|
64
65
|
async function fetchEmulation(apiUrl, token, templateId) {
|
|
65
66
|
const res = await apiRequest(apiUrl, token, "GET", `/org/environments/templates/${templateId}/emulation`);
|
|
66
67
|
if (!res.ok) {
|
|
67
68
|
console.error(`Error: ${res.error}`);
|
|
68
|
-
|
|
69
|
+
process2.exit(1);
|
|
69
70
|
}
|
|
70
71
|
return { config: res.config ?? { enabled: false, plugins: [] }, catalog: res.catalog ?? [] };
|
|
71
72
|
}
|
|
@@ -80,14 +81,14 @@ async function patchEmulation(apiUrl, token, templateId, config) {
|
|
|
80
81
|
);
|
|
81
82
|
if (!res.ok) {
|
|
82
83
|
console.error(`Error: ${res.error}`);
|
|
83
|
-
|
|
84
|
+
process2.exit(1);
|
|
84
85
|
}
|
|
85
86
|
}
|
|
86
87
|
function assertKnownPlugin(pluginId) {
|
|
87
88
|
const ids = getPluginIds();
|
|
88
89
|
if (!ids.includes(pluginId)) {
|
|
89
90
|
console.error(`Error: unknown plugin '${pluginId}'. Available: ${ids.join(", ")}`);
|
|
90
|
-
|
|
91
|
+
process2.exit(1);
|
|
91
92
|
}
|
|
92
93
|
}
|
|
93
94
|
function readJsonFileOrExit(path, flag) {
|
|
@@ -98,7 +99,7 @@ function readJsonFileOrExit(path, flag) {
|
|
|
98
99
|
console.error(
|
|
99
100
|
`Error reading ${flag} file '${path}': ${err instanceof Error ? err.message : String(err)}`
|
|
100
101
|
);
|
|
101
|
-
|
|
102
|
+
process2.exit(1);
|
|
102
103
|
}
|
|
103
104
|
try {
|
|
104
105
|
return JSON.parse(raw);
|
|
@@ -106,7 +107,7 @@ function readJsonFileOrExit(path, flag) {
|
|
|
106
107
|
console.error(
|
|
107
108
|
`Error parsing ${flag} file '${path}' as JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
108
109
|
);
|
|
109
|
-
|
|
110
|
+
process2.exit(1);
|
|
110
111
|
}
|
|
111
112
|
}
|
|
112
113
|
function readJsonObjectOrExit(path, flag) {
|
|
@@ -115,7 +116,7 @@ function readJsonObjectOrExit(path, flag) {
|
|
|
115
116
|
console.error(
|
|
116
117
|
`Error: ${flag} file '${path}' must contain a JSON object, not ${Array.isArray(parsed) ? "an array" : typeof parsed}.`
|
|
117
118
|
);
|
|
118
|
-
|
|
119
|
+
process2.exit(1);
|
|
119
120
|
}
|
|
120
121
|
return parsed;
|
|
121
122
|
}
|
|
@@ -161,7 +162,7 @@ async function handleEmulationGet(argv, apiUrl, token) {
|
|
|
161
162
|
const json = hasFlag(argv, "--json");
|
|
162
163
|
if (!templateId) {
|
|
163
164
|
console.error("Error: template ID required. Usage: canary sandbox emulation get <template-id>");
|
|
164
|
-
|
|
165
|
+
process2.exit(1);
|
|
165
166
|
}
|
|
166
167
|
const { config, catalog } = await fetchEmulation(apiUrl, token, templateId);
|
|
167
168
|
const available = getAllPlugins().map((plugin) => ({ id: plugin.id, name: plugin.name }));
|
|
@@ -210,7 +211,7 @@ async function setEmulationEnabled(argv, apiUrl, token, enable) {
|
|
|
210
211
|
console.error(
|
|
211
212
|
`Error: template ID required. Usage: canary sandbox emulation ${verb} <template-id> [pluginId]`
|
|
212
213
|
);
|
|
213
|
-
|
|
214
|
+
process2.exit(1);
|
|
214
215
|
}
|
|
215
216
|
const { config } = await fetchEmulation(apiUrl, token, templateId);
|
|
216
217
|
let next;
|
|
@@ -221,7 +222,7 @@ async function setEmulationEnabled(argv, apiUrl, token, enable) {
|
|
|
221
222
|
const exists = (config.plugins ?? []).some((plugin) => plugin.pluginId === pluginId);
|
|
222
223
|
if (!enable && !exists) {
|
|
223
224
|
console.error(`Error: plugin '${pluginId}' is not configured on this template.`);
|
|
224
|
-
|
|
225
|
+
process2.exit(1);
|
|
225
226
|
}
|
|
226
227
|
next = withPluginEnabled(config, pluginId, enable);
|
|
227
228
|
}
|
|
@@ -237,7 +238,7 @@ async function handleEmulationSet(argv, apiUrl, token) {
|
|
|
237
238
|
console.error(
|
|
238
239
|
"Usage: canary sandbox emulation set <template-id> <pluginId> [--config <file>] [--fixture <file>] [--env-mapping <file>]"
|
|
239
240
|
);
|
|
240
|
-
|
|
241
|
+
process2.exit(1);
|
|
241
242
|
}
|
|
242
243
|
assertKnownPlugin(pluginId);
|
|
243
244
|
const configPath = getArgValue(argv, "--config");
|
|
@@ -245,7 +246,7 @@ async function handleEmulationSet(argv, apiUrl, token) {
|
|
|
245
246
|
const envMappingPath = getArgValue(argv, "--env-mapping");
|
|
246
247
|
if (!configPath && !fixturePath && !envMappingPath) {
|
|
247
248
|
console.error("Error: provide at least one of --config, --fixture, --env-mapping");
|
|
248
|
-
|
|
249
|
+
process2.exit(1);
|
|
249
250
|
}
|
|
250
251
|
const pluginConfig = configPath ? readJsonObjectOrExit(configPath, "--config") : void 0;
|
|
251
252
|
const fixture = fixturePath ? readJsonFileOrExit(fixturePath, "--fixture") : void 0;
|
|
@@ -270,12 +271,12 @@ async function handleEmulationRemove(argv, apiUrl, token) {
|
|
|
270
271
|
console.error(
|
|
271
272
|
"Error: template ID and pluginId required. Usage: canary sandbox emulation remove <template-id> <pluginId>"
|
|
272
273
|
);
|
|
273
|
-
|
|
274
|
+
process2.exit(1);
|
|
274
275
|
}
|
|
275
276
|
const { config } = await fetchEmulation(apiUrl, token, templateId);
|
|
276
277
|
if (!(config.plugins ?? []).some((plugin) => plugin.pluginId === pluginId)) {
|
|
277
278
|
console.error(`Error: plugin '${pluginId}' is not configured on this template.`);
|
|
278
|
-
|
|
279
|
+
process2.exit(1);
|
|
279
280
|
}
|
|
280
281
|
const next = withoutPlugin(config, pluginId);
|
|
281
282
|
await patchEmulation(apiUrl, token, templateId, next);
|
|
@@ -304,12 +305,12 @@ async function handleEmulation(argv, apiUrl, token) {
|
|
|
304
305
|
console.error(
|
|
305
306
|
`Unknown emulation action: ${action2 ?? "(none)"}. Use get/enable/disable/set/remove.`
|
|
306
307
|
);
|
|
307
|
-
|
|
308
|
+
process2.exit(1);
|
|
308
309
|
}
|
|
309
310
|
}
|
|
310
311
|
|
|
311
312
|
// src/sandbox-logs.ts
|
|
312
|
-
import
|
|
313
|
+
import process3 from "process";
|
|
313
314
|
var requestFailureLogsUrl = (apiUrl, token, instanceId) => apiRequest(
|
|
314
315
|
apiUrl,
|
|
315
316
|
token,
|
|
@@ -344,7 +345,7 @@ async function handleLogs(argv, apiUrl, token) {
|
|
|
344
345
|
const json = hasFlag(argv, "--json");
|
|
345
346
|
if (!instanceId) {
|
|
346
347
|
console.error("Error: instance ID required. Usage: canary sandbox logs <instance-id>");
|
|
347
|
-
|
|
348
|
+
process3.exit(1);
|
|
348
349
|
}
|
|
349
350
|
const service2 = getArgValue(argv, "--service");
|
|
350
351
|
const tail = getArgValue(argv, "--tail") ?? "200";
|
|
@@ -354,13 +355,13 @@ async function handleLogs(argv, apiUrl, token) {
|
|
|
354
355
|
const bundle = await fetchFailureLogsBundle(apiUrl, token, instanceId);
|
|
355
356
|
if ("error" in bundle) {
|
|
356
357
|
console.error(`Error: ${bundle.error}`);
|
|
357
|
-
|
|
358
|
+
process3.exit(1);
|
|
358
359
|
}
|
|
359
360
|
if (json) {
|
|
360
361
|
writeRawOutput(JSON.stringify(bundle, null, 2));
|
|
361
362
|
return;
|
|
362
363
|
}
|
|
363
|
-
|
|
364
|
+
process3.stdout.write(bundle.text || "(empty bundle)");
|
|
364
365
|
return;
|
|
365
366
|
}
|
|
366
367
|
const res = await apiRequest(
|
|
@@ -382,33 +383,33 @@ async function handleLogs(argv, apiUrl, token) {
|
|
|
382
383
|
}
|
|
383
384
|
console.error(`(live logs unavailable: ${res.error}; showing captured failure snapshot)
|
|
384
385
|
`);
|
|
385
|
-
|
|
386
|
+
process3.stdout.write(bundle.text || "(empty bundle)");
|
|
386
387
|
return;
|
|
387
388
|
}
|
|
388
389
|
console.error(`Error: ${res.error}`);
|
|
389
|
-
|
|
390
|
+
process3.exit(1);
|
|
390
391
|
}
|
|
391
392
|
if (json) {
|
|
392
393
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
393
394
|
return;
|
|
394
395
|
}
|
|
395
|
-
|
|
396
|
+
process3.stdout.write(res.data.logs ?? "(no logs)");
|
|
396
397
|
}
|
|
397
398
|
|
|
398
399
|
// src/sandbox-preview.ts
|
|
399
|
-
import
|
|
400
|
+
import process4 from "process";
|
|
400
401
|
async function handlePreview(argv, apiUrl, token, appSubdomain) {
|
|
401
402
|
const pr = getArgValue(argv, "--pr");
|
|
402
403
|
if (!pr) {
|
|
403
404
|
console.error("Usage: cofactor sandbox preview --application <name|id> --pr <number> [--json]");
|
|
404
|
-
|
|
405
|
+
process4.exit(1);
|
|
405
406
|
}
|
|
406
407
|
const application = await resolveApplicationId(argv, apiUrl, token);
|
|
407
408
|
const query = new URLSearchParams({ application, pr });
|
|
408
409
|
const res = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/previews?${query}`);
|
|
409
410
|
if (!res.ok) {
|
|
410
411
|
console.error(`Error: ${res.error}`);
|
|
411
|
-
|
|
412
|
+
process4.exit(1);
|
|
412
413
|
}
|
|
413
414
|
const preview = res.data;
|
|
414
415
|
if (hasFlag(argv, "--json")) {
|
|
@@ -428,8 +429,8 @@ async function handlePreview(argv, apiUrl, token, appSubdomain) {
|
|
|
428
429
|
}
|
|
429
430
|
|
|
430
431
|
// src/sandbox-v2.ts
|
|
431
|
-
import
|
|
432
|
-
import { readFileSync as
|
|
432
|
+
import process6 from "process";
|
|
433
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
433
434
|
|
|
434
435
|
// ../sandbox-helper/src/manifest.ts
|
|
435
436
|
import { createHash as createHash2, randomBytes } from "crypto";
|
|
@@ -1270,1616 +1271,1889 @@ var prepareInputSchema = z2.strictObject({
|
|
|
1270
1271
|
*/
|
|
1271
1272
|
sync: z2.strictObject({ key: syncKeySchema })
|
|
1272
1273
|
});
|
|
1274
|
+
function definitionFile(dir) {
|
|
1275
|
+
if (dir === ".") return DEFINITION_FILE;
|
|
1276
|
+
if (dir === "" || dir.startsWith("/") || dir.endsWith("/") || dir.split("/").includes("..")) {
|
|
1277
|
+
throw new Error(`invalid definition directory "${dir}"`);
|
|
1278
|
+
}
|
|
1279
|
+
return `${dir}/${DEFINITION_FILE}`;
|
|
1280
|
+
}
|
|
1273
1281
|
|
|
1274
|
-
// src/sandbox-
|
|
1275
|
-
|
|
1276
|
-
|
|
1282
|
+
// src/sandbox-from-local.ts
|
|
1283
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
1284
|
+
import { createHash as createHash3, randomUUID } from "crypto";
|
|
1285
|
+
import {
|
|
1286
|
+
existsSync as existsSync2,
|
|
1287
|
+
mkdirSync,
|
|
1288
|
+
readFileSync as readFileSync3,
|
|
1289
|
+
readdirSync,
|
|
1290
|
+
renameSync,
|
|
1291
|
+
rmSync as rmSync2,
|
|
1292
|
+
writeFileSync
|
|
1293
|
+
} from "fs";
|
|
1294
|
+
import { relative as relative2, resolve as resolve2, join as join2 } from "path";
|
|
1295
|
+
import { z as z4 } from "zod";
|
|
1296
|
+
|
|
1297
|
+
// src/sandbox-sync.ts
|
|
1298
|
+
import { spawnSync } from "child_process";
|
|
1299
|
+
import process5 from "process";
|
|
1300
|
+
import { copyFileSync, existsSync, lstatSync, mkdtempSync, readFileSync as readFileSync2, realpathSync, rmSync } from "fs";
|
|
1301
|
+
import { tmpdir } from "os";
|
|
1302
|
+
import { join, relative, resolve, sep } from "path";
|
|
1303
|
+
|
|
1304
|
+
// ../sandbox-helper/src/image-archive.ts
|
|
1305
|
+
import { z as z3 } from "zod";
|
|
1306
|
+
var MAX_BLOB_BYTES = 5 * 1024 ** 3;
|
|
1307
|
+
var ImageBlob = z3.object({
|
|
1308
|
+
digest: z3.string().regex(/^sha256:[0-9a-f]{64}$/),
|
|
1309
|
+
size: z3.number().int().nonnegative()
|
|
1310
|
+
});
|
|
1311
|
+
var ImageBlobs = z3.object({
|
|
1312
|
+
config: ImageBlob,
|
|
1313
|
+
// Docker allows at most 127 layers; each costs a storage lookup per push.
|
|
1314
|
+
layers: z3.array(ImageBlob).min(1).max(128)
|
|
1315
|
+
});
|
|
1316
|
+
function withSandboxLayers(team, added, created) {
|
|
1317
|
+
return {
|
|
1318
|
+
...team,
|
|
1319
|
+
rootfs: { ...team.rootfs, diff_ids: [...team.rootfs.diff_ids, ...added.map((layer) => layer.digest)] },
|
|
1320
|
+
history: [...team.history ?? [], ...added.map(() => ({ created, created_by: "cofactor sandbox push" }))],
|
|
1321
|
+
config: { ...team.config, Entrypoint: ["/usr/local/bin/cofactor-sandbox", "boot"], Cmd: [] }
|
|
1322
|
+
};
|
|
1277
1323
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1324
|
+
var BLOCK = 512;
|
|
1325
|
+
var encoder = new TextEncoder();
|
|
1326
|
+
var decoder = new TextDecoder();
|
|
1327
|
+
function tarHeader(name, size, mode = 420) {
|
|
1328
|
+
if (encoder.encode(name).length > 100) throw new Error(`tar entry name too long: ${name}`);
|
|
1329
|
+
const header = new Uint8Array(BLOCK);
|
|
1330
|
+
const put = (offset, length, value) => header.set(encoder.encode(value).slice(0, length), offset);
|
|
1331
|
+
const octal = (offset, length, value) => put(offset, length, `${value.toString(8).padStart(length - 1, "0")}\0`);
|
|
1332
|
+
put(0, 100, name);
|
|
1333
|
+
octal(100, 8, mode);
|
|
1334
|
+
octal(108, 8, 0);
|
|
1335
|
+
octal(116, 8, 0);
|
|
1336
|
+
octal(124, 12, size);
|
|
1337
|
+
octal(136, 12, 0);
|
|
1338
|
+
put(148, 8, " ");
|
|
1339
|
+
put(156, 1, "0");
|
|
1340
|
+
put(257, 6, "ustar\0");
|
|
1341
|
+
put(263, 2, "00");
|
|
1342
|
+
const checksum = header.reduce((sum, byte) => sum + byte, 0);
|
|
1343
|
+
put(148, 8, `${checksum.toString(8).padStart(6, "0")}\0 `);
|
|
1344
|
+
return header;
|
|
1280
1345
|
}
|
|
1281
|
-
function
|
|
1282
|
-
return
|
|
1346
|
+
function tarPadding(size) {
|
|
1347
|
+
return new Uint8Array((BLOCK - size % BLOCK) % BLOCK);
|
|
1283
1348
|
}
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
const
|
|
1287
|
-
|
|
1349
|
+
var TAR_END = new Uint8Array(BLOCK * 2);
|
|
1350
|
+
function tarOf(files) {
|
|
1351
|
+
const parts = files.flatMap((file) => [tarHeader(file.name, file.data.length, file.mode), file.data, tarPadding(file.data.length)]);
|
|
1352
|
+
parts.push(TAR_END);
|
|
1353
|
+
const out = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
|
|
1354
|
+
let at = 0;
|
|
1355
|
+
for (const part of parts) {
|
|
1356
|
+
out.set(part, at);
|
|
1357
|
+
at += part.length;
|
|
1358
|
+
}
|
|
1359
|
+
return out;
|
|
1288
1360
|
}
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
)
|
|
1361
|
+
var field = (block, offset, length) => decoder.decode(block.subarray(offset, offset + length)).replace(/\0.*$/s, "");
|
|
1362
|
+
async function tarEntries(read) {
|
|
1363
|
+
const entries = [];
|
|
1364
|
+
let longName = null;
|
|
1365
|
+
for (let offset = 0; ; ) {
|
|
1366
|
+
const block = await read(offset, BLOCK);
|
|
1367
|
+
if (block.length < BLOCK) throw new Error(`the tar ends at byte ${offset + block.length}, before its end-of-archive block; it is truncated`);
|
|
1368
|
+
if (block.every((byte) => byte === 0)) return entries;
|
|
1369
|
+
const size = Number.parseInt(field(block, 124, 12).trim() || "0", 8);
|
|
1370
|
+
if (!Number.isSafeInteger(size) || size < 0) throw new Error(`tar entry at ${offset} has an invalid size`);
|
|
1371
|
+
const type = field(block, 156, 1) || "0";
|
|
1372
|
+
const prefix = field(block, 345, 155);
|
|
1373
|
+
const name = longName ?? (prefix ? `${prefix}/${field(block, 0, 100)}` : field(block, 0, 100));
|
|
1374
|
+
const data = offset + BLOCK;
|
|
1375
|
+
longName = null;
|
|
1376
|
+
if (type === "x" || type === "L") {
|
|
1377
|
+
const text = decoder.decode(await read(data, size)).replace(/\0+$/, "");
|
|
1378
|
+
longName = type === "L" ? text : /(?:^|\n)\d+ path=([^\n]*)\n/.exec(text)?.[1] ?? null;
|
|
1379
|
+
} else if (type === "0") {
|
|
1380
|
+
entries.push({ name, offset: data, size });
|
|
1381
|
+
}
|
|
1382
|
+
offset = data + size + tarPadding(size).length;
|
|
1383
|
+
}
|
|
1294
1384
|
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1385
|
+
|
|
1386
|
+
// src/sandbox-sync.ts
|
|
1387
|
+
var SyncError = class extends Error {
|
|
1388
|
+
};
|
|
1389
|
+
function git(args, options) {
|
|
1390
|
+
const result = spawnSync("git", args, {
|
|
1391
|
+
cwd: options.cwd,
|
|
1392
|
+
env: options.env ?? process5.env,
|
|
1393
|
+
input: options.input,
|
|
1394
|
+
encoding: "utf8",
|
|
1395
|
+
maxBuffer: 1 << 30
|
|
1396
|
+
// Node defaults to 1 MiB; this repo's `ls-files -s -z` alone is 1.2 MB
|
|
1397
|
+
});
|
|
1398
|
+
if (result.error) throw new SyncError(`git ${args[0]} failed: ${result.error.message}`);
|
|
1399
|
+
if (!(options.okCodes ?? [0]).includes(result.status ?? -1)) {
|
|
1400
|
+
throw new SyncError(`git ${args[0]} failed (${result.signal ?? result.status}): ${result.stderr.trim()}`);
|
|
1401
|
+
}
|
|
1402
|
+
return result.stdout;
|
|
1299
1403
|
}
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1404
|
+
var toPosix = (path) => path.split(sep).join("/");
|
|
1405
|
+
function repositoryOf(dir) {
|
|
1406
|
+
const top = git(["rev-parse", "--show-toplevel"], { cwd: dir }).trim();
|
|
1407
|
+
const gitDir = git(["rev-parse", "--absolute-git-dir"], { cwd: dir }).trim();
|
|
1408
|
+
return { top: realpathSync(top), gitDir };
|
|
1305
1409
|
}
|
|
1306
|
-
function
|
|
1307
|
-
|
|
1308
|
-
|
|
1410
|
+
function indexEntries(output2, strip, top) {
|
|
1411
|
+
const files = [];
|
|
1412
|
+
const skipped = [];
|
|
1413
|
+
for (const entry of output2.split("\0")) {
|
|
1414
|
+
if (entry === "") continue;
|
|
1415
|
+
const match = /^(\d{6}) ([0-9a-f]+) \d\t(.*)$/s.exec(entry);
|
|
1416
|
+
if (!match) throw new SyncError(`unexpected git ls-files line: ${entry}`);
|
|
1417
|
+
const [, mode, blob, repoPath] = match;
|
|
1418
|
+
const path = strip === "" ? repoPath : repoPath.slice(strip.length + 1);
|
|
1419
|
+
const onDisk = mode === "100644" || mode === "100755" ? lstatOrNull(join(top, repoPath)) : null;
|
|
1420
|
+
if (onDisk?.isFile()) files.push({ path, blob, mode: mode === "100755" ? 493 : 420, size: onDisk.size });
|
|
1421
|
+
else skipped.push(path);
|
|
1309
1422
|
}
|
|
1423
|
+
return { files, skipped };
|
|
1310
1424
|
}
|
|
1311
|
-
function
|
|
1312
|
-
|
|
1425
|
+
function workingTree(root) {
|
|
1426
|
+
const { top, gitDir } = repositoryOf(root);
|
|
1427
|
+
const prefix = toPosix(relative(top, realpathSync(root)));
|
|
1428
|
+
const temp = mkdtempSync(join(tmpdir(), "cofactor-sync-"));
|
|
1429
|
+
const cleanup = () => rmSync(temp, { recursive: true, force: true });
|
|
1430
|
+
try {
|
|
1431
|
+
const index = join(temp, "index");
|
|
1432
|
+
const env = { ...process5.env, GIT_INDEX_FILE: index };
|
|
1433
|
+
const userIndex = join(gitDir, "index");
|
|
1434
|
+
if (existsSync(userIndex)) copyFileSync(userIndex, index);
|
|
1435
|
+
else if (git(["rev-parse", "--verify", "-q", "HEAD"], { cwd: top, okCodes: [0, 1] }).trim() !== "") git(["read-tree", "HEAD"], { cwd: top, env });
|
|
1436
|
+
const pathspec = prefix === "" ? "." : `:(literal)${prefix}`;
|
|
1437
|
+
git(["add", "-A", "--", pathspec], { cwd: top, env });
|
|
1438
|
+
const entries = indexEntries(git(["ls-files", "-s", "-z", "--", pathspec], { cwd: top, env }), prefix, top);
|
|
1439
|
+
const files = withRawBlobs(entries.files, top, prefix);
|
|
1440
|
+
const skipped = entries.skipped;
|
|
1441
|
+
const tree = git(prefix === "" ? ["write-tree"] : ["write-tree", `--prefix=${prefix}/`], { cwd: top, env }).trim();
|
|
1442
|
+
const untracked = new Set(
|
|
1443
|
+
git(["ls-files", "--others", "--exclude-standard", "-z", "--", pathspec], { cwd: top }).split("\0").filter(Boolean).map((path) => prefix === "" ? path : path.slice(prefix.length + 1))
|
|
1444
|
+
);
|
|
1445
|
+
return { files, skipped, untracked, tree, repo: top, prefix, env, cleanup };
|
|
1446
|
+
} catch (error) {
|
|
1447
|
+
cleanup();
|
|
1448
|
+
throw error;
|
|
1449
|
+
}
|
|
1313
1450
|
}
|
|
1314
|
-
function
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1451
|
+
function buildOutput(dir, to) {
|
|
1452
|
+
const { gitDir } = repositoryOf(dir);
|
|
1453
|
+
const temp = mkdtempSync(join(tmpdir(), "cofactor-sync-"));
|
|
1454
|
+
try {
|
|
1455
|
+
const env = { ...process5.env, GIT_INDEX_FILE: join(temp, "index"), GIT_DIR: gitDir, GIT_WORK_TREE: realpathSync(dir) };
|
|
1456
|
+
git(["add", "-A", "-f", "--", "."], { cwd: dir, env });
|
|
1457
|
+
const entries = indexEntries(git(["ls-files", "-s", "-z"], { cwd: dir, env }), "", realpathSync(dir));
|
|
1458
|
+
const files = withRawBlobs(entries.files, realpathSync(dir), "", env);
|
|
1459
|
+
const skipped = entries.skipped;
|
|
1460
|
+
const tree = git(["write-tree"], { cwd: dir, env }).trim();
|
|
1461
|
+
return { files: files.map((file) => ({ ...file, path: `${to}/${file.path}` })), skipped: skipped.map((path) => `${to}/${path}`), tree };
|
|
1462
|
+
} finally {
|
|
1463
|
+
rmSync(temp, { recursive: true, force: true });
|
|
1326
1464
|
}
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1465
|
+
}
|
|
1466
|
+
var FILTER_ATTRIBUTES = ["filter", "text", "eol", "ident", "working-tree-encoding"];
|
|
1467
|
+
function withRawBlobs(files, top, prefix, env) {
|
|
1468
|
+
if (files.length === 0) return files;
|
|
1469
|
+
const options = { cwd: top, ...env ? { env } : {} };
|
|
1470
|
+
const repoPaths = files.map((file) => prefix === "" ? file.path : `${prefix}/${file.path}`);
|
|
1471
|
+
const autocrlf = git(["config", "--get", "core.autocrlf"], { ...options, okCodes: [0, 1] }).trim();
|
|
1472
|
+
const converts = autocrlf === "input" || autocrlf !== "" && git(["config", "--get", "--type=bool", "core.autocrlf"], options).trim() === "true";
|
|
1473
|
+
let filtered;
|
|
1474
|
+
if (converts) {
|
|
1475
|
+
filtered = new Set(repoPaths);
|
|
1476
|
+
} else {
|
|
1477
|
+
const out = git(["check-attr", "-z", "--stdin", ...FILTER_ATTRIBUTES], { ...options, input: repoPaths.map((path) => `${path}\0`).join("") }).split("\0");
|
|
1478
|
+
filtered = /* @__PURE__ */ new Set();
|
|
1479
|
+
for (let i = 0; i + 2 < out.length; i += 3) if (out[i + 2] !== "unspecified") filtered.add(out[i]);
|
|
1330
1480
|
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1481
|
+
return files.map(
|
|
1482
|
+
(file, i) => filtered.has(repoPaths[i]) ? { ...file, blob: gitBlobId(readFileSync2(join(top, repoPaths[i]))) } : file
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1485
|
+
function lstatOrNull(path) {
|
|
1486
|
+
try {
|
|
1487
|
+
return lstatSync(path);
|
|
1488
|
+
} catch (error) {
|
|
1489
|
+
if (error.code === "ENOENT") return null;
|
|
1490
|
+
throw error;
|
|
1340
1491
|
}
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1492
|
+
}
|
|
1493
|
+
function chunked(items, fn) {
|
|
1494
|
+
for (let i = 0; i < items.length; i += 200) fn(items.slice(i, i + 200));
|
|
1495
|
+
}
|
|
1496
|
+
function ignoredPaths(repo, env, paths) {
|
|
1497
|
+
if (paths.length === 0) return /* @__PURE__ */ new Set();
|
|
1498
|
+
const out = git(["check-ignore", "-z", "--stdin"], { cwd: repo, env, input: paths.map((p) => `${p}\0`).join(""), okCodes: [0, 1] });
|
|
1499
|
+
return new Set(out.split("\0").filter(Boolean));
|
|
1500
|
+
}
|
|
1501
|
+
function everTracked(repo, pathspecs) {
|
|
1502
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1503
|
+
if (pathspecs.length === 0) return seen;
|
|
1504
|
+
if (git(["rev-parse", "--verify", "-q", "HEAD"], { cwd: repo, okCodes: [0, 1] }).trim() === "") return seen;
|
|
1505
|
+
chunked(pathspecs, (chunk) => {
|
|
1506
|
+
const out = git(["log", "--all", "--format=", "--name-only", "-z", "--", ...chunk.map((p) => `:(literal)${p}`)], { cwd: repo });
|
|
1507
|
+
for (const path of out.split("\0")) {
|
|
1508
|
+
const trimmed = path.replace(/^\n+/, "");
|
|
1509
|
+
if (trimmed) seen.add(trimmed);
|
|
1350
1510
|
}
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1511
|
+
});
|
|
1512
|
+
return seen;
|
|
1513
|
+
}
|
|
1514
|
+
async function chooseDeletions(params) {
|
|
1515
|
+
const repoPath = (path) => params.prefix === "" ? path : `${params.prefix}/${path}`;
|
|
1516
|
+
const ignored = ignoredPaths(params.repo, params.env, params.copyOnly.map((entry) => repoPath(entry.path)));
|
|
1517
|
+
const candidates = params.copyOnly.filter((entry) => !ignored.has(repoPath(entry.path)));
|
|
1518
|
+
const history = everTracked(params.repo, candidates.map((entry) => repoPath(entry.path)));
|
|
1519
|
+
const kept = params.copyOnly.filter((entry) => ignored.has(repoPath(entry.path))).map((entry) => ({ ...entry, why: "ignored here" }));
|
|
1520
|
+
const remove = [];
|
|
1521
|
+
for (const entry of candidates) {
|
|
1522
|
+
if (!entry.path.endsWith("/")) {
|
|
1523
|
+
if (history.has(repoPath(entry.path))) remove.push(entry.path);
|
|
1524
|
+
else kept.push({ ...entry, why: "never tracked here" });
|
|
1525
|
+
continue;
|
|
1358
1526
|
}
|
|
1527
|
+
const tracked = [...history].some((path) => path.startsWith(repoPath(entry.path)));
|
|
1528
|
+
const listed = tracked ? await params.listDir(entry.path.slice(0, -1)) : [];
|
|
1529
|
+
const ignoredInside = ignoredPaths(params.repo, params.env, listed.map(repoPath));
|
|
1530
|
+
const files = listed.filter((path) => !ignoredInside.has(repoPath(path)) && history.has(repoPath(path)));
|
|
1531
|
+
remove.push(...files);
|
|
1532
|
+
if (files.length < entry.files) kept.push({ path: entry.path, files: entry.files - files.length, why: "never tracked here" });
|
|
1359
1533
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1534
|
+
return { delete: remove.sort(), kept: kept.sort((a, b) => a.path.localeCompare(b.path)) };
|
|
1535
|
+
}
|
|
1536
|
+
function applyBatches(files, remove) {
|
|
1537
|
+
const budget = SYNC_MAX_APPLY_BYTES - 1024 * 1024;
|
|
1538
|
+
const cost = (file) => 512 + Math.ceil(file.bytes.length / 512) * 512 + 100 + file.path.length;
|
|
1539
|
+
const batches = [[]];
|
|
1540
|
+
let size = 0;
|
|
1541
|
+
for (const file of files) {
|
|
1542
|
+
if (cost(file) > budget) throw new SyncError(`${file.path} is ${Math.ceil(file.bytes.length / 1024 / 1024)} MiB; a sync sends files of at most ${budget / 1024 / 1024} MiB`);
|
|
1543
|
+
if (size + cost(file) > budget && batches.at(-1).length > 0) {
|
|
1544
|
+
batches.push([]);
|
|
1545
|
+
size = 0;
|
|
1365
1546
|
}
|
|
1547
|
+
batches.at(-1).push(file);
|
|
1548
|
+
size += cost(file);
|
|
1366
1549
|
}
|
|
1367
|
-
|
|
1368
|
-
|
|
1550
|
+
return batches.map((batch, index) => {
|
|
1551
|
+
const manifest = {
|
|
1552
|
+
files: batch.map(({ path, blob, mode }) => ({ path, blob, mode })),
|
|
1553
|
+
delete: index === 0 ? remove : []
|
|
1554
|
+
};
|
|
1555
|
+
return tarOf([
|
|
1556
|
+
{ name: "manifest.json", data: new TextEncoder().encode(JSON.stringify(manifest)) },
|
|
1557
|
+
...batch.map((file, i) => ({ name: syncApplyEntryName(i), data: file.bytes }))
|
|
1558
|
+
]);
|
|
1559
|
+
});
|
|
1369
1560
|
}
|
|
1370
|
-
function
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1561
|
+
async function copyRequest(session, path, init) {
|
|
1562
|
+
let response;
|
|
1563
|
+
try {
|
|
1564
|
+
response = await fetch(`${session.url}${path}`, {
|
|
1565
|
+
method: "POST",
|
|
1566
|
+
headers: { authorization: `Bearer ${session.token}`, "content-type": init.contentType },
|
|
1567
|
+
body: init.body
|
|
1568
|
+
});
|
|
1569
|
+
} catch (error) {
|
|
1570
|
+
throw new SyncError(`could not reach the sandbox for ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1379
1571
|
}
|
|
1380
|
-
if (
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
for (const step of seeds) console.log(` ${step.id} ${step.action} ${(step.ms / 1e3).toFixed(1)}s`);
|
|
1387
|
-
const ran = data.snapshot.buildPhases.build?.steps.filter((step) => step.ran) ?? [];
|
|
1388
|
-
if (ran.length > 0) {
|
|
1389
|
-
console.log(` build steps: ${ran.map((step) => `${step.name} ${(step.ms / 1e3).toFixed(1)}s`).join(", ")}`);
|
|
1572
|
+
if (!response.ok) {
|
|
1573
|
+
const text = await response.text();
|
|
1574
|
+
let message = text;
|
|
1575
|
+
try {
|
|
1576
|
+
message = JSON.parse(text).error ?? text;
|
|
1577
|
+
} catch {
|
|
1390
1578
|
}
|
|
1579
|
+
throw new SyncError(`the sandbox refused ${path} (${response.status}): ${message.slice(0, 2e3)}`);
|
|
1391
1580
|
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1581
|
+
return response;
|
|
1582
|
+
}
|
|
1583
|
+
async function plan(session, local, to) {
|
|
1584
|
+
const body = JSON.stringify(to === void 0 ? { local } : { local, to });
|
|
1585
|
+
if (Buffer.byteLength(body) > SYNC_MAX_APPLY_BYTES) {
|
|
1586
|
+
throw new SyncError(
|
|
1587
|
+
`the file list (${local.length} files) is over the ${SYNC_MAX_APPLY_BYTES / 1024 / 1024} MiB a sync can plan in one request; ignore what the sandbox doesn't need (.gitignore)`
|
|
1588
|
+
);
|
|
1395
1589
|
}
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1590
|
+
const response = await copyRequest(session, "/sync/plan", { body, contentType: "application/json" });
|
|
1591
|
+
return parseAnswer(syncPlanResponseSchema, await response.text(), "/sync/plan");
|
|
1592
|
+
}
|
|
1593
|
+
function parseAnswer(schema, text, path) {
|
|
1594
|
+
let value;
|
|
1595
|
+
try {
|
|
1596
|
+
value = JSON.parse(text);
|
|
1597
|
+
} catch {
|
|
1598
|
+
throw new SyncError(`the sandbox answered ${path} with something other than JSON: ${text.slice(0, 300)}`);
|
|
1400
1599
|
}
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1600
|
+
const parsed = schema.safeParse(value);
|
|
1601
|
+
if (!parsed.success) throw new SyncError(`the sandbox answered ${path} unexpectedly: ${text.slice(0, 300)}`);
|
|
1602
|
+
return parsed.data;
|
|
1603
|
+
}
|
|
1604
|
+
async function apply(session, batches, build, json) {
|
|
1605
|
+
let written = 0;
|
|
1606
|
+
let deleted = 0;
|
|
1607
|
+
let restarted = [];
|
|
1608
|
+
const steps = [];
|
|
1609
|
+
for (const [index, body] of batches.entries()) {
|
|
1610
|
+
const last = index === batches.length - 1;
|
|
1611
|
+
const response = await copyRequest(session, `/sync/apply${build && last ? "?build=1" : ""}`, { body, contentType: "application/x-tar" });
|
|
1612
|
+
if (!(build && last)) {
|
|
1613
|
+
const result = parseAnswer(syncApplyResultSchema, await response.text(), "/sync/apply");
|
|
1614
|
+
written += result.written;
|
|
1615
|
+
deleted += result.deleted;
|
|
1616
|
+
continue;
|
|
1409
1617
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1618
|
+
let buffer = "";
|
|
1619
|
+
const decoder2 = new TextDecoder();
|
|
1620
|
+
let finished = false;
|
|
1621
|
+
for await (const chunk of response.body) {
|
|
1622
|
+
buffer += decoder2.decode(chunk, { stream: true });
|
|
1623
|
+
let newline = buffer.indexOf("\n");
|
|
1624
|
+
while (newline >= 0) {
|
|
1625
|
+
const event = parseAnswer(syncBuildEventSchema, buffer.slice(0, newline), "/sync/apply?build=1");
|
|
1626
|
+
buffer = buffer.slice(newline + 1);
|
|
1627
|
+
newline = buffer.indexOf("\n");
|
|
1628
|
+
if (event.type === "applied") {
|
|
1629
|
+
written += event.result.written;
|
|
1630
|
+
deleted += event.result.deleted;
|
|
1631
|
+
} else if (event.type === "output") {
|
|
1632
|
+
if (!json) process5.stderr.write(event.text);
|
|
1633
|
+
} else if (event.type === "step") {
|
|
1634
|
+
steps.push({ name: event.name, ran: event.ran, ms: event.ms });
|
|
1635
|
+
if (!json) process5.stderr.write(` build step ${event.name}: ${event.ran ? `ran in ${(event.ms / 1e3).toFixed(1)} s` : "unchanged"}
|
|
1636
|
+
`);
|
|
1637
|
+
} else if (event.type === "done") {
|
|
1638
|
+
restarted = event.restarted;
|
|
1639
|
+
finished = true;
|
|
1640
|
+
} else if (event.type === "error") {
|
|
1641
|
+
throw new SyncError(`the build failed after the files were synced: ${event.message}`);
|
|
1642
|
+
}
|
|
1422
1643
|
}
|
|
1423
1644
|
}
|
|
1645
|
+
if (!finished) throw new SyncError("the build stream ended early; check the copy with `cofactor sandbox exec <id> -- cofactor-sandbox status`");
|
|
1424
1646
|
}
|
|
1425
|
-
|
|
1426
|
-
console.log("");
|
|
1427
|
-
console.log(" Actions:");
|
|
1428
|
-
for (const a of data.actions) console.log(` ${formatActionSignature(a)}`);
|
|
1429
|
-
}
|
|
1430
|
-
if (data.operations.length > 0) {
|
|
1431
|
-
console.log("");
|
|
1432
|
-
console.log(" Operations:");
|
|
1433
|
-
for (const op of data.operations) console.log(` ${formatOperationSignature(op)}`);
|
|
1434
|
-
}
|
|
1647
|
+
return { written, deleted, restarted, steps };
|
|
1435
1648
|
}
|
|
1436
|
-
|
|
1437
|
-
|
|
1649
|
+
var SYNC_USAGE = "Usage: cofactor sandbox sync <id> [--build] [--json]\n cofactor sandbox sync <id> <local-dir> --to <path-in-sandbox> [--json]";
|
|
1650
|
+
var LIST_LIMIT = 20;
|
|
1651
|
+
async function handleSandboxSync(positionals, argv, apiUrl, token) {
|
|
1652
|
+
const [id, localDir] = positionals;
|
|
1653
|
+
const to = getArgValue(argv, "--to");
|
|
1654
|
+
const build = hasFlag(argv, "--build");
|
|
1438
1655
|
const json = hasFlag(argv, "--json");
|
|
1439
|
-
const
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
process4.exit(1);
|
|
1451
|
-
}
|
|
1452
|
-
const { definition, issues } = validateDefinition(yamlText, delivery);
|
|
1453
|
-
if (json) {
|
|
1454
|
-
writeRawOutput(JSON.stringify({ ok: issues.length === 0, issues }, null, 2));
|
|
1455
|
-
process4.exit(issues.length === 0 ? 0 : 1);
|
|
1656
|
+
const fail4 = (message) => {
|
|
1657
|
+
console.error(`Error: ${message}`);
|
|
1658
|
+
process5.exit(1);
|
|
1659
|
+
};
|
|
1660
|
+
if (!id) return fail4(`sandbox id required.
|
|
1661
|
+
${SYNC_USAGE}`);
|
|
1662
|
+
if (localDir === void 0 !== (to === void 0)) return fail4(`a local directory and --to go together.
|
|
1663
|
+
${SYNC_USAGE}`);
|
|
1664
|
+
if (to !== void 0 && build) return fail4("--build reruns the build of a working-tree sync; build output is already built (`sync <id> <dir> --to <path>`).");
|
|
1665
|
+
if (to !== void 0 && (to === "" || to.startsWith("/") || to.split("/").some((part) => part === "" || part === "." || part === ".."))) {
|
|
1666
|
+
return fail4("--to is a path under the sandbox's sync root, like `static` or `frontend/dist` (no leading `/`, no `..`).");
|
|
1456
1667
|
}
|
|
1457
|
-
if (
|
|
1458
|
-
|
|
1459
|
-
|
|
1668
|
+
if (localDir !== void 0 && (!existsSync(localDir) || !lstatSync(localDir).isDirectory())) return fail4(`${localDir} is not a directory`);
|
|
1669
|
+
if (!isInsideRepo(to === void 0 ? process5.cwd() : resolve(localDir))) {
|
|
1670
|
+
return fail4("run this inside a git repository: the files to sync and the synced tree come from git.");
|
|
1460
1671
|
}
|
|
1461
|
-
const
|
|
1462
|
-
const
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1672
|
+
const started = Date.now();
|
|
1673
|
+
const sessionRes = await apiRequest(
|
|
1674
|
+
apiUrl,
|
|
1675
|
+
token,
|
|
1676
|
+
"POST",
|
|
1677
|
+
`/org/sandboxes-v2/sandboxes/${id}/sync-session`
|
|
1466
1678
|
);
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
for (let i = 0; i < argv.length; i++) {
|
|
1495
|
-
const arg = argv[i];
|
|
1496
|
-
if (spec.values.includes(arg)) {
|
|
1497
|
-
i++;
|
|
1498
|
-
continue;
|
|
1679
|
+
if (!sessionRes.ok) return fail4(sessionRes.error ?? "could not start a sync");
|
|
1680
|
+
const session = sessionRes.data;
|
|
1681
|
+
try {
|
|
1682
|
+
let local;
|
|
1683
|
+
let decide;
|
|
1684
|
+
let cleanup = () => {
|
|
1685
|
+
};
|
|
1686
|
+
if (to !== void 0) {
|
|
1687
|
+
const dir = resolve(localDir);
|
|
1688
|
+
const output2 = buildOutput(dir, to);
|
|
1689
|
+
local = { ...output2, untracked: /* @__PURE__ */ new Set(), root: dir };
|
|
1690
|
+
decide = async (planned) => ({ delete: planned.copyOnly.map((entry) => entry.path), kept: [] });
|
|
1691
|
+
} else {
|
|
1692
|
+
const root = session.delivery === "github" ? repositoryOf(process5.cwd()).top : process5.cwd();
|
|
1693
|
+
if (session.delivery === "upload" && !existsSync(join(root, "cofactor.sandbox.yaml"))) {
|
|
1694
|
+
return fail4("run this from the directory holding cofactor.sandbox.yaml: a pushed image's WORKDIR mirrors it.");
|
|
1695
|
+
}
|
|
1696
|
+
const tree = workingTree(root);
|
|
1697
|
+
cleanup = tree.cleanup;
|
|
1698
|
+
local = { ...tree, root };
|
|
1699
|
+
decide = (planned) => chooseDeletions({
|
|
1700
|
+
copyOnly: planned.copyOnly,
|
|
1701
|
+
repo: tree.repo,
|
|
1702
|
+
prefix: tree.prefix,
|
|
1703
|
+
env: tree.env,
|
|
1704
|
+
listDir: async (dir) => (await plan(session, [], dir)).copyOnly.map((entry) => entry.path)
|
|
1705
|
+
});
|
|
1499
1706
|
}
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1707
|
+
try {
|
|
1708
|
+
const planStarted = Date.now();
|
|
1709
|
+
const planned = await plan(session, local.files, to);
|
|
1710
|
+
const skipped = new Set(local.skipped.flatMap((path) => [path, `${path}/`]));
|
|
1711
|
+
const { delete: remove, kept } = await decide({ ...planned, copyOnly: planned.copyOnly.filter((entry) => !skipped.has(entry.path)) });
|
|
1712
|
+
const planMs = Date.now() - planStarted;
|
|
1713
|
+
const need = new Set(planned.need);
|
|
1714
|
+
const sending = local.files.filter((file) => need.has(file.path));
|
|
1715
|
+
if (!json) {
|
|
1716
|
+
writeRawOutput(`Syncing ${local.root} into sandbox ${id} (${session.delivery === "github" ? "GitHub" : "pushed image"}, version ${session.version.slice(0, 12)})`);
|
|
1717
|
+
writeRawOutput(` plan: ${sending.length} to write, ${remove.length} to delete, ${local.files.length - sending.length} unchanged (${(planMs / 1e3).toFixed(1)} s)`);
|
|
1718
|
+
const untracked = sending.filter((file) => local.untracked.has(file.path));
|
|
1719
|
+
const tracked = sending.filter((file) => !local.untracked.has(file.path));
|
|
1720
|
+
if (sending.length > 0) writeRawOutput(" write:");
|
|
1721
|
+
for (const file of tracked.slice(0, LIST_LIMIT)) writeRawOutput(` ${file.path}`);
|
|
1722
|
+
if (tracked.length > LIST_LIMIT) writeRawOutput(` ...and ${tracked.length - LIST_LIMIT} more`);
|
|
1723
|
+
for (const file of untracked) writeRawOutput(` ${file.path} (untracked)`);
|
|
1724
|
+
if (remove.length > 0) writeRawOutput(" delete:");
|
|
1725
|
+
for (const path of remove) writeRawOutput(` ${path}`);
|
|
1726
|
+
if (kept.length > 0) {
|
|
1727
|
+
writeRawOutput(" kept (only in the sandbox):");
|
|
1728
|
+
for (const entry of kept.slice(0, LIST_LIMIT)) {
|
|
1729
|
+
writeRawOutput(` ${entry.path}${entry.files > 1 ? ` (${entry.files} files)` : ""}, ${entry.why}`);
|
|
1730
|
+
}
|
|
1731
|
+
if (kept.length > LIST_LIMIT) writeRawOutput(` ...and ${kept.length - LIST_LIMIT} more`);
|
|
1732
|
+
}
|
|
1733
|
+
if (local.skipped.length > 0) writeRawOutput(` not synced (symlinks and submodules): ${local.skipped.join(", ")}`);
|
|
1734
|
+
}
|
|
1735
|
+
const applyStarted = Date.now();
|
|
1736
|
+
const batches = sending.length === 0 && remove.length === 0 && !build ? [] : applyBatches(
|
|
1737
|
+
sending.map((file) => ({ ...file, bytes: readFileSync2(join(local.root, to === void 0 ? file.path : file.path.slice(to.length + 1))) })),
|
|
1738
|
+
remove
|
|
1506
1739
|
);
|
|
1507
|
-
|
|
1740
|
+
const applied = await apply(session, batches, build, json);
|
|
1741
|
+
const applyMs = Date.now() - applyStarted;
|
|
1742
|
+
const ms = Date.now() - started;
|
|
1743
|
+
const recorded = await apiRequest(apiUrl, token, "POST", `/org/sandboxes-v2/sandboxes/${id}/syncs`, {
|
|
1744
|
+
tree: local.tree,
|
|
1745
|
+
buildOutput: to !== void 0,
|
|
1746
|
+
written: applied.written,
|
|
1747
|
+
deleted: applied.deleted,
|
|
1748
|
+
ms
|
|
1749
|
+
});
|
|
1750
|
+
if (!recorded.ok) throw new SyncError(`the files were synced, but recording the sync failed: ${recorded.error}`);
|
|
1751
|
+
if (json) {
|
|
1752
|
+
writeRawOutput(
|
|
1753
|
+
JSON.stringify(
|
|
1754
|
+
{
|
|
1755
|
+
id,
|
|
1756
|
+
tree: local.tree,
|
|
1757
|
+
buildOutput: to !== void 0,
|
|
1758
|
+
written: sending.map((file) => file.path),
|
|
1759
|
+
deleted: remove,
|
|
1760
|
+
kept,
|
|
1761
|
+
build: build ? { steps: applied.steps, restarted: applied.restarted } : null,
|
|
1762
|
+
ms: { total: ms, plan: planMs, apply: applyMs }
|
|
1763
|
+
},
|
|
1764
|
+
null,
|
|
1765
|
+
2
|
|
1766
|
+
)
|
|
1767
|
+
);
|
|
1768
|
+
return;
|
|
1769
|
+
}
|
|
1770
|
+
writeRawOutput(` applied: ${applied.written} written, ${applied.deleted} deleted (${(applyMs / 1e3).toFixed(1)} s)`);
|
|
1771
|
+
if (build) writeRawOutput(` restarted: ${applied.restarted.join(", ") || "nothing"}`);
|
|
1772
|
+
writeRawOutput(`Synced ${to === void 0 ? "tree" : "build output"} ${local.tree} in ${(ms / 1e3).toFixed(1)} s.`);
|
|
1773
|
+
if (!build) {
|
|
1774
|
+
writeRawOutput(` Restart what runs it: cofactor sandbox exec ${id} -- cofactor-sandbox restart <service>`);
|
|
1775
|
+
}
|
|
1776
|
+
const shown = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
|
|
1777
|
+
for (const [name, url] of Object.entries(shown.data?.portUrls ?? {})) writeRawOutput(` ${name.padEnd(10)} ${url}`);
|
|
1778
|
+
} finally {
|
|
1779
|
+
cleanup();
|
|
1508
1780
|
}
|
|
1509
|
-
|
|
1510
|
-
|
|
1781
|
+
} catch (error) {
|
|
1782
|
+
if (error instanceof SyncError) return fail4(error.message);
|
|
1783
|
+
throw error;
|
|
1511
1784
|
}
|
|
1512
1785
|
}
|
|
1513
|
-
function
|
|
1514
|
-
|
|
1515
|
-
const result = [];
|
|
1516
|
-
for (let i = 0; i < argv.length; i++) {
|
|
1517
|
-
const arg = argv[i];
|
|
1518
|
-
if (values.includes(arg)) i++;
|
|
1519
|
-
else if (!arg.startsWith("-")) result.push(arg);
|
|
1520
|
-
}
|
|
1521
|
-
return result;
|
|
1786
|
+
function isInsideRepo(dir) {
|
|
1787
|
+
return git(["rev-parse", "--is-inside-work-tree"], { cwd: dir, okCodes: [0, 128] }).trim() === "true";
|
|
1522
1788
|
}
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1789
|
+
|
|
1790
|
+
// src/sandbox-from-local.ts
|
|
1791
|
+
var TTL_MS = 7 * 24 * 3600 * 1e3;
|
|
1792
|
+
var RecordSchema = z4.strictObject({
|
|
1793
|
+
repo: z4.string(),
|
|
1794
|
+
repoName: z4.string(),
|
|
1795
|
+
remoteIdentity: z4.string(),
|
|
1796
|
+
application: z4.string(),
|
|
1797
|
+
applicationId: z4.string().nullable(),
|
|
1798
|
+
claims: z4.array(z4.string()),
|
|
1799
|
+
apiUrl: z4.string(),
|
|
1800
|
+
version: z4.string().regex(/^[0-9a-f]{40}$/),
|
|
1801
|
+
ref: z4.string().regex(/^refs\/cofactor\/wip\/[0-9a-f]{40}$/),
|
|
1802
|
+
updatedAt: z4.number()
|
|
1803
|
+
});
|
|
1804
|
+
function git2(repo, args, env = process.env) {
|
|
1805
|
+
const result = spawnSync2("git", args, {
|
|
1806
|
+
cwd: repo,
|
|
1807
|
+
env,
|
|
1808
|
+
encoding: "utf8",
|
|
1809
|
+
maxBuffer: 64 * 1024 * 1024
|
|
1810
|
+
});
|
|
1811
|
+
if (result.error || result.status !== 0) {
|
|
1812
|
+
const reason = (result.error?.message ?? result.stderr).replace(
|
|
1813
|
+
/https?:\/\/[^\s/@]+(?::[^\s/@]*)?@/g,
|
|
1814
|
+
"https://***@"
|
|
1815
|
+
);
|
|
1816
|
+
throw new Error(`git ${args[0]} failed: ${reason.trim()}`);
|
|
1817
|
+
}
|
|
1818
|
+
return result.stdout.trim();
|
|
1819
|
+
}
|
|
1820
|
+
function remoteIdentity(remote) {
|
|
1821
|
+
if (!/^https?:\/\//.test(remote)) return remote;
|
|
1822
|
+
const url = new URL(remote);
|
|
1823
|
+
url.username = "";
|
|
1824
|
+
url.password = "";
|
|
1825
|
+
return url.href;
|
|
1826
|
+
}
|
|
1827
|
+
var stateDir = () => join2(activeConfigDir(), "sandbox-wip");
|
|
1828
|
+
function save(file, record) {
|
|
1829
|
+
mkdirSync(stateDir(), { recursive: true, mode: 448 });
|
|
1830
|
+
const temp = `${file}.${randomUUID()}.tmp`;
|
|
1831
|
+
writeFileSync(temp, JSON.stringify(record), { mode: 384 });
|
|
1832
|
+
renameSync(temp, file);
|
|
1833
|
+
}
|
|
1834
|
+
function records() {
|
|
1835
|
+
if (!existsSync2(stateDir())) return [];
|
|
1836
|
+
return readdirSync(stateDir()).filter((name) => name.endsWith(".json")).map((name) => {
|
|
1837
|
+
const file = join2(stateDir(), name);
|
|
1838
|
+
return { file, record: RecordSchema.parse(JSON.parse(readFileSync3(file, "utf8"))) };
|
|
1839
|
+
});
|
|
1531
1840
|
}
|
|
1532
|
-
|
|
1533
|
-
const
|
|
1534
|
-
if (!
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1841
|
+
function publishLocalSource(argv, apiUrl) {
|
|
1842
|
+
const application = getArgValue(argv, "--application") ?? getArgValue(argv, "--property");
|
|
1843
|
+
if (!application) throw new Error("--from-local requires --application <name|id>.");
|
|
1844
|
+
const { top } = repositoryOf(process.cwd());
|
|
1845
|
+
const remote = git2(top, ["remote", "get-url", "--push", "origin"]);
|
|
1846
|
+
const inferred = /^(?:git@github\.com:|ssh:\/\/git@github\.com\/|https:\/\/github\.com\/)([^/]+\/[^/]+?)(?:\.git)?$/.exec(
|
|
1847
|
+
remote
|
|
1848
|
+
)?.[1];
|
|
1849
|
+
const repoName = getArgValue(argv, "--repo") ?? inferred;
|
|
1850
|
+
if (!repoName)
|
|
1851
|
+
throw new Error("Cannot infer a GitHub repository from origin; pass --repo <owner/name>.");
|
|
1852
|
+
if (inferred && inferred.toLowerCase() !== repoName.toLowerCase())
|
|
1853
|
+
throw new Error("--repo must match the origin push repository.");
|
|
1854
|
+
const directory = getArgValue(argv, "--dir") ?? relative2(top, process.cwd());
|
|
1855
|
+
const definitionDir = relative2(top, resolve2(top, directory)).split("\\").join("/") || ".";
|
|
1856
|
+
if (definitionDir === ".." || definitionDir.startsWith("../"))
|
|
1857
|
+
throw new Error("--dir must be inside the repository.");
|
|
1858
|
+
const tree = workingTree(top);
|
|
1859
|
+
try {
|
|
1860
|
+
const yaml = git2(top, ["show", `${tree.tree}:${definitionFile(definitionDir)}`], tree.env);
|
|
1861
|
+
const { definition, issues } = validateDefinition(yaml, "github");
|
|
1862
|
+
if (!definition || issues.length)
|
|
1863
|
+
throw new Error(
|
|
1864
|
+
`Invalid sandbox definition:
|
|
1865
|
+
${issues.map((issue) => `${issue.path}: ${issue.message}`).join("\n")}`
|
|
1866
|
+
);
|
|
1867
|
+
const env = {
|
|
1868
|
+
...tree.env,
|
|
1869
|
+
GIT_AUTHOR_NAME: "Cofactor",
|
|
1870
|
+
GIT_AUTHOR_EMAIL: "sandbox@example.com",
|
|
1871
|
+
GIT_COMMITTER_NAME: "Cofactor",
|
|
1872
|
+
GIT_COMMITTER_EMAIL: "sandbox@example.com",
|
|
1873
|
+
GIT_AUTHOR_DATE: "2000-01-01T00:00:00Z",
|
|
1874
|
+
GIT_COMMITTER_DATE: "2000-01-01T00:00:00Z"
|
|
1875
|
+
};
|
|
1876
|
+
const version = git2(
|
|
1877
|
+
top,
|
|
1878
|
+
["commit-tree", "--no-gpg-sign", tree.tree, "-m", "Cofactor working tree"],
|
|
1879
|
+
env
|
|
1880
|
+
);
|
|
1881
|
+
const key = createHash3("sha256").update(JSON.stringify([top, repoName, application, apiUrl, version])).digest("hex");
|
|
1882
|
+
const file = join2(stateDir(), `${key}.json`);
|
|
1883
|
+
const previous = existsSync2(file) ? RecordSchema.parse(JSON.parse(readFileSync3(file, "utf8"))) : null;
|
|
1884
|
+
const record = {
|
|
1885
|
+
repo: top,
|
|
1886
|
+
repoName,
|
|
1887
|
+
remoteIdentity: remoteIdentity(remote),
|
|
1888
|
+
application,
|
|
1889
|
+
applicationId: previous?.applicationId ?? null,
|
|
1890
|
+
claims: previous?.claims ?? [],
|
|
1891
|
+
apiUrl,
|
|
1892
|
+
version,
|
|
1893
|
+
ref: `refs/cofactor/wip/${tree.tree}`,
|
|
1894
|
+
updatedAt: Date.now()
|
|
1895
|
+
};
|
|
1896
|
+
save(file, record);
|
|
1897
|
+
git2(top, ["push", "--porcelain", "origin", `${version}:${record.ref}`]);
|
|
1898
|
+
return { record, file, definitionDir };
|
|
1899
|
+
} finally {
|
|
1900
|
+
tree.cleanup();
|
|
1548
1901
|
}
|
|
1549
|
-
printApplicationHuman(res.data);
|
|
1550
1902
|
}
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
async function pollSandboxUntilReady(apiUrl, token, id, timeoutSeconds) {
|
|
1555
|
-
const deadline = Date.now() + timeoutSeconds * 1e3 + CLAIM_POLL_GRACE_MS;
|
|
1556
|
-
let toldUpgrading = false;
|
|
1557
|
-
while (Date.now() < deadline) {
|
|
1558
|
-
const res = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
|
|
1559
|
-
if (!res.ok) return { error: res.error ?? "unknown error" };
|
|
1560
|
-
if (res.data.status === "ready" || res.data.status === "failed") return { data: res.data };
|
|
1561
|
-
if (res.data.upgrading && !toldUpgrading) {
|
|
1562
|
-
toldUpgrading = true;
|
|
1563
|
-
console.error(" updating this push to the current sandbox platform (once; the claim continues after)...");
|
|
1564
|
-
}
|
|
1565
|
-
await sleep(CLAIM_POLL_INTERVAL_MS);
|
|
1566
|
-
}
|
|
1567
|
-
return { error: `timed out waiting for sandbox ${id} to become ready (${Math.round(timeoutSeconds / 60)} min)` };
|
|
1903
|
+
function rememberLocalApplication(source, applicationId) {
|
|
1904
|
+
source.record.applicationId = applicationId;
|
|
1905
|
+
save(source.file, source.record);
|
|
1568
1906
|
}
|
|
1569
|
-
|
|
1570
|
-
const
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
const
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
if (
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1907
|
+
function rememberLocalClaim(source, id) {
|
|
1908
|
+
const latest = RecordSchema.parse(JSON.parse(readFileSync3(source.file, "utf8")));
|
|
1909
|
+
source.record.claims = [.../* @__PURE__ */ new Set([...latest.claims, id])];
|
|
1910
|
+
save(source.file, source.record);
|
|
1911
|
+
}
|
|
1912
|
+
function trackedLocalSandbox(id, apiUrl) {
|
|
1913
|
+
const entry = records().find(
|
|
1914
|
+
({ record }) => record.apiUrl === apiUrl && record.claims.includes(id)
|
|
1915
|
+
);
|
|
1916
|
+
if (!entry || entry.record.applicationId === null) return null;
|
|
1917
|
+
return { applicationId: entry.record.applicationId, version: entry.record.version };
|
|
1918
|
+
}
|
|
1919
|
+
async function cleanupLocalRefs(apiUrl, token, applicationId, application, destroyed) {
|
|
1920
|
+
const candidates = records().filter(
|
|
1921
|
+
({ record }) => record.apiUrl === apiUrl && (record.applicationId === applicationId || record.applicationId === null && record.application === application) && (destroyed ? record.version === destroyed.version : Date.now() - record.updatedAt >= TTL_MS)
|
|
1922
|
+
);
|
|
1923
|
+
if (!candidates.length) return;
|
|
1924
|
+
const live = /* @__PURE__ */ new Set();
|
|
1925
|
+
let cursor = null;
|
|
1926
|
+
do {
|
|
1927
|
+
const query = new URLSearchParams({ status: "claiming,ready", limit: "100" });
|
|
1928
|
+
if (cursor) query.set("cursor", cursor);
|
|
1929
|
+
const page = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes?${query}`);
|
|
1930
|
+
if (!page.ok)
|
|
1931
|
+
throw new Error(page.error ?? "Could not check live claims; wip refs were retained.");
|
|
1932
|
+
for (const row of page.data) if (row.id !== destroyed?.id) live.add(row.version);
|
|
1933
|
+
cursor = page.nextCursor ?? null;
|
|
1934
|
+
} while (cursor);
|
|
1935
|
+
for (const { file, record } of candidates) {
|
|
1936
|
+
if (live.has(record.version)) continue;
|
|
1937
|
+
if (remoteIdentity(git2(record.repo, ["remote", "get-url", "--push", "origin"])) !== record.remoteIdentity) {
|
|
1938
|
+
throw new Error("The origin push repository changed; wip cleanup left it intact.");
|
|
1594
1939
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1940
|
+
const remoteSha = git2(record.repo, ["ls-remote", "--refs", "origin", record.ref]).split(
|
|
1941
|
+
/\s/
|
|
1942
|
+
)[0];
|
|
1943
|
+
if (remoteSha) {
|
|
1944
|
+
if (remoteSha !== record.version)
|
|
1945
|
+
throw new Error("Wip ref changed remotely; cleanup left it intact.");
|
|
1946
|
+
git2(record.repo, [
|
|
1947
|
+
"push",
|
|
1948
|
+
`--force-with-lease=${record.ref}:${record.version}`,
|
|
1949
|
+
"origin",
|
|
1950
|
+
`:${record.ref}`
|
|
1951
|
+
]);
|
|
1952
|
+
}
|
|
1953
|
+
rmSync2(file);
|
|
1609
1954
|
}
|
|
1610
|
-
if (data.status === "failed") process4.exit(1);
|
|
1611
1955
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1956
|
+
|
|
1957
|
+
// src/sandbox-v2.ts
|
|
1958
|
+
function pad2(s, n) {
|
|
1959
|
+
return s.padEnd(n);
|
|
1960
|
+
}
|
|
1961
|
+
function sleep(ms) {
|
|
1962
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
1963
|
+
}
|
|
1964
|
+
function indentLines(text, indent) {
|
|
1965
|
+
return text.split("\n").map((line) => `${indent}${line}`).join("\n");
|
|
1966
|
+
}
|
|
1967
|
+
function formatActionSignature(a) {
|
|
1968
|
+
const inputs = Object.keys(a.input).join(", ");
|
|
1969
|
+
const outputs = Object.keys(a.output).join(", ");
|
|
1970
|
+
return `${a.slug}(${inputs})${outputs ? ` \u2192 ${outputs}` : ""}`;
|
|
1971
|
+
}
|
|
1972
|
+
function jsonSchemaPropTypes(schema) {
|
|
1973
|
+
const properties = schema.properties ?? {};
|
|
1974
|
+
return Object.fromEntries(
|
|
1975
|
+
Object.entries(properties).map(([key, prop]) => [key, typeof prop.type === "string" ? prop.type : "string"])
|
|
1976
|
+
);
|
|
1977
|
+
}
|
|
1978
|
+
function formatOperationSignature(op) {
|
|
1979
|
+
const inputs = Object.keys(jsonSchemaPropTypes(op.inputSchema)).join(", ");
|
|
1980
|
+
const outputs = Object.keys(jsonSchemaPropTypes(op.outputSchema)).join(", ");
|
|
1981
|
+
return `${op.name}(${inputs})${outputs ? ` \u2192 ${outputs}` : ""}`;
|
|
1982
|
+
}
|
|
1983
|
+
function exitOnRefusal(res) {
|
|
1984
|
+
if (res.ok) return;
|
|
1985
|
+
console.error(`Error: ${res.error}`);
|
|
1986
|
+
if (res.code === "SANDBOX_V2_DEFINITION_INVALID") printIssues(res.issues, " ");
|
|
1987
|
+
process6.exit(1);
|
|
1988
|
+
}
|
|
1989
|
+
function printIssues(issues, indent) {
|
|
1990
|
+
for (const issue of issues ?? []) {
|
|
1991
|
+
console.log(`${indent}${issue.path || "(root)"}: ${issue.message}`);
|
|
1627
1992
|
}
|
|
1628
|
-
printSandbox(res.data);
|
|
1629
1993
|
}
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
"
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
if (!res.ok) {
|
|
1647
|
-
console.error(`Error: ${res.error ?? "could not list sandboxes"}`);
|
|
1648
|
-
process4.exit(1);
|
|
1994
|
+
function shortVersion(version) {
|
|
1995
|
+
return /^[0-9a-f]{40}$/.test(version) ? version.slice(0, 12) : version;
|
|
1996
|
+
}
|
|
1997
|
+
function printApplicationHuman(app) {
|
|
1998
|
+
console.log(
|
|
1999
|
+
app.delivery === "github" ? `Delivered from GitHub: ${app.repo}${app.definitionDir && app.definitionDir !== "." ? `:${app.definitionDir}` : ""}` : "Delivered as pushed images"
|
|
2000
|
+
);
|
|
2001
|
+
console.log(
|
|
2002
|
+
` version ${app.version ? shortVersion(app.version) : app.delivery === "github" ? "(no definition on the default branch yet; use --at <sha>)" : "(none yet: run `cofactor sandbox push`)"}`
|
|
2003
|
+
);
|
|
2004
|
+
if (app.pushes && app.pushes.length > 0) {
|
|
2005
|
+
console.log(" pushes:");
|
|
2006
|
+
for (const push2 of app.pushes) {
|
|
2007
|
+
const tags = push2.tags.length > 0 ? ` [${push2.tags.join(", ")}]` : "";
|
|
2008
|
+
console.log(` ${push2.id} ${pad2(push2.status, 9)} ${push2.createdAt}${tags}${push2.error ? `
|
|
2009
|
+
${push2.error}` : ""}`);
|
|
1649
2010
|
}
|
|
1650
|
-
claims.push(...res.data);
|
|
1651
|
-
cursor = res.nextCursor ?? null;
|
|
1652
|
-
} while (cursor);
|
|
1653
|
-
if (json) {
|
|
1654
|
-
writeRawOutput(JSON.stringify(claims, null, 2));
|
|
1655
|
-
return;
|
|
1656
|
-
}
|
|
1657
|
-
if (claims.length === 0) {
|
|
1658
|
-
console.log("No live claims.");
|
|
1659
|
-
return;
|
|
1660
2011
|
}
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
console.log(
|
|
1665
|
-
`${pad2(c.id, 38)} ${pad2(names.get(c.applicationId) ?? c.applicationId, 24)} ${pad2(c.environment, 14)} ${pad2(c.status, 10)} ${pad2(c.createdAt, 26)} ${c.leaseExpiresAt ?? "-"}`
|
|
1666
|
-
);
|
|
2012
|
+
if (app.sandboxEnvironments.length > 0) {
|
|
2013
|
+
console.log(" sandbox environments:");
|
|
2014
|
+
for (const env of app.sandboxEnvironments) console.log(` ${pad2(env.environment, 16)} ${env.name}`);
|
|
1667
2015
|
}
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
case "number": {
|
|
1677
|
-
const value = Number(raw);
|
|
1678
|
-
if (Number.isNaN(value)) throw new Error(`must be a number, got "${raw}"`);
|
|
1679
|
-
return value;
|
|
1680
|
-
}
|
|
1681
|
-
case "boolean": {
|
|
1682
|
-
if (raw === "true") return true;
|
|
1683
|
-
if (raw === "false") return false;
|
|
1684
|
-
throw new Error(`must be "true" or "false", got "${raw}"`);
|
|
2016
|
+
if (app.heads && app.heads.length > 0) {
|
|
2017
|
+
console.log(" heads (what flow runs and unpinned claims use; a warm environment switches after its warm-up; update with `cofactor sandbox refresh`):");
|
|
2018
|
+
for (const head of app.heads) {
|
|
2019
|
+
const version = head.version ? shortVersion(head.version) : "(none)";
|
|
2020
|
+
const built = head.builtAt ? `, built ${head.builtAt}` : "";
|
|
2021
|
+
const behind = head.behindMain && head.mainHead ? ` main has moved: now ${shortVersion(head.mainHead)}` : "";
|
|
2022
|
+
console.log(` ${pad2(head.environment, 16)} ${version} (${head.status}${built})${behind}`);
|
|
2023
|
+
if (head.error) console.log(` ${head.error}`);
|
|
1685
2024
|
}
|
|
1686
|
-
default:
|
|
1687
|
-
return raw;
|
|
1688
2025
|
}
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
const
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
if (
|
|
1696
|
-
console.
|
|
1697
|
-
|
|
2026
|
+
const def = app.definition;
|
|
2027
|
+
if (!def) return;
|
|
2028
|
+
console.log(" Environments:");
|
|
2029
|
+
for (const env of def.environments) {
|
|
2030
|
+
console.log(` ${env.name}${env.extends ? ` (extends ${env.extends})` : ""}`);
|
|
2031
|
+
console.log(indentLines(env.description, " "));
|
|
2032
|
+
if (env.seeds.length > 0) {
|
|
2033
|
+
console.log(" seeds:");
|
|
2034
|
+
for (const step of env.seeds) console.log(` ${step.id}: ${step.action}`);
|
|
1698
2035
|
}
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
2036
|
+
if (env.envKeys.length > 0) console.log(` env: ${env.envKeys.join(", ")}`);
|
|
2037
|
+
}
|
|
2038
|
+
if (def.actions.length > 0) {
|
|
2039
|
+
console.log(" Actions:");
|
|
2040
|
+
for (const a of def.actions) {
|
|
2041
|
+
console.log(` ${formatActionSignature(a)}`);
|
|
2042
|
+
console.log(` ${a.description}`);
|
|
1705
2043
|
}
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
2044
|
+
}
|
|
2045
|
+
if (def.operations.length > 0) {
|
|
2046
|
+
console.log(" Operations:");
|
|
2047
|
+
for (const op of def.operations) {
|
|
2048
|
+
console.log(` ${formatOperationSignature(op)}`);
|
|
2049
|
+
console.log(` ${op.description}`);
|
|
1711
2050
|
}
|
|
1712
2051
|
}
|
|
1713
|
-
|
|
2052
|
+
console.log(` Personas: ${def.personas.join(", ") || "(none)"}`);
|
|
2053
|
+
console.log(` Ports: ${def.ports.map((p) => `${p.name} (${p.access})`).join(", ") || "(none)"}`);
|
|
1714
2054
|
}
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
2055
|
+
function printSandbox(data) {
|
|
2056
|
+
console.log(`Sandbox ${data.id}: ${data.status}${data.expired ? " (expired)" : ""}`);
|
|
2057
|
+
console.log(` environment ${data.environment}`);
|
|
2058
|
+
console.log(` version ${shortVersion(data.version)}`);
|
|
2059
|
+
if (data.error) console.log(` error ${data.error}`);
|
|
2060
|
+
if (data.readyMs !== null) {
|
|
2061
|
+
const seconds = (ms) => `${(ms / 1e3).toFixed(1)}s`;
|
|
2062
|
+
const phases = data.phases ? ` (snapshot ${seconds(data.phases.snapshotMs)}, restore ${seconds(data.phases.restoreMs)}, prepare ${seconds(data.phases.prepareMs)})` : "";
|
|
2063
|
+
console.log(` ready in ${seconds(data.readyMs)}${phases}`);
|
|
2064
|
+
}
|
|
2065
|
+
if (data.snapshot?.buildPhases) {
|
|
2066
|
+
const { buildMs, prepareMs, seeds } = data.snapshot.buildPhases;
|
|
2067
|
+
const seedsMs = seeds.reduce((total, step) => total + step.ms, 0);
|
|
2068
|
+
console.log(
|
|
2069
|
+
` snapshot built ${data.snapshot.builtAt} (build ${(buildMs / 1e3).toFixed(1)}s, prepare ${(prepareMs / 1e3).toFixed(1)}s, ${seeds.length} seeds ${(seedsMs / 1e3).toFixed(1)}s)`
|
|
2070
|
+
);
|
|
2071
|
+
for (const step of seeds) console.log(` ${step.id} ${step.action} ${(step.ms / 1e3).toFixed(1)}s`);
|
|
2072
|
+
const ran = data.snapshot.buildPhases.build?.steps.filter((step) => step.ran) ?? [];
|
|
2073
|
+
if (ran.length > 0) {
|
|
2074
|
+
console.log(` build steps: ${ran.map((step) => `${step.name} ${(step.ms / 1e3).toFixed(1)}s`).join(", ")}`);
|
|
1722
2075
|
}
|
|
1723
|
-
if (pollRes.data.status !== "running") return pollRes.data;
|
|
1724
|
-
await sleep(2e3);
|
|
1725
2076
|
}
|
|
1726
|
-
console.
|
|
1727
|
-
|
|
1728
|
-
}
|
|
1729
|
-
async function handleSandboxRun(argv, apiUrl, token) {
|
|
1730
|
-
const positional = argv.filter((a) => !a.startsWith("-"));
|
|
1731
|
-
const [id, actionRef] = positional;
|
|
1732
|
-
if (!id || !actionRef) {
|
|
1733
|
-
console.error("Usage: cofactor sandbox run <id> <action|plugin.op> [--input k=v ...] [--json-input '{...}'] [--json]");
|
|
1734
|
-
process4.exit(1);
|
|
2077
|
+
if (data.leaseExpiresAt) console.log(` expires ${data.leaseExpiresAt}`);
|
|
2078
|
+
if (data.synced) {
|
|
2079
|
+
writeRawOutput(` synced ${data.synced.buildOutput ? "build output" : "tree"} ${data.synced.tree} at ${data.synced.at}`);
|
|
1735
2080
|
}
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
console.error(`Error: ${showRes.error}`);
|
|
1741
|
-
process4.exit(1);
|
|
2081
|
+
if (Object.keys(data.portUrls).length > 0) {
|
|
2082
|
+
console.log("");
|
|
2083
|
+
console.log(" URLs:");
|
|
2084
|
+
for (const [name, url] of Object.entries(data.portUrls)) writeRawOutput(` ${pad2(name, 10)} ${url}`);
|
|
1742
2085
|
}
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
2086
|
+
if (data.personas.length > 0) {
|
|
2087
|
+
console.log("");
|
|
2088
|
+
console.log(" Logins:");
|
|
2089
|
+
for (const p of data.personas) writeRawOutput(` ${pad2(p.name, 14)} ${p.email} ${p.password}`);
|
|
2090
|
+
if (data.nativeLogin !== void 0) {
|
|
2091
|
+
console.log(
|
|
2092
|
+
data.nativeLogin ? " (native Login nodes sign these in via authentication.run)" : " (no authentication.run: native Login nodes fail with script_not_declared)"
|
|
2093
|
+
);
|
|
2094
|
+
}
|
|
1751
2095
|
}
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
2096
|
+
if (data.signInUrl) {
|
|
2097
|
+
console.log("");
|
|
2098
|
+
writeRawOutput(` Open sign-in: ${data.signInUrl}`);
|
|
2099
|
+
}
|
|
2100
|
+
const seedIds = Object.keys(data.seedOutputs);
|
|
2101
|
+
if (seedIds.length > 0) {
|
|
2102
|
+
console.log("");
|
|
2103
|
+
console.log(" Seed outputs:");
|
|
2104
|
+
for (const id of seedIds) {
|
|
2105
|
+
for (const [key, value] of Object.entries(data.seedOutputs[id])) {
|
|
2106
|
+
writeRawOutput(` ${id}.${key}: ${JSON.stringify(value)}`);
|
|
2107
|
+
}
|
|
1759
2108
|
}
|
|
1760
|
-
} else if (repoAction) {
|
|
1761
|
-
const propTypes = Object.fromEntries(Object.entries(repoAction.input).map(([key, prop]) => [key, prop.type]));
|
|
1762
|
-
input = buildRunInputFromFlags(argv, propTypes);
|
|
1763
|
-
} else {
|
|
1764
|
-
input = buildRunInputFromFlags(argv, jsonSchemaPropTypes(operation.inputSchema));
|
|
1765
2109
|
}
|
|
1766
|
-
|
|
2110
|
+
if (data.actions.length > 0) {
|
|
2111
|
+
console.log("");
|
|
2112
|
+
console.log(" Actions:");
|
|
2113
|
+
for (const a of data.actions) console.log(` ${formatActionSignature(a)}`);
|
|
2114
|
+
}
|
|
2115
|
+
if (data.operations.length > 0) {
|
|
2116
|
+
console.log("");
|
|
2117
|
+
console.log(" Operations:");
|
|
2118
|
+
for (const op of data.operations) console.log(` ${formatOperationSignature(op)}`);
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
function handleSandboxValidate(argv) {
|
|
2122
|
+
const file = argv.find((a) => !a.startsWith("-")) ?? "./cofactor.sandbox.yaml";
|
|
2123
|
+
const json = hasFlag(argv, "--json");
|
|
2124
|
+
const delivery = hasFlag(argv, "--upload") ? "upload" : "github";
|
|
2125
|
+
let yamlText;
|
|
2126
|
+
try {
|
|
2127
|
+
yamlText = readFileSync4(file, "utf-8");
|
|
2128
|
+
} catch (error) {
|
|
2129
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2130
|
+
if (json) {
|
|
2131
|
+
writeRawOutput(JSON.stringify({ ok: false, issues: [{ path: "", message }] }, null, 2));
|
|
2132
|
+
} else {
|
|
2133
|
+
console.error(`Error reading "${file}": ${message}`);
|
|
2134
|
+
}
|
|
2135
|
+
process6.exit(1);
|
|
2136
|
+
}
|
|
2137
|
+
const { definition, issues } = validateDefinition(yamlText, delivery);
|
|
2138
|
+
if (json) {
|
|
2139
|
+
writeRawOutput(JSON.stringify({ ok: issues.length === 0, issues }, null, 2));
|
|
2140
|
+
process6.exit(issues.length === 0 ? 0 : 1);
|
|
2141
|
+
}
|
|
2142
|
+
if (issues.length > 0) {
|
|
2143
|
+
for (const issue of issues) console.error(`${issue.path || "(root)"}: ${issue.message}`);
|
|
2144
|
+
process6.exit(1);
|
|
2145
|
+
}
|
|
2146
|
+
const environmentCount = Object.keys(definition.environments).length;
|
|
2147
|
+
const actionCount = Object.keys(definition.actions).length;
|
|
2148
|
+
const operationCount = Object.keys(definition.emulation).reduce(
|
|
2149
|
+
(sum, pluginId) => sum + pluginOperations(pluginId).length,
|
|
2150
|
+
0
|
|
2151
|
+
);
|
|
2152
|
+
console.log(`\u2713 ${file} is valid`);
|
|
2153
|
+
console.log(` ${environmentCount} environment(s), ${actionCount} action(s), ${operationCount} operation(s)`);
|
|
2154
|
+
}
|
|
2155
|
+
async function fetchApplicationSandbox(apiUrl, token, applicationId, at) {
|
|
2156
|
+
const res = await apiRequest(
|
|
1767
2157
|
apiUrl,
|
|
1768
2158
|
token,
|
|
1769
|
-
"
|
|
1770
|
-
`/org/
|
|
1771
|
-
{ action: actionRef, input }
|
|
2159
|
+
"GET",
|
|
2160
|
+
`/org/applications/${applicationId}/sandbox${at ? `?at=${encodeURIComponent(at)}` : ""}`
|
|
1772
2161
|
);
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
2162
|
+
exitOnRefusal(res);
|
|
2163
|
+
return res.data;
|
|
2164
|
+
}
|
|
2165
|
+
var V2_ARGS = {
|
|
2166
|
+
envs: { values: ["--application", "--property"], flags: ["--json"], positionals: 0 },
|
|
2167
|
+
refresh: {
|
|
2168
|
+
values: ["--application", "--property", "--environment"],
|
|
2169
|
+
flags: ["--wait", "--json"],
|
|
2170
|
+
positionals: 0
|
|
2171
|
+
},
|
|
2172
|
+
connect: {
|
|
2173
|
+
values: ["--application", "--property", "--repo", "--dir", "--at"],
|
|
2174
|
+
flags: ["--json"],
|
|
2175
|
+
positionals: 0
|
|
2176
|
+
},
|
|
2177
|
+
push: {
|
|
2178
|
+
values: ["--application", "--property", "--definition", "--tag"],
|
|
2179
|
+
flags: ["--wait", "--json"],
|
|
2180
|
+
positionals: 1
|
|
2181
|
+
},
|
|
2182
|
+
claim: {
|
|
2183
|
+
values: ["--application", "--property", "--environment", "--at", "--timeout", "--repo", "--dir"],
|
|
2184
|
+
flags: ["--wait", "--json", "--from-local"],
|
|
2185
|
+
positionals: 0
|
|
2186
|
+
},
|
|
2187
|
+
show: { values: [], flags: ["--json"], positionals: 1 },
|
|
2188
|
+
claims: { values: ["--application", "--property"], flags: ["--json"], positionals: 0 },
|
|
2189
|
+
run: { values: ["--input", "--json-input"], flags: ["--json"], positionals: 2 },
|
|
2190
|
+
exec: { values: ["--timeout"], flags: ["--json"], positionals: 1 },
|
|
2191
|
+
sync: { values: ["--to"], flags: ["--build", "--json"], positionals: 2 },
|
|
2192
|
+
"auth-check": { values: ["--persona"], flags: ["--json"], positionals: 1 },
|
|
2193
|
+
"verify-env": {
|
|
2194
|
+
values: ["--application", "--property", "--environment", "--at"],
|
|
2195
|
+
flags: ["--json"],
|
|
2196
|
+
positionals: 0
|
|
2197
|
+
},
|
|
2198
|
+
destroy: { values: [], flags: [], positionals: 1 },
|
|
2199
|
+
secret: { values: ["--application", "--property", "--environment"], flags: ["--json"], positionals: 2 }
|
|
2200
|
+
};
|
|
2201
|
+
function rejectUnknownSandboxV2Args(command, argv) {
|
|
2202
|
+
const spec = V2_ARGS[command];
|
|
2203
|
+
if (!spec) return;
|
|
2204
|
+
let positionals = 0;
|
|
2205
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2206
|
+
const arg = argv[i];
|
|
2207
|
+
if (spec.values.includes(arg)) {
|
|
2208
|
+
const value = argv[i + 1];
|
|
2209
|
+
if (!value || value.startsWith("--")) {
|
|
2210
|
+
console.error(`Error: ${arg} requires a value. See \`cofactor sandbox --help\`.`);
|
|
2211
|
+
process6.exit(1);
|
|
1779
2212
|
}
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
writeRawOutput(JSON.stringify(repoAction ? repoAction.input : operation.inputSchema, null, 2));
|
|
1783
|
-
} else {
|
|
1784
|
-
console.error(`Error: ${enqueueRes.error}`);
|
|
2213
|
+
i++;
|
|
2214
|
+
continue;
|
|
1785
2215
|
}
|
|
1786
|
-
|
|
2216
|
+
if (["--input", "--tag"].some((value) => spec.values.includes(value) && arg.startsWith(`${value}=`))) continue;
|
|
2217
|
+
if (spec.flags.includes(arg) || arg === "--debug") continue;
|
|
2218
|
+
if (!arg.startsWith("-") && ++positionals <= spec.positionals) continue;
|
|
2219
|
+
if (command === "secret" && !arg.startsWith("-")) {
|
|
2220
|
+
console.error(
|
|
2221
|
+
"Error: `cofactor sandbox secret` takes no value argument: pipe the value on stdin. If you typed a real key on the command line, rotate it."
|
|
2222
|
+
);
|
|
2223
|
+
process6.exit(1);
|
|
2224
|
+
}
|
|
2225
|
+
console.error(`Error: \`cofactor sandbox ${command}\` does not take ${JSON.stringify(arg)}. See \`cofactor sandbox --help\`.`);
|
|
2226
|
+
process6.exit(1);
|
|
1787
2227
|
}
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
2228
|
+
}
|
|
2229
|
+
function v2Positionals(command, argv) {
|
|
2230
|
+
const values = V2_ARGS[command]?.values ?? [];
|
|
2231
|
+
const result = [];
|
|
2232
|
+
for (let i = 0; i < argv.length; i++) {
|
|
2233
|
+
const arg = argv[i];
|
|
2234
|
+
if (values.includes(arg)) i++;
|
|
2235
|
+
else if (!arg.startsWith("-")) result.push(arg);
|
|
2236
|
+
}
|
|
2237
|
+
return result;
|
|
2238
|
+
}
|
|
2239
|
+
async function handleSandboxEnvs(argv, apiUrl, token) {
|
|
2240
|
+
const applicationId = await resolveApplicationId(argv, apiUrl, token);
|
|
2241
|
+
const app = await fetchApplicationSandbox(apiUrl, token, applicationId);
|
|
2242
|
+
if (hasFlag(argv, "--json")) {
|
|
2243
|
+
writeRawOutput(JSON.stringify(app, null, 2));
|
|
1793
2244
|
return;
|
|
1794
2245
|
}
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
process4.exit(1);
|
|
2246
|
+
printApplicationHuman(app);
|
|
2247
|
+
}
|
|
2248
|
+
async function connectSandboxApplication(argv, apiUrl, token) {
|
|
2249
|
+
const repo = getArgValue(argv, "--repo");
|
|
2250
|
+
if (!repo) {
|
|
2251
|
+
console.error("Usage: cofactor sandbox connect --application <name|id> --repo <owner/name> [--dir <path>] [--json]");
|
|
2252
|
+
process6.exit(1);
|
|
1803
2253
|
}
|
|
1804
|
-
|
|
2254
|
+
const body = {
|
|
2255
|
+
repo,
|
|
2256
|
+
definitionDir: getArgValue(argv, "--dir") ?? ".",
|
|
2257
|
+
at: getArgValue(argv, "--at")
|
|
2258
|
+
};
|
|
2259
|
+
const checkCanConnect = async () => {
|
|
2260
|
+
exitOnRefusal(await apiRequest(apiUrl, token, "POST", "/org/sandboxes-v2/connect-check", body));
|
|
2261
|
+
};
|
|
2262
|
+
const applicationId = await resolveOrCreateSandboxApplication(argv, apiUrl, token, checkCanConnect);
|
|
2263
|
+
const res = await apiRequest(apiUrl, token, "PUT", `/org/applications/${applicationId}/sandbox`, body);
|
|
2264
|
+
exitOnRefusal(res);
|
|
2265
|
+
return res.data;
|
|
1805
2266
|
}
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
2267
|
+
async function handleSandboxConnect(argv, apiUrl, token) {
|
|
2268
|
+
const app = await connectSandboxApplication(argv, apiUrl, token);
|
|
2269
|
+
if (hasFlag(argv, "--json")) writeRawOutput(JSON.stringify(app, null, 2));
|
|
2270
|
+
else printApplicationHuman(app);
|
|
2271
|
+
}
|
|
2272
|
+
var CLAIM_POLL_INTERVAL_MS = 3e3;
|
|
2273
|
+
var CLAIM_TIMEOUT_SECONDS = 2 * 3600;
|
|
2274
|
+
var CLAIM_POLL_GRACE_MS = 2 * 60 * 1e3;
|
|
2275
|
+
async function pollSandboxUntilReady(apiUrl, token, id, timeoutSeconds) {
|
|
2276
|
+
const deadline = Date.now() + timeoutSeconds * 1e3 + CLAIM_POLL_GRACE_MS;
|
|
2277
|
+
let toldUpgrading = false;
|
|
2278
|
+
while (Date.now() < deadline) {
|
|
2279
|
+
const res = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
|
|
2280
|
+
if (!res.ok) return { error: res.error ?? "unknown error" };
|
|
2281
|
+
if (res.data.status === "ready" || res.data.status === "failed") return { data: res.data };
|
|
2282
|
+
if (res.data.upgrading && !toldUpgrading) {
|
|
2283
|
+
toldUpgrading = true;
|
|
2284
|
+
console.error(" updating this push to the current sandbox platform (once; the claim continues after)...");
|
|
1818
2285
|
}
|
|
2286
|
+
await sleep(CLAIM_POLL_INTERVAL_MS);
|
|
1819
2287
|
}
|
|
1820
|
-
return {
|
|
1821
|
-
}
|
|
1822
|
-
function execExitCode(run) {
|
|
1823
|
-
if (run.status === "succeeded") return 0;
|
|
1824
|
-
return run.exitCode ?? 1;
|
|
2288
|
+
return { error: `timed out waiting for sandbox ${id} to become ready (${Math.round(timeoutSeconds / 60)} min)` };
|
|
1825
2289
|
}
|
|
1826
|
-
async function
|
|
1827
|
-
const
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
2290
|
+
async function handleSandboxClaim(argv, apiUrl, token) {
|
|
2291
|
+
const timeout = parseDurationSeconds(getArgValue(argv, "--timeout"), "--timeout");
|
|
2292
|
+
const fromLocal = hasFlag(argv, "--from-local");
|
|
2293
|
+
if (fromLocal && hasFlag(argv, "--at")) throw new Error("--from-local and --at are mutually exclusive.");
|
|
2294
|
+
if (!fromLocal && (hasFlag(argv, "--repo") || hasFlag(argv, "--dir"))) throw new Error("--repo and --dir on claim require --from-local.");
|
|
2295
|
+
const source = fromLocal ? publishLocalSource(argv, apiUrl) : null;
|
|
2296
|
+
const applicationId = source ? (await connectSandboxApplication(["--application", source.record.application, "--repo", source.record.repoName, "--dir", source.definitionDir, "--at", source.record.version], apiUrl, token)).applicationId : await resolveApplicationId(argv, apiUrl, token);
|
|
2297
|
+
if (source) {
|
|
2298
|
+
rememberLocalApplication(source, applicationId);
|
|
2299
|
+
await cleanupLocalRefs(apiUrl, token, applicationId, source.record.application);
|
|
1831
2300
|
}
|
|
1832
|
-
const
|
|
1833
|
-
const
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
);
|
|
1840
|
-
if (
|
|
1841
|
-
|
|
1842
|
-
|
|
2301
|
+
const environment2 = getArgValue(argv, "--environment") ?? "default";
|
|
2302
|
+
const version = source?.record.version ?? getArgValue(argv, "--at");
|
|
2303
|
+
const timeoutSeconds = timeout ?? CLAIM_TIMEOUT_SECONDS;
|
|
2304
|
+
const wait = hasFlag(argv, "--wait");
|
|
2305
|
+
const json = hasFlag(argv, "--json");
|
|
2306
|
+
const body = { applicationId, environment: environment2 };
|
|
2307
|
+
if (version) body.version = version;
|
|
2308
|
+
if (timeout !== void 0) body.timeoutSeconds = timeout;
|
|
2309
|
+
if (wait) body.wait = true;
|
|
2310
|
+
const res = await apiRequest(apiUrl, token, "POST", "/org/sandboxes-v2/sandboxes", body);
|
|
2311
|
+
if (!res.ok) {
|
|
2312
|
+
console.error(`Error: ${res.error}`);
|
|
2313
|
+
if (res.code === "SANDBOX_V2_DEFINITION_INVALID") printIssues(res.issues, " ");
|
|
2314
|
+
process6.exit(1);
|
|
1843
2315
|
}
|
|
1844
|
-
const
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
2316
|
+
const id = res.data.id;
|
|
2317
|
+
if (source) rememberLocalClaim(source, id);
|
|
2318
|
+
if (source && wait && !json) console.log(`Source commit: ${version}`);
|
|
2319
|
+
if (!wait) {
|
|
2320
|
+
if (json) {
|
|
2321
|
+
writeRawOutput(JSON.stringify(source ? { id, version } : { id }, null, 2));
|
|
2322
|
+
} else {
|
|
2323
|
+
console.log(`Sandbox claiming: ${id}`);
|
|
2324
|
+
if (source) console.log(` Source commit: ${version}`);
|
|
2325
|
+
console.log(` Poll with: cofactor sandbox show ${id}`);
|
|
2326
|
+
}
|
|
2327
|
+
return;
|
|
1856
2328
|
}
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
process4.exit(1);
|
|
2329
|
+
const settled = "status" in res.data && (res.data.status === "ready" || res.data.status === "failed") ? res.data : null;
|
|
2330
|
+
if (!json && !settled) console.log(`Claiming sandbox ${id}...`);
|
|
2331
|
+
const waited = settled ? { data: settled } : await pollSandboxUntilReady(apiUrl, token, id, timeoutSeconds);
|
|
2332
|
+
if ("error" in waited) {
|
|
2333
|
+
console.error(`Error: ${waited.error}`);
|
|
2334
|
+
process6.exit(1);
|
|
1864
2335
|
}
|
|
1865
|
-
|
|
1866
|
-
if (
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
2336
|
+
const { data } = waited;
|
|
2337
|
+
if (json) {
|
|
2338
|
+
writeRawOutput(JSON.stringify(data, null, 2));
|
|
2339
|
+
} else {
|
|
2340
|
+
printSandbox(data);
|
|
2341
|
+
}
|
|
2342
|
+
if (data.status === "failed") process6.exit(1);
|
|
1871
2343
|
}
|
|
1872
|
-
async function
|
|
2344
|
+
async function handleSandboxShow(argv, apiUrl, token) {
|
|
1873
2345
|
const [id] = argv.filter((a) => !a.startsWith("-"));
|
|
1874
2346
|
if (!id) {
|
|
1875
|
-
console.error("Usage: cofactor sandbox
|
|
1876
|
-
|
|
2347
|
+
console.error("Usage: cofactor sandbox show <id> [--json]");
|
|
2348
|
+
process6.exit(1);
|
|
1877
2349
|
}
|
|
1878
|
-
const
|
|
1879
|
-
|
|
1880
|
-
token,
|
|
1881
|
-
"DELETE",
|
|
1882
|
-
`/org/sandboxes-v2/sandboxes/${id}`
|
|
1883
|
-
);
|
|
2350
|
+
const json = hasFlag(argv, "--json");
|
|
2351
|
+
const res = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
|
|
1884
2352
|
if (!res.ok) {
|
|
1885
2353
|
console.error(`Error: ${res.error}`);
|
|
1886
|
-
|
|
2354
|
+
process6.exit(1);
|
|
1887
2355
|
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
const applicationId = await resolveApplicationId(argv, apiUrl, token);
|
|
1892
|
-
const onlyEnvironment = getArgValue(argv, "--environment");
|
|
1893
|
-
const json = hasFlag(argv, "--json");
|
|
1894
|
-
const log = (line) => json ? console.error(line) : console.log(line);
|
|
1895
|
-
const app = await fetchApplicationSandbox(apiUrl, token, applicationId);
|
|
1896
|
-
if (!app.version || !app.definition) {
|
|
1897
|
-
console.error("Error: this application has no version to verify yet: run `cofactor sandbox push` first.");
|
|
1898
|
-
process4.exit(1);
|
|
1899
|
-
}
|
|
1900
|
-
const version = app.version;
|
|
1901
|
-
const allEnvironments = app.definition.environments.map((e) => e.name);
|
|
1902
|
-
if (onlyEnvironment && !allEnvironments.includes(onlyEnvironment)) {
|
|
1903
|
-
console.error(`Error: environment "${onlyEnvironment}" not found. Known: ${allEnvironments.join(", ")}`);
|
|
1904
|
-
process4.exit(1);
|
|
2356
|
+
if (json) {
|
|
2357
|
+
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
2358
|
+
return;
|
|
1905
2359
|
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
2360
|
+
printSandbox(res.data);
|
|
2361
|
+
}
|
|
2362
|
+
async function handleSandboxClaims(argv, apiUrl, token) {
|
|
2363
|
+
const json = hasFlag(argv, "--json");
|
|
2364
|
+
const applicationArg = getArgValue(argv, "--application") ?? getArgValue(argv, "--property");
|
|
2365
|
+
const applicationId = applicationArg ? await resolveApplicationId(argv, apiUrl, token) : void 0;
|
|
2366
|
+
const claims = [];
|
|
2367
|
+
let cursor = null;
|
|
2368
|
+
do {
|
|
2369
|
+
const query = new URLSearchParams({ mine: "true", status: "claiming,ready", limit: "100" });
|
|
2370
|
+
if (applicationId) query.set("applicationId", applicationId);
|
|
2371
|
+
if (cursor) query.set("cursor", cursor);
|
|
2372
|
+
const res = await apiRequest(
|
|
1913
2373
|
apiUrl,
|
|
1914
2374
|
token,
|
|
1915
|
-
"
|
|
1916
|
-
|
|
1917
|
-
{ applicationId, environment: environment2, version }
|
|
2375
|
+
"GET",
|
|
2376
|
+
`/org/sandboxes-v2/sandboxes?${query}`
|
|
1918
2377
|
);
|
|
1919
|
-
if (!
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
results.push({ environment: environment2, ok: false, seconds: seconds2, error: claimRes.error });
|
|
1923
|
-
continue;
|
|
1924
|
-
}
|
|
1925
|
-
const id = claimRes.data.id;
|
|
1926
|
-
log(` claimed ${id}, waiting...`);
|
|
1927
|
-
const waited = await pollSandboxUntilReady(apiUrl, token, id, CLAIM_TIMEOUT_SECONDS);
|
|
1928
|
-
const seconds = (Date.now() - started) / 1e3;
|
|
1929
|
-
if ("error" in waited || waited.data.status !== "ready") {
|
|
1930
|
-
const error = "error" in waited ? waited.error : waited.data.error ?? "failed";
|
|
1931
|
-
console.error(` failed: ${error}`);
|
|
1932
|
-
results.push({ environment: environment2, ok: false, seconds, error });
|
|
1933
|
-
} else {
|
|
1934
|
-
const { data } = waited;
|
|
1935
|
-
log(` ready in ${data.readyMs !== null ? (data.readyMs / 1e3).toFixed(1) : "?"}s`);
|
|
1936
|
-
for (const [stepId, outputs] of Object.entries(data.seedOutputs)) {
|
|
1937
|
-
for (const [key, value] of Object.entries(outputs)) {
|
|
1938
|
-
log(` ${stepId}.${key}: ${JSON.stringify(value)}`);
|
|
1939
|
-
}
|
|
1940
|
-
}
|
|
1941
|
-
const personas = [];
|
|
1942
|
-
if (data.nativeLogin) {
|
|
1943
|
-
for (const { name } of data.personas) {
|
|
1944
|
-
const checked = await apiRequest(
|
|
1945
|
-
apiUrl,
|
|
1946
|
-
token,
|
|
1947
|
-
"POST",
|
|
1948
|
-
`/org/sandboxes-v2/sandboxes/${id}/auth-check`,
|
|
1949
|
-
{ persona: name },
|
|
1950
|
-
// The API waits on the script (up to 60 s) and the sandbox connection.
|
|
1951
|
-
{ timeoutMs: 9e4 }
|
|
1952
|
-
);
|
|
1953
|
-
const check = checked.ok ? checked.data : { persona: name, ok: false, error: checked.error ?? "unknown error" };
|
|
1954
|
-
personas.push(check);
|
|
1955
|
-
if (check.ok) log(` persona ${name}: signed in`);
|
|
1956
|
-
else console.error(` persona ${name}: FAILED
|
|
1957
|
-
${indentLines(check.error ?? "", " ")}`);
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
const failedPersonas = personas.filter((p) => !p.ok).map((p) => p.persona);
|
|
1961
|
-
results.push({
|
|
1962
|
-
environment: environment2,
|
|
1963
|
-
ok: failedPersonas.length === 0,
|
|
1964
|
-
seconds,
|
|
1965
|
-
...failedPersonas.length > 0 ? { error: `persona sign-in failed: ${failedPersonas.join(", ")}` } : {},
|
|
1966
|
-
...data.nativeLogin ? { personas } : {}
|
|
1967
|
-
});
|
|
2378
|
+
if (!res.ok) {
|
|
2379
|
+
console.error(`Error: ${res.error ?? "could not list sandboxes"}`);
|
|
2380
|
+
process6.exit(1);
|
|
1968
2381
|
}
|
|
1969
|
-
|
|
1970
|
-
|
|
2382
|
+
claims.push(...res.data);
|
|
2383
|
+
cursor = res.nextCursor ?? null;
|
|
2384
|
+
} while (cursor);
|
|
2385
|
+
if (json) {
|
|
2386
|
+
writeRawOutput(JSON.stringify(claims, null, 2));
|
|
2387
|
+
return;
|
|
1971
2388
|
}
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
for (const r of results) {
|
|
1976
|
-
const errSuffix = r.error ? ` (${r.error})` : "";
|
|
1977
|
-
log(` ${pad2(r.environment, 20)} ${pad2(r.ok ? "OK" : "FAIL", 6)} ${r.seconds.toFixed(1)}${errSuffix}`);
|
|
2389
|
+
if (claims.length === 0) {
|
|
2390
|
+
console.log("No live claims.");
|
|
2391
|
+
return;
|
|
1978
2392
|
}
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
1986
|
-
import { createHash as createHash3 } from "crypto";
|
|
1987
|
-
import { createReadStream, createWriteStream, mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, rmSync as rmSync2 } from "fs";
|
|
1988
|
-
import { open } from "fs/promises";
|
|
1989
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
1990
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
1991
|
-
import { pipeline } from "stream/promises";
|
|
1992
|
-
import { createGunzip } from "zlib";
|
|
1993
|
-
|
|
1994
|
-
// src/sandbox-push-syncable.ts
|
|
1995
|
-
import { spawnSync } from "child_process";
|
|
1996
|
-
import { existsSync, mkdtempSync, readFileSync as readFileSync3, rmSync, statSync } from "fs";
|
|
1997
|
-
import { tmpdir } from "os";
|
|
1998
|
-
import { dirname, join, resolve } from "path";
|
|
1999
|
-
var realDocker = (args) => {
|
|
2000
|
-
const result = spawnSync("docker", args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 });
|
|
2001
|
-
if (result.error) return { status: null, stdout: "", stderr: result.error.message };
|
|
2002
|
-
return { status: result.status, stdout: result.stdout, stderr: result.stderr };
|
|
2003
|
-
};
|
|
2004
|
-
var SAMPLE = 20;
|
|
2005
|
-
function sampleOf(paths) {
|
|
2006
|
-
const sorted = [...paths].sort();
|
|
2007
|
-
if (sorted.length <= SAMPLE) return sorted;
|
|
2008
|
-
return Array.from({ length: SAMPLE }, (_, i) => sorted[Math.floor(i * sorted.length / SAMPLE)]);
|
|
2009
|
-
}
|
|
2010
|
-
var SYNCABLE_RULE = "a syncable image has your definition file at its WORKDIR, as `COPY . .` gives you";
|
|
2011
|
-
function syncabilityLine(params) {
|
|
2012
|
-
const docker2 = params.docker ?? realDocker;
|
|
2013
|
-
const git2 = spawnSync("git", ["ls-files", "-s", "-z"], { cwd: params.definitionDir, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
|
|
2014
|
-
if (git2.status !== 0) return "Sync: not checked (the definition is not in a git repository).";
|
|
2015
|
-
const tracked = git2.stdout.split("\0").flatMap((entry) => /^(100644|100755) [0-9a-f]+ \d\t(.*)$/s.exec(entry)?.[2] ?? []).filter((path) => path !== "cofactor.sandbox.yaml").filter((path) => existsSync(join(params.definitionDir, path)));
|
|
2016
|
-
const temp = mkdtempSync(join(tmpdir(), "cofactor-syncable-"));
|
|
2017
|
-
const created = docker2(["create", params.image]);
|
|
2018
|
-
if (created.status !== 0) {
|
|
2019
|
-
rmSync(temp, { recursive: true, force: true });
|
|
2020
|
-
return `Sync: not checked (docker create failed: ${created.stderr.trim()}).`;
|
|
2393
|
+
const names = new Map((await fetchProperties(apiUrl, token)).map((p) => [p.id, p.name]));
|
|
2394
|
+
console.log(`${pad2("ID", 38)} ${pad2("Application", 24)} ${pad2("Environment", 14)} ${pad2("Status", 10)} ${pad2("Claimed", 26)} Expires`);
|
|
2395
|
+
for (const c of claims) {
|
|
2396
|
+
console.log(
|
|
2397
|
+
`${pad2(c.id, 38)} ${pad2(names.get(c.applicationId) ?? c.applicationId, 24)} ${pad2(c.environment, 14)} ${pad2(c.status, 10)} ${pad2(c.createdAt, 26)} ${c.leaseExpiresAt ?? "-"}`
|
|
2398
|
+
);
|
|
2021
2399
|
}
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
if (
|
|
2028
|
-
return
|
|
2029
|
-
};
|
|
2030
|
-
const anchor = copyOut("cofactor.sandbox.yaml");
|
|
2031
|
-
if (!anchor) {
|
|
2032
|
-
return `Sync: not syncable: cofactor.sandbox.yaml isn't at ${params.workdir}/cofactor.sandbox.yaml (${SYNCABLE_RULE}).`;
|
|
2400
|
+
console.log("\nTear one down with: cofactor sandbox destroy <id>");
|
|
2401
|
+
}
|
|
2402
|
+
function coerceInputValue(raw, type) {
|
|
2403
|
+
switch (type) {
|
|
2404
|
+
case "integer": {
|
|
2405
|
+
if (!/^-?\d+$/.test(raw)) throw new Error(`must be an integer, got "${raw}"`);
|
|
2406
|
+
return Number(raw);
|
|
2033
2407
|
}
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
const bytes = copyOut(path);
|
|
2039
|
-
if (!bytes) missing += 1;
|
|
2040
|
-
else if (gitBlobId(bytes) !== gitBlobId(readFileSync3(join(params.definitionDir, path)))) differ += 1;
|
|
2408
|
+
case "number": {
|
|
2409
|
+
const value = Number(raw);
|
|
2410
|
+
if (Number.isNaN(value)) throw new Error(`must be a number, got "${raw}"`);
|
|
2411
|
+
return value;
|
|
2041
2412
|
}
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
docker2(["rm", container]);
|
|
2050
|
-
rmSync(temp, { recursive: true, force: true });
|
|
2413
|
+
case "boolean": {
|
|
2414
|
+
if (raw === "true") return true;
|
|
2415
|
+
if (raw === "false") return false;
|
|
2416
|
+
throw new Error(`must be "true" or "false", got "${raw}"`);
|
|
2417
|
+
}
|
|
2418
|
+
default:
|
|
2419
|
+
return raw;
|
|
2051
2420
|
}
|
|
2052
2421
|
}
|
|
2053
|
-
function
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2422
|
+
function buildRunInputFromFlags(argv, propTypes) {
|
|
2423
|
+
const pairs = getArgValues(argv, "--input");
|
|
2424
|
+
const input = {};
|
|
2425
|
+
for (const pair of pairs) {
|
|
2426
|
+
const eq = pair.indexOf("=");
|
|
2427
|
+
if (eq <= 0) {
|
|
2428
|
+
console.error(`Error: invalid --input "${pair}". Use key=value.`);
|
|
2429
|
+
process6.exit(1);
|
|
2430
|
+
}
|
|
2431
|
+
const key = pair.slice(0, eq);
|
|
2432
|
+
const raw = pair.slice(eq + 1);
|
|
2433
|
+
const type = propTypes[key];
|
|
2434
|
+
if (!type) {
|
|
2435
|
+
console.error(`Error: unknown input "${key}". Known: ${Object.keys(propTypes).join(", ") || "(none)"}`);
|
|
2436
|
+
process6.exit(1);
|
|
2437
|
+
}
|
|
2438
|
+
try {
|
|
2439
|
+
input[key] = coerceInputValue(raw, type);
|
|
2440
|
+
} catch (error) {
|
|
2441
|
+
console.error(`Error: --input ${key}: ${error instanceof Error ? error.message : String(error)}`);
|
|
2442
|
+
process6.exit(1);
|
|
2443
|
+
}
|
|
2059
2444
|
}
|
|
2445
|
+
return input;
|
|
2060
2446
|
}
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
}
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
function withSandboxLayers(team, added, created) {
|
|
2075
|
-
return {
|
|
2076
|
-
...team,
|
|
2077
|
-
rootfs: { ...team.rootfs, diff_ids: [...team.rootfs.diff_ids, ...added.map((layer) => layer.digest)] },
|
|
2078
|
-
history: [...team.history ?? [], ...added.map(() => ({ created, created_by: "cofactor sandbox push" }))],
|
|
2079
|
-
config: { ...team.config, Entrypoint: ["/usr/local/bin/cofactor-sandbox", "boot"], Cmd: [] }
|
|
2080
|
-
};
|
|
2447
|
+
async function pollRunUntilDone(apiUrl, token, runId, timeoutMs, timeoutNote = "") {
|
|
2448
|
+
const deadline = Date.now() + timeoutMs;
|
|
2449
|
+
while (Date.now() < deadline) {
|
|
2450
|
+
const pollRes = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/runs/${runId}`);
|
|
2451
|
+
if (!pollRes.ok) {
|
|
2452
|
+
console.error(`Error: ${pollRes.error}`);
|
|
2453
|
+
process6.exit(1);
|
|
2454
|
+
}
|
|
2455
|
+
if (pollRes.data.status !== "running") return pollRes.data;
|
|
2456
|
+
await sleep(2e3);
|
|
2457
|
+
}
|
|
2458
|
+
console.error(`Timed out waiting for run ${runId}${timeoutNote}.`);
|
|
2459
|
+
process6.exit(1);
|
|
2081
2460
|
}
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
put(0, 100, name);
|
|
2091
|
-
octal(100, 8, mode);
|
|
2092
|
-
octal(108, 8, 0);
|
|
2093
|
-
octal(116, 8, 0);
|
|
2094
|
-
octal(124, 12, size);
|
|
2095
|
-
octal(136, 12, 0);
|
|
2096
|
-
put(148, 8, " ");
|
|
2097
|
-
put(156, 1, "0");
|
|
2098
|
-
put(257, 6, "ustar\0");
|
|
2099
|
-
put(263, 2, "00");
|
|
2100
|
-
const checksum = header.reduce((sum, byte) => sum + byte, 0);
|
|
2101
|
-
put(148, 8, `${checksum.toString(8).padStart(6, "0")}\0 `);
|
|
2102
|
-
return header;
|
|
2103
|
-
}
|
|
2104
|
-
function tarPadding(size) {
|
|
2105
|
-
return new Uint8Array((BLOCK - size % BLOCK) % BLOCK);
|
|
2106
|
-
}
|
|
2107
|
-
var TAR_END = new Uint8Array(BLOCK * 2);
|
|
2108
|
-
function tarOf(files) {
|
|
2109
|
-
const parts = files.flatMap((file) => [tarHeader(file.name, file.data.length, file.mode), file.data, tarPadding(file.data.length)]);
|
|
2110
|
-
parts.push(TAR_END);
|
|
2111
|
-
const out = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
|
|
2112
|
-
let at = 0;
|
|
2113
|
-
for (const part of parts) {
|
|
2114
|
-
out.set(part, at);
|
|
2115
|
-
at += part.length;
|
|
2461
|
+
async function handleSandboxRun(argv, apiUrl, token) {
|
|
2462
|
+
const positional = argv.filter((a) => !a.startsWith("-"));
|
|
2463
|
+
const [id, actionRef] = positional;
|
|
2464
|
+
if (!id || !actionRef) {
|
|
2465
|
+
console.error(
|
|
2466
|
+
"Usage: cofactor sandbox run <id> <action|plugin.op> [--input k=v ...] [--json-input '{...}'] [--json]"
|
|
2467
|
+
);
|
|
2468
|
+
process6.exit(1);
|
|
2116
2469
|
}
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
}
|
|
2138
|
-
|
|
2470
|
+
const json = hasFlag(argv, "--json");
|
|
2471
|
+
const jsonInput = getArgValue(argv, "--json-input");
|
|
2472
|
+
const showRes = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
|
|
2473
|
+
if (!showRes.ok) {
|
|
2474
|
+
console.error(`Error: ${showRes.error}`);
|
|
2475
|
+
process6.exit(1);
|
|
2476
|
+
}
|
|
2477
|
+
const sandbox = showRes.data;
|
|
2478
|
+
const repoAction = sandbox.actions.find((a) => a.slug === actionRef);
|
|
2479
|
+
const operation = sandbox.operations.find((op) => op.name === actionRef);
|
|
2480
|
+
if (!repoAction && !operation) {
|
|
2481
|
+
console.error(`Error: unknown action or operation "${actionRef}".`);
|
|
2482
|
+
console.error(` Actions: ${sandbox.actions.map((a) => a.slug).join(", ") || "(none)"}`);
|
|
2483
|
+
console.error(` Operations: ${sandbox.operations.map((op) => op.name).join(", ") || "(none)"}`);
|
|
2484
|
+
process6.exit(1);
|
|
2485
|
+
}
|
|
2486
|
+
let input;
|
|
2487
|
+
if (jsonInput !== void 0) {
|
|
2488
|
+
try {
|
|
2489
|
+
input = JSON.parse(jsonInput);
|
|
2490
|
+
} catch (error) {
|
|
2491
|
+
console.error(`Error: invalid --json-input: ${error instanceof Error ? error.message : String(error)}`);
|
|
2492
|
+
process6.exit(1);
|
|
2139
2493
|
}
|
|
2140
|
-
|
|
2494
|
+
} else if (repoAction) {
|
|
2495
|
+
const propTypes = Object.fromEntries(Object.entries(repoAction.input).map(([key, prop]) => [key, prop.type]));
|
|
2496
|
+
input = buildRunInputFromFlags(argv, propTypes);
|
|
2497
|
+
} else {
|
|
2498
|
+
input = buildRunInputFromFlags(argv, jsonSchemaPropTypes(operation.inputSchema));
|
|
2499
|
+
}
|
|
2500
|
+
const enqueueRes = await apiRequest(
|
|
2501
|
+
apiUrl,
|
|
2502
|
+
token,
|
|
2503
|
+
"POST",
|
|
2504
|
+
`/org/sandboxes-v2/sandboxes/${id}/runs`,
|
|
2505
|
+
{ action: actionRef, input }
|
|
2506
|
+
);
|
|
2507
|
+
if (!enqueueRes.ok) {
|
|
2508
|
+
if (enqueueRes.code === "SANDBOX_V2_ACTION_INVALID_INPUT") {
|
|
2509
|
+
console.error(`Error: invalid input for "${actionRef}":`);
|
|
2510
|
+
for (const issue of enqueueRes.issues ?? []) {
|
|
2511
|
+
if (typeof issue === "string") console.error(` ${issue}`);
|
|
2512
|
+
else console.error(` ${(issue.path ?? []).join(".")}: ${issue.message ?? ""}`);
|
|
2513
|
+
}
|
|
2514
|
+
console.error("");
|
|
2515
|
+
console.error("Input schema:");
|
|
2516
|
+
writeRawOutput(JSON.stringify(repoAction ? repoAction.input : operation.inputSchema, null, 2));
|
|
2517
|
+
} else {
|
|
2518
|
+
console.error(`Error: ${enqueueRes.error}`);
|
|
2519
|
+
}
|
|
2520
|
+
process6.exit(1);
|
|
2521
|
+
}
|
|
2522
|
+
const runId = enqueueRes.data.id;
|
|
2523
|
+
const run = await pollRunUntilDone(apiUrl, token, runId, ((repoAction?.timeout ?? 60) + 60) * 1e3);
|
|
2524
|
+
if (json) {
|
|
2525
|
+
writeRawOutput(JSON.stringify(run, null, 2));
|
|
2526
|
+
if (run.status === "failed") process6.exit(1);
|
|
2527
|
+
return;
|
|
2528
|
+
}
|
|
2529
|
+
if (run.status === "failed") {
|
|
2530
|
+
console.error(`Run failed: ${run.error ?? "unknown error"}`);
|
|
2531
|
+
if (run.stderrTail) {
|
|
2532
|
+
console.error("");
|
|
2533
|
+
console.error("stderr:");
|
|
2534
|
+
console.error(run.stderrTail);
|
|
2535
|
+
}
|
|
2536
|
+
process6.exit(1);
|
|
2141
2537
|
}
|
|
2538
|
+
writeRawOutput(JSON.stringify(run.output ?? {}, null, 2));
|
|
2142
2539
|
}
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
"
|
|
2147
|
-
"",
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
var PushError = class extends Error {
|
|
2157
|
-
constructor(message, issues = []) {
|
|
2158
|
-
super(message);
|
|
2159
|
-
this.issues = issues;
|
|
2540
|
+
var EXEC_USAGE = "Usage: cofactor sandbox exec <id> [--timeout <seconds>] [--json] -- <command...>";
|
|
2541
|
+
function parseExecArgs(options, commandWords) {
|
|
2542
|
+
const command = commandWords.join(" ");
|
|
2543
|
+
if (command.trim() === "") return { ok: false, error: `a command is required after \`--\`. ${EXEC_USAGE}` };
|
|
2544
|
+
const id = v2Positionals("exec", options)[0];
|
|
2545
|
+
if (!id) return { ok: false, error: `sandbox id required. ${EXEC_USAGE}` };
|
|
2546
|
+
const timeout = getArgValue(options, "--timeout");
|
|
2547
|
+
let timeoutSeconds;
|
|
2548
|
+
if (timeout !== void 0) {
|
|
2549
|
+
timeoutSeconds = /^\d+$/.test(timeout) ? Number(timeout) : NaN;
|
|
2550
|
+
if (!Number.isInteger(timeoutSeconds) || timeoutSeconds < 1 || timeoutSeconds > EXEC_MAX_TIMEOUT_SECONDS) {
|
|
2551
|
+
return { ok: false, error: `--timeout must be a whole number of seconds from 1 to ${EXEC_MAX_TIMEOUT_SECONDS}.` };
|
|
2552
|
+
}
|
|
2160
2553
|
}
|
|
2161
|
-
|
|
2162
|
-
};
|
|
2163
|
-
function fail(message, issues) {
|
|
2164
|
-
throw new PushError(message, issues);
|
|
2554
|
+
return { ok: true, args: { id, timeoutSeconds, json: hasFlag(options, "--json"), command } };
|
|
2165
2555
|
}
|
|
2166
|
-
function
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
stdio: ["ignore", "pipe", options.quiet ? "pipe" : "inherit"],
|
|
2170
|
-
maxBuffer: 64 * 1024 * 1024
|
|
2171
|
-
});
|
|
2172
|
-
if (result.error) fail(`could not run docker (${result.error.message}); is Docker installed and running?`);
|
|
2173
|
-
if (result.status !== 0) fail(`docker ${args.slice(0, 2).join(" ")} failed:
|
|
2174
|
-
${result.stderr ?? ""}`.trimEnd());
|
|
2175
|
-
return result.stdout;
|
|
2556
|
+
function execExitCode(run) {
|
|
2557
|
+
if (run.status === "succeeded") return 0;
|
|
2558
|
+
return run.exitCode ?? 1;
|
|
2176
2559
|
}
|
|
2177
|
-
function
|
|
2178
|
-
const
|
|
2179
|
-
if (
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
if (inspected.Os !== "linux" || inspected.Architecture !== "amd64") {
|
|
2183
|
-
fail(
|
|
2184
|
-
`${image} is ${inspected.Os}/${inspected.Architecture}; sandboxes run linux/amd64. Rebuild it with
|
|
2185
|
-
docker buildx build --platform linux/amd64 -t ${image} --load <context>`
|
|
2186
|
-
);
|
|
2560
|
+
async function handleSandboxExec(options, commandWords, apiUrl, token) {
|
|
2561
|
+
const parsed = parseExecArgs(options, commandWords);
|
|
2562
|
+
if (!parsed.ok) {
|
|
2563
|
+
console.error(`Error: ${parsed.error}`);
|
|
2564
|
+
process6.exit(1);
|
|
2187
2565
|
}
|
|
2188
|
-
const
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2566
|
+
const { id, timeoutSeconds, json, command } = parsed.args;
|
|
2567
|
+
const enqueueRes = await apiRequest(
|
|
2568
|
+
apiUrl,
|
|
2569
|
+
token,
|
|
2570
|
+
"POST",
|
|
2571
|
+
`/org/sandboxes-v2/sandboxes/${id}/exec`,
|
|
2572
|
+
timeoutSeconds === void 0 ? { command } : { command, timeoutSeconds }
|
|
2573
|
+
);
|
|
2574
|
+
if (!enqueueRes.ok) {
|
|
2575
|
+
console.error(`Error: ${enqueueRes.error}`);
|
|
2576
|
+
process6.exit(1);
|
|
2193
2577
|
}
|
|
2194
|
-
const
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2578
|
+
const runId = enqueueRes.data.id;
|
|
2579
|
+
const run = await pollRunUntilDone(
|
|
2580
|
+
apiUrl,
|
|
2581
|
+
token,
|
|
2582
|
+
runId,
|
|
2583
|
+
((timeoutSeconds ?? EXEC_DEFAULT_TIMEOUT_SECONDS) + 60) * 1e3,
|
|
2584
|
+
"; the command may still be running in the sandbox"
|
|
2199
2585
|
);
|
|
2200
|
-
const
|
|
2201
|
-
if (
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
`${image}'s WORKDIR ${workdir} can't be used: ${parsed.error.issues[0]?.message ?? "invalid"}. Set one for your app in its Dockerfile (e.g. \`WORKDIR /app\`): services, prepare commands and actions run from it, and the sandbox makes it writable by the app.`
|
|
2205
|
-
);
|
|
2586
|
+
const code = execExitCode(run);
|
|
2587
|
+
if (json) {
|
|
2588
|
+
writeRawOutput(JSON.stringify(run, null, 2));
|
|
2589
|
+
process6.exit(code);
|
|
2206
2590
|
}
|
|
2207
|
-
|
|
2208
|
-
}
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
if (!res.ok) fail(res.error ?? "unknown error", res.issues);
|
|
2215
|
-
return res.data;
|
|
2216
|
-
}
|
|
2217
|
-
var mib = (bytes) => `${(bytes / 1024 / 1024).toFixed(bytes < 10 * 1024 * 1024 ? 1 : 0)} MB`;
|
|
2218
|
-
async function readAt(file, offset, size) {
|
|
2219
|
-
const bytes = new Uint8Array(size);
|
|
2220
|
-
for (let at = 0; at < size; ) {
|
|
2221
|
-
const { bytesRead } = await file.read(bytes, at, size - at, offset + at);
|
|
2222
|
-
if (bytesRead === 0) return bytes.subarray(0, at);
|
|
2223
|
-
at += bytesRead;
|
|
2591
|
+
if (run.status === "failed" && run.exitCode === null) {
|
|
2592
|
+
process6.stderr.write(`Error: ${run.error ?? "unknown error"}
|
|
2593
|
+
`);
|
|
2594
|
+
if (run.stdoutTail) process6.stderr.write(run.stdoutTail);
|
|
2595
|
+
if (run.stdoutTail && !run.stdoutTail.endsWith("\n") && run.stderrTail) process6.stderr.write("\n");
|
|
2596
|
+
if (run.stderrTail) process6.stderr.write(run.stderrTail);
|
|
2597
|
+
process6.exit(1);
|
|
2224
2598
|
}
|
|
2225
|
-
|
|
2599
|
+
if (run.stdoutTail) process6.stdout.write(run.stdoutTail);
|
|
2600
|
+
if (run.stderrTail) process6.stderr.write(run.stderrTail);
|
|
2601
|
+
if (code === 124 && run.error) process6.stderr.write(`
|
|
2602
|
+
${run.error}
|
|
2603
|
+
`);
|
|
2604
|
+
process6.exit(code);
|
|
2226
2605
|
}
|
|
2227
|
-
async function
|
|
2228
|
-
const
|
|
2229
|
-
|
|
2230
|
-
|
|
2606
|
+
async function handleSandboxDestroy(argv, apiUrl, token) {
|
|
2607
|
+
const [id] = argv.filter((a) => !a.startsWith("-"));
|
|
2608
|
+
if (!id) {
|
|
2609
|
+
console.error("Usage: cofactor sandbox destroy <id>");
|
|
2610
|
+
process6.exit(1);
|
|
2231
2611
|
}
|
|
2232
|
-
|
|
2612
|
+
const local = trackedLocalSandbox(id, apiUrl);
|
|
2613
|
+
const res = await apiRequest(
|
|
2614
|
+
apiUrl,
|
|
2615
|
+
token,
|
|
2616
|
+
"DELETE",
|
|
2617
|
+
`/org/sandboxes-v2/sandboxes/${id}`
|
|
2618
|
+
);
|
|
2619
|
+
if (!res.ok) {
|
|
2620
|
+
console.error(`Error: ${res.error}`);
|
|
2621
|
+
process6.exit(1);
|
|
2622
|
+
}
|
|
2623
|
+
console.log(`Sandbox ${id} destroying.`);
|
|
2624
|
+
if (local) await cleanupLocalRefs(apiUrl, token, local.applicationId, local.applicationId, { id, version: local.version });
|
|
2233
2625
|
}
|
|
2234
|
-
function
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
async function putBlob(url, headers, part) {
|
|
2238
|
-
const digest = part.digest;
|
|
2239
|
-
for (let attempt = 1; ; attempt++) {
|
|
2240
|
-
let response;
|
|
2241
|
-
try {
|
|
2242
|
-
const init = {
|
|
2243
|
-
method: "PUT",
|
|
2244
|
-
headers: { ...headers, "content-length": String(part.size) },
|
|
2245
|
-
body: part.body(),
|
|
2246
|
-
duplex: "half"
|
|
2247
|
-
};
|
|
2248
|
-
response = await fetch(url, init);
|
|
2249
|
-
} catch (error) {
|
|
2250
|
-
if (attempt >= UPLOAD_ATTEMPTS) throw error;
|
|
2251
|
-
await new Promise((resolve3) => setTimeout(resolve3, 2e3 * attempt));
|
|
2252
|
-
continue;
|
|
2253
|
-
}
|
|
2254
|
-
if (response.ok) return;
|
|
2255
|
-
const detail = `${response.status}: ${(await response.text()).slice(0, 300)}`;
|
|
2256
|
-
if (response.status < 500 || attempt >= UPLOAD_ATTEMPTS) {
|
|
2257
|
-
fail(`uploading ${digest} was refused (HTTP ${detail}); run the push again to upload what is still missing`);
|
|
2258
|
-
}
|
|
2259
|
-
await new Promise((resolve3) => setTimeout(resolve3, 2e3 * attempt));
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2262
|
-
async function savedImage(archive) {
|
|
2263
|
-
const file = await open(archive);
|
|
2264
|
-
try {
|
|
2265
|
-
const entries = await tarEntries((offset, length) => readAt(file, offset, length)).catch(
|
|
2266
|
-
(error) => fail(`reading the saved image: ${error instanceof Error ? error.message : String(error)}`)
|
|
2267
|
-
);
|
|
2268
|
-
const byName = new Map(entries.map((entry) => [entry.name.replace(/^\.\//, ""), entry]));
|
|
2269
|
-
const entryOf = (name) => {
|
|
2270
|
-
const entry = byName.get(name);
|
|
2271
|
-
if (!entry) fail(`the saved image has no ${name}; is this a \`docker save\` archive?`);
|
|
2272
|
-
return entry;
|
|
2273
|
-
};
|
|
2274
|
-
const json = async (name) => {
|
|
2275
|
-
const entry = entryOf(name);
|
|
2276
|
-
return JSON.parse(new TextDecoder().decode(await readAt(file, entry.offset, entry.size)));
|
|
2277
|
-
};
|
|
2278
|
-
const layerOf = async (name) => {
|
|
2279
|
-
const entry = entryOf(name);
|
|
2280
|
-
const tooLarge = (bytes) => fail(`a layer of the image is ${(bytes / 1024 ** 3).toFixed(1)} GB; a push takes at most 5 GB per layer. Slim the image.`);
|
|
2281
|
-
if (entry.size > MAX_BLOB_BYTES) tooLarge(entry.size);
|
|
2282
|
-
const range = { start: entry.offset, end: entry.offset + entry.size - 1 };
|
|
2283
|
-
const [first, second] = await readAt(file, entry.offset, 2);
|
|
2284
|
-
if (first === 31 && second === 139) {
|
|
2285
|
-
const unpacked = join2(dirname2(archive), `layer-${entry.offset}.tar`);
|
|
2286
|
-
await pipeline(createReadStream(archive, range), createGunzip(), createWriteStream(unpacked)).catch(
|
|
2287
|
-
(error) => fail(`unpacking a compressed layer of the image: ${error instanceof Error ? error.message : String(error)}`)
|
|
2288
|
-
);
|
|
2289
|
-
const plain = await open(unpacked);
|
|
2290
|
-
try {
|
|
2291
|
-
const { size } = await plain.stat();
|
|
2292
|
-
if (size > MAX_BLOB_BYTES) tooLarge(size);
|
|
2293
|
-
return { digest: await digestOf(plain, 0, size), size, body: () => createReadStream(unpacked) };
|
|
2294
|
-
} finally {
|
|
2295
|
-
await plain.close();
|
|
2296
|
-
}
|
|
2297
|
-
}
|
|
2298
|
-
return { digest: await digestOf(file, entry.offset, entry.size), size: entry.size, body: () => createReadStream(archive, range) };
|
|
2299
|
-
};
|
|
2300
|
-
const [manifest] = await json("manifest.json");
|
|
2301
|
-
if (!manifest) fail("the saved image lists no image");
|
|
2302
|
-
const layers = [];
|
|
2303
|
-
for (const name of manifest.Layers) layers.push(await layerOf(name));
|
|
2304
|
-
return { config: await json(manifest.Config), layers };
|
|
2305
|
-
} finally {
|
|
2306
|
-
await file.close();
|
|
2307
|
-
}
|
|
2308
|
-
}
|
|
2309
|
-
async function pushedImage(archive, cofactorLayers, definitionYaml, config) {
|
|
2310
|
-
const saved = await savedImage(archive);
|
|
2311
|
-
const own = partOf(
|
|
2312
|
-
tarOf([
|
|
2313
|
-
{ name: IMAGE_DEFINITION_FILE.slice(1), data: new TextEncoder().encode(definitionYaml) },
|
|
2314
|
-
{ name: IMAGE_CONFIG_FILE.slice(1), data: new TextEncoder().encode(JSON.stringify(config)) }
|
|
2315
|
-
])
|
|
2316
|
-
);
|
|
2317
|
-
const added = [...cofactorLayers, own];
|
|
2318
|
-
const pushed = withSandboxLayers(saved.config, added, (/* @__PURE__ */ new Date()).toISOString());
|
|
2319
|
-
const imageConfig = partOf(new TextEncoder().encode(JSON.stringify(pushed)));
|
|
2320
|
-
const blob = ({ digest, size }) => ({ digest, size });
|
|
2321
|
-
return {
|
|
2322
|
-
config: blob(imageConfig),
|
|
2323
|
-
layers: [...saved.layers, ...added].map(blob),
|
|
2324
|
-
blobs: new Map([imageConfig, ...saved.layers, own].map((part) => [part.digest, part]))
|
|
2325
|
-
};
|
|
2326
|
-
}
|
|
2327
|
-
async function handleSandboxPush(argv, apiUrl, token) {
|
|
2328
|
-
try {
|
|
2329
|
-
await push(argv, apiUrl, token);
|
|
2330
|
-
} catch (error) {
|
|
2331
|
-
if (!(error instanceof PushError)) throw error;
|
|
2332
|
-
console.error(`Error: ${error.message}`);
|
|
2333
|
-
for (const issue of error.issues) console.error(` ${issue.path || "(root)"}: ${issue.message}`);
|
|
2334
|
-
process5.exit(1);
|
|
2335
|
-
}
|
|
2336
|
-
}
|
|
2337
|
-
async function push(argv, apiUrl, token) {
|
|
2338
|
-
const valueFlags = /* @__PURE__ */ new Set(["--application", "--property", "--definition", "--tag", "--env", "--org", "--token", "--api-url", "--app-url"]);
|
|
2339
|
-
const positional = argv.filter((arg, i) => !arg.startsWith("-") && !valueFlags.has(argv[i - 1] ?? ""));
|
|
2340
|
-
const image = positional[0];
|
|
2341
|
-
if (!image) fail(`an image is required
|
|
2342
|
-
${USAGE}`);
|
|
2343
|
-
const definitionFile = getArgValue(argv, "--definition") ?? "./cofactor.sandbox.yaml";
|
|
2344
|
-
const tags = getArgValues(argv, "--tag");
|
|
2345
|
-
const badTag = tags.find((tag) => !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(tag));
|
|
2346
|
-
if (badTag !== void 0) fail(`--tag ${badTag}: use letters, digits, '.', '_' and '-' (at most 64, starting with a letter or digit)`);
|
|
2347
|
-
if (tags.length > 10) fail("a push takes at most 10 tags");
|
|
2348
|
-
const wait = hasFlag(argv, "--wait");
|
|
2626
|
+
async function handleSandboxVerifyEnv(argv, apiUrl, token) {
|
|
2627
|
+
const applicationId = await resolveApplicationId(argv, apiUrl, token);
|
|
2628
|
+
const onlyEnvironment = getArgValue(argv, "--environment");
|
|
2349
2629
|
const json = hasFlag(argv, "--json");
|
|
2350
2630
|
const log = (line) => json ? console.error(line) : console.log(line);
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
const applicationId = await resolveOrCreateSandboxApplication(argv, apiUrl, token);
|
|
2361
|
-
const config = imageConfigOf(image);
|
|
2362
|
-
log(syncabilityLineOrSkipped({ image, workdir: config.workdir, definitionFile }));
|
|
2363
|
-
const work = mkdtempSync2(join2(tmpdir2(), "cofactor-push-"));
|
|
2364
|
-
const cleanUp = () => rmSync2(work, { recursive: true, force: true });
|
|
2365
|
-
const interrupted = (signal) => {
|
|
2366
|
-
cleanUp();
|
|
2367
|
-
process5.exit(signal === "SIGINT" ? 130 : 143);
|
|
2368
|
-
};
|
|
2369
|
-
process5.once("SIGINT", interrupted);
|
|
2370
|
-
process5.once("SIGTERM", interrupted);
|
|
2371
|
-
process5.exitCode = 1;
|
|
2372
|
-
try {
|
|
2373
|
-
const ours = await apiOrFail(
|
|
2374
|
-
apiRequest(
|
|
2375
|
-
apiUrl,
|
|
2376
|
-
token,
|
|
2377
|
-
"GET",
|
|
2378
|
-
"/org/sandboxes-v2/helper-layer"
|
|
2379
|
-
)
|
|
2380
|
-
);
|
|
2381
|
-
if (!ours.helper) fail("this Cofactor takes pushes from an older cofactor CLI; use the version it ships with");
|
|
2382
|
-
const cofactorLayers = [ours.helper, ...storageProvider ? [ours.objectStorage[storageProvider]] : []];
|
|
2383
|
-
const archive = join2(work, "image.tar");
|
|
2384
|
-
log("Saving the image...");
|
|
2385
|
-
docker(["save", "--output", archive, image], { quiet: json });
|
|
2386
|
-
const pushing = await pushedImage(archive, cofactorLayers, definitionYaml, config);
|
|
2387
|
-
const { layers } = pushing;
|
|
2388
|
-
const created = await apiOrFail(
|
|
2389
|
-
apiRequest(
|
|
2390
|
-
apiUrl,
|
|
2391
|
-
token,
|
|
2392
|
-
"POST",
|
|
2393
|
-
`/org/applications/${applicationId}/sandbox/uploads`,
|
|
2394
|
-
{ definitionYaml, platformVersion: ours.platformVersion, tags, image: { config: pushing.config, layers } }
|
|
2395
|
-
)
|
|
2396
|
-
);
|
|
2397
|
-
const partsByDigest = pushing.blobs;
|
|
2398
|
-
const uploadBytes = created.uploads.reduce((total, upload) => total + (partsByDigest.get(upload.digest)?.size ?? 0), 0);
|
|
2399
|
-
const imageBytes = layers.reduce((total, layer) => total + layer.size, 0);
|
|
2400
|
-
log(`Pushing ${created.id}: uploading ${created.uploads.length} of ${layers.length + 1} blobs (${mib(uploadBytes)}); ${mib(imageBytes - uploadBytes)} already in Cofactor`);
|
|
2401
|
-
const queue = [...created.uploads];
|
|
2402
|
-
let sent = 0;
|
|
2403
|
-
await Promise.all(
|
|
2404
|
-
Array.from({ length: UPLOAD_CONCURRENCY }, async () => {
|
|
2405
|
-
for (let next = queue.shift(); next; next = queue.shift()) {
|
|
2406
|
-
const part = partsByDigest.get(next.digest);
|
|
2407
|
-
if (!part) fail(`Cofactor asked for ${next.digest}, which this image doesn't have`);
|
|
2408
|
-
await putBlob(next.url, next.headers, part);
|
|
2409
|
-
sent += part.size;
|
|
2410
|
-
if (part.size > 10 * 1024 * 1024) log(` ${mib(sent)} of ${mib(uploadBytes)}`);
|
|
2411
|
-
}
|
|
2412
|
-
})
|
|
2631
|
+
const app = await fetchApplicationSandbox(
|
|
2632
|
+
apiUrl,
|
|
2633
|
+
token,
|
|
2634
|
+
applicationId,
|
|
2635
|
+
getArgValue(argv, "--at")
|
|
2636
|
+
);
|
|
2637
|
+
if (!app.version || !app.definition) {
|
|
2638
|
+
console.error(
|
|
2639
|
+
"Error: no sandbox definition at the default version. For GitHub, push it and use --at <sha>; for images, run `cofactor sandbox push`."
|
|
2413
2640
|
);
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2641
|
+
process6.exit(1);
|
|
2642
|
+
}
|
|
2643
|
+
const version = app.version;
|
|
2644
|
+
const allEnvironments = app.definition.environments.map((e) => e.name);
|
|
2645
|
+
if (onlyEnvironment && !allEnvironments.includes(onlyEnvironment)) {
|
|
2646
|
+
console.error(`Error: environment "${onlyEnvironment}" not found. Known: ${allEnvironments.join(", ")}`);
|
|
2647
|
+
process6.exit(1);
|
|
2648
|
+
}
|
|
2649
|
+
const environments = onlyEnvironment ? [onlyEnvironment] : allEnvironments;
|
|
2650
|
+
const results = [];
|
|
2651
|
+
for (const environment2 of environments) {
|
|
2652
|
+
log(`
|
|
2653
|
+
=== ${environment2} ===`);
|
|
2654
|
+
const started = Date.now();
|
|
2655
|
+
const claimRes = await apiRequest(
|
|
2656
|
+
apiUrl,
|
|
2657
|
+
token,
|
|
2658
|
+
"POST",
|
|
2659
|
+
"/org/sandboxes-v2/sandboxes",
|
|
2660
|
+
{ applicationId, environment: environment2, version }
|
|
2422
2661
|
);
|
|
2423
|
-
if (
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
while ((pushed.status === "importing" || pushed.status === "uploading") && Date.now() < deadline) {
|
|
2429
|
-
await new Promise((resolve3) => setTimeout(resolve3, IMPORT_POLL_MS));
|
|
2430
|
-
if (Date.now() - reported >= 6e4) {
|
|
2431
|
-
reported = Date.now();
|
|
2432
|
-
log(` still importing (${Math.round((reported - importStarted) / 6e4)} min)`);
|
|
2433
|
-
}
|
|
2434
|
-
pushed = await apiOrFail(
|
|
2435
|
-
apiRequest(apiUrl, token, "GET", `/org/applications/${applicationId}/sandbox/uploads/${created.id}`)
|
|
2436
|
-
);
|
|
2437
|
-
}
|
|
2662
|
+
if (!claimRes.ok) {
|
|
2663
|
+
const seconds2 = (Date.now() - started) / 1e3;
|
|
2664
|
+
console.error(` claim failed: ${claimRes.error}`);
|
|
2665
|
+
results.push({ environment: environment2, ok: false, seconds: seconds2, error: claimRes.error });
|
|
2666
|
+
continue;
|
|
2438
2667
|
}
|
|
2439
|
-
const
|
|
2440
|
-
|
|
2441
|
-
const
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2668
|
+
const id = claimRes.data.id;
|
|
2669
|
+
log(` claimed ${id}, waiting...`);
|
|
2670
|
+
const waited = await pollSandboxUntilReady(apiUrl, token, id, CLAIM_TIMEOUT_SECONDS);
|
|
2671
|
+
const seconds = (Date.now() - started) / 1e3;
|
|
2672
|
+
if ("error" in waited || waited.data.status !== "ready") {
|
|
2673
|
+
const error = "error" in waited ? waited.error : waited.data.error ?? "failed";
|
|
2674
|
+
console.error(` failed: ${error}`);
|
|
2675
|
+
results.push({ environment: environment2, ok: false, seconds, error });
|
|
2447
2676
|
} else {
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2677
|
+
const { data } = waited;
|
|
2678
|
+
log(` ready in ${data.readyMs !== null ? (data.readyMs / 1e3).toFixed(1) : "?"}s`);
|
|
2679
|
+
for (const [stepId, outputs] of Object.entries(data.seedOutputs)) {
|
|
2680
|
+
for (const [key, value] of Object.entries(outputs)) {
|
|
2681
|
+
log(` ${stepId}.${key}: ${JSON.stringify(value)}`);
|
|
2682
|
+
}
|
|
2453
2683
|
}
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
}
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
throw new SyncError(`git ${args[0]} failed (${result.signal ?? result.status}): ${result.stderr.trim()}`);
|
|
2485
|
-
}
|
|
2486
|
-
return result.stdout;
|
|
2487
|
-
}
|
|
2488
|
-
var toPosix = (path) => path.split(sep).join("/");
|
|
2489
|
-
function repositoryOf(dir) {
|
|
2490
|
-
const top = git(["rev-parse", "--show-toplevel"], { cwd: dir }).trim();
|
|
2491
|
-
const gitDir = git(["rev-parse", "--absolute-git-dir"], { cwd: dir }).trim();
|
|
2492
|
-
return { top: realpathSync(top), gitDir };
|
|
2493
|
-
}
|
|
2494
|
-
function indexEntries(output2, strip, top) {
|
|
2495
|
-
const files = [];
|
|
2496
|
-
const skipped = [];
|
|
2497
|
-
for (const entry of output2.split("\0")) {
|
|
2498
|
-
if (entry === "") continue;
|
|
2499
|
-
const match = /^(\d{6}) ([0-9a-f]+) \d\t(.*)$/s.exec(entry);
|
|
2500
|
-
if (!match) throw new SyncError(`unexpected git ls-files line: ${entry}`);
|
|
2501
|
-
const [, mode, blob, repoPath] = match;
|
|
2502
|
-
const path = strip === "" ? repoPath : repoPath.slice(strip.length + 1);
|
|
2503
|
-
const onDisk = mode === "100644" || mode === "100755" ? lstatOrNull(join3(top, repoPath)) : null;
|
|
2504
|
-
if (onDisk?.isFile()) files.push({ path, blob, mode: mode === "100755" ? 493 : 420, size: onDisk.size });
|
|
2505
|
-
else skipped.push(path);
|
|
2684
|
+
const personas = [];
|
|
2685
|
+
if (data.nativeLogin) {
|
|
2686
|
+
for (const { name } of data.personas) {
|
|
2687
|
+
const checked = await apiRequest(
|
|
2688
|
+
apiUrl,
|
|
2689
|
+
token,
|
|
2690
|
+
"POST",
|
|
2691
|
+
`/org/sandboxes-v2/sandboxes/${id}/auth-check`,
|
|
2692
|
+
{ persona: name },
|
|
2693
|
+
// The API waits on the script (up to 60 s) and the sandbox connection.
|
|
2694
|
+
{ timeoutMs: 9e4 }
|
|
2695
|
+
);
|
|
2696
|
+
const check = checked.ok ? checked.data : { persona: name, ok: false, error: checked.error ?? "unknown error" };
|
|
2697
|
+
personas.push(check);
|
|
2698
|
+
if (check.ok) log(` persona ${name}: signed in`);
|
|
2699
|
+
else console.error(` persona ${name}: FAILED
|
|
2700
|
+
${indentLines(check.error ?? "", " ")}`);
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
const failedPersonas = personas.filter((p) => !p.ok).map((p) => p.persona);
|
|
2704
|
+
results.push({
|
|
2705
|
+
environment: environment2,
|
|
2706
|
+
ok: failedPersonas.length === 0,
|
|
2707
|
+
seconds,
|
|
2708
|
+
...failedPersonas.length > 0 ? { error: `persona sign-in failed: ${failedPersonas.join(", ")}` } : {},
|
|
2709
|
+
...data.nativeLogin ? { personas } : {}
|
|
2710
|
+
});
|
|
2711
|
+
}
|
|
2712
|
+
log(` destroying ${id}...`);
|
|
2713
|
+
await apiRequest(apiUrl, token, "DELETE", `/org/sandboxes-v2/sandboxes/${id}`);
|
|
2506
2714
|
}
|
|
2507
|
-
|
|
2715
|
+
log("");
|
|
2716
|
+
log("Summary:");
|
|
2717
|
+
log(` ${pad2("Environment", 20)} ${pad2("Result", 6)} Seconds`);
|
|
2718
|
+
for (const r of results) {
|
|
2719
|
+
const errSuffix = r.error ? ` (${r.error})` : "";
|
|
2720
|
+
log(` ${pad2(r.environment, 20)} ${pad2(r.ok ? "OK" : "FAIL", 6)} ${r.seconds.toFixed(1)}${errSuffix}`);
|
|
2721
|
+
}
|
|
2722
|
+
if (json) writeRawOutput(JSON.stringify(results, null, 2));
|
|
2723
|
+
if (results.some((r) => !r.ok)) process6.exit(1);
|
|
2508
2724
|
}
|
|
2509
|
-
function
|
|
2510
|
-
const
|
|
2511
|
-
const
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
const index = join3(temp, "index");
|
|
2516
|
-
const env = { ...process6.env, GIT_INDEX_FILE: index };
|
|
2517
|
-
const userIndex = join3(gitDir, "index");
|
|
2518
|
-
if (existsSync2(userIndex)) copyFileSync(userIndex, index);
|
|
2519
|
-
else if (git(["rev-parse", "--verify", "-q", "HEAD"], { cwd: top, okCodes: [0, 1] }).trim() !== "") git(["read-tree", "HEAD"], { cwd: top, env });
|
|
2520
|
-
const pathspec = prefix === "" ? "." : `:(literal)${prefix}`;
|
|
2521
|
-
git(["add", "-A", "--", pathspec], { cwd: top, env });
|
|
2522
|
-
const entries = indexEntries(git(["ls-files", "-s", "-z", "--", pathspec], { cwd: top, env }), prefix, top);
|
|
2523
|
-
const files = withRawBlobs(entries.files, top, prefix);
|
|
2524
|
-
const skipped = entries.skipped;
|
|
2525
|
-
const tree = git(prefix === "" ? ["write-tree"] : ["write-tree", `--prefix=${prefix}/`], { cwd: top, env }).trim();
|
|
2526
|
-
const untracked = new Set(
|
|
2527
|
-
git(["ls-files", "--others", "--exclude-standard", "-z", "--", pathspec], { cwd: top }).split("\0").filter(Boolean).map((path) => prefix === "" ? path : path.slice(prefix.length + 1))
|
|
2528
|
-
);
|
|
2529
|
-
return { files, skipped, untracked, tree, repo: top, prefix, env, cleanup };
|
|
2530
|
-
} catch (error) {
|
|
2531
|
-
cleanup();
|
|
2532
|
-
throw error;
|
|
2725
|
+
async function handleSandboxAuthCheck(argv, apiUrl, token) {
|
|
2726
|
+
const id = v2Positionals("auth-check", argv)[0];
|
|
2727
|
+
const persona = getArgValue(argv, "--persona");
|
|
2728
|
+
if (!id || !persona) {
|
|
2729
|
+
console.error("Usage: cofactor sandbox auth-check <id> --persona <name> [--json]");
|
|
2730
|
+
process6.exit(1);
|
|
2533
2731
|
}
|
|
2732
|
+
const checked = await apiRequest(
|
|
2733
|
+
apiUrl,
|
|
2734
|
+
token,
|
|
2735
|
+
"POST",
|
|
2736
|
+
`/org/sandboxes-v2/sandboxes/${encodeURIComponent(id)}/auth-check`,
|
|
2737
|
+
{ persona },
|
|
2738
|
+
{ timeoutMs: 9e4 }
|
|
2739
|
+
);
|
|
2740
|
+
exitOnRefusal(checked);
|
|
2741
|
+
if (hasFlag(argv, "--json")) writeRawOutput(JSON.stringify(checked.data, null, 2));
|
|
2742
|
+
else if (checked.data.ok) console.log(`Persona ${persona}: signed in`);
|
|
2743
|
+
else console.error(`Persona ${persona}: FAILED
|
|
2744
|
+
${checked.data.error ?? "unknown error"}`);
|
|
2745
|
+
if (!checked.data.ok) process6.exit(1);
|
|
2534
2746
|
}
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2747
|
+
|
|
2748
|
+
// src/sandbox-push.ts
|
|
2749
|
+
import process7 from "process";
|
|
2750
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
2751
|
+
import { createHash as createHash4 } from "crypto";
|
|
2752
|
+
import { createReadStream, createWriteStream, mkdtempSync as mkdtempSync3, readFileSync as readFileSync6, rmSync as rmSync4 } from "fs";
|
|
2753
|
+
import { open } from "fs/promises";
|
|
2754
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
2755
|
+
import { dirname as dirname2, join as join4 } from "path";
|
|
2756
|
+
import { pipeline } from "stream/promises";
|
|
2757
|
+
import { createGunzip } from "zlib";
|
|
2758
|
+
|
|
2759
|
+
// src/sandbox-push-syncable.ts
|
|
2760
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
2761
|
+
import { existsSync as existsSync3, mkdtempSync as mkdtempSync2, readFileSync as readFileSync5, rmSync as rmSync3, statSync } from "fs";
|
|
2762
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
2763
|
+
import { dirname, join as join3, resolve as resolve3 } from "path";
|
|
2764
|
+
var realDocker = (args) => {
|
|
2765
|
+
const result = spawnSync3("docker", args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 });
|
|
2766
|
+
if (result.error) return { status: null, stdout: "", stderr: result.error.message };
|
|
2767
|
+
return { status: result.status, stdout: result.stdout, stderr: result.stderr };
|
|
2768
|
+
};
|
|
2769
|
+
var SAMPLE = 20;
|
|
2770
|
+
function sampleOf(paths) {
|
|
2771
|
+
const sorted = [...paths].sort();
|
|
2772
|
+
if (sorted.length <= SAMPLE) return sorted;
|
|
2773
|
+
return Array.from({ length: SAMPLE }, (_, i) => sorted[Math.floor(i * sorted.length / SAMPLE)]);
|
|
2774
|
+
}
|
|
2775
|
+
var SYNCABLE_RULE = "a syncable image has your definition file at its WORKDIR, as `COPY . .` gives you";
|
|
2776
|
+
function syncabilityLine(params) {
|
|
2777
|
+
const docker2 = params.docker ?? realDocker;
|
|
2778
|
+
const git3 = spawnSync3("git", ["ls-files", "-s", "-z"], { cwd: params.definitionDir, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
|
|
2779
|
+
if (git3.status !== 0) return "Sync: not checked (the definition is not in a git repository).";
|
|
2780
|
+
const tracked = git3.stdout.split("\0").flatMap((entry) => /^(100644|100755) [0-9a-f]+ \d\t(.*)$/s.exec(entry)?.[2] ?? []).filter((path) => path !== "cofactor.sandbox.yaml").filter((path) => existsSync3(join3(params.definitionDir, path)));
|
|
2781
|
+
const temp = mkdtempSync2(join3(tmpdir2(), "cofactor-syncable-"));
|
|
2782
|
+
const created = docker2(["create", params.image]);
|
|
2783
|
+
if (created.status !== 0) {
|
|
2784
|
+
rmSync3(temp, { recursive: true, force: true });
|
|
2785
|
+
return `Sync: not checked (docker create failed: ${created.stderr.trim()}).`;
|
|
2786
|
+
}
|
|
2787
|
+
const container = created.stdout.trim();
|
|
2788
|
+
let copies = 0;
|
|
2538
2789
|
try {
|
|
2539
|
-
const
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
const
|
|
2545
|
-
|
|
2790
|
+
const copyOut = (path) => {
|
|
2791
|
+
const target = join3(temp, String(copies++));
|
|
2792
|
+
if (docker2(["cp", "-L", `${container}:${params.workdir}/${path}`, target]).status !== 0) return null;
|
|
2793
|
+
return statSync(target).isFile() ? readFileSync5(target) : null;
|
|
2794
|
+
};
|
|
2795
|
+
const anchor = copyOut("cofactor.sandbox.yaml");
|
|
2796
|
+
if (!anchor) {
|
|
2797
|
+
return `Sync: not syncable: cofactor.sandbox.yaml isn't at ${params.workdir}/cofactor.sandbox.yaml (${SYNCABLE_RULE}).`;
|
|
2798
|
+
}
|
|
2799
|
+
const sample = sampleOf(tracked);
|
|
2800
|
+
let differ = 0;
|
|
2801
|
+
let missing = 0;
|
|
2802
|
+
for (const path of sample) {
|
|
2803
|
+
const bytes = copyOut(path);
|
|
2804
|
+
if (!bytes) missing += 1;
|
|
2805
|
+
else if (gitBlobId(bytes) !== gitBlobId(readFileSync5(join3(params.definitionDir, path)))) differ += 1;
|
|
2806
|
+
}
|
|
2807
|
+
const anchorOlder = gitBlobId(anchor) !== gitBlobId(readFileSync5(params.definitionFile));
|
|
2808
|
+
const notes = [
|
|
2809
|
+
...anchorOlder || differ > 0 ? ["the image is older than your working tree, which is fine"] : [],
|
|
2810
|
+
...missing > 0 ? [`${missing} of ${sample.length} sampled files aren't in the image (fine for sources it doesn't ship)`] : []
|
|
2811
|
+
];
|
|
2812
|
+
return `Sync: syncable${notes.length > 0 ? ` (${notes.join("; ")})` : ""}.`;
|
|
2546
2813
|
} finally {
|
|
2814
|
+
docker2(["rm", container]);
|
|
2547
2815
|
rmSync3(temp, { recursive: true, force: true });
|
|
2548
2816
|
}
|
|
2549
2817
|
}
|
|
2550
|
-
|
|
2551
|
-
function withRawBlobs(files, top, prefix, env) {
|
|
2552
|
-
if (files.length === 0) return files;
|
|
2553
|
-
const options = { cwd: top, ...env ? { env } : {} };
|
|
2554
|
-
const repoPaths = files.map((file) => prefix === "" ? file.path : `${prefix}/${file.path}`);
|
|
2555
|
-
const autocrlf = git(["config", "--get", "core.autocrlf"], { ...options, okCodes: [0, 1] }).trim();
|
|
2556
|
-
const converts = autocrlf === "input" || autocrlf !== "" && git(["config", "--get", "--type=bool", "core.autocrlf"], options).trim() === "true";
|
|
2557
|
-
let filtered;
|
|
2558
|
-
if (converts) {
|
|
2559
|
-
filtered = new Set(repoPaths);
|
|
2560
|
-
} else {
|
|
2561
|
-
const out = git(["check-attr", "-z", "--stdin", ...FILTER_ATTRIBUTES], { ...options, input: repoPaths.map((path) => `${path}\0`).join("") }).split("\0");
|
|
2562
|
-
filtered = /* @__PURE__ */ new Set();
|
|
2563
|
-
for (let i = 0; i + 2 < out.length; i += 3) if (out[i + 2] !== "unspecified") filtered.add(out[i]);
|
|
2564
|
-
}
|
|
2565
|
-
return files.map(
|
|
2566
|
-
(file, i) => filtered.has(repoPaths[i]) ? { ...file, blob: gitBlobId(readFileSync5(join3(top, repoPaths[i]))) } : file
|
|
2567
|
-
);
|
|
2568
|
-
}
|
|
2569
|
-
function lstatOrNull(path) {
|
|
2818
|
+
function syncabilityLineOrSkipped(params) {
|
|
2570
2819
|
try {
|
|
2571
|
-
|
|
2820
|
+
const definitionFile2 = resolve3(params.definitionFile);
|
|
2821
|
+
return syncabilityLine({ ...params, definitionFile: definitionFile2, definitionDir: dirname(definitionFile2) });
|
|
2572
2822
|
} catch (error) {
|
|
2573
|
-
|
|
2574
|
-
throw error;
|
|
2823
|
+
return `Sync: not checked (${error instanceof Error ? error.message : String(error)}).`;
|
|
2575
2824
|
}
|
|
2576
2825
|
}
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
}
|
|
2595
|
-
});
|
|
2596
|
-
return seen;
|
|
2597
|
-
}
|
|
2598
|
-
async function chooseDeletions(params) {
|
|
2599
|
-
const repoPath = (path) => params.prefix === "" ? path : `${params.prefix}/${path}`;
|
|
2600
|
-
const ignored = ignoredPaths(params.repo, params.env, params.copyOnly.map((entry) => repoPath(entry.path)));
|
|
2601
|
-
const candidates = params.copyOnly.filter((entry) => !ignored.has(repoPath(entry.path)));
|
|
2602
|
-
const history = everTracked(params.repo, candidates.map((entry) => repoPath(entry.path)));
|
|
2603
|
-
const kept = params.copyOnly.filter((entry) => ignored.has(repoPath(entry.path))).map((entry) => ({ ...entry, why: "ignored here" }));
|
|
2604
|
-
const remove = [];
|
|
2605
|
-
for (const entry of candidates) {
|
|
2606
|
-
if (!entry.path.endsWith("/")) {
|
|
2607
|
-
if (history.has(repoPath(entry.path))) remove.push(entry.path);
|
|
2608
|
-
else kept.push({ ...entry, why: "never tracked here" });
|
|
2609
|
-
continue;
|
|
2610
|
-
}
|
|
2611
|
-
const tracked = [...history].some((path) => path.startsWith(repoPath(entry.path)));
|
|
2612
|
-
const listed = tracked ? await params.listDir(entry.path.slice(0, -1)) : [];
|
|
2613
|
-
const ignoredInside = ignoredPaths(params.repo, params.env, listed.map(repoPath));
|
|
2614
|
-
const files = listed.filter((path) => !ignoredInside.has(repoPath(path)) && history.has(repoPath(path)));
|
|
2615
|
-
remove.push(...files);
|
|
2616
|
-
if (files.length < entry.files) kept.push({ path: entry.path, files: entry.files - files.length, why: "never tracked here" });
|
|
2826
|
+
|
|
2827
|
+
// src/sandbox-push.ts
|
|
2828
|
+
var USAGE = [
|
|
2829
|
+
"Usage: cofactor sandbox push --application <name|id> <image> [--definition <file>] [--tag <tag>]... [--wait] [--json]",
|
|
2830
|
+
"",
|
|
2831
|
+
"Tag main builds `main` (or `nightly`): once an application has a push with either tag, its head is the",
|
|
2832
|
+
"newest such push, and only those runs move the visual-review baseline. Other pushes are claimed with --at."
|
|
2833
|
+
].join("\n");
|
|
2834
|
+
var UPLOAD_CONCURRENCY = 4;
|
|
2835
|
+
var UPLOAD_ATTEMPTS = 3;
|
|
2836
|
+
var HASH_CHUNK_BYTES = 8 * 1024 * 1024;
|
|
2837
|
+
var IMPORT_TIMEOUT_MS = 50 * 6e4;
|
|
2838
|
+
var IMPORT_POLL_MS = 5e3;
|
|
2839
|
+
var PushError = class extends Error {
|
|
2840
|
+
constructor(message, issues = []) {
|
|
2841
|
+
super(message);
|
|
2842
|
+
this.issues = issues;
|
|
2617
2843
|
}
|
|
2618
|
-
|
|
2844
|
+
issues;
|
|
2845
|
+
};
|
|
2846
|
+
function fail(message, issues) {
|
|
2847
|
+
throw new PushError(message, issues);
|
|
2619
2848
|
}
|
|
2620
|
-
function
|
|
2621
|
-
const
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
for (const file of files) {
|
|
2626
|
-
if (cost(file) > budget) throw new SyncError(`${file.path} is ${Math.ceil(file.bytes.length / 1024 / 1024)} MiB; a sync sends files of at most ${budget / 1024 / 1024} MiB`);
|
|
2627
|
-
if (size + cost(file) > budget && batches.at(-1).length > 0) {
|
|
2628
|
-
batches.push([]);
|
|
2629
|
-
size = 0;
|
|
2630
|
-
}
|
|
2631
|
-
batches.at(-1).push(file);
|
|
2632
|
-
size += cost(file);
|
|
2633
|
-
}
|
|
2634
|
-
return batches.map((batch, index) => {
|
|
2635
|
-
const manifest = {
|
|
2636
|
-
files: batch.map(({ path, blob, mode }) => ({ path, blob, mode })),
|
|
2637
|
-
delete: index === 0 ? remove : []
|
|
2638
|
-
};
|
|
2639
|
-
return tarOf([
|
|
2640
|
-
{ name: "manifest.json", data: new TextEncoder().encode(JSON.stringify(manifest)) },
|
|
2641
|
-
...batch.map((file, i) => ({ name: syncApplyEntryName(i), data: file.bytes }))
|
|
2642
|
-
]);
|
|
2849
|
+
function docker(args, options) {
|
|
2850
|
+
const result = spawnSync4("docker", args, {
|
|
2851
|
+
encoding: "utf8",
|
|
2852
|
+
stdio: ["ignore", "pipe", options.quiet ? "pipe" : "inherit"],
|
|
2853
|
+
maxBuffer: 64 * 1024 * 1024
|
|
2643
2854
|
});
|
|
2855
|
+
if (result.error) fail(`could not run docker (${result.error.message}); is Docker installed and running?`);
|
|
2856
|
+
if (result.status !== 0) fail(`docker ${args.slice(0, 2).join(" ")} failed:
|
|
2857
|
+
${result.stderr ?? ""}`.trimEnd());
|
|
2858
|
+
return result.stdout;
|
|
2644
2859
|
}
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2860
|
+
function imageConfigOf(image) {
|
|
2861
|
+
const result = spawnSync4("docker", ["image", "inspect", image], { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
|
|
2862
|
+
if (result.error) fail(`could not run docker (${result.error.message}); is Docker installed and running?`);
|
|
2863
|
+
const [inspected] = result.status === 0 ? JSON.parse(result.stdout) : [];
|
|
2864
|
+
if (!inspected) fail(`Docker has no local image ${image}: build or pull it first`);
|
|
2865
|
+
if (inspected.Os !== "linux" || inspected.Architecture !== "amd64") {
|
|
2866
|
+
fail(
|
|
2867
|
+
`${image} is ${inspected.Os}/${inspected.Architecture}; sandboxes run linux/amd64. Rebuild it with
|
|
2868
|
+
docker buildx build --platform linux/amd64 -t ${image} --load <context>`
|
|
2869
|
+
);
|
|
2655
2870
|
}
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2871
|
+
const user = inspected.Config.User ?? "";
|
|
2872
|
+
if (!["", "0", "root"].includes(user.split(":")[0])) {
|
|
2873
|
+
fail(
|
|
2874
|
+
`${image} runs as USER ${user}; a sandbox image must leave USER as root (the sandbox drops to uid 1000 itself). Remove the final \`USER\` line from the Dockerfile, or end it with \`USER root\`.`
|
|
2875
|
+
);
|
|
2876
|
+
}
|
|
2877
|
+
const env = Object.fromEntries(
|
|
2878
|
+
(inspected.Config.Env ?? []).map((entry) => {
|
|
2879
|
+
const separator = entry.indexOf("=");
|
|
2880
|
+
return [entry.slice(0, separator), entry.slice(separator + 1)];
|
|
2881
|
+
})
|
|
2882
|
+
);
|
|
2883
|
+
const parsed = imageConfigSchema.safeParse({ workdir: inspected.Config.WorkingDir ?? "", env });
|
|
2884
|
+
if (!parsed.success) {
|
|
2885
|
+
const workdir = inspected.Config.WorkingDir || "(none)";
|
|
2886
|
+
fail(
|
|
2887
|
+
`${image}'s WORKDIR ${workdir} can't be used: ${parsed.error.issues[0]?.message ?? "invalid"}. Set one for your app in its Dockerfile (e.g. \`WORKDIR /app\`): services, prepare commands and actions run from it, and the sandbox makes it writable by the app.`
|
|
2888
|
+
);
|
|
2664
2889
|
}
|
|
2665
|
-
return
|
|
2890
|
+
return parsed.data;
|
|
2666
2891
|
}
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2892
|
+
function shellWord(value) {
|
|
2893
|
+
return /^[\w@%+=:,./-]+$/.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
|
|
2894
|
+
}
|
|
2895
|
+
async function apiOrFail(result) {
|
|
2896
|
+
const res = await result;
|
|
2897
|
+
if (!res.ok) fail(res.error ?? "unknown error", res.issues);
|
|
2898
|
+
return res.data;
|
|
2899
|
+
}
|
|
2900
|
+
var mib = (bytes) => `${(bytes / 1024 / 1024).toFixed(bytes < 10 * 1024 * 1024 ? 1 : 0)} MB`;
|
|
2901
|
+
async function readAt(file, offset, size) {
|
|
2902
|
+
const bytes = new Uint8Array(size);
|
|
2903
|
+
for (let at = 0; at < size; ) {
|
|
2904
|
+
const { bytesRead } = await file.read(bytes, at, size - at, offset + at);
|
|
2905
|
+
if (bytesRead === 0) return bytes.subarray(0, at);
|
|
2906
|
+
at += bytesRead;
|
|
2673
2907
|
}
|
|
2674
|
-
|
|
2675
|
-
return parseAnswer(syncPlanResponseSchema, await response.text(), "/sync/plan");
|
|
2908
|
+
return bytes;
|
|
2676
2909
|
}
|
|
2677
|
-
function
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
} catch {
|
|
2682
|
-
throw new SyncError(`the sandbox answered ${path} with something other than JSON: ${text.slice(0, 300)}`);
|
|
2910
|
+
async function digestOf(file, offset, size) {
|
|
2911
|
+
const hash = createHash4("sha256");
|
|
2912
|
+
for (let at = 0; at < size; at += HASH_CHUNK_BYTES) {
|
|
2913
|
+
hash.update(await readAt(file, offset + at, Math.min(HASH_CHUNK_BYTES, size - at)));
|
|
2683
2914
|
}
|
|
2684
|
-
|
|
2685
|
-
if (!parsed.success) throw new SyncError(`the sandbox answered ${path} unexpectedly: ${text.slice(0, 300)}`);
|
|
2686
|
-
return parsed.data;
|
|
2915
|
+
return `sha256:${hash.digest("hex")}`;
|
|
2687
2916
|
}
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
const
|
|
2693
|
-
for (
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2917
|
+
function partOf(bytes) {
|
|
2918
|
+
return { digest: `sha256:${createHash4("sha256").update(bytes).digest("hex")}`, size: bytes.length, body: () => bytes };
|
|
2919
|
+
}
|
|
2920
|
+
async function putBlob(url, headers, part) {
|
|
2921
|
+
const digest = part.digest;
|
|
2922
|
+
for (let attempt = 1; ; attempt++) {
|
|
2923
|
+
let response;
|
|
2924
|
+
try {
|
|
2925
|
+
const init = {
|
|
2926
|
+
method: "PUT",
|
|
2927
|
+
headers: { ...headers, "content-length": String(part.size) },
|
|
2928
|
+
body: part.body(),
|
|
2929
|
+
duplex: "half"
|
|
2930
|
+
};
|
|
2931
|
+
response = await fetch(url, init);
|
|
2932
|
+
} catch (error) {
|
|
2933
|
+
if (attempt >= UPLOAD_ATTEMPTS) throw error;
|
|
2934
|
+
await new Promise((resolve4) => setTimeout(resolve4, 2e3 * attempt));
|
|
2700
2935
|
continue;
|
|
2701
2936
|
}
|
|
2702
|
-
|
|
2703
|
-
const
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
buffer += decoder2.decode(chunk, { stream: true });
|
|
2707
|
-
let newline = buffer.indexOf("\n");
|
|
2708
|
-
while (newline >= 0) {
|
|
2709
|
-
const event = parseAnswer(syncBuildEventSchema, buffer.slice(0, newline), "/sync/apply?build=1");
|
|
2710
|
-
buffer = buffer.slice(newline + 1);
|
|
2711
|
-
newline = buffer.indexOf("\n");
|
|
2712
|
-
if (event.type === "applied") {
|
|
2713
|
-
written += event.result.written;
|
|
2714
|
-
deleted += event.result.deleted;
|
|
2715
|
-
} else if (event.type === "output") {
|
|
2716
|
-
if (!json) process6.stderr.write(event.text);
|
|
2717
|
-
} else if (event.type === "step") {
|
|
2718
|
-
steps.push({ name: event.name, ran: event.ran, ms: event.ms });
|
|
2719
|
-
if (!json) process6.stderr.write(` build step ${event.name}: ${event.ran ? `ran in ${(event.ms / 1e3).toFixed(1)} s` : "unchanged"}
|
|
2720
|
-
`);
|
|
2721
|
-
} else if (event.type === "done") {
|
|
2722
|
-
restarted = event.restarted;
|
|
2723
|
-
finished = true;
|
|
2724
|
-
} else if (event.type === "error") {
|
|
2725
|
-
throw new SyncError(`the build failed after the files were synced: ${event.message}`);
|
|
2726
|
-
}
|
|
2727
|
-
}
|
|
2937
|
+
if (response.ok) return;
|
|
2938
|
+
const detail = `${response.status}: ${(await response.text()).slice(0, 300)}`;
|
|
2939
|
+
if (response.status < 500 || attempt >= UPLOAD_ATTEMPTS) {
|
|
2940
|
+
fail(`uploading ${digest} was refused (HTTP ${detail}); run the push again to upload what is still missing`);
|
|
2728
2941
|
}
|
|
2729
|
-
|
|
2942
|
+
await new Promise((resolve4) => setTimeout(resolve4, 2e3 * attempt));
|
|
2730
2943
|
}
|
|
2731
|
-
return { written, deleted, restarted, steps };
|
|
2732
2944
|
}
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
async function handleSandboxSync(positionals, argv, apiUrl, token) {
|
|
2736
|
-
const [id, localDir] = positionals;
|
|
2737
|
-
const to = getArgValue(argv, "--to");
|
|
2738
|
-
const build = hasFlag(argv, "--build");
|
|
2739
|
-
const json = hasFlag(argv, "--json");
|
|
2740
|
-
const fail4 = (message) => {
|
|
2741
|
-
console.error(`Error: ${message}`);
|
|
2742
|
-
process6.exit(1);
|
|
2743
|
-
};
|
|
2744
|
-
if (!id) return fail4(`sandbox id required.
|
|
2745
|
-
${SYNC_USAGE}`);
|
|
2746
|
-
if (localDir === void 0 !== (to === void 0)) return fail4(`a local directory and --to go together.
|
|
2747
|
-
${SYNC_USAGE}`);
|
|
2748
|
-
if (to !== void 0 && build) return fail4("--build reruns the build of a working-tree sync; build output is already built (`sync <id> <dir> --to <path>`).");
|
|
2749
|
-
if (to !== void 0 && (to === "" || to.startsWith("/") || to.split("/").some((part) => part === "" || part === "." || part === ".."))) {
|
|
2750
|
-
return fail4("--to is a path under the sandbox's sync root, like `static` or `frontend/dist` (no leading `/`, no `..`).");
|
|
2751
|
-
}
|
|
2752
|
-
if (localDir !== void 0 && (!existsSync2(localDir) || !lstatSync(localDir).isDirectory())) return fail4(`${localDir} is not a directory`);
|
|
2753
|
-
if (!isInsideRepo(to === void 0 ? process6.cwd() : resolve2(localDir))) {
|
|
2754
|
-
return fail4("run this inside a git repository: the files to sync and the synced tree come from git.");
|
|
2755
|
-
}
|
|
2756
|
-
const started = Date.now();
|
|
2757
|
-
const sessionRes = await apiRequest(
|
|
2758
|
-
apiUrl,
|
|
2759
|
-
token,
|
|
2760
|
-
"POST",
|
|
2761
|
-
`/org/sandboxes-v2/sandboxes/${id}/sync-session`
|
|
2762
|
-
);
|
|
2763
|
-
if (!sessionRes.ok) return fail4(sessionRes.error ?? "could not start a sync");
|
|
2764
|
-
const session = sessionRes.data;
|
|
2945
|
+
async function savedImage(archive) {
|
|
2946
|
+
const file = await open(archive);
|
|
2765
2947
|
try {
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2948
|
+
const entries = await tarEntries((offset, length) => readAt(file, offset, length)).catch(
|
|
2949
|
+
(error) => fail(`reading the saved image: ${error instanceof Error ? error.message : String(error)}`)
|
|
2950
|
+
);
|
|
2951
|
+
const byName = new Map(entries.map((entry) => [entry.name.replace(/^\.\//, ""), entry]));
|
|
2952
|
+
const entryOf = (name) => {
|
|
2953
|
+
const entry = byName.get(name);
|
|
2954
|
+
if (!entry) fail(`the saved image has no ${name}; is this a \`docker save\` archive?`);
|
|
2955
|
+
return entry;
|
|
2769
2956
|
};
|
|
2770
|
-
|
|
2771
|
-
const
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
const
|
|
2777
|
-
if (
|
|
2778
|
-
|
|
2957
|
+
const json = async (name) => {
|
|
2958
|
+
const entry = entryOf(name);
|
|
2959
|
+
return JSON.parse(new TextDecoder().decode(await readAt(file, entry.offset, entry.size)));
|
|
2960
|
+
};
|
|
2961
|
+
const layerOf = async (name) => {
|
|
2962
|
+
const entry = entryOf(name);
|
|
2963
|
+
const tooLarge = (bytes) => fail(`a layer of the image is ${(bytes / 1024 ** 3).toFixed(1)} GB; a push takes at most 5 GB per layer. Slim the image.`);
|
|
2964
|
+
if (entry.size > MAX_BLOB_BYTES) tooLarge(entry.size);
|
|
2965
|
+
const range = { start: entry.offset, end: entry.offset + entry.size - 1 };
|
|
2966
|
+
const [first, second] = await readAt(file, entry.offset, 2);
|
|
2967
|
+
if (first === 31 && second === 139) {
|
|
2968
|
+
const unpacked = join4(dirname2(archive), `layer-${entry.offset}.tar`);
|
|
2969
|
+
await pipeline(createReadStream(archive, range), createGunzip(), createWriteStream(unpacked)).catch(
|
|
2970
|
+
(error) => fail(`unpacking a compressed layer of the image: ${error instanceof Error ? error.message : String(error)}`)
|
|
2971
|
+
);
|
|
2972
|
+
const plain = await open(unpacked);
|
|
2973
|
+
try {
|
|
2974
|
+
const { size } = await plain.stat();
|
|
2975
|
+
if (size > MAX_BLOB_BYTES) tooLarge(size);
|
|
2976
|
+
return { digest: await digestOf(plain, 0, size), size, body: () => createReadStream(unpacked) };
|
|
2977
|
+
} finally {
|
|
2978
|
+
await plain.close();
|
|
2979
|
+
}
|
|
2779
2980
|
}
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2981
|
+
return { digest: await digestOf(file, entry.offset, entry.size), size: entry.size, body: () => createReadStream(archive, range) };
|
|
2982
|
+
};
|
|
2983
|
+
const [manifest] = await json("manifest.json");
|
|
2984
|
+
if (!manifest) fail("the saved image lists no image");
|
|
2985
|
+
const layers = [];
|
|
2986
|
+
for (const name of manifest.Layers) layers.push(await layerOf(name));
|
|
2987
|
+
return { config: await json(manifest.Config), layers };
|
|
2988
|
+
} finally {
|
|
2989
|
+
await file.close();
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
async function pushedImage(archive, cofactorLayers, definitionYaml, config) {
|
|
2993
|
+
const saved = await savedImage(archive);
|
|
2994
|
+
const own = partOf(
|
|
2995
|
+
tarOf([
|
|
2996
|
+
{ name: IMAGE_DEFINITION_FILE.slice(1), data: new TextEncoder().encode(definitionYaml) },
|
|
2997
|
+
{ name: IMAGE_CONFIG_FILE.slice(1), data: new TextEncoder().encode(JSON.stringify(config)) }
|
|
2998
|
+
])
|
|
2999
|
+
);
|
|
3000
|
+
const added = [...cofactorLayers, own];
|
|
3001
|
+
const pushed = withSandboxLayers(saved.config, added, (/* @__PURE__ */ new Date()).toISOString());
|
|
3002
|
+
const imageConfig = partOf(new TextEncoder().encode(JSON.stringify(pushed)));
|
|
3003
|
+
const blob = ({ digest, size }) => ({ digest, size });
|
|
3004
|
+
return {
|
|
3005
|
+
config: blob(imageConfig),
|
|
3006
|
+
layers: [...saved.layers, ...added].map(blob),
|
|
3007
|
+
blobs: new Map([imageConfig, ...saved.layers, own].map((part) => [part.digest, part]))
|
|
3008
|
+
};
|
|
3009
|
+
}
|
|
3010
|
+
async function handleSandboxPush(argv, apiUrl, token) {
|
|
3011
|
+
try {
|
|
3012
|
+
await push(argv, apiUrl, token);
|
|
3013
|
+
} catch (error) {
|
|
3014
|
+
if (!(error instanceof PushError)) throw error;
|
|
3015
|
+
console.error(`Error: ${error.message}`);
|
|
3016
|
+
for (const issue of error.issues) console.error(` ${issue.path || "(root)"}: ${issue.message}`);
|
|
3017
|
+
process7.exit(1);
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
async function push(argv, apiUrl, token) {
|
|
3021
|
+
const valueFlags = /* @__PURE__ */ new Set(["--application", "--property", "--definition", "--tag", "--env", "--org", "--token", "--api-url", "--app-url"]);
|
|
3022
|
+
const positional = argv.filter((arg, i) => !arg.startsWith("-") && !valueFlags.has(argv[i - 1] ?? ""));
|
|
3023
|
+
const image = positional[0];
|
|
3024
|
+
if (!image) fail(`an image is required
|
|
3025
|
+
${USAGE}`);
|
|
3026
|
+
const definitionFile2 = getArgValue(argv, "--definition") ?? "./cofactor.sandbox.yaml";
|
|
3027
|
+
const tags = getArgValues(argv, "--tag");
|
|
3028
|
+
const badTag = tags.find((tag) => !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(tag));
|
|
3029
|
+
if (badTag !== void 0) fail(`--tag ${badTag}: use letters, digits, '.', '_' and '-' (at most 64, starting with a letter or digit)`);
|
|
3030
|
+
if (tags.length > 10) fail("a push takes at most 10 tags");
|
|
3031
|
+
const wait = hasFlag(argv, "--wait");
|
|
3032
|
+
const json = hasFlag(argv, "--json");
|
|
3033
|
+
const log = (line) => json ? console.error(line) : console.log(line);
|
|
3034
|
+
let definitionYaml;
|
|
3035
|
+
try {
|
|
3036
|
+
definitionYaml = readFileSync6(definitionFile2, "utf8");
|
|
3037
|
+
} catch (error) {
|
|
3038
|
+
fail(`reading ${definitionFile2}: ${error instanceof Error ? error.message : String(error)}`);
|
|
3039
|
+
}
|
|
3040
|
+
const { definition, issues } = validateDefinition(definitionYaml, "upload");
|
|
3041
|
+
if (!definition || issues.length > 0) fail(`${definitionFile2} is not a valid definition for pushed images:`, issues);
|
|
3042
|
+
const storageProvider = definition.object_storage?.provider;
|
|
3043
|
+
const applicationId = await resolveOrCreateSandboxApplication(argv, apiUrl, token);
|
|
3044
|
+
const config = imageConfigOf(image);
|
|
3045
|
+
log(syncabilityLineOrSkipped({ image, workdir: config.workdir, definitionFile: definitionFile2 }));
|
|
3046
|
+
const work = mkdtempSync3(join4(tmpdir3(), "cofactor-push-"));
|
|
3047
|
+
const cleanUp = () => rmSync4(work, { recursive: true, force: true });
|
|
3048
|
+
const interrupted = (signal) => {
|
|
3049
|
+
cleanUp();
|
|
3050
|
+
process7.exit(signal === "SIGINT" ? 130 : 143);
|
|
3051
|
+
};
|
|
3052
|
+
process7.once("SIGINT", interrupted);
|
|
3053
|
+
process7.once("SIGTERM", interrupted);
|
|
3054
|
+
process7.exitCode = 1;
|
|
3055
|
+
try {
|
|
3056
|
+
const ours = await apiOrFail(
|
|
3057
|
+
apiRequest(
|
|
3058
|
+
apiUrl,
|
|
3059
|
+
token,
|
|
3060
|
+
"GET",
|
|
3061
|
+
"/org/sandboxes-v2/helper-layer"
|
|
3062
|
+
)
|
|
3063
|
+
);
|
|
3064
|
+
if (!ours.helper) fail("this Cofactor takes pushes from an older cofactor CLI; use the version it ships with");
|
|
3065
|
+
const cofactorLayers = [ours.helper, ...storageProvider ? [ours.objectStorage[storageProvider]] : []];
|
|
3066
|
+
const archive = join4(work, "image.tar");
|
|
3067
|
+
log("Saving the image...");
|
|
3068
|
+
docker(["save", "--output", archive, image], { quiet: json });
|
|
3069
|
+
const pushing = await pushedImage(archive, cofactorLayers, definitionYaml, config);
|
|
3070
|
+
const { layers } = pushing;
|
|
3071
|
+
const created = await apiOrFail(
|
|
3072
|
+
apiRequest(
|
|
3073
|
+
apiUrl,
|
|
3074
|
+
token,
|
|
3075
|
+
"POST",
|
|
3076
|
+
`/org/applications/${applicationId}/sandbox/uploads`,
|
|
3077
|
+
{ definitionYaml, platformVersion: ours.platformVersion, tags, image: { config: pushing.config, layers } }
|
|
3078
|
+
)
|
|
3079
|
+
);
|
|
3080
|
+
const partsByDigest = pushing.blobs;
|
|
3081
|
+
const uploadBytes = created.uploads.reduce((total, upload) => total + (partsByDigest.get(upload.digest)?.size ?? 0), 0);
|
|
3082
|
+
const imageBytes = layers.reduce((total, layer) => total + layer.size, 0);
|
|
3083
|
+
log(`Pushing ${created.id}: uploading ${created.uploads.length} of ${layers.length + 1} blobs (${mib(uploadBytes)}); ${mib(imageBytes - uploadBytes)} already in Cofactor`);
|
|
3084
|
+
const queue = [...created.uploads];
|
|
3085
|
+
let sent = 0;
|
|
3086
|
+
await Promise.all(
|
|
3087
|
+
Array.from({ length: UPLOAD_CONCURRENCY }, async () => {
|
|
3088
|
+
for (let next = queue.shift(); next; next = queue.shift()) {
|
|
3089
|
+
const part = partsByDigest.get(next.digest);
|
|
3090
|
+
if (!part) fail(`Cofactor asked for ${next.digest}, which this image doesn't have`);
|
|
3091
|
+
await putBlob(next.url, next.headers, part);
|
|
3092
|
+
sent += part.size;
|
|
3093
|
+
if (part.size > 10 * 1024 * 1024) log(` ${mib(sent)} of ${mib(uploadBytes)}`);
|
|
2816
3094
|
}
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
buildOutput: to !== void 0,
|
|
2842
|
-
written: sending.map((file) => file.path),
|
|
2843
|
-
deleted: remove,
|
|
2844
|
-
kept,
|
|
2845
|
-
build: build ? { steps: applied.steps, restarted: applied.restarted } : null,
|
|
2846
|
-
ms: { total: ms, plan: planMs, apply: applyMs }
|
|
2847
|
-
},
|
|
2848
|
-
null,
|
|
2849
|
-
2
|
|
2850
|
-
)
|
|
3095
|
+
})
|
|
3096
|
+
);
|
|
3097
|
+
rmSync4(archive);
|
|
3098
|
+
let pushed = await apiOrFail(
|
|
3099
|
+
apiRequest(
|
|
3100
|
+
apiUrl,
|
|
3101
|
+
token,
|
|
3102
|
+
"POST",
|
|
3103
|
+
`/org/applications/${applicationId}/sandbox/uploads/${created.id}/complete`
|
|
3104
|
+
)
|
|
3105
|
+
);
|
|
3106
|
+
if (wait) {
|
|
3107
|
+
log("Importing (Cofactor boots the image until its services are healthy)...");
|
|
3108
|
+
const deadline = Date.now() + IMPORT_TIMEOUT_MS;
|
|
3109
|
+
const importStarted = Date.now();
|
|
3110
|
+
let reported = importStarted;
|
|
3111
|
+
while ((pushed.status === "importing" || pushed.status === "uploading") && Date.now() < deadline) {
|
|
3112
|
+
await new Promise((resolve4) => setTimeout(resolve4, IMPORT_POLL_MS));
|
|
3113
|
+
if (Date.now() - reported >= 6e4) {
|
|
3114
|
+
reported = Date.now();
|
|
3115
|
+
log(` still importing (${Math.round((reported - importStarted) / 6e4)} min)`);
|
|
3116
|
+
}
|
|
3117
|
+
pushed = await apiOrFail(
|
|
3118
|
+
apiRequest(apiUrl, token, "GET", `/org/applications/${applicationId}/sandbox/uploads/${created.id}`)
|
|
2851
3119
|
);
|
|
2852
|
-
return;
|
|
2853
3120
|
}
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
3121
|
+
}
|
|
3122
|
+
const uploaded = { blobs: created.uploads.length, of: layers.length + 1, bytes: uploadBytes, reusedBytes: imageBytes - uploadBytes };
|
|
3123
|
+
const application = shellWord(getArgValue(argv, "--application") ?? getArgValue(argv, "--property") ?? applicationId);
|
|
3124
|
+
const target = ["--env", "--org"].map((flag) => {
|
|
3125
|
+
const value = getArgValue(process7.argv, flag);
|
|
3126
|
+
return value ? ` ${flag} ${shellWord(value)}` : "";
|
|
3127
|
+
}).join("");
|
|
3128
|
+
if (json) {
|
|
3129
|
+
writeRawOutput(JSON.stringify({ ...pushed, uploaded }, null, 2));
|
|
3130
|
+
} else {
|
|
3131
|
+
console.log(`Push ${pushed.id}: ${pushed.status}${pushed.tags.length > 0 ? ` [${pushed.tags.join(", ")}]` : ""}`);
|
|
3132
|
+
if (pushed.status === "ready") {
|
|
3133
|
+
console.log(` Claim it: cofactor sandbox claim --application ${application} --environment default --wait${target}`);
|
|
3134
|
+
} else if (pushed.status !== "failed") {
|
|
3135
|
+
console.log(` Importing; follow it with: cofactor sandbox envs --application ${application}${target}`);
|
|
2859
3136
|
}
|
|
2860
|
-
const shown = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
|
|
2861
|
-
for (const [name, url] of Object.entries(shown.data?.portUrls ?? {})) writeRawOutput(` ${name.padEnd(10)} ${url}`);
|
|
2862
|
-
} finally {
|
|
2863
|
-
cleanup();
|
|
2864
3137
|
}
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
3138
|
+
if (pushed.status === "failed") fail(`push ${pushed.id} failed:
|
|
3139
|
+
${pushed.error ?? "no reason given"}`);
|
|
3140
|
+
process7.exitCode = 0;
|
|
3141
|
+
} finally {
|
|
3142
|
+
process7.off("SIGINT", interrupted);
|
|
3143
|
+
process7.off("SIGTERM", interrupted);
|
|
3144
|
+
cleanUp();
|
|
2868
3145
|
}
|
|
2869
3146
|
}
|
|
2870
|
-
function isInsideRepo(dir) {
|
|
2871
|
-
return git(["rev-parse", "--is-inside-work-tree"], { cwd: dir, okCodes: [0, 128] }).trim() === "true";
|
|
2872
|
-
}
|
|
2873
3147
|
|
|
2874
3148
|
// src/sandbox-refresh.ts
|
|
2875
|
-
import
|
|
3149
|
+
import process8 from "process";
|
|
2876
3150
|
var POLL_INTERVAL_MS = 5e3;
|
|
2877
3151
|
var WAIT_TIMEOUT_MS = 50 * 6e4;
|
|
2878
3152
|
function shortVersion2(version) {
|
|
2879
3153
|
return /^[0-9a-f]{40}$/.test(version) ? version.slice(0, 12) : version;
|
|
2880
3154
|
}
|
|
2881
3155
|
function sleep2(ms) {
|
|
2882
|
-
return new Promise((
|
|
3156
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
2883
3157
|
}
|
|
2884
3158
|
async function handleSandboxRefresh(argv, apiUrl, token) {
|
|
2885
3159
|
const applicationId = await resolveApplicationId(argv, apiUrl, token);
|
|
@@ -2892,7 +3166,7 @@ async function handleSandboxRefresh(argv, apiUrl, token) {
|
|
|
2892
3166
|
});
|
|
2893
3167
|
if (!res.ok) {
|
|
2894
3168
|
console.error(`Error: ${res.error}`);
|
|
2895
|
-
|
|
3169
|
+
process8.exit(1);
|
|
2896
3170
|
}
|
|
2897
3171
|
return res.data;
|
|
2898
3172
|
};
|
|
@@ -2912,7 +3186,7 @@ async function handleSandboxRefresh(argv, apiUrl, token) {
|
|
|
2912
3186
|
);
|
|
2913
3187
|
if (!res.ok) {
|
|
2914
3188
|
console.error(`Error: ${res.error}`);
|
|
2915
|
-
|
|
3189
|
+
process8.exit(1);
|
|
2916
3190
|
}
|
|
2917
3191
|
if (res.data.status === "ready" && !res.data.isHead && !askedAgain) {
|
|
2918
3192
|
askedAgain = true;
|
|
@@ -2942,11 +3216,11 @@ async function handleSandboxRefresh(argv, apiUrl, token) {
|
|
|
2942
3216
|
}
|
|
2943
3217
|
}
|
|
2944
3218
|
}
|
|
2945
|
-
if (latest.head.status === "failed" || wait && latest.head.status === "building")
|
|
3219
|
+
if (latest.head.status === "failed" || wait && latest.head.status === "building") process8.exit(1);
|
|
2946
3220
|
}
|
|
2947
3221
|
|
|
2948
3222
|
// src/sandbox-warm.ts
|
|
2949
|
-
import
|
|
3223
|
+
import process9 from "process";
|
|
2950
3224
|
var STATE_LABELS = {
|
|
2951
3225
|
warming: "Filling",
|
|
2952
3226
|
warm: "On",
|
|
@@ -2973,7 +3247,7 @@ function isSandboxV2WarmCommand(first) {
|
|
|
2973
3247
|
}
|
|
2974
3248
|
function fail2(message) {
|
|
2975
3249
|
console.error(`Error: ${message}`);
|
|
2976
|
-
|
|
3250
|
+
process9.exit(1);
|
|
2977
3251
|
}
|
|
2978
3252
|
function rejectUnknownWarmArgs(command, argv) {
|
|
2979
3253
|
const spec = COMMANDS[command];
|
|
@@ -3047,7 +3321,7 @@ async function handleSandboxWarm(argv, apiUrl, token) {
|
|
|
3047
3321
|
if (!command || !isSandboxV2WarmCommand(command)) {
|
|
3048
3322
|
console.error(command ? `Unknown warm command: ${command}
|
|
3049
3323
|
${USAGE2}` : USAGE2);
|
|
3050
|
-
|
|
3324
|
+
process9.exit(1);
|
|
3051
3325
|
}
|
|
3052
3326
|
rejectUnknownWarmArgs(command, rest);
|
|
3053
3327
|
if (command === "list") {
|
|
@@ -3078,7 +3352,7 @@ ${USAGE2}` : USAGE2);
|
|
|
3078
3352
|
}
|
|
3079
3353
|
|
|
3080
3354
|
// src/sandbox-secrets.ts
|
|
3081
|
-
import
|
|
3355
|
+
import process10 from "process";
|
|
3082
3356
|
function scopeText(environment2) {
|
|
3083
3357
|
return environment2 === null ? "all environments" : `environment ${environment2}`;
|
|
3084
3358
|
}
|
|
@@ -3102,7 +3376,7 @@ function stdinRefusal(isTTY) {
|
|
|
3102
3376
|
}
|
|
3103
3377
|
function fail3(message) {
|
|
3104
3378
|
console.error(`Error: ${message}`);
|
|
3105
|
-
|
|
3379
|
+
process10.exit(1);
|
|
3106
3380
|
}
|
|
3107
3381
|
function decodeSecretValue(bytes) {
|
|
3108
3382
|
let text;
|
|
@@ -3116,7 +3390,7 @@ function decodeSecretValue(bytes) {
|
|
|
3116
3390
|
}
|
|
3117
3391
|
async function readStdin() {
|
|
3118
3392
|
const chunks = [];
|
|
3119
|
-
for await (const chunk of
|
|
3393
|
+
for await (const chunk of process10.stdin) chunks.push(chunk);
|
|
3120
3394
|
return Buffer.concat(chunks);
|
|
3121
3395
|
}
|
|
3122
3396
|
function environmentArg(argv) {
|
|
@@ -3132,7 +3406,7 @@ function printUsage() {
|
|
|
3132
3406
|
console.error("Usage: cofactor sandbox secret set <NAME> --application <name|id> [--environment <e>] [--json] (value on stdin)");
|
|
3133
3407
|
console.error(" cofactor sandbox secret list --application <name|id> [--json]");
|
|
3134
3408
|
console.error(" cofactor sandbox secret rm <NAME> --application <name|id> [--environment <e>] [--json]");
|
|
3135
|
-
|
|
3409
|
+
process10.exit(1);
|
|
3136
3410
|
}
|
|
3137
3411
|
async function handleSandboxSecret(argv, apiUrl, token) {
|
|
3138
3412
|
const [action2, name, ...extra] = v2Positionals("secret", argv);
|
|
@@ -3148,10 +3422,10 @@ async function handleSandboxSecret(argv, apiUrl, token) {
|
|
|
3148
3422
|
const json = hasFlag(argv, "--json");
|
|
3149
3423
|
const application = getArgValue(argv, "--application") ?? getArgValue(argv, "--property") ?? "";
|
|
3150
3424
|
if (action2 === "set") {
|
|
3151
|
-
const refusal = stdinRefusal(
|
|
3425
|
+
const refusal = stdinRefusal(process10.stdin.isTTY);
|
|
3152
3426
|
if (refusal) {
|
|
3153
3427
|
console.error(`Error: ${refusal}`);
|
|
3154
|
-
|
|
3428
|
+
process10.exit(1);
|
|
3155
3429
|
}
|
|
3156
3430
|
}
|
|
3157
3431
|
let value;
|
|
@@ -3170,7 +3444,7 @@ async function handleSandboxSecret(argv, apiUrl, token) {
|
|
|
3170
3444
|
);
|
|
3171
3445
|
if (!res2.ok) {
|
|
3172
3446
|
console.error(`Error: ${res2.error}`);
|
|
3173
|
-
|
|
3447
|
+
process10.exit(1);
|
|
3174
3448
|
}
|
|
3175
3449
|
if (json) {
|
|
3176
3450
|
writeRawOutput(JSON.stringify(res2.data, null, 2));
|
|
@@ -3199,7 +3473,7 @@ async function handleSandboxSecret(argv, apiUrl, token) {
|
|
|
3199
3473
|
);
|
|
3200
3474
|
if (!res.ok) {
|
|
3201
3475
|
console.error(`Error: ${res.error}`);
|
|
3202
|
-
|
|
3476
|
+
process10.exit(1);
|
|
3203
3477
|
}
|
|
3204
3478
|
if (json) {
|
|
3205
3479
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -3217,7 +3491,7 @@ var parseEgressMode = (value) => {
|
|
|
3217
3491
|
console.error(
|
|
3218
3492
|
`Error: invalid --egress-mode '${value}'. Must be one of: ${SANDBOX_EGRESS_MODES.join(", ")}`
|
|
3219
3493
|
);
|
|
3220
|
-
|
|
3494
|
+
process11.exit(1);
|
|
3221
3495
|
};
|
|
3222
3496
|
var parseEgressAllowlist = (argv) => {
|
|
3223
3497
|
const rawEntries = getArgValues(argv, "--egress-allow").flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean);
|
|
@@ -3227,13 +3501,13 @@ var parseEgressAllowlist = (argv) => {
|
|
|
3227
3501
|
console.error(
|
|
3228
3502
|
`Error: invalid --egress-allow '${entry}'. Use domain:example.com or cidr:203.0.113.0/24`
|
|
3229
3503
|
);
|
|
3230
|
-
|
|
3504
|
+
process11.exit(1);
|
|
3231
3505
|
}
|
|
3232
3506
|
const kind = entry.slice(0, separator);
|
|
3233
3507
|
const value = entry.slice(separator + 1);
|
|
3234
3508
|
if (kind !== "domain" && kind !== "cidr") {
|
|
3235
3509
|
console.error(`Error: invalid --egress-allow kind '${kind}'. Must be domain or cidr.`);
|
|
3236
|
-
|
|
3510
|
+
process11.exit(1);
|
|
3237
3511
|
}
|
|
3238
3512
|
return { kind, value };
|
|
3239
3513
|
});
|
|
@@ -3290,7 +3564,8 @@ function printHelp() {
|
|
|
3290
3564
|
' sync <id> <local-dir> --to <path> Mirror local build output into <path> in the sandbox (see "Sync options" below)',
|
|
3291
3565
|
" destroy <id> Tear a claimed sandbox down",
|
|
3292
3566
|
' warm list|set|show|clear [options] Keep an environment warm so claims start in about a second (staff only; see "Warm options" below)',
|
|
3293
|
-
"
|
|
3567
|
+
" auth-check <id> --persona <p> Prove sign-in on an existing sandbox",
|
|
3568
|
+
" verify-env --application <a> [options] Claim each environment (optionally --at <sha>), sign its personas in, destroy",
|
|
3294
3569
|
" preview --application <a> --pr <n> A pull request's preview and its logins",
|
|
3295
3570
|
" guide [topic] Show the Sandboxes v2 guide (`guide v2 [topic]` is the same)",
|
|
3296
3571
|
` topics: ${SANDBOX_V2_GUIDE_SECTIONS.map((section) => section.slug).join(", ")}`,
|
|
@@ -3309,6 +3584,9 @@ function printHelp() {
|
|
|
3309
3584
|
" --wait Block until Cofactor has imported (booted) the image",
|
|
3310
3585
|
"",
|
|
3311
3586
|
"Claim options (Sandboxes v2):",
|
|
3587
|
+
" --from-local Claim local tracked and nonignored untracked files without committing or merging",
|
|
3588
|
+
" --repo <owner/name> With --from-local: GitHub repository (default: origin)",
|
|
3589
|
+
" --dir <path> With --from-local: definition directory relative to the repository root (default: cwd)",
|
|
3312
3590
|
' --environment <name> Environment to claim (default: "default")',
|
|
3313
3591
|
" --at <version> A commit SHA, or a push id or tag (default: the default branch head / push head)",
|
|
3314
3592
|
" --timeout <duration> The claim's time limit: not ready by then, it fails (e.g. 45m, 3h; default 2h,",
|
|
@@ -3498,11 +3776,11 @@ async function handleList(argv, apiUrl, token) {
|
|
|
3498
3776
|
]);
|
|
3499
3777
|
if (!templatesRes.ok) {
|
|
3500
3778
|
console.error(`Error: ${templatesRes.error}`);
|
|
3501
|
-
|
|
3779
|
+
process11.exit(1);
|
|
3502
3780
|
}
|
|
3503
3781
|
if (!instancesRes.ok) {
|
|
3504
3782
|
console.error(`Error loading instances: ${instancesRes.error}`);
|
|
3505
|
-
|
|
3783
|
+
process11.exit(1);
|
|
3506
3784
|
}
|
|
3507
3785
|
if (json) {
|
|
3508
3786
|
writeRawOutput(JSON.stringify({ templates: templatesRes.data, instances: instancesRes.data }, null, 2));
|
|
@@ -3550,7 +3828,7 @@ async function handleTemplate(argv, apiUrl, token) {
|
|
|
3550
3828
|
);
|
|
3551
3829
|
if (!res.ok) {
|
|
3552
3830
|
console.error(`Error: ${res.error}`);
|
|
3553
|
-
|
|
3831
|
+
process11.exit(1);
|
|
3554
3832
|
}
|
|
3555
3833
|
if (json) {
|
|
3556
3834
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -3579,7 +3857,7 @@ async function handleArtifacts(argv, apiUrl, token) {
|
|
|
3579
3857
|
const json = hasFlag(argv, "--json");
|
|
3580
3858
|
if (!templateId) {
|
|
3581
3859
|
console.error("Error: template ID required. Usage: canary sandbox artifacts <template-id>");
|
|
3582
|
-
|
|
3860
|
+
process11.exit(1);
|
|
3583
3861
|
}
|
|
3584
3862
|
const res = await apiRequest(
|
|
3585
3863
|
apiUrl,
|
|
@@ -3589,7 +3867,7 @@ async function handleArtifacts(argv, apiUrl, token) {
|
|
|
3589
3867
|
);
|
|
3590
3868
|
if (!res.ok) {
|
|
3591
3869
|
console.error(`Error: ${res.error}`);
|
|
3592
|
-
|
|
3870
|
+
process11.exit(1);
|
|
3593
3871
|
}
|
|
3594
3872
|
if (json) {
|
|
3595
3873
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -3619,7 +3897,7 @@ async function handleChecklist(argv, apiUrl, token) {
|
|
|
3619
3897
|
const templateId = positional[0];
|
|
3620
3898
|
if (!templateId) {
|
|
3621
3899
|
console.error("Error: template ID required. Usage: canary sandbox checklist <template-id>");
|
|
3622
|
-
|
|
3900
|
+
process11.exit(1);
|
|
3623
3901
|
}
|
|
3624
3902
|
const res = await apiRequest(
|
|
3625
3903
|
apiUrl,
|
|
@@ -3629,7 +3907,7 @@ async function handleChecklist(argv, apiUrl, token) {
|
|
|
3629
3907
|
);
|
|
3630
3908
|
if (!res.ok) {
|
|
3631
3909
|
console.error(`Error: ${res.error}`);
|
|
3632
|
-
|
|
3910
|
+
process11.exit(1);
|
|
3633
3911
|
}
|
|
3634
3912
|
if (json) {
|
|
3635
3913
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -3664,7 +3942,7 @@ async function handleChecklistSkip(argv, apiUrl, token) {
|
|
|
3664
3942
|
const itemId = positional[1];
|
|
3665
3943
|
if (!templateId || !itemId) {
|
|
3666
3944
|
console.error("Error: both template ID and item ID required. Usage: canary sandbox checklist skip <template-id> <item-id>");
|
|
3667
|
-
|
|
3945
|
+
process11.exit(1);
|
|
3668
3946
|
}
|
|
3669
3947
|
const res = await apiRequest(
|
|
3670
3948
|
apiUrl,
|
|
@@ -3674,7 +3952,7 @@ async function handleChecklistSkip(argv, apiUrl, token) {
|
|
|
3674
3952
|
);
|
|
3675
3953
|
if (!res.ok) {
|
|
3676
3954
|
console.error(`Error: ${res.error}`);
|
|
3677
|
-
|
|
3955
|
+
process11.exit(1);
|
|
3678
3956
|
}
|
|
3679
3957
|
console.log(`Skipped checklist item: ${itemId}`);
|
|
3680
3958
|
}
|
|
@@ -3686,7 +3964,7 @@ async function handleChecklistComplete(argv, apiUrl, token) {
|
|
|
3686
3964
|
if (!templateId || !itemId) {
|
|
3687
3965
|
console.error("Error: both template ID and item ID required.");
|
|
3688
3966
|
console.error('Usage: canary sandbox checklist complete <template-id> <item-id> [--status success|failed] [--summary "..."]');
|
|
3689
|
-
|
|
3967
|
+
process11.exit(1);
|
|
3690
3968
|
}
|
|
3691
3969
|
const status = getArgValue(argv, "--status") ?? "success";
|
|
3692
3970
|
const summary = getArgValue(argv, "--summary");
|
|
@@ -3698,11 +3976,11 @@ async function handleChecklistComplete(argv, apiUrl, token) {
|
|
|
3698
3976
|
configSchema = JSON.parse(configSchemaArg);
|
|
3699
3977
|
} catch {
|
|
3700
3978
|
try {
|
|
3701
|
-
configSchema = JSON.parse(
|
|
3979
|
+
configSchema = JSON.parse(readFileSync7(configSchemaArg, "utf-8"));
|
|
3702
3980
|
} catch (e) {
|
|
3703
3981
|
const msg = e instanceof Error ? e.message : String(e);
|
|
3704
3982
|
console.error(`Error reading config schema from "${configSchemaArg}": ${msg}`);
|
|
3705
|
-
|
|
3983
|
+
process11.exit(1);
|
|
3706
3984
|
}
|
|
3707
3985
|
}
|
|
3708
3986
|
}
|
|
@@ -3721,7 +3999,7 @@ async function handleChecklistComplete(argv, apiUrl, token) {
|
|
|
3721
3999
|
);
|
|
3722
4000
|
if (!res.ok) {
|
|
3723
4001
|
console.error(`Error: ${res.error}`);
|
|
3724
|
-
|
|
4002
|
+
process11.exit(1);
|
|
3725
4003
|
}
|
|
3726
4004
|
if (json) {
|
|
3727
4005
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -3735,7 +4013,7 @@ async function handleConfigList(argv, apiUrl, token) {
|
|
|
3735
4013
|
const json = hasFlag(argv, "--json");
|
|
3736
4014
|
if (!templateId) {
|
|
3737
4015
|
console.error("Error: template ID required. Usage: canary sandbox config list <template-id>");
|
|
3738
|
-
|
|
4016
|
+
process11.exit(1);
|
|
3739
4017
|
}
|
|
3740
4018
|
const res = await apiRequest(
|
|
3741
4019
|
apiUrl,
|
|
@@ -3745,7 +4023,7 @@ async function handleConfigList(argv, apiUrl, token) {
|
|
|
3745
4023
|
);
|
|
3746
4024
|
if (!res.ok) {
|
|
3747
4025
|
console.error(`Error: ${res.error}`);
|
|
3748
|
-
|
|
4026
|
+
process11.exit(1);
|
|
3749
4027
|
}
|
|
3750
4028
|
const t = res.data;
|
|
3751
4029
|
const schema = t.configSchema ?? [];
|
|
@@ -3795,16 +4073,16 @@ async function handleConfigSet(argv, apiUrl, token) {
|
|
|
3795
4073
|
console.error("Error: template ID and key required.");
|
|
3796
4074
|
console.error("Usage: canary sandbox config set <template-id> <key> <value>");
|
|
3797
4075
|
console.error(' For sensitive values: echo "sk-ant-..." | canary sandbox config set <template-id> <key> --value-stdin');
|
|
3798
|
-
|
|
4076
|
+
process11.exit(1);
|
|
3799
4077
|
}
|
|
3800
4078
|
if (fromStdin) {
|
|
3801
4079
|
const chunks = [];
|
|
3802
|
-
for await (const chunk of
|
|
4080
|
+
for await (const chunk of process11.stdin) chunks.push(chunk);
|
|
3803
4081
|
value = Buffer.concat(chunks).toString("utf-8").trim();
|
|
3804
4082
|
}
|
|
3805
4083
|
if (value === void 0) {
|
|
3806
4084
|
console.error("Error: value required. Pass as argument or use --value-stdin");
|
|
3807
|
-
|
|
4085
|
+
process11.exit(1);
|
|
3808
4086
|
}
|
|
3809
4087
|
const res = await apiRequest(
|
|
3810
4088
|
apiUrl,
|
|
@@ -3818,7 +4096,7 @@ async function handleConfigSet(argv, apiUrl, token) {
|
|
|
3818
4096
|
);
|
|
3819
4097
|
if (!res.ok) {
|
|
3820
4098
|
console.error(`Error: ${res.error}`);
|
|
3821
|
-
|
|
4099
|
+
process11.exit(1);
|
|
3822
4100
|
}
|
|
3823
4101
|
console.log(`Config set: ${key}`);
|
|
3824
4102
|
}
|
|
@@ -3826,7 +4104,7 @@ async function handleConfigUnset(argv, apiUrl, token) {
|
|
|
3826
4104
|
const [templateId, key] = argv.filter((arg) => !arg.startsWith("-"));
|
|
3827
4105
|
if (!templateId || !key) {
|
|
3828
4106
|
console.error("Usage: canary sandbox config unset <template-id> <key>");
|
|
3829
|
-
|
|
4107
|
+
process11.exit(1);
|
|
3830
4108
|
}
|
|
3831
4109
|
const res = await apiRequest(
|
|
3832
4110
|
apiUrl,
|
|
@@ -3838,7 +4116,7 @@ async function handleConfigUnset(argv, apiUrl, token) {
|
|
|
3838
4116
|
);
|
|
3839
4117
|
if (!res.ok) {
|
|
3840
4118
|
console.error(`Error: ${res.error}`);
|
|
3841
|
-
|
|
4119
|
+
process11.exit(1);
|
|
3842
4120
|
}
|
|
3843
4121
|
console.log(`Config unset: ${key}`);
|
|
3844
4122
|
}
|
|
@@ -3847,12 +4125,12 @@ async function handleSetSize(argv, apiUrl, token) {
|
|
|
3847
4125
|
if (!templateId || !sizeArg) {
|
|
3848
4126
|
console.error("Error: template ID and size required.");
|
|
3849
4127
|
console.error(`Usage: canary sandbox set-size <template-id> <${INSTANCE_SIZES.join("|")}>`);
|
|
3850
|
-
|
|
4128
|
+
process11.exit(1);
|
|
3851
4129
|
}
|
|
3852
4130
|
const size = parseInstanceSize(sizeArg);
|
|
3853
4131
|
if (!size) {
|
|
3854
4132
|
console.error(`Error: invalid size '${sizeArg}'. Must be one of: ${INSTANCE_SIZES.join(", ")}`);
|
|
3855
|
-
|
|
4133
|
+
process11.exit(1);
|
|
3856
4134
|
}
|
|
3857
4135
|
const res = await apiRequest(
|
|
3858
4136
|
apiUrl,
|
|
@@ -3863,7 +4141,7 @@ async function handleSetSize(argv, apiUrl, token) {
|
|
|
3863
4141
|
);
|
|
3864
4142
|
if (!res.ok) {
|
|
3865
4143
|
console.error(`Error: ${res.error}`);
|
|
3866
|
-
|
|
4144
|
+
process11.exit(1);
|
|
3867
4145
|
}
|
|
3868
4146
|
console.log(`Instance size set to ${size}`);
|
|
3869
4147
|
}
|
|
@@ -3872,12 +4150,12 @@ async function handlePoolSetSize(argv, apiUrl, token) {
|
|
|
3872
4150
|
if (!templateId || countArg === void 0) {
|
|
3873
4151
|
console.error("Error: template ID and count required.");
|
|
3874
4152
|
console.error("Usage: canary sandbox pool set-size <template-id> <count>");
|
|
3875
|
-
|
|
4153
|
+
process11.exit(1);
|
|
3876
4154
|
}
|
|
3877
4155
|
const poolMinReady = /^\d+$/.test(countArg) ? Number(countArg) : Number.NaN;
|
|
3878
4156
|
if (!Number.isInteger(poolMinReady)) {
|
|
3879
4157
|
console.error(`Error: invalid count '${countArg}'. Must be a non-negative integer.`);
|
|
3880
|
-
|
|
4158
|
+
process11.exit(1);
|
|
3881
4159
|
}
|
|
3882
4160
|
const res = await apiRequest(
|
|
3883
4161
|
apiUrl,
|
|
@@ -3888,7 +4166,7 @@ async function handlePoolSetSize(argv, apiUrl, token) {
|
|
|
3888
4166
|
);
|
|
3889
4167
|
if (!res.ok) {
|
|
3890
4168
|
console.error(`Error: ${res.error}`);
|
|
3891
|
-
|
|
4169
|
+
process11.exit(1);
|
|
3892
4170
|
}
|
|
3893
4171
|
console.log(`Warm stopped-pool size set to ${poolMinReady}`);
|
|
3894
4172
|
}
|
|
@@ -3897,7 +4175,7 @@ async function handleCreate(argv, apiUrl, token) {
|
|
|
3897
4175
|
const json = hasFlag(argv, "--json");
|
|
3898
4176
|
const propertyHint = positional[0] ?? getArgValue(argv, "--property");
|
|
3899
4177
|
const property = await selectProperty(apiUrl, token, propertyHint ?? void 0);
|
|
3900
|
-
if (!property)
|
|
4178
|
+
if (!property) process11.exit(1);
|
|
3901
4179
|
const reposRes = await apiRequest(
|
|
3902
4180
|
apiUrl,
|
|
3903
4181
|
token,
|
|
@@ -3906,7 +4184,7 @@ async function handleCreate(argv, apiUrl, token) {
|
|
|
3906
4184
|
);
|
|
3907
4185
|
if (!reposRes.ok || !reposRes.items?.length) {
|
|
3908
4186
|
console.error(`No GitHub repos linked to property "${property.name}". Link one first in the web app.`);
|
|
3909
|
-
|
|
4187
|
+
process11.exit(1);
|
|
3910
4188
|
}
|
|
3911
4189
|
const linkedRepo = reposRes.items[0];
|
|
3912
4190
|
const branch = getArgValue(argv, "--branch") ?? linkedRepo.defaultBranch ?? "main";
|
|
@@ -3916,7 +4194,7 @@ async function handleCreate(argv, apiUrl, token) {
|
|
|
3916
4194
|
const instanceSize = sizeArg ? parseInstanceSize(sizeArg) : void 0;
|
|
3917
4195
|
if (sizeArg && !instanceSize) {
|
|
3918
4196
|
console.error(`Error: invalid --size '${sizeArg}'. Must be one of: ${INSTANCE_SIZES.join(", ")}`);
|
|
3919
|
-
|
|
4197
|
+
process11.exit(1);
|
|
3920
4198
|
}
|
|
3921
4199
|
const body = {
|
|
3922
4200
|
name,
|
|
@@ -3936,7 +4214,7 @@ async function handleCreate(argv, apiUrl, token) {
|
|
|
3936
4214
|
);
|
|
3937
4215
|
if (!res.ok) {
|
|
3938
4216
|
console.error(`Error: ${res.error}`);
|
|
3939
|
-
|
|
4217
|
+
process11.exit(1);
|
|
3940
4218
|
}
|
|
3941
4219
|
if (json) {
|
|
3942
4220
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -3956,7 +4234,7 @@ async function handleProvision(argv, apiUrl, token) {
|
|
|
3956
4234
|
const json = hasFlag(argv, "--json");
|
|
3957
4235
|
if (!templateId) {
|
|
3958
4236
|
console.error("Error: template ID required. Usage: canary sandbox provision <template-id> [--ttl 8]");
|
|
3959
|
-
|
|
4237
|
+
process11.exit(1);
|
|
3960
4238
|
}
|
|
3961
4239
|
const name = getArgValue(argv, "--name") ?? `instance-${Date.now()}`;
|
|
3962
4240
|
const ttl = parseInt(getArgValue(argv, "--ttl") ?? "8", 10);
|
|
@@ -3983,7 +4261,7 @@ async function handleProvision(argv, apiUrl, token) {
|
|
|
3983
4261
|
);
|
|
3984
4262
|
if (!res.ok) {
|
|
3985
4263
|
console.error(`Error: ${res.error}`);
|
|
3986
|
-
|
|
4264
|
+
process11.exit(1);
|
|
3987
4265
|
}
|
|
3988
4266
|
if (json) {
|
|
3989
4267
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -4008,7 +4286,7 @@ async function handleWarm(argv, apiUrl, token) {
|
|
|
4008
4286
|
"Error: template ID required. Usage: canary sandbox warm <template-id> [--wait] [--timeout <sec>]"
|
|
4009
4287
|
);
|
|
4010
4288
|
console.error("Find the template id with: canary sandbox list");
|
|
4011
|
-
|
|
4289
|
+
process11.exit(1);
|
|
4012
4290
|
}
|
|
4013
4291
|
const ensure = () => apiRequest(
|
|
4014
4292
|
apiUrl,
|
|
@@ -4023,7 +4301,7 @@ async function handleWarm(argv, apiUrl, token) {
|
|
|
4023
4301
|
} else {
|
|
4024
4302
|
console.error(`Error: ${first.error ?? "Failed to warm sandbox"}`);
|
|
4025
4303
|
}
|
|
4026
|
-
|
|
4304
|
+
process11.exit(1);
|
|
4027
4305
|
}
|
|
4028
4306
|
const terminalOk = /* @__PURE__ */ new Set(["running"]);
|
|
4029
4307
|
const terminalFail = /* @__PURE__ */ new Set(["failed", "terminated", "stopping"]);
|
|
@@ -4036,7 +4314,7 @@ async function handleWarm(argv, apiUrl, token) {
|
|
|
4036
4314
|
const poll = await ensure();
|
|
4037
4315
|
if (!poll.ok || !poll.data) {
|
|
4038
4316
|
console.error(`Error polling sandbox: ${poll.error ?? "Unknown error"}`);
|
|
4039
|
-
|
|
4317
|
+
process11.exit(1);
|
|
4040
4318
|
}
|
|
4041
4319
|
current = poll.data;
|
|
4042
4320
|
if (terminalOk.has(current.status) || terminalFail.has(current.status)) break;
|
|
@@ -4060,8 +4338,8 @@ async function handleWarm(argv, apiUrl, token) {
|
|
|
4060
4338
|
console.log("\nWarming in the background. Poll with: canary sandbox warm " + templateId + " --wait");
|
|
4061
4339
|
}
|
|
4062
4340
|
}
|
|
4063
|
-
if (terminalFail.has(current.status))
|
|
4064
|
-
if (wait && current.status !== "running")
|
|
4341
|
+
if (terminalFail.has(current.status)) process11.exit(1);
|
|
4342
|
+
if (wait && current.status !== "running") process11.exit(1);
|
|
4065
4343
|
}
|
|
4066
4344
|
function parseDockerPs(stdout) {
|
|
4067
4345
|
const lines = stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
@@ -4106,7 +4384,7 @@ async function handleVerify(argv, apiUrl, token) {
|
|
|
4106
4384
|
const autoTerminateOnFailure = hasFlag(argv, "--auto-terminate-on-failure");
|
|
4107
4385
|
if (!templateId) {
|
|
4108
4386
|
console.error("Error: template ID required. Usage: canary sandbox verify <template-id>");
|
|
4109
|
-
|
|
4387
|
+
process11.exit(1);
|
|
4110
4388
|
}
|
|
4111
4389
|
console.log(`Verifying template ${templateId}...`);
|
|
4112
4390
|
const verifyRes = await apiRequest(
|
|
@@ -4118,7 +4396,7 @@ async function handleVerify(argv, apiUrl, token) {
|
|
|
4118
4396
|
);
|
|
4119
4397
|
if (!verifyRes.ok) {
|
|
4120
4398
|
console.error(`Error: ${verifyRes.error}`);
|
|
4121
|
-
|
|
4399
|
+
process11.exit(1);
|
|
4122
4400
|
}
|
|
4123
4401
|
const instanceId = verifyRes.data.instanceId;
|
|
4124
4402
|
console.log(` Verification instance: ${instanceId}`);
|
|
@@ -4133,7 +4411,7 @@ async function handleVerify(argv, apiUrl, token) {
|
|
|
4133
4411
|
let routes = [];
|
|
4134
4412
|
let routesFetched = false;
|
|
4135
4413
|
while (Date.now() - startTime < TIMEOUT_MS) {
|
|
4136
|
-
await new Promise((
|
|
4414
|
+
await new Promise((resolve4) => setTimeout(resolve4, POLL_INTERVAL_MS2));
|
|
4137
4415
|
const diag = await apiRequest(
|
|
4138
4416
|
apiUrl,
|
|
4139
4417
|
token,
|
|
@@ -4154,23 +4432,23 @@ async function handleVerify(argv, apiUrl, token) {
|
|
|
4154
4432
|
const containers = parseDockerPs(dockerOutput);
|
|
4155
4433
|
const broken = containers.filter(isBrokenContainer);
|
|
4156
4434
|
const ttl = remaining > 60 ? `${elapsed}s elapsed` : `${remaining}s remaining`;
|
|
4157
|
-
|
|
4435
|
+
process11.stdout.write(
|
|
4158
4436
|
`\r [${ttl}] status: ${status}, app: ${appUp ? "up" : "waiting"}, containers: ${containers.length}, unhealthy: ${broken.length} `
|
|
4159
4437
|
);
|
|
4160
4438
|
if (status === "running" && appUp) {
|
|
4161
|
-
|
|
4439
|
+
process11.stdout.write("\n");
|
|
4162
4440
|
finalStatus = "running";
|
|
4163
4441
|
break;
|
|
4164
4442
|
}
|
|
4165
4443
|
if (status === "failed" || status === "terminated") {
|
|
4166
|
-
|
|
4444
|
+
process11.stdout.write("\n");
|
|
4167
4445
|
finalStatus = status;
|
|
4168
4446
|
break;
|
|
4169
4447
|
}
|
|
4170
4448
|
const shouldDump = !appUp && broken.length > 0 && now - lastDetailDumpAt >= DETAIL_DUMP_INTERVAL_MS;
|
|
4171
4449
|
if (shouldDump) {
|
|
4172
4450
|
lastDetailDumpAt = now;
|
|
4173
|
-
|
|
4451
|
+
process11.stdout.write("\n");
|
|
4174
4452
|
console.log("");
|
|
4175
4453
|
console.log(` App unreachable after ${elapsed}s. Container state:`);
|
|
4176
4454
|
for (const c of containers) {
|
|
@@ -4235,7 +4513,7 @@ async function handleVerify(argv, apiUrl, token) {
|
|
|
4235
4513
|
console.error(` Terminate manually with:`);
|
|
4236
4514
|
console.error(` canary sandbox terminate ${instanceId}`);
|
|
4237
4515
|
}
|
|
4238
|
-
|
|
4516
|
+
process11.exit(1);
|
|
4239
4517
|
}
|
|
4240
4518
|
const evidenceSummary = `Verified via canary sandbox verify \u2014 fresh instance ${instanceId} came up healthy.`;
|
|
4241
4519
|
for (const itemId of ["server-running", "server-accessible"]) {
|
|
@@ -4261,7 +4539,7 @@ async function handleVerify(argv, apiUrl, token) {
|
|
|
4261
4539
|
if (!completeRes.ok) {
|
|
4262
4540
|
console.error(`Failed to record verification: ${completeRes.error}`);
|
|
4263
4541
|
console.error(`Instance ${instanceId} left running for inspection.`);
|
|
4264
|
-
|
|
4542
|
+
process11.exit(1);
|
|
4265
4543
|
}
|
|
4266
4544
|
if (!keepInstance) {
|
|
4267
4545
|
await apiRequest(apiUrl, token, "POST", `/org/environments/instances/${instanceId}/terminate`);
|
|
@@ -4306,7 +4584,7 @@ async function handleVerify(argv, apiUrl, token) {
|
|
|
4306
4584
|
console.log('Complete: canary sandbox checklist complete <template-id> <item-id> --status success --summary "..."');
|
|
4307
4585
|
console.log("Skip: canary sandbox checklist skip <template-id> <item-id>");
|
|
4308
4586
|
}
|
|
4309
|
-
|
|
4587
|
+
process11.exit(1);
|
|
4310
4588
|
}
|
|
4311
4589
|
async function handleArtifactGet(argv, apiUrl, token) {
|
|
4312
4590
|
const positional = argv.filter((a) => !a.startsWith("-"));
|
|
@@ -4315,7 +4593,7 @@ async function handleArtifactGet(argv, apiUrl, token) {
|
|
|
4315
4593
|
const outputFile = getArgValue(argv, "--output");
|
|
4316
4594
|
if (!templateId || !type) {
|
|
4317
4595
|
console.error("Error: template ID and artifact type required. Usage: canary sandbox artifact get <template-id> <type>");
|
|
4318
|
-
|
|
4596
|
+
process11.exit(1);
|
|
4319
4597
|
}
|
|
4320
4598
|
const res = await apiRequest(
|
|
4321
4599
|
apiUrl,
|
|
@@ -4325,14 +4603,14 @@ async function handleArtifactGet(argv, apiUrl, token) {
|
|
|
4325
4603
|
);
|
|
4326
4604
|
if (!res.ok) {
|
|
4327
4605
|
console.error(`Error: ${res.error}`);
|
|
4328
|
-
|
|
4606
|
+
process11.exit(1);
|
|
4329
4607
|
}
|
|
4330
4608
|
if (outputFile) {
|
|
4331
|
-
const { writeFileSync } = await import("fs");
|
|
4332
|
-
|
|
4609
|
+
const { writeFileSync: writeFileSync2 } = await import("fs");
|
|
4610
|
+
writeFileSync2(outputFile, res.data.content ?? "", "utf-8");
|
|
4333
4611
|
console.log(`Written to ${outputFile}`);
|
|
4334
4612
|
} else {
|
|
4335
|
-
|
|
4613
|
+
process11.stdout.write(res.data.content ?? "");
|
|
4336
4614
|
}
|
|
4337
4615
|
}
|
|
4338
4616
|
async function handleArtifactCreate(argv, apiUrl, token) {
|
|
@@ -4343,17 +4621,17 @@ async function handleArtifactCreate(argv, apiUrl, token) {
|
|
|
4343
4621
|
const json = hasFlag(argv, "--json");
|
|
4344
4622
|
if (!templateId || !type || !filePath) {
|
|
4345
4623
|
console.error("Error: all args required. Usage: canary sandbox artifact create <template-id> <type> <file>");
|
|
4346
|
-
|
|
4624
|
+
process11.exit(1);
|
|
4347
4625
|
}
|
|
4348
4626
|
const WELL_KNOWN_TYPES = ["docker-compose", "dockerfile", "startup-script", "setup-script", "seed-script", "environment-config", "file"];
|
|
4349
4627
|
if (!WELL_KNOWN_TYPES.includes(type)) {
|
|
4350
4628
|
console.error(`Error: invalid artifact type "${type}". Valid types: ${WELL_KNOWN_TYPES.join(", ")}`);
|
|
4351
|
-
|
|
4629
|
+
process11.exit(1);
|
|
4352
4630
|
}
|
|
4353
4631
|
const destinationPath = getArgValue(argv, "--path");
|
|
4354
4632
|
if (type === "file" && !destinationPath) {
|
|
4355
4633
|
console.error("Error: file artifacts require --path <destination>. Example: --path /workspace/.canary/vite.sandbox.config.ts");
|
|
4356
|
-
|
|
4634
|
+
process11.exit(1);
|
|
4357
4635
|
}
|
|
4358
4636
|
if (destinationPath && !destinationPath.startsWith("/workspace/.canary/")) {
|
|
4359
4637
|
const suggestedName = destinationPath.split("/").filter(Boolean).pop() ?? "yourfile";
|
|
@@ -4361,14 +4639,14 @@ async function handleArtifactCreate(argv, apiUrl, token) {
|
|
|
4361
4639
|
`Error: --path must be under /workspace/.canary/ (the repo tree is user-owned \u2014 sandbox files live in .canary/).
|
|
4362
4640
|
try: --path /workspace/.canary/${suggestedName}`
|
|
4363
4641
|
);
|
|
4364
|
-
|
|
4642
|
+
process11.exit(1);
|
|
4365
4643
|
}
|
|
4366
4644
|
let content;
|
|
4367
4645
|
try {
|
|
4368
|
-
content =
|
|
4646
|
+
content = readFileSync7(filePath, "utf-8");
|
|
4369
4647
|
} catch (err) {
|
|
4370
4648
|
console.error(`Error reading file: ${err}`);
|
|
4371
|
-
|
|
4649
|
+
process11.exit(1);
|
|
4372
4650
|
}
|
|
4373
4651
|
const filename = filePath.split("/").pop() ?? filePath;
|
|
4374
4652
|
const res = await apiRequest(
|
|
@@ -4386,7 +4664,7 @@ async function handleArtifactCreate(argv, apiUrl, token) {
|
|
|
4386
4664
|
);
|
|
4387
4665
|
if (!res.ok) {
|
|
4388
4666
|
console.error(`Error: ${res.error}`);
|
|
4389
|
-
|
|
4667
|
+
process11.exit(1);
|
|
4390
4668
|
}
|
|
4391
4669
|
if (json) {
|
|
4392
4670
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -4406,7 +4684,7 @@ async function handleBuild(argv, apiUrl, token) {
|
|
|
4406
4684
|
const instanceId = positional[0];
|
|
4407
4685
|
if (!instanceId) {
|
|
4408
4686
|
console.error("Error: instance ID required. Usage: canary sandbox build <instance-id>");
|
|
4409
|
-
|
|
4687
|
+
process11.exit(1);
|
|
4410
4688
|
}
|
|
4411
4689
|
console.log("Building sandbox (this takes a few minutes)...");
|
|
4412
4690
|
const KICKOFF_TIMEOUT_MS = 8e3;
|
|
@@ -4428,7 +4706,7 @@ async function handleBuild(argv, apiUrl, token) {
|
|
|
4428
4706
|
buildResult = data.data;
|
|
4429
4707
|
} else if (data && !data.ok) {
|
|
4430
4708
|
console.error(`Error: ${data.error ?? "build failed"}`);
|
|
4431
|
-
|
|
4709
|
+
process11.exit(1);
|
|
4432
4710
|
}
|
|
4433
4711
|
} else {
|
|
4434
4712
|
const { failure } = result;
|
|
@@ -4436,7 +4714,7 @@ async function handleBuild(argv, apiUrl, token) {
|
|
|
4436
4714
|
if (!isExpectedAsync) {
|
|
4437
4715
|
if (isTerminalFailure(failure)) exitOnFailure(failure);
|
|
4438
4716
|
console.error(`Error: ${failure.message}`);
|
|
4439
|
-
|
|
4717
|
+
process11.exit(1);
|
|
4440
4718
|
}
|
|
4441
4719
|
}
|
|
4442
4720
|
if (buildCompleted && buildResult) {
|
|
@@ -4445,18 +4723,18 @@ async function handleBuild(argv, apiUrl, token) {
|
|
|
4445
4723
|
return;
|
|
4446
4724
|
}
|
|
4447
4725
|
if (buildResult.stdout) {
|
|
4448
|
-
|
|
4449
|
-
if (!buildResult.stdout.endsWith("\n"))
|
|
4726
|
+
process11.stdout.write(buildResult.stdout);
|
|
4727
|
+
if (!buildResult.stdout.endsWith("\n")) process11.stdout.write("\n");
|
|
4450
4728
|
}
|
|
4451
4729
|
if (buildResult.stderr) {
|
|
4452
|
-
|
|
4453
|
-
if (!buildResult.stderr.endsWith("\n"))
|
|
4730
|
+
process11.stderr.write(`STDERR: ${buildResult.stderr}`);
|
|
4731
|
+
if (!buildResult.stderr.endsWith("\n")) process11.stderr.write("\n");
|
|
4454
4732
|
}
|
|
4455
4733
|
if (buildResult.exitCode === 0) {
|
|
4456
4734
|
console.log("Build complete.");
|
|
4457
4735
|
} else {
|
|
4458
4736
|
console.error(`Build failed with exit code ${buildResult.exitCode}`);
|
|
4459
|
-
|
|
4737
|
+
process11.exit(1);
|
|
4460
4738
|
}
|
|
4461
4739
|
return;
|
|
4462
4740
|
}
|
|
@@ -4470,7 +4748,7 @@ async function handleBuild(argv, apiUrl, token) {
|
|
|
4470
4748
|
const POLL_INTERVAL_MS2 = 1e4;
|
|
4471
4749
|
const startTime = Date.now();
|
|
4472
4750
|
while (Date.now() - startTime < BUILD_POLL_TIMEOUT_MS) {
|
|
4473
|
-
await new Promise((
|
|
4751
|
+
await new Promise((resolve4) => setTimeout(resolve4, POLL_INTERVAL_MS2));
|
|
4474
4752
|
const diag = await apiRequest(
|
|
4475
4753
|
apiUrl,
|
|
4476
4754
|
token,
|
|
@@ -4486,18 +4764,18 @@ async function handleBuild(argv, apiUrl, token) {
|
|
|
4486
4764
|
const appUp = appOutput.length > 0 && !appOutput.includes("UNREACHABLE");
|
|
4487
4765
|
const containerCount = (dockerOutput.match(/\n/g) || []).length + (dockerOutput ? 1 : 0);
|
|
4488
4766
|
const healthyCount = (dockerOutput.match(/\(healthy\)/g) || []).length;
|
|
4489
|
-
|
|
4767
|
+
process11.stdout.write(`\r [${elapsed}s] containers: ${containerCount}, healthy: ${healthyCount}, app: ${appUp ? "up" : "waiting"} `);
|
|
4490
4768
|
if (appUp) {
|
|
4491
|
-
|
|
4769
|
+
process11.stdout.write("\n");
|
|
4492
4770
|
console.log("Build complete.");
|
|
4493
4771
|
return;
|
|
4494
4772
|
}
|
|
4495
4773
|
}
|
|
4496
|
-
|
|
4774
|
+
process11.stdout.write("\n");
|
|
4497
4775
|
console.error("Build timed out waiting for the app to respond. Check diagnostics and logs:");
|
|
4498
4776
|
console.error(` canary sandbox diagnostics ${instanceId}`);
|
|
4499
4777
|
console.error(` canary sandbox logs ${instanceId} --service <name>`);
|
|
4500
|
-
|
|
4778
|
+
process11.exit(1);
|
|
4501
4779
|
}
|
|
4502
4780
|
async function handleRunCommand(argv, apiUrl, token) {
|
|
4503
4781
|
const json = hasFlag(argv, "--json");
|
|
@@ -4514,7 +4792,7 @@ async function handleRunCommand(argv, apiUrl, token) {
|
|
|
4514
4792
|
const command = firstNonFlag >= 0 ? filtered.slice(firstNonFlag + 1).join(" ") : "";
|
|
4515
4793
|
if (!instanceId || !command) {
|
|
4516
4794
|
console.error("Error: instance ID and command required. Usage: canary sandbox run-command <instance-id> [--service <name>] [--timeout <ms>] <command...>");
|
|
4517
|
-
|
|
4795
|
+
process11.exit(1);
|
|
4518
4796
|
}
|
|
4519
4797
|
const timeoutMs = timeoutFlag ? Math.max(parseInt(timeoutFlag, 10), 1e3) : 6e4;
|
|
4520
4798
|
const res = await apiRequest(
|
|
@@ -4527,19 +4805,19 @@ async function handleRunCommand(argv, apiUrl, token) {
|
|
|
4527
4805
|
);
|
|
4528
4806
|
if (!res.ok) {
|
|
4529
4807
|
console.error(`Error: ${res.error}`);
|
|
4530
|
-
|
|
4808
|
+
process11.exit(1);
|
|
4531
4809
|
}
|
|
4532
4810
|
if (json) {
|
|
4533
4811
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
4534
4812
|
return;
|
|
4535
4813
|
}
|
|
4536
4814
|
if (res.data.stdout) {
|
|
4537
|
-
|
|
4538
|
-
if (!res.data.stdout.endsWith("\n"))
|
|
4815
|
+
process11.stdout.write(res.data.stdout);
|
|
4816
|
+
if (!res.data.stdout.endsWith("\n")) process11.stdout.write("\n");
|
|
4539
4817
|
}
|
|
4540
4818
|
if (res.data.stderr) {
|
|
4541
|
-
|
|
4542
|
-
if (!res.data.stderr.endsWith("\n"))
|
|
4819
|
+
process11.stderr.write(`STDERR: ${res.data.stderr}`);
|
|
4820
|
+
if (!res.data.stderr.endsWith("\n")) process11.stderr.write("\n");
|
|
4543
4821
|
}
|
|
4544
4822
|
if (res.data.exitCode !== 0) {
|
|
4545
4823
|
console.log(`Exit code: ${res.data.exitCode}`);
|
|
@@ -4551,7 +4829,7 @@ async function handleDiagnostics(argv, apiUrl, token) {
|
|
|
4551
4829
|
const json = hasFlag(argv, "--json");
|
|
4552
4830
|
if (!instanceId) {
|
|
4553
4831
|
console.error("Error: instance ID required. Usage: canary sandbox diagnostics <instance-id>");
|
|
4554
|
-
|
|
4832
|
+
process11.exit(1);
|
|
4555
4833
|
}
|
|
4556
4834
|
const res = await apiRequest(
|
|
4557
4835
|
apiUrl,
|
|
@@ -4563,7 +4841,7 @@ async function handleDiagnostics(argv, apiUrl, token) {
|
|
|
4563
4841
|
);
|
|
4564
4842
|
if (!res.ok) {
|
|
4565
4843
|
console.error(`Error: ${res.error}`);
|
|
4566
|
-
|
|
4844
|
+
process11.exit(1);
|
|
4567
4845
|
}
|
|
4568
4846
|
const routes = await fetchInstanceRoutes(apiUrl, token, instanceId);
|
|
4569
4847
|
const data = { ...res.data, routes };
|
|
@@ -4618,7 +4896,7 @@ async function handleTerminate(argv, apiUrl, token) {
|
|
|
4618
4896
|
const instanceId = positional[0];
|
|
4619
4897
|
if (!instanceId) {
|
|
4620
4898
|
console.error("Error: instance ID required. Usage: canary sandbox terminate <instance-id>");
|
|
4621
|
-
|
|
4899
|
+
process11.exit(1);
|
|
4622
4900
|
}
|
|
4623
4901
|
const res = await apiRequest(
|
|
4624
4902
|
apiUrl,
|
|
@@ -4628,7 +4906,7 @@ async function handleTerminate(argv, apiUrl, token) {
|
|
|
4628
4906
|
);
|
|
4629
4907
|
if (!res.ok) {
|
|
4630
4908
|
console.error(`Error: ${res.error}`);
|
|
4631
|
-
|
|
4909
|
+
process11.exit(1);
|
|
4632
4910
|
}
|
|
4633
4911
|
console.log(`Instance ${instanceId} termination initiated.`);
|
|
4634
4912
|
}
|
|
@@ -4637,7 +4915,7 @@ async function handleDelete(argv, apiUrl, token) {
|
|
|
4637
4915
|
const templateId = positional[0];
|
|
4638
4916
|
if (!templateId) {
|
|
4639
4917
|
console.error("Error: template ID required. Usage: canary sandbox delete <template-id>");
|
|
4640
|
-
|
|
4918
|
+
process11.exit(1);
|
|
4641
4919
|
}
|
|
4642
4920
|
const res = await apiRequest(
|
|
4643
4921
|
apiUrl,
|
|
@@ -4647,7 +4925,7 @@ async function handleDelete(argv, apiUrl, token) {
|
|
|
4647
4925
|
);
|
|
4648
4926
|
if (!res.ok) {
|
|
4649
4927
|
console.error(`Error: ${res.error}`);
|
|
4650
|
-
|
|
4928
|
+
process11.exit(1);
|
|
4651
4929
|
}
|
|
4652
4930
|
console.log(`Template ${templateId} deleted.`);
|
|
4653
4931
|
}
|
|
@@ -4657,7 +4935,7 @@ async function handleActionList(argv, apiUrl, token) {
|
|
|
4657
4935
|
const json = hasFlag(argv, "--json");
|
|
4658
4936
|
if (!templateId) {
|
|
4659
4937
|
console.error("Error: template ID required. Usage: canary sandbox action list <template-id>");
|
|
4660
|
-
|
|
4938
|
+
process11.exit(1);
|
|
4661
4939
|
}
|
|
4662
4940
|
const res = await apiRequest(
|
|
4663
4941
|
apiUrl,
|
|
@@ -4667,7 +4945,7 @@ async function handleActionList(argv, apiUrl, token) {
|
|
|
4667
4945
|
);
|
|
4668
4946
|
if (!res.ok) {
|
|
4669
4947
|
console.error(`Error: ${res.error}`);
|
|
4670
|
-
|
|
4948
|
+
process11.exit(1);
|
|
4671
4949
|
}
|
|
4672
4950
|
if (json) {
|
|
4673
4951
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -4691,7 +4969,7 @@ async function handleActionGet(argv, apiUrl, token) {
|
|
|
4691
4969
|
const json = hasFlag(argv, "--json");
|
|
4692
4970
|
if (!templateId || !slug) {
|
|
4693
4971
|
console.error("Error: template ID and slug required. Usage: canary sandbox action get <template-id> <slug>");
|
|
4694
|
-
|
|
4972
|
+
process11.exit(1);
|
|
4695
4973
|
}
|
|
4696
4974
|
const res = await apiRequest(
|
|
4697
4975
|
apiUrl,
|
|
@@ -4701,7 +4979,7 @@ async function handleActionGet(argv, apiUrl, token) {
|
|
|
4701
4979
|
);
|
|
4702
4980
|
if (!res.ok) {
|
|
4703
4981
|
console.error(`Error: ${res.error}`);
|
|
4704
|
-
|
|
4982
|
+
process11.exit(1);
|
|
4705
4983
|
}
|
|
4706
4984
|
if (json) {
|
|
4707
4985
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -4734,7 +5012,7 @@ async function handleActionCreate(argv, apiUrl, token) {
|
|
|
4734
5012
|
const json = hasFlag(argv, "--json");
|
|
4735
5013
|
if (!templateId || !slug) {
|
|
4736
5014
|
console.error("Error: template ID and slug required. Usage: canary sandbox action create <template-id> <slug> [options]");
|
|
4737
|
-
|
|
5015
|
+
process11.exit(1);
|
|
4738
5016
|
}
|
|
4739
5017
|
const scriptPath = getArgValue(argv, "--script");
|
|
4740
5018
|
const inputSchemaPath = getArgValue(argv, "--input-schema");
|
|
@@ -4752,63 +5030,63 @@ async function handleActionCreate(argv, apiUrl, token) {
|
|
|
4752
5030
|
const provisionOrderStr = getArgValue(argv, "--provision-order");
|
|
4753
5031
|
if (provisioningMode && provisioningMode !== "shared" && provisioningMode !== "unique") {
|
|
4754
5032
|
console.error("Error: --provisioning-mode must be 'shared' or 'unique'");
|
|
4755
|
-
|
|
5033
|
+
process11.exit(1);
|
|
4756
5034
|
}
|
|
4757
5035
|
if (provisionOrderStr && !/^-?\d+$/.test(provisionOrderStr)) {
|
|
4758
5036
|
console.error("Error: --provision-order must be an integer");
|
|
4759
|
-
|
|
5037
|
+
process11.exit(1);
|
|
4760
5038
|
}
|
|
4761
5039
|
if (!scriptPath) {
|
|
4762
5040
|
console.error("Error: --script <file> is required");
|
|
4763
|
-
|
|
5041
|
+
process11.exit(1);
|
|
4764
5042
|
}
|
|
4765
5043
|
if (!inputSchemaPath) {
|
|
4766
5044
|
console.error("Error: --input-schema <file> is required");
|
|
4767
|
-
|
|
5045
|
+
process11.exit(1);
|
|
4768
5046
|
}
|
|
4769
5047
|
let scriptContent;
|
|
4770
5048
|
try {
|
|
4771
|
-
scriptContent =
|
|
5049
|
+
scriptContent = readFileSync7(scriptPath, "utf-8");
|
|
4772
5050
|
} catch (err) {
|
|
4773
5051
|
console.error(`Error reading script file: ${err}`);
|
|
4774
|
-
|
|
5052
|
+
process11.exit(1);
|
|
4775
5053
|
}
|
|
4776
5054
|
let inputSchema;
|
|
4777
5055
|
try {
|
|
4778
|
-
inputSchema = JSON.parse(
|
|
5056
|
+
inputSchema = JSON.parse(readFileSync7(inputSchemaPath, "utf-8"));
|
|
4779
5057
|
} catch (err) {
|
|
4780
5058
|
console.error(`Error reading input schema file: ${err}`);
|
|
4781
|
-
|
|
5059
|
+
process11.exit(1);
|
|
4782
5060
|
}
|
|
4783
5061
|
let outputSchema;
|
|
4784
5062
|
if (outputSchemaPath) {
|
|
4785
5063
|
try {
|
|
4786
|
-
outputSchema = JSON.parse(
|
|
5064
|
+
outputSchema = JSON.parse(readFileSync7(outputSchemaPath, "utf-8"));
|
|
4787
5065
|
} catch (err) {
|
|
4788
5066
|
console.error(`Error reading output schema file: ${err}`);
|
|
4789
|
-
|
|
5067
|
+
process11.exit(1);
|
|
4790
5068
|
}
|
|
4791
5069
|
}
|
|
4792
5070
|
let credentialOutputConfig;
|
|
4793
5071
|
if (credentialOutputConfigPath) {
|
|
4794
5072
|
try {
|
|
4795
|
-
credentialOutputConfig = JSON.parse(
|
|
5073
|
+
credentialOutputConfig = JSON.parse(readFileSync7(credentialOutputConfigPath, "utf-8"));
|
|
4796
5074
|
} catch (err) {
|
|
4797
5075
|
console.error(`Error reading credential output config file: ${err}`);
|
|
4798
|
-
|
|
5076
|
+
process11.exit(1);
|
|
4799
5077
|
}
|
|
4800
5078
|
}
|
|
4801
5079
|
let outputConfig;
|
|
4802
5080
|
if (outputConfigPath) {
|
|
4803
5081
|
try {
|
|
4804
|
-
outputConfig = JSON.parse(
|
|
5082
|
+
outputConfig = JSON.parse(readFileSync7(outputConfigPath, "utf-8"));
|
|
4805
5083
|
} catch (err) {
|
|
4806
5084
|
console.error(`Error reading output config file: ${err}`);
|
|
4807
|
-
|
|
5085
|
+
process11.exit(1);
|
|
4808
5086
|
}
|
|
4809
5087
|
if (!Array.isArray(outputConfig)) {
|
|
4810
5088
|
console.error("Error: --output-config must contain a JSON array of output slots");
|
|
4811
|
-
|
|
5089
|
+
process11.exit(1);
|
|
4812
5090
|
}
|
|
4813
5091
|
}
|
|
4814
5092
|
const body = {
|
|
@@ -4836,7 +5114,7 @@ async function handleActionCreate(argv, apiUrl, token) {
|
|
|
4836
5114
|
);
|
|
4837
5115
|
if (!res.ok) {
|
|
4838
5116
|
console.error(`Error: ${res.error}`);
|
|
4839
|
-
|
|
5117
|
+
process11.exit(1);
|
|
4840
5118
|
}
|
|
4841
5119
|
if (json) {
|
|
4842
5120
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -4850,7 +5128,7 @@ async function handleActionDelete(argv, apiUrl, token) {
|
|
|
4850
5128
|
const slug = positional[1];
|
|
4851
5129
|
if (!templateId || !slug) {
|
|
4852
5130
|
console.error("Error: template ID and slug required. Usage: canary sandbox action delete <template-id> <slug>");
|
|
4853
|
-
|
|
5131
|
+
process11.exit(1);
|
|
4854
5132
|
}
|
|
4855
5133
|
const res = await apiRequest(
|
|
4856
5134
|
apiUrl,
|
|
@@ -4860,7 +5138,7 @@ async function handleActionDelete(argv, apiUrl, token) {
|
|
|
4860
5138
|
);
|
|
4861
5139
|
if (!res.ok) {
|
|
4862
5140
|
console.error(`Error: ${res.error}`);
|
|
4863
|
-
|
|
5141
|
+
process11.exit(1);
|
|
4864
5142
|
}
|
|
4865
5143
|
console.log(`Action '${slug}' deleted.`);
|
|
4866
5144
|
}
|
|
@@ -4871,16 +5149,16 @@ async function handleActionExecute(argv, apiUrl, token) {
|
|
|
4871
5149
|
const json = hasFlag(argv, "--json");
|
|
4872
5150
|
if (!instanceId || !slug) {
|
|
4873
5151
|
console.error("Error: instance ID and slug required. Usage: canary sandbox action execute <instance-id> <slug> [options]");
|
|
4874
|
-
|
|
5152
|
+
process11.exit(1);
|
|
4875
5153
|
}
|
|
4876
5154
|
let params = {};
|
|
4877
5155
|
const paramsFile = getArgValue(argv, "--params-file");
|
|
4878
5156
|
if (paramsFile) {
|
|
4879
5157
|
try {
|
|
4880
|
-
params = JSON.parse(
|
|
5158
|
+
params = JSON.parse(readFileSync7(paramsFile, "utf-8"));
|
|
4881
5159
|
} catch (err) {
|
|
4882
5160
|
console.error(`Error reading params file: ${err}`);
|
|
4883
|
-
|
|
5161
|
+
process11.exit(1);
|
|
4884
5162
|
}
|
|
4885
5163
|
}
|
|
4886
5164
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -4899,11 +5177,11 @@ async function handleActionExecute(argv, apiUrl, token) {
|
|
|
4899
5177
|
const parsed = Number(timeoutArg);
|
|
4900
5178
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
4901
5179
|
console.error(`Error: --timeout-ms must be a positive number, got '${timeoutArg}'`);
|
|
4902
|
-
|
|
5180
|
+
process11.exit(1);
|
|
4903
5181
|
}
|
|
4904
5182
|
if (!runAsync) {
|
|
4905
5183
|
console.error("Error: --timeout-ms only applies with --async");
|
|
4906
|
-
|
|
5184
|
+
process11.exit(1);
|
|
4907
5185
|
}
|
|
4908
5186
|
executionTimeoutMs = parsed;
|
|
4909
5187
|
}
|
|
@@ -4921,7 +5199,7 @@ async function handleActionExecute(argv, apiUrl, token) {
|
|
|
4921
5199
|
);
|
|
4922
5200
|
if (!res.ok) {
|
|
4923
5201
|
console.error(`Error: ${res.error}`);
|
|
4924
|
-
|
|
5202
|
+
process11.exit(1);
|
|
4925
5203
|
}
|
|
4926
5204
|
if (json) {
|
|
4927
5205
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -4962,7 +5240,7 @@ async function handleActionExecutions(argv, apiUrl, token) {
|
|
|
4962
5240
|
const json = hasFlag(argv, "--json");
|
|
4963
5241
|
if (!instanceId) {
|
|
4964
5242
|
console.error("Error: instance ID required. Usage: canary sandbox action executions <instance-id>");
|
|
4965
|
-
|
|
5243
|
+
process11.exit(1);
|
|
4966
5244
|
}
|
|
4967
5245
|
const res = await apiRequest(
|
|
4968
5246
|
apiUrl,
|
|
@@ -4972,7 +5250,7 @@ async function handleActionExecutions(argv, apiUrl, token) {
|
|
|
4972
5250
|
);
|
|
4973
5251
|
if (!res.ok) {
|
|
4974
5252
|
console.error(`Error: ${res.error}`);
|
|
4975
|
-
|
|
5253
|
+
process11.exit(1);
|
|
4976
5254
|
}
|
|
4977
5255
|
if (json) {
|
|
4978
5256
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -5016,7 +5294,7 @@ async function handleActionExecutionGet(argv, apiUrl, token) {
|
|
|
5016
5294
|
console.error(
|
|
5017
5295
|
"Error: instance ID and execution ID required. Usage: cofactor sandbox action execution get <instance-id> <execution-id>"
|
|
5018
5296
|
);
|
|
5019
|
-
|
|
5297
|
+
process11.exit(1);
|
|
5020
5298
|
}
|
|
5021
5299
|
const res = await apiRequest(
|
|
5022
5300
|
apiUrl,
|
|
@@ -5026,7 +5304,7 @@ async function handleActionExecutionGet(argv, apiUrl, token) {
|
|
|
5026
5304
|
);
|
|
5027
5305
|
if (!res.ok) {
|
|
5028
5306
|
console.error(`Error: ${res.error}`);
|
|
5029
|
-
|
|
5307
|
+
process11.exit(1);
|
|
5030
5308
|
}
|
|
5031
5309
|
if (json) {
|
|
5032
5310
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -5043,7 +5321,7 @@ async function handleActionExecutionCancel(argv, apiUrl, token) {
|
|
|
5043
5321
|
console.error(
|
|
5044
5322
|
"Error: instance ID and execution ID required. Usage: cofactor sandbox action execution cancel <instance-id> <execution-id>"
|
|
5045
5323
|
);
|
|
5046
|
-
|
|
5324
|
+
process11.exit(1);
|
|
5047
5325
|
}
|
|
5048
5326
|
const res = await apiRequest(
|
|
5049
5327
|
apiUrl,
|
|
@@ -5053,7 +5331,7 @@ async function handleActionExecutionCancel(argv, apiUrl, token) {
|
|
|
5053
5331
|
);
|
|
5054
5332
|
if (!res.ok) {
|
|
5055
5333
|
console.error(`Error: ${res.error}`);
|
|
5056
|
-
|
|
5334
|
+
process11.exit(1);
|
|
5057
5335
|
}
|
|
5058
5336
|
if (json) {
|
|
5059
5337
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -5088,7 +5366,7 @@ async function handleRepoList(argv, apiUrl, token) {
|
|
|
5088
5366
|
const templateId = getArgValue(argv, "--template");
|
|
5089
5367
|
if (!templateId) {
|
|
5090
5368
|
console.error("Error: --template is required.");
|
|
5091
|
-
|
|
5369
|
+
process11.exit(1);
|
|
5092
5370
|
}
|
|
5093
5371
|
const result = await apiRequest(
|
|
5094
5372
|
apiUrl,
|
|
@@ -5098,7 +5376,7 @@ async function handleRepoList(argv, apiUrl, token) {
|
|
|
5098
5376
|
);
|
|
5099
5377
|
if (!result.ok) {
|
|
5100
5378
|
console.error(`Error: ${result.error ?? "Unknown error"}`);
|
|
5101
|
-
|
|
5379
|
+
process11.exit(1);
|
|
5102
5380
|
}
|
|
5103
5381
|
if (json) {
|
|
5104
5382
|
writeRawOutput(JSON.stringify(result.items ?? [], null, 2));
|
|
@@ -5127,7 +5405,7 @@ async function handleRepoAdd(argv, apiUrl, token) {
|
|
|
5127
5405
|
if (!templateId || !propertyGithubRepoId) {
|
|
5128
5406
|
console.error("Error: --template and --property-github-repo-id are required.");
|
|
5129
5407
|
console.error("Tip: link the repo to the property first via `canary property repo link`, then pass its id here.");
|
|
5130
|
-
|
|
5408
|
+
process11.exit(1);
|
|
5131
5409
|
}
|
|
5132
5410
|
const parsed = spec ? parseRepoSpec(spec) : {};
|
|
5133
5411
|
const body = { propertyGithubRepoId };
|
|
@@ -5148,7 +5426,7 @@ async function handleRepoAdd(argv, apiUrl, token) {
|
|
|
5148
5426
|
if (!result.ok) {
|
|
5149
5427
|
if (json) writeRawOutput(JSON.stringify(result, null, 2));
|
|
5150
5428
|
else console.error(`Error: ${result.error ?? "Unknown error"}`);
|
|
5151
|
-
|
|
5429
|
+
process11.exit(1);
|
|
5152
5430
|
}
|
|
5153
5431
|
if (json) writeRawOutput(JSON.stringify(result.data, null, 2));
|
|
5154
5432
|
else console.log(`Linked repo to template ${templateId} (link=${result.data?.id})`);
|
|
@@ -5163,7 +5441,7 @@ async function handleRepoUpdate(argv, apiUrl, token) {
|
|
|
5163
5441
|
const serviceName = getArgValue(argv, "--service-name");
|
|
5164
5442
|
if (!templateId || !linkId) {
|
|
5165
5443
|
console.error("Error: --template and --link-id are required.");
|
|
5166
|
-
|
|
5444
|
+
process11.exit(1);
|
|
5167
5445
|
}
|
|
5168
5446
|
const body = {};
|
|
5169
5447
|
if (role !== void 0) body.role = role;
|
|
@@ -5180,7 +5458,7 @@ async function handleRepoUpdate(argv, apiUrl, token) {
|
|
|
5180
5458
|
if (!result.ok) {
|
|
5181
5459
|
if (json) writeRawOutput(JSON.stringify(result, null, 2));
|
|
5182
5460
|
else console.error(`Error: ${result.error ?? "Unknown error"}`);
|
|
5183
|
-
|
|
5461
|
+
process11.exit(1);
|
|
5184
5462
|
}
|
|
5185
5463
|
if (json) writeRawOutput(JSON.stringify(result.data, null, 2));
|
|
5186
5464
|
else console.log(`Updated repo link ${linkId}`);
|
|
@@ -5191,7 +5469,7 @@ async function handleRepoRemove(argv, apiUrl, token) {
|
|
|
5191
5469
|
const linkId = getArgValue(argv, "--link-id");
|
|
5192
5470
|
if (!templateId || !linkId) {
|
|
5193
5471
|
console.error("Error: --template and --link-id are required.");
|
|
5194
|
-
|
|
5472
|
+
process11.exit(1);
|
|
5195
5473
|
}
|
|
5196
5474
|
const result = await apiRequest(
|
|
5197
5475
|
apiUrl,
|
|
@@ -5202,7 +5480,7 @@ async function handleRepoRemove(argv, apiUrl, token) {
|
|
|
5202
5480
|
if (!result.ok) {
|
|
5203
5481
|
if (json) writeRawOutput(JSON.stringify(result, null, 2));
|
|
5204
5482
|
else console.error(`Error: ${result.error ?? "Unknown error"}`);
|
|
5205
|
-
|
|
5483
|
+
process11.exit(1);
|
|
5206
5484
|
}
|
|
5207
5485
|
if (json) writeRawOutput(JSON.stringify(result, null, 2));
|
|
5208
5486
|
else console.log(`Removed repo link ${linkId} from template ${templateId}`);
|
|
@@ -5231,7 +5509,7 @@ Examples:
|
|
|
5231
5509
|
canary sandbox repo add --template <template-id> --property-github-repo-id <uuid> --role frontend
|
|
5232
5510
|
canary sandbox repo add --template <template-id> --property-github-repo-id <uuid> --spec frontend=owner/web@main:/apps/web`
|
|
5233
5511
|
);
|
|
5234
|
-
|
|
5512
|
+
process11.exit(1);
|
|
5235
5513
|
}
|
|
5236
5514
|
}
|
|
5237
5515
|
async function runSandbox(argv) {
|
|
@@ -5250,7 +5528,7 @@ async function runSandbox(argv) {
|
|
|
5250
5528
|
const sections = legacy ? SANDBOX_GUIDE_SECTIONS : SANDBOX_V2_GUIDE_SECTIONS;
|
|
5251
5529
|
if (section && !sections.some((s) => s.slug === section) && SANDBOX_GUIDE_SECTIONS.some((s) => s.slug === section)) {
|
|
5252
5530
|
console.error(`"${section}" is a section of the v1 sandbox guide: run "cofactor sandbox guide v1 ${section}".`);
|
|
5253
|
-
|
|
5531
|
+
process11.exit(1);
|
|
5254
5532
|
}
|
|
5255
5533
|
await printServedGuide(legacy ? "sandbox" : "sandbox-v2", guideRest);
|
|
5256
5534
|
return;
|
|
@@ -5305,7 +5583,7 @@ async function runSandbox(argv) {
|
|
|
5305
5583
|
await handlePoolSetSize(poolRest, apiUrl, token);
|
|
5306
5584
|
} else {
|
|
5307
5585
|
console.error(`Unknown pool action: ${poolAction}. Use "set-size".`);
|
|
5308
|
-
|
|
5586
|
+
process11.exit(1);
|
|
5309
5587
|
}
|
|
5310
5588
|
break;
|
|
5311
5589
|
}
|
|
@@ -5317,7 +5595,7 @@ async function runSandbox(argv) {
|
|
|
5317
5595
|
await handleArtifactCreate(actionRest, apiUrl, token);
|
|
5318
5596
|
} else {
|
|
5319
5597
|
console.error(`Unknown artifact action: ${action2}. Use "get" or "create".`);
|
|
5320
|
-
|
|
5598
|
+
process11.exit(1);
|
|
5321
5599
|
}
|
|
5322
5600
|
break;
|
|
5323
5601
|
}
|
|
@@ -5331,7 +5609,7 @@ async function runSandbox(argv) {
|
|
|
5331
5609
|
await handleConfigUnset(configRest, apiUrl, token);
|
|
5332
5610
|
} else {
|
|
5333
5611
|
console.error(`Unknown config action: ${configAction}. Use "list", "set", or "unset".`);
|
|
5334
|
-
|
|
5612
|
+
process11.exit(1);
|
|
5335
5613
|
}
|
|
5336
5614
|
break;
|
|
5337
5615
|
}
|
|
@@ -5367,13 +5645,13 @@ async function runSandbox(argv) {
|
|
|
5367
5645
|
await handleActionExecutionCancel(execRest, apiUrl, token);
|
|
5368
5646
|
} else {
|
|
5369
5647
|
console.error(`Unknown execution sub-command: ${execSub}. Use "get" or "cancel".`);
|
|
5370
|
-
|
|
5648
|
+
process11.exit(1);
|
|
5371
5649
|
}
|
|
5372
5650
|
break;
|
|
5373
5651
|
}
|
|
5374
5652
|
default:
|
|
5375
5653
|
console.error(`Unknown action sub-command: ${actionSub}. Use "list", "get", "create", "delete", "execute", "executions", or "execution".`);
|
|
5376
|
-
|
|
5654
|
+
process11.exit(1);
|
|
5377
5655
|
}
|
|
5378
5656
|
break;
|
|
5379
5657
|
}
|
|
@@ -5425,6 +5703,9 @@ async function runSandbox(argv) {
|
|
|
5425
5703
|
case "sync":
|
|
5426
5704
|
await handleSandboxSync(v2Positionals("sync", args), args, apiUrl, token);
|
|
5427
5705
|
break;
|
|
5706
|
+
case "auth-check":
|
|
5707
|
+
await handleSandboxAuthCheck(args, apiUrl, token);
|
|
5708
|
+
break;
|
|
5428
5709
|
case "verify-env":
|
|
5429
5710
|
await handleSandboxVerifyEnv(args, apiUrl, token);
|
|
5430
5711
|
break;
|
|
@@ -5440,7 +5721,7 @@ async function runSandbox(argv) {
|
|
|
5440
5721
|
default:
|
|
5441
5722
|
console.error(`Unknown sub-command: ${subCommand}`);
|
|
5442
5723
|
printHelp();
|
|
5443
|
-
|
|
5724
|
+
process11.exit(1);
|
|
5444
5725
|
}
|
|
5445
5726
|
}
|
|
5446
5727
|
export {
|
|
@@ -5457,4 +5738,4 @@ export {
|
|
|
5457
5738
|
withPluginEnabled,
|
|
5458
5739
|
withoutPlugin
|
|
5459
5740
|
};
|
|
5460
|
-
//# sourceMappingURL=sandbox-
|
|
5741
|
+
//# sourceMappingURL=sandbox-FBLJEYA2.js.map
|