@metabase/cli 0.5.8 → 0.5.10

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.
Files changed (302) hide show
  1. package/dist/{add-collection-DIrpeq9O.mjs → add-collection-B6_b7Z0h.mjs} +3 -3
  2. package/dist/{alert-DuUreVMq.mjs → alert-CaOd6Sye.mjs} +7 -7
  3. package/dist/{alerts-C7vJ-NAI.mjs → alerts-B4XDd_EG.mjs} +6 -6
  4. package/dist/api-key-BEyGzjhr.mjs +13 -0
  5. package/dist/{append-v3fakJOd.mjs → append-D4yRU3nt.mjs} +4 -4
  6. package/dist/{archive-TBPRGm7s.mjs → archive-B0wPQhnH.mjs} +3 -3
  7. package/dist/{archive-DwVbn3FG.mjs → archive-B4KdkRj1.mjs} +3 -3
  8. package/dist/{archive-BrGj2yoP.mjs → archive-BTpXIs7W.mjs} +3 -3
  9. package/dist/{archive-Cy6RUJaD.mjs → archive-BTsVi373.mjs} +3 -3
  10. package/dist/{archive-5pjvTser.mjs → archive-BkCFPiWS.mjs} +3 -3
  11. package/dist/{archive-Bt8fwInI.mjs → archive-BxN7gbKa.mjs} +4 -4
  12. package/dist/{archive-CuygjxgQ.mjs → archive-CSt4KxhB.mjs} +3 -3
  13. package/dist/{archive-D299FxXt.mjs → archive-DJT5Du9l.mjs} +3 -3
  14. package/dist/{archive-DdPaXaJo.mjs → archive-DoZ8HBxg.mjs} +4 -4
  15. package/dist/{archive-CyhH-395.mjs → archive-btZAAdMv.mjs} +3 -3
  16. package/dist/{archive-CmhV_hlP.mjs → archive-jtNM7hhn.mjs} +3 -3
  17. package/dist/{archive-BEhhmkTO.mjs → archive-pMSD0Cou.mjs} +3 -3
  18. package/dist/{auth-B63Hfuwg.mjs → auth-BtrN725t.mjs} +5 -5
  19. package/dist/{branches-BM0L124r.mjs → branches-D4Lwau0h.mjs} +3 -3
  20. package/dist/{breaking-VMUrgVVE.mjs → breaking-DH9tr0ug.mjs} +5 -5
  21. package/dist/{broken-BbpNq5WG.mjs → broken-DiT2Ww1u.mjs} +6 -6
  22. package/dist/{bulk-discard-values-Cohox-Zb.mjs → bulk-discard-values-D7jD6GjV.mjs} +5 -5
  23. package/dist/{bulk-edit-B9-XH592.mjs → bulk-edit-DsetARSf.mjs} +3 -3
  24. package/dist/{bulk-rescan-values-CsWKirZw.mjs → bulk-rescan-values-DSUVmXSh.mjs} +4 -4
  25. package/dist/{bulk-sync-schema-CorpcAjz.mjs → bulk-sync-schema-n5Qe7UnU.mjs} +4 -4
  26. package/dist/{cancel-iGUVm1h2.mjs → cancel-oFccoFiE.mjs} +3 -3
  27. package/dist/{cancel-task-DWiicb_t.mjs → cancel-task-kpJZ4G2I.mjs} +2 -2
  28. package/dist/card-Bnkg5z4G.mjs +25 -0
  29. package/dist/{cards-D6jWz0N9.mjs → cards-BCU56A8s.mjs} +4 -4
  30. package/dist/{ci-BQKTyiRi.mjs → ci-DfbKRU5x.mjs} +2 -2
  31. package/dist/cli.mjs +52 -47
  32. package/dist/{client-Ba61gEcn.mjs → client-BvjSFQ8P.mjs} +27 -42
  33. package/dist/collection-CXYDfvsX.mjs +21 -0
  34. package/dist/{collection-namespace-D0_-6eWp.mjs → collection-namespace-lQLOPUbc.mjs} +1 -1
  35. package/dist/{content-translation-B57aHSuc.mjs → content-translation-CsRsYA5Q.mjs} +3 -3
  36. package/dist/{copy-D-VIVVB1.mjs → copy-7kyXaQVn.mjs} +4 -4
  37. package/dist/{copy-BeD_fk5J.mjs → copy-CR8wSNpp.mjs} +4 -4
  38. package/dist/{copy-flags-BcW9weVX.mjs → copy-flags-Bgy3sSFg.mjs} +1 -1
  39. package/dist/{create-CP9lrYE4.mjs → create-BAxf_Wqm.mjs} +2 -2
  40. package/dist/{create-BlmRvuQg.mjs → create-BIgILG1P.mjs} +3 -3
  41. package/dist/{create-HNchuWpj.mjs → create-BMe3ZtQi.mjs} +3 -3
  42. package/dist/{create-DybrZHV-.mjs → create-BZ9x1_Dm.mjs} +2 -2
  43. package/dist/{create-zALsdMhF.mjs → create-BZ_tLyVg.mjs} +2 -2
  44. package/dist/{create-DcDyXL4g.mjs → create-BewmenPL.mjs} +2 -2
  45. package/dist/{create-C6c-clLK.mjs → create-BhHZBIc4.mjs} +3 -3
  46. package/dist/{create-BURh8kHp.mjs → create-BrFgRgjY.mjs} +3 -3
  47. package/dist/{create-MmZVmRsH.mjs → create-C1CwTQjk.mjs} +2 -2
  48. package/dist/{create-CGiSOtIR.mjs → create-Cx47jTQ9.mjs} +3 -3
  49. package/dist/{create-Bw9DAq_x2.mjs → create-CxZe8ghH2.mjs} +5 -5
  50. package/dist/{create-B--aPIbp.mjs → create-D6FGsFnV.mjs} +2 -2
  51. package/dist/{create-DF7nC9qW.mjs → create-DG9NIdWy.mjs} +2 -2
  52. package/dist/{create-_wex54m1.mjs → create-DHJ75ufg.mjs} +2 -2
  53. package/dist/{create-lHqpAdxg.mjs → create-DVYRfN1s.mjs} +3 -3
  54. package/dist/{create-DQOapeeI.mjs → create-DYf1fYsW.mjs} +3 -3
  55. package/dist/{create-DMtlDzrE.mjs → create-DaspEGaN.mjs} +3 -3
  56. package/dist/{create-Dm8BvbsU.mjs → create-Ddt8jkGe.mjs} +2 -2
  57. package/dist/{create-branch-C8arQT4M.mjs → create-branch-CSqKpG-X.mjs} +2 -2
  58. package/dist/{create-D44fg_iT.mjs → create-gD1BKkjH.mjs} +2 -2
  59. package/dist/{csv-CFabsH6l.mjs → csv-CEQ5iHiR.mjs} +4 -4
  60. package/dist/{current-task-GG6JqfGp.mjs → current-task-Cwn-A02w.mjs} +2 -2
  61. package/dist/dashboard-BQMAwgQv.mjs +35 -0
  62. package/dist/data-action-BOyBN50C.mjs +24 -0
  63. package/dist/data-app-CTxlD3Sa.mjs +39 -0
  64. package/dist/{data-app-Blz61mG5.mjs → data-app-Cglgbj_E.mjs} +1 -1
  65. package/dist/{data-app-BUW-HlUo.mjs → data-app-bl88oUla.mjs} +27 -14
  66. package/dist/data-app-project-C1V4_8k5.mjs +155 -0
  67. package/dist/{db-DSj2I1X9.mjs → db-DjrLLqDW.mjs} +8 -8
  68. package/dist/{delete-Dkwk2mZH.mjs → delete-B3zIJHbp.mjs} +4 -4
  69. package/dist/{delete-YKX2HilI.mjs → delete-B8wPN6Mi.mjs} +4 -4
  70. package/dist/{delete-CsgkbmBy.mjs → delete-BecCd6_I.mjs} +4 -4
  71. package/dist/{delete-Cx75BsN6.mjs → delete-CA8N6OSh.mjs} +4 -4
  72. package/dist/{delete-AF64p0xG.mjs → delete-CYPidCqb.mjs} +4 -4
  73. package/dist/{delete-CnGb4QIm.mjs → delete-Crc2lguC.mjs} +4 -4
  74. package/dist/{delete-CxL5okLN.mjs → delete-DvHo5Dau.mjs} +4 -4
  75. package/dist/{delete-C6N_Aj78.mjs → delete-auLsOacz.mjs} +4 -4
  76. package/dist/{delete-D_l7FRE6.mjs → delete-rBkGuteA.mjs} +4 -4
  77. package/dist/{delete-runtime-BUbOQx5I.mjs → delete-runtime-Bxy6qAqZ.mjs} +2 -2
  78. package/dist/{delete-table-CubRk-5H.mjs → delete-table-CWo5Azjp.mjs} +4 -4
  79. package/dist/{dependencies-W2dxsCpF.mjs → dependencies-B5rZybIE.mjs} +5 -5
  80. package/dist/dependency-BCvsKkvF.mjs +15 -0
  81. package/dist/{dependency-BPCRTBKd.mjs → dependency-DdeyH8m5.mjs} +1 -1
  82. package/dist/{dependents-B4V8pDYq.mjs → dependents-DTokHohZ.mjs} +6 -6
  83. package/dist/{dirty-DKj4ScBN.mjs → dirty-DJzqGN8R.mjs} +3 -3
  84. package/dist/{discard-values-5l91z3Dy.mjs → discard-values-Z0Aaau-z.mjs} +5 -5
  85. package/dist/document-DaQNhYhh.mjs +20 -0
  86. package/dist/{download-DRxR3yGi.mjs → download-CUNQkaWC.mjs} +2 -2
  87. package/dist/{eid-B6JYogNC.mjs → eid-CA9nIsPk.mjs} +2 -2
  88. package/dist/{entry-flags-6Y0fdYbG.mjs → entry-flags-CsbT7fHf.mjs} +1 -1
  89. package/dist/{events-CE80dTib.mjs → events-CtMOLbVW.mjs} +4 -4
  90. package/dist/{execute-C9foK2IU.mjs → execute-BFKWsB4U.mjs} +3 -3
  91. package/dist/{export-B8QyoPWP.mjs → export-DsbEWoLo.mjs} +3 -3
  92. package/dist/{export-flags-DvdxpRMt.mjs → export-flags-DkbYlWlE.mjs} +1 -1
  93. package/dist/{export-preflight-BQbwxyoJ.mjs → export-preflight-TRzdLXI_.mjs} +2 -2
  94. package/dist/field-BnSUwI5l.mjs +21 -0
  95. package/dist/{fields-TE3PnmwW.mjs → fields-Cf-yzUqO.mjs} +4 -4
  96. package/dist/{filter-flags-DAUmdUwa.mjs → filter-flags-B5jGUuHb.mjs} +1 -1
  97. package/dist/{fks-B41Re5dt.mjs → fks-BL7aqSz3.mjs} +5 -5
  98. package/dist/generate-actions-D8iT2Vx_.mjs +38 -0
  99. package/dist/generate-queries-Cmo2IFAd.mjs +41 -0
  100. package/dist/generate-schema-Hubesglp.mjs +34 -0
  101. package/dist/{get-DguRhloC.mjs → get-10d6M3WX.mjs} +2 -2
  102. package/dist/{get-DvPgnqAA.mjs → get-45g97RD0.mjs} +2 -2
  103. package/dist/{get-DJeeXbwj.mjs → get-B0AwjMgu.mjs} +3 -3
  104. package/dist/{get-CtRJUybv.mjs → get-B6uK0K5I.mjs} +3 -3
  105. package/dist/{get-DeiC05Lm.mjs → get-B7I96VF7.mjs} +3 -3
  106. package/dist/{get-BcWr6U0n.mjs → get-BPrKH_VC.mjs} +2 -2
  107. package/dist/{get-BJgBsiEK.mjs → get-BRfgDBX1.mjs} +5 -5
  108. package/dist/{get-uWk0JKvF.mjs → get-B_1SDjVE.mjs} +4 -4
  109. package/dist/{get-BscyItLF.mjs → get-BsLJ6NN4.mjs} +3 -3
  110. package/dist/{get-PLIbg3Vy.mjs → get-C1d4sGGj.mjs} +3 -3
  111. package/dist/{get-sjhoDJyB.mjs → get-C4TSvV8x.mjs} +5 -5
  112. package/dist/{get-Cjk-A9dQ.mjs → get-CHmUuPoY.mjs} +3 -3
  113. package/dist/{get-BAGQ-bvt.mjs → get-CNwSqJ34.mjs} +3 -3
  114. package/dist/{get-CCTRO18v.mjs → get-CORcDYjE.mjs} +4 -4
  115. package/dist/{get-DG5fPl-S.mjs → get-CQx83tws.mjs} +4 -4
  116. package/dist/{get-m-vpr7GN.mjs → get-CkiiPNoS.mjs} +3 -3
  117. package/dist/{get-CU6AWbqL.mjs → get-Cvl3jefC.mjs} +3 -3
  118. package/dist/{get-CefCrGWy.mjs → get-DAsNzVI7.mjs} +3 -3
  119. package/dist/{get-DbZDp2ve.mjs → get-DIYDn-tm.mjs} +3 -3
  120. package/dist/{get-Ck0cRNyr.mjs → get-DUvEGkvw.mjs} +4 -4
  121. package/dist/{get-3XwqrjxC.mjs → get-O81o3nAv.mjs} +3 -3
  122. package/dist/{get-run-DnKCnvUa.mjs → get-run-uEQGMjmi.mjs} +4 -4
  123. package/dist/{get-BrhIHZYg.mjs → get-xMWLwAHj.mjs} +3 -3
  124. package/dist/git-sync-CswhluG_.mjs +29 -0
  125. package/dist/glossary-Cb37yiw3.mjs +14 -0
  126. package/dist/{graph-B2YkV2oX.mjs → graph-BKuSPVVZ.mjs} +4 -4
  127. package/dist/{group-D1nGaEXW.mjs → group-DxaqX3Gg.mjs} +1 -1
  128. package/dist/{has-remote-changes-ChMSjhmC.mjs → has-remote-changes-BUCHJZqQ.mjs} +2 -2
  129. package/dist/{import-CNpN9iS0.mjs → import-B-ekaPHP.mjs} +3 -3
  130. package/dist/{is-dirty-Bud2wuxQ.mjs → is-dirty-BGcu7D5c.mjs} +2 -2
  131. package/dist/{items-B1DXzdwg.mjs → items-vDPpkZUK.mjs} +3 -3
  132. package/dist/{library-8G0s5VLO.mjs → library-n-3eu6WW.mjs} +5 -5
  133. package/dist/{list-z4iqffgl.mjs → list-BIOCkt1A.mjs} +4 -4
  134. package/dist/{list-CAAvhl9h.mjs → list-BSQPBkVu.mjs} +3 -3
  135. package/dist/{list-tHzgEkzv.mjs → list-BiXIUvBg.mjs} +5 -5
  136. package/dist/{list-BVz0N0g_.mjs → list-BoOFUEYh.mjs} +3 -3
  137. package/dist/{list-Dk3upD11.mjs → list-BtENqZIq.mjs} +5 -5
  138. package/dist/{list-DAdWd8gn.mjs → list-C8a6WeMs.mjs} +3 -3
  139. package/dist/{list-BeaA7gQ7.mjs → list-CADlvzKj.mjs} +3 -3
  140. package/dist/{list-DjfY80vj.mjs → list-Ci8Y9AdJ.mjs} +3 -3
  141. package/dist/{list-Ckqu41Zx.mjs → list-CoM1L7ZK.mjs} +3 -3
  142. package/dist/{list-CAZHyhd5.mjs → list-D-zjbHfR.mjs} +3 -3
  143. package/dist/{list-BSvQ0AT_.mjs → list-D5yxCOsl.mjs} +3 -3
  144. package/dist/{list-0aX638ov.mjs → list-DJAlAVuS.mjs} +5 -5
  145. package/dist/{list-B2PbnXp4.mjs → list-DMdRy6Cx.mjs} +4 -4
  146. package/dist/{list-DpyIV41y.mjs → list-DU2dKd2T.mjs} +4 -4
  147. package/dist/{list-CcfGHN19.mjs → list-J5FxC72O.mjs} +3 -3
  148. package/dist/{list-BJasizCe.mjs → list-JWWHtIMj.mjs} +3 -3
  149. package/dist/{list-CO62N1eD.mjs → list-PvFvPn0p.mjs} +3 -3
  150. package/dist/{list-D1WO7lxP.mjs → list-Qy-5LvDC.mjs} +4 -4
  151. package/dist/{list-DX5SmBCQ.mjs → list-UJ3nPrrD.mjs} +3 -3
  152. package/dist/{list-mRSKPKoO.mjs → list-e8h2S-3y.mjs} +3 -3
  153. package/dist/{list-DZLGnsAK.mjs → list-jfxTNTSm.mjs} +3 -3
  154. package/dist/{list-CPwy-AEn.mjs → list-mkXqkfck.mjs} +8 -12
  155. package/dist/{list-JqIFAXpw.mjs → list-nd6-vgOU.mjs} +3 -3
  156. package/dist/{login-muOdP15h.mjs → login-DcneUyj9.mjs} +4 -4
  157. package/dist/{logout-Z0_gSSM4.mjs → logout-wA2-ebUy.mjs} +2 -2
  158. package/dist/{measure-CTF4qqiE.mjs → measure-BICKq4Ud.mjs} +6 -6
  159. package/dist/nano-id-qEgGloYk.mjs +55 -0
  160. package/dist/{notification-Bj45MgWm.mjs → notification-Ec1Jo0Oc.mjs} +1 -1
  161. package/dist/{parameter-values-to5u_-2n.mjs → parameter-values-d_zi14dw.mjs} +3 -3
  162. package/dist/{parse-id-CbF7zxGv.mjs → parse-id-DKKwr4c8.mjs} +1 -1
  163. package/dist/{path-B-0S-9i2.mjs → path-Cn1xCJb_.mjs} +4 -4
  164. package/dist/{poll-zB3cX0Kq.mjs → poll-BGWVry_U.mjs} +1 -1
  165. package/dist/{preflight-Dgj2yyY_.mjs → preflight-Beuxqhhf.mjs} +1 -1
  166. package/dist/{publish-tKh9tJeh.mjs → publish-C89wo-cw.mjs} +3 -3
  167. package/dist/{pulse-DhddeMMG.mjs → pulse-BWs9tqVG.mjs} +1 -1
  168. package/dist/{query-o1Z2dgtw.mjs → query-L29u5CEu.mjs} +4 -4
  169. package/dist/{query-B4xK9j0u.mjs → query-ja91JN10.mjs} +4 -4
  170. package/dist/{remapping-DT305P-k.mjs → remapping-B8aqlkSk.mjs} +3 -3
  171. package/dist/{remove-collection-Bsme1OBA.mjs → remove-collection-LbKh838S.mjs} +3 -3
  172. package/dist/{render-Df56xOPJ.mjs → render-C96TStlT.mjs} +1 -1
  173. package/dist/{replace-C3PQwvfl.mjs → replace-B9Cyb9q2.mjs} +4 -4
  174. package/dist/{requirement-check-qqRkRGcN.mjs → requirement-check-XJCPOfWw.mjs} +2 -2
  175. package/dist/{rescan-values-C0P0upEB.mjs → rescan-values-Ca4FVr2Q.mjs} +4 -4
  176. package/dist/{rescan-values-CGN4bjyB.mjs → rescan-values-uxZ22Z0c.mjs} +3 -3
  177. package/dist/{run-C_IZfmQ7.mjs → run-B4zwhVuB.mjs} +404 -85
  178. package/dist/{run-bVD66LrU.mjs → run-BQvsDYzx.mjs} +3 -3
  179. package/dist/{run-ZRY5wy4x.mjs → run-CtGMVO3W.mjs} +4 -4
  180. package/dist/{run-BaEBHum8.mjs → run-DskhDpUQ.mjs} +4 -4
  181. package/dist/{runs-DWm1pIdP.mjs → runs-C0s-CfQE.mjs} +5 -5
  182. package/dist/{runtime-Dd2uBKqN.mjs → runtime-B8leBJ1G.mjs} +6 -5
  183. package/dist/scaffold-BdrbFYhc.mjs +54 -0
  184. package/dist/{schema-DoELV3FT.mjs → schema-8tGj8t5W.mjs} +2 -2
  185. package/dist/{schema-tables-DGyHJnJX.mjs → schema-tables-DZRvTRLf.mjs} +5 -5
  186. package/dist/{schemas-7w9MPZcZ.mjs → schemas-yiE-cMoI.mjs} +4 -4
  187. package/dist/{search-BZh0BwZ9.mjs → search-1-5z-NQO.mjs} +4 -4
  188. package/dist/{search-CyyySVTz.mjs → search-Cdd2cDNA.mjs} +4 -4
  189. package/dist/{segment--JCnZCgL.mjs → segment-DcLtYcgq.mjs} +6 -6
  190. package/dist/{send-c4FpapMf.mjs → send-I5nk7MpB.mjs} +3 -3
  191. package/dist/{set-active-BexM75_s.mjs → set-active-DrgsPOnr.mjs} +2 -2
  192. package/dist/{set-data-actions-BnrX6ARh.mjs → set-data-actions-C-TNEnTJ.mjs} +3 -3
  193. package/dist/{set-enabled-Np4xqFA_.mjs → set-enabled-Co0jIMn0.mjs} +5 -5
  194. package/dist/{set-sensitivity-DDzrkjdv.mjs → set-sensitivity-DCOxoynX.mjs} +3 -3
  195. package/dist/{set-_puxfyaP.mjs → set-vigeEoz9.mjs} +2 -2
  196. package/dist/{setting-BGK1uRxm.mjs → setting-DqHsywkM.mjs} +4 -4
  197. package/dist/{setup-BCliYRAt.mjs → setup-2EJT5aTc.mjs} +2 -2
  198. package/dist/{skill-list-4LagdTq5.mjs → skill-list-Duqt2jXs.mjs} +1 -1
  199. package/dist/{skills-CoXIflBw.mjs → skills-n1IpS3pA.mjs} +3 -3
  200. package/dist/{skills-PULUh2VV.mjs → skills-xJsQWYll.mjs} +3 -3
  201. package/dist/{slug-COdwq63O.mjs → slug-9m4PXe-s.mjs} +1 -1
  202. package/dist/snippet-CDLpUxOv.mjs +19 -0
  203. package/dist/{stash-d3FtnE2F.mjs → stash-C7VstG6F.mjs} +3 -3
  204. package/dist/{status-BJTdBEN4.mjs → status-BclmIIUO.mjs} +3 -3
  205. package/dist/{status-CJLd6Uk6.mjs → status-DZh35kON.mjs} +2 -2
  206. package/dist/{subscription-Dpq45fMP.mjs → subscription-DaEZk3Am.mjs} +6 -6
  207. package/dist/{subscriptions-Cu4km2CN.mjs → subscriptions-DcOtdHen.mjs} +6 -6
  208. package/dist/{summary-3Av5251t.mjs → summary-yoqD8lKK.mjs} +3 -3
  209. package/dist/{sync-schema-BYJuUBsG.mjs → sync-schema-C7mvw4JY.mjs} +4 -4
  210. package/dist/{sync-schema-BNHt8S3P.mjs → sync-schema-QcmZUpbc.mjs} +4 -4
  211. package/dist/table-CSv2nzuP.mjs +26 -0
  212. package/dist/{table-DFbkLSgK.mjs → table-CcUdjp8F.mjs} +1 -1
  213. package/dist/{table-selector-flags-BiA8p56s.mjs → table-selector-flags-FNPxRc9B.mjs} +2 -2
  214. package/dist/{timeline-event-BLBqSAjH.mjs → timeline-event-B3bnxvSU.mjs} +6 -6
  215. package/dist/timeline-xSDfBYEA.mjs +21 -0
  216. package/dist/transform-D5ugnljj.mjs +28 -0
  217. package/dist/{transform-CKeLIqoc.mjs → transform-drjCAqQs.mjs} +1 -1
  218. package/dist/transform-job-C3uL7HVt.mjs +22 -0
  219. package/dist/{transform-tag-CETHXuAi.mjs → transform-tag-Dh_xkEHJ.mjs} +5 -5
  220. package/dist/transform-test-DO4J2dGZ.mjs +16 -0
  221. package/dist/{transforms-CstSQgQ-.mjs → transforms-BhycY9Dy.mjs} +5 -5
  222. package/dist/{transport-D74axlBJ.mjs → transport-B4wfgpqB.mjs} +3 -3
  223. package/dist/{tree-BDhm4E_W.mjs → tree-C0Jqbvr7.mjs} +2 -2
  224. package/dist/{unpublish-BNBSxZFl.mjs → unpublish-0erfDFu-.mjs} +3 -3
  225. package/dist/{unreferenced-B2nDcQ7m.mjs → unreferenced-CHJuU079.mjs} +5 -5
  226. package/dist/{update-BkFy2BV_.mjs → update-BAl0aZsq.mjs} +4 -4
  227. package/dist/{update-BGFGng3a.mjs → update-BGCKUTtV.mjs} +4 -4
  228. package/dist/{update-pzu78yZB.mjs → update-BHz5uxTe.mjs} +3 -3
  229. package/dist/{update-DRw_610p.mjs → update-BPaXLS7J.mjs} +4 -4
  230. package/dist/{update-Cpd1tKqr.mjs → update-BYfjNqm0.mjs} +3 -3
  231. package/dist/{update-BMHPp3nt.mjs → update-Bzbst6T5.mjs} +3 -3
  232. package/dist/{update-ByBqo1if.mjs → update-C7_YQxNg.mjs} +4 -4
  233. package/dist/{update-CWzmrGS_.mjs → update-C8Ec4fCx.mjs} +3 -3
  234. package/dist/{update-BS-E7QLh.mjs → update-CBcKfzne.mjs} +4 -4
  235. package/dist/{update-BenGrugz.mjs → update-CR1zzVES.mjs} +4 -4
  236. package/dist/{update-BA0NxInb.mjs → update-CZkKz3PD.mjs} +4 -4
  237. package/dist/{update-DU3jpGps.mjs → update-CjKfz61h.mjs} +3 -3
  238. package/dist/{update-B6xWuJqk.mjs → update-DAl3J-6n.mjs} +3 -3
  239. package/dist/{update-CFLJWHOT.mjs → update-DSlAN-t5.mjs} +3 -3
  240. package/dist/{update-DrvDC_0m2.mjs → update-Dh1X_b9e2.mjs} +3 -3
  241. package/dist/{update-DxLypq9G.mjs → update-DnGjFqwP.mjs} +6 -6
  242. package/dist/{update-B9iHHoJB.mjs → update-DqN3ikiq.mjs} +4 -4
  243. package/dist/{update-DT8KwBxO.mjs → update-Is5yboYO.mjs} +4 -4
  244. package/dist/{update-dashcard-BU3Wqcnf.mjs → update-dashcard-B8s5t0jG.mjs} +3 -3
  245. package/dist/{update-DKcPzoG9.mjs → update-ujP9d00H.mjs} +3 -3
  246. package/dist/{upgrade-Cq89D4Oe.mjs → upgrade-Dc43HJun.mjs} +2 -2
  247. package/dist/upload-DIiiLP23.mjs +13 -0
  248. package/dist/{upload-INQHve86.mjs → upload-DWwwFANs.mjs} +3 -3
  249. package/dist/{upload-CmwO6anj.mjs → upload-xXTXZQsW.mjs} +1 -1
  250. package/dist/{uuid-BNJKaQGB.mjs → uuid-BNZQ6Dyb.mjs} +2 -2
  251. package/dist/{validate-query-CQ4UPpZX.mjs → validate-query-37KgmRbb.mjs} +1 -1
  252. package/dist/{values-BVtqwRlR.mjs → values-thjqrbtC.mjs} +3 -3
  253. package/dist/{verify-BSlj7S_E.mjs → verify-CLObQxZk.mjs} +3 -3
  254. package/dist/{verify-Bp9ZyPgX.mjs → verify-CqJPc-DW.mjs} +3 -3
  255. package/dist/{verify-BwodYH0C.mjs → verify-DNhhRy4T.mjs} +3 -3
  256. package/dist/{wait-DZclBI3D.mjs → wait-DxeRAukN.mjs} +4 -4
  257. package/dist/{wait-flags-CmIfm6_U.mjs → wait-flags-P5MIy4fp.mjs} +2 -2
  258. package/dist/{window-BoW2xWwm.mjs → window-CI_QD04s.mjs} +1 -1
  259. package/dist/{yaml-BthVUNj3.mjs → yaml-BtI8iDba.mjs} +16 -7
  260. package/package.json +1 -1
  261. package/skill-data/core/SKILL.md +1 -1
  262. package/skill-data/data-action/SKILL.md +2 -3
  263. package/skill-data/data-app/SKILL.md +64 -45
  264. package/skill-data/data-app/template/.env.local.example +1 -0
  265. package/skill-data/data-app/template/README.md +16 -7
  266. package/skill-data/data-app/template/gitignore +0 -1
  267. package/skill-data/data-app/template/package.json +1 -2
  268. package/skill-data/data-app/template/src/metabase.actions.ts +3 -0
  269. package/skill-data/data-app/template/src/metabase.queries.ts +3 -0
  270. package/skill-data/data-app/template/src/metabase.schema.ts +3 -0
  271. package/skill-data/data-app/template/src/theme.ts +3 -1
  272. package/skill-data/data-app-actions/SKILL.md +43 -23
  273. package/skill-data/data-app-data/SKILL.md +86 -64
  274. package/skill-data/data-app-filters/SKILL.md +3 -3
  275. package/skill-data/data-app-ui/SKILL.md +12 -4
  276. package/skill-data/git-sync/SKILL.md +4 -6
  277. package/skill-data/transform/SKILL.md +1 -1
  278. package/dist/api-key-Cia6SHQ9.mjs +0 -13
  279. package/dist/card-C-KsmoJW.mjs +0 -25
  280. package/dist/collection-DsNRlVcN.mjs +0 -21
  281. package/dist/create-D-Eu1nBe.mjs +0 -37
  282. package/dist/dashboard-BRQtw_1e.mjs +0 -35
  283. package/dist/data-action-Bm6QMyV0.mjs +0 -24
  284. package/dist/data-app-DtW2i2SD.mjs +0 -41
  285. package/dist/data-app-project-CHYcJRrU.mjs +0 -58
  286. package/dist/delete-ClT1ROrM.mjs +0 -40
  287. package/dist/dependency-B0KmVz2u.mjs +0 -15
  288. package/dist/document--zYZP-u3.mjs +0 -20
  289. package/dist/field-Bk09crKC.mjs +0 -21
  290. package/dist/git-sync-DMyH3Y85.mjs +0 -29
  291. package/dist/glossary-CcRGJaVi.mjs +0 -14
  292. package/dist/scaffold-BnT99dhY.mjs +0 -50
  293. package/dist/set-bundle-B-qAvkDo.mjs +0 -40
  294. package/dist/snippet-Czzma-M2.mjs +0 -19
  295. package/dist/table-CwfF6ZTZ.mjs +0 -26
  296. package/dist/timeline-C4WCMzDX.mjs +0 -21
  297. package/dist/transform-job-SQ57QOlQ.mjs +0 -22
  298. package/dist/transform-jxF8cjNA.mjs +0 -28
  299. package/dist/transform-test-MlVjTlKo.mjs +0 -16
  300. package/dist/typed-schema-BZkv_z2f.mjs +0 -45
  301. package/dist/update-DrfAOlkf.mjs +0 -36
  302. package/dist/upload-DbDFl9qT.mjs +0 -13
@@ -1,9 +1,9 @@
1
1
  import { c as InternalError, f as ResponseShapeError, h as ValidationError, i as AbortError, o as ConfigError, p as TimeoutError, r as isPlainObject, s as FailedResultError, u as NetworkError } from "./predicates-0C0TsWcJ.mjs";
2
- import { I as CapabilityError, Y as isHttpNotFound, f as serializeJson, ft as profileFlag, o as jsonLineWriter, ot as connectionFlags, q as HttpError, t as interruptSignal } from "./cli.mjs";
3
- import { t as defineMetabaseCommand } from "./runtime-Dd2uBKqN.mjs";
4
- import { o as isTransformTestCode } from "./transform-test-D6QIyu8C.mjs";
2
+ import { R as CapabilityError, Y as HttpError, Z as isHttpNotFound, a as exitCodeFor, c as lineWriter, ct as connectionFlags, lt as formatFlags, m as serializeJson, mt as profileFlag, s as jsonLineWriter, t as interruptSignal, vt as parseInteger } from "./cli.mjs";
3
+ import { i as NotAuthenticatedError, t as defineMetabaseCommand } from "./runtime-B8leBJ1G.mjs";
4
+ import { i as TransformTestRunResult, o as isTransformTestCode } from "./transform-test-D6QIyu8C.mjs";
5
5
  import { a as runProcess, n as ProcessNotFoundError, o as runProcessBinary, r as ProcessTimeoutError, t as ProcessInterruptedError } from "./process-ByKCTa6A.mjs";
6
- import { t as parseMetabaseYaml } from "./yaml-BthVUNj3.mjs";
6
+ import { t as parseMetabaseYaml } from "./yaml-BtI8iDba.mjs";
7
7
  import { z } from "zod";
8
8
  import { rename, rm, writeFile } from "node:fs/promises";
9
9
  import { basename, dirname, join } from "node:path";
@@ -15,33 +15,53 @@ function byName(a, b) {
15
15
  return a.name.localeCompare(b.name);
16
16
  }
17
17
  function assessCoverage(inputs) {
18
+ const untested = (entityId, name, change) => {
19
+ return {
20
+ entityId,
21
+ name,
22
+ change,
23
+ tests: inputs.testedWithoutExpectationsAtHead.has(entityId) ? "no-expectations" : "none"
24
+ };
25
+ };
18
26
  const logicAtBase = /* @__PURE__ */ new Map();
19
27
  for (const { base } of inputs.changes) if (base?.kind === "transform") logicAtBase.set(base.entityId, base.logic);
20
28
  const changed = /* @__PURE__ */ new Map();
21
29
  for (const { head } of inputs.changes) {
22
30
  if (head?.kind !== "transform" || inputs.testedAtHead.has(head.entityId)) continue;
23
31
  const before = logicAtBase.get(head.entityId);
24
- if (before === void 0) changed.set(head.entityId, {
25
- entityId: head.entityId,
26
- name: head.name,
27
- change: "new"
28
- });
29
- else if (!isDeepStrictEqual(before, head.logic)) changed.set(head.entityId, {
30
- entityId: head.entityId,
31
- name: head.name,
32
- change: "edited"
33
- });
32
+ if (before === void 0) changed.set(head.entityId, untested(head.entityId, head.name, "new"));
33
+ else if (!isDeepStrictEqual(before, head.logic)) changed.set(head.entityId, untested(head.entityId, head.name, "edited"));
34
+ }
35
+ for (const { base } of inputs.changes) {
36
+ if (base?.kind !== "transform-test" || !base.hasExpectations || inputs.testedAtHead.has(base.transformId)) continue;
37
+ const name = inputs.transformsAtHead.get(base.transformId);
38
+ if (name !== void 0) changed.set(base.transformId, untested(base.transformId, name, "lost-test"));
34
39
  }
40
+ const untestedChanges = [];
41
+ const untestableChanges = [];
42
+ for (const transform of changed.values()) if (inputs.untestableAtHead.has(transform.entityId)) untestableChanges.push({
43
+ entityId: transform.entityId,
44
+ name: transform.name
45
+ });
46
+ else untestedChanges.push(transform);
35
47
  const untouchedUntested = [];
36
48
  for (const [entityId, name] of inputs.transformsAtHead) if (!inputs.testedAtHead.has(entityId) && !changed.has(entityId)) untouchedUntested.push({
37
49
  entityId,
38
50
  name
39
51
  });
40
52
  return {
41
- untestedChanges: [...changed.values()].toSorted(byName),
53
+ untestedChanges: untestedChanges.toSorted(byName),
54
+ untestableChanges: untestableChanges.toSorted(byName),
42
55
  untouchedUntested: untouchedUntested.toSorted(byName)
43
56
  };
44
57
  }
58
+ function untestedTransforms(assessment) {
59
+ return new Set([
60
+ ...assessment.untestedChanges,
61
+ ...assessment.untestableChanges,
62
+ ...assessment.untouchedUntested
63
+ ].map((transform) => transform.entityId));
64
+ }
45
65
  const UNTOUCHED_CHECK_ID = "untouched";
46
66
  function untouchedWarnings(untouched) {
47
67
  if (untouched.length === 0) return [];
@@ -50,6 +70,22 @@ function untouchedWarnings(untouched) {
50
70
  message: `${untouched.length === 1 ? "1 transform whose logic this change did not touch has" : `${untouched.length} transforms whose logic this change did not touch have`} no tests: ${untouched.map((transform) => transform.name).join(", ")}`
51
71
  }];
52
72
  }
73
+ const UNTESTABLE_CHECK_ID = "untestable";
74
+ function untestableWarning(untestable) {
75
+ return {
76
+ code: "untestable-transform",
77
+ message: `${untestable.map((transform) => transform.name).join(", ")} cannot have transform tests (Python transforms), so coverage does not require one`
78
+ };
79
+ }
80
+ function untestedMessage(transform) {
81
+ const withoutExpectations = transform.tests === "no-expectations";
82
+ if (transform.change === "lost-test") {
83
+ const lost = withoutExpectations ? "transform test with expectations" : "transform test";
84
+ return `${transform.name} lost its last ${lost} in this change`;
85
+ }
86
+ const gap = withoutExpectations ? "none of its transform tests has expectations" : "has no transform tests";
87
+ return `${transform.name} is ${transform.change} in this change and ${gap}`;
88
+ }
53
89
  function coverageStage(assessment) {
54
90
  return {
55
91
  name: "lint",
@@ -65,9 +101,24 @@ function coverageStage(assessment) {
65
101
  ms: 0,
66
102
  detail: {
67
103
  change: transform.change,
68
- message: `${transform.name} is ${transform.change} in this change and has no transform tests`
104
+ message: untestedMessage(transform)
69
105
  }
70
106
  });
107
+ const untestable = assessment.untestableChanges;
108
+ if (untestable.length > 0) report({
109
+ check: {
110
+ kind: "coverage",
111
+ id: UNTESTABLE_CHECK_ID,
112
+ label: "Transforms that cannot have transform tests"
113
+ },
114
+ verdict: "passed",
115
+ warnings: [untestableWarning(untestable)],
116
+ ms: 0,
117
+ detail: { untestable: untestable.map((transform) => ({
118
+ entity_id: transform.entityId,
119
+ name: transform.name
120
+ })) }
121
+ });
71
122
  const untouched = assessment.untouchedUntested;
72
123
  report({
73
124
  check: {
@@ -94,13 +145,11 @@ const Verdict = z.enum([
94
145
  "error",
95
146
  "refused"
96
147
  ]);
97
- const StageStatus = z.enum([
148
+ const JobState = z.enum([
98
149
  "passed",
99
150
  "failed",
100
- "errored",
101
- "skipped"
151
+ "errored"
102
152
  ]);
103
- const JobState = StageStatus.exclude(["skipped"]);
104
153
  const CiWarning = z.object({
105
154
  code: z.string(),
106
155
  message: z.string()
@@ -156,7 +205,7 @@ const CheckResult = EventEnvelope.extend({
156
205
  const StageEnd = EventEnvelope.extend({
157
206
  type: z.literal("stage.end"),
158
207
  stage: StageName,
159
- status: StageStatus,
208
+ status: JobState,
160
209
  stopped: StageStop.nullable()
161
210
  });
162
211
  const JobEnd = EventEnvelope.extend({
@@ -194,20 +243,15 @@ function stageStatus(verdicts, stopped) {
194
243
  if (stopped !== null || verdicts.includes("refused") || verdicts.includes("error")) return "errored";
195
244
  return "passed";
196
245
  }
246
+ function jobState(statuses) {
247
+ if (statuses.includes("failed")) return "failed";
248
+ return statuses.includes("errored") ? "errored" : "passed";
249
+ }
197
250
  async function runStages(stages, emit) {
198
- let state = "passed";
251
+ const statuses = [];
199
252
  let checks = 0;
200
253
  let warnings = 0;
201
254
  for (const stage of stages) {
202
- if (state !== "passed") {
203
- emit({
204
- type: "stage.end",
205
- stage: stage.name,
206
- status: "skipped",
207
- stopped: null
208
- });
209
- continue;
210
- }
211
255
  emit({
212
256
  type: "stage.start",
213
257
  stage: stage.name
@@ -223,21 +267,158 @@ async function runStages(stages, emit) {
223
267
  ...check
224
268
  });
225
269
  });
226
- state = stageStatus(verdicts, stopped);
270
+ const status = stageStatus(verdicts, stopped);
271
+ statuses.push(status);
227
272
  emit({
228
273
  type: "stage.end",
229
274
  stage: stage.name,
230
- status: state,
275
+ status,
231
276
  stopped
232
277
  });
233
278
  }
234
279
  return {
235
- state,
280
+ state: jobState(statuses),
236
281
  checks,
237
282
  warnings
238
283
  };
239
284
  }
240
285
  //#endregion
286
+ //#region src/output/ci-run.ts
287
+ const SHORT_SHA_LENGTH = 7;
288
+ const MS_PER_SECOND = 1e3;
289
+ const PASSED_EXIT_CODE = 0;
290
+ const CHECK_INDENT = " ";
291
+ const DETAIL_INDENT = " ";
292
+ const ROW_INDENT = " ";
293
+ const VERDICT_MARK = {
294
+ passed: "✓",
295
+ failed: "✗",
296
+ error: "!",
297
+ refused: "!"
298
+ };
299
+ const NO_FAILED_CHECKS = "none";
300
+ const FAILED_TEXT = {
301
+ none: "FAILED fix the transform",
302
+ transform: "FAILED fix the transform",
303
+ tests: "FAILED add or restore transform tests",
304
+ both: "FAILED fix the transform and add the missing tests"
305
+ };
306
+ const ERRORED_TEXT = "ERRORED fix the test or the setup";
307
+ const CheckMessage = z.object({ message: z.string() });
308
+ const HttpFailureMessage = CheckMessage.extend({ status: z.number().int() });
309
+ function failedChecksAfter(failed, event) {
310
+ if (event.type !== "check.result" || event.verdict !== "failed") return failed;
311
+ const kind = event.check.kind === "coverage" ? "tests" : "transform";
312
+ if (failed === "none" || failed === kind) return kind;
313
+ return "both";
314
+ }
315
+ function ciEventLines(event, maxRows, failed) {
316
+ switch (event.type) {
317
+ case "job.start": {
318
+ const count = event.checked.length;
319
+ const noun = count === 1 ? "transform" : "transforms";
320
+ return [`mb ci run base ${shortSha(event.snapshot.base)} head ${shortSha(event.snapshot.head)} ${count} ${noun} checked`, ...event.warnings.map((warning) => `⚠ ${warning.message}`)];
321
+ }
322
+ case "stage.start": return [event.stage];
323
+ case "check.result": return checkLines(event, maxRows);
324
+ case "stage.end": return stageEndLines(event);
325
+ case "job.end": return [`${jobEndText(event.state, failed)} (exit ${jobExitCode(event.state)})`];
326
+ default: throw new InternalError(`ci event of no known type: ${JSON.stringify(event)}`);
327
+ }
328
+ }
329
+ function stageEndLines(event) {
330
+ if (event.stopped !== null) return [`${event.stage} stopped: ${event.stopped.message} (${event.stopped.code})`];
331
+ return [];
332
+ }
333
+ function shortSha(sha) {
334
+ return sha.slice(0, SHORT_SHA_LENGTH);
335
+ }
336
+ function jobEndText(state, failed) {
337
+ if (state === "passed") return "PASSED";
338
+ return state === "failed" ? FAILED_TEXT[failed] : ERRORED_TEXT;
339
+ }
340
+ function jobExitCode(state) {
341
+ return state === "passed" ? PASSED_EXIT_CODE : exitCodeFor("failed");
342
+ }
343
+ function checkLines(event, maxRows) {
344
+ const parts = [`${CHECK_INDENT}${VERDICT_MARK[event.verdict]} ${event.check.label}`];
345
+ if (event.verdict === "error" || event.verdict === "refused") parts.push(event.verdict);
346
+ if (event.ms > 0) parts.push(duration(event.ms));
347
+ const warnings = event.warnings.map((warning) => `${DETAIL_INDENT}⚠ ${warning.message}`);
348
+ const detail = event.verdict === "passed" ? [] : detailLines(event, maxRows);
349
+ return [
350
+ parts.join(" "),
351
+ ...warnings,
352
+ ...detail
353
+ ];
354
+ }
355
+ function duration(ms) {
356
+ if (ms < MS_PER_SECOND) return `${Math.round(ms)}ms`;
357
+ return `${(ms / MS_PER_SECOND).toFixed(1)}s`;
358
+ }
359
+ function detailLines(event, maxRows) {
360
+ const result = TransformTestRunResult.safeParse(event.detail);
361
+ if (result.success) return result.data.expectations.flatMap((expectation) => expectationLines(expectation, maxRows));
362
+ const httpFailure = HttpFailureMessage.safeParse(event.detail);
363
+ if (httpFailure.success) return [`${DETAIL_INDENT}${httpFailure.data.message} (HTTP ${httpFailure.data.status})`];
364
+ const message = CheckMessage.safeParse(event.detail);
365
+ if (!message.success) throw new InternalError(`ci check "${event.check.label}" has a detail of no known shape: ${message.error.message}`);
366
+ return [`${DETAIL_INDENT}${message.data.message}`];
367
+ }
368
+ function expectationLines(result, maxRows) {
369
+ const line = `${DETAIL_INDENT}${VERDICT_MARK[result.status]} ${result.name}`;
370
+ if (result.status === "passed") return [line];
371
+ if (result.status === "error") return [`${line} error: ${result.error.message}`];
372
+ if (result.type === "empty") {
373
+ const rows = result.sample.length + result.truncated;
374
+ const sample = result.sample.map((row) => `row ${cellsText(row, columnNames(result.columns))}`);
375
+ return [`${line} ${rows} ${rowNoun(rows)}, expected none`, ...capped(sample, result.truncated, maxRows)];
376
+ }
377
+ const counts = result["row-counts"];
378
+ const rowLines = rowDiffLines(result["missing-rows"], result["extra-rows"], columnNames(result.columns));
379
+ const mismatches = result["cell-mismatches"].map((mismatch) => `${mismatch.column}: expected ${cellText(mismatch.expected)}, got ${cellText(mismatch.actual)}`);
380
+ return [`${line} ${counts.actual} ${rowNoun(counts.actual)}, expected ${counts.expected}`, ...capped([...rowLines, ...mismatches], result.truncated, maxRows)];
381
+ }
382
+ function rowNoun(count) {
383
+ return count === 1 ? "row" : "rows";
384
+ }
385
+ function columnNames(columns) {
386
+ return columns.map((column) => column.name);
387
+ }
388
+ function capped(lines, truncated, maxRows) {
389
+ const shown = lines.slice(0, maxRows).map((line) => `${ROW_INDENT}${line}`);
390
+ const hidden = Math.max(lines.length - maxRows, 0) + truncated;
391
+ if (hidden === 0) return shown;
392
+ return [...shown, `${ROW_INDENT}+${hidden} more`];
393
+ }
394
+ function rowDiffLines(missing, extra, columns) {
395
+ const [key, ...rest] = columns;
396
+ if (key === void 0) return [...missing.map((row) => `missing ${cellsText(row, Object.keys(row))}`), ...extra.map((row) => `extra ${cellsText(row, Object.keys(row))}`)];
397
+ const unpaired = [...extra];
398
+ const lines = [];
399
+ for (const row of missing) {
400
+ const index = unpaired.findIndex((candidate) => candidate[key] === row[key]);
401
+ const partner = unpaired[index];
402
+ if (partner === void 0) {
403
+ lines.push(`missing ${cellsText(row, columns)}`);
404
+ continue;
405
+ }
406
+ unpaired.splice(index, 1);
407
+ const differing = rest.filter((column) => row[column] !== partner[column]);
408
+ lines.push(`missing ${cellsText(row, [key, ...differing])}`);
409
+ lines.push(`extra ${cellsText(partner, [key, ...differing])}`);
410
+ }
411
+ for (const row of unpaired) lines.push(`extra ${cellsText(row, columns)}`);
412
+ return lines;
413
+ }
414
+ function cellsText(row, columns) {
415
+ return columns.map((column) => `${column}=${cellText(row[column])}`).join(" ");
416
+ }
417
+ function cellText(value) {
418
+ if (value === void 0) return "(absent)";
419
+ return value === null ? "null" : String(value);
420
+ }
421
+ //#endregion
241
422
  //#region src/core/ci-affected.ts
242
423
  const SINGLE_PATH_STATUSES = /* @__PURE__ */ new Map([
243
424
  ["A", "added"],
@@ -326,7 +507,22 @@ const TransformDocument = z.object({
326
507
  source: z.unknown(),
327
508
  target: z.unknown()
328
509
  }).loose();
329
- const TransformTestDocument = z.object({ transform_id: z.string().min(1) }).loose();
510
+ const TransformLogicDocument = TransformDocument.pick({
511
+ source: true,
512
+ target: true
513
+ });
514
+ const QUERY_SOURCE_TYPE = "query";
515
+ function isTestableSource(source) {
516
+ if (!isPlainObject(source)) return true;
517
+ const type = source["type"];
518
+ return typeof type !== "string" || type === QUERY_SOURCE_TYPE;
519
+ }
520
+ const TRANSFORM_RULE = "a Transform must have an entity_id, a name, a source and a target";
521
+ const TRANSFORM_TEST_RULE = "a TransformTest must name its transform_id, and its expectations must be a list";
522
+ const TransformTestDocument = z.object({
523
+ transform_id: z.string().min(1),
524
+ expectations: z.array(z.unknown()).nullish()
525
+ }).loose();
330
526
  const SerdesMeta = z.array(z.object({ model: z.string() }).loose()).min(1);
331
527
  function parseOrRefuse(path, schema, value, rule) {
332
528
  const parsed = schema.safeParse(value);
@@ -336,26 +532,41 @@ function parseOrRefuse(path, schema, value, rule) {
336
532
  });
337
533
  return parsed.data;
338
534
  }
339
- function classifyContentFile(path, document) {
535
+ function classifyContentFile(path, yaml) {
536
+ const document = yaml.yaml12;
340
537
  if (!isPlainObject(document) || !(SERDES_META_KEY in document)) return { kind: "other" };
341
538
  const last = parseOrRefuse(path, SerdesMeta, document[SERDES_META_KEY], `${SERDES_META_KEY} must be a non-empty list of entries with a model`).at(-1);
342
539
  if (last === void 0) throw new InternalError(`${path}: ${SERDES_META_KEY} passed a non-empty check but has no entries`);
343
540
  if (last.model === "Transform") {
344
- const transform = parseOrRefuse(path, TransformDocument, document, "a Transform must have an entity_id, a name, a source and a target");
541
+ const transform = parseOrRefuse(path, TransformDocument, document, TRANSFORM_RULE);
542
+ const asYaml11 = parseOrRefuse(path, TransformLogicDocument, yaml.yaml11, TRANSFORM_RULE);
345
543
  return {
346
544
  kind: "transform",
347
545
  entityId: transform.entity_id,
348
546
  name: transform.name,
349
547
  logic: {
350
- source: transform.source,
351
- target: transform.target
352
- }
548
+ yaml12: {
549
+ source: transform.source,
550
+ target: transform.target
551
+ },
552
+ yaml11: {
553
+ source: asYaml11.source,
554
+ target: asYaml11.target
555
+ }
556
+ },
557
+ testable: isTestableSource(transform.source)
558
+ };
559
+ }
560
+ if (last.model === "TransformTest") {
561
+ const test = parseOrRefuse(path, TransformTestDocument, document, TRANSFORM_TEST_RULE);
562
+ const expectations = test.expectations;
563
+ const hasExpectations = expectations !== void 0 && expectations !== null && expectations.length > 0;
564
+ return {
565
+ kind: "transform-test",
566
+ transformId: test.transform_id,
567
+ hasExpectations
353
568
  };
354
569
  }
355
- if (last.model === "TransformTest") return {
356
- kind: "transform-test",
357
- transformId: parseOrRefuse(path, TransformTestDocument, document, "a TransformTest must name its transform_id").transform_id
358
- };
359
570
  return { kind: "other" };
360
571
  }
361
572
  function transformEntityId(file) {
@@ -381,14 +592,19 @@ function affectedTransforms(changes) {
381
592
  }
382
593
  return [...touched].filter((entityId) => atHead.has(entityId) || !atBase.has(entityId));
383
594
  }
384
- const TRANSFORM_MODEL_PATTERN = `model["']?:[[:space:]]*["']?Transform`;
595
+ const TRANSFORM_MODEL_PATTERN = "Transform";
385
596
  function headTransforms(files) {
386
597
  const atHead = {
387
598
  names: /* @__PURE__ */ new Map(),
388
- tested: /* @__PURE__ */ new Set()
599
+ tested: /* @__PURE__ */ new Set(),
600
+ testedWithoutExpectations: /* @__PURE__ */ new Set(),
601
+ untestable: /* @__PURE__ */ new Set()
389
602
  };
390
- for (const file of files) if (file?.kind === "transform") atHead.names.set(file.entityId, file.name);
391
- else if (file?.kind === "transform-test") atHead.tested.add(file.transformId);
603
+ for (const file of files) if (file?.kind === "transform") {
604
+ atHead.names.set(file.entityId, file.name);
605
+ if (!file.testable) atHead.untestable.add(file.entityId);
606
+ } else if (file?.kind === "transform-test") (file.hasExpectations ? atHead.tested : atHead.testedWithoutExpectations).add(file.transformId);
607
+ for (const entityId of atHead.tested) atHead.testedWithoutExpectations.delete(entityId);
392
608
  return atHead;
393
609
  }
394
610
  const CONTENT_TOP_LEVEL_FOLDERS = [
@@ -422,7 +638,7 @@ function noContentChangesWarning(entries) {
422
638
  if (entries.some((entry) => isContentPath(entry.path) || entry.status === "renamed" && isContentPath(entry.oldPath))) return null;
423
639
  return {
424
640
  code: "no-content-changes",
425
- message: "the change touches no Metabase content files, so CI checked nothing"
641
+ message: "the change touches no Metabase content files, so no transform test ran"
426
642
  };
427
643
  }
428
644
  function uncommittedContentWarning(paths) {
@@ -433,6 +649,13 @@ function uncommittedContentWarning(paths) {
433
649
  message: `${content.length === 1 ? "1 content file has uncommitted changes" : `${content.length} content files have uncommitted changes`}, which CI does not see; it checks the committed version: ${content.join(", ")}`
434
650
  };
435
651
  }
652
+ function unreadContentWarning(reasons) {
653
+ if (reasons.length === 0) return null;
654
+ return {
655
+ code: "unread-content",
656
+ message: `${reasons.length === 1 ? "1 content file this change did not touch could not be read, so the coverage check skipped it" : `${reasons.length} content files this change did not touch could not be read, so the coverage check skipped them`}: ${reasons.join("; ")}`
657
+ };
658
+ }
436
659
  //#endregion
437
660
  //#region src/runtime/git.ts
438
661
  const GIT = "git";
@@ -598,21 +821,46 @@ async function readFilesAt(repo, commit, paths, signal) {
598
821
  //#region src/commands/ci/job-plan.ts
599
822
  const CONTENT_PATHSPECS = CONTENT_TOP_LEVEL_FOLDERS.map((folder) => `${folder}/*.yaml`);
600
823
  const FILE_MODES = /* @__PURE__ */ new Set(["100644", "100755"]);
601
- async function classifyAt(repo, commit, paths, signal) {
824
+ function unread(commit, path, message) {
825
+ return {
826
+ kind: "unread",
827
+ error: new ValidationError(message, {
828
+ source: `${commit}:${path}`,
829
+ zodIssues: []
830
+ })
831
+ };
832
+ }
833
+ async function readAt(repo, commit, paths, signal) {
602
834
  const content = paths.filter(isContentPath);
603
- if (content.length === 0) return /* @__PURE__ */ new Map();
835
+ const readings = /* @__PURE__ */ new Map();
836
+ if (content.length === 0) return readings;
604
837
  const modes = await fileModesAt(repo, commit, signal);
838
+ const readable = [];
605
839
  for (const path of content) {
606
840
  const mode = modes.get(path);
607
841
  if (mode === void 0) throw new InternalError(`git listed ${path} at ${commit}, but its tree does not hold it`);
608
- if (!FILE_MODES.has(mode)) throw new ValidationError(`${commit}:${path} is not a regular file (git mode ${mode}): Metabase would import what it points to, which this check cannot read; commit the file itself`, {
609
- source: `${commit}:${path}`,
610
- zodIssues: []
842
+ if (!FILE_MODES.has(mode)) {
843
+ const reason = `${commit}:${path} is not a regular file (git mode ${mode}): Metabase would import what it points to, which this check cannot read; commit the file itself`;
844
+ readings.set(path, unread(commit, path, reason));
845
+ } else if (path.includes("\n")) {
846
+ const reason = `${commit}:${JSON.stringify(path)}: a path with a newline cannot be read; rename the file`;
847
+ readings.set(path, unread(commit, path, reason));
848
+ } else readable.push(path);
849
+ }
850
+ for (const [path, text] of await readFilesAt(repo, commit, readable, signal)) try {
851
+ const file = classifyContentFile(path, parseMetabaseYaml(text, `${commit}:${path}`));
852
+ readings.set(path, {
853
+ kind: "read",
854
+ file
855
+ });
856
+ } catch (error) {
857
+ if (!(error instanceof ValidationError)) throw error;
858
+ readings.set(path, {
859
+ kind: "unread",
860
+ error
611
861
  });
612
862
  }
613
- const classified = /* @__PURE__ */ new Map();
614
- for (const [path, text] of await readFilesAt(repo, commit, content, signal)) classified.set(path, classifyContentFile(path, parseMetabaseYaml(text, `${commit}:${path}`)));
615
- return classified;
863
+ return readings;
616
864
  }
617
865
  function basePath(entry) {
618
866
  if (entry.status === "added") return null;
@@ -624,23 +872,40 @@ function headPath(entry) {
624
872
  function present(paths) {
625
873
  return paths.filter((path) => path !== null);
626
874
  }
627
- function sideOf(files, path) {
875
+ function readingOf(readings, path) {
628
876
  if (path === null || !isContentPath(path)) return null;
629
- const file = files.get(path);
630
- if (file === void 0) throw new InternalError(`${path} was not read, though the diff names it`);
631
- return file;
877
+ const reading = readings.get(path);
878
+ if (reading === void 0) throw new InternalError(`${path} was not read, though git names it`);
879
+ return reading;
880
+ }
881
+ function headSide(reading) {
882
+ if (reading?.kind === "unread") throw reading.error;
883
+ return reading === null ? null : reading.file;
884
+ }
885
+ function baseSide(reading) {
886
+ return reading?.kind === "read" ? reading.file : null;
632
887
  }
633
888
  async function classifyChanges(repo, forkPoint, head, entries, signal) {
634
- const [baseFiles, headFiles] = await Promise.all([classifyAt(repo, forkPoint, present(entries.map(basePath)), signal), classifyAt(repo, head, present(entries.map(headPath)), signal)]);
635
- return entries.map((entry) => ({
889
+ const changedPaths = new Set(entries.map((entry) => entry.path));
890
+ const unchanged = (await filesMatching(repo, head, TRANSFORM_MODEL_PATTERN, CONTENT_PATHSPECS, signal)).filter((path) => !changedPaths.has(path));
891
+ const [baseReadings, headReadings] = await Promise.all([readAt(repo, forkPoint, present(entries.map(basePath)), signal), readAt(repo, head, [...present(entries.map(headPath)), ...unchanged], signal)]);
892
+ const changes = entries.map((entry) => ({
636
893
  path: entry.path,
637
- base: sideOf(baseFiles, basePath(entry)),
638
- head: sideOf(headFiles, headPath(entry))
894
+ base: baseSide(readingOf(baseReadings, basePath(entry))),
895
+ head: headSide(readingOf(headReadings, headPath(entry)))
639
896
  }));
640
- }
641
- async function scanHead(repo, head, changes, changedPaths, signal) {
642
- const unchangedFiles = (await classifyAt(repo, head, (await filesMatching(repo, head, TRANSFORM_MODEL_PATTERN, CONTENT_PATHSPECS, signal)).filter((path) => !changedPaths.has(path)), signal)).values();
643
- return headTransforms([...changes.map((change) => change.head), ...unchangedFiles]);
897
+ const unchangedAtHead = [];
898
+ const unreadUnchanged = [];
899
+ for (const path of unchanged) {
900
+ const reading = readingOf(headReadings, path);
901
+ if (reading?.kind === "read") unchangedAtHead.push(reading.file);
902
+ else if (reading?.kind === "unread") unreadUnchanged.push(reading.error);
903
+ }
904
+ return {
905
+ changes,
906
+ unchangedAtHead,
907
+ unreadUnchanged
908
+ };
644
909
  }
645
910
  async function planJob(repoPath, baseRef, headRef, signal) {
646
911
  const repo = await repositoryRoot(repoPath, signal);
@@ -651,12 +916,11 @@ async function planJob(repoPath, baseRef, headRef, signal) {
651
916
  ]);
652
917
  const forkPoint = await mergeBase(repo, base, head, signal);
653
918
  const entries = parseNameStatus(await diffNameStatus(repo, forkPoint, head, signal));
654
- const changes = await classifyChanges(repo, forkPoint, head, entries, signal);
655
- const atHead = await scanHead(repo, head, changes, new Set(entries.map((entry) => entry.path)), signal);
919
+ const { changes, unchangedAtHead, unreadUnchanged } = await classifyChanges(repo, forkPoint, head, entries, signal);
920
+ const atHead = headTransforms([...changes.map((change) => change.head), ...unchangedAtHead]);
656
921
  const checked = [];
657
922
  const warnings = [];
658
- const noContent = noContentChangesWarning(entries);
659
- if (noContent !== null) warnings.push(noContent);
923
+ for (const warning of [noContentChangesWarning(entries), unreadContentWarning(unreadUnchanged.map((error) => error.message))]) if (warning !== null) warnings.push(warning);
660
924
  for (const entityId of affectedTransforms(changes)) {
661
925
  const name = atHead.names.get(entityId);
662
926
  if (name === void 0) {
@@ -674,7 +938,9 @@ async function planJob(repoPath, baseRef, headRef, signal) {
674
938
  const coverage = assessCoverage({
675
939
  changes,
676
940
  transformsAtHead: atHead.names,
677
- testedAtHead: atHead.tested
941
+ testedAtHead: atHead.tested,
942
+ testedWithoutExpectationsAtHead: atHead.testedWithoutExpectations,
943
+ untestableAtHead: atHead.untestable
678
944
  });
679
945
  if (head === checkout) {
680
946
  const uncommitted = uncommittedContentWarning(parseStatusPorcelain(await statusPorcelain(repo, signal)));
@@ -765,6 +1031,10 @@ const INVALID_ENTITY_ID = {
765
1031
  error_code: "ci.invalid-entity-id",
766
1032
  message: "the transform's entity_id in the branch is not a valid entity id"
767
1033
  };
1034
+ const NO_TESTS_ON_INSTANCE = {
1035
+ error_code: "ci.no-tests-on-instance",
1036
+ message: "the branch has a test for this transform, but the Metabase instance lists none, so no test ran; load the branch into it first"
1037
+ };
768
1038
  const TRANSFORM_UNREADABLE = {
769
1039
  error_code: "ci.transform-unreadable",
770
1040
  message: "the CI identity cannot read this transform, so Metabase hides its tests: reading it needs query permission on every table it reads"
@@ -816,6 +1086,13 @@ function lookupStop(error) {
816
1086
  message: failure.message
817
1087
  };
818
1088
  }
1089
+ function connectStop(error) {
1090
+ if (!(error instanceof NotAuthenticatedError)) return null;
1091
+ return {
1092
+ code: "ci.no-credential",
1093
+ message: `no Metabase credential, so the transform tests did not run: ${error.message}`
1094
+ };
1095
+ }
819
1096
  const REFUSAL_FOR_FORBIDDEN = {
820
1097
  read: TRANSFORM_UNREADABLE,
821
1098
  list: null,
@@ -921,7 +1198,7 @@ function reportFailure(failure, check, ms, report) {
921
1198
  });
922
1199
  return null;
923
1200
  }
924
- async function checkTransform(transform, transformId, port, report) {
1201
+ async function checkTransform(transform, transformId, untestedInBranch, port, report) {
925
1202
  const transformCheck = {
926
1203
  kind: "transform-test",
927
1204
  id: transform.entity_id,
@@ -932,6 +1209,16 @@ async function checkTransform(transform, transformId, port, report) {
932
1209
  if (read.kind !== "settled") return reportFailure(read, transformCheck, port.nowMs() - started, report);
933
1210
  const tests = await attempt("list", () => port.listTests(transformId));
934
1211
  if (tests.kind !== "settled") return reportFailure(tests, transformCheck, port.nowMs() - started, report);
1212
+ if (tests.value.length === 0 && !untestedInBranch) {
1213
+ report({
1214
+ check: transformCheck,
1215
+ verdict: "refused",
1216
+ warnings: [],
1217
+ ms: port.nowMs() - started,
1218
+ detail: NO_TESTS_ON_INSTANCE
1219
+ });
1220
+ return null;
1221
+ }
935
1222
  for (const test of tests.value.toSorted((a, b) => a.id - b.id)) {
936
1223
  const check = {
937
1224
  kind: "transform-test",
@@ -956,11 +1243,18 @@ async function checkTransform(transform, transformId, port, report) {
956
1243
  }
957
1244
  return null;
958
1245
  }
959
- function unitStage(checked, connect) {
1246
+ function unitStage(checked, untestedInBranch, connect) {
960
1247
  return {
961
1248
  name: "unit",
962
1249
  run: async (report) => {
963
- const port = await connect();
1250
+ let port;
1251
+ try {
1252
+ port = await connect();
1253
+ } catch (error) {
1254
+ const stop = connectStop(error);
1255
+ if (stop === null) throw error;
1256
+ return stop;
1257
+ }
964
1258
  let lookups;
965
1259
  try {
966
1260
  lookups = await port.lookUpTransforms(checked.map((transform) => transform.entity_id));
@@ -986,7 +1280,8 @@ function unitStage(checked, connect) {
986
1280
  });
987
1281
  continue;
988
1282
  }
989
- const stop = await checkTransform(transform, lookup.id, port, report);
1283
+ const untested = untestedInBranch.has(transform.entity_id);
1284
+ const stop = await checkTransform(transform, lookup.id, untested, port, report);
990
1285
  if (stop !== null) return stop;
991
1286
  }
992
1287
  return null;
@@ -998,9 +1293,14 @@ function unitStage(checked, connect) {
998
1293
  var run_default = defineMetabaseCommand({
999
1294
  meta: {
1000
1295
  name: "run",
1001
- description: "Check the transforms a branch changed, writing one JSON event per line"
1296
+ description: "Check the transforms a branch changed, reporting each check as it finishes"
1002
1297
  },
1003
- details: "Compares --head with its merge base with --base and finds the transforms whose files or tests changed. Two stages check them: `lint` fails each transform the branch adds or whose logic (source or target) it edits that has no transform test, and warns about untested transforms the branch did not touch; `unit` runs the changed transforms' tests on the Metabase instance that --url, MB_URL or --profile names, in that order, which must already hold the branch's content, and is skipped when no transform changed. Content files are the `.yaml` files Metabase's import reads: not dotfiles, under a top-level serialization folder such as `transforms/` or `collections/`. Only committed content is checked: when --head is the checked-out commit, a content file with uncommitted changes is listed in an `uncommitted-content` warning, and its committed version is checked. Writes one JSON event per line (`job.start`, then each stage and check, then `job.end`); `job.start.checked` lists the transforms the job covered, and a `no-content-changes` warning says when the change touches no content file, so nothing was checked; each transform test's result is passed through as the check's `detail`. Exits 0 when the job passes. Exits 1 after `job.end` when it does not: `state` is `failed` when a check failed (fix the transform), and `errored` when no check failed but one could not judge the change (fix the test or the setup); a failure outranks errors, since it is a finding about the change. A test run that times out, loses its connection or gets an HTTP error is an `error` check whose `detail.error_code` starts with `ci.`, and the next test runs. A failure every later request would repeat ends the unit stage early, `errored` unless a check failed, with the reason in `stage.end.stopped`: Metabase rejects the credential, the server has no transform testing, the entity-id lookup fails, or Metabase cannot be reached before a test runs. Exits 2 on a usage or config error: a bad flag, a --repo, --base or --head that git cannot resolve or relate, or no Metabase credential when a transform needs testing, which is found after `job.start`. Exits 1 on a crash, such as a changed content file that does not parse, and 130 on an interrupt: the stream stops before `job.end`, the error is on stderr, and no --results file is left, not even an earlier run's. When the reader closes stdout early, the job still runs to the end, writes --results and exits by its state.",
1298
+ details: [
1299
+ "Compares --head with its merge base with --base and finds the transforms whose files or tests changed. Two stages check them: `lint` fails each transform the branch adds or whose logic (source or target) it edits that has no transform test, fails each transform the branch leaves at head without the last of its transform tests, and warns about untested transforms whose logic the branch did not change. A transform whose source type is not `query`, such as a Python transform, cannot have transform tests, so `lint` does not fail it: an `untestable-transform` warning lists each such transform the branch adds, edits or strips of its tests. A change to the transform's query text, whitespace included, is an edit; a change to the YAML layout alone is not. A transform test is a TransformTest file at --head whose `transform_id` names the transform and whose `expectations` list is not empty; a test without expectations checks nothing, so it does not count. `unit` runs the instance's tests of the transforms in `job.start.checked`, on the Metabase instance that --url, MB_URL or --profile names, in that order, which must already hold the branch's content; it is absent when `checked` is empty, so a job with nothing to test never connects to Metabase, and it runs even when `lint` fails, so one job reports both stages' findings. A checked transform the branch tests but the instance lists no test for is `refused` with `detail.error_code` `ci.no-tests-on-instance`. Content files are the `.yaml` files Metabase's import reads: not dotfiles, under a top-level serialization folder such as `transforms/` or `collections/`. Only committed content is checked: when --head is the checked-out commit, a content file with uncommitted changes is listed in an `uncommitted-content` warning, and its committed version is checked.",
1300
+ "With --format json, the default when stdout is not a terminal, writes one JSON event per line (`job.start`, then each stage and check, then `job.end`); `job.start.checked` lists the transforms the job covered, and a `no-content-changes` warning says when the change touches no content file, so no transform test ran; an `unread-content` warning lists the content files at --head that the change did not touch and this check cannot read, which coverage skips; each transform test's result is passed through as the check's `detail`, with every row the server sent. With --format text, the default on a terminal, writes the same events as lines for a person: each failed expectation with up to --max-rows lines of its missing and extra rows, keyed by the first column, then a count of the rest. The --results file is JSON whatever the format, and holds every row the server sent.",
1301
+ "Exits 0 when the job passes. Exits 1 after `job.end` when it does not: `state` is `failed` when a check failed (fix the transform), and `errored` when no check failed but one could not judge the change (fix the test or the setup); a failure outranks errors, since it is a finding about the change. Exits 2 on a usage or config error: a bad flag, a --repo, --base or --head that git cannot resolve or relate, or, found after `job.start` when the unit stage connects, a configuration it cannot use, such as a browser-login profile bound to another URL; a missing credential is not one, since it ends the unit stage instead.",
1302
+ "A test run that times out, loses its connection or gets an HTTP error is an `error` check whose `detail.error_code` starts with `ci.`, and the next test runs. A failure every later request would repeat ends the unit stage early, `errored` unless a check failed, with the reason in `stage.end.stopped`: no Metabase credential is configured (`ci.no-credential`, before any request), Metabase rejects the credential, the server has no transform testing, the entity-id lookup fails, or Metabase cannot be reached before a test runs. Exits 1 on a crash, such as a content file the change touched that does not parse at --head, and 130 on an interrupt: the stream stops before `job.end`, the error is on stderr, and no --results file is left, not even an earlier run's. When the reader closes stdout early, the job still runs to the end, writes --results and exits by its state."
1303
+ ].join("\n\n"),
1004
1304
  requires: [
1005
1305
  "eidTranslation.translate",
1006
1306
  "transform.get",
@@ -1008,6 +1308,7 @@ var run_default = defineMetabaseCommand({
1008
1308
  "transformTest.run"
1009
1309
  ],
1010
1310
  args: {
1311
+ ...formatFlags,
1011
1312
  ...profileFlag,
1012
1313
  ...connectionFlags,
1013
1314
  base: {
@@ -1027,12 +1328,21 @@ var run_default = defineMetabaseCommand({
1027
1328
  },
1028
1329
  results: {
1029
1330
  type: "string",
1030
- description: "Also write every event to this file, as one JSON array"
1331
+ description: "Also write every event to this file, as one JSON array, with every row the server sent"
1332
+ },
1333
+ "max-rows": {
1334
+ type: "string",
1335
+ description: "Row lines each failed expectation shows under --format text; 0 shows only counts. JSON output and the --results file keep every row",
1336
+ default: String(5)
1031
1337
  }
1032
1338
  },
1033
1339
  outputSchema: CiEvent,
1034
- examples: ["mb ci run --base origin/main", "mb ci run --base origin/main --results results.json"],
1035
- async run({ args, getClient }) {
1340
+ examples: ["mb ci run --base origin/main", "mb ci run --base origin/main --format text --results results.json"],
1341
+ async run({ args, ctx, getClient }) {
1342
+ const maxRows = parseInteger(args["max-rows"], {
1343
+ name: "--max-rows",
1344
+ min: 0
1345
+ });
1036
1346
  const state = await runJob({
1037
1347
  repo: args.repo,
1038
1348
  base: args.base,
@@ -1040,16 +1350,25 @@ var run_default = defineMetabaseCommand({
1040
1350
  stages: async (plan) => {
1041
1351
  const lint = coverageStage(plan.coverage);
1042
1352
  if (plan.checked.length === 0) return [lint];
1043
- return [lint, unitStage(plan.checked, async () => clientPort(await getClient()))];
1353
+ const connect = async () => clientPort(await getClient());
1354
+ return [lint, unitStage(plan.checked, untestedTransforms(plan.coverage), connect)];
1044
1355
  },
1045
1356
  job: randomUUID(),
1046
1357
  now: () => /* @__PURE__ */ new Date(),
1047
- write: jsonLineWriter(process.stdout),
1358
+ write: ctx.format === "json" ? jsonLineWriter(process.stdout) : textEventWriter(maxRows),
1048
1359
  resultsPath: args.results ?? null,
1049
1360
  signal: interruptSignal
1050
1361
  });
1051
1362
  if (state !== "passed") throw new FailedResultError(`ci job ${state}`);
1052
1363
  }
1053
1364
  });
1365
+ function textEventWriter(maxRows) {
1366
+ const writeLine = lineWriter(process.stdout);
1367
+ let failed = NO_FAILED_CHECKS;
1368
+ return (event) => {
1369
+ for (const line of ciEventLines(event, maxRows, failed)) writeLine(line);
1370
+ failed = failedChecksAfter(failed, event);
1371
+ };
1372
+ }
1054
1373
  //#endregion
1055
1374
  export { run_default as default };