@keystrokehq/cli 0.1.147 → 0.1.149
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 +35 -42
- package/dist/{alias-RTYYYW3D-CLFgrowj.mjs → alias-RTYYYW3D-BDy5K94v.mjs} +2 -2
- package/dist/{alias-RTYYYW3D-CLFgrowj.mjs.map → alias-RTYYYW3D-BDy5K94v.mjs.map} +1 -1
- package/dist/{awk2-LA3USKJP-BbsfXVlR.mjs → awk2-LA3USKJP-bQiQEw4P.mjs} +8 -8
- package/dist/{awk2-LA3USKJP-BbsfXVlR.mjs.map → awk2-LA3USKJP-bQiQEw4P.mjs.map} +1 -1
- package/dist/{base64-C2AIWVNC-C0WIgu5V.mjs → base64-C2AIWVNC-DMnyEdfa.mjs} +2 -2
- package/dist/{base64-C2AIWVNC-C0WIgu5V.mjs.map → base64-C2AIWVNC-DMnyEdfa.mjs.map} +1 -1
- package/dist/{basename-UB3CIYNI-BgNN3bGm.mjs → basename-UB3CIYNI-DOxZLtwj.mjs} +2 -2
- package/dist/{basename-UB3CIYNI-BgNN3bGm.mjs.map → basename-UB3CIYNI-DOxZLtwj.mjs.map} +1 -1
- package/dist/{bash-YZ33HQZQ-N6YyKtUH.mjs → bash-YZ33HQZQ-D4vRBW1d.mjs} +2 -2
- package/dist/{bash-YZ33HQZQ-N6YyKtUH.mjs.map → bash-YZ33HQZQ-D4vRBW1d.mjs.map} +1 -1
- package/dist/{cat-TSFMZVYS-Dz3-oHsV.mjs → cat-TSFMZVYS-D8QgnfyP.mjs} +3 -3
- package/dist/{cat-TSFMZVYS-Dz3-oHsV.mjs.map → cat-TSFMZVYS-D8QgnfyP.mjs.map} +1 -1
- package/dist/{chmod-TFEPA42X-YF02QJUv.mjs → chmod-TFEPA42X-CRWIslQq.mjs} +2 -2
- package/dist/{chmod-TFEPA42X-YF02QJUv.mjs.map → chmod-TFEPA42X-CRWIslQq.mjs.map} +1 -1
- package/dist/{chunk-5H5SCKJM-BvfXlL01.mjs → chunk-5H5SCKJM-BaNr0aSJ.mjs} +4 -4
- package/dist/{chunk-5H5SCKJM-BvfXlL01.mjs.map → chunk-5H5SCKJM-BaNr0aSJ.mjs.map} +1 -1
- package/dist/{chunk-EBAPSGAO-Ctfslw2R.mjs → chunk-EBAPSGAO-BRba1GMs.mjs} +2 -2
- package/dist/{chunk-EBAPSGAO-Ctfslw2R.mjs.map → chunk-EBAPSGAO-BRba1GMs.mjs.map} +1 -1
- package/dist/{chunk-LEKBROJD-CJMrAyu9.mjs → chunk-LEKBROJD-D1fMh3yQ.mjs} +3 -3
- package/dist/{chunk-LEKBROJD-CJMrAyu9.mjs.map → chunk-LEKBROJD-D1fMh3yQ.mjs.map} +1 -1
- package/dist/{clear-HKGFEOF6-CekN2x-O.mjs → clear-HKGFEOF6-Cy0E2H3t.mjs} +2 -2
- package/dist/{clear-HKGFEOF6-CekN2x-O.mjs.map → clear-HKGFEOF6-Cy0E2H3t.mjs.map} +1 -1
- package/dist/{column-XT6UFXNQ-DHENV9D2.mjs → column-XT6UFXNQ-czPN0bKF.mjs} +2 -2
- package/dist/{column-XT6UFXNQ-DHENV9D2.mjs.map → column-XT6UFXNQ-czPN0bKF.mjs.map} +1 -1
- package/dist/{comm-VV2LDX2J-ppxDJFkY.mjs → comm-VV2LDX2J-nTDBS00q.mjs} +2 -2
- package/dist/{comm-VV2LDX2J-ppxDJFkY.mjs.map → comm-VV2LDX2J-nTDBS00q.mjs.map} +1 -1
- package/dist/{cp-BISAAS7A-CMWayU4n.mjs → cp-BISAAS7A-CXqDGSmH.mjs} +3 -3
- package/dist/{cp-BISAAS7A-CMWayU4n.mjs.map → cp-BISAAS7A-CXqDGSmH.mjs.map} +1 -1
- package/dist/{cut-OKARJCCV-Bt8b58eI.mjs → cut-OKARJCCV-8_v90N99.mjs} +3 -3
- package/dist/{cut-OKARJCCV-Bt8b58eI.mjs.map → cut-OKARJCCV-8_v90N99.mjs.map} +1 -1
- package/dist/{date-UUUPW43J-B7T5OTZZ.mjs → date-UUUPW43J-BiJvTWWs.mjs} +2 -2
- package/dist/{date-UUUPW43J-B7T5OTZZ.mjs.map → date-UUUPW43J-BiJvTWWs.mjs.map} +1 -1
- package/dist/{diff-MWJFIG7X-DhnPc_5r.mjs → diff-MWJFIG7X-D5by0o7Y.mjs} +2 -2
- package/dist/{diff-MWJFIG7X-DhnPc_5r.mjs.map → diff-MWJFIG7X-D5by0o7Y.mjs.map} +1 -1
- package/dist/{dirname-MPHRFUTI-Dkb3S4OX.mjs → dirname-MPHRFUTI-VCKXiC16.mjs} +2 -2
- package/dist/{dirname-MPHRFUTI-Dkb3S4OX.mjs.map → dirname-MPHRFUTI-VCKXiC16.mjs.map} +1 -1
- package/dist/dist-BlsDG7kb.mjs +3 -0
- package/dist/{dist-E6dh6qXe.mjs → dist-C6adeAws.mjs} +1006 -138
- package/dist/dist-C6adeAws.mjs.map +1 -0
- package/dist/dist-DJQnugPi.mjs +1829 -0
- package/dist/dist-DJQnugPi.mjs.map +1 -0
- package/dist/{dist-B72RPrho.mjs → dist-DSkS8uEB.mjs} +43888 -28664
- package/dist/dist-DSkS8uEB.mjs.map +1 -0
- package/dist/dist-Dq1kkbLy.mjs +4 -0
- package/dist/{du-572XNP42-ClP4jXB1.mjs → du-572XNP42-C_zifijt.mjs} +3 -3
- package/dist/{du-572XNP42-ClP4jXB1.mjs.map → du-572XNP42-C_zifijt.mjs.map} +1 -1
- package/dist/{env-36M5BO7M-CsP_aiWr.mjs → env-36M5BO7M-CEbFSWHW.mjs} +6 -6
- package/dist/{env-36M5BO7M-CsP_aiWr.mjs.map → env-36M5BO7M-CEbFSWHW.mjs.map} +1 -1
- package/dist/{expand-JSPG6VOP-B-YUvYpu.mjs → expand-JSPG6VOP-CxlV2zRG.mjs} +2 -2
- package/dist/{expand-JSPG6VOP-B-YUvYpu.mjs.map → expand-JSPG6VOP-CxlV2zRG.mjs.map} +1 -1
- package/dist/{expr-5JAACS4X-1TBq84gG.mjs → expr-5JAACS4X-CM1VDr6S.mjs} +2 -2
- package/dist/{expr-5JAACS4X-1TBq84gG.mjs.map → expr-5JAACS4X-CM1VDr6S.mjs.map} +1 -1
- package/dist/{file-IPZJC3FQ-DcZNOWyY.mjs → file-IPZJC3FQ-DYa9X4WH.mjs} +17 -17
- package/dist/file-IPZJC3FQ-DYa9X4WH.mjs.map +1 -0
- package/dist/{find-INTH3OLC-6p47KeeT.mjs → find-INTH3OLC-CPiHWE9r.mjs} +4 -4
- package/dist/{find-INTH3OLC-6p47KeeT.mjs.map → find-INTH3OLC-CPiHWE9r.mjs.map} +1 -1
- package/dist/{fold-4TQNYMSW-C54bHzLd.mjs → fold-4TQNYMSW-B4tfWHg3.mjs} +2 -2
- package/dist/{fold-4TQNYMSW-C54bHzLd.mjs.map → fold-4TQNYMSW-B4tfWHg3.mjs.map} +1 -1
- package/dist/{grep-V3LQVMRQ-D99Bq7Kj.mjs → grep-V3LQVMRQ-BbRYJxzz.mjs} +3 -3
- package/dist/{grep-V3LQVMRQ-D99Bq7Kj.mjs.map → grep-V3LQVMRQ-BbRYJxzz.mjs.map} +1 -1
- package/dist/{gzip-O5ASJAFY-CZDRROqP.mjs → gzip-O5ASJAFY-rKFFXJyd.mjs} +2 -2
- package/dist/{gzip-O5ASJAFY-CZDRROqP.mjs.map → gzip-O5ASJAFY-rKFFXJyd.mjs.map} +1 -1
- package/dist/{head-442HYESI-D96RMdki.mjs → head-442HYESI-sl2UFkqV.mjs} +2 -2
- package/dist/{head-442HYESI-D96RMdki.mjs.map → head-442HYESI-sl2UFkqV.mjs.map} +1 -1
- package/dist/{history-WYYKSLSZ-ACNQVupn.mjs → history-WYYKSLSZ-BQO1BlWh.mjs} +2 -2
- package/dist/{history-WYYKSLSZ-ACNQVupn.mjs.map → history-WYYKSLSZ-BQO1BlWh.mjs.map} +1 -1
- package/dist/{html-to-markdown-JW4MSQZO-DQOxFIvF.mjs → html-to-markdown-JW4MSQZO-CJGwkgpQ.mjs} +2 -2
- package/dist/{html-to-markdown-JW4MSQZO-DQOxFIvF.mjs.map → html-to-markdown-JW4MSQZO-CJGwkgpQ.mjs.map} +1 -1
- package/dist/index.mjs +9 -1
- package/dist/index.mjs.map +1 -1
- package/dist/{join-TBRGI3LQ-B8I9tkJ5.mjs → join-TBRGI3LQ-BAeS4llp.mjs} +2 -2
- package/dist/{join-TBRGI3LQ-B8I9tkJ5.mjs.map → join-TBRGI3LQ-BAeS4llp.mjs.map} +1 -1
- package/dist/{jq-4XLYLOS5-wFZYfsfJ.mjs → jq-4XLYLOS5-BqwoG7Hj.mjs} +7 -7
- package/dist/{jq-4XLYLOS5-wFZYfsfJ.mjs.map → jq-4XLYLOS5-BqwoG7Hj.mjs.map} +1 -1
- package/dist/{js-exec-N5KEZBH7-CoRL1IOd.mjs → js-exec-N5KEZBH7-DbXmMwbQ.mjs} +13 -13
- package/dist/{js-exec-N5KEZBH7-CoRL1IOd.mjs.map → js-exec-N5KEZBH7-DbXmMwbQ.mjs.map} +1 -1
- package/dist/{ln-4LGSXXGD-Dg0R9i5G.mjs → ln-4LGSXXGD-CpN9ORGB.mjs} +2 -2
- package/dist/{ln-4LGSXXGD-Dg0R9i5G.mjs.map → ln-4LGSXXGD-CpN9ORGB.mjs.map} +1 -1
- package/dist/{ls-ZJGQER7M-DLDSfI4x.mjs → ls-ZJGQER7M-B0J6G8Ao.mjs} +3 -3
- package/dist/{ls-ZJGQER7M-DLDSfI4x.mjs.map → ls-ZJGQER7M-B0J6G8Ao.mjs.map} +1 -1
- package/dist/{maybe-auto-update-CKoJBwJ_.mjs → maybe-auto-update-dzy8lzxn.mjs} +13 -8
- package/dist/maybe-auto-update-dzy8lzxn.mjs.map +1 -0
- package/dist/{md5sum-SPU24VSG-DQc8sqXO.mjs → md5sum-SPU24VSG-CETB591F.mjs} +3 -3
- package/dist/{md5sum-SPU24VSG-DQc8sqXO.mjs.map → md5sum-SPU24VSG-CETB591F.mjs.map} +1 -1
- package/dist/{mkdir-MEPGZOB6-BjUKzhdL.mjs → mkdir-MEPGZOB6-B5bpLWTB.mjs} +6 -6
- package/dist/{mkdir-MEPGZOB6-BjUKzhdL.mjs.map → mkdir-MEPGZOB6-B5bpLWTB.mjs.map} +1 -1
- package/dist/{mv-W5BIQ646-CdSIyY-U.mjs → mv-W5BIQ646-7tYLVIVw.mjs} +3 -3
- package/dist/{mv-W5BIQ646-CdSIyY-U.mjs.map → mv-W5BIQ646-7tYLVIVw.mjs.map} +1 -1
- package/dist/{nl-WSDW7I4O-DmtCnyKU.mjs → nl-WSDW7I4O-CHnycLPl.mjs} +2 -2
- package/dist/{nl-WSDW7I4O-DmtCnyKU.mjs.map → nl-WSDW7I4O-CHnycLPl.mjs.map} +1 -1
- package/dist/{paste-7JC6S4DX-CtmM5Qy8.mjs → paste-7JC6S4DX-CKUaf7iE.mjs} +2 -2
- package/dist/{paste-7JC6S4DX-CtmM5Qy8.mjs.map → paste-7JC6S4DX-CKUaf7iE.mjs.map} +1 -1
- package/dist/{printf-TWGXF445-B7cTysaa.mjs → printf-TWGXF445-Byg8yI9x.mjs} +3 -3
- package/dist/{printf-TWGXF445-B7cTysaa.mjs.map → printf-TWGXF445-Byg8yI9x.mjs.map} +1 -1
- package/dist/{program-OcNo9lRF.mjs → program-CyirDIA2.mjs} +4538 -1013
- package/dist/program-CyirDIA2.mjs.map +1 -0
- package/dist/{python3-POMOR4OA-CKy80mpF.mjs → python3-POMOR4OA-D-6P4EKs.mjs} +17 -17
- package/dist/python3-POMOR4OA-D-6P4EKs.mjs.map +1 -0
- package/dist/{readlink-OPJF4DL5-Rc1Mz_Xx.mjs → readlink-OPJF4DL5-CGS-PxkH.mjs} +2 -2
- package/dist/{readlink-OPJF4DL5-Rc1Mz_Xx.mjs.map → readlink-OPJF4DL5-CGS-PxkH.mjs.map} +1 -1
- package/dist/{rev-5EHFX4EJ-3dd9vwHW.mjs → rev-5EHFX4EJ-BLJdSt1D.mjs} +2 -2
- package/dist/{rev-5EHFX4EJ-3dd9vwHW.mjs.map → rev-5EHFX4EJ-BLJdSt1D.mjs.map} +1 -1
- package/dist/{rg-S4FXYXWB-BbXVo6JX.mjs → rg-S4FXYXWB-Ekm2aMrv.mjs} +14 -14
- package/dist/{rg-S4FXYXWB-BbXVo6JX.mjs.map → rg-S4FXYXWB-Ekm2aMrv.mjs.map} +1 -1
- package/dist/{rm-SSGETQVQ-Ch53Rmrr.mjs → rm-SSGETQVQ-DV-525hw.mjs} +6 -6
- package/dist/{rm-SSGETQVQ-Ch53Rmrr.mjs.map → rm-SSGETQVQ-DV-525hw.mjs.map} +1 -1
- package/dist/{rmdir-OC4ZLPYA-D3QCHlIB.mjs → rmdir-OC4ZLPYA-BGOrZMUa.mjs} +3 -3
- package/dist/{rmdir-OC4ZLPYA-D3QCHlIB.mjs.map → rmdir-OC4ZLPYA-BGOrZMUa.mjs.map} +1 -1
- package/dist/{run-package-manager-update-DgeSZuUc.mjs → run-package-manager-update-C4uA6xWB.mjs} +281 -66
- package/dist/run-package-manager-update-C4uA6xWB.mjs.map +1 -0
- package/dist/{sed-S5UIK574-DE1KeTk3.mjs → sed-S5UIK574-C3nurgzc.mjs} +4 -4
- package/dist/{sed-S5UIK574-DE1KeTk3.mjs.map → sed-S5UIK574-C3nurgzc.mjs.map} +1 -1
- package/dist/{sha1sum-2PTOAFR6-DNjSNcYK.mjs → sha1sum-2PTOAFR6-GDQNigjA.mjs} +3 -3
- package/dist/{sha1sum-2PTOAFR6-DNjSNcYK.mjs.map → sha1sum-2PTOAFR6-GDQNigjA.mjs.map} +1 -1
- package/dist/{sha256sum-NS7D3IXX-BMaCKkT5.mjs → sha256sum-NS7D3IXX-bI78J2Ek.mjs} +3 -3
- package/dist/{sha256sum-NS7D3IXX-BMaCKkT5.mjs.map → sha256sum-NS7D3IXX-bI78J2Ek.mjs.map} +1 -1
- package/dist/skills-bundle/_AGENTS.mcp.md +9 -9
- package/dist/skills-bundle/_AGENTS.md +88 -47
- package/dist/{sleep-X22JJINO-Do5hEQQW.mjs → sleep-X22JJINO-gsrMSr5k.mjs} +2 -2
- package/dist/{sleep-X22JJINO-Do5hEQQW.mjs.map → sleep-X22JJINO-gsrMSr5k.mjs.map} +1 -1
- package/dist/{sort-SW2YEO5B-B7j1zw8k.mjs → sort-SW2YEO5B-D5UsbJuA.mjs} +3 -3
- package/dist/{sort-SW2YEO5B-B7j1zw8k.mjs.map → sort-SW2YEO5B-D5UsbJuA.mjs.map} +1 -1
- package/dist/{split-4KKZZXXE-h8GD4HP4.mjs → split-4KKZZXXE-D_eM0wGE.mjs} +2 -2
- package/dist/{split-4KKZZXXE-h8GD4HP4.mjs.map → split-4KKZZXXE-D_eM0wGE.mjs.map} +1 -1
- package/dist/{sqlite3-CGOEFJAO-DvzzghDg.mjs → sqlite3-CGOEFJAO-Eu-N4sPa.mjs} +3 -3
- package/dist/{sqlite3-CGOEFJAO-DvzzghDg.mjs.map → sqlite3-CGOEFJAO-Eu-N4sPa.mjs.map} +1 -1
- package/dist/{stat-CD34IZ4P-B5ZOJh4J.mjs → stat-CD34IZ4P-BS1XgX8K.mjs} +2 -2
- package/dist/{stat-CD34IZ4P-B5ZOJh4J.mjs.map → stat-CD34IZ4P-BS1XgX8K.mjs.map} +1 -1
- package/dist/{strings-6WDHLGMX-DdiEdoL-.mjs → strings-6WDHLGMX-BGTuHTcR.mjs} +2 -2
- package/dist/{strings-6WDHLGMX-DdiEdoL-.mjs.map → strings-6WDHLGMX-BGTuHTcR.mjs.map} +1 -1
- package/dist/{tail-R4PCA2C4-CUfroNeu.mjs → tail-R4PCA2C4-CfCvDsXY.mjs} +2 -2
- package/dist/{tail-R4PCA2C4-CUfroNeu.mjs.map → tail-R4PCA2C4-CfCvDsXY.mjs.map} +1 -1
- package/dist/{tar-STHHZTZ6-DRf60e-G.mjs → tar-STHHZTZ6-CzlFhy51.mjs} +3 -3
- package/dist/{tar-STHHZTZ6-DRf60e-G.mjs.map → tar-STHHZTZ6-CzlFhy51.mjs.map} +1 -1
- package/dist/{tee-YUZ2FKCJ-po_dfyc_.mjs → tee-YUZ2FKCJ-rZEvi44P.mjs} +2 -2
- package/dist/{tee-YUZ2FKCJ-po_dfyc_.mjs.map → tee-YUZ2FKCJ-rZEvi44P.mjs.map} +1 -1
- package/dist/templates/hello-world/README.md +1 -1
- package/dist/templates/hello-world/package.json +2 -3
- package/dist/{time-D4LNBSWX-DVWhAldK.mjs → time-D4LNBSWX-D_2r6q9s.mjs} +6 -6
- package/dist/{time-D4LNBSWX-DVWhAldK.mjs.map → time-D4LNBSWX-D_2r6q9s.mjs.map} +1 -1
- package/dist/{timeout-YDCRSLPQ-B_yntmXU.mjs → timeout-YDCRSLPQ-BCyope7T.mjs} +7 -7
- package/dist/{timeout-YDCRSLPQ-B_yntmXU.mjs.map → timeout-YDCRSLPQ-BCyope7T.mjs.map} +1 -1
- package/dist/{touch-UA33VN3N-oW1SBT1r.mjs → touch-UA33VN3N-NfG_nyJw.mjs} +2 -2
- package/dist/{touch-UA33VN3N-oW1SBT1r.mjs.map → touch-UA33VN3N-NfG_nyJw.mjs.map} +1 -1
- package/dist/{tr-36LHWFRQ-CTq3z6wq.mjs → tr-36LHWFRQ-BqtCYOhC.mjs} +3 -3
- package/dist/{tr-36LHWFRQ-CTq3z6wq.mjs.map → tr-36LHWFRQ-BqtCYOhC.mjs.map} +1 -1
- package/dist/{tree-YLD52CNT-CIozNsLQ.mjs → tree-YLD52CNT-BjNKvvwq.mjs} +3 -3
- package/dist/{tree-YLD52CNT-CIozNsLQ.mjs.map → tree-YLD52CNT-BjNKvvwq.mjs.map} +1 -1
- package/dist/{unexpand-CADSA4VO-Cq9DTcUg.mjs → unexpand-CADSA4VO-BRBq66Ny.mjs} +2 -2
- package/dist/{unexpand-CADSA4VO-Cq9DTcUg.mjs.map → unexpand-CADSA4VO-BRBq66Ny.mjs.map} +1 -1
- package/dist/{uniq-XSIZR6PB-D5lt-IXF.mjs → uniq-XSIZR6PB-CEAOHvgb.mjs} +3 -3
- package/dist/{uniq-XSIZR6PB-D5lt-IXF.mjs.map → uniq-XSIZR6PB-CEAOHvgb.mjs.map} +1 -1
- package/dist/{wc-LF7NU4LA-BFPabrwD.mjs → wc-LF7NU4LA-DQsmzFSM.mjs} +3 -3
- package/dist/{wc-LF7NU4LA-BFPabrwD.mjs.map → wc-LF7NU4LA-DQsmzFSM.mjs.map} +1 -1
- package/dist/{which-XEM24D5D-2LvlkpUo.mjs → which-XEM24D5D-ClMwNewh.mjs} +2 -2
- package/dist/{which-XEM24D5D-2LvlkpUo.mjs.map → which-XEM24D5D-ClMwNewh.mjs.map} +1 -1
- package/dist/{xan-Y6WF3IRG-DjKO96Pj.mjs → xan-Y6WF3IRG-CvZAKNm2.mjs} +7 -7
- package/dist/{xan-Y6WF3IRG-DjKO96Pj.mjs.map → xan-Y6WF3IRG-CvZAKNm2.mjs.map} +1 -1
- package/dist/{xargs-MGZPH7AX-D2bIgrdg.mjs → xargs-MGZPH7AX-DaERa6_Q.mjs} +6 -6
- package/dist/{xargs-MGZPH7AX-D2bIgrdg.mjs.map → xargs-MGZPH7AX-DaERa6_Q.mjs.map} +1 -1
- package/dist/{yq-4QJW3EQG-XhB3aACo.mjs → yq-4QJW3EQG-DABmi_in.mjs} +88 -88
- package/dist/yq-4QJW3EQG-DABmi_in.mjs.map +1 -0
- package/package.json +3 -6
- package/dist/dist-B72RPrho.mjs.map +0 -1
- package/dist/dist-CBVf3SwI.mjs +0 -11661
- package/dist/dist-CBVf3SwI.mjs.map +0 -1
- package/dist/dist-CyKcvwuI.mjs +0 -816
- package/dist/dist-CyKcvwuI.mjs.map +0 -1
- package/dist/dist-Duu6X1Sh.mjs +0 -4
- package/dist/dist-E6dh6qXe.mjs.map +0 -1
- package/dist/file-IPZJC3FQ-DcZNOWyY.mjs.map +0 -1
- package/dist/maybe-auto-update-CKoJBwJ_.mjs.map +0 -1
- package/dist/program-OcNo9lRF.mjs.map +0 -1
- package/dist/python3-POMOR4OA-CKy80mpF.mjs.map +0 -1
- package/dist/run-package-manager-update-DgeSZuUc.mjs.map +0 -1
- package/dist/watch-app-DTIeKrbl-CSpsNzVG.mjs +0 -100
- package/dist/watch-app-DTIeKrbl-CSpsNzVG.mjs.map +0 -1
- package/dist/yq-4QJW3EQG-XhB3aACo.mjs.map +0 -1
|
@@ -0,0 +1,1829 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { an as PROJECT_MANIFEST_REL_PATH, cn as PROJECT_SOURCE_IGNORED_DIRS, dn as PROJECT_SOURCE_MAX_TOTAL_BYTES, fi as isIgnoredProjectSourceFileName, ln as PROJECT_SOURCE_MAX_FILES, mi as isProbablyBinaryProjectSource, un as PROJECT_SOURCE_MAX_FILE_BYTES } from "./dist-C6adeAws.mjs";
|
|
3
|
+
import { at as classifyCall, ct as locateWorkflow, dt as entryIdFromFile, ft as readKeystrokeIgnoreDirective, it as packDirFromDisk, ot as computeCallSiteIds, pt as shouldSkipKeystrokeModuleFile, st as diagnoseWorkflowSource } from "./dist-DSkS8uEB.mjs";
|
|
4
|
+
import { createRequire, isBuiltin } from "node:module";
|
|
5
|
+
import "node:os";
|
|
6
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
|
+
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { build, mergeConfig } from "tsdown";
|
|
11
|
+
import ts from "typescript";
|
|
12
|
+
//#region ../../packages/build/dist/walk-project-CLy8GqGX.mjs
|
|
13
|
+
function toPosix$1(path) {
|
|
14
|
+
return path.split(sep).join("/");
|
|
15
|
+
}
|
|
16
|
+
function projectRelativePath(root, filePath) {
|
|
17
|
+
return toPosix$1(relative(root, normalizeBuildFilePath(filePath)));
|
|
18
|
+
}
|
|
19
|
+
/** Normalize paths so /var and /private/var compare equal on macOS. */
|
|
20
|
+
function normalizeBuildFilePath(filePath) {
|
|
21
|
+
const absolute = resolve(filePath);
|
|
22
|
+
try {
|
|
23
|
+
return realpathSync.native(absolute);
|
|
24
|
+
} catch {
|
|
25
|
+
return absolute;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function normalizeModuleId(id, root) {
|
|
29
|
+
if (id.startsWith("\0")) return null;
|
|
30
|
+
const normalized = normalizeBuildFilePath(id.startsWith("file://") ? fileURLToPath(id) : id);
|
|
31
|
+
if (!normalized.startsWith(normalizeBuildFilePath(root))) return null;
|
|
32
|
+
return normalized;
|
|
33
|
+
}
|
|
34
|
+
function formatImportGuardError(root, ignoredPath, importer) {
|
|
35
|
+
const ignored = projectRelativePath(root, ignoredPath);
|
|
36
|
+
if (importer) return `Cannot import "${ignored}" (@keystroke ignore) from "${projectRelativePath(root, importer)}". Remove the directive or stop importing it from a shipped module.`;
|
|
37
|
+
return `Cannot import "${ignored}" (@keystroke ignore). Remove the directive or stop importing it from a shipped module.`;
|
|
38
|
+
}
|
|
39
|
+
/** Throw when a built module imports a `@keystroke ignore` source file. */
|
|
40
|
+
function keystrokeIgnoreGuardPlugin(options) {
|
|
41
|
+
const root = normalizeBuildFilePath(options.root);
|
|
42
|
+
const ignoredFiles = new Set(options.ignoredFiles.map((filePath) => normalizeBuildFilePath(filePath)));
|
|
43
|
+
function assertNotIgnored(filePath, importer) {
|
|
44
|
+
const normalized = normalizeBuildFilePath(filePath);
|
|
45
|
+
if (!ignoredFiles.has(normalized)) return;
|
|
46
|
+
throw new Error(formatImportGuardError(root, normalized, importer));
|
|
47
|
+
}
|
|
48
|
+
return {
|
|
49
|
+
name: "keystroke-ignore-guard",
|
|
50
|
+
async resolveId(source, importer, resolveOptions) {
|
|
51
|
+
if (resolveOptions?.isEntry || !importer) return null;
|
|
52
|
+
const resolved = await this.resolve(source, importer, {
|
|
53
|
+
...resolveOptions,
|
|
54
|
+
skipSelf: true
|
|
55
|
+
});
|
|
56
|
+
if (!resolved) return null;
|
|
57
|
+
assertNotIgnored(typeof resolved === "string" ? resolved : resolved.id, importer);
|
|
58
|
+
return null;
|
|
59
|
+
},
|
|
60
|
+
load(id) {
|
|
61
|
+
const normalized = normalizeModuleId(id, root);
|
|
62
|
+
if (!normalized) return null;
|
|
63
|
+
assertNotIgnored(normalized);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const MAX_FILE_BYTES = PROJECT_SOURCE_MAX_FILE_BYTES;
|
|
69
|
+
const MAX_TOTAL_BYTES = PROJECT_SOURCE_MAX_TOTAL_BYTES;
|
|
70
|
+
const MAX_FILES = PROJECT_SOURCE_MAX_FILES;
|
|
71
|
+
function toPosix$6(path) {
|
|
72
|
+
return path.split(sep).join("/");
|
|
73
|
+
}
|
|
74
|
+
function classifyBuildEntry(root, srcRoot, filePath) {
|
|
75
|
+
const relativePath = toPosix$6(relative(root, filePath));
|
|
76
|
+
const agentsPrefix = `${toPosix$6(relative(root, join(srcRoot, "agents")))}/`;
|
|
77
|
+
const workflowsPrefix = `${toPosix$6(relative(root, join(srcRoot, "workflows")))}/`;
|
|
78
|
+
const triggersPrefix = `${toPosix$6(relative(root, join(srcRoot, "triggers")))}/`;
|
|
79
|
+
const actionsPrefix = `${toPosix$6(relative(root, join(srcRoot, "actions")))}/`;
|
|
80
|
+
if (relativePath.startsWith(agentsPrefix) && /\.(ts|mts)$/.test(relativePath) && !/\.(int\.)?test\.(ts|mts)$/.test(relativePath)) {
|
|
81
|
+
const id = entryIdFromFile(join(srcRoot, "agents"), filePath, { nestedEntry: "agent" });
|
|
82
|
+
return id ? {
|
|
83
|
+
entryKey: `agents/${id}`,
|
|
84
|
+
entryPath: filePath
|
|
85
|
+
} : null;
|
|
86
|
+
}
|
|
87
|
+
if (relativePath.startsWith(actionsPrefix) && /\.(ts|mts)$/.test(relativePath) && !/\.(int\.)?test\.(ts|mts)$/.test(relativePath)) {
|
|
88
|
+
const id = entryIdFromFile(join(srcRoot, "actions"), filePath, { nestedEntry: "action" });
|
|
89
|
+
return id ? {
|
|
90
|
+
entryKey: `actions/${id}`,
|
|
91
|
+
entryPath: filePath
|
|
92
|
+
} : null;
|
|
93
|
+
}
|
|
94
|
+
if (relativePath.startsWith(workflowsPrefix) && /\.(ts|mts)$/.test(relativePath) && !/\.(int\.)?test\.(ts|mts)$/.test(relativePath)) {
|
|
95
|
+
const id = entryIdFromFile(join(srcRoot, "workflows"), filePath, { nestedEntry: "workflow" });
|
|
96
|
+
return id ? {
|
|
97
|
+
entryKey: `workflows/${id}`,
|
|
98
|
+
entryPath: filePath
|
|
99
|
+
} : null;
|
|
100
|
+
}
|
|
101
|
+
if (relativePath.startsWith(triggersPrefix) && /\.(ts|mts)$/.test(relativePath) && !/\.(int\.)?test\.(ts|mts)$/.test(relativePath)) {
|
|
102
|
+
const id = entryIdFromFile(join(srcRoot, "triggers"), filePath, { nestedEntry: "trigger" });
|
|
103
|
+
return id ? {
|
|
104
|
+
entryKey: `triggers/${id}`,
|
|
105
|
+
entryPath: filePath
|
|
106
|
+
} : null;
|
|
107
|
+
}
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
function shouldSkipIgnoredModule(filePath, phase) {
|
|
111
|
+
return shouldSkipKeystrokeModuleFile(readKeystrokeIgnoreDirective(filePath), phase);
|
|
112
|
+
}
|
|
113
|
+
function isSrcTypeScriptFile(srcRoot, filePath) {
|
|
114
|
+
const relativePath = toPosix$6(relative(srcRoot, filePath));
|
|
115
|
+
if (relativePath.startsWith("..")) return false;
|
|
116
|
+
return /\.(ts|mts)$/.test(relativePath);
|
|
117
|
+
}
|
|
118
|
+
function walkTree(root, dir, srcRoot, phase, collectSources, sourceFiles, buildEntries, ignoredFiles, totals) {
|
|
119
|
+
for (const name of readdirSync(dir).sort()) {
|
|
120
|
+
if (collectSources && (sourceFiles.length >= MAX_FILES || totals.bytes >= MAX_TOTAL_BYTES)) return;
|
|
121
|
+
const absolute = join(dir, name);
|
|
122
|
+
const stats = statSync(absolute);
|
|
123
|
+
if (stats.isDirectory()) {
|
|
124
|
+
if (PROJECT_SOURCE_IGNORED_DIRS.has(name)) continue;
|
|
125
|
+
walkTree(root, absolute, srcRoot, phase, collectSources, sourceFiles, buildEntries, ignoredFiles, totals);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (!stats.isFile()) continue;
|
|
129
|
+
const isUnderSrc = isSrcTypeScriptFile(srcRoot, absolute);
|
|
130
|
+
const skipIgnored = isUnderSrc ? shouldSkipIgnoredModule(absolute, phase) : false;
|
|
131
|
+
if (isUnderSrc && skipIgnored) ignoredFiles.push(normalizeBuildFilePath(absolute));
|
|
132
|
+
const buildEntry = classifyBuildEntry(root, srcRoot, absolute);
|
|
133
|
+
if (buildEntry && !skipIgnored) buildEntries[buildEntry.entryKey] = buildEntry.entryPath;
|
|
134
|
+
if (!collectSources) continue;
|
|
135
|
+
if (stats.size > MAX_FILE_BYTES || isIgnoredProjectSourceFileName(name)) continue;
|
|
136
|
+
const buffer = readFileSync(absolute);
|
|
137
|
+
if (isProbablyBinaryProjectSource(buffer)) continue;
|
|
138
|
+
totals.bytes += buffer.byteLength;
|
|
139
|
+
sourceFiles.push({
|
|
140
|
+
path: toPosix$6(relative(root, absolute)),
|
|
141
|
+
contents: buffer.toString("utf8"),
|
|
142
|
+
hash: createHash("sha256").update(buffer).digest("hex")
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* One full-tree walk of the project: classifies tsdown entries and optionally
|
|
148
|
+
* collects deploy source files (path + contents + hash) under snapshot limits.
|
|
149
|
+
*/
|
|
150
|
+
function walkProject(root, srcDirOrOptions = "src", maybeOptions) {
|
|
151
|
+
const srcDir = typeof srcDirOrOptions === "string" ? srcDirOrOptions : srcDirOrOptions.srcDir ?? "src";
|
|
152
|
+
const options = typeof srcDirOrOptions === "string" ? maybeOptions ?? {} : srcDirOrOptions;
|
|
153
|
+
const collectSources = options.collectSources ?? false;
|
|
154
|
+
const phase = options.phase ?? "build";
|
|
155
|
+
const sourceFiles = [];
|
|
156
|
+
const buildEntries = {};
|
|
157
|
+
const ignoredFiles = [];
|
|
158
|
+
walkTree(root, root, join(root, srcDir), phase, collectSources, sourceFiles, buildEntries, ignoredFiles, { bytes: 0 });
|
|
159
|
+
return {
|
|
160
|
+
sourceFiles,
|
|
161
|
+
buildEntries,
|
|
162
|
+
ignoredFiles
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region ../../packages/tsdown-config/deps.js
|
|
167
|
+
/** Native addons kept external — resolved from the CLI runtime at app start. */
|
|
168
|
+
const NATIVE_RUNTIME_DEPS = [
|
|
169
|
+
"pg",
|
|
170
|
+
"better-sqlite3",
|
|
171
|
+
"@parcel/watcher"
|
|
172
|
+
];
|
|
173
|
+
/**
|
|
174
|
+
* Framework/runtime `@keystrokehq/*` shipped in the project-server image, so user-app builds
|
|
175
|
+
* keep them external and resolve them from the runtime at boot. Everything else under the scope
|
|
176
|
+
* (Composio catalog integrations like `@keystrokehq/posthog`, …) is bundled into the artifact.
|
|
177
|
+
*
|
|
178
|
+
* This set MUST equal the `@keystrokehq/*` dependency closure of `@keystrokehq/project-runtime`
|
|
179
|
+
* (the deploy image) — `deps.test.ts` enforces it. To change what stays external, change what the
|
|
180
|
+
* runtime image depends on; do not edit this list by hand. `exa` is the only integration
|
|
181
|
+
* package the framework imports directly (web search); `gateway` ships Slack and other channel
|
|
182
|
+
* adapters in the runtime image.
|
|
183
|
+
*/
|
|
184
|
+
const RUNTIME_KEYSTROKE_PACKAGES = new Set([
|
|
185
|
+
"access-control",
|
|
186
|
+
"action",
|
|
187
|
+
"agent",
|
|
188
|
+
"app",
|
|
189
|
+
"auth",
|
|
190
|
+
"build",
|
|
191
|
+
"config",
|
|
192
|
+
"credentials",
|
|
193
|
+
"database",
|
|
194
|
+
"exa",
|
|
195
|
+
"gateway",
|
|
196
|
+
"hosting",
|
|
197
|
+
"http",
|
|
198
|
+
"keystroke",
|
|
199
|
+
"manifest",
|
|
200
|
+
"mcp",
|
|
201
|
+
"memory",
|
|
202
|
+
"oauth",
|
|
203
|
+
"platform-database",
|
|
204
|
+
"project-runtime",
|
|
205
|
+
"projects",
|
|
206
|
+
"runtime",
|
|
207
|
+
"sandbox",
|
|
208
|
+
"scheduler",
|
|
209
|
+
"sdk",
|
|
210
|
+
"secrets",
|
|
211
|
+
"shared",
|
|
212
|
+
"storage",
|
|
213
|
+
"telemetry",
|
|
214
|
+
"tracing",
|
|
215
|
+
"trigger",
|
|
216
|
+
"tsdown-config",
|
|
217
|
+
"web-search",
|
|
218
|
+
"worker",
|
|
219
|
+
"workflow",
|
|
220
|
+
"workflow-canvas"
|
|
221
|
+
]);
|
|
222
|
+
/** Matches framework/runtime `@keystrokehq/*` kept external during user-app builds. */
|
|
223
|
+
const FRAMEWORK_KEYSTROKE_EXTERNAL = new RegExp(`^@keystrokehq\\/(?:${[...RUNTIME_KEYSTROKE_PACKAGES].sort().join("|")})(?:\\/|$)`);
|
|
224
|
+
function keystrokePackageName(name) {
|
|
225
|
+
if (!name.startsWith("@keystrokehq/")) return null;
|
|
226
|
+
return name.slice(13).split("/")[0] ?? null;
|
|
227
|
+
}
|
|
228
|
+
function isRuntimeKeystrokePackage(name) {
|
|
229
|
+
const pkg = keystrokePackageName(name);
|
|
230
|
+
return pkg !== null && RUNTIME_KEYSTROKE_PACKAGES.has(pkg);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Kept external so .d.ts emission does not inline internal/inferred types.
|
|
234
|
+
*
|
|
235
|
+
* `typescript` is external because the TS compiler cannot be cleanly bundled into
|
|
236
|
+
* ESM — it mixes `require()`/`__filename` with top-level await, which Node rejects
|
|
237
|
+
* with `ERR_AMBIGUOUS_MODULE_SYNTAX` on load. Packages that use the compiler
|
|
238
|
+
* (`workflow-canvas`, `manifest`, `build`) declare it as a dependency, so it
|
|
239
|
+
* resolves from `node_modules` at runtime instead of being inlined.
|
|
240
|
+
*/
|
|
241
|
+
const LIBRARY_EXTERNAL_DEPS = [
|
|
242
|
+
"better-auth",
|
|
243
|
+
/^@better-auth\//,
|
|
244
|
+
/^better-auth\//,
|
|
245
|
+
"typescript"
|
|
246
|
+
];
|
|
247
|
+
/** Kept external when bundling published entry points (cli, keystroke, platform). */
|
|
248
|
+
const BUNDLED_ENTRY_EXTERNAL_DEPS = [...[
|
|
249
|
+
...NATIVE_RUNTIME_DEPS,
|
|
250
|
+
"dockerode",
|
|
251
|
+
"ssh2",
|
|
252
|
+
"cpu-features",
|
|
253
|
+
"microsandbox",
|
|
254
|
+
/^@aws-sdk\//,
|
|
255
|
+
/^@napi-rs\//,
|
|
256
|
+
"hono",
|
|
257
|
+
/^@hono\//,
|
|
258
|
+
"undici"
|
|
259
|
+
], ...LIBRARY_EXTERNAL_DEPS];
|
|
260
|
+
/** Bundle into CLI — device login uses Better Auth client only. */
|
|
261
|
+
const BETTER_AUTH_CLIENT_BUNDLE_PATTERNS = [/^better-auth\/client$/, /^better-auth\/client\//];
|
|
262
|
+
function isNativeRuntimeDep(id) {
|
|
263
|
+
return NATIVE_RUNTIME_DEPS.some((dep) => id === dep || id.startsWith(`${dep}/`));
|
|
264
|
+
}
|
|
265
|
+
function packageName(id) {
|
|
266
|
+
if (id.startsWith("@")) {
|
|
267
|
+
const [scope, name] = id.split("/");
|
|
268
|
+
return name ? `${scope}/${name}` : id;
|
|
269
|
+
}
|
|
270
|
+
return id.split("/")[0] ?? id;
|
|
271
|
+
}
|
|
272
|
+
/** Zod ships in the project-runtime image; keep one copy out of user-app artifacts. */
|
|
273
|
+
function isRuntimeSharedNpmDep(id) {
|
|
274
|
+
return packageName(id) === "zod";
|
|
275
|
+
}
|
|
276
|
+
function isRuntimeExternal(id) {
|
|
277
|
+
const name = packageName(id);
|
|
278
|
+
if (isNativeRuntimeDep(name)) return true;
|
|
279
|
+
if (isRuntimeSharedNpmDep(name)) return true;
|
|
280
|
+
if (name.startsWith("@keystrokehq/")) return isRuntimeKeystrokePackage(name);
|
|
281
|
+
if (name === "better-auth" || name.startsWith("@better-auth/")) return true;
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
/** Library packages resolve npm deps from node_modules at runtime. */
|
|
285
|
+
function libraryBuildDepsConfig() {
|
|
286
|
+
return {
|
|
287
|
+
alwaysBundle: (_id) => null,
|
|
288
|
+
neverBundle: [
|
|
289
|
+
...NATIVE_RUNTIME_DEPS,
|
|
290
|
+
/^@keystrokehq\//,
|
|
291
|
+
...LIBRARY_EXTERNAL_DEPS
|
|
292
|
+
],
|
|
293
|
+
onlyBundle: false
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
/** Bundle npm deps into dist; resolve @keystrokehq/*, zod, and natives from the runtime. */
|
|
297
|
+
function userAppBuildDepsConfig() {
|
|
298
|
+
return {
|
|
299
|
+
alwaysBundle: (id, _importer) => {
|
|
300
|
+
if (id.startsWith(".") || id.startsWith("/") || isBuiltin(id)) return null;
|
|
301
|
+
return isRuntimeExternal(id) ? null : true;
|
|
302
|
+
},
|
|
303
|
+
neverBundle: [
|
|
304
|
+
...NATIVE_RUNTIME_DEPS,
|
|
305
|
+
FRAMEWORK_KEYSTROKE_EXTERNAL,
|
|
306
|
+
/^zod(?:\/|$)/,
|
|
307
|
+
...LIBRARY_EXTERNAL_DEPS
|
|
308
|
+
],
|
|
309
|
+
onlyBundle: false
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Published entry packages (cli, keystroke, platform): declare bundled @keystrokehq/*
|
|
314
|
+
* in dependencies (changesets release graph ignores devDependencies). Inlining is
|
|
315
|
+
* driven by alwaysBundle below, not by dep kind. Runtime npm deps stay external.
|
|
316
|
+
*
|
|
317
|
+
* `@keystrokehq/keystroke` uses workspace:>=0.0.0 ranges so private primitive
|
|
318
|
+
* patches do not cascade-publish the facade; CLI/platform keep workspace:*.
|
|
319
|
+
*/
|
|
320
|
+
function bundledEntryDepsConfig() {
|
|
321
|
+
return {
|
|
322
|
+
alwaysBundle: [/^@keystrokehq\//],
|
|
323
|
+
neverBundle: [...BUNDLED_ENTRY_EXTERNAL_DEPS],
|
|
324
|
+
onlyBundle: false
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
[...BETTER_AUTH_CLIENT_BUNDLE_PATTERNS];
|
|
328
|
+
//#endregion
|
|
329
|
+
//#region ../../packages/tsdown-config/index.js
|
|
330
|
+
const baseTsdownConfig = {
|
|
331
|
+
format: ["esm", "cjs"],
|
|
332
|
+
dts: true,
|
|
333
|
+
clean: true,
|
|
334
|
+
sourcemap: true,
|
|
335
|
+
target: "node20",
|
|
336
|
+
deps: libraryBuildDepsConfig()
|
|
337
|
+
};
|
|
338
|
+
bundledEntryDepsConfig();
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region ../../packages/build/dist/index.mjs
|
|
341
|
+
/**
|
|
342
|
+
* Build-time injection of the deterministic structural `callSiteId` into every
|
|
343
|
+
* durable step call in a workflow source file, so the runtime emits
|
|
344
|
+
* `step:<callSiteId>#<occurrence>` correlation ids (and `step:<callSiteId>`
|
|
345
|
+
* credential consumer ids) that line up 1:1 with the canvas graph nodes the
|
|
346
|
+
* deploy-time producer mints from the same source. There is no user-authored
|
|
347
|
+
* step id — identity is entirely internal and injected here.
|
|
348
|
+
*
|
|
349
|
+
* Injection shape by kind (the `callSiteId` is the same id `computeCallSiteIds`
|
|
350
|
+
* returns, which equals the producer's step-node id):
|
|
351
|
+
* - action/sub-workflow `x.run(i)` → `x.run(i).__site("<id>")` (builder method)
|
|
352
|
+
* - agent `a.prompt(i, opts?)` → `__site` merged into the prompt options
|
|
353
|
+
* - llm `promptLlm(i, opts?)` → `__site` merged into the options
|
|
354
|
+
* - hook `ctx.hook(opts?)` → `__site` merged into the options
|
|
355
|
+
* - sleep `ctx.sleep(d, opts?)` → `__site` merged into a second options arg
|
|
356
|
+
*/
|
|
357
|
+
function injectCallSiteIds(code, fileName) {
|
|
358
|
+
const sourceFile = ts.createSourceFile(fileName, code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
359
|
+
const located = locateWorkflow(sourceFile);
|
|
360
|
+
if (!located) return code;
|
|
361
|
+
const ids = computeCallSiteIds(sourceFile);
|
|
362
|
+
if (ids.size === 0) return code;
|
|
363
|
+
const edits = [];
|
|
364
|
+
for (const [node, id] of ids) {
|
|
365
|
+
const info = classifyCall(node, located.ctxParamName);
|
|
366
|
+
if (!info) continue;
|
|
367
|
+
if (info.callKind === "workflow-step") {
|
|
368
|
+
edits.push({
|
|
369
|
+
pos: node.getEnd(),
|
|
370
|
+
text: `.__site(${JSON.stringify(id)})`
|
|
371
|
+
});
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (info.callKind === "agent" || info.callKind === "llm") {
|
|
375
|
+
pushOptionsEdit(edits, node, id);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (info.callKind === "hook") {
|
|
379
|
+
pushHookOptionsEdit(edits, node, id);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
if (info.callKind === "wait") pushSleepOptionsEdit(edits, node, id);
|
|
383
|
+
}
|
|
384
|
+
if (edits.length === 0) return code;
|
|
385
|
+
edits.sort((a, b) => b.pos - a.pos);
|
|
386
|
+
let out = code;
|
|
387
|
+
for (const edit of edits) out = out.slice(0, edit.pos) + edit.text + out.slice(edit.pos);
|
|
388
|
+
return out;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Merge `__site: "<id>"` into a call's options argument (agents / promptLlm).
|
|
392
|
+
* - no 2nd arg → append `, { __site: "id" }`
|
|
393
|
+
* - 2nd arg object literal → insert `__site: "id", ` after its `{`
|
|
394
|
+
* - 2nd arg anything else → wrap as `{ ...orig, __site: "id" }`
|
|
395
|
+
*/
|
|
396
|
+
function pushOptionsEdit(edits, call, id) {
|
|
397
|
+
const literal = JSON.stringify(id);
|
|
398
|
+
const optionsArg = call.arguments[1];
|
|
399
|
+
if (!optionsArg) {
|
|
400
|
+
const firstArg = call.arguments[0];
|
|
401
|
+
const insertAt = firstArg ? firstArg.getEnd() : call.getEnd() - 1;
|
|
402
|
+
edits.push({
|
|
403
|
+
pos: insertAt,
|
|
404
|
+
text: `, { __site: ${literal} }`
|
|
405
|
+
});
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
if (ts.isObjectLiteralExpression(optionsArg)) {
|
|
409
|
+
edits.push({
|
|
410
|
+
pos: optionsArg.getStart() + 1,
|
|
411
|
+
text: ` __site: ${literal},`
|
|
412
|
+
});
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
edits.push({
|
|
416
|
+
pos: optionsArg.getStart(),
|
|
417
|
+
text: "{ ..."
|
|
418
|
+
});
|
|
419
|
+
edits.push({
|
|
420
|
+
pos: optionsArg.getEnd(),
|
|
421
|
+
text: `, __site: ${literal} }`
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
/** Merge `__site` into `ctx.hook(opts?)` — options are the first (only) argument. */
|
|
425
|
+
function pushHookOptionsEdit(edits, call, id) {
|
|
426
|
+
const literal = JSON.stringify(id);
|
|
427
|
+
const optionsArg = call.arguments[0];
|
|
428
|
+
if (!optionsArg) {
|
|
429
|
+
edits.push({
|
|
430
|
+
pos: call.getEnd() - 1,
|
|
431
|
+
text: `{ __site: ${literal} }`
|
|
432
|
+
});
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (ts.isObjectLiteralExpression(optionsArg)) {
|
|
436
|
+
edits.push({
|
|
437
|
+
pos: optionsArg.getStart() + 1,
|
|
438
|
+
text: ` __site: ${literal},`
|
|
439
|
+
});
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
edits.push({
|
|
443
|
+
pos: optionsArg.getStart(),
|
|
444
|
+
text: "{ ..."
|
|
445
|
+
});
|
|
446
|
+
edits.push({
|
|
447
|
+
pos: optionsArg.getEnd(),
|
|
448
|
+
text: `, __site: ${literal} }`
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
/** Append `{ __site }` as the second arg to `ctx.sleep(duration)`. */
|
|
452
|
+
function pushSleepOptionsEdit(edits, call, id) {
|
|
453
|
+
const literal = JSON.stringify(id);
|
|
454
|
+
const durationArg = call.arguments[0];
|
|
455
|
+
const insertAt = durationArg ? durationArg.getEnd() : call.getEnd() - 1;
|
|
456
|
+
edits.push({
|
|
457
|
+
pos: insertAt,
|
|
458
|
+
text: `, { __site: ${literal} }`
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
function toPosix$5(path) {
|
|
462
|
+
return path.split(sep).join("/");
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Rolldown plugin that runs {@link injectCallSiteIds} over workflow source files
|
|
466
|
+
* (and their same-file helpers) before transpile. Scoped to `src/workflows/` so
|
|
467
|
+
* non-workflow modules are untouched; cross-file helpers stay unkeyed by design.
|
|
468
|
+
*/
|
|
469
|
+
function callSiteIdInjectionPlugin(options) {
|
|
470
|
+
const srcDir = options.srcDir ?? "src";
|
|
471
|
+
const workflowsPrefix = toPosix$5(`${options.root}/${srcDir}/workflows/`);
|
|
472
|
+
return {
|
|
473
|
+
name: "keystroke-call-site-ids",
|
|
474
|
+
transform(code, id) {
|
|
475
|
+
const normalized = toPosix$5(id);
|
|
476
|
+
if (!normalized.startsWith(workflowsPrefix) || !normalized.endsWith(".ts")) return null;
|
|
477
|
+
const transformed = injectCallSiteIds(code, normalized);
|
|
478
|
+
return transformed === code ? null : {
|
|
479
|
+
code: transformed,
|
|
480
|
+
map: null
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
const TEST_FILE = /\.(test|int\.test|spec)\.ts$/;
|
|
486
|
+
function toPosix$4(path) {
|
|
487
|
+
return path.split(sep).join("/");
|
|
488
|
+
}
|
|
489
|
+
function collectTsFiles(dir, out) {
|
|
490
|
+
let entries;
|
|
491
|
+
try {
|
|
492
|
+
entries = readdirSync(dir);
|
|
493
|
+
} catch {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
for (const entry of entries) {
|
|
497
|
+
if (entry === "node_modules" || entry.startsWith(".")) continue;
|
|
498
|
+
const full = join(dir, entry);
|
|
499
|
+
if (statSync(full).isDirectory()) {
|
|
500
|
+
collectTsFiles(full, out);
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
if (entry.endsWith(".ts") && !TEST_FILE.test(entry)) out.push(full);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Run the workflow-canvas reliability checks over every `src/**` module. Errors
|
|
508
|
+
* (steps outside a workflow, steps nested as call arguments) block the build;
|
|
509
|
+
* warnings (opaque code-blocks, dropped spread inputs) explain each degraded
|
|
510
|
+
* construct. Best-effort per-file: an unparseable file is skipped, never fatal.
|
|
511
|
+
*/
|
|
512
|
+
function collectWorkflowDiagnostics(root, srcDir = "src") {
|
|
513
|
+
const srcRoot = join(root, srcDir);
|
|
514
|
+
if (!existsSync(srcRoot)) return [];
|
|
515
|
+
const files = [];
|
|
516
|
+
collectTsFiles(srcRoot, files);
|
|
517
|
+
const diagnostics = [];
|
|
518
|
+
for (const file of files) {
|
|
519
|
+
const rel = toPosix$4(relative(root, file));
|
|
520
|
+
try {
|
|
521
|
+
const source = readFileSync(file, "utf8");
|
|
522
|
+
diagnostics.push(...diagnoseWorkflowSource(source, rel));
|
|
523
|
+
} catch {}
|
|
524
|
+
}
|
|
525
|
+
return diagnostics;
|
|
526
|
+
}
|
|
527
|
+
function formatDiagnostic(diagnostic) {
|
|
528
|
+
return ` ${diagnostic.line !== void 0 ? `${diagnostic.fileName}:${diagnostic.line}:${diagnostic.column ?? 1}` : diagnostic.fileName}\n ${diagnostic.message}`;
|
|
529
|
+
}
|
|
530
|
+
/** Throw when any blocking (error-severity) diagnostic is found. */
|
|
531
|
+
function assertWorkflowDiagnosticErrorsFrom(diagnostics) {
|
|
532
|
+
const errors = diagnostics.filter((d) => d.severity === "error");
|
|
533
|
+
if (errors.length > 0) throw new Error(`Workflow canvas: ${errors.length} unsupported construct(s) must be fixed before deploy:\n${errors.map(formatDiagnostic).join("\n")}\n`);
|
|
534
|
+
}
|
|
535
|
+
/** Print soft (warning-severity) diagnostics after a successful build. */
|
|
536
|
+
function warnWorkflowDiagnosticsFrom(diagnostics) {
|
|
537
|
+
const warnings = diagnostics.filter((d) => d.severity === "warning");
|
|
538
|
+
if (warnings.length > 0) console.warn(`\n⚠ Workflow canvas: ${warnings.length} construct(s) will render as a degraded (opaque) canvas:\n${warnings.map(formatDiagnostic).join("\n")}\n`);
|
|
539
|
+
}
|
|
540
|
+
const CACHE_DIR = ".keystroke/cache";
|
|
541
|
+
const CACHE_KEY_FILE = "route-manifest.key";
|
|
542
|
+
const CACHE_MANIFEST_DIR = "manifest";
|
|
543
|
+
function cachePaths(projectRoot) {
|
|
544
|
+
const dir = join(projectRoot, CACHE_DIR);
|
|
545
|
+
return {
|
|
546
|
+
keyPath: join(dir, CACHE_KEY_FILE),
|
|
547
|
+
manifestDir: join(dir, CACHE_MANIFEST_DIR)
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
/** Fingerprint of integration package versions resolved from the project. */
|
|
551
|
+
function integrationPackagesFingerprint(projectRoot) {
|
|
552
|
+
const hash = createHash("sha256");
|
|
553
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
554
|
+
if (!existsSync(packageJsonPath)) return hash.digest("hex");
|
|
555
|
+
const raw = readFileSync(packageJsonPath, "utf8");
|
|
556
|
+
hash.update(raw);
|
|
557
|
+
let dependencies = {};
|
|
558
|
+
try {
|
|
559
|
+
dependencies = JSON.parse(raw).dependencies ?? {};
|
|
560
|
+
} catch {
|
|
561
|
+
return hash.digest("hex");
|
|
562
|
+
}
|
|
563
|
+
const require = createRequire(packageJsonPath);
|
|
564
|
+
for (const name of Object.keys(dependencies).sort()) {
|
|
565
|
+
if (!name.startsWith("@keystrokehq/")) continue;
|
|
566
|
+
try {
|
|
567
|
+
const resolved = require.resolve(`${name}/package.json`);
|
|
568
|
+
hash.update(name);
|
|
569
|
+
hash.update("\0");
|
|
570
|
+
hash.update(readFileSync(resolved, "utf8"));
|
|
571
|
+
hash.update("\0");
|
|
572
|
+
} catch {
|
|
573
|
+
hash.update(name);
|
|
574
|
+
hash.update("\0missing\0");
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return hash.digest("hex");
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Content-addressed key for the deploy route-manifest. Invalidates when project
|
|
581
|
+
* sources, build entries, ignore set, phase, or integration package versions change.
|
|
582
|
+
*/
|
|
583
|
+
function computeRouteManifestCacheKey(input) {
|
|
584
|
+
const hash = createHash("sha256");
|
|
585
|
+
hash.update(input.phase);
|
|
586
|
+
hash.update("\0");
|
|
587
|
+
hash.update(input.integrationFingerprint);
|
|
588
|
+
hash.update("\0");
|
|
589
|
+
for (const key of [...input.buildEntryKeys].sort()) {
|
|
590
|
+
hash.update(key);
|
|
591
|
+
hash.update("\0");
|
|
592
|
+
}
|
|
593
|
+
for (const file of [...input.ignoredFiles].sort()) {
|
|
594
|
+
hash.update(file);
|
|
595
|
+
hash.update("\0");
|
|
596
|
+
}
|
|
597
|
+
for (const file of [...input.sourceFiles].sort((a, b) => a.path.localeCompare(b.path))) {
|
|
598
|
+
hash.update(file.path);
|
|
599
|
+
hash.update("\0");
|
|
600
|
+
hash.update(file.hash);
|
|
601
|
+
hash.update("\0");
|
|
602
|
+
}
|
|
603
|
+
return hash.digest("hex");
|
|
604
|
+
}
|
|
605
|
+
/** True when `.keystroke/cache/route-manifest.key` matches `cacheKey`. */
|
|
606
|
+
function isRouteManifestCacheHit(projectRoot, cacheKey) {
|
|
607
|
+
const { keyPath, manifestDir } = cachePaths(projectRoot);
|
|
608
|
+
if (!existsSync(keyPath) || !existsSync(join(manifestDir, "manifest.json"))) return false;
|
|
609
|
+
return readFileSync(keyPath, "utf8").trim() === cacheKey;
|
|
610
|
+
}
|
|
611
|
+
/** Restore cached modular manifest into `dist/.keystroke/` when the key matches. */
|
|
612
|
+
function tryRestoreCachedRouteManifest(projectRoot, cacheKey) {
|
|
613
|
+
if (!isRouteManifestCacheHit(projectRoot, cacheKey)) return false;
|
|
614
|
+
const { manifestDir } = cachePaths(projectRoot);
|
|
615
|
+
const dest = join(projectRoot, "dist", ".keystroke");
|
|
616
|
+
mkdirSync(dest, { recursive: true });
|
|
617
|
+
for (const name of [
|
|
618
|
+
"manifest.json",
|
|
619
|
+
"modules",
|
|
620
|
+
"catalogs",
|
|
621
|
+
"assets",
|
|
622
|
+
"build-index.json"
|
|
623
|
+
]) {
|
|
624
|
+
const from = join(manifestDir, name);
|
|
625
|
+
if (!existsSync(from)) continue;
|
|
626
|
+
cpSync(from, join(dest, name), { recursive: true });
|
|
627
|
+
}
|
|
628
|
+
const assetsModule = join(manifestDir, "assets.mjs");
|
|
629
|
+
if (existsSync(assetsModule)) cpSync(assetsModule, join(dest, "assets.mjs"));
|
|
630
|
+
return existsSync(join(projectRoot, PROJECT_MANIFEST_REL_PATH));
|
|
631
|
+
}
|
|
632
|
+
/** Persist the just-emitted modular manifest into the deploy cache. */
|
|
633
|
+
function storeCachedRouteManifest(projectRoot, cacheKey) {
|
|
634
|
+
const sourceMeta = join(projectRoot, "dist", ".keystroke");
|
|
635
|
+
if (!existsSync(join(sourceMeta, "manifest.json"))) return;
|
|
636
|
+
const { keyPath, manifestDir } = cachePaths(projectRoot);
|
|
637
|
+
rmSync(manifestDir, {
|
|
638
|
+
recursive: true,
|
|
639
|
+
force: true
|
|
640
|
+
});
|
|
641
|
+
mkdirSync(manifestDir, { recursive: true });
|
|
642
|
+
for (const name of [
|
|
643
|
+
"manifest.json",
|
|
644
|
+
"modules",
|
|
645
|
+
"catalogs",
|
|
646
|
+
"assets",
|
|
647
|
+
"assets.mjs",
|
|
648
|
+
"build-index.json"
|
|
649
|
+
]) {
|
|
650
|
+
const from = join(sourceMeta, name);
|
|
651
|
+
if (!existsSync(from)) continue;
|
|
652
|
+
cpSync(from, join(manifestDir, name), { recursive: true });
|
|
653
|
+
}
|
|
654
|
+
mkdirSync(dirname(keyPath), { recursive: true });
|
|
655
|
+
writeFileSync(keyPath, `${cacheKey}\n`);
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* True when a prior deploy `dist/` still has the compiled outputs for every
|
|
659
|
+
* build entry (so we can skip tsdown when the content-hash cache hits).
|
|
660
|
+
*/
|
|
661
|
+
function distDeployOutputsPresent(projectRoot, buildEntries) {
|
|
662
|
+
const distRoot = join(projectRoot, "dist");
|
|
663
|
+
if (!existsSync(distRoot)) return false;
|
|
664
|
+
for (const entryKey of Object.keys(buildEntries)) if (!existsSync(join(distRoot, entryKey === "config" ? "config.mjs" : `${entryKey}.mjs`))) return false;
|
|
665
|
+
if (!existsSync(join(distRoot, ".keystroke", "assets.mjs"))) return false;
|
|
666
|
+
return true;
|
|
667
|
+
}
|
|
668
|
+
const SKIP_DIRS = new Set([".git", "node_modules"]);
|
|
669
|
+
/** Content hash of every file under a skill/files pack directory. */
|
|
670
|
+
function hashAssetPackDir(dirPath) {
|
|
671
|
+
const hash = createHash("sha256");
|
|
672
|
+
const walk = (current) => {
|
|
673
|
+
for (const name of readdirSync(current).sort()) {
|
|
674
|
+
if (SKIP_DIRS.has(name)) continue;
|
|
675
|
+
const abs = join(current, name);
|
|
676
|
+
const st = statSync(abs);
|
|
677
|
+
if (st.isDirectory()) {
|
|
678
|
+
walk(abs);
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
if (!st.isFile()) continue;
|
|
682
|
+
hash.update(relative(dirPath, abs).replace(/\\/g, "/"));
|
|
683
|
+
hash.update("\0");
|
|
684
|
+
hash.update(readFileSync(abs));
|
|
685
|
+
hash.update("\0");
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
if (!statSync(dirPath, { throwIfNoEntry: false })?.isDirectory()) return hash.digest("hex");
|
|
689
|
+
walk(dirPath);
|
|
690
|
+
return hash.digest("hex");
|
|
691
|
+
}
|
|
692
|
+
function listPackIds(rootPath) {
|
|
693
|
+
if (!statSync(rootPath, { throwIfNoEntry: false })?.isDirectory()) return [];
|
|
694
|
+
return readdirSync(rootPath).filter((name) => !SKIP_DIRS.has(name)).filter((name) => statSync(join(rootPath, name)).isDirectory()).sort();
|
|
695
|
+
}
|
|
696
|
+
function shardRelPath(kind, id) {
|
|
697
|
+
return `${kind}/${id}.json`;
|
|
698
|
+
}
|
|
699
|
+
function readIndex(assetsRoot) {
|
|
700
|
+
const path = join(assetsRoot, "index.json");
|
|
701
|
+
if (!existsSync(path)) return;
|
|
702
|
+
try {
|
|
703
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
704
|
+
} catch {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
function loadShard(assetsRoot, rel) {
|
|
709
|
+
const abs = join(assetsRoot, rel);
|
|
710
|
+
if (!existsSync(abs)) return;
|
|
711
|
+
try {
|
|
712
|
+
return JSON.parse(readFileSync(abs, "utf8"));
|
|
713
|
+
} catch {
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
function renderAssetModule$3(manifest) {
|
|
718
|
+
return `export default ${JSON.stringify(manifest)};\n`;
|
|
719
|
+
}
|
|
720
|
+
function packKind(input) {
|
|
721
|
+
const kindRoot = join(input.projectRoot, input.srcDir, input.kind);
|
|
722
|
+
const prevById = new Map((input.kind === "skills" ? input.previous?.skills : input.previous?.files)?.map((ref) => [ref.id, ref]) ?? []);
|
|
723
|
+
const packs = {};
|
|
724
|
+
const refs = [];
|
|
725
|
+
const wrote = [];
|
|
726
|
+
const skipped = [];
|
|
727
|
+
for (const id of listPackIds(kindRoot)) {
|
|
728
|
+
const packDir = join(kindRoot, id);
|
|
729
|
+
const contentHash = hashAssetPackDir(packDir);
|
|
730
|
+
const path = shardRelPath(input.kind, id);
|
|
731
|
+
const existing = prevById.get(id)?.contentHash === contentHash ? loadShard(input.assetsRoot, path) : void 0;
|
|
732
|
+
let packed;
|
|
733
|
+
if (existing) {
|
|
734
|
+
packed = existing;
|
|
735
|
+
skipped.push(path);
|
|
736
|
+
} else {
|
|
737
|
+
packed = packDirFromDisk(packDir);
|
|
738
|
+
const abs = join(input.assetsRoot, path);
|
|
739
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
740
|
+
writeFileSync(abs, `${JSON.stringify(packed)}\n`);
|
|
741
|
+
wrote.push(path);
|
|
742
|
+
}
|
|
743
|
+
packs[id] = packed;
|
|
744
|
+
refs.push({
|
|
745
|
+
id,
|
|
746
|
+
path,
|
|
747
|
+
contentHash
|
|
748
|
+
});
|
|
749
|
+
}
|
|
750
|
+
return {
|
|
751
|
+
packs,
|
|
752
|
+
refs,
|
|
753
|
+
wrote,
|
|
754
|
+
skipped
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
function removeOrphanShards(assetsRoot, kind, keep) {
|
|
758
|
+
const kindDir = join(assetsRoot, kind);
|
|
759
|
+
if (!existsSync(kindDir)) return;
|
|
760
|
+
for (const name of readdirSync(kindDir)) {
|
|
761
|
+
if (!name.endsWith(".json")) continue;
|
|
762
|
+
const rel = `${kind}/${name}`;
|
|
763
|
+
if (!keep.has(rel)) rmSync(join(kindDir, name), { force: true });
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Pack `src/skills` + `src/files` into per-pack shards with content-hash skip.
|
|
768
|
+
* Composes `dist/.keystroke/assets.mjs` only when at least one shard changed
|
|
769
|
+
* (or the composed module is missing) so the runtime loader stays unchanged.
|
|
770
|
+
*/
|
|
771
|
+
function packModularProjectAssets(input) {
|
|
772
|
+
const srcDir = input.srcDir ?? "src";
|
|
773
|
+
const outDir = input.outDir ?? "dist";
|
|
774
|
+
const keystrokeDir = join(input.projectRoot, outDir, ".keystroke");
|
|
775
|
+
const assetsRoot = join(keystrokeDir, "assets");
|
|
776
|
+
mkdirSync(assetsRoot, { recursive: true });
|
|
777
|
+
const previous = readIndex(assetsRoot);
|
|
778
|
+
const skills = packKind({
|
|
779
|
+
projectRoot: input.projectRoot,
|
|
780
|
+
srcDir,
|
|
781
|
+
kind: "skills",
|
|
782
|
+
assetsRoot,
|
|
783
|
+
previous
|
|
784
|
+
});
|
|
785
|
+
const files = packKind({
|
|
786
|
+
projectRoot: input.projectRoot,
|
|
787
|
+
srcDir,
|
|
788
|
+
kind: "files",
|
|
789
|
+
assetsRoot,
|
|
790
|
+
previous
|
|
791
|
+
});
|
|
792
|
+
removeOrphanShards(assetsRoot, "skills", new Set(skills.refs.map((ref) => ref.path)));
|
|
793
|
+
removeOrphanShards(assetsRoot, "files", new Set(files.refs.map((ref) => ref.path)));
|
|
794
|
+
const index = {
|
|
795
|
+
version: 1,
|
|
796
|
+
skills: skills.refs,
|
|
797
|
+
files: files.refs
|
|
798
|
+
};
|
|
799
|
+
writeFileSync(join(assetsRoot, "index.json"), `${JSON.stringify(index)}\n`);
|
|
800
|
+
const manifest = {
|
|
801
|
+
skills: skills.packs,
|
|
802
|
+
files: files.packs
|
|
803
|
+
};
|
|
804
|
+
const assetsModulePath = join(keystrokeDir, "assets.mjs");
|
|
805
|
+
const wroteShards = [...skills.wrote, ...files.wrote];
|
|
806
|
+
const skippedShards = [...skills.skipped, ...files.skipped];
|
|
807
|
+
const prevSkillIds = (previous?.skills ?? []).map((r) => r.id).join("\0");
|
|
808
|
+
const nextSkillIds = skills.refs.map((r) => r.id).join("\0");
|
|
809
|
+
const prevFileIds = (previous?.files ?? []).map((r) => r.id).join("\0");
|
|
810
|
+
const nextFileIds = files.refs.map((r) => r.id).join("\0");
|
|
811
|
+
const membershipChanged = prevSkillIds !== nextSkillIds || prevFileIds !== nextFileIds;
|
|
812
|
+
const anyDirty = wroteShards.length > 0 || !existsSync(assetsModulePath) || membershipChanged;
|
|
813
|
+
let wroteAssetsModule = false;
|
|
814
|
+
if (anyDirty) {
|
|
815
|
+
writeFileSync(assetsModulePath, renderAssetModule$3(manifest));
|
|
816
|
+
wroteAssetsModule = true;
|
|
817
|
+
}
|
|
818
|
+
return {
|
|
819
|
+
manifest,
|
|
820
|
+
index,
|
|
821
|
+
wroteShards,
|
|
822
|
+
skippedShards,
|
|
823
|
+
wroteAssetsModule
|
|
824
|
+
};
|
|
825
|
+
}
|
|
826
|
+
/** Relative path under project root. */
|
|
827
|
+
const BUILD_INDEX_REL_PATH = "dist/.keystroke/build-index.json";
|
|
828
|
+
function toPosix$3(path) {
|
|
829
|
+
return path.replace(/\\/g, "/");
|
|
830
|
+
}
|
|
831
|
+
function sha256Bytes(data) {
|
|
832
|
+
return createHash("sha256").update(data).digest("hex");
|
|
833
|
+
}
|
|
834
|
+
function compactJson(value) {
|
|
835
|
+
return `${JSON.stringify(value)}\n`;
|
|
836
|
+
}
|
|
837
|
+
/** sha256 over sorted (path, file bytes) for project-relative inputs. */
|
|
838
|
+
function hashSourceInputs(projectRoot, inputs) {
|
|
839
|
+
const hash = createHash("sha256");
|
|
840
|
+
for (const rel of [...inputs].sort()) {
|
|
841
|
+
hash.update(rel);
|
|
842
|
+
hash.update("\0");
|
|
843
|
+
const abs = join(projectRoot, rel);
|
|
844
|
+
if (existsSync(abs) && statSync(abs).isFile()) hash.update(readFileSync(abs));
|
|
845
|
+
else hash.update("missing");
|
|
846
|
+
hash.update("\0");
|
|
847
|
+
}
|
|
848
|
+
return hash.digest("hex");
|
|
849
|
+
}
|
|
850
|
+
/** Fingerprint bundled package.json files (version bumps dirty consumers). */
|
|
851
|
+
function hashBundledPackages(projectRoot, packages) {
|
|
852
|
+
const hash = createHash("sha256");
|
|
853
|
+
const packageJsonPath = join(projectRoot, "package.json");
|
|
854
|
+
if (!existsSync(packageJsonPath)) return hash.digest("hex");
|
|
855
|
+
const require = createRequire(packageJsonPath);
|
|
856
|
+
for (const name of [...packages].sort()) {
|
|
857
|
+
hash.update(name);
|
|
858
|
+
hash.update("\0");
|
|
859
|
+
try {
|
|
860
|
+
const resolved = require.resolve(`${name}/package.json`);
|
|
861
|
+
hash.update(readFileSync(resolved, "utf8"));
|
|
862
|
+
} catch {
|
|
863
|
+
hash.update("missing");
|
|
864
|
+
}
|
|
865
|
+
hash.update("\0");
|
|
866
|
+
}
|
|
867
|
+
return hash.digest("hex");
|
|
868
|
+
}
|
|
869
|
+
function buildIndexPath(projectRoot) {
|
|
870
|
+
return join(projectRoot, BUILD_INDEX_REL_PATH);
|
|
871
|
+
}
|
|
872
|
+
function tryReadBuildIndex(projectRoot) {
|
|
873
|
+
const path = buildIndexPath(projectRoot);
|
|
874
|
+
if (!existsSync(path)) return;
|
|
875
|
+
try {
|
|
876
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
877
|
+
if (parsed.version !== 1 || typeof parsed.entries !== "object") return;
|
|
878
|
+
return parsed;
|
|
879
|
+
} catch {
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
function writeBuildIndex(projectRoot, index) {
|
|
884
|
+
const path = buildIndexPath(projectRoot);
|
|
885
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
886
|
+
writeFileSync(path, compactJson(index));
|
|
887
|
+
}
|
|
888
|
+
/**
|
|
889
|
+
* Compare on-disk build-index to current sources / bundled package fingerprints.
|
|
890
|
+
* Does not run the bundler.
|
|
891
|
+
*/
|
|
892
|
+
function listDirtyEntries(projectRoot, currentEntryKeys) {
|
|
893
|
+
const index = tryReadBuildIndex(projectRoot);
|
|
894
|
+
const expected = [...currentEntryKeys].filter((key) => key !== "config").sort();
|
|
895
|
+
if (!index) return {
|
|
896
|
+
dirty: expected,
|
|
897
|
+
forceFull: true,
|
|
898
|
+
reason: "missing build-index"
|
|
899
|
+
};
|
|
900
|
+
const indexed = Object.keys(index.entries).sort();
|
|
901
|
+
if (indexed.length !== expected.length || indexed.some((key, i) => key !== expected[i])) return {
|
|
902
|
+
dirty: expected,
|
|
903
|
+
forceFull: true,
|
|
904
|
+
reason: "entry membership changed"
|
|
905
|
+
};
|
|
906
|
+
const dirty = [];
|
|
907
|
+
for (const key of expected) {
|
|
908
|
+
const entry = index.entries[key];
|
|
909
|
+
if (!existsSync(join(projectRoot, "dist", entry.artifactPath))) {
|
|
910
|
+
dirty.push(key);
|
|
911
|
+
continue;
|
|
912
|
+
}
|
|
913
|
+
if (hashSourceInputs(projectRoot, entry.inputs) !== entry.sourceHash) {
|
|
914
|
+
dirty.push(key);
|
|
915
|
+
continue;
|
|
916
|
+
}
|
|
917
|
+
if (hashBundledPackages(projectRoot, entry.packages) !== entry.packagesHash) dirty.push(key);
|
|
918
|
+
}
|
|
919
|
+
return {
|
|
920
|
+
dirty,
|
|
921
|
+
forceFull: false
|
|
922
|
+
};
|
|
923
|
+
}
|
|
924
|
+
/** Default manifest shard paths for an entry key (best-effort). */
|
|
925
|
+
function defaultManifestPathsForEntry(entryKey) {
|
|
926
|
+
const [kind, ...rest] = entryKey.split("/");
|
|
927
|
+
const id = rest.join("/");
|
|
928
|
+
if (!kind || !id) return [];
|
|
929
|
+
if (kind === "agents") return [`modules/agents/${id}.json`];
|
|
930
|
+
if (kind === "workflows") return [`modules/workflows/${id}.json`, `modules/workflows/${id}.flow.json`];
|
|
931
|
+
if (kind === "triggers") return [`modules/triggers/${id}.json`];
|
|
932
|
+
if (kind === "actions") return ["catalogs/actions.json"];
|
|
933
|
+
return [];
|
|
934
|
+
}
|
|
935
|
+
/** Build a BuildIndexEntry from closure facts + emitted artifact bytes. */
|
|
936
|
+
function makeBuildIndexEntry(input) {
|
|
937
|
+
return {
|
|
938
|
+
sourceHash: hashSourceInputs(input.projectRoot, input.inputs),
|
|
939
|
+
artifactHash: sha256Bytes(input.artifactBytes),
|
|
940
|
+
artifactPath: toPosix$3(input.artifactPath),
|
|
941
|
+
inputs: [...input.inputs].sort(),
|
|
942
|
+
packages: [...input.packages].sort(),
|
|
943
|
+
packagesHash: hashBundledPackages(input.projectRoot, input.packages),
|
|
944
|
+
manifestPaths: defaultManifestPathsForEntry(input.entryKey)
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
function toPosix$2(path) {
|
|
948
|
+
return path.split(sep).join("/");
|
|
949
|
+
}
|
|
950
|
+
function projectRelativeModuleId$1(root, id) {
|
|
951
|
+
if (id.startsWith("\0") || id.includes("\0") || id.startsWith("node:")) return null;
|
|
952
|
+
let absolute = id;
|
|
953
|
+
if (id.startsWith("file://")) absolute = fileURLToPath(id);
|
|
954
|
+
if (!isAbsolute(absolute) && !absolute.startsWith(".")) return null;
|
|
955
|
+
const normalizedRoot = normalizeBuildFilePath(root);
|
|
956
|
+
let normalizedAbsolute;
|
|
957
|
+
try {
|
|
958
|
+
normalizedAbsolute = normalizeBuildFilePath(absolute);
|
|
959
|
+
} catch {
|
|
960
|
+
return null;
|
|
961
|
+
}
|
|
962
|
+
const relativePath = toPosix$2(relative(normalizedRoot, normalizedAbsolute));
|
|
963
|
+
if (!relativePath || relativePath.startsWith("..") || relativePath.includes("node_modules/") || relativePath.startsWith("node_modules/")) return null;
|
|
964
|
+
return relativePath;
|
|
965
|
+
}
|
|
966
|
+
function packageNameFromModuleId$1(id) {
|
|
967
|
+
if (id.startsWith("\0") || id.includes("\0") || id.startsWith("node:")) return null;
|
|
968
|
+
let absolute = id;
|
|
969
|
+
try {
|
|
970
|
+
if (id.startsWith("file://")) absolute = fileURLToPath(id);
|
|
971
|
+
} catch {
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
const normalized = absolute.replace(/\\/g, "/");
|
|
975
|
+
const index = normalized.lastIndexOf("/node_modules/");
|
|
976
|
+
if (index < 0) return null;
|
|
977
|
+
const rest = normalized.slice(index + 14);
|
|
978
|
+
if (!rest || rest.startsWith(".")) return null;
|
|
979
|
+
if (rest.startsWith("@")) {
|
|
980
|
+
const parts = rest.split("/");
|
|
981
|
+
if (parts.length < 2 || !parts[0] || !parts[1]) return null;
|
|
982
|
+
return `${parts[0]}/${parts[1]}`;
|
|
983
|
+
}
|
|
984
|
+
return rest.split("/")[0] || null;
|
|
985
|
+
}
|
|
986
|
+
function isFrameworkExternalPackage(name) {
|
|
987
|
+
if (!name.startsWith("@keystrokehq/")) return false;
|
|
988
|
+
const pkg = name.slice(13).split("/")[0] ?? "";
|
|
989
|
+
return RUNTIME_KEYSTROKE_PACKAGES.has(pkg);
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Collect per-entry source inputs + bundled packages from the emit graph.
|
|
993
|
+
* Used to write `build-index.json` after tsdown.
|
|
994
|
+
*/
|
|
995
|
+
function entryClosurePlugin(options) {
|
|
996
|
+
const root = normalizeBuildFilePath(options.root);
|
|
997
|
+
return {
|
|
998
|
+
name: "keystroke-entry-closure",
|
|
999
|
+
generateBundle(_outputOptions, bundle) {
|
|
1000
|
+
const closures = /* @__PURE__ */ new Map();
|
|
1001
|
+
for (const output of Object.values(bundle)) {
|
|
1002
|
+
if (output.type !== "chunk" || !output.isEntry) continue;
|
|
1003
|
+
const entryKey = output.name.replace(/\\/g, "/");
|
|
1004
|
+
if (entryKey === "config" || entryKey.includes("\0")) continue;
|
|
1005
|
+
if (!entryKey.startsWith("agents/") && !entryKey.startsWith("actions/") && !entryKey.startsWith("workflows/") && !entryKey.startsWith("triggers/")) continue;
|
|
1006
|
+
const inputs = /* @__PURE__ */ new Set();
|
|
1007
|
+
const packages = /* @__PURE__ */ new Set();
|
|
1008
|
+
const moduleIds = "moduleIds" in output && Array.isArray(output.moduleIds) ? output.moduleIds : Object.keys(output.modules ?? {});
|
|
1009
|
+
for (const moduleId of moduleIds) {
|
|
1010
|
+
const rel = projectRelativeModuleId$1(root, moduleId);
|
|
1011
|
+
if (rel) inputs.add(rel);
|
|
1012
|
+
const pkg = packageNameFromModuleId$1(moduleId);
|
|
1013
|
+
if (pkg && !isFrameworkExternalPackage(pkg) && pkg !== "zod") packages.add(pkg);
|
|
1014
|
+
}
|
|
1015
|
+
if (output.facadeModuleId) {
|
|
1016
|
+
const entryRel = projectRelativeModuleId$1(root, output.facadeModuleId);
|
|
1017
|
+
if (entryRel) inputs.add(entryRel);
|
|
1018
|
+
}
|
|
1019
|
+
closures.set(entryKey, {
|
|
1020
|
+
inputs: [...inputs].sort(),
|
|
1021
|
+
packages: [...packages].sort()
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
options.onClosures(closures);
|
|
1025
|
+
}
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
const ASSETS_MODULE$2 = "@keystrokehq/assets";
|
|
1029
|
+
const VIRTUAL_PREFIX$1 = `\0${ASSETS_MODULE$2}:`;
|
|
1030
|
+
function renderAssetModule$2(manifest) {
|
|
1031
|
+
return `export default ${JSON.stringify(manifest)};\n`;
|
|
1032
|
+
}
|
|
1033
|
+
/** Resolve `@keystrokehq/assets` without re-packing on every entry. */
|
|
1034
|
+
function agentAssetsResolvePlugin(manifest) {
|
|
1035
|
+
return {
|
|
1036
|
+
name: "keystroke-assets-resolve",
|
|
1037
|
+
resolveId(source) {
|
|
1038
|
+
if (source !== ASSETS_MODULE$2) return null;
|
|
1039
|
+
return `${VIRTUAL_PREFIX$1}manifest`;
|
|
1040
|
+
},
|
|
1041
|
+
load(id) {
|
|
1042
|
+
if (id !== `${VIRTUAL_PREFIX$1}manifest`) return null;
|
|
1043
|
+
return renderAssetModule$2(manifest);
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Build each entry in isolation so local imports are inlined (self-contained
|
|
1049
|
+
* `.mjs`) and closures include transitive project sources — same contract as
|
|
1050
|
+
* `buildFilteredApp`.
|
|
1051
|
+
*/
|
|
1052
|
+
async function buildSelfContainedEntries(options) {
|
|
1053
|
+
const root = options.root;
|
|
1054
|
+
const srcDir = options.srcDir ?? "src";
|
|
1055
|
+
const outDir = options.outDir ?? "dist";
|
|
1056
|
+
const phase = options.phase ?? "build";
|
|
1057
|
+
const distRoot = join(root, outDir);
|
|
1058
|
+
const clean = options.clean ?? true;
|
|
1059
|
+
const allEntries = {
|
|
1060
|
+
...options.configEntry ? { config: options.configEntry } : {},
|
|
1061
|
+
...options.buildEntries
|
|
1062
|
+
};
|
|
1063
|
+
const entryKeys = Object.keys(allEntries).sort();
|
|
1064
|
+
if (entryKeys.length === 0) throw new Error("Nothing to build — add keystroke.config.ts or modules under src/");
|
|
1065
|
+
const dirtySet = new Set(options.dirtyKeys && options.dirtyKeys.length > 0 ? options.dirtyKeys : entryKeys.filter((key) => key !== "config").concat(options.configEntry ? ["config"] : []));
|
|
1066
|
+
if (options.configEntry && (clean || dirtySet.size > 0)) dirtySet.add("config");
|
|
1067
|
+
if (clean && existsSync(distRoot)) rmSync(distRoot, {
|
|
1068
|
+
recursive: true,
|
|
1069
|
+
force: true
|
|
1070
|
+
});
|
|
1071
|
+
const packed = packModularProjectAssets({
|
|
1072
|
+
projectRoot: root,
|
|
1073
|
+
srcDir,
|
|
1074
|
+
outDir
|
|
1075
|
+
});
|
|
1076
|
+
const closures = /* @__PURE__ */ new Map();
|
|
1077
|
+
const previous = tryReadBuildIndex(root);
|
|
1078
|
+
for (const entryKey of [...dirtySet].sort()) {
|
|
1079
|
+
const entryPath = allEntries[entryKey];
|
|
1080
|
+
if (!entryPath) continue;
|
|
1081
|
+
const plugins = [
|
|
1082
|
+
keystrokeIgnoreGuardPlugin({
|
|
1083
|
+
root,
|
|
1084
|
+
ignoredFiles: options.ignoredFiles
|
|
1085
|
+
}),
|
|
1086
|
+
agentAssetsResolvePlugin(packed.manifest),
|
|
1087
|
+
entryClosurePlugin({
|
|
1088
|
+
root,
|
|
1089
|
+
onClosures: (next) => {
|
|
1090
|
+
for (const [key, value] of next) closures.set(key, value);
|
|
1091
|
+
}
|
|
1092
|
+
})
|
|
1093
|
+
];
|
|
1094
|
+
if (entryKey.startsWith("workflows/")) plugins.splice(1, 0, callSiteIdInjectionPlugin({
|
|
1095
|
+
root,
|
|
1096
|
+
srcDir
|
|
1097
|
+
}));
|
|
1098
|
+
await build(mergeConfig(baseTsdownConfig, {
|
|
1099
|
+
entry: { [entryKey]: entryPath },
|
|
1100
|
+
format: ["esm"],
|
|
1101
|
+
outDir: distRoot,
|
|
1102
|
+
clean: false,
|
|
1103
|
+
dts: false,
|
|
1104
|
+
sourcemap: phase === "deploy" ? false : true,
|
|
1105
|
+
...phase === "deploy" ? { logLevel: "error" } : {},
|
|
1106
|
+
loader: { ".md": "text" },
|
|
1107
|
+
deps: userAppBuildDepsConfig(),
|
|
1108
|
+
plugins
|
|
1109
|
+
}));
|
|
1110
|
+
}
|
|
1111
|
+
const entries = {};
|
|
1112
|
+
for (const key of Object.keys(options.buildEntries).sort()) {
|
|
1113
|
+
if (closures.has(key)) {
|
|
1114
|
+
const closure = closures.get(key);
|
|
1115
|
+
const artifactPath = `${key}.mjs`;
|
|
1116
|
+
const abs = join(distRoot, artifactPath);
|
|
1117
|
+
if (!existsSync(abs)) continue;
|
|
1118
|
+
entries[key] = makeBuildIndexEntry({
|
|
1119
|
+
projectRoot: root,
|
|
1120
|
+
entryKey: key,
|
|
1121
|
+
inputs: closure.inputs,
|
|
1122
|
+
packages: closure.packages,
|
|
1123
|
+
artifactPath,
|
|
1124
|
+
artifactBytes: readFileSync(abs)
|
|
1125
|
+
});
|
|
1126
|
+
continue;
|
|
1127
|
+
}
|
|
1128
|
+
if (previous?.entries[key]) entries[key] = previous.entries[key];
|
|
1129
|
+
}
|
|
1130
|
+
const index = {
|
|
1131
|
+
version: 1,
|
|
1132
|
+
integrationFingerprint: integrationPackagesFingerprint(root),
|
|
1133
|
+
entries
|
|
1134
|
+
};
|
|
1135
|
+
writeBuildIndex(root, index);
|
|
1136
|
+
return {
|
|
1137
|
+
closures,
|
|
1138
|
+
index
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
/** Analyzer stdout / plan digest contract version for Modal Precise Deploy. */
|
|
1142
|
+
const ANALYZER_CONTRACT_VERSION = "safe-filtered-deploy-v3";
|
|
1143
|
+
const PACKAGE_MANIFEST_FILES = new Set(["package.json"]);
|
|
1144
|
+
const LOCKFILE_NAMES = new Set([
|
|
1145
|
+
"package-lock.json",
|
|
1146
|
+
"pnpm-lock.yaml",
|
|
1147
|
+
"yarn.lock",
|
|
1148
|
+
"bun.lock",
|
|
1149
|
+
"bun.lockb",
|
|
1150
|
+
"npm-shrinkwrap.json"
|
|
1151
|
+
]);
|
|
1152
|
+
function toPosix(path) {
|
|
1153
|
+
return path.split(sep).join("/");
|
|
1154
|
+
}
|
|
1155
|
+
function requireFull(code, reason) {
|
|
1156
|
+
return {
|
|
1157
|
+
requireFull: true,
|
|
1158
|
+
code,
|
|
1159
|
+
reason
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
function isDeployableResourceKey(key) {
|
|
1163
|
+
return key.startsWith("agents/") || key.startsWith("workflows/");
|
|
1164
|
+
}
|
|
1165
|
+
function kindFromResourceKey(key) {
|
|
1166
|
+
if (key.startsWith("agents/")) return "agent";
|
|
1167
|
+
if (key.startsWith("workflows/")) return "workflow";
|
|
1168
|
+
return null;
|
|
1169
|
+
}
|
|
1170
|
+
/** Normalize caller paths to project-relative posix form. */
|
|
1171
|
+
function normalizeProjectRelativePath(root, path) {
|
|
1172
|
+
const withoutDot = path.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
1173
|
+
const absolute = isAbsolute(withoutDot) ? withoutDot : join(normalizeBuildFilePath(root), withoutDot);
|
|
1174
|
+
const relativePath = toPosix(relative(normalizeBuildFilePath(root), normalizeBuildFilePath(absolute)));
|
|
1175
|
+
if (!relativePath || relativePath === "." || relativePath.startsWith("..")) throw new Error(`Path "${path}" is outside project root`);
|
|
1176
|
+
return relativePath;
|
|
1177
|
+
}
|
|
1178
|
+
function basenamePosix(path) {
|
|
1179
|
+
const parts = path.split("/");
|
|
1180
|
+
return parts[parts.length - 1] ?? path;
|
|
1181
|
+
}
|
|
1182
|
+
function isPackageManifestPath(path) {
|
|
1183
|
+
return PACKAGE_MANIFEST_FILES.has(basenamePosix(path));
|
|
1184
|
+
}
|
|
1185
|
+
function isLockfilePath(path) {
|
|
1186
|
+
return LOCKFILE_NAMES.has(basenamePosix(path));
|
|
1187
|
+
}
|
|
1188
|
+
function normalizeChanges(root, changedPaths) {
|
|
1189
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
1190
|
+
for (const change of changedPaths) {
|
|
1191
|
+
const raw = typeof change === "string" ? {
|
|
1192
|
+
path: change,
|
|
1193
|
+
kind: "modified"
|
|
1194
|
+
} : change;
|
|
1195
|
+
let path;
|
|
1196
|
+
try {
|
|
1197
|
+
path = normalizeProjectRelativePath(root, raw.path);
|
|
1198
|
+
} catch {
|
|
1199
|
+
return requireFull("unproven_safe", `Changed path "${raw.path}" cannot be normalized to a project-relative source path`);
|
|
1200
|
+
}
|
|
1201
|
+
let previousPath;
|
|
1202
|
+
if (raw.previousPath) try {
|
|
1203
|
+
previousPath = normalizeProjectRelativePath(root, raw.previousPath);
|
|
1204
|
+
} catch {
|
|
1205
|
+
return requireFull("unproven_safe", `Rename previous path "${raw.previousPath}" cannot be normalized`);
|
|
1206
|
+
}
|
|
1207
|
+
const normalized = {
|
|
1208
|
+
path,
|
|
1209
|
+
kind: raw.kind,
|
|
1210
|
+
...previousPath ? { previousPath } : {}
|
|
1211
|
+
};
|
|
1212
|
+
const existing = byPath.get(path);
|
|
1213
|
+
if (existing && existing.kind !== normalized.kind) return requireFull("ambiguous_ownership", `Changed path "${path}" has ambiguous change kinds (${existing.kind} vs ${normalized.kind})`);
|
|
1214
|
+
byPath.set(path, normalized);
|
|
1215
|
+
}
|
|
1216
|
+
return [...byPath.values()];
|
|
1217
|
+
}
|
|
1218
|
+
function projectRelativeModuleId(root, id) {
|
|
1219
|
+
if (id.startsWith("\0") || id.includes("\0") || id.startsWith("node:")) return null;
|
|
1220
|
+
let absolute = id;
|
|
1221
|
+
if (id.startsWith("file://")) absolute = fileURLToPath(id);
|
|
1222
|
+
if (!isAbsolute(absolute) && !absolute.startsWith(".")) return null;
|
|
1223
|
+
const normalizedRoot = normalizeBuildFilePath(root);
|
|
1224
|
+
let normalizedAbsolute;
|
|
1225
|
+
try {
|
|
1226
|
+
normalizedAbsolute = normalizeBuildFilePath(absolute);
|
|
1227
|
+
} catch {
|
|
1228
|
+
return null;
|
|
1229
|
+
}
|
|
1230
|
+
if (!normalizedAbsolute.startsWith(normalizedRoot + sep) && normalizedAbsolute !== normalizedRoot) {
|
|
1231
|
+
const rel = toPosix(relative(normalizedRoot, normalizedAbsolute));
|
|
1232
|
+
if (!rel || rel.startsWith("..") || rel.includes("node_modules/")) return null;
|
|
1233
|
+
}
|
|
1234
|
+
const relativePath = toPosix(relative(normalizedRoot, normalizedAbsolute));
|
|
1235
|
+
if (!relativePath || relativePath.startsWith("..") || relativePath.includes("node_modules/") || relativePath.startsWith("node_modules/")) return null;
|
|
1236
|
+
return relativePath;
|
|
1237
|
+
}
|
|
1238
|
+
/** Extract a package name from a resolved module id under node_modules. */
|
|
1239
|
+
function packageNameFromModuleId(id) {
|
|
1240
|
+
if (id.startsWith("\0") || id.includes("\0") || id.startsWith("node:")) return null;
|
|
1241
|
+
let absolute = id;
|
|
1242
|
+
try {
|
|
1243
|
+
if (id.startsWith("file://")) absolute = fileURLToPath(id);
|
|
1244
|
+
} catch {
|
|
1245
|
+
return null;
|
|
1246
|
+
}
|
|
1247
|
+
const normalized = absolute.replace(/\\/g, "/");
|
|
1248
|
+
const index = normalized.lastIndexOf("/node_modules/");
|
|
1249
|
+
if (index < 0) return null;
|
|
1250
|
+
const rest = normalized.slice(index + 14);
|
|
1251
|
+
if (!rest || rest.startsWith(".")) return null;
|
|
1252
|
+
if (rest.startsWith("@")) {
|
|
1253
|
+
const parts = rest.split("/");
|
|
1254
|
+
if (parts.length < 2 || !parts[0] || !parts[1]) return null;
|
|
1255
|
+
return `${parts[0]}/${parts[1]}`;
|
|
1256
|
+
}
|
|
1257
|
+
return rest.split("/")[0] || null;
|
|
1258
|
+
}
|
|
1259
|
+
function isRuntimeExternalPackage(name) {
|
|
1260
|
+
if (!name.startsWith("@keystrokehq/")) return false;
|
|
1261
|
+
const pkg = name.slice(13).split("/")[0] ?? "";
|
|
1262
|
+
return RUNTIME_KEYSTROKE_PACKAGES.has(pkg);
|
|
1263
|
+
}
|
|
1264
|
+
function readDependencyMap(packageJsonContents) {
|
|
1265
|
+
try {
|
|
1266
|
+
const parsed = JSON.parse(packageJsonContents);
|
|
1267
|
+
const map = /* @__PURE__ */ new Map();
|
|
1268
|
+
for (const field of [
|
|
1269
|
+
"dependencies",
|
|
1270
|
+
"devDependencies",
|
|
1271
|
+
"optionalDependencies",
|
|
1272
|
+
"peerDependencies"
|
|
1273
|
+
]) {
|
|
1274
|
+
const value = parsed[field];
|
|
1275
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) continue;
|
|
1276
|
+
for (const [name, version] of Object.entries(value)) if (typeof version === "string") map.set(name, version);
|
|
1277
|
+
}
|
|
1278
|
+
return map;
|
|
1279
|
+
} catch {
|
|
1280
|
+
return null;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
function changedDependencyNames(base, draft) {
|
|
1284
|
+
const names = new Set([...base.keys(), ...draft.keys()]);
|
|
1285
|
+
const changed = [];
|
|
1286
|
+
for (const name of names) if ((base.get(name) ?? null) !== (draft.get(name) ?? null)) changed.push(name);
|
|
1287
|
+
return changed.sort();
|
|
1288
|
+
}
|
|
1289
|
+
function collectModuleGraphPlugin(graph) {
|
|
1290
|
+
return {
|
|
1291
|
+
name: "keystroke-collect-module-graph",
|
|
1292
|
+
async resolveId(source, importer, resolveOptions) {
|
|
1293
|
+
if (!importer || resolveOptions?.isEntry) return null;
|
|
1294
|
+
if (!await this.resolve(source, importer, {
|
|
1295
|
+
...resolveOptions,
|
|
1296
|
+
skipSelf: true
|
|
1297
|
+
})) {
|
|
1298
|
+
const existing = graph.unresolvedByImporter.get(importer) ?? [];
|
|
1299
|
+
existing.push(source);
|
|
1300
|
+
graph.unresolvedByImporter.set(importer, existing);
|
|
1301
|
+
}
|
|
1302
|
+
return null;
|
|
1303
|
+
},
|
|
1304
|
+
buildEnd() {
|
|
1305
|
+
for (const id of this.getModuleIds()) {
|
|
1306
|
+
const info = this.getModuleInfo(id);
|
|
1307
|
+
if (!info) continue;
|
|
1308
|
+
graph.byId.set(id, {
|
|
1309
|
+
importedIds: [...info.importedIds],
|
|
1310
|
+
dynamicallyImportedIds: [...info.dynamicallyImportedIds]
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
function closureFromGraph(root, resourceKey, entryAbsolute, graph) {
|
|
1317
|
+
const kind = kindFromResourceKey(resourceKey);
|
|
1318
|
+
if (!kind) throw new Error(`Not a deployable workflow/agent key: ${resourceKey}`);
|
|
1319
|
+
const entryNormalized = normalizeBuildFilePath(entryAbsolute);
|
|
1320
|
+
const queue = [];
|
|
1321
|
+
for (const id of graph.byId.keys()) try {
|
|
1322
|
+
if (normalizeBuildFilePath(id.startsWith("file://") ? fileURLToPath(id) : id) === entryNormalized) {
|
|
1323
|
+
queue.push(id);
|
|
1324
|
+
break;
|
|
1325
|
+
}
|
|
1326
|
+
} catch {}
|
|
1327
|
+
if (queue.length === 0) {
|
|
1328
|
+
const entryRel = toPosix(relative(normalizeBuildFilePath(root), entryNormalized));
|
|
1329
|
+
for (const id of graph.byId.keys()) if (projectRelativeModuleId(root, id) === entryRel) {
|
|
1330
|
+
queue.push(id);
|
|
1331
|
+
break;
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1335
|
+
const inputs = /* @__PURE__ */ new Set();
|
|
1336
|
+
const packages = /* @__PURE__ */ new Set();
|
|
1337
|
+
let hasDynamicImports = false;
|
|
1338
|
+
const unresolvedImports = /* @__PURE__ */ new Set();
|
|
1339
|
+
while (queue.length > 0) {
|
|
1340
|
+
const id = queue.pop();
|
|
1341
|
+
if (seen.has(id)) continue;
|
|
1342
|
+
seen.add(id);
|
|
1343
|
+
const relativePath = projectRelativeModuleId(root, id);
|
|
1344
|
+
if (relativePath) inputs.add(relativePath);
|
|
1345
|
+
const packageName = packageNameFromModuleId(id);
|
|
1346
|
+
if (packageName && !isRuntimeExternalPackage(packageName)) packages.add(packageName);
|
|
1347
|
+
for (const unresolved of graph.unresolvedByImporter.get(id) ?? []) unresolvedImports.add(unresolved);
|
|
1348
|
+
const node = graph.byId.get(id);
|
|
1349
|
+
if (!node) continue;
|
|
1350
|
+
if (node.dynamicallyImportedIds.length > 0) hasDynamicImports = true;
|
|
1351
|
+
for (const imported of node.importedIds) queue.push(imported);
|
|
1352
|
+
for (const imported of node.dynamicallyImportedIds) queue.push(imported);
|
|
1353
|
+
}
|
|
1354
|
+
const entryPath = projectRelativeModuleId(root, entryAbsolute) ?? toPosix(relative(normalizeBuildFilePath(root), entryNormalized));
|
|
1355
|
+
inputs.add(entryPath);
|
|
1356
|
+
return {
|
|
1357
|
+
resourceKey,
|
|
1358
|
+
kind,
|
|
1359
|
+
entryPath,
|
|
1360
|
+
inputs: [...inputs].sort(),
|
|
1361
|
+
packages: [...packages].sort(),
|
|
1362
|
+
hasDynamicImports,
|
|
1363
|
+
unresolvedImports: [...unresolvedImports].sort()
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
/**
|
|
1367
|
+
* Analyze bundler input closures for every deployable workflow and agent entry.
|
|
1368
|
+
*
|
|
1369
|
+
* Uses the same tsdown/rolldown resolver settings as artifact builds so aliases,
|
|
1370
|
+
* package exports, TS resolution, and externals match production.
|
|
1371
|
+
*/
|
|
1372
|
+
async function analyzeDeployableClosures(options = {}) {
|
|
1373
|
+
const root = normalizeBuildFilePath(options.root ?? process.cwd());
|
|
1374
|
+
const walked = walkProject(root, {
|
|
1375
|
+
srcDir: options.srcDir ?? "src",
|
|
1376
|
+
phase: options.phase ?? "deploy"
|
|
1377
|
+
});
|
|
1378
|
+
const entries = {};
|
|
1379
|
+
for (const [key, absolute] of Object.entries(walked.buildEntries)) if (isDeployableResourceKey(key)) entries[key] = absolute;
|
|
1380
|
+
const entryList = Object.entries(entries).sort(([a], [b]) => a.localeCompare(b));
|
|
1381
|
+
if (entryList.length === 0) return {
|
|
1382
|
+
closures: [],
|
|
1383
|
+
failures: []
|
|
1384
|
+
};
|
|
1385
|
+
mkdirSync(join(root, ".keystroke"), { recursive: true });
|
|
1386
|
+
const scratchRoot = mkdtempSync(join(root, ".keystroke", "closure-analyze-"));
|
|
1387
|
+
const previous = process.cwd();
|
|
1388
|
+
const closures = [];
|
|
1389
|
+
const failures = [];
|
|
1390
|
+
try {
|
|
1391
|
+
process.chdir(root);
|
|
1392
|
+
for (const [resourceKey, entryPath] of entryList) {
|
|
1393
|
+
const graph = {
|
|
1394
|
+
byId: /* @__PURE__ */ new Map(),
|
|
1395
|
+
unresolvedByImporter: /* @__PURE__ */ new Map()
|
|
1396
|
+
};
|
|
1397
|
+
const outDir = join(scratchRoot, "dist", resourceKey.replaceAll("/", "__"));
|
|
1398
|
+
try {
|
|
1399
|
+
await build(mergeConfig(baseTsdownConfig, {
|
|
1400
|
+
entry: { [resourceKey]: entryPath },
|
|
1401
|
+
format: ["esm"],
|
|
1402
|
+
outDir,
|
|
1403
|
+
clean: true,
|
|
1404
|
+
dts: false,
|
|
1405
|
+
sourcemap: false,
|
|
1406
|
+
logLevel: "error",
|
|
1407
|
+
loader: { ".md": "text" },
|
|
1408
|
+
deps: userAppBuildDepsConfig(),
|
|
1409
|
+
plugins: [collectModuleGraphPlugin(graph)]
|
|
1410
|
+
}));
|
|
1411
|
+
const closure = closureFromGraph(root, resourceKey, entryPath, graph);
|
|
1412
|
+
closures.push(closure);
|
|
1413
|
+
} catch (error) {
|
|
1414
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1415
|
+
failures.push({
|
|
1416
|
+
resourceKey,
|
|
1417
|
+
reason: message
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
} finally {
|
|
1422
|
+
process.chdir(previous);
|
|
1423
|
+
rmSync(scratchRoot, {
|
|
1424
|
+
recursive: true,
|
|
1425
|
+
force: true
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
return {
|
|
1429
|
+
closures,
|
|
1430
|
+
failures
|
|
1431
|
+
};
|
|
1432
|
+
}
|
|
1433
|
+
function ownershipIndex(closures) {
|
|
1434
|
+
const index = /* @__PURE__ */ new Map();
|
|
1435
|
+
for (const closure of closures) for (const input of closure.inputs) {
|
|
1436
|
+
const owners = index.get(input) ?? [];
|
|
1437
|
+
owners.push(closure.resourceKey);
|
|
1438
|
+
index.set(input, owners);
|
|
1439
|
+
}
|
|
1440
|
+
for (const [path, owners] of index) index.set(path, [...new Set(owners)].sort());
|
|
1441
|
+
return index;
|
|
1442
|
+
}
|
|
1443
|
+
function packageOwnershipIndex(closures) {
|
|
1444
|
+
const packageOwners = /* @__PURE__ */ new Map();
|
|
1445
|
+
for (const closure of closures) for (const packageName of closure.packages) {
|
|
1446
|
+
const owners = packageOwners.get(packageName) ?? [];
|
|
1447
|
+
owners.push(closure.resourceKey);
|
|
1448
|
+
packageOwners.set(packageName, owners);
|
|
1449
|
+
}
|
|
1450
|
+
for (const [packageName, owners] of packageOwners) packageOwners.set(packageName, [...new Set(owners)].sort());
|
|
1451
|
+
return packageOwners;
|
|
1452
|
+
}
|
|
1453
|
+
function unionOwners(...lists) {
|
|
1454
|
+
return [...new Set(lists.flatMap((list) => list ?? []))].sort();
|
|
1455
|
+
}
|
|
1456
|
+
function intersectsRequested(owners, requested) {
|
|
1457
|
+
return owners.some((owner) => requested.has(owner));
|
|
1458
|
+
}
|
|
1459
|
+
function sortCandidateOps(ops) {
|
|
1460
|
+
return [...ops].sort((a, b) => {
|
|
1461
|
+
const byPath = a.path.localeCompare(b.path);
|
|
1462
|
+
if (byPath !== 0) return byPath;
|
|
1463
|
+
return a.op.localeCompare(b.op);
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
/**
|
|
1467
|
+
* Transition ownership for a changed path:
|
|
1468
|
+
* - added → D0 owners
|
|
1469
|
+
* - deleted → M0 owners
|
|
1470
|
+
* - modified → M0 ∪ D0 owners
|
|
1471
|
+
* - renamed → previousPath M0 ∪ new path D0 (promote both ops when either side matches)
|
|
1472
|
+
*/
|
|
1473
|
+
function transitionAttribution(change, draftOwnersByPath, mainOwnersByPath) {
|
|
1474
|
+
if (change.kind === "renamed") {
|
|
1475
|
+
if (!change.previousPath) return requireFull("ambiguous_ownership", `Renamed path "${change.path}" is missing previousPath; filtered deploy cannot attribute ownership`);
|
|
1476
|
+
return {
|
|
1477
|
+
owners: unionOwners(mainOwnersByPath.get(change.previousPath), draftOwnersByPath.get(change.path)),
|
|
1478
|
+
ops: [{
|
|
1479
|
+
path: change.previousPath,
|
|
1480
|
+
op: "delete"
|
|
1481
|
+
}, {
|
|
1482
|
+
path: change.path,
|
|
1483
|
+
op: "upsert"
|
|
1484
|
+
}]
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
if (change.kind === "added") return {
|
|
1488
|
+
owners: draftOwnersByPath.get(change.path) ?? [],
|
|
1489
|
+
ops: [{
|
|
1490
|
+
path: change.path,
|
|
1491
|
+
op: "upsert"
|
|
1492
|
+
}]
|
|
1493
|
+
};
|
|
1494
|
+
if (change.kind === "deleted") return {
|
|
1495
|
+
owners: mainOwnersByPath.get(change.path) ?? [],
|
|
1496
|
+
ops: [{
|
|
1497
|
+
path: change.path,
|
|
1498
|
+
op: "delete"
|
|
1499
|
+
}]
|
|
1500
|
+
};
|
|
1501
|
+
return {
|
|
1502
|
+
owners: unionOwners(mainOwnersByPath.get(change.path), draftOwnersByPath.get(change.path)),
|
|
1503
|
+
ops: [{
|
|
1504
|
+
path: change.path,
|
|
1505
|
+
op: "upsert"
|
|
1506
|
+
}]
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
function filteredSuccess(input) {
|
|
1510
|
+
return {
|
|
1511
|
+
requireFull: false,
|
|
1512
|
+
resourceKeys: [...input.resourceKeys].sort(),
|
|
1513
|
+
impactedResourceKeys: [...input.impactedResourceKeys].sort(),
|
|
1514
|
+
expansions: [...input.expansions].sort((a, b) => a.resourceKey.localeCompare(b.resourceKey)),
|
|
1515
|
+
sourceDelta: [...input.sourceDelta].sort(),
|
|
1516
|
+
candidateOps: sortCandidateOps(input.candidateOps),
|
|
1517
|
+
impactCompleteness: input.impactCompleteness,
|
|
1518
|
+
warnings: [...input.warnings]
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
/**
|
|
1522
|
+
* Compute the smallest safe filtered resource set + source delta for a draft delta.
|
|
1523
|
+
*
|
|
1524
|
+
* Dual-graph (M0/D0) transition ownership promotes only paths attributable to
|
|
1525
|
+
* the requested resources. Impacts are M0 consumers of promoted paths (candidate
|
|
1526
|
+
* = M0 + sourceDelta). Incomplete main-impact enumeration is soft-unknown, not
|
|
1527
|
+
* a hard block.
|
|
1528
|
+
*/
|
|
1529
|
+
async function computeSafeFilteredDeploy(options) {
|
|
1530
|
+
const root = normalizeBuildFilePath(options.root ?? process.cwd());
|
|
1531
|
+
const srcDir = options.srcDir ?? "src";
|
|
1532
|
+
const phase = options.phase ?? "deploy";
|
|
1533
|
+
const normalized = normalizeChanges(root, options.changedPaths);
|
|
1534
|
+
if ("requireFull" in normalized && normalized.requireFull) return normalized;
|
|
1535
|
+
const changes = normalized;
|
|
1536
|
+
const packageManifestChanges = [];
|
|
1537
|
+
const lockfileChanges = [];
|
|
1538
|
+
const sourceLikeChanges = [];
|
|
1539
|
+
for (const change of changes) {
|
|
1540
|
+
if (isPackageManifestPath(change.path)) {
|
|
1541
|
+
packageManifestChanges.push(change);
|
|
1542
|
+
continue;
|
|
1543
|
+
}
|
|
1544
|
+
if (isLockfilePath(change.path)) {
|
|
1545
|
+
lockfileChanges.push(change);
|
|
1546
|
+
continue;
|
|
1547
|
+
}
|
|
1548
|
+
sourceLikeChanges.push(change);
|
|
1549
|
+
}
|
|
1550
|
+
const selected = [...new Set(options.selectedResourceKeys)];
|
|
1551
|
+
if (selected.length === 0) return requireFull("unknown_selected_key", "At least one selected resource key is required");
|
|
1552
|
+
const draftAnalysis = options.closures !== void 0 ? {
|
|
1553
|
+
closures: options.closures,
|
|
1554
|
+
failures: options.failures ?? []
|
|
1555
|
+
} : await analyzeDeployableClosures({
|
|
1556
|
+
root,
|
|
1557
|
+
srcDir,
|
|
1558
|
+
phase
|
|
1559
|
+
});
|
|
1560
|
+
const draftClosures = draftAnalysis.closures;
|
|
1561
|
+
const draftFailures = draftAnalysis.failures;
|
|
1562
|
+
const draftByKey = new Map(draftClosures.map((closure) => [closure.resourceKey, closure]));
|
|
1563
|
+
const draftFailedKeys = new Set(draftFailures.map((failure) => failure.resourceKey));
|
|
1564
|
+
const mainClosures = options.mainClosures ?? draftClosures;
|
|
1565
|
+
const mainFailures = options.mainClosures !== void 0 ? options.mainFailures ?? [] : draftFailures;
|
|
1566
|
+
const mainByKey = new Map(mainClosures.map((closure) => [closure.resourceKey, closure]));
|
|
1567
|
+
const mainFailedKeys = new Set(mainFailures.map((failure) => failure.resourceKey));
|
|
1568
|
+
for (const key of selected) {
|
|
1569
|
+
if (draftFailedKeys.has(key)) {
|
|
1570
|
+
const failure = draftFailures.find((item) => item.resourceKey === key);
|
|
1571
|
+
return requireFull("unproven_safe", `Selected resource "${key}" failed bundler analysis; filtered deploy cannot prove a complete closure` + (failure ? `: ${failure.reason}` : ""));
|
|
1572
|
+
}
|
|
1573
|
+
if (!draftByKey.has(key)) return requireFull("unknown_selected_key", `Unknown selected resource key "${key}". Use keys like agents/foo or workflows/bar`);
|
|
1574
|
+
}
|
|
1575
|
+
for (const key of selected) {
|
|
1576
|
+
const closure = draftByKey.get(key);
|
|
1577
|
+
if (closure.hasDynamicImports || closure.unresolvedImports.length > 0) return requireFull("dynamic_or_unresolved_import", `Selected resource "${key}" has dynamic or unresolved imports; filtered deploy cannot prove a complete closure`);
|
|
1578
|
+
}
|
|
1579
|
+
const draftOwnersByPath = ownershipIndex(draftClosures);
|
|
1580
|
+
const mainOwnersByPath = ownershipIndex(mainClosures);
|
|
1581
|
+
const mainPackageOwners = packageOwnershipIndex(mainClosures);
|
|
1582
|
+
const requestedSet = new Set(selected);
|
|
1583
|
+
const sourceDelta = /* @__PURE__ */ new Set();
|
|
1584
|
+
const candidateOpsByKey = /* @__PURE__ */ new Map();
|
|
1585
|
+
const expansions = /* @__PURE__ */ new Map();
|
|
1586
|
+
const impactedSet = /* @__PURE__ */ new Set();
|
|
1587
|
+
const warnings = [];
|
|
1588
|
+
let impactCompleteness = "complete";
|
|
1589
|
+
const markUnknown = (warning) => {
|
|
1590
|
+
impactCompleteness = "unknown";
|
|
1591
|
+
if (!warnings.includes(warning)) warnings.push(warning);
|
|
1592
|
+
};
|
|
1593
|
+
const addCandidateOp = (op) => {
|
|
1594
|
+
const key = `${op.op}:${op.path}`;
|
|
1595
|
+
candidateOpsByKey.set(key, op);
|
|
1596
|
+
sourceDelta.add(op.path);
|
|
1597
|
+
};
|
|
1598
|
+
const markImpact = (resourceKey, sharedInputs, reason) => {
|
|
1599
|
+
if (requestedSet.has(resourceKey)) return;
|
|
1600
|
+
impactedSet.add(resourceKey);
|
|
1601
|
+
const existing = expansions.get(resourceKey);
|
|
1602
|
+
const mergedInputs = existing ? [...new Set([...existing.sharedInputs, ...sharedInputs])].sort() : [...sharedInputs].sort();
|
|
1603
|
+
expansions.set(resourceKey, {
|
|
1604
|
+
resourceKey,
|
|
1605
|
+
reason: existing ? `${existing.reason}; ${reason}` : reason,
|
|
1606
|
+
sharedInputs: mergedInputs
|
|
1607
|
+
});
|
|
1608
|
+
};
|
|
1609
|
+
const selectedEntryPaths = new Set(selected.map((key) => draftByKey.get(key)?.entryPath).filter((path) => Boolean(path)));
|
|
1610
|
+
for (const change of sourceLikeChanges) {
|
|
1611
|
+
const attribution = transitionAttribution(change, draftOwnersByPath, mainOwnersByPath);
|
|
1612
|
+
if ("requireFull" in attribution) return attribution;
|
|
1613
|
+
const { owners, ops } = attribution;
|
|
1614
|
+
if ((selectedEntryPaths.has(change.path) || change.previousPath != null && selectedEntryPaths.has(change.previousPath) || selected.some((key) => {
|
|
1615
|
+
const mainEntry = mainByKey.get(key)?.entryPath;
|
|
1616
|
+
return mainEntry === change.path || mainEntry === change.previousPath;
|
|
1617
|
+
})) && !intersectsRequested(owners, requestedSet)) return requireFull("missing_ownership", `Changed path "${change.path}" must be included for the selected resource(s) but selected ownership cannot be proven`);
|
|
1618
|
+
if (!intersectsRequested(owners, requestedSet)) continue;
|
|
1619
|
+
for (const op of ops) addCandidateOp(op);
|
|
1620
|
+
for (const op of ops) {
|
|
1621
|
+
const mainOwners = mainOwnersByPath.get(op.path) ?? [];
|
|
1622
|
+
for (const owner of mainOwners) {
|
|
1623
|
+
if (requestedSet.has(owner)) continue;
|
|
1624
|
+
const mainClosure = mainByKey.get(owner);
|
|
1625
|
+
if (!mainClosure) {
|
|
1626
|
+
if (mainFailedKeys.has(owner)) {
|
|
1627
|
+
markUnknown(`Main-tip analysis failed for "${owner}"; additional owners of promoted path "${op.path}" may be hidden`);
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1630
|
+
markUnknown(`Promoted path "${op.path}" lists main owner "${owner}" without an analyzable main closure`);
|
|
1631
|
+
continue;
|
|
1632
|
+
}
|
|
1633
|
+
if (mainClosure.hasDynamicImports || mainClosure.unresolvedImports.length > 0) markUnknown(`Impacted main resource "${owner}" has dynamic or unresolved imports; additional impacts may be hidden`);
|
|
1634
|
+
markImpact(owner, [op.path], `Shared input ${`"${op.path}"`} also used by requested resource(s) on main`);
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
if (packageManifestChanges.length > 0 || lockfileChanges.length > 0) {
|
|
1639
|
+
const packageResult = attributePackageChanges({
|
|
1640
|
+
root,
|
|
1641
|
+
packageManifestChanges,
|
|
1642
|
+
lockfileChanges,
|
|
1643
|
+
baseManifestFiles: options.baseManifestFiles ?? [],
|
|
1644
|
+
mainClosures,
|
|
1645
|
+
draftClosures,
|
|
1646
|
+
requestedSet,
|
|
1647
|
+
mainPackageOwners
|
|
1648
|
+
});
|
|
1649
|
+
if (packageResult.include) {
|
|
1650
|
+
for (const path of packageResult.manifestDelta) addCandidateOp({
|
|
1651
|
+
path,
|
|
1652
|
+
op: "upsert"
|
|
1653
|
+
});
|
|
1654
|
+
for (const expansion of packageResult.expansions) markImpact(expansion.resourceKey, expansion.sharedInputs, expansion.reason);
|
|
1655
|
+
if (packageResult.unknownCompleteness) for (const warning of packageResult.warnings) markUnknown(warning);
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
if (mainFailures.length > 0 && sourceDelta.size > 0) {
|
|
1659
|
+
const failedList = [...mainFailedKeys].sort().join(", ");
|
|
1660
|
+
if (![...sourceDelta].every((path) => selectedEntryPaths.has(path))) markUnknown(`Main-tip bundler analysis failed for deployable resource(s) ${failedList}; additional owners of promoted shared paths may be hidden`);
|
|
1661
|
+
}
|
|
1662
|
+
if (sourceDelta.size === 0) return filteredSuccess({
|
|
1663
|
+
resourceKeys: selected,
|
|
1664
|
+
impactedResourceKeys: [],
|
|
1665
|
+
expansions: [],
|
|
1666
|
+
sourceDelta: [],
|
|
1667
|
+
candidateOps: [],
|
|
1668
|
+
impactCompleteness: "complete",
|
|
1669
|
+
warnings: []
|
|
1670
|
+
});
|
|
1671
|
+
return filteredSuccess({
|
|
1672
|
+
resourceKeys: [...requestedSet, ...impactedSet],
|
|
1673
|
+
impactedResourceKeys: [...impactedSet],
|
|
1674
|
+
expansions: [...expansions.values()],
|
|
1675
|
+
sourceDelta: [...sourceDelta],
|
|
1676
|
+
candidateOps: [...candidateOpsByKey.values()],
|
|
1677
|
+
impactCompleteness,
|
|
1678
|
+
warnings
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
function attributePackageChanges(input) {
|
|
1682
|
+
if (input.packageManifestChanges.length === 0) return { include: false };
|
|
1683
|
+
if (input.baseManifestFiles.length === 0) return { include: false };
|
|
1684
|
+
const draftPackageJsonPath = join(input.root, "package.json");
|
|
1685
|
+
let draftPackageJson;
|
|
1686
|
+
try {
|
|
1687
|
+
draftPackageJson = readFileSync(draftPackageJsonPath, "utf8");
|
|
1688
|
+
} catch {
|
|
1689
|
+
return { include: false };
|
|
1690
|
+
}
|
|
1691
|
+
const basePackageJson = new Map(input.baseManifestFiles.map((file) => [file.path.replace(/\\/g, "/"), file.contents])).get("package.json");
|
|
1692
|
+
if (basePackageJson == null) return { include: false };
|
|
1693
|
+
const baseDeps = readDependencyMap(basePackageJson);
|
|
1694
|
+
const draftDeps = readDependencyMap(draftPackageJson);
|
|
1695
|
+
if (!baseDeps || !draftDeps) return { include: false };
|
|
1696
|
+
const changedPackages = changedDependencyNames(baseDeps, draftDeps);
|
|
1697
|
+
if (changedPackages.length === 0) return { include: false };
|
|
1698
|
+
const runtimeChanged = changedPackages.filter((name) => isRuntimeExternalPackage(name));
|
|
1699
|
+
const bundledChanged = changedPackages.filter((name) => !isRuntimeExternalPackage(name));
|
|
1700
|
+
const selectedConsumesBundled = [...input.requestedSet].some((key) => {
|
|
1701
|
+
const draft = input.draftClosures.find((item) => item.resourceKey === key);
|
|
1702
|
+
const main = input.mainClosures.find((item) => item.resourceKey === key);
|
|
1703
|
+
const packages = new Set([...draft?.packages ?? [], ...main?.packages ?? []]);
|
|
1704
|
+
return bundledChanged.some((name) => packages.has(name));
|
|
1705
|
+
});
|
|
1706
|
+
if (runtimeChanged.length === 0 && !selectedConsumesBundled) return { include: false };
|
|
1707
|
+
const expansions = /* @__PURE__ */ new Map();
|
|
1708
|
+
const markImpact = (resourceKey, reason, sharedInputs) => {
|
|
1709
|
+
if (input.requestedSet.has(resourceKey)) return;
|
|
1710
|
+
const existing = expansions.get(resourceKey);
|
|
1711
|
+
expansions.set(resourceKey, {
|
|
1712
|
+
resourceKey,
|
|
1713
|
+
reason: existing ? `${existing.reason}; ${reason}` : reason,
|
|
1714
|
+
sharedInputs: [...new Set([...existing?.sharedInputs ?? [], ...sharedInputs])].sort()
|
|
1715
|
+
});
|
|
1716
|
+
};
|
|
1717
|
+
if (runtimeChanged.length > 0) for (const closure of input.mainClosures) markImpact(closure.resourceKey, `Runtime package update (${runtimeChanged.map((name) => `"${name}"`).join(", ")}) requires rebuilding all resources from main`, runtimeChanged);
|
|
1718
|
+
for (const packageName of bundledChanged) {
|
|
1719
|
+
if (!selectedConsumesBundled) continue;
|
|
1720
|
+
const owners = input.mainPackageOwners.get(packageName) ?? [];
|
|
1721
|
+
for (const owner of owners) markImpact(owner, `Bundled package "${packageName}" changed and is imported by this resource`, [packageName]);
|
|
1722
|
+
}
|
|
1723
|
+
const manifestDelta = [...input.packageManifestChanges.map((change) => change.path), ...input.lockfileChanges.map((change) => change.path)].sort();
|
|
1724
|
+
const warnings = [];
|
|
1725
|
+
let unknownCompleteness = false;
|
|
1726
|
+
if (input.lockfileChanges.length > 0) {
|
|
1727
|
+
unknownCompleteness = true;
|
|
1728
|
+
warnings.push("Lockfile included with selected package change; lockfile resolution may hide additional package consumers");
|
|
1729
|
+
}
|
|
1730
|
+
return {
|
|
1731
|
+
include: true,
|
|
1732
|
+
manifestDelta,
|
|
1733
|
+
expansions: [...expansions.values()],
|
|
1734
|
+
unknownCompleteness,
|
|
1735
|
+
warnings
|
|
1736
|
+
};
|
|
1737
|
+
}
|
|
1738
|
+
/** Build user agents, workflows, triggers, and config to `dist/`. */
|
|
1739
|
+
async function buildApp(options = {}) {
|
|
1740
|
+
const root = options.root ?? process.cwd();
|
|
1741
|
+
const srcDir = options.srcDir ?? "src";
|
|
1742
|
+
const emitManifest = options.emitManifest ?? true;
|
|
1743
|
+
const collectSources = options.collectSources ?? false;
|
|
1744
|
+
const phase = options.phase ?? "build";
|
|
1745
|
+
const previous = process.cwd();
|
|
1746
|
+
const walked = walkProject(root, {
|
|
1747
|
+
srcDir,
|
|
1748
|
+
collectSources,
|
|
1749
|
+
phase
|
|
1750
|
+
});
|
|
1751
|
+
const timings = {
|
|
1752
|
+
bundleMs: 0,
|
|
1753
|
+
manifestMs: 0,
|
|
1754
|
+
manifestCacheHit: false,
|
|
1755
|
+
bundleSkipped: false
|
|
1756
|
+
};
|
|
1757
|
+
const cacheKey = phase === "deploy" && collectSources && walked.sourceFiles.length > 0 ? computeRouteManifestCacheKey({
|
|
1758
|
+
phase,
|
|
1759
|
+
sourceFiles: walked.sourceFiles,
|
|
1760
|
+
buildEntryKeys: Object.keys(walked.buildEntries),
|
|
1761
|
+
ignoredFiles: walked.ignoredFiles,
|
|
1762
|
+
integrationFingerprint: integrationPackagesFingerprint(root)
|
|
1763
|
+
}) : null;
|
|
1764
|
+
try {
|
|
1765
|
+
process.chdir(root);
|
|
1766
|
+
if (cacheKey && emitManifest && isRouteManifestCacheHit(root, cacheKey) && distDeployOutputsPresent(root, walked.buildEntries)) {
|
|
1767
|
+
const manifestStarted = Date.now();
|
|
1768
|
+
tryRestoreCachedRouteManifest(root, cacheKey);
|
|
1769
|
+
timings.manifestCacheHit = true;
|
|
1770
|
+
timings.bundleSkipped = true;
|
|
1771
|
+
timings.manifestMs = Date.now() - manifestStarted;
|
|
1772
|
+
return {
|
|
1773
|
+
sourceFiles: walked.sourceFiles,
|
|
1774
|
+
timings
|
|
1775
|
+
};
|
|
1776
|
+
}
|
|
1777
|
+
const dirtyResult = listDirtyEntries(root, Object.keys(walked.buildEntries));
|
|
1778
|
+
const skipBundle = !dirtyResult.forceFull && dirtyResult.dirty.length === 0 && distDeployOutputsPresent(root, walked.buildEntries);
|
|
1779
|
+
const workflowDiagnostics = collectWorkflowDiagnostics(root, srcDir);
|
|
1780
|
+
assertWorkflowDiagnosticErrorsFrom(workflowDiagnostics);
|
|
1781
|
+
try {
|
|
1782
|
+
if (skipBundle) {
|
|
1783
|
+
packModularProjectAssets({
|
|
1784
|
+
projectRoot: root,
|
|
1785
|
+
srcDir,
|
|
1786
|
+
outDir: options.outDir ?? "dist"
|
|
1787
|
+
});
|
|
1788
|
+
timings.bundleSkipped = true;
|
|
1789
|
+
} else {
|
|
1790
|
+
const bundleStarted = Date.now();
|
|
1791
|
+
const configEntry = existsSync(join(root, "keystroke.config.ts")) ? join(root, "keystroke.config.ts") : void 0;
|
|
1792
|
+
await buildSelfContainedEntries({
|
|
1793
|
+
root,
|
|
1794
|
+
srcDir,
|
|
1795
|
+
outDir: options.outDir ?? "dist",
|
|
1796
|
+
phase,
|
|
1797
|
+
clean: dirtyResult.forceFull && (options.clean ?? true),
|
|
1798
|
+
buildEntries: walked.buildEntries,
|
|
1799
|
+
ignoredFiles: walked.ignoredFiles,
|
|
1800
|
+
dirtyKeys: dirtyResult.forceFull ? void 0 : dirtyResult.dirty,
|
|
1801
|
+
configEntry
|
|
1802
|
+
});
|
|
1803
|
+
timings.bundleMs = Date.now() - bundleStarted;
|
|
1804
|
+
}
|
|
1805
|
+
} finally {
|
|
1806
|
+
warnWorkflowDiagnosticsFrom(workflowDiagnostics);
|
|
1807
|
+
}
|
|
1808
|
+
if (emitManifest) {
|
|
1809
|
+
const manifestStarted = Date.now();
|
|
1810
|
+
if (cacheKey && tryRestoreCachedRouteManifest(root, cacheKey)) timings.manifestCacheHit = true;
|
|
1811
|
+
else {
|
|
1812
|
+
const { emitStoredRouteManifestForProject } = await import("./dist-Dq1kkbLy.mjs");
|
|
1813
|
+
await emitStoredRouteManifestForProject(root);
|
|
1814
|
+
if (cacheKey) storeCachedRouteManifest(root, cacheKey);
|
|
1815
|
+
}
|
|
1816
|
+
timings.manifestMs = Date.now() - manifestStarted;
|
|
1817
|
+
}
|
|
1818
|
+
} finally {
|
|
1819
|
+
process.chdir(previous);
|
|
1820
|
+
}
|
|
1821
|
+
return {
|
|
1822
|
+
sourceFiles: walked.sourceFiles,
|
|
1823
|
+
timings
|
|
1824
|
+
};
|
|
1825
|
+
}
|
|
1826
|
+
//#endregion
|
|
1827
|
+
export { buildApp as a, computeSafeFilteredDeploy as c, normalizeProjectRelativePath as d, tryReadBuildIndex as f, assertWorkflowDiagnosticErrorsFrom as i, injectCallSiteIds as l, BUILD_INDEX_REL_PATH as n, callSiteIdInjectionPlugin as o, warnWorkflowDiagnosticsFrom as p, analyzeDeployableClosures as r, collectWorkflowDiagnostics as s, ANALYZER_CONTRACT_VERSION as t, listDirtyEntries as u };
|
|
1828
|
+
|
|
1829
|
+
//# sourceMappingURL=dist-DJQnugPi.mjs.map
|