@metabase/cli 0.5.7-alpha.bryan-rci-6-coverage.6f886fd → 0.5.8

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 (273) hide show
  1. package/dist/{add-collection-C7c6Y3T3.mjs → add-collection-DIrpeq9O.mjs} +3 -3
  2. package/dist/{alert-J0qLs6rc.mjs → alert-DuUreVMq.mjs} +7 -7
  3. package/dist/{alerts-mjDIdof3.mjs → alerts-C7vJ-NAI.mjs} +4 -4
  4. package/dist/api-key-Cia6SHQ9.mjs +13 -0
  5. package/dist/{append-yaTEz2AE.mjs → append-v3fakJOd.mjs} +4 -4
  6. package/dist/{archive-Cg67G-qa.mjs → archive-5pjvTser.mjs} +3 -3
  7. package/dist/{archive-CgcNck0G.mjs → archive-BEhhmkTO.mjs} +3 -3
  8. package/dist/{archive-rhxBPOrL.mjs → archive-BrGj2yoP.mjs} +3 -3
  9. package/dist/{archive-B93SkIvs.mjs → archive-Bt8fwInI.mjs} +3 -3
  10. package/dist/{archive-VZ0dgYTd.mjs → archive-CmhV_hlP.mjs} +3 -3
  11. package/dist/{archive-DYdHrm6V.mjs → archive-CuygjxgQ.mjs} +3 -3
  12. package/dist/{archive-Dg9eMYU6.mjs → archive-Cy6RUJaD.mjs} +3 -3
  13. package/dist/{archive-CbtQNK96.mjs → archive-CyhH-395.mjs} +3 -3
  14. package/dist/{archive-DzXi2Frh.mjs → archive-D299FxXt.mjs} +3 -3
  15. package/dist/{archive-BmU6DnFz.mjs → archive-DdPaXaJo.mjs} +3 -3
  16. package/dist/{archive-DrCoSvio.mjs → archive-DwVbn3FG.mjs} +3 -3
  17. package/dist/{archive-P-y61Wi_.mjs → archive-TBPRGm7s.mjs} +3 -3
  18. package/dist/{auth-CxN5tDg1.mjs → auth-B63Hfuwg.mjs} +5 -5
  19. package/dist/{branches-1ZK62u6h.mjs → branches-BM0L124r.mjs} +2 -2
  20. package/dist/{breaking-Bv4wZN9X.mjs → breaking-VMUrgVVE.mjs} +3 -3
  21. package/dist/{broken-BU7MrnHI.mjs → broken-BbpNq5WG.mjs} +4 -4
  22. package/dist/{bulk-discard-values-6TLs4p2e.mjs → bulk-discard-values-Cohox-Zb.mjs} +4 -4
  23. package/dist/{bulk-edit-CYnFA1xm.mjs → bulk-edit-B9-XH592.mjs} +2 -2
  24. package/dist/{bulk-rescan-values-T__5XlPh.mjs → bulk-rescan-values-CsWKirZw.mjs} +3 -3
  25. package/dist/{bulk-sync-schema-V1Tpqvxg.mjs → bulk-sync-schema-CorpcAjz.mjs} +3 -3
  26. package/dist/{cancel-RTd-Bg5f.mjs → cancel-iGUVm1h2.mjs} +3 -3
  27. package/dist/{cancel-task-B6GpgrME.mjs → cancel-task-DWiicb_t.mjs} +2 -2
  28. package/dist/card-C-KsmoJW.mjs +25 -0
  29. package/dist/{cards-hYVyzntk.mjs → cards-D6jWz0N9.mjs} +3 -3
  30. package/dist/{ci-CPRKvyWe.mjs → ci-BQKTyiRi.mjs} +2 -2
  31. package/dist/cli.mjs +67 -46
  32. package/dist/{client-C_5K9lZA.mjs → client-Ba61gEcn.mjs} +6 -6
  33. package/dist/collection-DsNRlVcN.mjs +21 -0
  34. package/dist/{content-translation-Bux3aWnt.mjs → content-translation-B57aHSuc.mjs} +3 -3
  35. package/dist/{copy-B8HnAc11.mjs → copy-BeD_fk5J.mjs} +4 -4
  36. package/dist/{copy-BAP_y6s1.mjs → copy-D-VIVVB1.mjs} +4 -4
  37. package/dist/{copy-flags-VQzi7vqf.mjs → copy-flags-BcW9weVX.mjs} +1 -1
  38. package/dist/{create-CH2ebn9r.mjs → create-B--aPIbp.mjs} +4 -4
  39. package/dist/{create-BcbdVQr_.mjs → create-BURh8kHp.mjs} +3 -3
  40. package/dist/{create-Z3UZdGTY.mjs → create-BlmRvuQg.mjs} +3 -3
  41. package/dist/{create-BmEECHlW2.mjs → create-Bw9DAq_x2.mjs} +3 -3
  42. package/dist/{create-BWrIVbLC.mjs → create-C6c-clLK.mjs} +3 -3
  43. package/dist/{create-DF7jPhn1.mjs → create-CGiSOtIR.mjs} +3 -3
  44. package/dist/{create-DqtqJZa8.mjs → create-CP9lrYE4.mjs} +2 -2
  45. package/dist/{create-DNkqcne5.mjs → create-D-Eu1nBe.mjs} +2 -2
  46. package/dist/{create-BOn7IisH.mjs → create-D44fg_iT.mjs} +2 -2
  47. package/dist/{create-qCZ9z3II.mjs → create-DF7nC9qW.mjs} +2 -2
  48. package/dist/{create-CzVYAVbb.mjs → create-DMtlDzrE.mjs} +2 -2
  49. package/dist/{create-Bckej8a2.mjs → create-DQOapeeI.mjs} +2 -2
  50. package/dist/{create-BUPy6vmU.mjs → create-DcDyXL4g.mjs} +2 -2
  51. package/dist/{create-B0l5arU2.mjs → create-Dm8BvbsU.mjs} +2 -2
  52. package/dist/{create-BfD4GgYA.mjs → create-DybrZHV-.mjs} +2 -2
  53. package/dist/{create-Ds6gZXFm.mjs → create-HNchuWpj.mjs} +3 -3
  54. package/dist/{create-Dd_NNxYT.mjs → create-MmZVmRsH.mjs} +2 -2
  55. package/dist/{create-D70fh_7H.mjs → create-_wex54m1.mjs} +2 -2
  56. package/dist/{create-branch-CQyWpIxd.mjs → create-branch-C8arQT4M.mjs} +2 -2
  57. package/dist/{create-C_5-HH9u.mjs → create-lHqpAdxg.mjs} +3 -3
  58. package/dist/{create-CUcwiQgH.mjs → create-zALsdMhF.mjs} +2 -2
  59. package/dist/{csv-CFL145Ta.mjs → csv-CFabsH6l.mjs} +4 -4
  60. package/dist/{current-task-l8KZ0nv8.mjs → current-task-GG6JqfGp.mjs} +2 -2
  61. package/dist/dashboard-BRQtw_1e.mjs +35 -0
  62. package/dist/data-action-Bm6QMyV0.mjs +24 -0
  63. package/dist/{data-app-CxQNqhV2.mjs → data-app-DtW2i2SD.mjs} +10 -10
  64. package/dist/{data-app-project-B_zd-YrJ.mjs → data-app-project-CHYcJRrU.mjs} +1 -1
  65. package/dist/{db-DlDWN6UP.mjs → db-DSj2I1X9.mjs} +8 -8
  66. package/dist/{delete-3KVEIg6-.mjs → delete-AF64p0xG.mjs} +4 -4
  67. package/dist/{delete-YFXRPnnb.mjs → delete-C6N_Aj78.mjs} +4 -4
  68. package/dist/{delete-D3D0CTo-.mjs → delete-ClT1ROrM.mjs} +3 -3
  69. package/dist/{delete-CfkSkxHN.mjs → delete-CnGb4QIm.mjs} +4 -4
  70. package/dist/{delete-BmPR3qB0.mjs → delete-CsgkbmBy.mjs} +4 -4
  71. package/dist/{delete-Eje0xxEa.mjs → delete-Cx75BsN6.mjs} +4 -4
  72. package/dist/{delete-Dj0ImrZL.mjs → delete-CxL5okLN.mjs} +4 -4
  73. package/dist/{delete-BlLyxBY_.mjs → delete-D_l7FRE6.mjs} +4 -4
  74. package/dist/{delete-CcY_tcx4.mjs → delete-Dkwk2mZH.mjs} +4 -4
  75. package/dist/{delete-DweFD4-0.mjs → delete-YKX2HilI.mjs} +4 -4
  76. package/dist/{delete-runtime-C05-0fcn.mjs → delete-runtime-BUbOQx5I.mjs} +1 -1
  77. package/dist/{delete-table-BqpeGdMY.mjs → delete-table-CubRk-5H.mjs} +4 -4
  78. package/dist/{dependencies-DhA1jbDT.mjs → dependencies-W2dxsCpF.mjs} +3 -3
  79. package/dist/dependency-B0KmVz2u.mjs +15 -0
  80. package/dist/{dependents-4KgDZ4tl.mjs → dependents-B4V8pDYq.mjs} +4 -4
  81. package/dist/{dirty-DWwwmOmi.mjs → dirty-DKj4ScBN.mjs} +2 -2
  82. package/dist/{discard-values-6nmAd4kW.mjs → discard-values-5l91z3Dy.mjs} +4 -4
  83. package/dist/document--zYZP-u3.mjs +20 -0
  84. package/dist/{download-ZCfcKa2L.mjs → download-DRxR3yGi.mjs} +2 -2
  85. package/dist/{eid-CjLvyBgT.mjs → eid-B6JYogNC.mjs} +2 -2
  86. package/dist/{entry-flags-2u45mDJ4.mjs → entry-flags-6Y0fdYbG.mjs} +1 -1
  87. package/dist/{events-B_UxJw3Z.mjs → events-CE80dTib.mjs} +3 -3
  88. package/dist/{execute-Dg94sYQd.mjs → execute-C9foK2IU.mjs} +3 -3
  89. package/dist/{export-BdLB30iy.mjs → export-B8QyoPWP.mjs} +3 -3
  90. package/dist/{export-preflight-ChaWosZf.mjs → export-preflight-BQbwxyoJ.mjs} +2 -2
  91. package/dist/field-Bk09crKC.mjs +21 -0
  92. package/dist/{fields-B1cPeIGv.mjs → fields-TE3PnmwW.mjs} +3 -3
  93. package/dist/{filter-flags-BEz2s28-.mjs → filter-flags-DAUmdUwa.mjs} +1 -1
  94. package/dist/{fks-DgvW6D_7.mjs → fks-B41Re5dt.mjs} +3 -3
  95. package/dist/{get-C_bCGVsR.mjs → get-3XwqrjxC.mjs} +3 -3
  96. package/dist/{get-UNjwy8OI.mjs → get-BAGQ-bvt.mjs} +3 -3
  97. package/dist/{get-VrzY4qxg.mjs → get-BJgBsiEK.mjs} +2 -2
  98. package/dist/{get-BK7nxbPU.mjs → get-BcWr6U0n.mjs} +2 -2
  99. package/dist/{get-n9udbUQS.mjs → get-BrhIHZYg.mjs} +3 -3
  100. package/dist/{get-DStLX5Ah.mjs → get-BscyItLF.mjs} +3 -3
  101. package/dist/{get-B69NUYR0.mjs → get-CCTRO18v.mjs} +3 -3
  102. package/dist/{get-jphhROdg.mjs → get-CU6AWbqL.mjs} +3 -3
  103. package/dist/{get-CmWe9xq9.mjs → get-CefCrGWy.mjs} +5 -5
  104. package/dist/{get-Dsm_2AXn.mjs → get-Cjk-A9dQ.mjs} +3 -3
  105. package/dist/{get-DIR9LODa.mjs → get-Ck0cRNyr.mjs} +3 -3
  106. package/dist/{get-DC1lAsH_.mjs → get-CtRJUybv.mjs} +3 -3
  107. package/dist/{get-BxrS4Vi8.mjs → get-DG5fPl-S.mjs} +3 -3
  108. package/dist/{get-BYNEZFNj.mjs → get-DJeeXbwj.mjs} +3 -3
  109. package/dist/{get-DuVVF8xb.mjs → get-DbZDp2ve.mjs} +3 -3
  110. package/dist/{get-Ck2LNXxw.mjs → get-DeiC05Lm.mjs} +3 -3
  111. package/dist/{get-B94nj2uY.mjs → get-DguRhloC.mjs} +2 -2
  112. package/dist/{get-BM9AFJAi.mjs → get-DvPgnqAA.mjs} +2 -2
  113. package/dist/{get-Ljt0QVR0.mjs → get-PLIbg3Vy.mjs} +3 -3
  114. package/dist/{get-DkSvigdw.mjs → get-m-vpr7GN.mjs} +3 -3
  115. package/dist/{get-run-D5vfGnIN.mjs → get-run-DnKCnvUa.mjs} +3 -3
  116. package/dist/{get-D4oXmJTn.mjs → get-sjhoDJyB.mjs} +3 -3
  117. package/dist/{get-DckpwiZG.mjs → get-uWk0JKvF.mjs} +3 -3
  118. package/dist/git-sync-DMyH3Y85.mjs +29 -0
  119. package/dist/glossary-CcRGJaVi.mjs +14 -0
  120. package/dist/{graph-CvIhSOEe.mjs → graph-B2YkV2oX.mjs} +3 -3
  121. package/dist/{group-KiKh0UFs.mjs → group-D1nGaEXW.mjs} +1 -1
  122. package/dist/{has-remote-changes-57yJkr3b.mjs → has-remote-changes-ChMSjhmC.mjs} +2 -2
  123. package/dist/{import-HHsz_zDq.mjs → import-CNpN9iS0.mjs} +3 -3
  124. package/dist/{is-dirty-CxX_4rBC.mjs → is-dirty-Bud2wuxQ.mjs} +2 -2
  125. package/dist/{items-B2NR1_u3.mjs → items-B1DXzdwg.mjs} +2 -2
  126. package/dist/{library-DXvOVBTe.mjs → library-8G0s5VLO.mjs} +5 -5
  127. package/dist/{list-DEQvaioL.mjs → list-0aX638ov.mjs} +3 -3
  128. package/dist/{list-JS7ASJc-.mjs → list-B2PbnXp4.mjs} +2 -2
  129. package/dist/{list-Bwi5npRx.mjs → list-BJasizCe.mjs} +2 -2
  130. package/dist/{list-CcZAP1sH.mjs → list-BSvQ0AT_.mjs} +2 -2
  131. package/dist/{list-DhCSllG2.mjs → list-BVz0N0g_.mjs} +2 -2
  132. package/dist/{list-CtW9rAs4.mjs → list-BeaA7gQ7.mjs} +2 -2
  133. package/dist/{list-BU72dCx0.mjs → list-CAAvhl9h.mjs} +2 -2
  134. package/dist/{list-B44qweQa.mjs → list-CAZHyhd5.mjs} +2 -2
  135. package/dist/{list-l09h4Zp6.mjs → list-CO62N1eD.mjs} +2 -2
  136. package/dist/{list-Dv8U4e-C.mjs → list-CPwy-AEn.mjs} +2 -2
  137. package/dist/{list-CNoCRL4D.mjs → list-CcfGHN19.mjs} +2 -2
  138. package/dist/{list-rA9U_VyF.mjs → list-Ckqu41Zx.mjs} +2 -2
  139. package/dist/{list-DQ5BDB52.mjs → list-D1WO7lxP.mjs} +2 -2
  140. package/dist/{list-L4_At_o9.mjs → list-DAdWd8gn.mjs} +2 -2
  141. package/dist/{list-BsQHh8w4.mjs → list-DX5SmBCQ.mjs} +2 -2
  142. package/dist/{list-BK4VwtKe.mjs → list-DZLGnsAK.mjs} +2 -2
  143. package/dist/{list-DJogq3Ez.mjs → list-DjfY80vj.mjs} +2 -2
  144. package/dist/{list-BTpj7dv4.mjs → list-Dk3upD11.mjs} +3 -3
  145. package/dist/{list-DJ0_FcgB.mjs → list-DpyIV41y.mjs} +5 -5
  146. package/dist/{list-DiN5mPrk.mjs → list-JqIFAXpw.mjs} +2 -2
  147. package/dist/{list-DMbtH6tY.mjs → list-mRSKPKoO.mjs} +2 -2
  148. package/dist/{list-C6A-Eqgv.mjs → list-tHzgEkzv.mjs} +4 -4
  149. package/dist/{list-CY54buuD.mjs → list-z4iqffgl.mjs} +2 -2
  150. package/dist/{login-DVLN1666.mjs → login-muOdP15h.mjs} +5 -5
  151. package/dist/{logout-BiZxbftK.mjs → logout-Z0_gSSM4.mjs} +2 -2
  152. package/dist/{measure-B7Ud8GU1.mjs → measure-CTF4qqiE.mjs} +6 -6
  153. package/dist/{parameter-values-BwaIYczs.mjs → parameter-values-to5u_-2n.mjs} +3 -3
  154. package/dist/{parse-id-BXkvTl89.mjs → parse-id-CbF7zxGv.mjs} +1 -1
  155. package/dist/{path-BbR0QB1I.mjs → path-B-0S-9i2.mjs} +3 -3
  156. package/dist/{poll-D5eA9cN2.mjs → poll-zB3cX0Kq.mjs} +1 -1
  157. package/dist/{preflight-BJ5Bve-N.mjs → preflight-Dgj2yyY_.mjs} +1 -1
  158. package/dist/{process-cDa9fEBe.mjs → process-ByKCTa6A.mjs} +22 -10
  159. package/dist/{publish-d_WnLoJD.mjs → publish-tKh9tJeh.mjs} +3 -3
  160. package/dist/{query-Dkod9Tev.mjs → query-B4xK9j0u.mjs} +3 -3
  161. package/dist/{query-DFb-XQNz.mjs → query-o1Z2dgtw.mjs} +3 -3
  162. package/dist/{remapping-clW2Rgcz.mjs → remapping-DT305P-k.mjs} +3 -3
  163. package/dist/{remove-collection-CjRtrkLW.mjs → remove-collection-Bsme1OBA.mjs} +3 -3
  164. package/dist/{render-C_Oy6cF5.mjs → render-Df56xOPJ.mjs} +1 -1
  165. package/dist/{replace-8hEY7br8.mjs → replace-C3PQwvfl.mjs} +4 -4
  166. package/dist/{requirement-check-B7MNfiXv.mjs → requirement-check-qqRkRGcN.mjs} +1 -1
  167. package/dist/{rescan-values-0owEJyWj.mjs → rescan-values-C0P0upEB.mjs} +3 -3
  168. package/dist/{rescan-values-DaEu5sVZ.mjs → rescan-values-CGN4bjyB.mjs} +3 -3
  169. package/dist/{run-BlCJ0MYm.mjs → run-BaEBHum8.mjs} +4 -4
  170. package/dist/{run-Dc3u_olU.mjs → run-C_IZfmQ7.mjs} +403 -167
  171. package/dist/{run-Cr7QHB5S.mjs → run-ZRY5wy4x.mjs} +6 -6
  172. package/dist/{run-CXv_3NQt.mjs → run-bVD66LrU.mjs} +3 -3
  173. package/dist/{runs-624fiVP3.mjs → runs-DWm1pIdP.mjs} +3 -3
  174. package/dist/{runtime-DsfR_yrb.mjs → runtime-Dd2uBKqN.mjs} +3 -3
  175. package/dist/{scaffold-D4Jo6VQc.mjs → scaffold-BnT99dhY.mjs} +4 -4
  176. package/dist/{schema-DXsx2_Mk.mjs → schema-DoELV3FT.mjs} +2 -2
  177. package/dist/{schema-tables-Bat1bYLS.mjs → schema-tables-DGyHJnJX.mjs} +3 -3
  178. package/dist/{schemas-B8bgnxOA.mjs → schemas-7w9MPZcZ.mjs} +3 -3
  179. package/dist/{search-fXh_MkLZ.mjs → search-BZh0BwZ9.mjs} +3 -3
  180. package/dist/{search-BslDGFtS.mjs → search-CyyySVTz.mjs} +3 -3
  181. package/dist/{segment-Bb7JTPmv.mjs → segment--JCnZCgL.mjs} +6 -6
  182. package/dist/{send-YnL6iiOt.mjs → send-c4FpapMf.mjs} +3 -3
  183. package/dist/{set-CydWEHNW.mjs → set-_puxfyaP.mjs} +2 -2
  184. package/dist/{set-active-C-x_A0AY.mjs → set-active-BexM75_s.mjs} +2 -2
  185. package/dist/{set-bundle-DaHlkEYA.mjs → set-bundle-B-qAvkDo.mjs} +3 -3
  186. package/dist/{set-data-actions-C3NwuCSF.mjs → set-data-actions-BnrX6ARh.mjs} +3 -3
  187. package/dist/{set-enabled-CjUOudzn.mjs → set-enabled-Np4xqFA_.mjs} +2 -2
  188. package/dist/{set-sensitivity-DAodU0MG.mjs → set-sensitivity-DDzrkjdv.mjs} +3 -3
  189. package/dist/{setting-Bil7qFf0.mjs → setting-BGK1uRxm.mjs} +4 -4
  190. package/dist/{setup-B8GRygkY.mjs → setup-BCliYRAt.mjs} +2 -2
  191. package/dist/{skills-CW1wIl0c.mjs → skills-CoXIflBw.mjs} +3 -3
  192. package/dist/{skills-DYfZF1GA.mjs → skills-PULUh2VV.mjs} +3 -3
  193. package/dist/snippet-Czzma-M2.mjs +19 -0
  194. package/dist/{stash-T0WRHnFD.mjs → stash-d3FtnE2F.mjs} +3 -3
  195. package/dist/{status-BRMzSa12.mjs → status-BJTdBEN4.mjs} +3 -3
  196. package/dist/{status-CW1bsfaP.mjs → status-CJLd6Uk6.mjs} +2 -2
  197. package/dist/{subscription-Edc8UK2V.mjs → subscription-Dpq45fMP.mjs} +6 -6
  198. package/dist/{subscriptions-DMiWl84r.mjs → subscriptions-Cu4km2CN.mjs} +4 -4
  199. package/dist/{summary-CFnwsMoV.mjs → summary-3Av5251t.mjs} +3 -3
  200. package/dist/{sync-schema-COvbx-g4.mjs → sync-schema-BNHt8S3P.mjs} +4 -4
  201. package/dist/{sync-schema-VzwA_5tH.mjs → sync-schema-BYJuUBsG.mjs} +3 -3
  202. package/dist/table-CwfF6ZTZ.mjs +26 -0
  203. package/dist/{table-selector-flags-BhZo8RHH.mjs → table-selector-flags-BiA8p56s.mjs} +2 -2
  204. package/dist/timeline-C4WCMzDX.mjs +21 -0
  205. package/dist/{timeline-event-BqM82_7L.mjs → timeline-event-BLBqSAjH.mjs} +6 -6
  206. package/dist/transform-job-SQ57QOlQ.mjs +22 -0
  207. package/dist/transform-jxF8cjNA.mjs +28 -0
  208. package/dist/{transform-tag-CxXDADhM.mjs → transform-tag-CETHXuAi.mjs} +5 -5
  209. package/dist/{transform-test-C0c4zjcf.mjs → transform-test-D-e9oi6r.mjs} +1 -1
  210. package/dist/{transform-test-DHTxBt4k.mjs → transform-test-D6QIyu8C.mjs} +6 -2
  211. package/dist/transform-test-MlVjTlKo.mjs +16 -0
  212. package/dist/{transforms-WyRUDM1j.mjs → transforms-CstSQgQ-.mjs} +3 -3
  213. package/dist/{transport-Byycx3RW.mjs → transport-D74axlBJ.mjs} +3 -3
  214. package/dist/{tree-CMeV4bI8.mjs → tree-BDhm4E_W.mjs} +2 -2
  215. package/dist/{typed-schema-DsGXH-La.mjs → typed-schema-BZkv_z2f.mjs} +3 -3
  216. package/dist/{unpublish-EaVPnnZ5.mjs → unpublish-BNBSxZFl.mjs} +3 -3
  217. package/dist/{unreferenced-BvE7ke0M.mjs → unreferenced-B2nDcQ7m.mjs} +3 -3
  218. package/dist/{update-BtWR4ri9.mjs → update-B6xWuJqk.mjs} +3 -3
  219. package/dist/{update-DrY4i6eO.mjs → update-B9iHHoJB.mjs} +4 -4
  220. package/dist/{update-DZPo69an.mjs → update-BA0NxInb.mjs} +3 -3
  221. package/dist/{update-DyXj5mTv.mjs → update-BGFGng3a.mjs} +4 -4
  222. package/dist/{update-Dcwpy8BO.mjs → update-BMHPp3nt.mjs} +3 -3
  223. package/dist/{update-Bb1HPTH2.mjs → update-BS-E7QLh.mjs} +3 -3
  224. package/dist/{update-DwGo58Kc.mjs → update-BenGrugz.mjs} +4 -4
  225. package/dist/{update--uKYjq9_.mjs → update-BkFy2BV_.mjs} +3 -3
  226. package/dist/{update-BSWoKNrf.mjs → update-ByBqo1if.mjs} +4 -4
  227. package/dist/{update-CFYVJJqw.mjs → update-CFLJWHOT.mjs} +3 -3
  228. package/dist/{update-DjBJQCNg.mjs → update-CWzmrGS_.mjs} +3 -3
  229. package/dist/{update-BDghcetp.mjs → update-Cpd1tKqr.mjs} +3 -3
  230. package/dist/{update-CNzu9eG1.mjs → update-DKcPzoG9.mjs} +5 -5
  231. package/dist/{update-B8EnadKJ.mjs → update-DRw_610p.mjs} +4 -4
  232. package/dist/{update-t2zdccd5.mjs → update-DT8KwBxO.mjs} +4 -4
  233. package/dist/{update-CT9e8m-I.mjs → update-DU3jpGps.mjs} +3 -3
  234. package/dist/{update-BPhLIVs9.mjs → update-DrfAOlkf.mjs} +2 -2
  235. package/dist/{update-wh2EjN242.mjs → update-DrvDC_0m2.mjs} +3 -3
  236. package/dist/{update-BgQ9TU63.mjs → update-DxLypq9G.mjs} +4 -4
  237. package/dist/{update-dashcard-BhT2FBd4.mjs → update-dashcard-BU3Wqcnf.mjs} +3 -3
  238. package/dist/{update-xYxKw2UB.mjs → update-pzu78yZB.mjs} +3 -3
  239. package/dist/{upgrade-BqBjnjjU.mjs → upgrade-Cq89D4Oe.mjs} +3 -3
  240. package/dist/{upload-D0zRTkyv.mjs → upload-CmwO6anj.mjs} +1 -1
  241. package/dist/upload-DbDFl9qT.mjs +13 -0
  242. package/dist/{upload-kM0-7Bx2.mjs → upload-INQHve86.mjs} +3 -3
  243. package/dist/{uuid-DsuYe9z7.mjs → uuid-BNJKaQGB.mjs} +2 -2
  244. package/dist/{validate-query-Bf5FhhMI.mjs → validate-query-CQ4UPpZX.mjs} +1 -1
  245. package/dist/{values-kLEvyjSy.mjs → values-BVtqwRlR.mjs} +3 -3
  246. package/dist/{verify-CvYsiaqY.mjs → verify-BSlj7S_E.mjs} +3 -3
  247. package/dist/{verify-ChvmI4lq.mjs → verify-Bp9ZyPgX.mjs} +2 -2
  248. package/dist/{verify-D27n5y3t.mjs → verify-BwodYH0C.mjs} +3 -3
  249. package/dist/{wait-DioDMGLJ.mjs → wait-DZclBI3D.mjs} +4 -4
  250. package/dist/{wait-flags-Qqvlb3Rv.mjs → wait-flags-CmIfm6_U.mjs} +2 -2
  251. package/dist/{yaml-QoEP-7KU.mjs → yaml-BthVUNj3.mjs} +16 -1
  252. package/package.json +1 -1
  253. package/skill-data/data-app/SKILL.md +8 -2
  254. package/skill-data/data-app/template/package.json +4 -4
  255. package/skill-data/data-app-data/SKILL.md +2 -0
  256. package/skill-data/data-app-ui/SKILL.md +3 -0
  257. package/dist/api-key-Bw2jDvT4.mjs +0 -13
  258. package/dist/card-BlSUKf2e.mjs +0 -25
  259. package/dist/collection-CeaSalia.mjs +0 -21
  260. package/dist/dashboard-CIOXUaBJ.mjs +0 -35
  261. package/dist/data-action-_7UX21i9.mjs +0 -24
  262. package/dist/dependency-BPwCXzX4.mjs +0 -15
  263. package/dist/document-CC60L29U.mjs +0 -20
  264. package/dist/field-ClWwbYtG.mjs +0 -21
  265. package/dist/git-sync-Bc9pf1LL.mjs +0 -29
  266. package/dist/glossary-CAzA-OIR.mjs +0 -14
  267. package/dist/snippet-D9g83-mO.mjs +0 -19
  268. package/dist/table-CC-rFakp.mjs +0 -26
  269. package/dist/timeline-C2ZUL_9F.mjs +0 -21
  270. package/dist/transform-BtB6iOJ9.mjs +0 -28
  271. package/dist/transform-job-I2Q5f0yN.mjs +0 -22
  272. package/dist/transform-test-B6jvvXeh.mjs +0 -16
  273. package/dist/upload-C0GfBUvp.mjs +0 -13
@@ -1,11 +1,12 @@
1
- import { c as InternalError, h as ValidationError, o as ConfigError, r as isPlainObject, s as FailedResultError } from "./predicates-0C0TsWcJ.mjs";
2
- import { at as connectionFlags, d as serializeJson, dt as profileFlag, p as writeJsonLine, q as HttpError, t as interruptSignal } from "./cli.mjs";
3
- import { t as defineMetabaseCommand } from "./runtime-DsfR_yrb.mjs";
4
- import { i as TransformTestRefusalCode } from "./transform-test-DHTxBt4k.mjs";
5
- import { i as runProcessBinary, r as runProcess, t as ProcessNotFoundError } from "./process-cDa9fEBe.mjs";
6
- import { t as parseYaml } from "./yaml-QoEP-7KU.mjs";
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";
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";
7
7
  import { z } from "zod";
8
- import { writeFile } from "node:fs/promises";
8
+ import { rename, rm, writeFile } from "node:fs/promises";
9
+ import { basename, dirname, join } from "node:path";
9
10
  import { randomUUID } from "node:crypto";
10
11
  import { isDeepStrictEqual } from "node:util";
11
12
  import { performance } from "node:perf_hooks";
@@ -82,6 +83,7 @@ function coverageStage(assessment) {
82
83
  name: transform.name
83
84
  })) }
84
85
  });
86
+ return null;
85
87
  }
86
88
  };
87
89
  }
@@ -103,6 +105,10 @@ const CiWarning = z.object({
103
105
  code: z.string(),
104
106
  message: z.string()
105
107
  });
108
+ const StageStop = z.object({
109
+ code: z.string(),
110
+ message: z.string()
111
+ });
106
112
  const CheckedTransform = z.object({
107
113
  entity_id: z.string(),
108
114
  name: z.string()
@@ -150,7 +156,8 @@ const CheckResult = EventEnvelope.extend({
150
156
  const StageEnd = EventEnvelope.extend({
151
157
  type: z.literal("stage.end"),
152
158
  stage: StageName,
153
- status: StageStatus
159
+ status: StageStatus,
160
+ stopped: StageStop.nullable()
154
161
  });
155
162
  const JobEnd = EventEnvelope.extend({
156
163
  type: z.literal("job.end"),
@@ -179,11 +186,12 @@ function createEmitter(options) {
179
186
  if (!parsed.success) throw new InternalError(`ci event "${body.type}" does not match its schema: ${parsed.error.message}`);
180
187
  seq += 1;
181
188
  options.write(parsed.data);
189
+ return parsed.data;
182
190
  };
183
191
  }
184
- function stageStatus(verdicts) {
192
+ function stageStatus(verdicts, stopped) {
185
193
  if (verdicts.includes("failed")) return "failed";
186
- if (verdicts.includes("refused") || verdicts.includes("error")) return "errored";
194
+ if (stopped !== null || verdicts.includes("refused") || verdicts.includes("error")) return "errored";
187
195
  return "passed";
188
196
  }
189
197
  async function runStages(stages, emit) {
@@ -195,7 +203,8 @@ async function runStages(stages, emit) {
195
203
  emit({
196
204
  type: "stage.end",
197
205
  stage: stage.name,
198
- status: "skipped"
206
+ status: "skipped",
207
+ stopped: null
199
208
  });
200
209
  continue;
201
210
  }
@@ -204,7 +213,7 @@ async function runStages(stages, emit) {
204
213
  stage: stage.name
205
214
  });
206
215
  const verdicts = [];
207
- await stage.run((check) => {
216
+ const stopped = await stage.run((check) => {
208
217
  verdicts.push(check.verdict);
209
218
  checks += 1;
210
219
  warnings += check.warnings.length;
@@ -214,11 +223,12 @@ async function runStages(stages, emit) {
214
223
  ...check
215
224
  });
216
225
  });
217
- state = stageStatus(verdicts);
226
+ state = stageStatus(verdicts, stopped);
218
227
  emit({
219
228
  type: "stage.end",
220
229
  stage: stage.name,
221
- status: state
230
+ status: state,
231
+ stopped
222
232
  });
223
233
  }
224
234
  return {
@@ -235,6 +245,7 @@ const SINGLE_PATH_STATUSES = /* @__PURE__ */ new Map([
235
245
  ["D", "deleted"],
236
246
  ["T", "modified"]
237
247
  ]);
248
+ const STATUS_CODE = /^(?:[AMDT](?:\d{3})?|[RC]\d{3})$/;
238
249
  const RENAME_STATUS = "R";
239
250
  const COPY_STATUS = "C";
240
251
  function nameStatusError(message) {
@@ -252,6 +263,7 @@ function parseNameStatus(output) {
252
263
  let index = 0;
253
264
  while (index < tokens.length) {
254
265
  const code = requireToken(tokens[index], "a status");
266
+ if (!STATUS_CODE.test(code)) throw nameStatusError(`unexpected status "${code}"`);
255
267
  const missingPath = `a path for status "${code}"`;
256
268
  const first = tokens[index + 1];
257
269
  const second = tokens[index + 2];
@@ -275,6 +287,7 @@ function parseNameStatus(output) {
275
287
  continue;
276
288
  }
277
289
  if (letter === COPY_STATUS) {
290
+ requireToken(first, missingPath);
278
291
  entries.push({
279
292
  status: "added",
280
293
  path: requireToken(second, missingPath)
@@ -315,46 +328,34 @@ const TransformDocument = z.object({
315
328
  }).loose();
316
329
  const TransformTestDocument = z.object({ transform_id: z.string().min(1) }).loose();
317
330
  const SerdesMeta = z.array(z.object({ model: z.string() }).loose()).min(1);
318
- function serdesModel(path, document) {
319
- if (!isPlainObject(document) || !(SERDES_META_KEY in document)) return null;
320
- const meta = SerdesMeta.safeParse(document[SERDES_META_KEY]);
321
- if (!meta.success) throw new ValidationError(`${path}: ${SERDES_META_KEY} must be a non-empty list of entries with a model`, {
331
+ function parseOrRefuse(path, schema, value, rule) {
332
+ const parsed = schema.safeParse(value);
333
+ if (!parsed.success) throw new ValidationError(`${path}: ${rule}`, {
322
334
  source: path,
323
- zodIssues: meta.error.issues
335
+ zodIssues: parsed.error.issues
324
336
  });
325
- const last = meta.data.at(-1);
326
- if (last === void 0) throw new InternalError(`${path}: ${SERDES_META_KEY} passed a non-empty check but has no entries`);
327
- return last.model;
337
+ return parsed.data;
328
338
  }
329
339
  function classifyContentFile(path, document) {
330
- const model = serdesModel(path, document);
331
- if (model === "Transform") {
332
- const transform = TransformDocument.safeParse(document);
333
- if (!transform.success) throw new ValidationError(`${path}: a Transform must have an entity_id, a name, a source and a target`, {
334
- source: path,
335
- zodIssues: transform.error.issues
336
- });
340
+ if (!isPlainObject(document) || !(SERDES_META_KEY in document)) return { kind: "other" };
341
+ 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
+ if (last === void 0) throw new InternalError(`${path}: ${SERDES_META_KEY} passed a non-empty check but has no entries`);
343
+ 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");
337
345
  return {
338
346
  kind: "transform",
339
- entityId: transform.data.entity_id,
340
- name: transform.data.name,
347
+ entityId: transform.entity_id,
348
+ name: transform.name,
341
349
  logic: {
342
- source: transform.data.source,
343
- target: transform.data.target
350
+ source: transform.source,
351
+ target: transform.target
344
352
  }
345
353
  };
346
354
  }
347
- if (model === "TransformTest") {
348
- const test = TransformTestDocument.safeParse(document);
349
- if (!test.success) throw new ValidationError(`${path}: a TransformTest must name its transform_id`, {
350
- source: path,
351
- zodIssues: test.error.issues
352
- });
353
- return {
354
- kind: "transform-test",
355
- transformId: test.data.transform_id
356
- };
357
- }
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
+ };
358
359
  return { kind: "other" };
359
360
  }
360
361
  function transformEntityId(file) {
@@ -364,7 +365,11 @@ function affectedTransforms(changes) {
364
365
  const touched = /* @__PURE__ */ new Set();
365
366
  const atHead = /* @__PURE__ */ new Set();
366
367
  const atBase = /* @__PURE__ */ new Set();
367
- for (const { base, head } of changes) {
368
+ for (const { path, base, head } of changes) {
369
+ if (base !== null && base.kind !== "other" && head?.kind === "other") throw new ValidationError(`${path}: a Transform or TransformTest at base no longer reads as one at head`, {
370
+ source: path,
371
+ zodIssues: []
372
+ });
368
373
  const headId = transformEntityId(head);
369
374
  const baseId = transformEntityId(base);
370
375
  if (headId !== null) {
@@ -376,16 +381,56 @@ function affectedTransforms(changes) {
376
381
  }
377
382
  return [...touched].filter((entityId) => atHead.has(entityId) || !atBase.has(entityId));
378
383
  }
379
- const CONTENT_FILE_EXTENSIONS = [".yaml", ".yml"];
384
+ const TRANSFORM_MODEL_PATTERN = `model["']?:[[:space:]]*["']?Transform`;
385
+ function headTransforms(files) {
386
+ const atHead = {
387
+ names: /* @__PURE__ */ new Map(),
388
+ tested: /* @__PURE__ */ new Set()
389
+ };
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);
392
+ return atHead;
393
+ }
394
+ const CONTENT_TOP_LEVEL_FOLDERS = [
395
+ "actions",
396
+ "channels",
397
+ "collections",
398
+ "custom_viz_plugins",
399
+ "data_apps",
400
+ "databases",
401
+ "embedding_themes",
402
+ "glossary",
403
+ "metabots",
404
+ "python_libraries",
405
+ "python-libraries",
406
+ "osi_ai_context",
407
+ "snippets",
408
+ "transforms"
409
+ ];
410
+ const SHARED_TOP_LEVEL_FILE_NAMES = /* @__PURE__ */ new Map([["data_apps", "data_app.yaml"]]);
411
+ const CONTENT_FOLDERS = new Set(CONTENT_TOP_LEVEL_FOLDERS);
380
412
  function isContentPath(path) {
381
- return CONTENT_FILE_EXTENSIONS.some((extension) => path.endsWith(extension));
413
+ const slash = path.indexOf("/");
414
+ if (slash === -1) return false;
415
+ const top = path.slice(0, slash);
416
+ const fileName = path.slice(path.lastIndexOf("/") + 1);
417
+ if (!fileName.endsWith(".yaml") || fileName.startsWith(".") || !CONTENT_FOLDERS.has(top)) return false;
418
+ const entityFileName = SHARED_TOP_LEVEL_FILE_NAMES.get(top);
419
+ return entityFileName === void 0 || entityFileName === fileName;
420
+ }
421
+ function noContentChangesWarning(entries) {
422
+ if (entries.some((entry) => isContentPath(entry.path) || entry.status === "renamed" && isContentPath(entry.oldPath))) return null;
423
+ return {
424
+ code: "no-content-changes",
425
+ message: "the change touches no Metabase content files, so CI checked nothing"
426
+ };
382
427
  }
383
428
  function uncommittedContentWarning(paths) {
384
429
  const content = paths.filter(isContentPath);
385
430
  if (content.length === 0) return null;
386
431
  return {
387
432
  code: "uncommitted-content",
388
- message: `${content.length === 1 ? "1 content file is" : `${content.length} content files are`} not committed, so CI did not check: ${content.join(", ")}`
433
+ 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(", ")}`
389
434
  };
390
435
  }
391
436
  //#endregion
@@ -394,65 +439,71 @@ const GIT = "git";
394
439
  const NO_MATCH_EXIT_CODE = 1;
395
440
  const NO_MERGE_BASE_EXIT_CODE = 1;
396
441
  const UNKNOWN_REF_EXIT_CODE = 1;
397
- async function withGit(spawn) {
442
+ const GIT_TIMEOUT_MS = 12e4;
443
+ async function withGit(repo, args, spawn) {
398
444
  try {
399
445
  return await spawn();
400
446
  } catch (error) {
401
447
  if (error instanceof ProcessNotFoundError) throw new ConfigError("git is not installed or not on PATH");
448
+ if (error instanceof ProcessInterruptedError) throw new AbortError(`interrupted while git ${args.join(" ")} ran in ${repo}`);
449
+ if (error instanceof ProcessTimeoutError) throw new ConfigError(`git ${args.join(" ")} did not finish within ${GIT_TIMEOUT_MS / 1e3} s in ${repo}`);
402
450
  throw error;
403
451
  }
404
452
  }
405
- function runGit(repo, args) {
406
- return withGit(() => runProcess(GIT, [
453
+ function runGit(repo, args, signal) {
454
+ return withGit(repo, args, () => runProcess(GIT, [
407
455
  "-C",
408
456
  repo,
409
457
  ...args
410
- ]));
458
+ ], {
459
+ signal,
460
+ timeoutMs: GIT_TIMEOUT_MS
461
+ }));
411
462
  }
412
- async function git(repo, args) {
413
- const result = await runGit(repo, args);
463
+ async function git(repo, args, signal) {
464
+ const result = await runGit(repo, args, signal);
414
465
  if (result.exitCode !== 0) throw new ConfigError(`git ${args.join(" ")} failed in ${repo}: ${result.stderr.trim()}`);
415
466
  return result.stdout;
416
467
  }
417
- async function repositoryRoot(repo) {
418
- const result = await runGit(repo, ["rev-parse", "--show-toplevel"]);
468
+ async function repositoryRoot(repo, signal) {
469
+ const result = await runGit(repo, ["rev-parse", "--show-toplevel"], signal);
419
470
  if (result.exitCode !== 0) throw new ConfigError(`--repo ${repo} is not inside a git work tree: ${result.stderr.trim()}`);
420
471
  return result.stdout.trim();
421
472
  }
422
- async function resolveCommit(repo, ref, flag) {
473
+ async function resolveCommit(repo, ref, flag, signal) {
423
474
  const result = await runGit(repo, [
424
475
  "rev-parse",
425
476
  "--verify",
426
477
  "--quiet",
427
478
  "--end-of-options",
428
479
  `${ref}^{commit}`
429
- ]);
480
+ ], signal);
430
481
  if (result.exitCode === UNKNOWN_REF_EXIT_CODE) throw new ConfigError(`${flag} ${ref} is not a commit in ${repo}`);
431
482
  if (result.exitCode !== 0) throw new ConfigError(`git rev-parse failed in ${repo}: ${result.stderr.trim()}`);
432
483
  return result.stdout.trim();
433
484
  }
434
- async function checkedOutCommit(repo) {
485
+ async function checkedOutCommit(repo, signal) {
435
486
  const result = await runGit(repo, [
436
487
  "rev-parse",
437
488
  "--verify",
438
489
  "--quiet",
439
490
  "HEAD"
440
- ]);
491
+ ], signal);
441
492
  if (result.exitCode === UNKNOWN_REF_EXIT_CODE) return null;
442
493
  if (result.exitCode !== 0) throw new ConfigError(`git rev-parse HEAD failed in ${repo}: ${result.stderr.trim()}`);
443
494
  return result.stdout.trim();
444
495
  }
445
- async function mergeBase(repo, base, head) {
496
+ async function mergeBase(repo, base, head, signal) {
446
497
  const result = await runGit(repo, [
447
498
  "merge-base",
448
499
  base,
449
500
  head
450
- ]);
501
+ ], signal);
451
502
  if (result.exitCode === NO_MERGE_BASE_EXIT_CODE) throw new ConfigError(`--base and --head share no history in ${repo}: the clone may be shallow (fetch the full history, e.g. actions/checkout with fetch-depth: 0), or the histories are unrelated`);
452
503
  if (result.exitCode !== 0) throw new ConfigError(`git merge-base failed in ${repo}: ${result.stderr.trim()}`);
453
504
  return result.stdout.trim();
454
505
  }
455
- function diffNameStatus(repo, from, to) {
506
+ function diffNameStatus(repo, from, to, signal) {
456
507
  return git(repo, [
457
508
  "diff",
458
509
  "--name-status",
@@ -460,24 +511,35 @@ function diffNameStatus(repo, from, to) {
460
511
  "-M",
461
512
  from,
462
513
  to
463
- ]);
514
+ ], signal);
464
515
  }
465
- function statusPorcelain(repo) {
516
+ function statusPorcelain(repo, signal) {
466
517
  return git(repo, [
467
518
  "status",
468
519
  "--porcelain",
469
520
  "-z",
470
521
  "--untracked-files=all"
471
- ]);
522
+ ], signal);
472
523
  }
473
- function readFileAt(repo, commit, path) {
474
- return git(repo, [
475
- "cat-file",
476
- "blob",
477
- `${commit}:${path}`
478
- ]);
524
+ async function fileModesAt(repo, commit, signal) {
525
+ const output = await git(repo, [
526
+ "ls-tree",
527
+ "-r",
528
+ "-z",
529
+ "--full-tree",
530
+ commit
531
+ ], signal);
532
+ const modes = /* @__PURE__ */ new Map();
533
+ for (const record of output.split("\0")) {
534
+ if (record === "") continue;
535
+ const space = record.indexOf(" ");
536
+ const tab = record.indexOf(" ");
537
+ if (space === -1 || tab < space) throw new InternalError(`git ls-tree -z: malformed entry "${record}"`);
538
+ modes.set(record.slice(tab + 1), record.slice(0, space));
539
+ }
540
+ return modes;
479
541
  }
480
- async function filesMatching(repo, commit, pattern, pathspecs) {
542
+ async function filesMatching(repo, commit, pattern, pathspecs, signal) {
481
543
  const result = await runGit(repo, [
482
544
  "grep",
483
545
  "--no-color",
@@ -489,7 +551,7 @@ async function filesMatching(repo, commit, pattern, pathspecs) {
489
551
  commit,
490
552
  "--",
491
553
  ...pathspecs
492
- ]);
554
+ ], signal);
493
555
  if (result.exitCode === NO_MATCH_EXIT_CODE) return [];
494
556
  if (result.exitCode !== 0) throw new ConfigError(`git grep failed in ${repo}: ${result.stderr.trim()}`);
495
557
  const prefix = `${commit}:`;
@@ -500,17 +562,21 @@ async function filesMatching(repo, commit, pattern, pathspecs) {
500
562
  }
501
563
  const NEWLINE = 10;
502
564
  const utf8 = new TextDecoder("utf-8");
503
- async function readFilesAt(repo, commit, paths) {
565
+ async function readFilesAt(repo, commit, paths, signal) {
504
566
  const files = /* @__PURE__ */ new Map();
505
567
  if (paths.length === 0) return files;
506
568
  const multiline = paths.find((path) => path.includes("\n"));
507
569
  if (multiline !== void 0) throw new ConfigError(`cannot read ${JSON.stringify(multiline)}: a path with a newline cannot be batched`);
508
- const result = await withGit(() => runProcessBinary(GIT, [
570
+ const args = ["cat-file", "--batch"];
571
+ const result = await withGit(repo, args, () => runProcessBinary(GIT, [
509
572
  "-C",
510
573
  repo,
511
- "cat-file",
512
- "--batch"
513
- ], { stdin: paths.map((path) => `${commit}:${path}\n`).join("") }));
574
+ ...args
575
+ ], {
576
+ stdin: paths.map((path) => `${commit}:${path}\n`).join(""),
577
+ signal,
578
+ timeoutMs: GIT_TIMEOUT_MS
579
+ }));
514
580
  if (result.exitCode !== 0) throw new ConfigError(`git cat-file --batch failed in ${repo}: ${result.stderr.trim()}`);
515
581
  const output = result.stdout;
516
582
  let offset = 0;
@@ -530,10 +596,23 @@ async function readFilesAt(repo, commit, paths) {
530
596
  }
531
597
  //#endregion
532
598
  //#region src/commands/ci/job-plan.ts
533
- const CONTENT_PATHSPECS = CONTENT_FILE_EXTENSIONS.map((extension) => `*${extension}`);
534
- async function classifyAt(repo, commit, path) {
535
- if (path === null || !isContentPath(path)) return null;
536
- return classifyContentFile(path, parseYaml(await readFileAt(repo, commit, path), z.unknown(), { source: `${commit}:${path}` }));
599
+ const CONTENT_PATHSPECS = CONTENT_TOP_LEVEL_FOLDERS.map((folder) => `${folder}/*.yaml`);
600
+ const FILE_MODES = /* @__PURE__ */ new Set(["100644", "100755"]);
601
+ async function classifyAt(repo, commit, paths, signal) {
602
+ const content = paths.filter(isContentPath);
603
+ if (content.length === 0) return /* @__PURE__ */ new Map();
604
+ const modes = await fileModesAt(repo, commit, signal);
605
+ for (const path of content) {
606
+ const mode = modes.get(path);
607
+ 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: []
611
+ });
612
+ }
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;
537
616
  }
538
617
  function basePath(entry) {
539
618
  if (entry.status === "added") return null;
@@ -542,41 +621,42 @@ function basePath(entry) {
542
621
  function headPath(entry) {
543
622
  return entry.status === "deleted" ? null : entry.path;
544
623
  }
545
- async function classifyChange(repo, forkPoint, head, entry) {
546
- const [baseSide, headSide] = await Promise.all([classifyAt(repo, forkPoint, basePath(entry)), classifyAt(repo, head, headPath(entry))]);
547
- return {
548
- base: baseSide,
549
- head: headSide
550
- };
624
+ function present(paths) {
625
+ return paths.filter((path) => path !== null);
551
626
  }
552
- const TRANSFORM_MODEL_PATTERN = `model["']?:[[:space:]]*["']?Transform`;
553
- function record(atHead, file) {
554
- if (file?.kind === "transform") atHead.names.set(file.entityId, file.name);
555
- else if (file?.kind === "transform-test") atHead.tested.add(file.transformId);
627
+ function sideOf(files, path) {
628
+ 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;
556
632
  }
557
- async function scanHead(repo, head, changes, changedPaths) {
558
- const atHead = {
559
- names: /* @__PURE__ */ new Map(),
560
- tested: /* @__PURE__ */ new Set()
561
- };
562
- for (const change of changes) record(atHead, change.head);
563
- const unchanged = (await filesMatching(repo, head, TRANSFORM_MODEL_PATTERN, CONTENT_PATHSPECS)).filter((path) => !changedPaths.has(path));
564
- for (const [path, text] of await readFilesAt(repo, head, unchanged)) record(atHead, classifyContentFile(path, parseYaml(text, z.unknown(), { source: `${head}:${path}` })));
565
- return atHead;
633
+ 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) => ({
636
+ path: entry.path,
637
+ base: sideOf(baseFiles, basePath(entry)),
638
+ head: sideOf(headFiles, headPath(entry))
639
+ }));
566
640
  }
567
- async function planJob(repoPath, baseRef, headRef) {
568
- const repo = await repositoryRoot(repoPath);
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]);
644
+ }
645
+ async function planJob(repoPath, baseRef, headRef, signal) {
646
+ const repo = await repositoryRoot(repoPath, signal);
569
647
  const [base, head, checkout] = await Promise.all([
570
- resolveCommit(repo, baseRef, "--base"),
571
- resolveCommit(repo, headRef, "--head"),
572
- checkedOutCommit(repo)
648
+ resolveCommit(repo, baseRef, "--base", signal),
649
+ resolveCommit(repo, headRef, "--head", signal),
650
+ checkedOutCommit(repo, signal)
573
651
  ]);
574
- const forkPoint = await mergeBase(repo, base, head);
575
- const entries = parseNameStatus(await diffNameStatus(repo, forkPoint, head));
576
- const changes = await Promise.all(entries.map((entry) => classifyChange(repo, forkPoint, head, entry)));
577
- const atHead = await scanHead(repo, head, changes, new Set(entries.map((entry) => entry.path)));
652
+ const forkPoint = await mergeBase(repo, base, head, signal);
653
+ 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);
578
656
  const checked = [];
579
657
  const warnings = [];
658
+ const noContent = noContentChangesWarning(entries);
659
+ if (noContent !== null) warnings.push(noContent);
580
660
  for (const entityId of affectedTransforms(changes)) {
581
661
  const name = atHead.names.get(entityId);
582
662
  if (name === void 0) {
@@ -597,7 +677,7 @@ async function planJob(repoPath, baseRef, headRef) {
597
677
  testedAtHead: atHead.tested
598
678
  });
599
679
  if (head === checkout) {
600
- const uncommitted = uncommittedContentWarning(parseStatusPorcelain(await statusPorcelain(repo)));
680
+ const uncommitted = uncommittedContentWarning(parseStatusPorcelain(await statusPorcelain(repo, signal)));
601
681
  if (uncommitted !== null) warnings.push(uncommitted);
602
682
  }
603
683
  return {
@@ -612,17 +692,31 @@ async function planJob(repoPath, baseRef, headRef) {
612
692
  }
613
693
  //#endregion
614
694
  //#region src/commands/ci/job.ts
695
+ async function writeResults(path, events) {
696
+ const temporary = join(dirname(path), `.${basename(path)}.${randomUUID()}.tmp`);
697
+ await writeFile(temporary, serializeJson(events, false) + "\n", {
698
+ encoding: "utf8",
699
+ flag: "wx"
700
+ });
701
+ try {
702
+ await rename(temporary, path);
703
+ } catch (error) {
704
+ await rm(temporary, { force: true });
705
+ throw error;
706
+ }
707
+ }
615
708
  async function runJob(options) {
709
+ if (options.resultsPath !== null) await rm(options.resultsPath, { force: true });
616
710
  const events = [];
617
711
  const emit = createEmitter({
618
712
  job: options.job,
619
713
  now: options.now,
620
714
  write: (event) => {
621
715
  events.push(event);
622
- options.write(event);
716
+ if (event.type !== "job.end") options.write(event);
623
717
  }
624
718
  });
625
- const plan = await planJob(options.repo, options.base, options.head);
719
+ const plan = await planJob(options.repo, options.base, options.head, options.signal);
626
720
  emit({
627
721
  type: "job.start",
628
722
  v: 1,
@@ -631,7 +725,7 @@ async function runJob(options) {
631
725
  warnings: plan.warnings
632
726
  });
633
727
  const outcome = await runStages(await options.stages(plan), emit);
634
- emit({
728
+ const end = emit({
635
729
  type: "job.end",
636
730
  state: outcome.state,
637
731
  counts: {
@@ -639,7 +733,8 @@ async function runJob(options) {
639
733
  warnings: outcome.warnings + plan.warnings.length
640
734
  }
641
735
  });
642
- if (options.resultsPath !== null) await writeFile(options.resultsPath, serializeJson(events, false) + "\n", "utf8");
736
+ if (options.resultsPath !== null) await writeResults(options.resultsPath, events);
737
+ options.write(end);
643
738
  return outcome.state;
644
739
  }
645
740
  //#endregion
@@ -648,21 +743,122 @@ function testVerdict(result) {
648
743
  const statuses = new Set(result.expectations.map((expectation) => expectation.status));
649
744
  if (statuses.has("failed")) return "failed";
650
745
  if (statuses.has("error")) return "error";
651
- return "passed";
746
+ return result.status;
652
747
  }
653
748
  function testRefusal(error) {
654
- if (!(error instanceof HttpError)) return null;
655
- const code = TransformTestRefusalCode.safeParse(error.errorCode);
656
- if (!code.success) return null;
749
+ if (!(error instanceof HttpError) || error.errorCode === null || !isTransformTestCode(error.errorCode)) return null;
657
750
  return {
658
- error_code: code.data,
659
- message: error.serverMessage ?? error.message
751
+ error_code: error.errorCode,
752
+ message: error.serverMessage ?? error.message,
753
+ tables: error.tables
660
754
  };
661
755
  }
662
756
  const CHANGE_FAILURE_CODES = /* @__PURE__ */ new Set(["transform-test.transform-failed", "transform-test.unparseable-source"]);
663
757
  function refusalVerdict(refusal) {
664
758
  return CHANGE_FAILURE_CODES.has(refusal.error_code) ? "failed" : "refused";
665
759
  }
760
+ const TRANSFORM_NOT_ON_INSTANCE = {
761
+ error_code: "ci.transform-not-on-instance",
762
+ message: "the transform is in the branch but not on the Metabase instance; load the branch into it first"
763
+ };
764
+ const INVALID_ENTITY_ID = {
765
+ error_code: "ci.invalid-entity-id",
766
+ message: "the transform's entity_id in the branch is not a valid entity id"
767
+ };
768
+ const TRANSFORM_UNREADABLE = {
769
+ error_code: "ci.transform-unreadable",
770
+ message: "the CI identity cannot read this transform, so Metabase hides its tests: reading it needs query permission on every table it reads"
771
+ };
772
+ const TEST_RUN_FORBIDDEN = {
773
+ error_code: "ci.test-run-forbidden",
774
+ message: "Metabase did not let the CI identity run this test: running a test needs write access to it, and a read-only remote-synced instance refuses everyone"
775
+ };
776
+ function requestFailure(error) {
777
+ if (error instanceof HttpError) return {
778
+ error_code: "ci.http-error",
779
+ status: error.status,
780
+ message: error.message
781
+ };
782
+ if (error instanceof TimeoutError) return {
783
+ error_code: "ci.timeout",
784
+ status: null,
785
+ message: error.message
786
+ };
787
+ if (error instanceof NetworkError) return {
788
+ error_code: "ci.network-error",
789
+ status: null,
790
+ message: error.message
791
+ };
792
+ if (error instanceof ResponseShapeError) return {
793
+ error_code: "ci.unreadable-answer",
794
+ status: null,
795
+ message: error.message
796
+ };
797
+ return null;
798
+ }
799
+ function sessionStop(error) {
800
+ if (error instanceof CapabilityError) return {
801
+ code: "ci.unsupported-server",
802
+ message: error.message
803
+ };
804
+ if (error instanceof HttpError && error.kind === "auth") return {
805
+ code: "ci.unauthorized",
806
+ message: error.message
807
+ };
808
+ return null;
809
+ }
810
+ function lookupStop(error) {
811
+ const stop = sessionStop(error);
812
+ if (stop !== null) return stop;
813
+ const failure = requestFailure(error);
814
+ return failure === null ? null : {
815
+ code: failure.error_code,
816
+ message: failure.message
817
+ };
818
+ }
819
+ const REFUSAL_FOR_FORBIDDEN = {
820
+ read: TRANSFORM_UNREADABLE,
821
+ list: null,
822
+ run: TEST_RUN_FORBIDDEN
823
+ };
824
+ function transformFailure(error, request) {
825
+ const stop = sessionStop(error);
826
+ if (stop !== null) return {
827
+ kind: "stop",
828
+ stop
829
+ };
830
+ const refusal = request === "run" ? testRefusal(error) : null;
831
+ if (refusal !== null) return {
832
+ kind: "check",
833
+ verdict: refusalVerdict(refusal),
834
+ detail: refusal
835
+ };
836
+ const forbiddenRefusal = error instanceof HttpError && error.kind === "forbidden" ? REFUSAL_FOR_FORBIDDEN[request] : null;
837
+ if (forbiddenRefusal !== null) return {
838
+ kind: "check",
839
+ verdict: "refused",
840
+ detail: forbiddenRefusal
841
+ };
842
+ if (request === "read" && isHttpNotFound(error)) return {
843
+ kind: "check",
844
+ verdict: "refused",
845
+ detail: TRANSFORM_NOT_ON_INSTANCE
846
+ };
847
+ const failure = requestFailure(error);
848
+ if (failure === null) return null;
849
+ if (error instanceof NetworkError && request !== "run") return {
850
+ kind: "stop",
851
+ stop: {
852
+ code: failure.error_code,
853
+ message: failure.message
854
+ }
855
+ };
856
+ return {
857
+ kind: "check",
858
+ verdict: "error",
859
+ detail: failure
860
+ };
861
+ }
666
862
  //#endregion
667
863
  //#region src/commands/ci/unit-stage.ts
668
864
  const TEST_TIMEOUT_MS = 12e4;
@@ -680,12 +876,17 @@ function transformLookup(entityId, entry) {
680
876
  function clientPort(client) {
681
877
  return {
682
878
  lookUpTransforms: async (entityIds) => {
683
- const result = await client.eidTranslation.translate({ entity_ids: { transform: [...entityIds] } });
879
+ const result = await client.eidTranslation.translate({ entity_ids: { transform: [...entityIds] } }, { signal: interruptSignal });
684
880
  const lookups = /* @__PURE__ */ new Map();
685
881
  for (const entityId of entityIds) lookups.set(entityId, transformLookup(entityId, result.entity_ids[entityId]));
686
882
  return lookups;
687
883
  },
688
- listTests: async (transformId) => (await client.transformTest.list({ "transform-id": transformId })).data,
884
+ readTransform: async (transformId) => {
885
+ await client.transform.get(transformId, { signal: interruptSignal });
886
+ },
887
+ listTests: async (transformId) => {
888
+ return (await client.transformTest.list({ "transform-id": transformId }, { signal: interruptSignal })).data;
889
+ },
689
890
  runTest: (testId) => client.transformTest.run(testId, {
690
891
  timeoutMs: TEST_TIMEOUT_MS,
691
892
  signal: interruptSignal
@@ -694,21 +895,80 @@ function clientPort(client) {
694
895
  };
695
896
  }
696
897
  const MISSING_TRANSFORM_DETAIL = {
697
- "not-found": {
698
- error_code: "ci.transform-not-on-instance",
699
- message: "the transform is in the branch but not on the Metabase instance; load the branch into it first"
700
- },
701
- "invalid-entity-id": {
702
- error_code: "ci.invalid-entity-id",
703
- message: "the transform's entity_id in the branch is not a valid entity id"
704
- }
898
+ "not-found": TRANSFORM_NOT_ON_INSTANCE,
899
+ "invalid-entity-id": INVALID_ENTITY_ID
705
900
  };
901
+ async function attempt(request, call) {
902
+ try {
903
+ return {
904
+ kind: "settled",
905
+ value: await call()
906
+ };
907
+ } catch (error) {
908
+ const failure = transformFailure(error, request);
909
+ if (failure === null) throw error;
910
+ return failure;
911
+ }
912
+ }
913
+ function reportFailure(failure, check, ms, report) {
914
+ if (failure.kind === "stop") return failure.stop;
915
+ report({
916
+ check,
917
+ verdict: failure.verdict,
918
+ warnings: [],
919
+ ms,
920
+ detail: failure.detail
921
+ });
922
+ return null;
923
+ }
924
+ async function checkTransform(transform, transformId, port, report) {
925
+ const transformCheck = {
926
+ kind: "transform-test",
927
+ id: transform.entity_id,
928
+ label: transform.name
929
+ };
930
+ const started = port.nowMs();
931
+ const read = await attempt("read", () => port.readTransform(transformId));
932
+ if (read.kind !== "settled") return reportFailure(read, transformCheck, port.nowMs() - started, report);
933
+ const tests = await attempt("list", () => port.listTests(transformId));
934
+ if (tests.kind !== "settled") return reportFailure(tests, transformCheck, port.nowMs() - started, report);
935
+ for (const test of tests.value.toSorted((a, b) => a.id - b.id)) {
936
+ const check = {
937
+ kind: "transform-test",
938
+ id: String(test.id),
939
+ label: `${transform.name}: ${test.name}`
940
+ };
941
+ const runStarted = port.nowMs();
942
+ const run = await attempt("run", () => port.runTest(test.id));
943
+ const ms = port.nowMs() - runStarted;
944
+ if (run.kind === "settled") {
945
+ report({
946
+ check,
947
+ verdict: testVerdict(run.value),
948
+ warnings: [],
949
+ ms,
950
+ detail: run.value
951
+ });
952
+ continue;
953
+ }
954
+ const stop = reportFailure(run, check, ms, report);
955
+ if (stop !== null) return stop;
956
+ }
957
+ return null;
958
+ }
706
959
  function unitStage(checked, connect) {
707
960
  return {
708
961
  name: "unit",
709
962
  run: async (report) => {
710
963
  const port = await connect();
711
- const lookups = await port.lookUpTransforms(checked.map((transform) => transform.entity_id));
964
+ let lookups;
965
+ try {
966
+ lookups = await port.lookUpTransforms(checked.map((transform) => transform.entity_id));
967
+ } catch (error) {
968
+ const stop = lookupStop(error);
969
+ if (stop === null) throw error;
970
+ return stop;
971
+ }
712
972
  for (const transform of checked) {
713
973
  const lookup = lookups.get(transform.entity_id);
714
974
  if (lookup === void 0) throw new InternalError(`no lookup for transform ${transform.entity_id}`);
@@ -726,36 +986,10 @@ function unitStage(checked, connect) {
726
986
  });
727
987
  continue;
728
988
  }
729
- const tests = await port.listTests(lookup.id);
730
- for (const test of tests.toSorted((a, b) => a.id - b.id)) {
731
- const check = {
732
- kind: "transform-test",
733
- id: String(test.id),
734
- label: `${transform.name}: ${test.name}`
735
- };
736
- const started = port.nowMs();
737
- try {
738
- const result = await port.runTest(test.id);
739
- report({
740
- check,
741
- verdict: testVerdict(result),
742
- warnings: [],
743
- ms: port.nowMs() - started,
744
- detail: result
745
- });
746
- } catch (error) {
747
- const refusal = testRefusal(error);
748
- if (refusal === null) throw error;
749
- report({
750
- check,
751
- verdict: refusalVerdict(refusal),
752
- warnings: [],
753
- ms: port.nowMs() - started,
754
- detail: refusal
755
- });
756
- }
757
- }
989
+ const stop = await checkTransform(transform, lookup.id, port, report);
990
+ if (stop !== null) return stop;
758
991
  }
992
+ return null;
759
993
  }
760
994
  };
761
995
  }
@@ -766,9 +1000,10 @@ var run_default = defineMetabaseCommand({
766
1000
  name: "run",
767
1001
  description: "Check the transforms a branch changed, writing one JSON event per line"
768
1002
  },
769
- details: "Compares --head with its merge base with --base, finds the transforms whose files or tests changed, and runs their transform tests on the Metabase instance of --profile, which must already hold the branch's content. Only committed content is checked: when --head is the checked-out commit, a content file that is modified or untracked in the working tree is listed in an `uncommitted-content` warning instead. 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 each transform test's result is passed through as the check's `detail`. Exits 1 when the job does not pass; `job.end.state` says whether a check failed (`failed`: fix the transform) or could not run (`errored`: fix the test or the setup). A crash stops the stream before `job.end` and writes no --results file.",
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.",
770
1004
  requires: [
771
1005
  "eidTranslation.translate",
1006
+ "transform.get",
772
1007
  "transformTest.list",
773
1008
  "transformTest.run"
774
1009
  ],
@@ -787,12 +1022,12 @@ var run_default = defineMetabaseCommand({
787
1022
  },
788
1023
  repo: {
789
1024
  type: "string",
790
- description: "Path to the git repository",
1025
+ description: "Path inside the git work tree to check",
791
1026
  default: "."
792
1027
  },
793
1028
  results: {
794
1029
  type: "string",
795
- description: "Also write every event to this JSON file"
1030
+ description: "Also write every event to this file, as one JSON array"
796
1031
  }
797
1032
  },
798
1033
  outputSchema: CiEvent,
@@ -809,8 +1044,9 @@ var run_default = defineMetabaseCommand({
809
1044
  },
810
1045
  job: randomUUID(),
811
1046
  now: () => /* @__PURE__ */ new Date(),
812
- write: writeJsonLine,
813
- resultsPath: args.results ?? null
1047
+ write: jsonLineWriter(process.stdout),
1048
+ resultsPath: args.results ?? null,
1049
+ signal: interruptSignal
814
1050
  });
815
1051
  if (state !== "passed") throw new FailedResultError(`ci job ${state}`);
816
1052
  }