@metabase/cli 0.5.8 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{add-collection-DIrpeq9O.mjs → add-collection-CmMm_mrd.mjs} +3 -3
- package/dist/{alert-DuUreVMq.mjs → alert-DzgnuHeD.mjs} +7 -7
- package/dist/{alerts-C7vJ-NAI.mjs → alerts-XVPPjYuA.mjs} +6 -6
- package/dist/api-key-fIfek7nB.mjs +13 -0
- package/dist/{append-v3fakJOd.mjs → append-CroWojmD.mjs} +4 -4
- package/dist/{archive-DwVbn3FG.mjs → archive-3auH9Cqu.mjs} +3 -3
- package/dist/{archive-CuygjxgQ.mjs → archive-B5vB7VmB.mjs} +3 -3
- package/dist/{archive-BEhhmkTO.mjs → archive-BNSqVL0X.mjs} +3 -3
- package/dist/{archive-D299FxXt.mjs → archive-Bct5mmyU.mjs} +3 -3
- package/dist/{archive-BrGj2yoP.mjs → archive-C2IztpGM.mjs} +3 -3
- package/dist/{archive-TBPRGm7s.mjs → archive-CcY1DPPD.mjs} +3 -3
- package/dist/{archive-Cy6RUJaD.mjs → archive-CqO4ewSD.mjs} +3 -3
- package/dist/{archive-5pjvTser.mjs → archive-D2X9wa0V.mjs} +3 -3
- package/dist/{archive-CmhV_hlP.mjs → archive-DR7_wlfU.mjs} +3 -3
- package/dist/{archive-Bt8fwInI.mjs → archive-Df5QrYTT.mjs} +4 -4
- package/dist/{archive-CyhH-395.mjs → archive-Dvv8fBSZ.mjs} +3 -3
- package/dist/{archive-DdPaXaJo.mjs → archive-wE0j9Gh2.mjs} +4 -4
- package/dist/{auth-B63Hfuwg.mjs → auth-DZuXbdpQ.mjs} +5 -5
- package/dist/{branches-BM0L124r.mjs → branches-BezC3sHZ.mjs} +3 -3
- package/dist/{breaking-VMUrgVVE.mjs → breaking-CqjNq8CR.mjs} +5 -5
- package/dist/{broken-BbpNq5WG.mjs → broken-lZ8qMOln.mjs} +6 -6
- package/dist/{bulk-discard-values-Cohox-Zb.mjs → bulk-discard-values-BPXPfKJy.mjs} +5 -5
- package/dist/{bulk-edit-B9-XH592.mjs → bulk-edit-Bajdousu.mjs} +3 -3
- package/dist/{bulk-rescan-values-CsWKirZw.mjs → bulk-rescan-values-BtjDygX8.mjs} +4 -4
- package/dist/{bulk-sync-schema-CorpcAjz.mjs → bulk-sync-schema-B14uspcP.mjs} +4 -4
- package/dist/{cancel-iGUVm1h2.mjs → cancel-DEVOa-MW.mjs} +3 -3
- package/dist/{cancel-task-DWiicb_t.mjs → cancel-task-CTwY4JBD.mjs} +2 -2
- package/dist/card-6k-rr68M.mjs +25 -0
- package/dist/{cards-D6jWz0N9.mjs → cards-Crcdtq-N.mjs} +4 -4
- package/dist/{ci-BQKTyiRi.mjs → ci-jYpJfw-b.mjs} +2 -2
- package/dist/cli.mjs +48 -43
- package/dist/{client-Ba61gEcn.mjs → client-KtK81Nqf.mjs} +5 -5
- package/dist/collection-D_GyzwaY.mjs +21 -0
- package/dist/{collection-namespace-D0_-6eWp.mjs → collection-namespace-lQLOPUbc.mjs} +1 -1
- package/dist/{content-translation-B57aHSuc.mjs → content-translation-DxQ9Ofz9.mjs} +3 -3
- package/dist/{copy-BeD_fk5J.mjs → copy-CoxRAUMr.mjs} +4 -4
- package/dist/{copy-D-VIVVB1.mjs → copy-DFitNtKJ.mjs} +4 -4
- package/dist/{copy-flags-BcW9weVX.mjs → copy-flags-Bgy3sSFg.mjs} +1 -1
- package/dist/{create-_wex54m1.mjs → create-BC9wRB9s.mjs} +2 -2
- package/dist/{create-BURh8kHp.mjs → create-BGbRafMp.mjs} +3 -3
- package/dist/{create-CGiSOtIR.mjs → create-Bb88WIvb.mjs} +3 -3
- package/dist/{create-B--aPIbp.mjs → create-C-YkTe7c.mjs} +2 -2
- package/dist/{create-Bw9DAq_x2.mjs → create-C0AulMAA2.mjs} +5 -5
- package/dist/{create-lHqpAdxg.mjs → create-CbC0hOjy.mjs} +3 -3
- package/dist/{create-Dm8BvbsU.mjs → create-Cry2OdY-.mjs} +2 -2
- package/dist/{create-DQOapeeI.mjs → create-Cw69tpmd.mjs} +3 -3
- package/dist/{create-zALsdMhF.mjs → create-D7g0hJOc.mjs} +2 -2
- package/dist/{create-MmZVmRsH.mjs → create-D9NWzXsE.mjs} +2 -2
- package/dist/{create-DF7nC9qW.mjs → create-DOe4dci_.mjs} +2 -2
- package/dist/{create-D44fg_iT.mjs → create-DR24U6M5.mjs} +2 -2
- package/dist/{create-DcDyXL4g.mjs → create-DZ7qd9Ml.mjs} +2 -2
- package/dist/{create-BlmRvuQg.mjs → create-Dgle-z_r.mjs} +3 -3
- package/dist/{create-DybrZHV-.mjs → create-Dja5oR7y.mjs} +2 -2
- package/dist/{create-D-Eu1nBe.mjs → create-W6plJIxv.mjs} +3 -3
- package/dist/{create-CP9lrYE4.mjs → create-Y4dYSqkx.mjs} +2 -2
- package/dist/{create-branch-C8arQT4M.mjs → create-branch-3zuqlQtJ.mjs} +2 -2
- package/dist/{create-HNchuWpj.mjs → create-fES0KGq_.mjs} +3 -3
- package/dist/{create-C6c-clLK.mjs → create-ktw5Ac64.mjs} +3 -3
- package/dist/{create-DMtlDzrE.mjs → create-tNnX8P12.mjs} +3 -3
- package/dist/{csv-CFabsH6l.mjs → csv-BAHZWlX7.mjs} +4 -4
- package/dist/{current-task-GG6JqfGp.mjs → current-task-iCOdB8fq.mjs} +2 -2
- package/dist/dashboard-CZTlBLWV.mjs +35 -0
- package/dist/data-action-C4wpuM5b.mjs +24 -0
- package/dist/{data-app-DtW2i2SD.mjs → data-app-DLKxuzzt.mjs} +9 -10
- package/dist/{data-app-project-CHYcJRrU.mjs → data-app-project-tbEA14ZN.mjs} +2 -11
- package/dist/{db-DSj2I1X9.mjs → db-p_oz1oom.mjs} +8 -8
- package/dist/{delete-Dkwk2mZH.mjs → delete-1gN-o-5K.mjs} +4 -4
- package/dist/{delete-CsgkbmBy.mjs → delete-CG2tAvao.mjs} +4 -4
- package/dist/{delete-Cx75BsN6.mjs → delete-CioggolY.mjs} +4 -4
- package/dist/{delete-CxL5okLN.mjs → delete-D4lz39ia.mjs} +4 -4
- package/dist/{delete-CnGb4QIm.mjs → delete-DLdB_wgT.mjs} +4 -4
- package/dist/{delete-AF64p0xG.mjs → delete-Dk_HWWGD.mjs} +4 -4
- package/dist/{delete-D_l7FRE6.mjs → delete-PGOszHpK.mjs} +4 -4
- package/dist/{delete-runtime-BUbOQx5I.mjs → delete-runtime-CdyjCkIN.mjs} +1 -1
- package/dist/{delete-table-CubRk-5H.mjs → delete-table-Cac6Ca0V.mjs} +4 -4
- package/dist/{delete-YKX2HilI.mjs → delete-udML_JUv.mjs} +4 -4
- package/dist/{delete-C6N_Aj78.mjs → delete-yifA3qMj.mjs} +4 -4
- package/dist/{delete-ClT1ROrM.mjs → delete-z-gK8iJm.mjs} +3 -3
- package/dist/{dependencies-W2dxsCpF.mjs → dependencies-BHAXa11a.mjs} +5 -5
- package/dist/dependency-DAZ7izEx.mjs +15 -0
- package/dist/{dependency-BPCRTBKd.mjs → dependency-DdeyH8m5.mjs} +1 -1
- package/dist/{dependents-B4V8pDYq.mjs → dependents-CQW2r57U.mjs} +6 -6
- package/dist/{dirty-DKj4ScBN.mjs → dirty-5J3cFbn0.mjs} +3 -3
- package/dist/{discard-values-5l91z3Dy.mjs → discard-values-DoMNb8_8.mjs} +5 -5
- package/dist/document-BfUBDUop.mjs +20 -0
- package/dist/{download-DRxR3yGi.mjs → download-DKC9Fc5U.mjs} +2 -2
- package/dist/{eid-B6JYogNC.mjs → eid-CaOQrj7K.mjs} +2 -2
- package/dist/{entry-flags-6Y0fdYbG.mjs → entry-flags-D9XxKqja.mjs} +1 -1
- package/dist/{events-CE80dTib.mjs → events-xcwriG3s.mjs} +4 -4
- package/dist/{execute-C9foK2IU.mjs → execute-Bl0aIiVm.mjs} +3 -3
- package/dist/{export-B8QyoPWP.mjs → export-CAAsW3Ic.mjs} +3 -3
- package/dist/{export-flags-DvdxpRMt.mjs → export-flags-DkbYlWlE.mjs} +1 -1
- package/dist/{export-preflight-BQbwxyoJ.mjs → export-preflight-z3se4KNH.mjs} +2 -2
- package/dist/field-BaP1WuNk.mjs +21 -0
- package/dist/{fields-TE3PnmwW.mjs → fields-D6ahsDuL.mjs} +4 -4
- package/dist/{filter-flags-DAUmdUwa.mjs → filter-flags-B5jGUuHb.mjs} +1 -1
- package/dist/{fks-B41Re5dt.mjs → fks-YDJYEf6A.mjs} +5 -5
- package/dist/{get-uWk0JKvF.mjs → get-B--gct26.mjs} +4 -4
- package/dist/{get-BAGQ-bvt.mjs → get-B2EZrxOx.mjs} +3 -3
- package/dist/{get-CefCrGWy.mjs → get-B37iHLDH.mjs} +3 -3
- package/dist/{get-Ck0cRNyr.mjs → get-B6_odQZ5.mjs} +4 -4
- package/dist/{get-m-vpr7GN.mjs → get-BP1LF94h.mjs} +3 -3
- package/dist/{get-DG5fPl-S.mjs → get-BTGk87ul.mjs} +4 -4
- package/dist/{get-sjhoDJyB.mjs → get-B_umve7N.mjs} +5 -5
- package/dist/{get-DeiC05Lm.mjs → get-BqRBiE5p.mjs} +3 -3
- package/dist/{get-BcWr6U0n.mjs → get-Buo9ZBkR.mjs} +2 -2
- package/dist/{get-CU6AWbqL.mjs → get-Bvm3hqLA.mjs} +3 -3
- package/dist/{get-PLIbg3Vy.mjs → get-C3nYPTjw.mjs} +3 -3
- package/dist/{get-DbZDp2ve.mjs → get-CIF7wpWz.mjs} +3 -3
- package/dist/{get-BscyItLF.mjs → get-CLS88UQZ.mjs} +3 -3
- package/dist/{get-3XwqrjxC.mjs → get-CshKB-TI.mjs} +3 -3
- package/dist/{get-DJeeXbwj.mjs → get-D-GWrJbm.mjs} +3 -3
- package/dist/{get-BJgBsiEK.mjs → get-DDco3nzW.mjs} +2 -2
- package/dist/{get-CtRJUybv.mjs → get-DRyMrvcE.mjs} +3 -3
- package/dist/{get-DvPgnqAA.mjs → get-DVjckQ-H.mjs} +2 -2
- package/dist/{get-CCTRO18v.mjs → get-DYVS_TQr.mjs} +4 -4
- package/dist/{get-Cjk-A9dQ.mjs → get-DbMcR5BK.mjs} +3 -3
- package/dist/{get-BrhIHZYg.mjs → get-DlmfJ-Oz.mjs} +3 -3
- package/dist/{get-DguRhloC.mjs → get-_U_p81sV.mjs} +2 -2
- package/dist/{get-run-DnKCnvUa.mjs → get-run-TKAGC3P7.mjs} +4 -4
- package/dist/git-sync-OiTQkETw.mjs +29 -0
- package/dist/glossary-Lh3tF1Qq.mjs +14 -0
- package/dist/{graph-B2YkV2oX.mjs → graph-PlB4hnwq.mjs} +4 -4
- package/dist/{group-D1nGaEXW.mjs → group-DxaqX3Gg.mjs} +1 -1
- package/dist/{has-remote-changes-ChMSjhmC.mjs → has-remote-changes-DEWZ6Ou9.mjs} +2 -2
- package/dist/{import-CNpN9iS0.mjs → import-B2ypv7h6.mjs} +3 -3
- package/dist/{is-dirty-Bud2wuxQ.mjs → is-dirty-DOT8Vudt.mjs} +2 -2
- package/dist/{items-B1DXzdwg.mjs → items-B3_o0a77.mjs} +3 -3
- package/dist/{library-8G0s5VLO.mjs → library-Cb2Fftvz.mjs} +5 -5
- package/dist/{list-Ckqu41Zx.mjs → list--edVvzMi.mjs} +3 -3
- package/dist/{list-BeaA7gQ7.mjs → list-BDtjlkFq.mjs} +3 -3
- package/dist/{list-CcfGHN19.mjs → list-BG3Wnz6W.mjs} +3 -3
- package/dist/{list-Dk3upD11.mjs → list-BR7IFq0C.mjs} +5 -5
- package/dist/{list-CO62N1eD.mjs → list-BY60e8C8.mjs} +3 -3
- package/dist/{list-D1WO7lxP.mjs → list-CHIem8-N.mjs} +4 -4
- package/dist/{list-CPwy-AEn.mjs → list-CSFgvwH_.mjs} +3 -3
- package/dist/{list-0aX638ov.mjs → list-CXSp-j5P.mjs} +5 -5
- package/dist/{list-B2PbnXp4.mjs → list-C_B1kTwx.mjs} +4 -4
- package/dist/{list-BSvQ0AT_.mjs → list-CkX5ENLW.mjs} +3 -3
- package/dist/{list-BVz0N0g_.mjs → list-CmWo_ZjN.mjs} +3 -3
- package/dist/{list-DX5SmBCQ.mjs → list-DWPpi4dL.mjs} +3 -3
- package/dist/{list-CAZHyhd5.mjs → list-DX2i7VnN.mjs} +3 -3
- package/dist/{list-tHzgEkzv.mjs → list-DcqNpXXs.mjs} +5 -5
- package/dist/{list-z4iqffgl.mjs → list-Ddt1V45U.mjs} +4 -4
- package/dist/{list-DpyIV41y.mjs → list-DfB0bJFW.mjs} +4 -4
- package/dist/{list-JqIFAXpw.mjs → list-DfW_DTZJ.mjs} +3 -3
- package/dist/{list-CAAvhl9h.mjs → list-Dq770-CV.mjs} +3 -3
- package/dist/{list-mRSKPKoO.mjs → list-HJD-0aTX.mjs} +3 -3
- package/dist/{list-DZLGnsAK.mjs → list-l4RHGqYc.mjs} +3 -3
- package/dist/{list-DAdWd8gn.mjs → list-pEV6jJNj.mjs} +3 -3
- package/dist/{list-DjfY80vj.mjs → list-shpxsUkv.mjs} +3 -3
- package/dist/{list-BJasizCe.mjs → list-vjCcTMd9.mjs} +3 -3
- package/dist/{login-muOdP15h.mjs → login-BQWBAyTZ.mjs} +4 -4
- package/dist/{logout-Z0_gSSM4.mjs → logout-BWUdSl4Y.mjs} +2 -2
- package/dist/{measure-CTF4qqiE.mjs → measure-BvVdTVUb.mjs} +6 -6
- package/dist/{notification-Bj45MgWm.mjs → notification-Ec1Jo0Oc.mjs} +1 -1
- package/dist/{parameter-values-to5u_-2n.mjs → parameter-values-BdJU4BZt.mjs} +3 -3
- package/dist/{parse-id-CbF7zxGv.mjs → parse-id-DKKwr4c8.mjs} +1 -1
- package/dist/{path-B-0S-9i2.mjs → path-BvKEH753.mjs} +4 -4
- package/dist/{poll-zB3cX0Kq.mjs → poll-CAd7bLO6.mjs} +1 -1
- package/dist/{preflight-Dgj2yyY_.mjs → preflight-Beuxqhhf.mjs} +1 -1
- package/dist/{publish-tKh9tJeh.mjs → publish-C3cI9Qtw.mjs} +3 -3
- package/dist/{pulse-DhddeMMG.mjs → pulse-BWs9tqVG.mjs} +1 -1
- package/dist/{query-o1Z2dgtw.mjs → query-Cl56xhGd.mjs} +4 -4
- package/dist/{query-B4xK9j0u.mjs → query-DF5jl95P.mjs} +4 -4
- package/dist/{remapping-DT305P-k.mjs → remapping-D0Qs4ZA0.mjs} +3 -3
- package/dist/{remove-collection-Bsme1OBA.mjs → remove-collection-DsZSjVj0.mjs} +3 -3
- package/dist/{render-Df56xOPJ.mjs → render-D3jLDfVB.mjs} +1 -1
- package/dist/{replace-C3PQwvfl.mjs → replace-dk5xzOnf.mjs} +4 -4
- package/dist/{requirement-check-qqRkRGcN.mjs → requirement-check-CPY23KwS.mjs} +2 -2
- package/dist/{rescan-values-C0P0upEB.mjs → rescan-values-C_iCyt-D.mjs} +4 -4
- package/dist/{rescan-values-CGN4bjyB.mjs → rescan-values-W5Flh4Sz.mjs} +3 -3
- package/dist/{run-ZRY5wy4x.mjs → run-B99b6wgr.mjs} +4 -4
- package/dist/{run-bVD66LrU.mjs → run-BT7raH2f.mjs} +3 -3
- package/dist/{run-BaEBHum8.mjs → run-C0s2hLrU.mjs} +4 -4
- package/dist/{run-C_IZfmQ7.mjs → run-Ch9KAm3W.mjs} +404 -85
- package/dist/{runs-DWm1pIdP.mjs → runs-ClQVvDP2.mjs} +5 -5
- package/dist/{runtime-Dd2uBKqN.mjs → runtime-fJ-A3m-R.mjs} +6 -5
- package/dist/{scaffold-BnT99dhY.mjs → scaffold-Pr1BC2uw.mjs} +4 -4
- package/dist/{schema-DoELV3FT.mjs → schema-BeSsPxHM.mjs} +2 -2
- package/dist/{schema-tables-DGyHJnJX.mjs → schema-tables-D1meU1m8.mjs} +5 -5
- package/dist/{schemas-7w9MPZcZ.mjs → schemas-Db3hQ0pY.mjs} +4 -4
- package/dist/{search-CyyySVTz.mjs → search-D5y8utnT.mjs} +4 -4
- package/dist/{search-BZh0BwZ9.mjs → search-oxkqbIhJ.mjs} +4 -4
- package/dist/{segment--JCnZCgL.mjs → segment-D41roFlj.mjs} +6 -6
- package/dist/{send-c4FpapMf.mjs → send-CuHw5ZWw.mjs} +3 -3
- package/dist/{set-_puxfyaP.mjs → set-CdyFUlSB.mjs} +2 -2
- package/dist/{set-active-BexM75_s.mjs → set-active-BsQ0NqQ5.mjs} +2 -2
- package/dist/{set-data-actions-BnrX6ARh.mjs → set-data-actions-BvNTY-Z1.mjs} +3 -3
- package/dist/{set-enabled-Np4xqFA_.mjs → set-enabled-Q7t_Jjev.mjs} +2 -2
- package/dist/{set-sensitivity-DDzrkjdv.mjs → set-sensitivity-swrcaUgD.mjs} +3 -3
- package/dist/{setting-BGK1uRxm.mjs → setting-h61KIVKH.mjs} +4 -4
- package/dist/{setup-BCliYRAt.mjs → setup-okGErkpB.mjs} +2 -2
- package/dist/{skill-list-4LagdTq5.mjs → skill-list-Duqt2jXs.mjs} +1 -1
- package/dist/{skills-CoXIflBw.mjs → skills-DdWfwXGa.mjs} +3 -3
- package/dist/{skills-PULUh2VV.mjs → skills-Dt95bt39.mjs} +3 -3
- package/dist/snippet-BBGYQKde.mjs +19 -0
- package/dist/{stash-d3FtnE2F.mjs → stash-BSO_xPcW.mjs} +3 -3
- package/dist/{status-CJLd6Uk6.mjs → status-BLgQLUHs.mjs} +2 -2
- package/dist/{status-BJTdBEN4.mjs → status-BxjdPnb1.mjs} +3 -3
- package/dist/{subscription-Dpq45fMP.mjs → subscription-DfvU-S6P.mjs} +6 -6
- package/dist/{subscriptions-Cu4km2CN.mjs → subscriptions-Zqtq7ela.mjs} +6 -6
- package/dist/{summary-3Av5251t.mjs → summary-Bsxzo3_S.mjs} +3 -3
- package/dist/{sync-schema-BNHt8S3P.mjs → sync-schema-BBOF-1tN.mjs} +4 -4
- package/dist/{sync-schema-BYJuUBsG.mjs → sync-schema-BgxhQ9xQ.mjs} +4 -4
- package/dist/table-Bye5_War.mjs +26 -0
- package/dist/{table-DFbkLSgK.mjs → table-CcUdjp8F.mjs} +1 -1
- package/dist/{table-selector-flags-BiA8p56s.mjs → table-selector-flags-FNPxRc9B.mjs} +2 -2
- package/dist/timeline-CKCQZSb9.mjs +21 -0
- package/dist/{timeline-event-BLBqSAjH.mjs → timeline-event-BMWbJ0Jh.mjs} +6 -6
- package/dist/transform-CBxCkbqF.mjs +28 -0
- package/dist/{transform-CKeLIqoc.mjs → transform-drjCAqQs.mjs} +1 -1
- package/dist/transform-job-BTM0t8YZ.mjs +22 -0
- package/dist/{transform-tag-CETHXuAi.mjs → transform-tag-DcJRURqM.mjs} +5 -5
- package/dist/transform-test-CSRDJsx3.mjs +16 -0
- package/dist/{transforms-CstSQgQ-.mjs → transforms-DFDPNGru.mjs} +5 -5
- package/dist/{transport-D74axlBJ.mjs → transport-Bu0oqcBN.mjs} +3 -3
- package/dist/{tree-BDhm4E_W.mjs → tree-vMNC9YkV.mjs} +2 -2
- package/dist/{typed-schema-BZkv_z2f.mjs → typed-schema-DTL_8ggU.mjs} +3 -3
- package/dist/{unpublish-BNBSxZFl.mjs → unpublish-D8ZsafOS.mjs} +3 -3
- package/dist/{unreferenced-B2nDcQ7m.mjs → unreferenced-DgzxPYG_.mjs} +5 -5
- package/dist/{update-BenGrugz.mjs → update-BDpitlSQ.mjs} +4 -4
- package/dist/{update-B6xWuJqk.mjs → update-BGKttGAR.mjs} +3 -3
- package/dist/{update-DRw_610p.mjs → update-BNreBhC8.mjs} +4 -4
- package/dist/{update-BkFy2BV_.mjs → update-BU0PtxW0.mjs} +4 -4
- package/dist/{update-BA0NxInb.mjs → update-BWV2CQgP.mjs} +4 -4
- package/dist/{update-Cpd1tKqr.mjs → update-BuFR6rqD.mjs} +3 -3
- package/dist/{update-pzu78yZB.mjs → update-C26H37WA.mjs} +3 -3
- package/dist/{update-DT8KwBxO.mjs → update-CFLc58dG.mjs} +4 -4
- package/dist/{update-DU3jpGps.mjs → update-CjlKU5fK.mjs} +3 -3
- package/dist/{update-ByBqo1if.mjs → update-Cn49siEu.mjs} +4 -4
- package/dist/{update-CWzmrGS_.mjs → update-D0vrQg4H.mjs} +3 -3
- package/dist/{update-DrfAOlkf.mjs → update-D32XqpkV.mjs} +3 -3
- package/dist/{update-BGFGng3a.mjs → update-DLvN8aDj.mjs} +4 -4
- package/dist/{update-B9iHHoJB.mjs → update-Dnm1GajC.mjs} +4 -4
- package/dist/{update-DxLypq9G.mjs → update-HsVZcqV2.mjs} +6 -6
- package/dist/{update-BMHPp3nt.mjs → update-OknB30b_.mjs} +3 -3
- package/dist/{update-CFLJWHOT.mjs → update-Xu0_gOUu.mjs} +3 -3
- package/dist/{update-DKcPzoG9.mjs → update-Z4wNTkeQ.mjs} +3 -3
- package/dist/{update-dashcard-BU3Wqcnf.mjs → update-dashcard-De_j3nl_.mjs} +3 -3
- package/dist/{update-BS-E7QLh.mjs → update-rq_xiQwh.mjs} +4 -4
- package/dist/{update-DrvDC_0m2.mjs → update-vXsXeXxz2.mjs} +3 -3
- package/dist/{upgrade-Cq89D4Oe.mjs → upgrade-BrABBJSO.mjs} +2 -2
- package/dist/{upload-CmwO6anj.mjs → upload-B2OzfuaC.mjs} +1 -1
- package/dist/{upload-INQHve86.mjs → upload-B5FlC_HI.mjs} +3 -3
- package/dist/upload-DLOZtgEQ.mjs +13 -0
- package/dist/{uuid-BNJKaQGB.mjs → uuid-DIJU2aoo.mjs} +2 -2
- package/dist/{validate-query-CQ4UPpZX.mjs → validate-query-37KgmRbb.mjs} +1 -1
- package/dist/{values-BVtqwRlR.mjs → values-CQVSrZwT.mjs} +3 -3
- package/dist/{verify-Bp9ZyPgX.mjs → verify-1POh5DMA.mjs} +3 -3
- package/dist/{verify-BwodYH0C.mjs → verify-BwUP351R.mjs} +3 -3
- package/dist/{verify-BSlj7S_E.mjs → verify-DaDS87sR.mjs} +3 -3
- package/dist/{wait-DZclBI3D.mjs → wait-DF5Cxdc8.mjs} +4 -4
- package/dist/{wait-flags-CmIfm6_U.mjs → wait-flags-WpNrwgA3.mjs} +2 -2
- package/dist/{window-BoW2xWwm.mjs → window-CI_QD04s.mjs} +1 -1
- package/dist/{yaml-BthVUNj3.mjs → yaml-Ctv1l2-P.mjs} +15 -6
- package/package.json +1 -1
- package/skill-data/data-app/SKILL.md +23 -17
- package/skill-data/data-app/template/README.md +4 -5
- package/skill-data/data-app/template/gitignore +0 -1
- package/skill-data/data-app/template/package.json +1 -2
- package/skill-data/data-app-ui/SKILL.md +1 -1
- package/skill-data/transform/SKILL.md +1 -1
- package/dist/api-key-Cia6SHQ9.mjs +0 -13
- package/dist/card-C-KsmoJW.mjs +0 -25
- package/dist/collection-DsNRlVcN.mjs +0 -21
- package/dist/dashboard-BRQtw_1e.mjs +0 -35
- package/dist/data-action-Bm6QMyV0.mjs +0 -24
- package/dist/dependency-B0KmVz2u.mjs +0 -15
- package/dist/document--zYZP-u3.mjs +0 -20
- package/dist/field-Bk09crKC.mjs +0 -21
- package/dist/git-sync-DMyH3Y85.mjs +0 -29
- package/dist/glossary-CcRGJaVi.mjs +0 -14
- package/dist/set-bundle-B-qAvkDo.mjs +0 -40
- package/dist/snippet-Czzma-M2.mjs +0 -19
- package/dist/table-CwfF6ZTZ.mjs +0 -26
- package/dist/timeline-C4WCMzDX.mjs +0 -21
- package/dist/transform-job-SQ57QOlQ.mjs +0 -22
- package/dist/transform-jxF8cjNA.mjs +0 -28
- package/dist/transform-test-MlVjTlKo.mjs +0 -16
- 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 {
|
|
3
|
-
import { t as defineMetabaseCommand } from "./runtime-
|
|
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-fJ-A3m-R.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-
|
|
6
|
+
import { t as parseMetabaseYaml } from "./yaml-Ctv1l2-P.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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
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,
|
|
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
|
-
|
|
351
|
-
|
|
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 =
|
|
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")
|
|
391
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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))
|
|
609
|
-
|
|
610
|
-
|
|
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
|
-
|
|
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
|
|
875
|
+
function readingOf(readings, path) {
|
|
628
876
|
if (path === null || !isContentPath(path)) return null;
|
|
629
|
-
const
|
|
630
|
-
if (
|
|
631
|
-
return
|
|
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
|
|
635
|
-
|
|
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:
|
|
638
|
-
head:
|
|
894
|
+
base: baseSide(readingOf(baseReadings, basePath(entry))),
|
|
895
|
+
head: headSide(readingOf(headReadings, headPath(entry)))
|
|
639
896
|
}));
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
|
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,
|
|
1296
|
+
description: "Check the transforms a branch changed, reporting each check as it finishes"
|
|
1002
1297
|
},
|
|
1003
|
-
details:
|
|
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
|
-
|
|
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 };
|