@runcofactor/cli 0.54.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/README.md +2 -1
- package/dist/{account-6YJAU53I.js → account-CZYZD2JS.js} +5 -5
- package/dist/{alert-Z7V2ZUEW.js → alert-JQ4SDEB7.js} +8 -8
- package/dist/{api-catalog-UEDTRM6E.js → api-catalog-IWQC35P2.js} +5 -5
- package/dist/{api-key-V5GEPRRG.js → api-key-GUDXIIEA.js} +5 -5
- package/dist/{attachment-M7VZ6GSE.js → attachment-CPZYOBUB.js} +5 -5
- package/dist/{auth-DVTMWJKF.js → auth-DS6MBVEI.js} +3 -3
- package/dist/{benchmark-KZJZYUTQ.js → benchmark-6HPCIQHD.js} +5 -5
- package/dist/{blackcat-QJXYDE66.js → blackcat-V3L63WFE.js} +3 -3
- package/dist/{chunk-FLDIN6VX.js → chunk-2E3STBHS.js} +2 -2
- package/dist/{chunk-KNWKOKM7.js → chunk-2OHYMT3A.js} +20 -11
- package/dist/chunk-2OHYMT3A.js.map +1 -0
- package/dist/{chunk-L4Y5PZUE.js → chunk-3LGCHNBC.js} +2 -2
- package/dist/{chunk-ZFN5FWN2.js → chunk-4MFMPC5P.js} +46 -2
- package/dist/chunk-4MFMPC5P.js.map +1 -0
- package/dist/{chunk-2M4PZK6Q.js → chunk-56HFZHQM.js} +8 -8
- package/dist/{chunk-AQOHNAEN.js → chunk-5OYWNS6D.js} +20 -9
- package/dist/chunk-5OYWNS6D.js.map +1 -0
- package/dist/{chunk-RA2NRAWU.js → chunk-62EXS76C.js} +6 -6
- package/dist/{chunk-HGEJHS45.js → chunk-6ILWJFKY.js} +368 -6
- package/dist/chunk-6ILWJFKY.js.map +1 -0
- package/dist/{chunk-4EWSONHO.js → chunk-BDOL622T.js} +4 -4
- package/dist/{chunk-T6EQFICG.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-MQQCUU5S.js → chunk-DEVD2WD2.js} +2 -2
- package/dist/{chunk-TRC63RSK.js → chunk-EWK44F6V.js} +3 -3
- package/dist/chunk-EWK44F6V.js.map +1 -0
- package/dist/{chunk-Q7UAEFW5.js → chunk-FNHPTTRF.js} +7 -5
- package/dist/chunk-FNHPTTRF.js.map +1 -0
- package/dist/{chunk-JAIMLQOU.js → chunk-GCD4EHCD.js} +6 -6
- package/dist/{chunk-7MRZWPVC.js → chunk-H6ZMKNP7.js} +3 -3
- package/dist/{chunk-ZOPVBINJ.js → chunk-IEWAH7W3.js} +5 -5
- package/dist/{chunk-74TU6PGO.js → chunk-IV4HRGKX.js} +77 -12
- package/dist/chunk-IV4HRGKX.js.map +1 -0
- package/dist/{chunk-ZO5MSM4Q.js → chunk-J2ZQULU3.js} +2 -2
- package/dist/{chunk-IVWWYKAM.js → chunk-K2IDR6FR.js} +3 -3
- package/dist/{chunk-QGP67JOK.js → chunk-KTZRI27M.js} +4 -4
- package/dist/{chunk-HESV3F6U.js → chunk-L644NMDI.js} +3 -3
- package/dist/{chunk-HESV3F6U.js.map → chunk-L644NMDI.js.map} +1 -1
- package/dist/{chunk-LPQTXBJF.js → chunk-LFT7GUWS.js} +63 -17
- package/dist/chunk-LFT7GUWS.js.map +1 -0
- package/dist/{chunk-O7MBDOMO.js → chunk-O7B2IYCZ.js} +30 -14
- package/dist/chunk-O7B2IYCZ.js.map +1 -0
- package/dist/{chunk-R2CVV7QT.js → chunk-RHWPZKRV.js} +28 -1
- package/dist/chunk-RHWPZKRV.js.map +1 -0
- package/dist/{chunk-3YNR6U6Q.js → chunk-UJSW72LG.js} +3 -3
- package/dist/{chunk-G2SCV2CY.js → chunk-VACACCD6.js} +3 -3
- package/dist/{chunk-CV22445U.js → chunk-VMAR67FF.js} +2 -2
- package/dist/{chunk-QYHJUHL7.js → chunk-VMJ7TBVM.js} +2 -2
- package/dist/chunk-WRRA4ATV.js +332 -0
- package/dist/chunk-WRRA4ATV.js.map +1 -0
- package/dist/{chunk-YACDH6MJ.js → chunk-XHEVDABB.js} +2 -2
- package/dist/{chunk-7VBX2M4F.js → chunk-XHGF6VC6.js} +4 -4
- package/dist/{credential-546ER7WQ.js → credential-GJW7LGYP.js} +5 -5
- package/dist/{credential-template-BWMEBGBN.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-NIVBDDI2.js → digest-XEHGCJM4.js} +8 -8
- package/dist/{docs-AK4MDP6K.js → docs-KH4BCEQF.js} +5 -5
- package/dist/{doctor-NCLA47EL.js → doctor-FOXXE5SK.js} +3 -3
- package/dist/{environment-X3PV5IN2.js → environment-EBDPQTIS.js} +7 -7
- package/dist/{eval-5FZKYZ6O.js → eval-QZSSEICV.js} +67 -27
- package/dist/eval-QZSSEICV.js.map +1 -0
- package/dist/{exploration-2RVGIUKT.js → exploration-LCGKRGOQ.js} +5 -5
- package/dist/{exploratory-test-SJ5HFI3L.js → exploratory-test-SGECQGIX.js} +13 -5
- package/dist/exploratory-test-SGECQGIX.js.map +1 -0
- package/dist/{external-run-4NDVAHQB.js → external-run-2AJAI5O7.js} +9 -9
- package/dist/{feature-flag-C4IRE724.js → feature-flag-P7N4WQIH.js} +5 -5
- package/dist/{feedback-337OZCKC.js → feedback-LAT7TQ2T.js} +6 -6
- package/dist/{flow-D7ZLBKCH.js → flow-NH2DC6AP.js} +5 -5
- package/dist/{github-LKXYIALH.js → github-FCEKBLXI.js} +6 -6
- package/dist/index.js +106 -408
- package/dist/index.js.map +1 -1
- package/dist/{init-GKEC6LPH.js → init-5U5EZOAW.js} +5 -5
- package/dist/{install-K45SAT2V.js → install-X3UNVXKY.js} +5 -5
- package/dist/{issues-2HMTQYWR.js → issues-4P6I3YQW.js} +8 -8
- package/dist/{knobs-KUCGLO5Z.js → knobs-JKTWJ6ZG.js} +5 -5
- package/dist/{landing-KAET2I73.js → landing-6AGLKI66.js} +7 -7
- package/dist/{list-GDV4UYWV.js → list-TWP2RP33.js} +5 -5
- package/dist/{local-NFOSJLLV.js → local-D75QE5TB.js} +7 -7
- package/dist/{login-Y77HWROY.js → login-RBET4TIP.js} +5 -5
- package/dist/{mcp-CPCFMU6B.js → mcp-562SUUMK.js} +11 -11
- package/dist/{metrics-Y245ST63.js → metrics-4WF2GEPA.js} +5 -5
- package/dist/{monitor-BUDWSC3S.js → monitor-WJXH54H2.js} +5 -5
- package/dist/{perf-3JOAXPHP.js → perf-C6N6P7YG.js} +12 -361
- package/dist/perf-C6N6P7YG.js.map +1 -0
- package/dist/{product-CXL3YTMD.js → product-CYEQ7HOB.js} +5 -5
- package/dist/{product-map-656BTU74.js → product-map-MSDLIFZY.js} +7 -7
- package/dist/{prompts-3SZ7O5E6.js → prompts-ME53WPN4.js} +5 -5
- package/dist/{proofs-4CSCCSNL.js → proofs-D3G6ZUFP.js} +11 -9
- package/dist/{property-IEKBXRVY.js → property-5GLJNAVB.js} +29 -10
- package/dist/property-5GLJNAVB.js.map +1 -0
- package/dist/{psql-M4CM6TSI.js → psql-EXAUDU3W.js} +5 -5
- package/dist/{record-QP4TYZBS.js → record-EE7NGNKY.js} +6 -6
- package/dist/{redis-KG47IGJV.js → redis-M4EYWKOJ.js} +5 -5
- package/dist/{release-QHPSQULK.js → release-4KUWVWWC.js} +5 -5
- package/dist/{runbook-YVBG2QK3.js → runbook-AHU77ANQ.js} +5 -5
- package/dist/{sandbox-ZXDZTCCN.js → sandbox-FBLJEYA2.js} +1917 -1633
- package/dist/sandbox-FBLJEYA2.js.map +1 -0
- package/dist/{scenarios-2SX2NUCD.js → scenarios-TU656T2B.js} +5 -5
- package/dist/{schedule-P6F7MLXX.js → schedule-DXJI6SYO.js} +8 -8
- package/dist/{session-SZMCZTPE.js → session-RUFTJHF4.js} +10 -10
- package/dist/{skill-D4XFW6QI.js → skill-ZK76GQ47.js} +111 -13
- package/dist/skill-ZK76GQ47.js.map +1 -0
- package/dist/skills/cofactor-debug-run/SKILL.md +6 -1
- package/dist/skills/cofactor-debug-run/history/2.0.10.md +320 -0
- package/dist/skills/cofactor-debug-run/history/2.0.9.md +319 -0
- package/dist/skills/cofactor-debug-run/version.json +1 -1
- package/dist/{slack-UDXTPOSW.js → slack-M3OKDVMY.js} +6 -6
- package/dist/{spec-YCBBIV27.js → spec-5MXEBLYR.js} +5 -5
- package/dist/{src-DY7GLRO2.js → src-2X5OQRPJ.js} +4 -4
- package/dist/{src-NG7DRE5A.js → src-72EFBA2R.js} +3 -3
- package/dist/{src-DEGBASCD.js → src-KH6WSBJV.js} +5 -5
- package/dist/{src-ZKVNVLXY.js → src-VLV36QYH.js} +127 -20
- package/dist/src-VLV36QYH.js.map +1 -0
- package/dist/{start-ZYZSORPY.js → start-PDYBRB5T.js} +5 -5
- package/dist/{status-ESIG6A7W.js → status-VMVPVCDA.js} +5 -5
- package/dist/{tag-3K5Y25AR.js → tag-7KTDLPVU.js} +6 -6
- package/dist/{task-JLNX7O3M.js → task-QLFYZNKR.js} +6 -6
- package/dist/{teams-F4IU3EXE.js → teams-Y7BXCY6P.js} +7 -7
- package/dist/{test-suite-QHDUW5W5.js → test-suite-6U22AYFG.js} +15 -14
- package/dist/test-suite-6U22AYFG.js.map +1 -0
- package/dist/{tickets-NBL5WACL.js → tickets-CQDJDNPY.js} +5 -5
- package/dist/{trace-ZKYAI5JF.js → trace-QDORWXYX.js} +8 -8
- package/dist/{triage-5NWCREUM.js → triage-GY7M3UGG.js} +6 -6
- package/dist/{trigger-KXYYY5IR.js → trigger-DLFAORX4.js} +9 -9
- package/dist/{update-check-Q2URVJLC.js → update-check-ES6XQ7OF.js} +4 -4
- package/dist/{upgrade-4XSASC7U.js → upgrade-WKEOW5GS.js} +5 -5
- package/dist/{usage-UY7VZYVY.js → usage-E6FDTR3W.js} +7 -7
- package/dist/{utilities-53NXNLA7.js → utilities-4Z6YX3L5.js} +6 -6
- package/dist/{visual-IJAT7X7D.js → visual-TA6LPVDJ.js} +5 -5
- package/dist/{workflow-OQNH2FIS.js → workflow-B5FXS2CJ.js} +92 -36
- package/dist/workflow-B5FXS2CJ.js.map +1 -0
- package/dist/{workflow-cache-FP6MKLVR.js → workflow-cache-MNLEMRMM.js} +16 -16
- package/dist/{workflow-cache-convergence-resolve-HS5KO7US.js → workflow-cache-convergence-resolve-G5HJM6MF.js} +5 -5
- package/dist/{workflow-debug-EM55H2QE.js → workflow-debug-TNBUGSBK.js} +11 -11
- package/dist/{workflow-feedback-GNWNWVHH.js → workflow-feedback-G2ZXAUGI.js} +5 -5
- package/dist/{workflow-fork-M5AQWST7.js → workflow-fork-6HHOMC2D.js} +5 -5
- package/dist/{workflow-promotion-HYULZDYL.js → workflow-promotion-RG7L6JZT.js} +5 -5
- package/dist/{workflow-proposals-EN327FSE.js → workflow-proposals-ZNF2F3UE.js} +5 -5
- package/dist/{workflow-revisions-ZMTVNWRW.js → workflow-revisions-TP64Z6FO.js} +5 -5
- package/dist/{workflow-status-FNAJG34W.js → workflow-status-5RG3JX56.js} +5 -5
- package/package.json +3 -2
- package/dist/chunk-74TU6PGO.js.map +0 -1
- package/dist/chunk-AQOHNAEN.js.map +0 -1
- package/dist/chunk-HGEJHS45.js.map +0 -1
- package/dist/chunk-KNWKOKM7.js.map +0 -1
- package/dist/chunk-LPQTXBJF.js.map +0 -1
- package/dist/chunk-O7MBDOMO.js.map +0 -1
- package/dist/chunk-Q7UAEFW5.js.map +0 -1
- package/dist/chunk-R2CVV7QT.js.map +0 -1
- package/dist/chunk-TRC63RSK.js.map +0 -1
- package/dist/chunk-ZFN5FWN2.js.map +0 -1
- package/dist/eval-5FZKYZ6O.js.map +0 -1
- package/dist/exploratory-test-SJ5HFI3L.js.map +0 -1
- package/dist/perf-3JOAXPHP.js.map +0 -1
- package/dist/property-IEKBXRVY.js.map +0 -1
- package/dist/sandbox-ZXDZTCCN.js.map +0 -1
- package/dist/skill-D4XFW6QI.js.map +0 -1
- package/dist/src-ZKVNVLXY.js.map +0 -1
- package/dist/test-suite-QHDUW5W5.js.map +0 -1
- package/dist/workflow-OQNH2FIS.js.map +0 -1
- /package/dist/{account-6YJAU53I.js.map → account-CZYZD2JS.js.map} +0 -0
- /package/dist/{alert-Z7V2ZUEW.js.map → alert-JQ4SDEB7.js.map} +0 -0
- /package/dist/{api-catalog-UEDTRM6E.js.map → api-catalog-IWQC35P2.js.map} +0 -0
- /package/dist/{api-key-V5GEPRRG.js.map → api-key-GUDXIIEA.js.map} +0 -0
- /package/dist/{attachment-M7VZ6GSE.js.map → attachment-CPZYOBUB.js.map} +0 -0
- /package/dist/{auth-DVTMWJKF.js.map → auth-DS6MBVEI.js.map} +0 -0
- /package/dist/{benchmark-KZJZYUTQ.js.map → benchmark-6HPCIQHD.js.map} +0 -0
- /package/dist/{blackcat-QJXYDE66.js.map → blackcat-V3L63WFE.js.map} +0 -0
- /package/dist/{chunk-FLDIN6VX.js.map → chunk-2E3STBHS.js.map} +0 -0
- /package/dist/{chunk-L4Y5PZUE.js.map → chunk-3LGCHNBC.js.map} +0 -0
- /package/dist/{chunk-2M4PZK6Q.js.map → chunk-56HFZHQM.js.map} +0 -0
- /package/dist/{chunk-RA2NRAWU.js.map → chunk-62EXS76C.js.map} +0 -0
- /package/dist/{chunk-4EWSONHO.js.map → chunk-BDOL622T.js.map} +0 -0
- /package/dist/{chunk-T6EQFICG.js.map → chunk-C3CXVX2B.js.map} +0 -0
- /package/dist/{chunk-MQQCUU5S.js.map → chunk-DEVD2WD2.js.map} +0 -0
- /package/dist/{chunk-JAIMLQOU.js.map → chunk-GCD4EHCD.js.map} +0 -0
- /package/dist/{chunk-7MRZWPVC.js.map → chunk-H6ZMKNP7.js.map} +0 -0
- /package/dist/{chunk-ZOPVBINJ.js.map → chunk-IEWAH7W3.js.map} +0 -0
- /package/dist/{chunk-ZO5MSM4Q.js.map → chunk-J2ZQULU3.js.map} +0 -0
- /package/dist/{chunk-IVWWYKAM.js.map → chunk-K2IDR6FR.js.map} +0 -0
- /package/dist/{chunk-QGP67JOK.js.map → chunk-KTZRI27M.js.map} +0 -0
- /package/dist/{chunk-3YNR6U6Q.js.map → chunk-UJSW72LG.js.map} +0 -0
- /package/dist/{chunk-G2SCV2CY.js.map → chunk-VACACCD6.js.map} +0 -0
- /package/dist/{chunk-CV22445U.js.map → chunk-VMAR67FF.js.map} +0 -0
- /package/dist/{chunk-QYHJUHL7.js.map → chunk-VMJ7TBVM.js.map} +0 -0
- /package/dist/{chunk-YACDH6MJ.js.map → chunk-XHEVDABB.js.map} +0 -0
- /package/dist/{chunk-7VBX2M4F.js.map → chunk-XHGF6VC6.js.map} +0 -0
- /package/dist/{credential-546ER7WQ.js.map → credential-GJW7LGYP.js.map} +0 -0
- /package/dist/{credential-template-BWMEBGBN.js.map → credential-template-UWBYKKWD.js.map} +0 -0
- /package/dist/{digest-NIVBDDI2.js.map → digest-XEHGCJM4.js.map} +0 -0
- /package/dist/{docs-AK4MDP6K.js.map → docs-KH4BCEQF.js.map} +0 -0
- /package/dist/{doctor-NCLA47EL.js.map → doctor-FOXXE5SK.js.map} +0 -0
- /package/dist/{environment-X3PV5IN2.js.map → environment-EBDPQTIS.js.map} +0 -0
- /package/dist/{exploration-2RVGIUKT.js.map → exploration-LCGKRGOQ.js.map} +0 -0
- /package/dist/{external-run-4NDVAHQB.js.map → external-run-2AJAI5O7.js.map} +0 -0
- /package/dist/{feature-flag-C4IRE724.js.map → feature-flag-P7N4WQIH.js.map} +0 -0
- /package/dist/{feedback-337OZCKC.js.map → feedback-LAT7TQ2T.js.map} +0 -0
- /package/dist/{flow-D7ZLBKCH.js.map → flow-NH2DC6AP.js.map} +0 -0
- /package/dist/{github-LKXYIALH.js.map → github-FCEKBLXI.js.map} +0 -0
- /package/dist/{init-GKEC6LPH.js.map → init-5U5EZOAW.js.map} +0 -0
- /package/dist/{install-K45SAT2V.js.map → install-X3UNVXKY.js.map} +0 -0
- /package/dist/{issues-2HMTQYWR.js.map → issues-4P6I3YQW.js.map} +0 -0
- /package/dist/{knobs-KUCGLO5Z.js.map → knobs-JKTWJ6ZG.js.map} +0 -0
- /package/dist/{landing-KAET2I73.js.map → landing-6AGLKI66.js.map} +0 -0
- /package/dist/{list-GDV4UYWV.js.map → list-TWP2RP33.js.map} +0 -0
- /package/dist/{local-NFOSJLLV.js.map → local-D75QE5TB.js.map} +0 -0
- /package/dist/{login-Y77HWROY.js.map → login-RBET4TIP.js.map} +0 -0
- /package/dist/{mcp-CPCFMU6B.js.map → mcp-562SUUMK.js.map} +0 -0
- /package/dist/{metrics-Y245ST63.js.map → metrics-4WF2GEPA.js.map} +0 -0
- /package/dist/{monitor-BUDWSC3S.js.map → monitor-WJXH54H2.js.map} +0 -0
- /package/dist/{product-CXL3YTMD.js.map → product-CYEQ7HOB.js.map} +0 -0
- /package/dist/{product-map-656BTU74.js.map → product-map-MSDLIFZY.js.map} +0 -0
- /package/dist/{prompts-3SZ7O5E6.js.map → prompts-ME53WPN4.js.map} +0 -0
- /package/dist/{proofs-4CSCCSNL.js.map → proofs-D3G6ZUFP.js.map} +0 -0
- /package/dist/{psql-M4CM6TSI.js.map → psql-EXAUDU3W.js.map} +0 -0
- /package/dist/{record-QP4TYZBS.js.map → record-EE7NGNKY.js.map} +0 -0
- /package/dist/{redis-KG47IGJV.js.map → redis-M4EYWKOJ.js.map} +0 -0
- /package/dist/{release-QHPSQULK.js.map → release-4KUWVWWC.js.map} +0 -0
- /package/dist/{runbook-YVBG2QK3.js.map → runbook-AHU77ANQ.js.map} +0 -0
- /package/dist/{scenarios-2SX2NUCD.js.map → scenarios-TU656T2B.js.map} +0 -0
- /package/dist/{schedule-P6F7MLXX.js.map → schedule-DXJI6SYO.js.map} +0 -0
- /package/dist/{session-SZMCZTPE.js.map → session-RUFTJHF4.js.map} +0 -0
- /package/dist/{slack-UDXTPOSW.js.map → slack-M3OKDVMY.js.map} +0 -0
- /package/dist/{spec-YCBBIV27.js.map → spec-5MXEBLYR.js.map} +0 -0
- /package/dist/{src-DEGBASCD.js.map → src-2X5OQRPJ.js.map} +0 -0
- /package/dist/{src-DY7GLRO2.js.map → src-72EFBA2R.js.map} +0 -0
- /package/dist/{src-NG7DRE5A.js.map → src-KH6WSBJV.js.map} +0 -0
- /package/dist/{start-ZYZSORPY.js.map → start-PDYBRB5T.js.map} +0 -0
- /package/dist/{status-ESIG6A7W.js.map → status-VMVPVCDA.js.map} +0 -0
- /package/dist/{tag-3K5Y25AR.js.map → tag-7KTDLPVU.js.map} +0 -0
- /package/dist/{task-JLNX7O3M.js.map → task-QLFYZNKR.js.map} +0 -0
- /package/dist/{teams-F4IU3EXE.js.map → teams-Y7BXCY6P.js.map} +0 -0
- /package/dist/{tickets-NBL5WACL.js.map → tickets-CQDJDNPY.js.map} +0 -0
- /package/dist/{trace-ZKYAI5JF.js.map → trace-QDORWXYX.js.map} +0 -0
- /package/dist/{triage-5NWCREUM.js.map → triage-GY7M3UGG.js.map} +0 -0
- /package/dist/{trigger-KXYYY5IR.js.map → trigger-DLFAORX4.js.map} +0 -0
- /package/dist/{update-check-Q2URVJLC.js.map → update-check-ES6XQ7OF.js.map} +0 -0
- /package/dist/{upgrade-4XSASC7U.js.map → upgrade-WKEOW5GS.js.map} +0 -0
- /package/dist/{usage-UY7VZYVY.js.map → usage-E6FDTR3W.js.map} +0 -0
- /package/dist/{utilities-53NXNLA7.js.map → utilities-4Z6YX3L5.js.map} +0 -0
- /package/dist/{visual-IJAT7X7D.js.map → visual-TA6LPVDJ.js.map} +0 -0
- /package/dist/{workflow-cache-FP6MKLVR.js.map → workflow-cache-MNLEMRMM.js.map} +0 -0
- /package/dist/{workflow-cache-convergence-resolve-HS5KO7US.js.map → workflow-cache-convergence-resolve-G5HJM6MF.js.map} +0 -0
- /package/dist/{workflow-debug-EM55H2QE.js.map → workflow-debug-TNBUGSBK.js.map} +0 -0
- /package/dist/{workflow-feedback-GNWNWVHH.js.map → workflow-feedback-G2ZXAUGI.js.map} +0 -0
- /package/dist/{workflow-fork-M5AQWST7.js.map → workflow-fork-6HHOMC2D.js.map} +0 -0
- /package/dist/{workflow-promotion-HYULZDYL.js.map → workflow-promotion-RG7L6JZT.js.map} +0 -0
- /package/dist/{workflow-proposals-EN327FSE.js.map → workflow-proposals-ZNF2F3UE.js.map} +0 -0
- /package/dist/{workflow-revisions-ZMTVNWRW.js.map → workflow-revisions-TP64Z6FO.js.map} +0 -0
- /package/dist/{workflow-status-FNAJG34W.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,
|
|
@@ -37,33 +38,35 @@ import {
|
|
|
37
38
|
hasFlag,
|
|
38
39
|
isTerminalFailure,
|
|
39
40
|
resolveConfig,
|
|
41
|
+
resolveOrgAppUrl,
|
|
40
42
|
splitAtSeparator
|
|
41
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-4MFMPC5P.js";
|
|
42
44
|
import {
|
|
43
45
|
SANDBOX_GUIDE_SECTIONS,
|
|
44
46
|
writeRawOutput
|
|
45
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-2OHYMT3A.js";
|
|
46
48
|
import {
|
|
47
49
|
INSTANCE_SIZES,
|
|
48
50
|
INSTANCE_SIZE_SPEC,
|
|
49
51
|
SANDBOX_EGRESS_MODES,
|
|
50
|
-
parseInstanceSize
|
|
51
|
-
|
|
52
|
+
parseInstanceSize,
|
|
53
|
+
sandboxPreviewPagePath
|
|
54
|
+
} from "./chunk-RHWPZKRV.js";
|
|
52
55
|
import "./chunk-TOWZAG3I.js";
|
|
53
56
|
|
|
54
57
|
// src/sandbox.ts
|
|
55
|
-
import
|
|
56
|
-
import { readFileSync as
|
|
58
|
+
import process11 from "process";
|
|
59
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
57
60
|
|
|
58
61
|
// src/sandbox-emulation.ts
|
|
59
|
-
import
|
|
62
|
+
import process2 from "process";
|
|
60
63
|
import { readFileSync } from "fs";
|
|
61
64
|
var pad = (value, width) => value.padEnd(width);
|
|
62
65
|
async function fetchEmulation(apiUrl, token, templateId) {
|
|
63
66
|
const res = await apiRequest(apiUrl, token, "GET", `/org/environments/templates/${templateId}/emulation`);
|
|
64
67
|
if (!res.ok) {
|
|
65
68
|
console.error(`Error: ${res.error}`);
|
|
66
|
-
|
|
69
|
+
process2.exit(1);
|
|
67
70
|
}
|
|
68
71
|
return { config: res.config ?? { enabled: false, plugins: [] }, catalog: res.catalog ?? [] };
|
|
69
72
|
}
|
|
@@ -78,14 +81,14 @@ async function patchEmulation(apiUrl, token, templateId, config) {
|
|
|
78
81
|
);
|
|
79
82
|
if (!res.ok) {
|
|
80
83
|
console.error(`Error: ${res.error}`);
|
|
81
|
-
|
|
84
|
+
process2.exit(1);
|
|
82
85
|
}
|
|
83
86
|
}
|
|
84
87
|
function assertKnownPlugin(pluginId) {
|
|
85
88
|
const ids = getPluginIds();
|
|
86
89
|
if (!ids.includes(pluginId)) {
|
|
87
90
|
console.error(`Error: unknown plugin '${pluginId}'. Available: ${ids.join(", ")}`);
|
|
88
|
-
|
|
91
|
+
process2.exit(1);
|
|
89
92
|
}
|
|
90
93
|
}
|
|
91
94
|
function readJsonFileOrExit(path, flag) {
|
|
@@ -96,7 +99,7 @@ function readJsonFileOrExit(path, flag) {
|
|
|
96
99
|
console.error(
|
|
97
100
|
`Error reading ${flag} file '${path}': ${err instanceof Error ? err.message : String(err)}`
|
|
98
101
|
);
|
|
99
|
-
|
|
102
|
+
process2.exit(1);
|
|
100
103
|
}
|
|
101
104
|
try {
|
|
102
105
|
return JSON.parse(raw);
|
|
@@ -104,7 +107,7 @@ function readJsonFileOrExit(path, flag) {
|
|
|
104
107
|
console.error(
|
|
105
108
|
`Error parsing ${flag} file '${path}' as JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
106
109
|
);
|
|
107
|
-
|
|
110
|
+
process2.exit(1);
|
|
108
111
|
}
|
|
109
112
|
}
|
|
110
113
|
function readJsonObjectOrExit(path, flag) {
|
|
@@ -113,7 +116,7 @@ function readJsonObjectOrExit(path, flag) {
|
|
|
113
116
|
console.error(
|
|
114
117
|
`Error: ${flag} file '${path}' must contain a JSON object, not ${Array.isArray(parsed) ? "an array" : typeof parsed}.`
|
|
115
118
|
);
|
|
116
|
-
|
|
119
|
+
process2.exit(1);
|
|
117
120
|
}
|
|
118
121
|
return parsed;
|
|
119
122
|
}
|
|
@@ -159,7 +162,7 @@ async function handleEmulationGet(argv, apiUrl, token) {
|
|
|
159
162
|
const json = hasFlag(argv, "--json");
|
|
160
163
|
if (!templateId) {
|
|
161
164
|
console.error("Error: template ID required. Usage: canary sandbox emulation get <template-id>");
|
|
162
|
-
|
|
165
|
+
process2.exit(1);
|
|
163
166
|
}
|
|
164
167
|
const { config, catalog } = await fetchEmulation(apiUrl, token, templateId);
|
|
165
168
|
const available = getAllPlugins().map((plugin) => ({ id: plugin.id, name: plugin.name }));
|
|
@@ -208,7 +211,7 @@ async function setEmulationEnabled(argv, apiUrl, token, enable) {
|
|
|
208
211
|
console.error(
|
|
209
212
|
`Error: template ID required. Usage: canary sandbox emulation ${verb} <template-id> [pluginId]`
|
|
210
213
|
);
|
|
211
|
-
|
|
214
|
+
process2.exit(1);
|
|
212
215
|
}
|
|
213
216
|
const { config } = await fetchEmulation(apiUrl, token, templateId);
|
|
214
217
|
let next;
|
|
@@ -219,7 +222,7 @@ async function setEmulationEnabled(argv, apiUrl, token, enable) {
|
|
|
219
222
|
const exists = (config.plugins ?? []).some((plugin) => plugin.pluginId === pluginId);
|
|
220
223
|
if (!enable && !exists) {
|
|
221
224
|
console.error(`Error: plugin '${pluginId}' is not configured on this template.`);
|
|
222
|
-
|
|
225
|
+
process2.exit(1);
|
|
223
226
|
}
|
|
224
227
|
next = withPluginEnabled(config, pluginId, enable);
|
|
225
228
|
}
|
|
@@ -235,7 +238,7 @@ async function handleEmulationSet(argv, apiUrl, token) {
|
|
|
235
238
|
console.error(
|
|
236
239
|
"Usage: canary sandbox emulation set <template-id> <pluginId> [--config <file>] [--fixture <file>] [--env-mapping <file>]"
|
|
237
240
|
);
|
|
238
|
-
|
|
241
|
+
process2.exit(1);
|
|
239
242
|
}
|
|
240
243
|
assertKnownPlugin(pluginId);
|
|
241
244
|
const configPath = getArgValue(argv, "--config");
|
|
@@ -243,7 +246,7 @@ async function handleEmulationSet(argv, apiUrl, token) {
|
|
|
243
246
|
const envMappingPath = getArgValue(argv, "--env-mapping");
|
|
244
247
|
if (!configPath && !fixturePath && !envMappingPath) {
|
|
245
248
|
console.error("Error: provide at least one of --config, --fixture, --env-mapping");
|
|
246
|
-
|
|
249
|
+
process2.exit(1);
|
|
247
250
|
}
|
|
248
251
|
const pluginConfig = configPath ? readJsonObjectOrExit(configPath, "--config") : void 0;
|
|
249
252
|
const fixture = fixturePath ? readJsonFileOrExit(fixturePath, "--fixture") : void 0;
|
|
@@ -268,12 +271,12 @@ async function handleEmulationRemove(argv, apiUrl, token) {
|
|
|
268
271
|
console.error(
|
|
269
272
|
"Error: template ID and pluginId required. Usage: canary sandbox emulation remove <template-id> <pluginId>"
|
|
270
273
|
);
|
|
271
|
-
|
|
274
|
+
process2.exit(1);
|
|
272
275
|
}
|
|
273
276
|
const { config } = await fetchEmulation(apiUrl, token, templateId);
|
|
274
277
|
if (!(config.plugins ?? []).some((plugin) => plugin.pluginId === pluginId)) {
|
|
275
278
|
console.error(`Error: plugin '${pluginId}' is not configured on this template.`);
|
|
276
|
-
|
|
279
|
+
process2.exit(1);
|
|
277
280
|
}
|
|
278
281
|
const next = withoutPlugin(config, pluginId);
|
|
279
282
|
await patchEmulation(apiUrl, token, templateId, next);
|
|
@@ -302,12 +305,12 @@ async function handleEmulation(argv, apiUrl, token) {
|
|
|
302
305
|
console.error(
|
|
303
306
|
`Unknown emulation action: ${action2 ?? "(none)"}. Use get/enable/disable/set/remove.`
|
|
304
307
|
);
|
|
305
|
-
|
|
308
|
+
process2.exit(1);
|
|
306
309
|
}
|
|
307
310
|
}
|
|
308
311
|
|
|
309
312
|
// src/sandbox-logs.ts
|
|
310
|
-
import
|
|
313
|
+
import process3 from "process";
|
|
311
314
|
var requestFailureLogsUrl = (apiUrl, token, instanceId) => apiRequest(
|
|
312
315
|
apiUrl,
|
|
313
316
|
token,
|
|
@@ -342,7 +345,7 @@ async function handleLogs(argv, apiUrl, token) {
|
|
|
342
345
|
const json = hasFlag(argv, "--json");
|
|
343
346
|
if (!instanceId) {
|
|
344
347
|
console.error("Error: instance ID required. Usage: canary sandbox logs <instance-id>");
|
|
345
|
-
|
|
348
|
+
process3.exit(1);
|
|
346
349
|
}
|
|
347
350
|
const service2 = getArgValue(argv, "--service");
|
|
348
351
|
const tail = getArgValue(argv, "--tail") ?? "200";
|
|
@@ -352,13 +355,13 @@ async function handleLogs(argv, apiUrl, token) {
|
|
|
352
355
|
const bundle = await fetchFailureLogsBundle(apiUrl, token, instanceId);
|
|
353
356
|
if ("error" in bundle) {
|
|
354
357
|
console.error(`Error: ${bundle.error}`);
|
|
355
|
-
|
|
358
|
+
process3.exit(1);
|
|
356
359
|
}
|
|
357
360
|
if (json) {
|
|
358
361
|
writeRawOutput(JSON.stringify(bundle, null, 2));
|
|
359
362
|
return;
|
|
360
363
|
}
|
|
361
|
-
|
|
364
|
+
process3.stdout.write(bundle.text || "(empty bundle)");
|
|
362
365
|
return;
|
|
363
366
|
}
|
|
364
367
|
const res = await apiRequest(
|
|
@@ -380,33 +383,33 @@ async function handleLogs(argv, apiUrl, token) {
|
|
|
380
383
|
}
|
|
381
384
|
console.error(`(live logs unavailable: ${res.error}; showing captured failure snapshot)
|
|
382
385
|
`);
|
|
383
|
-
|
|
386
|
+
process3.stdout.write(bundle.text || "(empty bundle)");
|
|
384
387
|
return;
|
|
385
388
|
}
|
|
386
389
|
console.error(`Error: ${res.error}`);
|
|
387
|
-
|
|
390
|
+
process3.exit(1);
|
|
388
391
|
}
|
|
389
392
|
if (json) {
|
|
390
393
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
391
394
|
return;
|
|
392
395
|
}
|
|
393
|
-
|
|
396
|
+
process3.stdout.write(res.data.logs ?? "(no logs)");
|
|
394
397
|
}
|
|
395
398
|
|
|
396
399
|
// src/sandbox-preview.ts
|
|
397
|
-
import
|
|
398
|
-
async function handlePreview(argv, apiUrl, token) {
|
|
400
|
+
import process4 from "process";
|
|
401
|
+
async function handlePreview(argv, apiUrl, token, appSubdomain) {
|
|
399
402
|
const pr = getArgValue(argv, "--pr");
|
|
400
403
|
if (!pr) {
|
|
401
404
|
console.error("Usage: cofactor sandbox preview --application <name|id> --pr <number> [--json]");
|
|
402
|
-
|
|
405
|
+
process4.exit(1);
|
|
403
406
|
}
|
|
404
407
|
const application = await resolveApplicationId(argv, apiUrl, token);
|
|
405
408
|
const query = new URLSearchParams({ application, pr });
|
|
406
409
|
const res = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/previews?${query}`);
|
|
407
410
|
if (!res.ok) {
|
|
408
411
|
console.error(`Error: ${res.error}`);
|
|
409
|
-
|
|
412
|
+
process4.exit(1);
|
|
410
413
|
}
|
|
411
414
|
const preview = res.data;
|
|
412
415
|
if (hasFlag(argv, "--json")) {
|
|
@@ -415,6 +418,7 @@ async function handlePreview(argv, apiUrl, token) {
|
|
|
415
418
|
}
|
|
416
419
|
const lines = [
|
|
417
420
|
`#${preview.prNumber}: ${preview.status}`,
|
|
421
|
+
` page ${resolveOrgAppUrl(apiUrl, appSubdomain)}${sandboxPreviewPagePath(preview.applicationId, preview.prNumber)}`,
|
|
418
422
|
` commit ${(preview.appliedSha ?? preview.headSha).slice(0, 12)}${preview.appliedSha && preview.appliedSha !== preview.headSha ? ` (building ${preview.headSha.slice(0, 12)})` : ""}`,
|
|
419
423
|
...Object.entries(preview.portUrls).map(([name, url]) => ` ${name.padEnd(8)} ${url}`),
|
|
420
424
|
...preview.personas.map((p) => ` ${p.name.padEnd(8)} ${p.email} ${p.password}`)
|
|
@@ -425,8 +429,8 @@ async function handlePreview(argv, apiUrl, token) {
|
|
|
425
429
|
}
|
|
426
430
|
|
|
427
431
|
// src/sandbox-v2.ts
|
|
428
|
-
import
|
|
429
|
-
import { readFileSync as
|
|
432
|
+
import process6 from "process";
|
|
433
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
430
434
|
|
|
431
435
|
// ../sandbox-helper/src/manifest.ts
|
|
432
436
|
import { createHash as createHash2, randomBytes } from "crypto";
|
|
@@ -1267,1616 +1271,1889 @@ var prepareInputSchema = z2.strictObject({
|
|
|
1267
1271
|
*/
|
|
1268
1272
|
sync: z2.strictObject({ key: syncKeySchema })
|
|
1269
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
|
+
}
|
|
1270
1281
|
|
|
1271
|
-
// src/sandbox-
|
|
1272
|
-
|
|
1273
|
-
|
|
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
|
+
};
|
|
1274
1323
|
}
|
|
1275
|
-
|
|
1276
|
-
|
|
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;
|
|
1277
1345
|
}
|
|
1278
|
-
function
|
|
1279
|
-
return
|
|
1346
|
+
function tarPadding(size) {
|
|
1347
|
+
return new Uint8Array((BLOCK - size % BLOCK) % BLOCK);
|
|
1280
1348
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
const
|
|
1284
|
-
|
|
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;
|
|
1285
1360
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
)
|
|
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
|
+
}
|
|
1291
1384
|
}
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
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;
|
|
1296
1403
|
}
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
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 };
|
|
1302
1409
|
}
|
|
1303
|
-
function
|
|
1304
|
-
|
|
1305
|
-
|
|
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);
|
|
1306
1422
|
}
|
|
1423
|
+
return { files, skipped };
|
|
1307
1424
|
}
|
|
1308
|
-
function
|
|
1309
|
-
|
|
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
|
+
}
|
|
1310
1450
|
}
|
|
1311
|
-
function
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
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 });
|
|
1323
1464
|
}
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
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]);
|
|
1327
1480
|
}
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
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;
|
|
1337
1491
|
}
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
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);
|
|
1347
1510
|
}
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
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;
|
|
1355
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" });
|
|
1356
1533
|
}
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
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;
|
|
1362
1546
|
}
|
|
1547
|
+
batches.at(-1).push(file);
|
|
1548
|
+
size += cost(file);
|
|
1363
1549
|
}
|
|
1364
|
-
|
|
1365
|
-
|
|
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
|
+
});
|
|
1366
1560
|
}
|
|
1367
|
-
function
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
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)}`);
|
|
1376
1571
|
}
|
|
1377
|
-
if (
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
for (const step of seeds) console.log(` ${step.id} ${step.action} ${(step.ms / 1e3).toFixed(1)}s`);
|
|
1384
|
-
const ran = data.snapshot.buildPhases.build?.steps.filter((step) => step.ran) ?? [];
|
|
1385
|
-
if (ran.length > 0) {
|
|
1386
|
-
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 {
|
|
1387
1578
|
}
|
|
1579
|
+
throw new SyncError(`the sandbox refused ${path} (${response.status}): ${message.slice(0, 2e3)}`);
|
|
1388
1580
|
}
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
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
|
+
);
|
|
1392
1589
|
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
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)}`);
|
|
1397
1599
|
}
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
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;
|
|
1406
1617
|
}
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
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
|
+
}
|
|
1419
1643
|
}
|
|
1420
1644
|
}
|
|
1645
|
+
if (!finished) throw new SyncError("the build stream ended early; check the copy with `cofactor sandbox exec <id> -- cofactor-sandbox status`");
|
|
1421
1646
|
}
|
|
1422
|
-
|
|
1423
|
-
console.log("");
|
|
1424
|
-
console.log(" Actions:");
|
|
1425
|
-
for (const a of data.actions) console.log(` ${formatActionSignature(a)}`);
|
|
1426
|
-
}
|
|
1427
|
-
if (data.operations.length > 0) {
|
|
1428
|
-
console.log("");
|
|
1429
|
-
console.log(" Operations:");
|
|
1430
|
-
for (const op of data.operations) console.log(` ${formatOperationSignature(op)}`);
|
|
1431
|
-
}
|
|
1647
|
+
return { written, deleted, restarted, steps };
|
|
1432
1648
|
}
|
|
1433
|
-
|
|
1434
|
-
|
|
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");
|
|
1435
1655
|
const json = hasFlag(argv, "--json");
|
|
1436
|
-
const
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
process4.exit(1);
|
|
1448
|
-
}
|
|
1449
|
-
const { definition, issues } = validateDefinition(yamlText, delivery);
|
|
1450
|
-
if (json) {
|
|
1451
|
-
writeRawOutput(JSON.stringify({ ok: issues.length === 0, issues }, null, 2));
|
|
1452
|
-
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 `..`).");
|
|
1453
1667
|
}
|
|
1454
|
-
if (
|
|
1455
|
-
|
|
1456
|
-
|
|
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.");
|
|
1457
1671
|
}
|
|
1458
|
-
const
|
|
1459
|
-
const
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1672
|
+
const started = Date.now();
|
|
1673
|
+
const sessionRes = await apiRequest(
|
|
1674
|
+
apiUrl,
|
|
1675
|
+
token,
|
|
1676
|
+
"POST",
|
|
1677
|
+
`/org/sandboxes-v2/sandboxes/${id}/sync-session`
|
|
1463
1678
|
);
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
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
|
-
for (let i = 0; i < argv.length; i++) {
|
|
1492
|
-
const arg = argv[i];
|
|
1493
|
-
if (spec.values.includes(arg)) {
|
|
1494
|
-
i++;
|
|
1495
|
-
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
|
+
});
|
|
1496
1706
|
}
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
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
|
|
1503
1739
|
);
|
|
1504
|
-
|
|
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();
|
|
1505
1780
|
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1781
|
+
} catch (error) {
|
|
1782
|
+
if (error instanceof SyncError) return fail4(error.message);
|
|
1783
|
+
throw error;
|
|
1508
1784
|
}
|
|
1509
1785
|
}
|
|
1510
|
-
function
|
|
1511
|
-
|
|
1512
|
-
const result = [];
|
|
1513
|
-
for (let i = 0; i < argv.length; i++) {
|
|
1514
|
-
const arg = argv[i];
|
|
1515
|
-
if (values.includes(arg)) i++;
|
|
1516
|
-
else if (!arg.startsWith("-")) result.push(arg);
|
|
1517
|
-
}
|
|
1518
|
-
return result;
|
|
1786
|
+
function isInsideRepo(dir) {
|
|
1787
|
+
return git(["rev-parse", "--is-inside-work-tree"], { cwd: dir, okCodes: [0, 128] }).trim() === "true";
|
|
1519
1788
|
}
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
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
|
+
});
|
|
1528
1840
|
}
|
|
1529
|
-
|
|
1530
|
-
const
|
|
1531
|
-
if (!
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
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();
|
|
1545
1901
|
}
|
|
1546
|
-
printApplicationHuman(res.data);
|
|
1547
1902
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
async function pollSandboxUntilReady(apiUrl, token, id, timeoutSeconds) {
|
|
1552
|
-
const deadline = Date.now() + timeoutSeconds * 1e3 + CLAIM_POLL_GRACE_MS;
|
|
1553
|
-
let toldUpgrading = false;
|
|
1554
|
-
while (Date.now() < deadline) {
|
|
1555
|
-
const res = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
|
|
1556
|
-
if (!res.ok) return { error: res.error ?? "unknown error" };
|
|
1557
|
-
if (res.data.status === "ready" || res.data.status === "failed") return { data: res.data };
|
|
1558
|
-
if (res.data.upgrading && !toldUpgrading) {
|
|
1559
|
-
toldUpgrading = true;
|
|
1560
|
-
console.error(" updating this push to the current sandbox platform (once; the claim continues after)...");
|
|
1561
|
-
}
|
|
1562
|
-
await sleep(CLAIM_POLL_INTERVAL_MS);
|
|
1563
|
-
}
|
|
1564
|
-
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);
|
|
1565
1906
|
}
|
|
1566
|
-
|
|
1567
|
-
const
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
const
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
if (
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
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.");
|
|
1591
1939
|
}
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
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);
|
|
1606
1954
|
}
|
|
1607
|
-
if (data.status === "failed") process4.exit(1);
|
|
1608
1955
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
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}`);
|
|
1624
1992
|
}
|
|
1625
|
-
printSandbox(res.data);
|
|
1626
1993
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
"
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
if (!res.ok) {
|
|
1644
|
-
console.error(`Error: ${res.error ?? "could not list sandboxes"}`);
|
|
1645
|
-
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}` : ""}`);
|
|
1646
2010
|
}
|
|
1647
|
-
claims.push(...res.data);
|
|
1648
|
-
cursor = res.nextCursor ?? null;
|
|
1649
|
-
} while (cursor);
|
|
1650
|
-
if (json) {
|
|
1651
|
-
writeRawOutput(JSON.stringify(claims, null, 2));
|
|
1652
|
-
return;
|
|
1653
|
-
}
|
|
1654
|
-
if (claims.length === 0) {
|
|
1655
|
-
console.log("No live claims.");
|
|
1656
|
-
return;
|
|
1657
2011
|
}
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
console.log(
|
|
1662
|
-
`${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 ?? "-"}`
|
|
1663
|
-
);
|
|
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}`);
|
|
1664
2015
|
}
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
case "number": {
|
|
1674
|
-
const value = Number(raw);
|
|
1675
|
-
if (Number.isNaN(value)) throw new Error(`must be a number, got "${raw}"`);
|
|
1676
|
-
return value;
|
|
1677
|
-
}
|
|
1678
|
-
case "boolean": {
|
|
1679
|
-
if (raw === "true") return true;
|
|
1680
|
-
if (raw === "false") return false;
|
|
1681
|
-
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}`);
|
|
1682
2024
|
}
|
|
1683
|
-
default:
|
|
1684
|
-
return raw;
|
|
1685
2025
|
}
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
const
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
if (
|
|
1693
|
-
console.
|
|
1694
|
-
|
|
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}`);
|
|
1695
2035
|
}
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
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}`);
|
|
1702
2043
|
}
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
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}`);
|
|
1708
2050
|
}
|
|
1709
2051
|
}
|
|
1710
|
-
|
|
2052
|
+
console.log(` Personas: ${def.personas.join(", ") || "(none)"}`);
|
|
2053
|
+
console.log(` Ports: ${def.ports.map((p) => `${p.name} (${p.access})`).join(", ") || "(none)"}`);
|
|
1711
2054
|
}
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
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(", ")}`);
|
|
1719
2075
|
}
|
|
1720
|
-
if (pollRes.data.status !== "running") return pollRes.data;
|
|
1721
|
-
await sleep(2e3);
|
|
1722
2076
|
}
|
|
1723
|
-
console.
|
|
1724
|
-
|
|
1725
|
-
}
|
|
1726
|
-
async function handleSandboxRun(argv, apiUrl, token) {
|
|
1727
|
-
const positional = argv.filter((a) => !a.startsWith("-"));
|
|
1728
|
-
const [id, actionRef] = positional;
|
|
1729
|
-
if (!id || !actionRef) {
|
|
1730
|
-
console.error("Usage: cofactor sandbox run <id> <action|plugin.op> [--input k=v ...] [--json-input '{...}'] [--json]");
|
|
1731
|
-
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}`);
|
|
1732
2080
|
}
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
console.error(`Error: ${showRes.error}`);
|
|
1738
|
-
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}`);
|
|
1739
2085
|
}
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
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
|
+
}
|
|
1748
2095
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
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
|
+
}
|
|
1756
2108
|
}
|
|
1757
|
-
} else if (repoAction) {
|
|
1758
|
-
const propTypes = Object.fromEntries(Object.entries(repoAction.input).map(([key, prop]) => [key, prop.type]));
|
|
1759
|
-
input = buildRunInputFromFlags(argv, propTypes);
|
|
1760
|
-
} else {
|
|
1761
|
-
input = buildRunInputFromFlags(argv, jsonSchemaPropTypes(operation.inputSchema));
|
|
1762
2109
|
}
|
|
1763
|
-
|
|
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(
|
|
1764
2157
|
apiUrl,
|
|
1765
2158
|
token,
|
|
1766
|
-
"
|
|
1767
|
-
`/org/
|
|
1768
|
-
{ action: actionRef, input }
|
|
2159
|
+
"GET",
|
|
2160
|
+
`/org/applications/${applicationId}/sandbox${at ? `?at=${encodeURIComponent(at)}` : ""}`
|
|
1769
2161
|
);
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
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);
|
|
1776
2212
|
}
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
writeRawOutput(JSON.stringify(repoAction ? repoAction.input : operation.inputSchema, null, 2));
|
|
1780
|
-
} else {
|
|
1781
|
-
console.error(`Error: ${enqueueRes.error}`);
|
|
2213
|
+
i++;
|
|
2214
|
+
continue;
|
|
1782
2215
|
}
|
|
1783
|
-
|
|
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);
|
|
1784
2227
|
}
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
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));
|
|
1790
2244
|
return;
|
|
1791
2245
|
}
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
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);
|
|
1800
2253
|
}
|
|
1801
|
-
|
|
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;
|
|
1802
2266
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
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)...");
|
|
1815
2285
|
}
|
|
2286
|
+
await sleep(CLAIM_POLL_INTERVAL_MS);
|
|
1816
2287
|
}
|
|
1817
|
-
return {
|
|
1818
|
-
}
|
|
1819
|
-
function execExitCode(run) {
|
|
1820
|
-
if (run.status === "succeeded") return 0;
|
|
1821
|
-
return run.exitCode ?? 1;
|
|
2288
|
+
return { error: `timed out waiting for sandbox ${id} to become ready (${Math.round(timeoutSeconds / 60)} min)` };
|
|
1822
2289
|
}
|
|
1823
|
-
async function
|
|
1824
|
-
const
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
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);
|
|
1828
2300
|
}
|
|
1829
|
-
const
|
|
1830
|
-
const
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
);
|
|
1837
|
-
if (
|
|
1838
|
-
|
|
1839
|
-
|
|
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);
|
|
1840
2315
|
}
|
|
1841
|
-
const
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
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;
|
|
1853
2328
|
}
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
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);
|
|
1861
2335
|
}
|
|
1862
|
-
|
|
1863
|
-
if (
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
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);
|
|
1868
2343
|
}
|
|
1869
|
-
async function
|
|
2344
|
+
async function handleSandboxShow(argv, apiUrl, token) {
|
|
1870
2345
|
const [id] = argv.filter((a) => !a.startsWith("-"));
|
|
1871
2346
|
if (!id) {
|
|
1872
|
-
console.error("Usage: cofactor sandbox
|
|
1873
|
-
|
|
2347
|
+
console.error("Usage: cofactor sandbox show <id> [--json]");
|
|
2348
|
+
process6.exit(1);
|
|
1874
2349
|
}
|
|
1875
|
-
const
|
|
1876
|
-
|
|
1877
|
-
token,
|
|
1878
|
-
"DELETE",
|
|
1879
|
-
`/org/sandboxes-v2/sandboxes/${id}`
|
|
1880
|
-
);
|
|
2350
|
+
const json = hasFlag(argv, "--json");
|
|
2351
|
+
const res = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
|
|
1881
2352
|
if (!res.ok) {
|
|
1882
2353
|
console.error(`Error: ${res.error}`);
|
|
1883
|
-
|
|
2354
|
+
process6.exit(1);
|
|
1884
2355
|
}
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
const applicationId = await resolveApplicationId(argv, apiUrl, token);
|
|
1889
|
-
const onlyEnvironment = getArgValue(argv, "--environment");
|
|
1890
|
-
const json = hasFlag(argv, "--json");
|
|
1891
|
-
const log = (line) => json ? console.error(line) : console.log(line);
|
|
1892
|
-
const app = await fetchApplicationSandbox(apiUrl, token, applicationId);
|
|
1893
|
-
if (!app.version || !app.definition) {
|
|
1894
|
-
console.error("Error: this application has no version to verify yet: run `cofactor sandbox push` first.");
|
|
1895
|
-
process4.exit(1);
|
|
1896
|
-
}
|
|
1897
|
-
const version = app.version;
|
|
1898
|
-
const allEnvironments = app.definition.environments.map((e) => e.name);
|
|
1899
|
-
if (onlyEnvironment && !allEnvironments.includes(onlyEnvironment)) {
|
|
1900
|
-
console.error(`Error: environment "${onlyEnvironment}" not found. Known: ${allEnvironments.join(", ")}`);
|
|
1901
|
-
process4.exit(1);
|
|
2356
|
+
if (json) {
|
|
2357
|
+
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
2358
|
+
return;
|
|
1902
2359
|
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
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(
|
|
1910
2373
|
apiUrl,
|
|
1911
2374
|
token,
|
|
1912
|
-
"
|
|
1913
|
-
|
|
1914
|
-
{ applicationId, environment: environment2, version }
|
|
2375
|
+
"GET",
|
|
2376
|
+
`/org/sandboxes-v2/sandboxes?${query}`
|
|
1915
2377
|
);
|
|
1916
|
-
if (!
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
results.push({ environment: environment2, ok: false, seconds: seconds2, error: claimRes.error });
|
|
1920
|
-
continue;
|
|
1921
|
-
}
|
|
1922
|
-
const id = claimRes.data.id;
|
|
1923
|
-
log(` claimed ${id}, waiting...`);
|
|
1924
|
-
const waited = await pollSandboxUntilReady(apiUrl, token, id, CLAIM_TIMEOUT_SECONDS);
|
|
1925
|
-
const seconds = (Date.now() - started) / 1e3;
|
|
1926
|
-
if ("error" in waited || waited.data.status !== "ready") {
|
|
1927
|
-
const error = "error" in waited ? waited.error : waited.data.error ?? "failed";
|
|
1928
|
-
console.error(` failed: ${error}`);
|
|
1929
|
-
results.push({ environment: environment2, ok: false, seconds, error });
|
|
1930
|
-
} else {
|
|
1931
|
-
const { data } = waited;
|
|
1932
|
-
log(` ready in ${data.readyMs !== null ? (data.readyMs / 1e3).toFixed(1) : "?"}s`);
|
|
1933
|
-
for (const [stepId, outputs] of Object.entries(data.seedOutputs)) {
|
|
1934
|
-
for (const [key, value] of Object.entries(outputs)) {
|
|
1935
|
-
log(` ${stepId}.${key}: ${JSON.stringify(value)}`);
|
|
1936
|
-
}
|
|
1937
|
-
}
|
|
1938
|
-
const personas = [];
|
|
1939
|
-
if (data.nativeLogin) {
|
|
1940
|
-
for (const { name } of data.personas) {
|
|
1941
|
-
const checked = await apiRequest(
|
|
1942
|
-
apiUrl,
|
|
1943
|
-
token,
|
|
1944
|
-
"POST",
|
|
1945
|
-
`/org/sandboxes-v2/sandboxes/${id}/auth-check`,
|
|
1946
|
-
{ persona: name },
|
|
1947
|
-
// The API waits on the script (up to 60 s) and the sandbox connection.
|
|
1948
|
-
{ timeoutMs: 9e4 }
|
|
1949
|
-
);
|
|
1950
|
-
const check = checked.ok ? checked.data : { persona: name, ok: false, error: checked.error ?? "unknown error" };
|
|
1951
|
-
personas.push(check);
|
|
1952
|
-
if (check.ok) log(` persona ${name}: signed in`);
|
|
1953
|
-
else console.error(` persona ${name}: FAILED
|
|
1954
|
-
${indentLines(check.error ?? "", " ")}`);
|
|
1955
|
-
}
|
|
1956
|
-
}
|
|
1957
|
-
const failedPersonas = personas.filter((p) => !p.ok).map((p) => p.persona);
|
|
1958
|
-
results.push({
|
|
1959
|
-
environment: environment2,
|
|
1960
|
-
ok: failedPersonas.length === 0,
|
|
1961
|
-
seconds,
|
|
1962
|
-
...failedPersonas.length > 0 ? { error: `persona sign-in failed: ${failedPersonas.join(", ")}` } : {},
|
|
1963
|
-
...data.nativeLogin ? { personas } : {}
|
|
1964
|
-
});
|
|
2378
|
+
if (!res.ok) {
|
|
2379
|
+
console.error(`Error: ${res.error ?? "could not list sandboxes"}`);
|
|
2380
|
+
process6.exit(1);
|
|
1965
2381
|
}
|
|
1966
|
-
|
|
1967
|
-
|
|
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;
|
|
1968
2388
|
}
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
for (const r of results) {
|
|
1973
|
-
const errSuffix = r.error ? ` (${r.error})` : "";
|
|
1974
|
-
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;
|
|
1975
2392
|
}
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
1983
|
-
import { createHash as createHash3 } from "crypto";
|
|
1984
|
-
import { createReadStream, createWriteStream, mkdtempSync as mkdtempSync2, readFileSync as readFileSync4, rmSync as rmSync2 } from "fs";
|
|
1985
|
-
import { open } from "fs/promises";
|
|
1986
|
-
import { tmpdir as tmpdir2 } from "os";
|
|
1987
|
-
import { dirname as dirname2, join as join2 } from "path";
|
|
1988
|
-
import { pipeline } from "stream/promises";
|
|
1989
|
-
import { createGunzip } from "zlib";
|
|
1990
|
-
|
|
1991
|
-
// src/sandbox-push-syncable.ts
|
|
1992
|
-
import { spawnSync } from "child_process";
|
|
1993
|
-
import { existsSync, mkdtempSync, readFileSync as readFileSync3, rmSync, statSync } from "fs";
|
|
1994
|
-
import { tmpdir } from "os";
|
|
1995
|
-
import { dirname, join, resolve } from "path";
|
|
1996
|
-
var realDocker = (args) => {
|
|
1997
|
-
const result = spawnSync("docker", args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 });
|
|
1998
|
-
if (result.error) return { status: null, stdout: "", stderr: result.error.message };
|
|
1999
|
-
return { status: result.status, stdout: result.stdout, stderr: result.stderr };
|
|
2000
|
-
};
|
|
2001
|
-
var SAMPLE = 20;
|
|
2002
|
-
function sampleOf(paths) {
|
|
2003
|
-
const sorted = [...paths].sort();
|
|
2004
|
-
if (sorted.length <= SAMPLE) return sorted;
|
|
2005
|
-
return Array.from({ length: SAMPLE }, (_, i) => sorted[Math.floor(i * sorted.length / SAMPLE)]);
|
|
2006
|
-
}
|
|
2007
|
-
var SYNCABLE_RULE = "a syncable image has your definition file at its WORKDIR, as `COPY . .` gives you";
|
|
2008
|
-
function syncabilityLine(params) {
|
|
2009
|
-
const docker2 = params.docker ?? realDocker;
|
|
2010
|
-
const git2 = spawnSync("git", ["ls-files", "-s", "-z"], { cwd: params.definitionDir, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
|
|
2011
|
-
if (git2.status !== 0) return "Sync: not checked (the definition is not in a git repository).";
|
|
2012
|
-
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)));
|
|
2013
|
-
const temp = mkdtempSync(join(tmpdir(), "cofactor-syncable-"));
|
|
2014
|
-
const created = docker2(["create", params.image]);
|
|
2015
|
-
if (created.status !== 0) {
|
|
2016
|
-
rmSync(temp, { recursive: true, force: true });
|
|
2017
|
-
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
|
+
);
|
|
2018
2399
|
}
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
if (
|
|
2025
|
-
return
|
|
2026
|
-
};
|
|
2027
|
-
const anchor = copyOut("cofactor.sandbox.yaml");
|
|
2028
|
-
if (!anchor) {
|
|
2029
|
-
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);
|
|
2030
2407
|
}
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
const bytes = copyOut(path);
|
|
2036
|
-
if (!bytes) missing += 1;
|
|
2037
|
-
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;
|
|
2038
2412
|
}
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
docker2(["rm", container]);
|
|
2047
|
-
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;
|
|
2048
2420
|
}
|
|
2049
2421
|
}
|
|
2050
|
-
function
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
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
|
+
}
|
|
2056
2444
|
}
|
|
2445
|
+
return input;
|
|
2057
2446
|
}
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
}
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
function withSandboxLayers(team, added, created) {
|
|
2072
|
-
return {
|
|
2073
|
-
...team,
|
|
2074
|
-
rootfs: { ...team.rootfs, diff_ids: [...team.rootfs.diff_ids, ...added.map((layer) => layer.digest)] },
|
|
2075
|
-
history: [...team.history ?? [], ...added.map(() => ({ created, created_by: "cofactor sandbox push" }))],
|
|
2076
|
-
config: { ...team.config, Entrypoint: ["/usr/local/bin/cofactor-sandbox", "boot"], Cmd: [] }
|
|
2077
|
-
};
|
|
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);
|
|
2078
2460
|
}
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
put(0, 100, name);
|
|
2088
|
-
octal(100, 8, mode);
|
|
2089
|
-
octal(108, 8, 0);
|
|
2090
|
-
octal(116, 8, 0);
|
|
2091
|
-
octal(124, 12, size);
|
|
2092
|
-
octal(136, 12, 0);
|
|
2093
|
-
put(148, 8, " ");
|
|
2094
|
-
put(156, 1, "0");
|
|
2095
|
-
put(257, 6, "ustar\0");
|
|
2096
|
-
put(263, 2, "00");
|
|
2097
|
-
const checksum = header.reduce((sum, byte) => sum + byte, 0);
|
|
2098
|
-
put(148, 8, `${checksum.toString(8).padStart(6, "0")}\0 `);
|
|
2099
|
-
return header;
|
|
2100
|
-
}
|
|
2101
|
-
function tarPadding(size) {
|
|
2102
|
-
return new Uint8Array((BLOCK - size % BLOCK) % BLOCK);
|
|
2103
|
-
}
|
|
2104
|
-
var TAR_END = new Uint8Array(BLOCK * 2);
|
|
2105
|
-
function tarOf(files) {
|
|
2106
|
-
const parts = files.flatMap((file) => [tarHeader(file.name, file.data.length, file.mode), file.data, tarPadding(file.data.length)]);
|
|
2107
|
-
parts.push(TAR_END);
|
|
2108
|
-
const out = new Uint8Array(parts.reduce((total, part) => total + part.length, 0));
|
|
2109
|
-
let at = 0;
|
|
2110
|
-
for (const part of parts) {
|
|
2111
|
-
out.set(part, at);
|
|
2112
|
-
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);
|
|
2113
2469
|
}
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
}
|
|
2135
|
-
|
|
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);
|
|
2136
2493
|
}
|
|
2137
|
-
|
|
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);
|
|
2138
2537
|
}
|
|
2538
|
+
writeRawOutput(JSON.stringify(run.output ?? {}, null, 2));
|
|
2139
2539
|
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
"
|
|
2144
|
-
"",
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
var PushError = class extends Error {
|
|
2154
|
-
constructor(message, issues = []) {
|
|
2155
|
-
super(message);
|
|
2156
|
-
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
|
+
}
|
|
2157
2553
|
}
|
|
2158
|
-
|
|
2159
|
-
};
|
|
2160
|
-
function fail(message, issues) {
|
|
2161
|
-
throw new PushError(message, issues);
|
|
2554
|
+
return { ok: true, args: { id, timeoutSeconds, json: hasFlag(options, "--json"), command } };
|
|
2162
2555
|
}
|
|
2163
|
-
function
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
stdio: ["ignore", "pipe", options.quiet ? "pipe" : "inherit"],
|
|
2167
|
-
maxBuffer: 64 * 1024 * 1024
|
|
2168
|
-
});
|
|
2169
|
-
if (result.error) fail(`could not run docker (${result.error.message}); is Docker installed and running?`);
|
|
2170
|
-
if (result.status !== 0) fail(`docker ${args.slice(0, 2).join(" ")} failed:
|
|
2171
|
-
${result.stderr ?? ""}`.trimEnd());
|
|
2172
|
-
return result.stdout;
|
|
2556
|
+
function execExitCode(run) {
|
|
2557
|
+
if (run.status === "succeeded") return 0;
|
|
2558
|
+
return run.exitCode ?? 1;
|
|
2173
2559
|
}
|
|
2174
|
-
function
|
|
2175
|
-
const
|
|
2176
|
-
if (
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
if (inspected.Os !== "linux" || inspected.Architecture !== "amd64") {
|
|
2180
|
-
fail(
|
|
2181
|
-
`${image} is ${inspected.Os}/${inspected.Architecture}; sandboxes run linux/amd64. Rebuild it with
|
|
2182
|
-
docker buildx build --platform linux/amd64 -t ${image} --load <context>`
|
|
2183
|
-
);
|
|
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);
|
|
2184
2565
|
}
|
|
2185
|
-
const
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
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);
|
|
2190
2577
|
}
|
|
2191
|
-
const
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
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"
|
|
2196
2585
|
);
|
|
2197
|
-
const
|
|
2198
|
-
if (
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
`${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.`
|
|
2202
|
-
);
|
|
2586
|
+
const code = execExitCode(run);
|
|
2587
|
+
if (json) {
|
|
2588
|
+
writeRawOutput(JSON.stringify(run, null, 2));
|
|
2589
|
+
process6.exit(code);
|
|
2203
2590
|
}
|
|
2204
|
-
|
|
2205
|
-
}
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
if (!res.ok) fail(res.error ?? "unknown error", res.issues);
|
|
2212
|
-
return res.data;
|
|
2213
|
-
}
|
|
2214
|
-
var mib = (bytes) => `${(bytes / 1024 / 1024).toFixed(bytes < 10 * 1024 * 1024 ? 1 : 0)} MB`;
|
|
2215
|
-
async function readAt(file, offset, size) {
|
|
2216
|
-
const bytes = new Uint8Array(size);
|
|
2217
|
-
for (let at = 0; at < size; ) {
|
|
2218
|
-
const { bytesRead } = await file.read(bytes, at, size - at, offset + at);
|
|
2219
|
-
if (bytesRead === 0) return bytes.subarray(0, at);
|
|
2220
|
-
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);
|
|
2221
2598
|
}
|
|
2222
|
-
|
|
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);
|
|
2223
2605
|
}
|
|
2224
|
-
async function
|
|
2225
|
-
const
|
|
2226
|
-
|
|
2227
|
-
|
|
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);
|
|
2228
2611
|
}
|
|
2229
|
-
|
|
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 });
|
|
2230
2625
|
}
|
|
2231
|
-
function
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
async function putBlob(url, headers, part) {
|
|
2235
|
-
const digest = part.digest;
|
|
2236
|
-
for (let attempt = 1; ; attempt++) {
|
|
2237
|
-
let response;
|
|
2238
|
-
try {
|
|
2239
|
-
const init = {
|
|
2240
|
-
method: "PUT",
|
|
2241
|
-
headers: { ...headers, "content-length": String(part.size) },
|
|
2242
|
-
body: part.body(),
|
|
2243
|
-
duplex: "half"
|
|
2244
|
-
};
|
|
2245
|
-
response = await fetch(url, init);
|
|
2246
|
-
} catch (error) {
|
|
2247
|
-
if (attempt >= UPLOAD_ATTEMPTS) throw error;
|
|
2248
|
-
await new Promise((resolve3) => setTimeout(resolve3, 2e3 * attempt));
|
|
2249
|
-
continue;
|
|
2250
|
-
}
|
|
2251
|
-
if (response.ok) return;
|
|
2252
|
-
const detail = `${response.status}: ${(await response.text()).slice(0, 300)}`;
|
|
2253
|
-
if (response.status < 500 || attempt >= UPLOAD_ATTEMPTS) {
|
|
2254
|
-
fail(`uploading ${digest} was refused (HTTP ${detail}); run the push again to upload what is still missing`);
|
|
2255
|
-
}
|
|
2256
|
-
await new Promise((resolve3) => setTimeout(resolve3, 2e3 * attempt));
|
|
2257
|
-
}
|
|
2258
|
-
}
|
|
2259
|
-
async function savedImage(archive) {
|
|
2260
|
-
const file = await open(archive);
|
|
2261
|
-
try {
|
|
2262
|
-
const entries = await tarEntries((offset, length) => readAt(file, offset, length)).catch(
|
|
2263
|
-
(error) => fail(`reading the saved image: ${error instanceof Error ? error.message : String(error)}`)
|
|
2264
|
-
);
|
|
2265
|
-
const byName = new Map(entries.map((entry) => [entry.name.replace(/^\.\//, ""), entry]));
|
|
2266
|
-
const entryOf = (name) => {
|
|
2267
|
-
const entry = byName.get(name);
|
|
2268
|
-
if (!entry) fail(`the saved image has no ${name}; is this a \`docker save\` archive?`);
|
|
2269
|
-
return entry;
|
|
2270
|
-
};
|
|
2271
|
-
const json = async (name) => {
|
|
2272
|
-
const entry = entryOf(name);
|
|
2273
|
-
return JSON.parse(new TextDecoder().decode(await readAt(file, entry.offset, entry.size)));
|
|
2274
|
-
};
|
|
2275
|
-
const layerOf = async (name) => {
|
|
2276
|
-
const entry = entryOf(name);
|
|
2277
|
-
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.`);
|
|
2278
|
-
if (entry.size > MAX_BLOB_BYTES) tooLarge(entry.size);
|
|
2279
|
-
const range = { start: entry.offset, end: entry.offset + entry.size - 1 };
|
|
2280
|
-
const [first, second] = await readAt(file, entry.offset, 2);
|
|
2281
|
-
if (first === 31 && second === 139) {
|
|
2282
|
-
const unpacked = join2(dirname2(archive), `layer-${entry.offset}.tar`);
|
|
2283
|
-
await pipeline(createReadStream(archive, range), createGunzip(), createWriteStream(unpacked)).catch(
|
|
2284
|
-
(error) => fail(`unpacking a compressed layer of the image: ${error instanceof Error ? error.message : String(error)}`)
|
|
2285
|
-
);
|
|
2286
|
-
const plain = await open(unpacked);
|
|
2287
|
-
try {
|
|
2288
|
-
const { size } = await plain.stat();
|
|
2289
|
-
if (size > MAX_BLOB_BYTES) tooLarge(size);
|
|
2290
|
-
return { digest: await digestOf(plain, 0, size), size, body: () => createReadStream(unpacked) };
|
|
2291
|
-
} finally {
|
|
2292
|
-
await plain.close();
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
return { digest: await digestOf(file, entry.offset, entry.size), size: entry.size, body: () => createReadStream(archive, range) };
|
|
2296
|
-
};
|
|
2297
|
-
const [manifest] = await json("manifest.json");
|
|
2298
|
-
if (!manifest) fail("the saved image lists no image");
|
|
2299
|
-
const layers = [];
|
|
2300
|
-
for (const name of manifest.Layers) layers.push(await layerOf(name));
|
|
2301
|
-
return { config: await json(manifest.Config), layers };
|
|
2302
|
-
} finally {
|
|
2303
|
-
await file.close();
|
|
2304
|
-
}
|
|
2305
|
-
}
|
|
2306
|
-
async function pushedImage(archive, cofactorLayers, definitionYaml, config) {
|
|
2307
|
-
const saved = await savedImage(archive);
|
|
2308
|
-
const own = partOf(
|
|
2309
|
-
tarOf([
|
|
2310
|
-
{ name: IMAGE_DEFINITION_FILE.slice(1), data: new TextEncoder().encode(definitionYaml) },
|
|
2311
|
-
{ name: IMAGE_CONFIG_FILE.slice(1), data: new TextEncoder().encode(JSON.stringify(config)) }
|
|
2312
|
-
])
|
|
2313
|
-
);
|
|
2314
|
-
const added = [...cofactorLayers, own];
|
|
2315
|
-
const pushed = withSandboxLayers(saved.config, added, (/* @__PURE__ */ new Date()).toISOString());
|
|
2316
|
-
const imageConfig = partOf(new TextEncoder().encode(JSON.stringify(pushed)));
|
|
2317
|
-
const blob = ({ digest, size }) => ({ digest, size });
|
|
2318
|
-
return {
|
|
2319
|
-
config: blob(imageConfig),
|
|
2320
|
-
layers: [...saved.layers, ...added].map(blob),
|
|
2321
|
-
blobs: new Map([imageConfig, ...saved.layers, own].map((part) => [part.digest, part]))
|
|
2322
|
-
};
|
|
2323
|
-
}
|
|
2324
|
-
async function handleSandboxPush(argv, apiUrl, token) {
|
|
2325
|
-
try {
|
|
2326
|
-
await push(argv, apiUrl, token);
|
|
2327
|
-
} catch (error) {
|
|
2328
|
-
if (!(error instanceof PushError)) throw error;
|
|
2329
|
-
console.error(`Error: ${error.message}`);
|
|
2330
|
-
for (const issue of error.issues) console.error(` ${issue.path || "(root)"}: ${issue.message}`);
|
|
2331
|
-
process5.exit(1);
|
|
2332
|
-
}
|
|
2333
|
-
}
|
|
2334
|
-
async function push(argv, apiUrl, token) {
|
|
2335
|
-
const valueFlags = /* @__PURE__ */ new Set(["--application", "--property", "--definition", "--tag", "--env", "--org", "--token", "--api-url", "--app-url"]);
|
|
2336
|
-
const positional = argv.filter((arg, i) => !arg.startsWith("-") && !valueFlags.has(argv[i - 1] ?? ""));
|
|
2337
|
-
const image = positional[0];
|
|
2338
|
-
if (!image) fail(`an image is required
|
|
2339
|
-
${USAGE}`);
|
|
2340
|
-
const definitionFile = getArgValue(argv, "--definition") ?? "./cofactor.sandbox.yaml";
|
|
2341
|
-
const tags = getArgValues(argv, "--tag");
|
|
2342
|
-
const badTag = tags.find((tag) => !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(tag));
|
|
2343
|
-
if (badTag !== void 0) fail(`--tag ${badTag}: use letters, digits, '.', '_' and '-' (at most 64, starting with a letter or digit)`);
|
|
2344
|
-
if (tags.length > 10) fail("a push takes at most 10 tags");
|
|
2345
|
-
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");
|
|
2346
2629
|
const json = hasFlag(argv, "--json");
|
|
2347
2630
|
const log = (line) => json ? console.error(line) : console.log(line);
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
const applicationId = await resolveOrCreateSandboxApplication(argv, apiUrl, token);
|
|
2358
|
-
const config = imageConfigOf(image);
|
|
2359
|
-
log(syncabilityLineOrSkipped({ image, workdir: config.workdir, definitionFile }));
|
|
2360
|
-
const work = mkdtempSync2(join2(tmpdir2(), "cofactor-push-"));
|
|
2361
|
-
const cleanUp = () => rmSync2(work, { recursive: true, force: true });
|
|
2362
|
-
const interrupted = (signal) => {
|
|
2363
|
-
cleanUp();
|
|
2364
|
-
process5.exit(signal === "SIGINT" ? 130 : 143);
|
|
2365
|
-
};
|
|
2366
|
-
process5.once("SIGINT", interrupted);
|
|
2367
|
-
process5.once("SIGTERM", interrupted);
|
|
2368
|
-
process5.exitCode = 1;
|
|
2369
|
-
try {
|
|
2370
|
-
const ours = await apiOrFail(
|
|
2371
|
-
apiRequest(
|
|
2372
|
-
apiUrl,
|
|
2373
|
-
token,
|
|
2374
|
-
"GET",
|
|
2375
|
-
"/org/sandboxes-v2/helper-layer"
|
|
2376
|
-
)
|
|
2377
|
-
);
|
|
2378
|
-
if (!ours.helper) fail("this Cofactor takes pushes from an older cofactor CLI; use the version it ships with");
|
|
2379
|
-
const cofactorLayers = [ours.helper, ...storageProvider ? [ours.objectStorage[storageProvider]] : []];
|
|
2380
|
-
const archive = join2(work, "image.tar");
|
|
2381
|
-
log("Saving the image...");
|
|
2382
|
-
docker(["save", "--output", archive, image], { quiet: json });
|
|
2383
|
-
const pushing = await pushedImage(archive, cofactorLayers, definitionYaml, config);
|
|
2384
|
-
const { layers } = pushing;
|
|
2385
|
-
const created = await apiOrFail(
|
|
2386
|
-
apiRequest(
|
|
2387
|
-
apiUrl,
|
|
2388
|
-
token,
|
|
2389
|
-
"POST",
|
|
2390
|
-
`/org/applications/${applicationId}/sandbox/uploads`,
|
|
2391
|
-
{ definitionYaml, platformVersion: ours.platformVersion, tags, image: { config: pushing.config, layers } }
|
|
2392
|
-
)
|
|
2393
|
-
);
|
|
2394
|
-
const partsByDigest = pushing.blobs;
|
|
2395
|
-
const uploadBytes = created.uploads.reduce((total, upload) => total + (partsByDigest.get(upload.digest)?.size ?? 0), 0);
|
|
2396
|
-
const imageBytes = layers.reduce((total, layer) => total + layer.size, 0);
|
|
2397
|
-
log(`Pushing ${created.id}: uploading ${created.uploads.length} of ${layers.length + 1} blobs (${mib(uploadBytes)}); ${mib(imageBytes - uploadBytes)} already in Cofactor`);
|
|
2398
|
-
const queue = [...created.uploads];
|
|
2399
|
-
let sent = 0;
|
|
2400
|
-
await Promise.all(
|
|
2401
|
-
Array.from({ length: UPLOAD_CONCURRENCY }, async () => {
|
|
2402
|
-
for (let next = queue.shift(); next; next = queue.shift()) {
|
|
2403
|
-
const part = partsByDigest.get(next.digest);
|
|
2404
|
-
if (!part) fail(`Cofactor asked for ${next.digest}, which this image doesn't have`);
|
|
2405
|
-
await putBlob(next.url, next.headers, part);
|
|
2406
|
-
sent += part.size;
|
|
2407
|
-
if (part.size > 10 * 1024 * 1024) log(` ${mib(sent)} of ${mib(uploadBytes)}`);
|
|
2408
|
-
}
|
|
2409
|
-
})
|
|
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`."
|
|
2410
2640
|
);
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
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 }
|
|
2419
2661
|
);
|
|
2420
|
-
if (
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
while ((pushed.status === "importing" || pushed.status === "uploading") && Date.now() < deadline) {
|
|
2426
|
-
await new Promise((resolve3) => setTimeout(resolve3, IMPORT_POLL_MS));
|
|
2427
|
-
if (Date.now() - reported >= 6e4) {
|
|
2428
|
-
reported = Date.now();
|
|
2429
|
-
log(` still importing (${Math.round((reported - importStarted) / 6e4)} min)`);
|
|
2430
|
-
}
|
|
2431
|
-
pushed = await apiOrFail(
|
|
2432
|
-
apiRequest(apiUrl, token, "GET", `/org/applications/${applicationId}/sandbox/uploads/${created.id}`)
|
|
2433
|
-
);
|
|
2434
|
-
}
|
|
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;
|
|
2435
2667
|
}
|
|
2436
|
-
const
|
|
2437
|
-
|
|
2438
|
-
const
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
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 });
|
|
2444
2676
|
} else {
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
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
|
+
}
|
|
2450
2683
|
}
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
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
|
-
throw new SyncError(`git ${args[0]} failed (${result.signal ?? result.status}): ${result.stderr.trim()}`);
|
|
2482
|
-
}
|
|
2483
|
-
return result.stdout;
|
|
2484
|
-
}
|
|
2485
|
-
var toPosix = (path) => path.split(sep).join("/");
|
|
2486
|
-
function repositoryOf(dir) {
|
|
2487
|
-
const top = git(["rev-parse", "--show-toplevel"], { cwd: dir }).trim();
|
|
2488
|
-
const gitDir = git(["rev-parse", "--absolute-git-dir"], { cwd: dir }).trim();
|
|
2489
|
-
return { top: realpathSync(top), gitDir };
|
|
2490
|
-
}
|
|
2491
|
-
function indexEntries(output2, strip, top) {
|
|
2492
|
-
const files = [];
|
|
2493
|
-
const skipped = [];
|
|
2494
|
-
for (const entry of output2.split("\0")) {
|
|
2495
|
-
if (entry === "") continue;
|
|
2496
|
-
const match = /^(\d{6}) ([0-9a-f]+) \d\t(.*)$/s.exec(entry);
|
|
2497
|
-
if (!match) throw new SyncError(`unexpected git ls-files line: ${entry}`);
|
|
2498
|
-
const [, mode, blob, repoPath] = match;
|
|
2499
|
-
const path = strip === "" ? repoPath : repoPath.slice(strip.length + 1);
|
|
2500
|
-
const onDisk = mode === "100644" || mode === "100755" ? lstatOrNull(join3(top, repoPath)) : null;
|
|
2501
|
-
if (onDisk?.isFile()) files.push({ path, blob, mode: mode === "100755" ? 493 : 420, size: onDisk.size });
|
|
2502
|
-
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}`);
|
|
2503
2714
|
}
|
|
2504
|
-
|
|
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);
|
|
2505
2724
|
}
|
|
2506
|
-
function
|
|
2507
|
-
const
|
|
2508
|
-
const
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
const index = join3(temp, "index");
|
|
2513
|
-
const env = { ...process6.env, GIT_INDEX_FILE: index };
|
|
2514
|
-
const userIndex = join3(gitDir, "index");
|
|
2515
|
-
if (existsSync2(userIndex)) copyFileSync(userIndex, index);
|
|
2516
|
-
else if (git(["rev-parse", "--verify", "-q", "HEAD"], { cwd: top, okCodes: [0, 1] }).trim() !== "") git(["read-tree", "HEAD"], { cwd: top, env });
|
|
2517
|
-
const pathspec = prefix === "" ? "." : `:(literal)${prefix}`;
|
|
2518
|
-
git(["add", "-A", "--", pathspec], { cwd: top, env });
|
|
2519
|
-
const entries = indexEntries(git(["ls-files", "-s", "-z", "--", pathspec], { cwd: top, env }), prefix, top);
|
|
2520
|
-
const files = withRawBlobs(entries.files, top, prefix);
|
|
2521
|
-
const skipped = entries.skipped;
|
|
2522
|
-
const tree = git(prefix === "" ? ["write-tree"] : ["write-tree", `--prefix=${prefix}/`], { cwd: top, env }).trim();
|
|
2523
|
-
const untracked = new Set(
|
|
2524
|
-
git(["ls-files", "--others", "--exclude-standard", "-z", "--", pathspec], { cwd: top }).split("\0").filter(Boolean).map((path) => prefix === "" ? path : path.slice(prefix.length + 1))
|
|
2525
|
-
);
|
|
2526
|
-
return { files, skipped, untracked, tree, repo: top, prefix, env, cleanup };
|
|
2527
|
-
} catch (error) {
|
|
2528
|
-
cleanup();
|
|
2529
|
-
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);
|
|
2530
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);
|
|
2531
2746
|
}
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
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;
|
|
2535
2789
|
try {
|
|
2536
|
-
const
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
const
|
|
2542
|
-
|
|
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("; ")})` : ""}.`;
|
|
2543
2813
|
} finally {
|
|
2814
|
+
docker2(["rm", container]);
|
|
2544
2815
|
rmSync3(temp, { recursive: true, force: true });
|
|
2545
2816
|
}
|
|
2546
2817
|
}
|
|
2547
|
-
|
|
2548
|
-
function withRawBlobs(files, top, prefix, env) {
|
|
2549
|
-
if (files.length === 0) return files;
|
|
2550
|
-
const options = { cwd: top, ...env ? { env } : {} };
|
|
2551
|
-
const repoPaths = files.map((file) => prefix === "" ? file.path : `${prefix}/${file.path}`);
|
|
2552
|
-
const autocrlf = git(["config", "--get", "core.autocrlf"], { ...options, okCodes: [0, 1] }).trim();
|
|
2553
|
-
const converts = autocrlf === "input" || autocrlf !== "" && git(["config", "--get", "--type=bool", "core.autocrlf"], options).trim() === "true";
|
|
2554
|
-
let filtered;
|
|
2555
|
-
if (converts) {
|
|
2556
|
-
filtered = new Set(repoPaths);
|
|
2557
|
-
} else {
|
|
2558
|
-
const out = git(["check-attr", "-z", "--stdin", ...FILTER_ATTRIBUTES], { ...options, input: repoPaths.map((path) => `${path}\0`).join("") }).split("\0");
|
|
2559
|
-
filtered = /* @__PURE__ */ new Set();
|
|
2560
|
-
for (let i = 0; i + 2 < out.length; i += 3) if (out[i + 2] !== "unspecified") filtered.add(out[i]);
|
|
2561
|
-
}
|
|
2562
|
-
return files.map(
|
|
2563
|
-
(file, i) => filtered.has(repoPaths[i]) ? { ...file, blob: gitBlobId(readFileSync5(join3(top, repoPaths[i]))) } : file
|
|
2564
|
-
);
|
|
2565
|
-
}
|
|
2566
|
-
function lstatOrNull(path) {
|
|
2818
|
+
function syncabilityLineOrSkipped(params) {
|
|
2567
2819
|
try {
|
|
2568
|
-
|
|
2820
|
+
const definitionFile2 = resolve3(params.definitionFile);
|
|
2821
|
+
return syncabilityLine({ ...params, definitionFile: definitionFile2, definitionDir: dirname(definitionFile2) });
|
|
2569
2822
|
} catch (error) {
|
|
2570
|
-
|
|
2571
|
-
throw error;
|
|
2823
|
+
return `Sync: not checked (${error instanceof Error ? error.message : String(error)}).`;
|
|
2572
2824
|
}
|
|
2573
2825
|
}
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
}
|
|
2592
|
-
});
|
|
2593
|
-
return seen;
|
|
2594
|
-
}
|
|
2595
|
-
async function chooseDeletions(params) {
|
|
2596
|
-
const repoPath = (path) => params.prefix === "" ? path : `${params.prefix}/${path}`;
|
|
2597
|
-
const ignored = ignoredPaths(params.repo, params.env, params.copyOnly.map((entry) => repoPath(entry.path)));
|
|
2598
|
-
const candidates = params.copyOnly.filter((entry) => !ignored.has(repoPath(entry.path)));
|
|
2599
|
-
const history = everTracked(params.repo, candidates.map((entry) => repoPath(entry.path)));
|
|
2600
|
-
const kept = params.copyOnly.filter((entry) => ignored.has(repoPath(entry.path))).map((entry) => ({ ...entry, why: "ignored here" }));
|
|
2601
|
-
const remove = [];
|
|
2602
|
-
for (const entry of candidates) {
|
|
2603
|
-
if (!entry.path.endsWith("/")) {
|
|
2604
|
-
if (history.has(repoPath(entry.path))) remove.push(entry.path);
|
|
2605
|
-
else kept.push({ ...entry, why: "never tracked here" });
|
|
2606
|
-
continue;
|
|
2607
|
-
}
|
|
2608
|
-
const tracked = [...history].some((path) => path.startsWith(repoPath(entry.path)));
|
|
2609
|
-
const listed = tracked ? await params.listDir(entry.path.slice(0, -1)) : [];
|
|
2610
|
-
const ignoredInside = ignoredPaths(params.repo, params.env, listed.map(repoPath));
|
|
2611
|
-
const files = listed.filter((path) => !ignoredInside.has(repoPath(path)) && history.has(repoPath(path)));
|
|
2612
|
-
remove.push(...files);
|
|
2613
|
-
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;
|
|
2614
2843
|
}
|
|
2615
|
-
|
|
2844
|
+
issues;
|
|
2845
|
+
};
|
|
2846
|
+
function fail(message, issues) {
|
|
2847
|
+
throw new PushError(message, issues);
|
|
2616
2848
|
}
|
|
2617
|
-
function
|
|
2618
|
-
const
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
for (const file of files) {
|
|
2623
|
-
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`);
|
|
2624
|
-
if (size + cost(file) > budget && batches.at(-1).length > 0) {
|
|
2625
|
-
batches.push([]);
|
|
2626
|
-
size = 0;
|
|
2627
|
-
}
|
|
2628
|
-
batches.at(-1).push(file);
|
|
2629
|
-
size += cost(file);
|
|
2630
|
-
}
|
|
2631
|
-
return batches.map((batch, index) => {
|
|
2632
|
-
const manifest = {
|
|
2633
|
-
files: batch.map(({ path, blob, mode }) => ({ path, blob, mode })),
|
|
2634
|
-
delete: index === 0 ? remove : []
|
|
2635
|
-
};
|
|
2636
|
-
return tarOf([
|
|
2637
|
-
{ name: "manifest.json", data: new TextEncoder().encode(JSON.stringify(manifest)) },
|
|
2638
|
-
...batch.map((file, i) => ({ name: syncApplyEntryName(i), data: file.bytes }))
|
|
2639
|
-
]);
|
|
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
|
|
2640
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;
|
|
2641
2859
|
}
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
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
|
+
);
|
|
2652
2870
|
}
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
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
|
+
);
|
|
2661
2889
|
}
|
|
2662
|
-
return
|
|
2890
|
+
return parsed.data;
|
|
2663
2891
|
}
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
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;
|
|
2670
2907
|
}
|
|
2671
|
-
|
|
2672
|
-
return parseAnswer(syncPlanResponseSchema, await response.text(), "/sync/plan");
|
|
2908
|
+
return bytes;
|
|
2673
2909
|
}
|
|
2674
|
-
function
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
} catch {
|
|
2679
|
-
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)));
|
|
2680
2914
|
}
|
|
2681
|
-
|
|
2682
|
-
if (!parsed.success) throw new SyncError(`the sandbox answered ${path} unexpectedly: ${text.slice(0, 300)}`);
|
|
2683
|
-
return parsed.data;
|
|
2915
|
+
return `sha256:${hash.digest("hex")}`;
|
|
2684
2916
|
}
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
const
|
|
2690
|
-
for (
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
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));
|
|
2697
2935
|
continue;
|
|
2698
2936
|
}
|
|
2699
|
-
|
|
2700
|
-
const
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
buffer += decoder2.decode(chunk, { stream: true });
|
|
2704
|
-
let newline = buffer.indexOf("\n");
|
|
2705
|
-
while (newline >= 0) {
|
|
2706
|
-
const event = parseAnswer(syncBuildEventSchema, buffer.slice(0, newline), "/sync/apply?build=1");
|
|
2707
|
-
buffer = buffer.slice(newline + 1);
|
|
2708
|
-
newline = buffer.indexOf("\n");
|
|
2709
|
-
if (event.type === "applied") {
|
|
2710
|
-
written += event.result.written;
|
|
2711
|
-
deleted += event.result.deleted;
|
|
2712
|
-
} else if (event.type === "output") {
|
|
2713
|
-
if (!json) process6.stderr.write(event.text);
|
|
2714
|
-
} else if (event.type === "step") {
|
|
2715
|
-
steps.push({ name: event.name, ran: event.ran, ms: event.ms });
|
|
2716
|
-
if (!json) process6.stderr.write(` build step ${event.name}: ${event.ran ? `ran in ${(event.ms / 1e3).toFixed(1)} s` : "unchanged"}
|
|
2717
|
-
`);
|
|
2718
|
-
} else if (event.type === "done") {
|
|
2719
|
-
restarted = event.restarted;
|
|
2720
|
-
finished = true;
|
|
2721
|
-
} else if (event.type === "error") {
|
|
2722
|
-
throw new SyncError(`the build failed after the files were synced: ${event.message}`);
|
|
2723
|
-
}
|
|
2724
|
-
}
|
|
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`);
|
|
2725
2941
|
}
|
|
2726
|
-
|
|
2942
|
+
await new Promise((resolve4) => setTimeout(resolve4, 2e3 * attempt));
|
|
2727
2943
|
}
|
|
2728
|
-
return { written, deleted, restarted, steps };
|
|
2729
2944
|
}
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
async function handleSandboxSync(positionals, argv, apiUrl, token) {
|
|
2733
|
-
const [id, localDir] = positionals;
|
|
2734
|
-
const to = getArgValue(argv, "--to");
|
|
2735
|
-
const build = hasFlag(argv, "--build");
|
|
2736
|
-
const json = hasFlag(argv, "--json");
|
|
2737
|
-
const fail4 = (message) => {
|
|
2738
|
-
console.error(`Error: ${message}`);
|
|
2739
|
-
process6.exit(1);
|
|
2740
|
-
};
|
|
2741
|
-
if (!id) return fail4(`sandbox id required.
|
|
2742
|
-
${SYNC_USAGE}`);
|
|
2743
|
-
if (localDir === void 0 !== (to === void 0)) return fail4(`a local directory and --to go together.
|
|
2744
|
-
${SYNC_USAGE}`);
|
|
2745
|
-
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>`).");
|
|
2746
|
-
if (to !== void 0 && (to === "" || to.startsWith("/") || to.split("/").some((part) => part === "" || part === "." || part === ".."))) {
|
|
2747
|
-
return fail4("--to is a path under the sandbox's sync root, like `static` or `frontend/dist` (no leading `/`, no `..`).");
|
|
2748
|
-
}
|
|
2749
|
-
if (localDir !== void 0 && (!existsSync2(localDir) || !lstatSync(localDir).isDirectory())) return fail4(`${localDir} is not a directory`);
|
|
2750
|
-
if (!isInsideRepo(to === void 0 ? process6.cwd() : resolve2(localDir))) {
|
|
2751
|
-
return fail4("run this inside a git repository: the files to sync and the synced tree come from git.");
|
|
2752
|
-
}
|
|
2753
|
-
const started = Date.now();
|
|
2754
|
-
const sessionRes = await apiRequest(
|
|
2755
|
-
apiUrl,
|
|
2756
|
-
token,
|
|
2757
|
-
"POST",
|
|
2758
|
-
`/org/sandboxes-v2/sandboxes/${id}/sync-session`
|
|
2759
|
-
);
|
|
2760
|
-
if (!sessionRes.ok) return fail4(sessionRes.error ?? "could not start a sync");
|
|
2761
|
-
const session = sessionRes.data;
|
|
2945
|
+
async function savedImage(archive) {
|
|
2946
|
+
const file = await open(archive);
|
|
2762
2947
|
try {
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
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;
|
|
2766
2956
|
};
|
|
2767
|
-
|
|
2768
|
-
const
|
|
2769
|
-
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
const
|
|
2774
|
-
if (
|
|
2775
|
-
|
|
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
|
+
}
|
|
2776
2980
|
}
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
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
|
-
|
|
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)}`);
|
|
2813
3094
|
}
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
buildOutput: to !== void 0,
|
|
2839
|
-
written: sending.map((file) => file.path),
|
|
2840
|
-
deleted: remove,
|
|
2841
|
-
kept,
|
|
2842
|
-
build: build ? { steps: applied.steps, restarted: applied.restarted } : null,
|
|
2843
|
-
ms: { total: ms, plan: planMs, apply: applyMs }
|
|
2844
|
-
},
|
|
2845
|
-
null,
|
|
2846
|
-
2
|
|
2847
|
-
)
|
|
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}`)
|
|
2848
3119
|
);
|
|
2849
|
-
return;
|
|
2850
3120
|
}
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
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}`);
|
|
2856
3136
|
}
|
|
2857
|
-
const shown = await apiRequest(apiUrl, token, "GET", `/org/sandboxes-v2/sandboxes/${id}`);
|
|
2858
|
-
for (const [name, url] of Object.entries(shown.data?.portUrls ?? {})) writeRawOutput(` ${name.padEnd(10)} ${url}`);
|
|
2859
|
-
} finally {
|
|
2860
|
-
cleanup();
|
|
2861
3137
|
}
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
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();
|
|
2865
3145
|
}
|
|
2866
3146
|
}
|
|
2867
|
-
function isInsideRepo(dir) {
|
|
2868
|
-
return git(["rev-parse", "--is-inside-work-tree"], { cwd: dir, okCodes: [0, 128] }).trim() === "true";
|
|
2869
|
-
}
|
|
2870
3147
|
|
|
2871
3148
|
// src/sandbox-refresh.ts
|
|
2872
|
-
import
|
|
3149
|
+
import process8 from "process";
|
|
2873
3150
|
var POLL_INTERVAL_MS = 5e3;
|
|
2874
3151
|
var WAIT_TIMEOUT_MS = 50 * 6e4;
|
|
2875
3152
|
function shortVersion2(version) {
|
|
2876
3153
|
return /^[0-9a-f]{40}$/.test(version) ? version.slice(0, 12) : version;
|
|
2877
3154
|
}
|
|
2878
3155
|
function sleep2(ms) {
|
|
2879
|
-
return new Promise((
|
|
3156
|
+
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
2880
3157
|
}
|
|
2881
3158
|
async function handleSandboxRefresh(argv, apiUrl, token) {
|
|
2882
3159
|
const applicationId = await resolveApplicationId(argv, apiUrl, token);
|
|
@@ -2889,7 +3166,7 @@ async function handleSandboxRefresh(argv, apiUrl, token) {
|
|
|
2889
3166
|
});
|
|
2890
3167
|
if (!res.ok) {
|
|
2891
3168
|
console.error(`Error: ${res.error}`);
|
|
2892
|
-
|
|
3169
|
+
process8.exit(1);
|
|
2893
3170
|
}
|
|
2894
3171
|
return res.data;
|
|
2895
3172
|
};
|
|
@@ -2909,7 +3186,7 @@ async function handleSandboxRefresh(argv, apiUrl, token) {
|
|
|
2909
3186
|
);
|
|
2910
3187
|
if (!res.ok) {
|
|
2911
3188
|
console.error(`Error: ${res.error}`);
|
|
2912
|
-
|
|
3189
|
+
process8.exit(1);
|
|
2913
3190
|
}
|
|
2914
3191
|
if (res.data.status === "ready" && !res.data.isHead && !askedAgain) {
|
|
2915
3192
|
askedAgain = true;
|
|
@@ -2939,11 +3216,11 @@ async function handleSandboxRefresh(argv, apiUrl, token) {
|
|
|
2939
3216
|
}
|
|
2940
3217
|
}
|
|
2941
3218
|
}
|
|
2942
|
-
if (latest.head.status === "failed" || wait && latest.head.status === "building")
|
|
3219
|
+
if (latest.head.status === "failed" || wait && latest.head.status === "building") process8.exit(1);
|
|
2943
3220
|
}
|
|
2944
3221
|
|
|
2945
3222
|
// src/sandbox-warm.ts
|
|
2946
|
-
import
|
|
3223
|
+
import process9 from "process";
|
|
2947
3224
|
var STATE_LABELS = {
|
|
2948
3225
|
warming: "Filling",
|
|
2949
3226
|
warm: "On",
|
|
@@ -2970,7 +3247,7 @@ function isSandboxV2WarmCommand(first) {
|
|
|
2970
3247
|
}
|
|
2971
3248
|
function fail2(message) {
|
|
2972
3249
|
console.error(`Error: ${message}`);
|
|
2973
|
-
|
|
3250
|
+
process9.exit(1);
|
|
2974
3251
|
}
|
|
2975
3252
|
function rejectUnknownWarmArgs(command, argv) {
|
|
2976
3253
|
const spec = COMMANDS[command];
|
|
@@ -3044,7 +3321,7 @@ async function handleSandboxWarm(argv, apiUrl, token) {
|
|
|
3044
3321
|
if (!command || !isSandboxV2WarmCommand(command)) {
|
|
3045
3322
|
console.error(command ? `Unknown warm command: ${command}
|
|
3046
3323
|
${USAGE2}` : USAGE2);
|
|
3047
|
-
|
|
3324
|
+
process9.exit(1);
|
|
3048
3325
|
}
|
|
3049
3326
|
rejectUnknownWarmArgs(command, rest);
|
|
3050
3327
|
if (command === "list") {
|
|
@@ -3075,7 +3352,7 @@ ${USAGE2}` : USAGE2);
|
|
|
3075
3352
|
}
|
|
3076
3353
|
|
|
3077
3354
|
// src/sandbox-secrets.ts
|
|
3078
|
-
import
|
|
3355
|
+
import process10 from "process";
|
|
3079
3356
|
function scopeText(environment2) {
|
|
3080
3357
|
return environment2 === null ? "all environments" : `environment ${environment2}`;
|
|
3081
3358
|
}
|
|
@@ -3099,7 +3376,7 @@ function stdinRefusal(isTTY) {
|
|
|
3099
3376
|
}
|
|
3100
3377
|
function fail3(message) {
|
|
3101
3378
|
console.error(`Error: ${message}`);
|
|
3102
|
-
|
|
3379
|
+
process10.exit(1);
|
|
3103
3380
|
}
|
|
3104
3381
|
function decodeSecretValue(bytes) {
|
|
3105
3382
|
let text;
|
|
@@ -3113,7 +3390,7 @@ function decodeSecretValue(bytes) {
|
|
|
3113
3390
|
}
|
|
3114
3391
|
async function readStdin() {
|
|
3115
3392
|
const chunks = [];
|
|
3116
|
-
for await (const chunk of
|
|
3393
|
+
for await (const chunk of process10.stdin) chunks.push(chunk);
|
|
3117
3394
|
return Buffer.concat(chunks);
|
|
3118
3395
|
}
|
|
3119
3396
|
function environmentArg(argv) {
|
|
@@ -3129,7 +3406,7 @@ function printUsage() {
|
|
|
3129
3406
|
console.error("Usage: cofactor sandbox secret set <NAME> --application <name|id> [--environment <e>] [--json] (value on stdin)");
|
|
3130
3407
|
console.error(" cofactor sandbox secret list --application <name|id> [--json]");
|
|
3131
3408
|
console.error(" cofactor sandbox secret rm <NAME> --application <name|id> [--environment <e>] [--json]");
|
|
3132
|
-
|
|
3409
|
+
process10.exit(1);
|
|
3133
3410
|
}
|
|
3134
3411
|
async function handleSandboxSecret(argv, apiUrl, token) {
|
|
3135
3412
|
const [action2, name, ...extra] = v2Positionals("secret", argv);
|
|
@@ -3145,10 +3422,10 @@ async function handleSandboxSecret(argv, apiUrl, token) {
|
|
|
3145
3422
|
const json = hasFlag(argv, "--json");
|
|
3146
3423
|
const application = getArgValue(argv, "--application") ?? getArgValue(argv, "--property") ?? "";
|
|
3147
3424
|
if (action2 === "set") {
|
|
3148
|
-
const refusal = stdinRefusal(
|
|
3425
|
+
const refusal = stdinRefusal(process10.stdin.isTTY);
|
|
3149
3426
|
if (refusal) {
|
|
3150
3427
|
console.error(`Error: ${refusal}`);
|
|
3151
|
-
|
|
3428
|
+
process10.exit(1);
|
|
3152
3429
|
}
|
|
3153
3430
|
}
|
|
3154
3431
|
let value;
|
|
@@ -3167,7 +3444,7 @@ async function handleSandboxSecret(argv, apiUrl, token) {
|
|
|
3167
3444
|
);
|
|
3168
3445
|
if (!res2.ok) {
|
|
3169
3446
|
console.error(`Error: ${res2.error}`);
|
|
3170
|
-
|
|
3447
|
+
process10.exit(1);
|
|
3171
3448
|
}
|
|
3172
3449
|
if (json) {
|
|
3173
3450
|
writeRawOutput(JSON.stringify(res2.data, null, 2));
|
|
@@ -3196,7 +3473,7 @@ async function handleSandboxSecret(argv, apiUrl, token) {
|
|
|
3196
3473
|
);
|
|
3197
3474
|
if (!res.ok) {
|
|
3198
3475
|
console.error(`Error: ${res.error}`);
|
|
3199
|
-
|
|
3476
|
+
process10.exit(1);
|
|
3200
3477
|
}
|
|
3201
3478
|
if (json) {
|
|
3202
3479
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -3214,7 +3491,7 @@ var parseEgressMode = (value) => {
|
|
|
3214
3491
|
console.error(
|
|
3215
3492
|
`Error: invalid --egress-mode '${value}'. Must be one of: ${SANDBOX_EGRESS_MODES.join(", ")}`
|
|
3216
3493
|
);
|
|
3217
|
-
|
|
3494
|
+
process11.exit(1);
|
|
3218
3495
|
};
|
|
3219
3496
|
var parseEgressAllowlist = (argv) => {
|
|
3220
3497
|
const rawEntries = getArgValues(argv, "--egress-allow").flatMap((value) => value.split(",")).map((value) => value.trim()).filter(Boolean);
|
|
@@ -3224,13 +3501,13 @@ var parseEgressAllowlist = (argv) => {
|
|
|
3224
3501
|
console.error(
|
|
3225
3502
|
`Error: invalid --egress-allow '${entry}'. Use domain:example.com or cidr:203.0.113.0/24`
|
|
3226
3503
|
);
|
|
3227
|
-
|
|
3504
|
+
process11.exit(1);
|
|
3228
3505
|
}
|
|
3229
3506
|
const kind = entry.slice(0, separator);
|
|
3230
3507
|
const value = entry.slice(separator + 1);
|
|
3231
3508
|
if (kind !== "domain" && kind !== "cidr") {
|
|
3232
3509
|
console.error(`Error: invalid --egress-allow kind '${kind}'. Must be domain or cidr.`);
|
|
3233
|
-
|
|
3510
|
+
process11.exit(1);
|
|
3234
3511
|
}
|
|
3235
3512
|
return { kind, value };
|
|
3236
3513
|
});
|
|
@@ -3287,7 +3564,8 @@ function printHelp() {
|
|
|
3287
3564
|
' sync <id> <local-dir> --to <path> Mirror local build output into <path> in the sandbox (see "Sync options" below)',
|
|
3288
3565
|
" destroy <id> Tear a claimed sandbox down",
|
|
3289
3566
|
' warm list|set|show|clear [options] Keep an environment warm so claims start in about a second (staff only; see "Warm options" below)',
|
|
3290
|
-
"
|
|
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",
|
|
3291
3569
|
" preview --application <a> --pr <n> A pull request's preview and its logins",
|
|
3292
3570
|
" guide [topic] Show the Sandboxes v2 guide (`guide v2 [topic]` is the same)",
|
|
3293
3571
|
` topics: ${SANDBOX_V2_GUIDE_SECTIONS.map((section) => section.slug).join(", ")}`,
|
|
@@ -3306,6 +3584,9 @@ function printHelp() {
|
|
|
3306
3584
|
" --wait Block until Cofactor has imported (booted) the image",
|
|
3307
3585
|
"",
|
|
3308
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)",
|
|
3309
3590
|
' --environment <name> Environment to claim (default: "default")',
|
|
3310
3591
|
" --at <version> A commit SHA, or a push id or tag (default: the default branch head / push head)",
|
|
3311
3592
|
" --timeout <duration> The claim's time limit: not ready by then, it fails (e.g. 45m, 3h; default 2h,",
|
|
@@ -3495,11 +3776,11 @@ async function handleList(argv, apiUrl, token) {
|
|
|
3495
3776
|
]);
|
|
3496
3777
|
if (!templatesRes.ok) {
|
|
3497
3778
|
console.error(`Error: ${templatesRes.error}`);
|
|
3498
|
-
|
|
3779
|
+
process11.exit(1);
|
|
3499
3780
|
}
|
|
3500
3781
|
if (!instancesRes.ok) {
|
|
3501
3782
|
console.error(`Error loading instances: ${instancesRes.error}`);
|
|
3502
|
-
|
|
3783
|
+
process11.exit(1);
|
|
3503
3784
|
}
|
|
3504
3785
|
if (json) {
|
|
3505
3786
|
writeRawOutput(JSON.stringify({ templates: templatesRes.data, instances: instancesRes.data }, null, 2));
|
|
@@ -3547,7 +3828,7 @@ async function handleTemplate(argv, apiUrl, token) {
|
|
|
3547
3828
|
);
|
|
3548
3829
|
if (!res.ok) {
|
|
3549
3830
|
console.error(`Error: ${res.error}`);
|
|
3550
|
-
|
|
3831
|
+
process11.exit(1);
|
|
3551
3832
|
}
|
|
3552
3833
|
if (json) {
|
|
3553
3834
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -3576,7 +3857,7 @@ async function handleArtifacts(argv, apiUrl, token) {
|
|
|
3576
3857
|
const json = hasFlag(argv, "--json");
|
|
3577
3858
|
if (!templateId) {
|
|
3578
3859
|
console.error("Error: template ID required. Usage: canary sandbox artifacts <template-id>");
|
|
3579
|
-
|
|
3860
|
+
process11.exit(1);
|
|
3580
3861
|
}
|
|
3581
3862
|
const res = await apiRequest(
|
|
3582
3863
|
apiUrl,
|
|
@@ -3586,7 +3867,7 @@ async function handleArtifacts(argv, apiUrl, token) {
|
|
|
3586
3867
|
);
|
|
3587
3868
|
if (!res.ok) {
|
|
3588
3869
|
console.error(`Error: ${res.error}`);
|
|
3589
|
-
|
|
3870
|
+
process11.exit(1);
|
|
3590
3871
|
}
|
|
3591
3872
|
if (json) {
|
|
3592
3873
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -3616,7 +3897,7 @@ async function handleChecklist(argv, apiUrl, token) {
|
|
|
3616
3897
|
const templateId = positional[0];
|
|
3617
3898
|
if (!templateId) {
|
|
3618
3899
|
console.error("Error: template ID required. Usage: canary sandbox checklist <template-id>");
|
|
3619
|
-
|
|
3900
|
+
process11.exit(1);
|
|
3620
3901
|
}
|
|
3621
3902
|
const res = await apiRequest(
|
|
3622
3903
|
apiUrl,
|
|
@@ -3626,7 +3907,7 @@ async function handleChecklist(argv, apiUrl, token) {
|
|
|
3626
3907
|
);
|
|
3627
3908
|
if (!res.ok) {
|
|
3628
3909
|
console.error(`Error: ${res.error}`);
|
|
3629
|
-
|
|
3910
|
+
process11.exit(1);
|
|
3630
3911
|
}
|
|
3631
3912
|
if (json) {
|
|
3632
3913
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -3661,7 +3942,7 @@ async function handleChecklistSkip(argv, apiUrl, token) {
|
|
|
3661
3942
|
const itemId = positional[1];
|
|
3662
3943
|
if (!templateId || !itemId) {
|
|
3663
3944
|
console.error("Error: both template ID and item ID required. Usage: canary sandbox checklist skip <template-id> <item-id>");
|
|
3664
|
-
|
|
3945
|
+
process11.exit(1);
|
|
3665
3946
|
}
|
|
3666
3947
|
const res = await apiRequest(
|
|
3667
3948
|
apiUrl,
|
|
@@ -3671,7 +3952,7 @@ async function handleChecklistSkip(argv, apiUrl, token) {
|
|
|
3671
3952
|
);
|
|
3672
3953
|
if (!res.ok) {
|
|
3673
3954
|
console.error(`Error: ${res.error}`);
|
|
3674
|
-
|
|
3955
|
+
process11.exit(1);
|
|
3675
3956
|
}
|
|
3676
3957
|
console.log(`Skipped checklist item: ${itemId}`);
|
|
3677
3958
|
}
|
|
@@ -3683,7 +3964,7 @@ async function handleChecklistComplete(argv, apiUrl, token) {
|
|
|
3683
3964
|
if (!templateId || !itemId) {
|
|
3684
3965
|
console.error("Error: both template ID and item ID required.");
|
|
3685
3966
|
console.error('Usage: canary sandbox checklist complete <template-id> <item-id> [--status success|failed] [--summary "..."]');
|
|
3686
|
-
|
|
3967
|
+
process11.exit(1);
|
|
3687
3968
|
}
|
|
3688
3969
|
const status = getArgValue(argv, "--status") ?? "success";
|
|
3689
3970
|
const summary = getArgValue(argv, "--summary");
|
|
@@ -3695,11 +3976,11 @@ async function handleChecklistComplete(argv, apiUrl, token) {
|
|
|
3695
3976
|
configSchema = JSON.parse(configSchemaArg);
|
|
3696
3977
|
} catch {
|
|
3697
3978
|
try {
|
|
3698
|
-
configSchema = JSON.parse(
|
|
3979
|
+
configSchema = JSON.parse(readFileSync7(configSchemaArg, "utf-8"));
|
|
3699
3980
|
} catch (e) {
|
|
3700
3981
|
const msg = e instanceof Error ? e.message : String(e);
|
|
3701
3982
|
console.error(`Error reading config schema from "${configSchemaArg}": ${msg}`);
|
|
3702
|
-
|
|
3983
|
+
process11.exit(1);
|
|
3703
3984
|
}
|
|
3704
3985
|
}
|
|
3705
3986
|
}
|
|
@@ -3718,7 +3999,7 @@ async function handleChecklistComplete(argv, apiUrl, token) {
|
|
|
3718
3999
|
);
|
|
3719
4000
|
if (!res.ok) {
|
|
3720
4001
|
console.error(`Error: ${res.error}`);
|
|
3721
|
-
|
|
4002
|
+
process11.exit(1);
|
|
3722
4003
|
}
|
|
3723
4004
|
if (json) {
|
|
3724
4005
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -3732,7 +4013,7 @@ async function handleConfigList(argv, apiUrl, token) {
|
|
|
3732
4013
|
const json = hasFlag(argv, "--json");
|
|
3733
4014
|
if (!templateId) {
|
|
3734
4015
|
console.error("Error: template ID required. Usage: canary sandbox config list <template-id>");
|
|
3735
|
-
|
|
4016
|
+
process11.exit(1);
|
|
3736
4017
|
}
|
|
3737
4018
|
const res = await apiRequest(
|
|
3738
4019
|
apiUrl,
|
|
@@ -3742,7 +4023,7 @@ async function handleConfigList(argv, apiUrl, token) {
|
|
|
3742
4023
|
);
|
|
3743
4024
|
if (!res.ok) {
|
|
3744
4025
|
console.error(`Error: ${res.error}`);
|
|
3745
|
-
|
|
4026
|
+
process11.exit(1);
|
|
3746
4027
|
}
|
|
3747
4028
|
const t = res.data;
|
|
3748
4029
|
const schema = t.configSchema ?? [];
|
|
@@ -3792,16 +4073,16 @@ async function handleConfigSet(argv, apiUrl, token) {
|
|
|
3792
4073
|
console.error("Error: template ID and key required.");
|
|
3793
4074
|
console.error("Usage: canary sandbox config set <template-id> <key> <value>");
|
|
3794
4075
|
console.error(' For sensitive values: echo "sk-ant-..." | canary sandbox config set <template-id> <key> --value-stdin');
|
|
3795
|
-
|
|
4076
|
+
process11.exit(1);
|
|
3796
4077
|
}
|
|
3797
4078
|
if (fromStdin) {
|
|
3798
4079
|
const chunks = [];
|
|
3799
|
-
for await (const chunk of
|
|
4080
|
+
for await (const chunk of process11.stdin) chunks.push(chunk);
|
|
3800
4081
|
value = Buffer.concat(chunks).toString("utf-8").trim();
|
|
3801
4082
|
}
|
|
3802
4083
|
if (value === void 0) {
|
|
3803
4084
|
console.error("Error: value required. Pass as argument or use --value-stdin");
|
|
3804
|
-
|
|
4085
|
+
process11.exit(1);
|
|
3805
4086
|
}
|
|
3806
4087
|
const res = await apiRequest(
|
|
3807
4088
|
apiUrl,
|
|
@@ -3815,7 +4096,7 @@ async function handleConfigSet(argv, apiUrl, token) {
|
|
|
3815
4096
|
);
|
|
3816
4097
|
if (!res.ok) {
|
|
3817
4098
|
console.error(`Error: ${res.error}`);
|
|
3818
|
-
|
|
4099
|
+
process11.exit(1);
|
|
3819
4100
|
}
|
|
3820
4101
|
console.log(`Config set: ${key}`);
|
|
3821
4102
|
}
|
|
@@ -3823,7 +4104,7 @@ async function handleConfigUnset(argv, apiUrl, token) {
|
|
|
3823
4104
|
const [templateId, key] = argv.filter((arg) => !arg.startsWith("-"));
|
|
3824
4105
|
if (!templateId || !key) {
|
|
3825
4106
|
console.error("Usage: canary sandbox config unset <template-id> <key>");
|
|
3826
|
-
|
|
4107
|
+
process11.exit(1);
|
|
3827
4108
|
}
|
|
3828
4109
|
const res = await apiRequest(
|
|
3829
4110
|
apiUrl,
|
|
@@ -3835,7 +4116,7 @@ async function handleConfigUnset(argv, apiUrl, token) {
|
|
|
3835
4116
|
);
|
|
3836
4117
|
if (!res.ok) {
|
|
3837
4118
|
console.error(`Error: ${res.error}`);
|
|
3838
|
-
|
|
4119
|
+
process11.exit(1);
|
|
3839
4120
|
}
|
|
3840
4121
|
console.log(`Config unset: ${key}`);
|
|
3841
4122
|
}
|
|
@@ -3844,12 +4125,12 @@ async function handleSetSize(argv, apiUrl, token) {
|
|
|
3844
4125
|
if (!templateId || !sizeArg) {
|
|
3845
4126
|
console.error("Error: template ID and size required.");
|
|
3846
4127
|
console.error(`Usage: canary sandbox set-size <template-id> <${INSTANCE_SIZES.join("|")}>`);
|
|
3847
|
-
|
|
4128
|
+
process11.exit(1);
|
|
3848
4129
|
}
|
|
3849
4130
|
const size = parseInstanceSize(sizeArg);
|
|
3850
4131
|
if (!size) {
|
|
3851
4132
|
console.error(`Error: invalid size '${sizeArg}'. Must be one of: ${INSTANCE_SIZES.join(", ")}`);
|
|
3852
|
-
|
|
4133
|
+
process11.exit(1);
|
|
3853
4134
|
}
|
|
3854
4135
|
const res = await apiRequest(
|
|
3855
4136
|
apiUrl,
|
|
@@ -3860,7 +4141,7 @@ async function handleSetSize(argv, apiUrl, token) {
|
|
|
3860
4141
|
);
|
|
3861
4142
|
if (!res.ok) {
|
|
3862
4143
|
console.error(`Error: ${res.error}`);
|
|
3863
|
-
|
|
4144
|
+
process11.exit(1);
|
|
3864
4145
|
}
|
|
3865
4146
|
console.log(`Instance size set to ${size}`);
|
|
3866
4147
|
}
|
|
@@ -3869,12 +4150,12 @@ async function handlePoolSetSize(argv, apiUrl, token) {
|
|
|
3869
4150
|
if (!templateId || countArg === void 0) {
|
|
3870
4151
|
console.error("Error: template ID and count required.");
|
|
3871
4152
|
console.error("Usage: canary sandbox pool set-size <template-id> <count>");
|
|
3872
|
-
|
|
4153
|
+
process11.exit(1);
|
|
3873
4154
|
}
|
|
3874
4155
|
const poolMinReady = /^\d+$/.test(countArg) ? Number(countArg) : Number.NaN;
|
|
3875
4156
|
if (!Number.isInteger(poolMinReady)) {
|
|
3876
4157
|
console.error(`Error: invalid count '${countArg}'. Must be a non-negative integer.`);
|
|
3877
|
-
|
|
4158
|
+
process11.exit(1);
|
|
3878
4159
|
}
|
|
3879
4160
|
const res = await apiRequest(
|
|
3880
4161
|
apiUrl,
|
|
@@ -3885,7 +4166,7 @@ async function handlePoolSetSize(argv, apiUrl, token) {
|
|
|
3885
4166
|
);
|
|
3886
4167
|
if (!res.ok) {
|
|
3887
4168
|
console.error(`Error: ${res.error}`);
|
|
3888
|
-
|
|
4169
|
+
process11.exit(1);
|
|
3889
4170
|
}
|
|
3890
4171
|
console.log(`Warm stopped-pool size set to ${poolMinReady}`);
|
|
3891
4172
|
}
|
|
@@ -3894,7 +4175,7 @@ async function handleCreate(argv, apiUrl, token) {
|
|
|
3894
4175
|
const json = hasFlag(argv, "--json");
|
|
3895
4176
|
const propertyHint = positional[0] ?? getArgValue(argv, "--property");
|
|
3896
4177
|
const property = await selectProperty(apiUrl, token, propertyHint ?? void 0);
|
|
3897
|
-
if (!property)
|
|
4178
|
+
if (!property) process11.exit(1);
|
|
3898
4179
|
const reposRes = await apiRequest(
|
|
3899
4180
|
apiUrl,
|
|
3900
4181
|
token,
|
|
@@ -3903,7 +4184,7 @@ async function handleCreate(argv, apiUrl, token) {
|
|
|
3903
4184
|
);
|
|
3904
4185
|
if (!reposRes.ok || !reposRes.items?.length) {
|
|
3905
4186
|
console.error(`No GitHub repos linked to property "${property.name}". Link one first in the web app.`);
|
|
3906
|
-
|
|
4187
|
+
process11.exit(1);
|
|
3907
4188
|
}
|
|
3908
4189
|
const linkedRepo = reposRes.items[0];
|
|
3909
4190
|
const branch = getArgValue(argv, "--branch") ?? linkedRepo.defaultBranch ?? "main";
|
|
@@ -3913,7 +4194,7 @@ async function handleCreate(argv, apiUrl, token) {
|
|
|
3913
4194
|
const instanceSize = sizeArg ? parseInstanceSize(sizeArg) : void 0;
|
|
3914
4195
|
if (sizeArg && !instanceSize) {
|
|
3915
4196
|
console.error(`Error: invalid --size '${sizeArg}'. Must be one of: ${INSTANCE_SIZES.join(", ")}`);
|
|
3916
|
-
|
|
4197
|
+
process11.exit(1);
|
|
3917
4198
|
}
|
|
3918
4199
|
const body = {
|
|
3919
4200
|
name,
|
|
@@ -3933,7 +4214,7 @@ async function handleCreate(argv, apiUrl, token) {
|
|
|
3933
4214
|
);
|
|
3934
4215
|
if (!res.ok) {
|
|
3935
4216
|
console.error(`Error: ${res.error}`);
|
|
3936
|
-
|
|
4217
|
+
process11.exit(1);
|
|
3937
4218
|
}
|
|
3938
4219
|
if (json) {
|
|
3939
4220
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -3953,7 +4234,7 @@ async function handleProvision(argv, apiUrl, token) {
|
|
|
3953
4234
|
const json = hasFlag(argv, "--json");
|
|
3954
4235
|
if (!templateId) {
|
|
3955
4236
|
console.error("Error: template ID required. Usage: canary sandbox provision <template-id> [--ttl 8]");
|
|
3956
|
-
|
|
4237
|
+
process11.exit(1);
|
|
3957
4238
|
}
|
|
3958
4239
|
const name = getArgValue(argv, "--name") ?? `instance-${Date.now()}`;
|
|
3959
4240
|
const ttl = parseInt(getArgValue(argv, "--ttl") ?? "8", 10);
|
|
@@ -3980,7 +4261,7 @@ async function handleProvision(argv, apiUrl, token) {
|
|
|
3980
4261
|
);
|
|
3981
4262
|
if (!res.ok) {
|
|
3982
4263
|
console.error(`Error: ${res.error}`);
|
|
3983
|
-
|
|
4264
|
+
process11.exit(1);
|
|
3984
4265
|
}
|
|
3985
4266
|
if (json) {
|
|
3986
4267
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -4005,7 +4286,7 @@ async function handleWarm(argv, apiUrl, token) {
|
|
|
4005
4286
|
"Error: template ID required. Usage: canary sandbox warm <template-id> [--wait] [--timeout <sec>]"
|
|
4006
4287
|
);
|
|
4007
4288
|
console.error("Find the template id with: canary sandbox list");
|
|
4008
|
-
|
|
4289
|
+
process11.exit(1);
|
|
4009
4290
|
}
|
|
4010
4291
|
const ensure = () => apiRequest(
|
|
4011
4292
|
apiUrl,
|
|
@@ -4020,7 +4301,7 @@ async function handleWarm(argv, apiUrl, token) {
|
|
|
4020
4301
|
} else {
|
|
4021
4302
|
console.error(`Error: ${first.error ?? "Failed to warm sandbox"}`);
|
|
4022
4303
|
}
|
|
4023
|
-
|
|
4304
|
+
process11.exit(1);
|
|
4024
4305
|
}
|
|
4025
4306
|
const terminalOk = /* @__PURE__ */ new Set(["running"]);
|
|
4026
4307
|
const terminalFail = /* @__PURE__ */ new Set(["failed", "terminated", "stopping"]);
|
|
@@ -4033,7 +4314,7 @@ async function handleWarm(argv, apiUrl, token) {
|
|
|
4033
4314
|
const poll = await ensure();
|
|
4034
4315
|
if (!poll.ok || !poll.data) {
|
|
4035
4316
|
console.error(`Error polling sandbox: ${poll.error ?? "Unknown error"}`);
|
|
4036
|
-
|
|
4317
|
+
process11.exit(1);
|
|
4037
4318
|
}
|
|
4038
4319
|
current = poll.data;
|
|
4039
4320
|
if (terminalOk.has(current.status) || terminalFail.has(current.status)) break;
|
|
@@ -4057,8 +4338,8 @@ async function handleWarm(argv, apiUrl, token) {
|
|
|
4057
4338
|
console.log("\nWarming in the background. Poll with: canary sandbox warm " + templateId + " --wait");
|
|
4058
4339
|
}
|
|
4059
4340
|
}
|
|
4060
|
-
if (terminalFail.has(current.status))
|
|
4061
|
-
if (wait && current.status !== "running")
|
|
4341
|
+
if (terminalFail.has(current.status)) process11.exit(1);
|
|
4342
|
+
if (wait && current.status !== "running") process11.exit(1);
|
|
4062
4343
|
}
|
|
4063
4344
|
function parseDockerPs(stdout) {
|
|
4064
4345
|
const lines = stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
@@ -4103,7 +4384,7 @@ async function handleVerify(argv, apiUrl, token) {
|
|
|
4103
4384
|
const autoTerminateOnFailure = hasFlag(argv, "--auto-terminate-on-failure");
|
|
4104
4385
|
if (!templateId) {
|
|
4105
4386
|
console.error("Error: template ID required. Usage: canary sandbox verify <template-id>");
|
|
4106
|
-
|
|
4387
|
+
process11.exit(1);
|
|
4107
4388
|
}
|
|
4108
4389
|
console.log(`Verifying template ${templateId}...`);
|
|
4109
4390
|
const verifyRes = await apiRequest(
|
|
@@ -4115,7 +4396,7 @@ async function handleVerify(argv, apiUrl, token) {
|
|
|
4115
4396
|
);
|
|
4116
4397
|
if (!verifyRes.ok) {
|
|
4117
4398
|
console.error(`Error: ${verifyRes.error}`);
|
|
4118
|
-
|
|
4399
|
+
process11.exit(1);
|
|
4119
4400
|
}
|
|
4120
4401
|
const instanceId = verifyRes.data.instanceId;
|
|
4121
4402
|
console.log(` Verification instance: ${instanceId}`);
|
|
@@ -4130,7 +4411,7 @@ async function handleVerify(argv, apiUrl, token) {
|
|
|
4130
4411
|
let routes = [];
|
|
4131
4412
|
let routesFetched = false;
|
|
4132
4413
|
while (Date.now() - startTime < TIMEOUT_MS) {
|
|
4133
|
-
await new Promise((
|
|
4414
|
+
await new Promise((resolve4) => setTimeout(resolve4, POLL_INTERVAL_MS2));
|
|
4134
4415
|
const diag = await apiRequest(
|
|
4135
4416
|
apiUrl,
|
|
4136
4417
|
token,
|
|
@@ -4151,23 +4432,23 @@ async function handleVerify(argv, apiUrl, token) {
|
|
|
4151
4432
|
const containers = parseDockerPs(dockerOutput);
|
|
4152
4433
|
const broken = containers.filter(isBrokenContainer);
|
|
4153
4434
|
const ttl = remaining > 60 ? `${elapsed}s elapsed` : `${remaining}s remaining`;
|
|
4154
|
-
|
|
4435
|
+
process11.stdout.write(
|
|
4155
4436
|
`\r [${ttl}] status: ${status}, app: ${appUp ? "up" : "waiting"}, containers: ${containers.length}, unhealthy: ${broken.length} `
|
|
4156
4437
|
);
|
|
4157
4438
|
if (status === "running" && appUp) {
|
|
4158
|
-
|
|
4439
|
+
process11.stdout.write("\n");
|
|
4159
4440
|
finalStatus = "running";
|
|
4160
4441
|
break;
|
|
4161
4442
|
}
|
|
4162
4443
|
if (status === "failed" || status === "terminated") {
|
|
4163
|
-
|
|
4444
|
+
process11.stdout.write("\n");
|
|
4164
4445
|
finalStatus = status;
|
|
4165
4446
|
break;
|
|
4166
4447
|
}
|
|
4167
4448
|
const shouldDump = !appUp && broken.length > 0 && now - lastDetailDumpAt >= DETAIL_DUMP_INTERVAL_MS;
|
|
4168
4449
|
if (shouldDump) {
|
|
4169
4450
|
lastDetailDumpAt = now;
|
|
4170
|
-
|
|
4451
|
+
process11.stdout.write("\n");
|
|
4171
4452
|
console.log("");
|
|
4172
4453
|
console.log(` App unreachable after ${elapsed}s. Container state:`);
|
|
4173
4454
|
for (const c of containers) {
|
|
@@ -4232,7 +4513,7 @@ async function handleVerify(argv, apiUrl, token) {
|
|
|
4232
4513
|
console.error(` Terminate manually with:`);
|
|
4233
4514
|
console.error(` canary sandbox terminate ${instanceId}`);
|
|
4234
4515
|
}
|
|
4235
|
-
|
|
4516
|
+
process11.exit(1);
|
|
4236
4517
|
}
|
|
4237
4518
|
const evidenceSummary = `Verified via canary sandbox verify \u2014 fresh instance ${instanceId} came up healthy.`;
|
|
4238
4519
|
for (const itemId of ["server-running", "server-accessible"]) {
|
|
@@ -4258,7 +4539,7 @@ async function handleVerify(argv, apiUrl, token) {
|
|
|
4258
4539
|
if (!completeRes.ok) {
|
|
4259
4540
|
console.error(`Failed to record verification: ${completeRes.error}`);
|
|
4260
4541
|
console.error(`Instance ${instanceId} left running for inspection.`);
|
|
4261
|
-
|
|
4542
|
+
process11.exit(1);
|
|
4262
4543
|
}
|
|
4263
4544
|
if (!keepInstance) {
|
|
4264
4545
|
await apiRequest(apiUrl, token, "POST", `/org/environments/instances/${instanceId}/terminate`);
|
|
@@ -4303,7 +4584,7 @@ async function handleVerify(argv, apiUrl, token) {
|
|
|
4303
4584
|
console.log('Complete: canary sandbox checklist complete <template-id> <item-id> --status success --summary "..."');
|
|
4304
4585
|
console.log("Skip: canary sandbox checklist skip <template-id> <item-id>");
|
|
4305
4586
|
}
|
|
4306
|
-
|
|
4587
|
+
process11.exit(1);
|
|
4307
4588
|
}
|
|
4308
4589
|
async function handleArtifactGet(argv, apiUrl, token) {
|
|
4309
4590
|
const positional = argv.filter((a) => !a.startsWith("-"));
|
|
@@ -4312,7 +4593,7 @@ async function handleArtifactGet(argv, apiUrl, token) {
|
|
|
4312
4593
|
const outputFile = getArgValue(argv, "--output");
|
|
4313
4594
|
if (!templateId || !type) {
|
|
4314
4595
|
console.error("Error: template ID and artifact type required. Usage: canary sandbox artifact get <template-id> <type>");
|
|
4315
|
-
|
|
4596
|
+
process11.exit(1);
|
|
4316
4597
|
}
|
|
4317
4598
|
const res = await apiRequest(
|
|
4318
4599
|
apiUrl,
|
|
@@ -4322,14 +4603,14 @@ async function handleArtifactGet(argv, apiUrl, token) {
|
|
|
4322
4603
|
);
|
|
4323
4604
|
if (!res.ok) {
|
|
4324
4605
|
console.error(`Error: ${res.error}`);
|
|
4325
|
-
|
|
4606
|
+
process11.exit(1);
|
|
4326
4607
|
}
|
|
4327
4608
|
if (outputFile) {
|
|
4328
|
-
const { writeFileSync } = await import("fs");
|
|
4329
|
-
|
|
4609
|
+
const { writeFileSync: writeFileSync2 } = await import("fs");
|
|
4610
|
+
writeFileSync2(outputFile, res.data.content ?? "", "utf-8");
|
|
4330
4611
|
console.log(`Written to ${outputFile}`);
|
|
4331
4612
|
} else {
|
|
4332
|
-
|
|
4613
|
+
process11.stdout.write(res.data.content ?? "");
|
|
4333
4614
|
}
|
|
4334
4615
|
}
|
|
4335
4616
|
async function handleArtifactCreate(argv, apiUrl, token) {
|
|
@@ -4340,17 +4621,17 @@ async function handleArtifactCreate(argv, apiUrl, token) {
|
|
|
4340
4621
|
const json = hasFlag(argv, "--json");
|
|
4341
4622
|
if (!templateId || !type || !filePath) {
|
|
4342
4623
|
console.error("Error: all args required. Usage: canary sandbox artifact create <template-id> <type> <file>");
|
|
4343
|
-
|
|
4624
|
+
process11.exit(1);
|
|
4344
4625
|
}
|
|
4345
4626
|
const WELL_KNOWN_TYPES = ["docker-compose", "dockerfile", "startup-script", "setup-script", "seed-script", "environment-config", "file"];
|
|
4346
4627
|
if (!WELL_KNOWN_TYPES.includes(type)) {
|
|
4347
4628
|
console.error(`Error: invalid artifact type "${type}". Valid types: ${WELL_KNOWN_TYPES.join(", ")}`);
|
|
4348
|
-
|
|
4629
|
+
process11.exit(1);
|
|
4349
4630
|
}
|
|
4350
4631
|
const destinationPath = getArgValue(argv, "--path");
|
|
4351
4632
|
if (type === "file" && !destinationPath) {
|
|
4352
4633
|
console.error("Error: file artifacts require --path <destination>. Example: --path /workspace/.canary/vite.sandbox.config.ts");
|
|
4353
|
-
|
|
4634
|
+
process11.exit(1);
|
|
4354
4635
|
}
|
|
4355
4636
|
if (destinationPath && !destinationPath.startsWith("/workspace/.canary/")) {
|
|
4356
4637
|
const suggestedName = destinationPath.split("/").filter(Boolean).pop() ?? "yourfile";
|
|
@@ -4358,14 +4639,14 @@ async function handleArtifactCreate(argv, apiUrl, token) {
|
|
|
4358
4639
|
`Error: --path must be under /workspace/.canary/ (the repo tree is user-owned \u2014 sandbox files live in .canary/).
|
|
4359
4640
|
try: --path /workspace/.canary/${suggestedName}`
|
|
4360
4641
|
);
|
|
4361
|
-
|
|
4642
|
+
process11.exit(1);
|
|
4362
4643
|
}
|
|
4363
4644
|
let content;
|
|
4364
4645
|
try {
|
|
4365
|
-
content =
|
|
4646
|
+
content = readFileSync7(filePath, "utf-8");
|
|
4366
4647
|
} catch (err) {
|
|
4367
4648
|
console.error(`Error reading file: ${err}`);
|
|
4368
|
-
|
|
4649
|
+
process11.exit(1);
|
|
4369
4650
|
}
|
|
4370
4651
|
const filename = filePath.split("/").pop() ?? filePath;
|
|
4371
4652
|
const res = await apiRequest(
|
|
@@ -4383,7 +4664,7 @@ async function handleArtifactCreate(argv, apiUrl, token) {
|
|
|
4383
4664
|
);
|
|
4384
4665
|
if (!res.ok) {
|
|
4385
4666
|
console.error(`Error: ${res.error}`);
|
|
4386
|
-
|
|
4667
|
+
process11.exit(1);
|
|
4387
4668
|
}
|
|
4388
4669
|
if (json) {
|
|
4389
4670
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -4403,7 +4684,7 @@ async function handleBuild(argv, apiUrl, token) {
|
|
|
4403
4684
|
const instanceId = positional[0];
|
|
4404
4685
|
if (!instanceId) {
|
|
4405
4686
|
console.error("Error: instance ID required. Usage: canary sandbox build <instance-id>");
|
|
4406
|
-
|
|
4687
|
+
process11.exit(1);
|
|
4407
4688
|
}
|
|
4408
4689
|
console.log("Building sandbox (this takes a few minutes)...");
|
|
4409
4690
|
const KICKOFF_TIMEOUT_MS = 8e3;
|
|
@@ -4425,7 +4706,7 @@ async function handleBuild(argv, apiUrl, token) {
|
|
|
4425
4706
|
buildResult = data.data;
|
|
4426
4707
|
} else if (data && !data.ok) {
|
|
4427
4708
|
console.error(`Error: ${data.error ?? "build failed"}`);
|
|
4428
|
-
|
|
4709
|
+
process11.exit(1);
|
|
4429
4710
|
}
|
|
4430
4711
|
} else {
|
|
4431
4712
|
const { failure } = result;
|
|
@@ -4433,7 +4714,7 @@ async function handleBuild(argv, apiUrl, token) {
|
|
|
4433
4714
|
if (!isExpectedAsync) {
|
|
4434
4715
|
if (isTerminalFailure(failure)) exitOnFailure(failure);
|
|
4435
4716
|
console.error(`Error: ${failure.message}`);
|
|
4436
|
-
|
|
4717
|
+
process11.exit(1);
|
|
4437
4718
|
}
|
|
4438
4719
|
}
|
|
4439
4720
|
if (buildCompleted && buildResult) {
|
|
@@ -4442,18 +4723,18 @@ async function handleBuild(argv, apiUrl, token) {
|
|
|
4442
4723
|
return;
|
|
4443
4724
|
}
|
|
4444
4725
|
if (buildResult.stdout) {
|
|
4445
|
-
|
|
4446
|
-
if (!buildResult.stdout.endsWith("\n"))
|
|
4726
|
+
process11.stdout.write(buildResult.stdout);
|
|
4727
|
+
if (!buildResult.stdout.endsWith("\n")) process11.stdout.write("\n");
|
|
4447
4728
|
}
|
|
4448
4729
|
if (buildResult.stderr) {
|
|
4449
|
-
|
|
4450
|
-
if (!buildResult.stderr.endsWith("\n"))
|
|
4730
|
+
process11.stderr.write(`STDERR: ${buildResult.stderr}`);
|
|
4731
|
+
if (!buildResult.stderr.endsWith("\n")) process11.stderr.write("\n");
|
|
4451
4732
|
}
|
|
4452
4733
|
if (buildResult.exitCode === 0) {
|
|
4453
4734
|
console.log("Build complete.");
|
|
4454
4735
|
} else {
|
|
4455
4736
|
console.error(`Build failed with exit code ${buildResult.exitCode}`);
|
|
4456
|
-
|
|
4737
|
+
process11.exit(1);
|
|
4457
4738
|
}
|
|
4458
4739
|
return;
|
|
4459
4740
|
}
|
|
@@ -4467,7 +4748,7 @@ async function handleBuild(argv, apiUrl, token) {
|
|
|
4467
4748
|
const POLL_INTERVAL_MS2 = 1e4;
|
|
4468
4749
|
const startTime = Date.now();
|
|
4469
4750
|
while (Date.now() - startTime < BUILD_POLL_TIMEOUT_MS) {
|
|
4470
|
-
await new Promise((
|
|
4751
|
+
await new Promise((resolve4) => setTimeout(resolve4, POLL_INTERVAL_MS2));
|
|
4471
4752
|
const diag = await apiRequest(
|
|
4472
4753
|
apiUrl,
|
|
4473
4754
|
token,
|
|
@@ -4483,18 +4764,18 @@ async function handleBuild(argv, apiUrl, token) {
|
|
|
4483
4764
|
const appUp = appOutput.length > 0 && !appOutput.includes("UNREACHABLE");
|
|
4484
4765
|
const containerCount = (dockerOutput.match(/\n/g) || []).length + (dockerOutput ? 1 : 0);
|
|
4485
4766
|
const healthyCount = (dockerOutput.match(/\(healthy\)/g) || []).length;
|
|
4486
|
-
|
|
4767
|
+
process11.stdout.write(`\r [${elapsed}s] containers: ${containerCount}, healthy: ${healthyCount}, app: ${appUp ? "up" : "waiting"} `);
|
|
4487
4768
|
if (appUp) {
|
|
4488
|
-
|
|
4769
|
+
process11.stdout.write("\n");
|
|
4489
4770
|
console.log("Build complete.");
|
|
4490
4771
|
return;
|
|
4491
4772
|
}
|
|
4492
4773
|
}
|
|
4493
|
-
|
|
4774
|
+
process11.stdout.write("\n");
|
|
4494
4775
|
console.error("Build timed out waiting for the app to respond. Check diagnostics and logs:");
|
|
4495
4776
|
console.error(` canary sandbox diagnostics ${instanceId}`);
|
|
4496
4777
|
console.error(` canary sandbox logs ${instanceId} --service <name>`);
|
|
4497
|
-
|
|
4778
|
+
process11.exit(1);
|
|
4498
4779
|
}
|
|
4499
4780
|
async function handleRunCommand(argv, apiUrl, token) {
|
|
4500
4781
|
const json = hasFlag(argv, "--json");
|
|
@@ -4511,7 +4792,7 @@ async function handleRunCommand(argv, apiUrl, token) {
|
|
|
4511
4792
|
const command = firstNonFlag >= 0 ? filtered.slice(firstNonFlag + 1).join(" ") : "";
|
|
4512
4793
|
if (!instanceId || !command) {
|
|
4513
4794
|
console.error("Error: instance ID and command required. Usage: canary sandbox run-command <instance-id> [--service <name>] [--timeout <ms>] <command...>");
|
|
4514
|
-
|
|
4795
|
+
process11.exit(1);
|
|
4515
4796
|
}
|
|
4516
4797
|
const timeoutMs = timeoutFlag ? Math.max(parseInt(timeoutFlag, 10), 1e3) : 6e4;
|
|
4517
4798
|
const res = await apiRequest(
|
|
@@ -4524,19 +4805,19 @@ async function handleRunCommand(argv, apiUrl, token) {
|
|
|
4524
4805
|
);
|
|
4525
4806
|
if (!res.ok) {
|
|
4526
4807
|
console.error(`Error: ${res.error}`);
|
|
4527
|
-
|
|
4808
|
+
process11.exit(1);
|
|
4528
4809
|
}
|
|
4529
4810
|
if (json) {
|
|
4530
4811
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
4531
4812
|
return;
|
|
4532
4813
|
}
|
|
4533
4814
|
if (res.data.stdout) {
|
|
4534
|
-
|
|
4535
|
-
if (!res.data.stdout.endsWith("\n"))
|
|
4815
|
+
process11.stdout.write(res.data.stdout);
|
|
4816
|
+
if (!res.data.stdout.endsWith("\n")) process11.stdout.write("\n");
|
|
4536
4817
|
}
|
|
4537
4818
|
if (res.data.stderr) {
|
|
4538
|
-
|
|
4539
|
-
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");
|
|
4540
4821
|
}
|
|
4541
4822
|
if (res.data.exitCode !== 0) {
|
|
4542
4823
|
console.log(`Exit code: ${res.data.exitCode}`);
|
|
@@ -4548,7 +4829,7 @@ async function handleDiagnostics(argv, apiUrl, token) {
|
|
|
4548
4829
|
const json = hasFlag(argv, "--json");
|
|
4549
4830
|
if (!instanceId) {
|
|
4550
4831
|
console.error("Error: instance ID required. Usage: canary sandbox diagnostics <instance-id>");
|
|
4551
|
-
|
|
4832
|
+
process11.exit(1);
|
|
4552
4833
|
}
|
|
4553
4834
|
const res = await apiRequest(
|
|
4554
4835
|
apiUrl,
|
|
@@ -4560,7 +4841,7 @@ async function handleDiagnostics(argv, apiUrl, token) {
|
|
|
4560
4841
|
);
|
|
4561
4842
|
if (!res.ok) {
|
|
4562
4843
|
console.error(`Error: ${res.error}`);
|
|
4563
|
-
|
|
4844
|
+
process11.exit(1);
|
|
4564
4845
|
}
|
|
4565
4846
|
const routes = await fetchInstanceRoutes(apiUrl, token, instanceId);
|
|
4566
4847
|
const data = { ...res.data, routes };
|
|
@@ -4615,7 +4896,7 @@ async function handleTerminate(argv, apiUrl, token) {
|
|
|
4615
4896
|
const instanceId = positional[0];
|
|
4616
4897
|
if (!instanceId) {
|
|
4617
4898
|
console.error("Error: instance ID required. Usage: canary sandbox terminate <instance-id>");
|
|
4618
|
-
|
|
4899
|
+
process11.exit(1);
|
|
4619
4900
|
}
|
|
4620
4901
|
const res = await apiRequest(
|
|
4621
4902
|
apiUrl,
|
|
@@ -4625,7 +4906,7 @@ async function handleTerminate(argv, apiUrl, token) {
|
|
|
4625
4906
|
);
|
|
4626
4907
|
if (!res.ok) {
|
|
4627
4908
|
console.error(`Error: ${res.error}`);
|
|
4628
|
-
|
|
4909
|
+
process11.exit(1);
|
|
4629
4910
|
}
|
|
4630
4911
|
console.log(`Instance ${instanceId} termination initiated.`);
|
|
4631
4912
|
}
|
|
@@ -4634,7 +4915,7 @@ async function handleDelete(argv, apiUrl, token) {
|
|
|
4634
4915
|
const templateId = positional[0];
|
|
4635
4916
|
if (!templateId) {
|
|
4636
4917
|
console.error("Error: template ID required. Usage: canary sandbox delete <template-id>");
|
|
4637
|
-
|
|
4918
|
+
process11.exit(1);
|
|
4638
4919
|
}
|
|
4639
4920
|
const res = await apiRequest(
|
|
4640
4921
|
apiUrl,
|
|
@@ -4644,7 +4925,7 @@ async function handleDelete(argv, apiUrl, token) {
|
|
|
4644
4925
|
);
|
|
4645
4926
|
if (!res.ok) {
|
|
4646
4927
|
console.error(`Error: ${res.error}`);
|
|
4647
|
-
|
|
4928
|
+
process11.exit(1);
|
|
4648
4929
|
}
|
|
4649
4930
|
console.log(`Template ${templateId} deleted.`);
|
|
4650
4931
|
}
|
|
@@ -4654,7 +4935,7 @@ async function handleActionList(argv, apiUrl, token) {
|
|
|
4654
4935
|
const json = hasFlag(argv, "--json");
|
|
4655
4936
|
if (!templateId) {
|
|
4656
4937
|
console.error("Error: template ID required. Usage: canary sandbox action list <template-id>");
|
|
4657
|
-
|
|
4938
|
+
process11.exit(1);
|
|
4658
4939
|
}
|
|
4659
4940
|
const res = await apiRequest(
|
|
4660
4941
|
apiUrl,
|
|
@@ -4664,7 +4945,7 @@ async function handleActionList(argv, apiUrl, token) {
|
|
|
4664
4945
|
);
|
|
4665
4946
|
if (!res.ok) {
|
|
4666
4947
|
console.error(`Error: ${res.error}`);
|
|
4667
|
-
|
|
4948
|
+
process11.exit(1);
|
|
4668
4949
|
}
|
|
4669
4950
|
if (json) {
|
|
4670
4951
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -4688,7 +4969,7 @@ async function handleActionGet(argv, apiUrl, token) {
|
|
|
4688
4969
|
const json = hasFlag(argv, "--json");
|
|
4689
4970
|
if (!templateId || !slug) {
|
|
4690
4971
|
console.error("Error: template ID and slug required. Usage: canary sandbox action get <template-id> <slug>");
|
|
4691
|
-
|
|
4972
|
+
process11.exit(1);
|
|
4692
4973
|
}
|
|
4693
4974
|
const res = await apiRequest(
|
|
4694
4975
|
apiUrl,
|
|
@@ -4698,7 +4979,7 @@ async function handleActionGet(argv, apiUrl, token) {
|
|
|
4698
4979
|
);
|
|
4699
4980
|
if (!res.ok) {
|
|
4700
4981
|
console.error(`Error: ${res.error}`);
|
|
4701
|
-
|
|
4982
|
+
process11.exit(1);
|
|
4702
4983
|
}
|
|
4703
4984
|
if (json) {
|
|
4704
4985
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -4731,7 +5012,7 @@ async function handleActionCreate(argv, apiUrl, token) {
|
|
|
4731
5012
|
const json = hasFlag(argv, "--json");
|
|
4732
5013
|
if (!templateId || !slug) {
|
|
4733
5014
|
console.error("Error: template ID and slug required. Usage: canary sandbox action create <template-id> <slug> [options]");
|
|
4734
|
-
|
|
5015
|
+
process11.exit(1);
|
|
4735
5016
|
}
|
|
4736
5017
|
const scriptPath = getArgValue(argv, "--script");
|
|
4737
5018
|
const inputSchemaPath = getArgValue(argv, "--input-schema");
|
|
@@ -4749,63 +5030,63 @@ async function handleActionCreate(argv, apiUrl, token) {
|
|
|
4749
5030
|
const provisionOrderStr = getArgValue(argv, "--provision-order");
|
|
4750
5031
|
if (provisioningMode && provisioningMode !== "shared" && provisioningMode !== "unique") {
|
|
4751
5032
|
console.error("Error: --provisioning-mode must be 'shared' or 'unique'");
|
|
4752
|
-
|
|
5033
|
+
process11.exit(1);
|
|
4753
5034
|
}
|
|
4754
5035
|
if (provisionOrderStr && !/^-?\d+$/.test(provisionOrderStr)) {
|
|
4755
5036
|
console.error("Error: --provision-order must be an integer");
|
|
4756
|
-
|
|
5037
|
+
process11.exit(1);
|
|
4757
5038
|
}
|
|
4758
5039
|
if (!scriptPath) {
|
|
4759
5040
|
console.error("Error: --script <file> is required");
|
|
4760
|
-
|
|
5041
|
+
process11.exit(1);
|
|
4761
5042
|
}
|
|
4762
5043
|
if (!inputSchemaPath) {
|
|
4763
5044
|
console.error("Error: --input-schema <file> is required");
|
|
4764
|
-
|
|
5045
|
+
process11.exit(1);
|
|
4765
5046
|
}
|
|
4766
5047
|
let scriptContent;
|
|
4767
5048
|
try {
|
|
4768
|
-
scriptContent =
|
|
5049
|
+
scriptContent = readFileSync7(scriptPath, "utf-8");
|
|
4769
5050
|
} catch (err) {
|
|
4770
5051
|
console.error(`Error reading script file: ${err}`);
|
|
4771
|
-
|
|
5052
|
+
process11.exit(1);
|
|
4772
5053
|
}
|
|
4773
5054
|
let inputSchema;
|
|
4774
5055
|
try {
|
|
4775
|
-
inputSchema = JSON.parse(
|
|
5056
|
+
inputSchema = JSON.parse(readFileSync7(inputSchemaPath, "utf-8"));
|
|
4776
5057
|
} catch (err) {
|
|
4777
5058
|
console.error(`Error reading input schema file: ${err}`);
|
|
4778
|
-
|
|
5059
|
+
process11.exit(1);
|
|
4779
5060
|
}
|
|
4780
5061
|
let outputSchema;
|
|
4781
5062
|
if (outputSchemaPath) {
|
|
4782
5063
|
try {
|
|
4783
|
-
outputSchema = JSON.parse(
|
|
5064
|
+
outputSchema = JSON.parse(readFileSync7(outputSchemaPath, "utf-8"));
|
|
4784
5065
|
} catch (err) {
|
|
4785
5066
|
console.error(`Error reading output schema file: ${err}`);
|
|
4786
|
-
|
|
5067
|
+
process11.exit(1);
|
|
4787
5068
|
}
|
|
4788
5069
|
}
|
|
4789
5070
|
let credentialOutputConfig;
|
|
4790
5071
|
if (credentialOutputConfigPath) {
|
|
4791
5072
|
try {
|
|
4792
|
-
credentialOutputConfig = JSON.parse(
|
|
5073
|
+
credentialOutputConfig = JSON.parse(readFileSync7(credentialOutputConfigPath, "utf-8"));
|
|
4793
5074
|
} catch (err) {
|
|
4794
5075
|
console.error(`Error reading credential output config file: ${err}`);
|
|
4795
|
-
|
|
5076
|
+
process11.exit(1);
|
|
4796
5077
|
}
|
|
4797
5078
|
}
|
|
4798
5079
|
let outputConfig;
|
|
4799
5080
|
if (outputConfigPath) {
|
|
4800
5081
|
try {
|
|
4801
|
-
outputConfig = JSON.parse(
|
|
5082
|
+
outputConfig = JSON.parse(readFileSync7(outputConfigPath, "utf-8"));
|
|
4802
5083
|
} catch (err) {
|
|
4803
5084
|
console.error(`Error reading output config file: ${err}`);
|
|
4804
|
-
|
|
5085
|
+
process11.exit(1);
|
|
4805
5086
|
}
|
|
4806
5087
|
if (!Array.isArray(outputConfig)) {
|
|
4807
5088
|
console.error("Error: --output-config must contain a JSON array of output slots");
|
|
4808
|
-
|
|
5089
|
+
process11.exit(1);
|
|
4809
5090
|
}
|
|
4810
5091
|
}
|
|
4811
5092
|
const body = {
|
|
@@ -4833,7 +5114,7 @@ async function handleActionCreate(argv, apiUrl, token) {
|
|
|
4833
5114
|
);
|
|
4834
5115
|
if (!res.ok) {
|
|
4835
5116
|
console.error(`Error: ${res.error}`);
|
|
4836
|
-
|
|
5117
|
+
process11.exit(1);
|
|
4837
5118
|
}
|
|
4838
5119
|
if (json) {
|
|
4839
5120
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -4847,7 +5128,7 @@ async function handleActionDelete(argv, apiUrl, token) {
|
|
|
4847
5128
|
const slug = positional[1];
|
|
4848
5129
|
if (!templateId || !slug) {
|
|
4849
5130
|
console.error("Error: template ID and slug required. Usage: canary sandbox action delete <template-id> <slug>");
|
|
4850
|
-
|
|
5131
|
+
process11.exit(1);
|
|
4851
5132
|
}
|
|
4852
5133
|
const res = await apiRequest(
|
|
4853
5134
|
apiUrl,
|
|
@@ -4857,7 +5138,7 @@ async function handleActionDelete(argv, apiUrl, token) {
|
|
|
4857
5138
|
);
|
|
4858
5139
|
if (!res.ok) {
|
|
4859
5140
|
console.error(`Error: ${res.error}`);
|
|
4860
|
-
|
|
5141
|
+
process11.exit(1);
|
|
4861
5142
|
}
|
|
4862
5143
|
console.log(`Action '${slug}' deleted.`);
|
|
4863
5144
|
}
|
|
@@ -4868,16 +5149,16 @@ async function handleActionExecute(argv, apiUrl, token) {
|
|
|
4868
5149
|
const json = hasFlag(argv, "--json");
|
|
4869
5150
|
if (!instanceId || !slug) {
|
|
4870
5151
|
console.error("Error: instance ID and slug required. Usage: canary sandbox action execute <instance-id> <slug> [options]");
|
|
4871
|
-
|
|
5152
|
+
process11.exit(1);
|
|
4872
5153
|
}
|
|
4873
5154
|
let params = {};
|
|
4874
5155
|
const paramsFile = getArgValue(argv, "--params-file");
|
|
4875
5156
|
if (paramsFile) {
|
|
4876
5157
|
try {
|
|
4877
|
-
params = JSON.parse(
|
|
5158
|
+
params = JSON.parse(readFileSync7(paramsFile, "utf-8"));
|
|
4878
5159
|
} catch (err) {
|
|
4879
5160
|
console.error(`Error reading params file: ${err}`);
|
|
4880
|
-
|
|
5161
|
+
process11.exit(1);
|
|
4881
5162
|
}
|
|
4882
5163
|
}
|
|
4883
5164
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -4896,11 +5177,11 @@ async function handleActionExecute(argv, apiUrl, token) {
|
|
|
4896
5177
|
const parsed = Number(timeoutArg);
|
|
4897
5178
|
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
4898
5179
|
console.error(`Error: --timeout-ms must be a positive number, got '${timeoutArg}'`);
|
|
4899
|
-
|
|
5180
|
+
process11.exit(1);
|
|
4900
5181
|
}
|
|
4901
5182
|
if (!runAsync) {
|
|
4902
5183
|
console.error("Error: --timeout-ms only applies with --async");
|
|
4903
|
-
|
|
5184
|
+
process11.exit(1);
|
|
4904
5185
|
}
|
|
4905
5186
|
executionTimeoutMs = parsed;
|
|
4906
5187
|
}
|
|
@@ -4918,7 +5199,7 @@ async function handleActionExecute(argv, apiUrl, token) {
|
|
|
4918
5199
|
);
|
|
4919
5200
|
if (!res.ok) {
|
|
4920
5201
|
console.error(`Error: ${res.error}`);
|
|
4921
|
-
|
|
5202
|
+
process11.exit(1);
|
|
4922
5203
|
}
|
|
4923
5204
|
if (json) {
|
|
4924
5205
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -4959,7 +5240,7 @@ async function handleActionExecutions(argv, apiUrl, token) {
|
|
|
4959
5240
|
const json = hasFlag(argv, "--json");
|
|
4960
5241
|
if (!instanceId) {
|
|
4961
5242
|
console.error("Error: instance ID required. Usage: canary sandbox action executions <instance-id>");
|
|
4962
|
-
|
|
5243
|
+
process11.exit(1);
|
|
4963
5244
|
}
|
|
4964
5245
|
const res = await apiRequest(
|
|
4965
5246
|
apiUrl,
|
|
@@ -4969,7 +5250,7 @@ async function handleActionExecutions(argv, apiUrl, token) {
|
|
|
4969
5250
|
);
|
|
4970
5251
|
if (!res.ok) {
|
|
4971
5252
|
console.error(`Error: ${res.error}`);
|
|
4972
|
-
|
|
5253
|
+
process11.exit(1);
|
|
4973
5254
|
}
|
|
4974
5255
|
if (json) {
|
|
4975
5256
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -5013,7 +5294,7 @@ async function handleActionExecutionGet(argv, apiUrl, token) {
|
|
|
5013
5294
|
console.error(
|
|
5014
5295
|
"Error: instance ID and execution ID required. Usage: cofactor sandbox action execution get <instance-id> <execution-id>"
|
|
5015
5296
|
);
|
|
5016
|
-
|
|
5297
|
+
process11.exit(1);
|
|
5017
5298
|
}
|
|
5018
5299
|
const res = await apiRequest(
|
|
5019
5300
|
apiUrl,
|
|
@@ -5023,7 +5304,7 @@ async function handleActionExecutionGet(argv, apiUrl, token) {
|
|
|
5023
5304
|
);
|
|
5024
5305
|
if (!res.ok) {
|
|
5025
5306
|
console.error(`Error: ${res.error}`);
|
|
5026
|
-
|
|
5307
|
+
process11.exit(1);
|
|
5027
5308
|
}
|
|
5028
5309
|
if (json) {
|
|
5029
5310
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -5040,7 +5321,7 @@ async function handleActionExecutionCancel(argv, apiUrl, token) {
|
|
|
5040
5321
|
console.error(
|
|
5041
5322
|
"Error: instance ID and execution ID required. Usage: cofactor sandbox action execution cancel <instance-id> <execution-id>"
|
|
5042
5323
|
);
|
|
5043
|
-
|
|
5324
|
+
process11.exit(1);
|
|
5044
5325
|
}
|
|
5045
5326
|
const res = await apiRequest(
|
|
5046
5327
|
apiUrl,
|
|
@@ -5050,7 +5331,7 @@ async function handleActionExecutionCancel(argv, apiUrl, token) {
|
|
|
5050
5331
|
);
|
|
5051
5332
|
if (!res.ok) {
|
|
5052
5333
|
console.error(`Error: ${res.error}`);
|
|
5053
|
-
|
|
5334
|
+
process11.exit(1);
|
|
5054
5335
|
}
|
|
5055
5336
|
if (json) {
|
|
5056
5337
|
writeRawOutput(JSON.stringify(res.data, null, 2));
|
|
@@ -5085,7 +5366,7 @@ async function handleRepoList(argv, apiUrl, token) {
|
|
|
5085
5366
|
const templateId = getArgValue(argv, "--template");
|
|
5086
5367
|
if (!templateId) {
|
|
5087
5368
|
console.error("Error: --template is required.");
|
|
5088
|
-
|
|
5369
|
+
process11.exit(1);
|
|
5089
5370
|
}
|
|
5090
5371
|
const result = await apiRequest(
|
|
5091
5372
|
apiUrl,
|
|
@@ -5095,7 +5376,7 @@ async function handleRepoList(argv, apiUrl, token) {
|
|
|
5095
5376
|
);
|
|
5096
5377
|
if (!result.ok) {
|
|
5097
5378
|
console.error(`Error: ${result.error ?? "Unknown error"}`);
|
|
5098
|
-
|
|
5379
|
+
process11.exit(1);
|
|
5099
5380
|
}
|
|
5100
5381
|
if (json) {
|
|
5101
5382
|
writeRawOutput(JSON.stringify(result.items ?? [], null, 2));
|
|
@@ -5124,7 +5405,7 @@ async function handleRepoAdd(argv, apiUrl, token) {
|
|
|
5124
5405
|
if (!templateId || !propertyGithubRepoId) {
|
|
5125
5406
|
console.error("Error: --template and --property-github-repo-id are required.");
|
|
5126
5407
|
console.error("Tip: link the repo to the property first via `canary property repo link`, then pass its id here.");
|
|
5127
|
-
|
|
5408
|
+
process11.exit(1);
|
|
5128
5409
|
}
|
|
5129
5410
|
const parsed = spec ? parseRepoSpec(spec) : {};
|
|
5130
5411
|
const body = { propertyGithubRepoId };
|
|
@@ -5145,7 +5426,7 @@ async function handleRepoAdd(argv, apiUrl, token) {
|
|
|
5145
5426
|
if (!result.ok) {
|
|
5146
5427
|
if (json) writeRawOutput(JSON.stringify(result, null, 2));
|
|
5147
5428
|
else console.error(`Error: ${result.error ?? "Unknown error"}`);
|
|
5148
|
-
|
|
5429
|
+
process11.exit(1);
|
|
5149
5430
|
}
|
|
5150
5431
|
if (json) writeRawOutput(JSON.stringify(result.data, null, 2));
|
|
5151
5432
|
else console.log(`Linked repo to template ${templateId} (link=${result.data?.id})`);
|
|
@@ -5160,7 +5441,7 @@ async function handleRepoUpdate(argv, apiUrl, token) {
|
|
|
5160
5441
|
const serviceName = getArgValue(argv, "--service-name");
|
|
5161
5442
|
if (!templateId || !linkId) {
|
|
5162
5443
|
console.error("Error: --template and --link-id are required.");
|
|
5163
|
-
|
|
5444
|
+
process11.exit(1);
|
|
5164
5445
|
}
|
|
5165
5446
|
const body = {};
|
|
5166
5447
|
if (role !== void 0) body.role = role;
|
|
@@ -5177,7 +5458,7 @@ async function handleRepoUpdate(argv, apiUrl, token) {
|
|
|
5177
5458
|
if (!result.ok) {
|
|
5178
5459
|
if (json) writeRawOutput(JSON.stringify(result, null, 2));
|
|
5179
5460
|
else console.error(`Error: ${result.error ?? "Unknown error"}`);
|
|
5180
|
-
|
|
5461
|
+
process11.exit(1);
|
|
5181
5462
|
}
|
|
5182
5463
|
if (json) writeRawOutput(JSON.stringify(result.data, null, 2));
|
|
5183
5464
|
else console.log(`Updated repo link ${linkId}`);
|
|
@@ -5188,7 +5469,7 @@ async function handleRepoRemove(argv, apiUrl, token) {
|
|
|
5188
5469
|
const linkId = getArgValue(argv, "--link-id");
|
|
5189
5470
|
if (!templateId || !linkId) {
|
|
5190
5471
|
console.error("Error: --template and --link-id are required.");
|
|
5191
|
-
|
|
5472
|
+
process11.exit(1);
|
|
5192
5473
|
}
|
|
5193
5474
|
const result = await apiRequest(
|
|
5194
5475
|
apiUrl,
|
|
@@ -5199,7 +5480,7 @@ async function handleRepoRemove(argv, apiUrl, token) {
|
|
|
5199
5480
|
if (!result.ok) {
|
|
5200
5481
|
if (json) writeRawOutput(JSON.stringify(result, null, 2));
|
|
5201
5482
|
else console.error(`Error: ${result.error ?? "Unknown error"}`);
|
|
5202
|
-
|
|
5483
|
+
process11.exit(1);
|
|
5203
5484
|
}
|
|
5204
5485
|
if (json) writeRawOutput(JSON.stringify(result, null, 2));
|
|
5205
5486
|
else console.log(`Removed repo link ${linkId} from template ${templateId}`);
|
|
@@ -5228,7 +5509,7 @@ Examples:
|
|
|
5228
5509
|
canary sandbox repo add --template <template-id> --property-github-repo-id <uuid> --role frontend
|
|
5229
5510
|
canary sandbox repo add --template <template-id> --property-github-repo-id <uuid> --spec frontend=owner/web@main:/apps/web`
|
|
5230
5511
|
);
|
|
5231
|
-
|
|
5512
|
+
process11.exit(1);
|
|
5232
5513
|
}
|
|
5233
5514
|
}
|
|
5234
5515
|
async function runSandbox(argv) {
|
|
@@ -5247,7 +5528,7 @@ async function runSandbox(argv) {
|
|
|
5247
5528
|
const sections = legacy ? SANDBOX_GUIDE_SECTIONS : SANDBOX_V2_GUIDE_SECTIONS;
|
|
5248
5529
|
if (section && !sections.some((s) => s.slug === section) && SANDBOX_GUIDE_SECTIONS.some((s) => s.slug === section)) {
|
|
5249
5530
|
console.error(`"${section}" is a section of the v1 sandbox guide: run "cofactor sandbox guide v1 ${section}".`);
|
|
5250
|
-
|
|
5531
|
+
process11.exit(1);
|
|
5251
5532
|
}
|
|
5252
5533
|
await printServedGuide(legacy ? "sandbox" : "sandbox-v2", guideRest);
|
|
5253
5534
|
return;
|
|
@@ -5256,7 +5537,7 @@ async function runSandbox(argv) {
|
|
|
5256
5537
|
handleSandboxValidate(rest);
|
|
5257
5538
|
return;
|
|
5258
5539
|
}
|
|
5259
|
-
const { apiUrl, token } = await resolveConfig(options);
|
|
5540
|
+
const { apiUrl, token, appSubdomain } = await resolveConfig(options);
|
|
5260
5541
|
const globalFlags = /* @__PURE__ */ new Set(["--env", "--api-url", "--app-url", "--token", "--org"]);
|
|
5261
5542
|
const args = rest.filter((arg, i) => {
|
|
5262
5543
|
if (globalFlags.has(arg)) return false;
|
|
@@ -5302,7 +5583,7 @@ async function runSandbox(argv) {
|
|
|
5302
5583
|
await handlePoolSetSize(poolRest, apiUrl, token);
|
|
5303
5584
|
} else {
|
|
5304
5585
|
console.error(`Unknown pool action: ${poolAction}. Use "set-size".`);
|
|
5305
|
-
|
|
5586
|
+
process11.exit(1);
|
|
5306
5587
|
}
|
|
5307
5588
|
break;
|
|
5308
5589
|
}
|
|
@@ -5314,7 +5595,7 @@ async function runSandbox(argv) {
|
|
|
5314
5595
|
await handleArtifactCreate(actionRest, apiUrl, token);
|
|
5315
5596
|
} else {
|
|
5316
5597
|
console.error(`Unknown artifact action: ${action2}. Use "get" or "create".`);
|
|
5317
|
-
|
|
5598
|
+
process11.exit(1);
|
|
5318
5599
|
}
|
|
5319
5600
|
break;
|
|
5320
5601
|
}
|
|
@@ -5328,7 +5609,7 @@ async function runSandbox(argv) {
|
|
|
5328
5609
|
await handleConfigUnset(configRest, apiUrl, token);
|
|
5329
5610
|
} else {
|
|
5330
5611
|
console.error(`Unknown config action: ${configAction}. Use "list", "set", or "unset".`);
|
|
5331
|
-
|
|
5612
|
+
process11.exit(1);
|
|
5332
5613
|
}
|
|
5333
5614
|
break;
|
|
5334
5615
|
}
|
|
@@ -5364,13 +5645,13 @@ async function runSandbox(argv) {
|
|
|
5364
5645
|
await handleActionExecutionCancel(execRest, apiUrl, token);
|
|
5365
5646
|
} else {
|
|
5366
5647
|
console.error(`Unknown execution sub-command: ${execSub}. Use "get" or "cancel".`);
|
|
5367
|
-
|
|
5648
|
+
process11.exit(1);
|
|
5368
5649
|
}
|
|
5369
5650
|
break;
|
|
5370
5651
|
}
|
|
5371
5652
|
default:
|
|
5372
5653
|
console.error(`Unknown action sub-command: ${actionSub}. Use "list", "get", "create", "delete", "execute", "executions", or "execution".`);
|
|
5373
|
-
|
|
5654
|
+
process11.exit(1);
|
|
5374
5655
|
}
|
|
5375
5656
|
break;
|
|
5376
5657
|
}
|
|
@@ -5387,7 +5668,7 @@ async function runSandbox(argv) {
|
|
|
5387
5668
|
await handleLogs(args, apiUrl, token);
|
|
5388
5669
|
break;
|
|
5389
5670
|
case "preview":
|
|
5390
|
-
await handlePreview(args, apiUrl, token);
|
|
5671
|
+
await handlePreview(args, apiUrl, token, appSubdomain);
|
|
5391
5672
|
break;
|
|
5392
5673
|
case "envs":
|
|
5393
5674
|
await handleSandboxEnvs(args, apiUrl, token);
|
|
@@ -5422,6 +5703,9 @@ async function runSandbox(argv) {
|
|
|
5422
5703
|
case "sync":
|
|
5423
5704
|
await handleSandboxSync(v2Positionals("sync", args), args, apiUrl, token);
|
|
5424
5705
|
break;
|
|
5706
|
+
case "auth-check":
|
|
5707
|
+
await handleSandboxAuthCheck(args, apiUrl, token);
|
|
5708
|
+
break;
|
|
5425
5709
|
case "verify-env":
|
|
5426
5710
|
await handleSandboxVerifyEnv(args, apiUrl, token);
|
|
5427
5711
|
break;
|
|
@@ -5437,7 +5721,7 @@ async function runSandbox(argv) {
|
|
|
5437
5721
|
default:
|
|
5438
5722
|
console.error(`Unknown sub-command: ${subCommand}`);
|
|
5439
5723
|
printHelp();
|
|
5440
|
-
|
|
5724
|
+
process11.exit(1);
|
|
5441
5725
|
}
|
|
5442
5726
|
}
|
|
5443
5727
|
export {
|
|
@@ -5454,4 +5738,4 @@ export {
|
|
|
5454
5738
|
withPluginEnabled,
|
|
5455
5739
|
withoutPlugin
|
|
5456
5740
|
};
|
|
5457
|
-
//# sourceMappingURL=sandbox-
|
|
5741
|
+
//# sourceMappingURL=sandbox-FBLJEYA2.js.map
|