@prisma/orm-toolchain 8.0.0-rc.1-dev.38 → 8.0.0-rc.1-dev.40
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/bin__prisma-next.mjs +25 -318
- package/dist/bin__prisma-next.mjs.map +1 -1
- package/dist/{caught-errors-DLPzfzIW-CQ9BoXD-.mjs → caught-errors-BZo7zcRc-C-pIEasG.mjs} +3 -3
- package/dist/{caught-errors-DLPzfzIW-CQ9BoXD-.mjs.map → caught-errors-BZo7zcRc-C-pIEasG.mjs.map} +1 -1
- package/dist/{cli-errors-BG6bItjG-DwdKIeGz.mjs → cli-errors-B4zTLaqQ-CXgjO9t4.mjs} +20 -3
- package/dist/cli-errors-B4zTLaqQ-CXgjO9t4.mjs.map +1 -0
- package/dist/{cli-errors-BiKBrOXc-JPxQqO58.d.mts → cli-errors-iquUoY6O-BFboxglv.d.mts} +17 -9
- package/dist/cli-errors-iquUoY6O-BFboxglv.d.mts.map +1 -0
- package/dist/cli.mjs +2895 -182
- package/dist/cli.mjs.map +1 -1
- package/dist/cli__commands__contract-emit.mjs +1 -1
- package/dist/cli__commands__contract-infer.mjs +1 -1
- package/dist/cli__commands__db-init.mjs +1 -1
- package/dist/cli__commands__db-schema.mjs +1 -1
- package/dist/cli__commands__db-sign.mjs +1 -1
- package/dist/cli__commands__db-update.mjs +1 -1
- package/dist/cli__commands__db-verify.mjs +1 -1
- package/dist/cli__commands__migrate.d.mts +2 -2
- package/dist/cli__commands__migrate.mjs +1 -1
- package/dist/cli__commands__migration-check.d.mts +1 -1
- package/dist/cli__commands__migration-check.mjs +2 -2
- package/dist/cli__commands__migration-graph.d.mts +2 -2
- package/dist/cli__commands__migration-graph.mjs +1 -1
- package/dist/cli__commands__migration-list.d.mts +2 -2
- package/dist/cli__commands__migration-list.mjs +1 -1
- package/dist/cli__commands__migration-log.d.mts +2 -2
- package/dist/cli__commands__migration-log.mjs +1 -1
- package/dist/cli__commands__migration-new.mjs +1 -1
- package/dist/cli__commands__migration-plan.d.mts +1 -1
- package/dist/cli__commands__migration-plan.mjs +1 -1
- package/dist/cli__commands__migration-show.d.mts +1 -1
- package/dist/cli__commands__migration-show.mjs +1 -1
- package/dist/cli__commands__migration-status.d.mts +2 -2
- package/dist/cli__commands__migration-status.mjs +2 -2
- package/dist/cli__commands__ref.mjs +1 -1
- package/dist/cli__commands__telemetry.mjs +1 -1
- package/dist/cli__control-api.d.mts +4 -4
- package/dist/cli__control-api.mjs +15 -15
- package/dist/cli__control-api__testing.d.mts +1 -1
- package/dist/cli__control-api__testing.mjs +2 -1
- package/dist/cli__control-api__testing.mjs.map +1 -1
- package/dist/cli__init-output.d.mts +1 -1
- package/dist/cli__init-output.mjs +1 -1
- package/dist/{client-DU9qtBxW-BjVHiKzE.mjs → client-C6Qp5ujC-Dxb7r79B.mjs} +12 -15
- package/dist/client-C6Qp5ujC-Dxb7r79B.mjs.map +1 -0
- package/dist/code-templates-D73q329L-CSx0Jg-6.mjs +295 -0
- package/dist/code-templates-D73q329L-CSx0Jg-6.mjs.map +1 -0
- package/dist/combine-verify-results-B87B_pMc-D4l22OqY.mjs +65 -0
- package/dist/combine-verify-results-B87B_pMc-D4l22OqY.mjs.map +1 -0
- package/dist/{command-helpers-CwLvFwMB-BWwUQhlT.mjs → command-helpers-BCgwcqTe-lNCtppzM.mjs} +3 -3
- package/dist/{command-helpers-CwLvFwMB-BWwUQhlT.mjs.map → command-helpers-BCgwcqTe-lNCtppzM.mjs.map} +1 -1
- package/dist/{contract-at-errors-Dptf8RE_-B5gr04Sc.mjs → contract-at-errors-BSoDI0Hw-BXeUNE3s.mjs} +3 -3
- package/dist/{contract-at-errors-Dptf8RE_-B5gr04Sc.mjs.map → contract-at-errors-BSoDI0Hw-BXeUNE3s.mjs.map} +1 -1
- package/dist/contract-emit-DDWBBvF1-BeGZCRrb.mjs +2 -0
- package/dist/{contract-emit-CZQZ1i4z-p9LSGmmQ.mjs → contract-emit-DFScp9D6-6H-UJMFv.mjs} +5 -5
- package/dist/{contract-emit-CZQZ1i4z-p9LSGmmQ.mjs.map → contract-emit-DFScp9D6-6H-UJMFv.mjs.map} +1 -1
- package/dist/{contract-emit-BQFpcb2A-BwT9iVdb.mjs → contract-emit-DsvAx-dY-DQzoDO34.mjs} +5 -5
- package/dist/{contract-emit-BQFpcb2A-BwT9iVdb.mjs.map → contract-emit-DsvAx-dY-DQzoDO34.mjs.map} +1 -1
- package/dist/{contract-infer-C6cYOihE-CIUCgmw0.mjs → contract-infer-BwIDGl7v-CZrdWXAr.mjs} +4 -4
- package/dist/{contract-infer-C6cYOihE-CIUCgmw0.mjs.map → contract-infer-BwIDGl7v-CZrdWXAr.mjs.map} +1 -1
- package/dist/{contract-snapshot-resolution-DvZvbNcZ-DHFCWe_k.mjs → contract-snapshot-resolution-De2VvOJn-DqIAV9cr.mjs} +4 -4
- package/dist/{contract-snapshot-resolution-DvZvbNcZ-DHFCWe_k.mjs.map → contract-snapshot-resolution-De2VvOJn-DqIAV9cr.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-CoLTf3Ce-pQDru2w0.mjs → contract-space-aggregate-loader-fa04OIaL-B7LXHle3.mjs} +4 -4
- package/dist/{contract-space-aggregate-loader-CoLTf3Ce-pQDru2w0.mjs.map → contract-space-aggregate-loader-fa04OIaL-B7LXHle3.mjs.map} +1 -1
- package/dist/{db-init-DypCy1e3-CBrYfhHF.mjs → db-init-BolXrKAI-CwG-zEDW.mjs} +6 -6
- package/dist/{db-init-DypCy1e3-CBrYfhHF.mjs.map → db-init-BolXrKAI-CwG-zEDW.mjs.map} +1 -1
- package/dist/{db-init-failure-BmCcHUfc-BqDxyrJE.mjs → db-init-failure-B5cQML4A-5zr-99Ek.mjs} +3 -3
- package/dist/{db-init-failure-BmCcHUfc-BqDxyrJE.mjs.map → db-init-failure-B5cQML4A-5zr-99Ek.mjs.map} +1 -1
- package/dist/{db-schema-DHfPM-5V-JeCjrQ55.mjs → db-schema-DvdLBdEY-Dl6e1d_Y.mjs} +4 -4
- package/dist/{db-schema-DHfPM-5V-JeCjrQ55.mjs.map → db-schema-DvdLBdEY-Dl6e1d_Y.mjs.map} +1 -1
- package/dist/{db-sign-DbAxRcZh-C-nT9kcG.mjs → db-sign-DCp4c8CA-B3tun-kh.mjs} +6 -6
- package/dist/{db-sign-DbAxRcZh-C-nT9kcG.mjs.map → db-sign-DCp4c8CA-B3tun-kh.mjs.map} +1 -1
- package/dist/{db-update-J59yROwL-DCP-_z2W.mjs → db-update-CfayE8WE-CPhQyDaZ.mjs} +6 -6
- package/dist/{db-update-J59yROwL-DCP-_z2W.mjs.map → db-update-CfayE8WE-CPhQyDaZ.mjs.map} +1 -1
- package/dist/{db-verify-0TpDMziA-CJDKutMN.mjs → db-verify-BN7IrOAL-kEzRNJUl.mjs} +7 -65
- package/dist/db-verify-BN7IrOAL-kEzRNJUl.mjs.map +1 -0
- package/dist/{exports-DaxzMVtq.mjs → exports-B3xY_FHp.mjs} +304 -18
- package/dist/exports-B3xY_FHp.mjs.map +1 -0
- package/dist/{format-cbJY7l6v-CjMUDTAs.mjs → format-Ca7dUE6B-BMFqrc6D.mjs} +5 -5
- package/dist/{format-cbJY7l6v-CjMUDTAs.mjs.map → format-Ca7dUE6B-BMFqrc6D.mjs.map} +1 -1
- package/dist/{framework-components-C3K7SBeC-Bj5gx85-.mjs → framework-components-Bau7o1-J-DTp6AUoq.mjs} +3 -3
- package/dist/{framework-components-C3K7SBeC-Bj5gx85-.mjs.map → framework-components-Bau7o1-J-DTp6AUoq.mjs.map} +1 -1
- package/dist/init-DJrxDasP-DoQftaPY.mjs +800 -0
- package/dist/init-DJrxDasP-DoQftaPY.mjs.map +1 -0
- package/dist/{inspect-live-schema-Cf77j9Vd-i5fzNW4T.mjs → inspect-live-schema-BPHXdvqW-DrzL2ZkQ.mjs} +5 -5
- package/dist/{inspect-live-schema-Cf77j9Vd-i5fzNW4T.mjs.map → inspect-live-schema-BPHXdvqW-DrzL2ZkQ.mjs.map} +1 -1
- package/dist/integrity-violation-to-check-failure-BmWMLAae-DqiC4_3p.mjs +115 -0
- package/dist/integrity-violation-to-check-failure-BmWMLAae-DqiC4_3p.mjs.map +1 -0
- package/dist/{invariants-1zL5-9Mc-BCeOLxd_.mjs → invariants-Bltd8DyA-Cg_1Lu0q.mjs} +4 -4
- package/dist/{invariants-1zL5-9Mc-BCeOLxd_.mjs.map → invariants-Bltd8DyA-Cg_1Lu0q.mjs.map} +1 -1
- package/dist/{legend-BQcPKTLe-bn9e4_ZU.mjs → legend-DGv9e2_0-BtetgRz4.mjs} +3 -3
- package/dist/{legend-BQcPKTLe-bn9e4_ZU.mjs.map → legend-DGv9e2_0-BtetgRz4.mjs.map} +1 -1
- package/dist/{migrate-CgSnzAUm-aGstaO5N.mjs → migrate-BXATUjYE-Do79Imf-.mjs} +12 -12
- package/dist/{migrate-CgSnzAUm-aGstaO5N.mjs.map → migrate-BXATUjYE-Do79Imf-.mjs.map} +1 -1
- package/dist/{migrate-failure-CnoUcDms-taa9pfd7.mjs → migrate-failure-D4NvkoCk-0bttmQs2.mjs} +5 -5
- package/dist/{migrate-failure-CnoUcDms-taa9pfd7.mjs.map → migrate-failure-D4NvkoCk-0bttmQs2.mjs.map} +1 -1
- package/dist/{migrate-show-Dlo-YzOk-BXo-DmwI.d.mts → migrate-show-B8UJ9dmb-C4RTPbDk.d.mts} +3 -3
- package/dist/{migrate-show-Dlo-YzOk-BXo-DmwI.d.mts.map → migrate-show-B8UJ9dmb-C4RTPbDk.d.mts.map} +1 -1
- package/dist/{migrate-show-kVUG1xdo-bVTW8tu4.mjs → migrate-show-C_9xR4Qt-B-Y0PGKw.mjs} +6 -6
- package/dist/{migrate-show-kVUG1xdo-bVTW8tu4.mjs.map → migrate-show-C_9xR4Qt-B-Y0PGKw.mjs.map} +1 -1
- package/dist/{migration-check-CBMoMDX2-DhSEOGNg.mjs → migration-check-Bbdw7t-r-C2VTMK61.mjs} +6 -6
- package/dist/{migration-check-CBMoMDX2-DhSEOGNg.mjs.map → migration-check-Bbdw7t-r-C2VTMK61.mjs.map} +1 -1
- package/dist/migration-check-DbHgYK7F-7mtYNVFu.mjs +163 -0
- package/dist/migration-check-DbHgYK7F-7mtYNVFu.mjs.map +1 -0
- package/dist/{migration-command-scaffold-B1BiWKLI-Bskk5UKX.mjs → migration-command-scaffold-SzjR2naH-Yi3-rNfX.mjs} +5 -5
- package/dist/{migration-command-scaffold-B1BiWKLI-Bskk5UKX.mjs.map → migration-command-scaffold-SzjR2naH-Yi3-rNfX.mjs.map} +1 -1
- package/dist/{migration-graph-B30X5398-BH3zB9Mn.mjs → migration-graph-B8WxW_tm-qGNf7jQh.mjs} +3 -3
- package/dist/{migration-graph-B30X5398-BH3zB9Mn.mjs.map → migration-graph-B8WxW_tm-qGNf7jQh.mjs.map} +1 -1
- package/dist/{migration-graph-D2dbLCkl-CCHL0aDA.mjs → migration-graph-BOwu76mF-kOTWFvfV.mjs} +9 -9
- package/dist/{migration-graph-D2dbLCkl-CCHL0aDA.mjs.map → migration-graph-BOwu76mF-kOTWFvfV.mjs.map} +1 -1
- package/dist/{migration-graph-command-render-BeuEyf2B-BNH9oUB_.mjs → migration-graph-command-render-h0VdTkSM-C3rPZEw1.mjs} +17 -3
- package/dist/migration-graph-command-render-h0VdTkSM-C3rPZEw1.mjs.map +1 -0
- package/dist/{migration-graph-sections-0lKA5l1b-CTHQquu7.mjs → migration-graph-sections-BBHPOFN--COKyUKQ4.mjs} +4 -4
- package/dist/{migration-graph-sections-0lKA5l1b-CTHQquu7.mjs.map → migration-graph-sections-BBHPOFN--COKyUKQ4.mjs.map} +1 -1
- package/dist/{migration-list-DCx0KOFp-B3MiGnpa.mjs → migration-list-CKuYcMQq-CubZzE9i.mjs} +7 -7
- package/dist/{migration-list-DCx0KOFp-B3MiGnpa.mjs.map → migration-list-CKuYcMQq-CubZzE9i.mjs.map} +1 -1
- package/dist/{migration-list-BQTfSGbT-DoNqbNsu.mjs → migration-list-CY08bD8I-PoXuv94c.mjs} +3 -3
- package/dist/{migration-list-BQTfSGbT-DoNqbNsu.mjs.map → migration-list-CY08bD8I-PoXuv94c.mjs.map} +1 -1
- package/dist/{migration-log-CJ6_9_uj-uWyc3vlC.mjs → migration-log-CUbGla-9-BLIrWjwN.mjs} +8 -8
- package/dist/{migration-log-CJ6_9_uj-uWyc3vlC.mjs.map → migration-log-CUbGla-9-BLIrWjwN.mjs.map} +1 -1
- package/dist/{migration-log-table-DRiOnvzf-zTQBThqe.mjs → migration-log-table-eES9lPjv-BCFm8qJr.mjs} +3 -3
- package/dist/{migration-log-table-DRiOnvzf-zTQBThqe.mjs.map → migration-log-table-eES9lPjv-BCFm8qJr.mjs.map} +1 -1
- package/dist/{migration-new-CXLgOLXz-2C5s_q7n.mjs → migration-new-ChvZjf5X-BFhEM0BF.mjs} +4 -4
- package/dist/{migration-new-CXLgOLXz-2C5s_q7n.mjs.map → migration-new-ChvZjf5X-BFhEM0BF.mjs.map} +1 -1
- package/dist/{migration-new-Bp5dPKnF-D-Fb6vEp.mjs → migration-new-DXMvJdbB-BkrpDfcr.mjs} +7 -7
- package/dist/{migration-new-Bp5dPKnF-D-Fb6vEp.mjs.map → migration-new-DXMvJdbB-BkrpDfcr.mjs.map} +1 -1
- package/dist/{migration-path-target-BNTSTPAc-D7dQ4K7x.mjs → migration-path-target-JDWqF9kj-B7zoMq6X.mjs} +3 -3
- package/dist/{migration-path-target-BNTSTPAc-D7dQ4K7x.mjs.map → migration-path-target-JDWqF9kj-B7zoMq6X.mjs.map} +1 -1
- package/dist/{migration-plan-CharRhoN-BIB3WL65.d.mts → migration-plan-BV9f5Xjq-DiyscOMc.d.mts} +3 -3
- package/dist/{migration-plan-CharRhoN-BIB3WL65.d.mts.map → migration-plan-BV9f5Xjq-DiyscOMc.d.mts.map} +1 -1
- package/dist/{migration-plan-BxfF1Hdo-BKn0xMlY.mjs → migration-plan-Bz1hOTHF-CFCtPuOv.mjs} +4 -4
- package/dist/{migration-plan-BxfF1Hdo-BKn0xMlY.mjs.map → migration-plan-Bz1hOTHF-CFCtPuOv.mjs.map} +1 -1
- package/dist/{migration-plan-C6KrFHJT-CRkTsE7L.mjs → migration-plan-Cfz0efEL-DMNFq5b2.mjs} +8 -8
- package/dist/{migration-plan-C6KrFHJT-CRkTsE7L.mjs.map → migration-plan-Cfz0efEL-DMNFq5b2.mjs.map} +1 -1
- package/dist/{migration-show-Dhnlx_GU-DhWLLt3Z.mjs → migration-show-BGdR1xnT-Cg3M0FPh.mjs} +8 -8
- package/dist/{migration-show-Dhnlx_GU-DhWLLt3Z.mjs.map → migration-show-BGdR1xnT-Cg3M0FPh.mjs.map} +1 -1
- package/dist/{migration-status-S88DjM8c-DLHnCKfW.mjs → migration-status-BIrAJ5af-D-_mMBUA.mjs} +12 -11
- package/dist/{migration-status-S88DjM8c-DLHnCKfW.mjs.map → migration-status-BIrAJ5af-D-_mMBUA.mjs.map} +1 -1
- package/dist/{output-DWNYqZGc-D2sRgLAm.mjs → output-DP3woAjV-BxCr9nHA.mjs} +21 -11
- package/dist/output-DP3woAjV-BxCr9nHA.mjs.map +1 -0
- package/dist/{project-import-root-nwdoPWap--5vXe4zp.mjs → project-import-root-B4nM-VWX-D__g0hTb.mjs} +3 -3
- package/dist/{project-import-root-nwdoPWap--5vXe4zp.mjs.map → project-import-root-B4nM-VWX-D__g0hTb.mjs.map} +1 -1
- package/dist/{init-Bco4QkKJ-DqUZVrBU.mjs → redact-secrets-Cvia6eTB-BE_Pg3k9.mjs} +741 -1517
- package/dist/redact-secrets-Cvia6eTB-BE_Pg3k9.mjs.map +1 -0
- package/dist/{ref-gZQjdz8I-CW6n9Fc0.mjs → ref-4x6BGSsd-ClaoZIQh.mjs} +4 -4
- package/dist/{ref-gZQjdz8I-CW6n9Fc0.mjs.map → ref-4x6BGSsd-ClaoZIQh.mjs.map} +1 -1
- package/dist/{ref-C8oaaeky-ColNkWpr.mjs → ref-WXLIR9Xm-12IUtoBU.mjs} +5 -5
- package/dist/{ref-C8oaaeky-ColNkWpr.mjs.map → ref-WXLIR9Xm-12IUtoBU.mjs.map} +1 -1
- package/dist/{ref-resolution-DAK4OK3x-B-8ZZc0f.mjs → ref-resolution-BSN1amKm-C6-TTM2N.mjs} +3 -3
- package/dist/{ref-resolution-DAK4OK3x-B-8ZZc0f.mjs.map → ref-resolution-BSN1amKm-C6-TTM2N.mjs.map} +1 -1
- package/dist/{schemas-Bm2e3Hk1-qLKF0B36.d.mts → schemas-BGgjiKV0-Rh8RlMCl.d.mts} +4 -2
- package/dist/schemas-BGgjiKV0-Rh8RlMCl.d.mts.map +1 -0
- package/dist/{schemas-BMkqaqvM-CLaqlat4.mjs → schemas-CYSD83Cu-Bff4PPoi.mjs} +3 -2
- package/dist/schemas-CYSD83Cu-Bff4PPoi.mjs.map +1 -0
- package/dist/{telemetry-DPXFYAsz-CPW2USxs.mjs → telemetry-BD52LETE-Cqeb7ebh.mjs} +3 -3
- package/dist/{telemetry-DPXFYAsz-CPW2USxs.mjs.map → telemetry-BD52LETE-Cqeb7ebh.mjs.map} +1 -1
- package/dist/vite-plugin-contract-emit.mjs +1 -1
- package/package.json +13 -13
- package/dist/cli-errors-BG6bItjG-DwdKIeGz.mjs.map +0 -1
- package/dist/cli-errors-BiKBrOXc-JPxQqO58.d.mts.map +0 -1
- package/dist/client-DU9qtBxW-BjVHiKzE.mjs.map +0 -1
- package/dist/contract-emit-Bzt11N4v-BQ2Pr8KG.mjs +0 -2
- package/dist/db-verify-0TpDMziA-CJDKutMN.mjs.map +0 -1
- package/dist/exports-DaxzMVtq.mjs.map +0 -1
- package/dist/init-Bco4QkKJ-DqUZVrBU.mjs.map +0 -1
- package/dist/migration-check-BVK9vfan-Dfg8lbeB.mjs +0 -272
- package/dist/migration-check-BVK9vfan-Dfg8lbeB.mjs.map +0 -1
- package/dist/migration-graph-command-render-BeuEyf2B-BNH9oUB_.mjs.map +0 -1
- package/dist/output-DWNYqZGc-D2sRgLAm.mjs.map +0 -1
- package/dist/schemas-BMkqaqvM-CLaqlat4.mjs.map +0 -1
- package/dist/schemas-Bm2e3Hk1-qLKF0B36.d.mts.map +0 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,47 +1,58 @@
|
|
|
1
1
|
import { i as loadConfig } from "./exports-BZLNbtkY.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { A as errorRuntime$1, F as errorUnexpected, L as requireLiveDatabase, N as errorTargetMigrationNotSupported, P as errorTargetMismatch, R as runCommandAction, S as errorNoMigrations, _ as errorLegendHumanOnly, a as errorConfigValidation$1, b as errorMigrationPackageNotFound, d as errorDestructiveChanges, f as errorDriverRequired, k as errorRunnerFailed, l as errorContractValidationFailed, m as errorHashMismatch, o as errorConsentPlanMismatch, p as errorFileNotFound, r as chooseAction, s as errorContractArgConflict, t as CliStructuredError$1$1, u as errorDatabaseConnectionRequired, x as errorMigrationPlanningFailed, y as errorMarkerMissing } from "./cli-errors-B4zTLaqQ-CXgjO9t4.mjs";
|
|
3
3
|
import { r as getEmittedArtifactPaths } from "./exports-BCLg4CiV-05kuwcb0.mjs";
|
|
4
|
-
import { n as executeContractEmit } from "./contract-emit-
|
|
4
|
+
import { n as executeContractEmit } from "./contract-emit-DFScp9D6-6H-UJMFv.mjs";
|
|
5
5
|
import { h as renderPreviewStatement, m as previewBlockHeader } from "./terminal-ui-Cqz4LVL7-B0mcVdLy.mjs";
|
|
6
6
|
import { c as spaceMigrationDirectory } from "./verify-contract-spaces-DhJTb0zM-_oh6W6lp.mjs";
|
|
7
|
-
import { S as targetSupportsMigrations, f as maskConnectionUrl, n as closeQuietly, v as sanitizeErrorMessage } from "./command-helpers-
|
|
8
|
-
import { t as createContractEmitCommand } from "./contract-emit-
|
|
9
|
-
import { n as executeFormat, t as createFormatCommand } from "./format-
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
7
|
+
import { S as targetSupportsMigrations, f as maskConnectionUrl, h as readContractEnvelope, n as closeQuietly, v as sanitizeErrorMessage } from "./command-helpers-BCgwcqTe-lNCtppzM.mjs";
|
|
8
|
+
import { t as createContractEmitCommand } from "./contract-emit-DsvAx-dY-DQzoDO34.mjs";
|
|
9
|
+
import { n as executeFormat, t as createFormatCommand } from "./format-Ca7dUE6B-BMFqrc6D.mjs";
|
|
10
|
+
import { c as targetLabel, i as scaffoldSpecifierResolverFor, l as targetPackageName, n as dbFile, o as starterSchema, r as defaultSchemaPath, t as configFile } from "./code-templates-D73q329L-CSx0Jg-6.mjs";
|
|
11
|
+
import { a as loadContractSpaceAggregateForCli, i as loadContractRawSafely, l as refusePackageCorruptionOnAggregate, r as buildReadAggregate, s as refuseContractSpaceIntegrity, u as toDeclaredExtensionsFromRaw } from "./contract-space-aggregate-loader-fa04OIaL-B7LXHle3.mjs";
|
|
12
|
+
import { t as createControlClient } from "./client-C6Qp5ujC-Dxb7r79B.mjs";
|
|
12
13
|
import { a as readContractIR, o as resolveRefAdvancementFields, t as advanceRefSafely } from "./ref-advancement-DaZBzm7m-WoqnFAyr.mjs";
|
|
13
|
-
import { t as mapDbInitFailure } from "./db-init-failure-
|
|
14
|
-
import { t as resolveContractRefToSnapshot } from "./contract-snapshot-resolution-
|
|
15
|
-
import { t as
|
|
16
|
-
import { t as
|
|
17
|
-
import {
|
|
18
|
-
import { t as
|
|
19
|
-
import {
|
|
20
|
-
import { n as
|
|
21
|
-
import {
|
|
22
|
-
import { n as
|
|
23
|
-
import { n as
|
|
24
|
-
import { t as
|
|
25
|
-
import { n as
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
14
|
+
import { t as mapDbInitFailure } from "./db-init-failure-B5cQML4A-5zr-99Ek.mjs";
|
|
15
|
+
import { t as resolveContractRefToSnapshot } from "./contract-snapshot-resolution-De2VvOJn-DqIAV9cr.mjs";
|
|
16
|
+
import { t as combineVerifyResults } from "./combine-verify-results-B87B_pMc-D4l22OqY.mjs";
|
|
17
|
+
import { t as mapCaughtMigrationError } from "./caught-errors-BZo7zcRc-C-pIEasG.mjs";
|
|
18
|
+
import { t as mapContractAtError } from "./contract-at-errors-BSoDI0Hw-BXeUNE3s.mjs";
|
|
19
|
+
import { i as refuseUnknownInvariants, n as refuseMarkerOutsideGraph, r as refuseMissingInvariantPath, t as hasMigrationPath } from "./invariants-Bltd8DyA-Cg_1Lu0q.mjs";
|
|
20
|
+
import { t as executeMigrateShowPlan } from "./migrate-show-C_9xR4Qt-B-Y0PGKw.mjs";
|
|
21
|
+
import { n as resolveMigrationRef, t as resolveContractRef } from "./ref-resolution-BSN1amKm-C6-TTM2N.mjs";
|
|
22
|
+
import { n as migrationSpaceListEntriesFromAggregate, r as runMigrationList, t as listRefsByContractHash } from "./migration-list-CY08bD8I-PoXuv94c.mjs";
|
|
23
|
+
import { C as toneSpans, S as toneDrawing, b as renderMigrationGraphSpaceTrees, d as createToneMigrationListStyler, m as indentMigrationGraphTreeBlock, n as TONE_MIGRATION_GRAPH_PALETTE, v as renderMigrationGraphLegend, x as renderMigrationListWithStyle } from "./migration-graph-command-render-h0VdTkSM-C3rPZEw1.mjs";
|
|
24
|
+
import { n as migrateShowRunListRows, r as renderMigrateShowGraph, t as mapMigrateFailure } from "./migrate-failure-D4NvkoCk-0bttmQs2.mjs";
|
|
25
|
+
import { n as looksLikePath, r as resolveAppTargetPath, t as findPackageByDirPath } from "./migration-path-target-JDWqF9kj-B7zoMq6X.mjs";
|
|
26
|
+
import { i as runMigrationCheck, n as enumerateCheckSpaces, r as loadAggregateIntegrityViolations, t as checkSingleTarget } from "./migration-check-Bbdw7t-r-C2VTMK61.mjs";
|
|
27
|
+
import { t as integrityViolationToCheckFailure } from "./integrity-violation-to-check-failure-BmWMLAae-DqiC4_3p.mjs";
|
|
28
|
+
import { t as buildMigrationSpaceGraphEntries } from "./migration-graph-B8WxW_tm-qGNf7jQh.mjs";
|
|
29
|
+
import { n as renderMigrationGraphDot, r as renderMigrationGraphSections, t as buildMigrationGraphTreeSections } from "./migration-graph-sections-BBHPOFN--COKyUKQ4.mjs";
|
|
30
|
+
import { a as sortLedgerEntries, i as serializeLedgerEntriesForJson, n as formatLedgerAppliedAt, o as styleHashTransition, t as MIGRATION_LOG_EMPTY_MESSAGE } from "./migration-log-table-eES9lPjv-BCFm8qJr.mjs";
|
|
31
|
+
import { t as executeMigrationNewCommand } from "./migration-new-DXMvJdbB-BkrpDfcr.mjs";
|
|
32
|
+
import { t as executeMigrationPlanCommand } from "./migration-plan-Cfz0efEL-DMNFq5b2.mjs";
|
|
33
|
+
import { a as statusForMigrationHash, i as readMigrationRefs, n as deriveStatusEdgeAnnotations, r as originHashForStatus, t as appliedHashesFromLedger } from "./refs-fQLFcMoq-aTBeUU9H.mjs";
|
|
34
|
+
import { n as executeRefListCommand, r as executeRefSetCommand, t as executeRefDeleteCommand } from "./ref-WXLIR9Xm-12IUtoBU.mjs";
|
|
35
|
+
import { A as isRecognisedPnpmResolutionError, B as removeDependency, C as findStaleArtifacts, E as formatRunCommand, F as mergeTsConfig, G as validateSchemaPath, H as resolveAuthoring, I as minimalProjectReadmeMd, L as probeServerVersion, M as mergeGitattributes, N as mergeGitignore, O as formatSkillSourceUrl, P as mergePackageScripts, R as quickReferenceMd, S as errorInitWriteFailed, U as resolveProjectSkillInstallCommands, V as requiredGitattributesLines, W as resolveTarget, _ as errorInitProbeFailed, a as TsConfigParseError, b as errorInitStrictProbeWithoutProbe, d as envFileContent, g as errorInitMissingFlags, h as errorInitInvalidTsconfig, i as REQUIRED_SCRIPTS, j as legacySkillDirs, l as ensureEsmModuleType, m as errorInitInvalidManifest, n as DEFAULT_SKILL_SOURCES, o as buildCatalogWarnings, r as MIN_SERVER_VERSION, s as defaultTsConfig, t as DEFAULT_SKILL_AGENTS, u as envExampleContent, x as errorInitUserAborted, z as redactSecrets } from "./redact-secrets-Cvia6eTB-BE_Pg3k9.mjs";
|
|
36
|
+
import { n as buildNextSteps, t as InitOutputSchema } from "./output-DP3woAjV-BxCr9nHA.mjs";
|
|
28
37
|
import { CliStructuredError } from "@prisma/orm-framework/errors/control";
|
|
29
38
|
import { ifDefined } from "@prisma/orm-framework/utils/defined";
|
|
30
|
-
import { basename, dirname, join, normalize, relative, resolve } from "pathe";
|
|
31
|
-
import { existsSync, unlinkSync, writeFileSync } from "node:fs";
|
|
32
|
-
import { DOCS_BASE, isStructuredError } from "@prisma/orm-framework/utils/structured-error";
|
|
39
|
+
import { basename, dirname, isAbsolute, join, normalize, relative, resolve } from "pathe";
|
|
40
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
41
|
+
import { DOCS_BASE, docsUrlFor, isStructuredError, isStructuredErrorCode } from "@prisma/orm-framework/utils/structured-error";
|
|
33
42
|
import { blindCast, castAs } from "@prisma/orm-framework/utils/casts";
|
|
34
|
-
import { InternalError, assertNever } from "@prisma/orm-framework/utils/internal-error";
|
|
43
|
+
import { InternalError, assertNever, isInternalError } from "@prisma/orm-framework/utils/internal-error";
|
|
35
44
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
36
|
-
import { APP_SPACE_ID, createControlStack } from "@prisma/orm-framework/components/control";
|
|
45
|
+
import { APP_SPACE_ID, VERIFY_CODE_HASH_MISMATCH, VERIFY_CODE_MARKER_MISSING, VERIFY_CODE_TARGET_MISMATCH, createControlStack, issueOutcome } from "@prisma/orm-framework/components/control";
|
|
46
|
+
import { type } from "arktype";
|
|
37
47
|
import { collectConfigIssues } from "@prisma/orm-framework/config/config-validation";
|
|
38
48
|
import { tmpdir } from "node:os";
|
|
49
|
+
import { detect } from "package-manager-detector/detect";
|
|
39
50
|
import { printPsl } from "@prisma/orm-framework/psl-printer";
|
|
40
|
-
import { defineCommand, defineCommandFamily, defineConfigSection, flag, positional } from "@prisma/cli-engine";
|
|
51
|
+
import { defineCommand, defineCommandFamily, defineConfigSection, defineServerCommand, flag, positional } from "@prisma/cli-engine";
|
|
41
52
|
import { CliStructuredError as CliStructuredError$1, notOk, ok } from "@prisma/cli-engine/protocol";
|
|
42
53
|
import { pathToFileURL } from "node:url";
|
|
43
54
|
import { build } from "esbuild";
|
|
44
|
-
//#region ../../../1-framework/3-tooling/cli/dist/load-config-
|
|
55
|
+
//#region ../../../1-framework/3-tooling/cli/dist/load-config-CVli79Ro.mjs
|
|
45
56
|
const MISSING_CONFIG_ACTION = {
|
|
46
57
|
kind: "run-command",
|
|
47
58
|
label: "Create a config file",
|
|
@@ -251,6 +262,11 @@ function normalizeError(error) {
|
|
|
251
262
|
* prisma/prisma's `CliStructuredError` — both classes carry that name — and then settles it
|
|
252
263
|
* through fields the engine's own class has and this one does not. The envelope that results is
|
|
253
264
|
* off-protocol. Catching here means every handler settles through the single conversion instead.
|
|
265
|
+
*
|
|
266
|
+
* An `InternalError` is the one thing this boundary does not convert. Its own contract says never
|
|
267
|
+
* to catch it outside the outermost boundary: it means an invariant broke, which is a bug in
|
|
268
|
+
* Prisma Next rather than something the user did. Re-throwing lets the engine settle it as a bug
|
|
269
|
+
* at exit 1, where converting it would report the same number as a bad connection string.
|
|
254
270
|
*/
|
|
255
271
|
function defineOrmCommand(def) {
|
|
256
272
|
return defineCommand({
|
|
@@ -259,6 +275,7 @@ function defineOrmCommand(def) {
|
|
|
259
275
|
try {
|
|
260
276
|
return await def.handler(args, ctx);
|
|
261
277
|
} catch (error) {
|
|
278
|
+
if (isInternalError(error)) throw error;
|
|
262
279
|
return notOk(normalizeError(error));
|
|
263
280
|
}
|
|
264
281
|
}
|
|
@@ -608,7 +625,7 @@ function perSpaceBlocks(perSpace, mode) {
|
|
|
608
625
|
roots: spaceNodes(perSpace, mode)
|
|
609
626
|
}, ...destructiveWarningBlocks(hasDestructive)];
|
|
610
627
|
}
|
|
611
|
-
function operationBlocks$
|
|
628
|
+
function operationBlocks$2(result) {
|
|
612
629
|
const perSpace = result.perSpace;
|
|
613
630
|
if (perSpace !== void 0 && perSpace.length > 0) return perSpaceBlocks(perSpace, result.mode);
|
|
614
631
|
if (result.plan.operations.length === 0) return [];
|
|
@@ -619,7 +636,7 @@ function operationBlocks$1(result) {
|
|
|
619
636
|
}, ...destructiveWarningBlocks(hasDestructive)];
|
|
620
637
|
}
|
|
621
638
|
/** The statements a database would run, printed verbatim rather than laid out. */
|
|
622
|
-
function previewBlocks$
|
|
639
|
+
function previewBlocks$2(preview) {
|
|
623
640
|
if (preview === void 0) return [];
|
|
624
641
|
const header = {
|
|
625
642
|
kind: "summary",
|
|
@@ -666,7 +683,7 @@ function applySummaryText(result) {
|
|
|
666
683
|
if (executed === 0) return `Database already matches contract${across}`;
|
|
667
684
|
return `Applied ${executed} operation(s)${across}`;
|
|
668
685
|
}
|
|
669
|
-
function planBlocks(result) {
|
|
686
|
+
function planBlocks$1(result) {
|
|
670
687
|
const planned = result.plannedAdvanceRef;
|
|
671
688
|
return [
|
|
672
689
|
{
|
|
@@ -675,7 +692,7 @@ function planBlocks(result) {
|
|
|
675
692
|
text: planSummaryText(result)
|
|
676
693
|
},
|
|
677
694
|
...plannerWarningBlocks(result),
|
|
678
|
-
...operationBlocks$
|
|
695
|
+
...operationBlocks$2(result),
|
|
679
696
|
{
|
|
680
697
|
kind: "fields",
|
|
681
698
|
rows: [{
|
|
@@ -694,7 +711,7 @@ function planBlocks(result) {
|
|
|
694
711
|
tone: "identifier"
|
|
695
712
|
}]
|
|
696
713
|
}],
|
|
697
|
-
...previewBlocks$
|
|
714
|
+
...previewBlocks$2(result.plan.preview),
|
|
698
715
|
{
|
|
699
716
|
kind: "summary",
|
|
700
717
|
status: "info",
|
|
@@ -736,7 +753,7 @@ function applyBlocks(result) {
|
|
|
736
753
|
text: applySummaryText(result)
|
|
737
754
|
},
|
|
738
755
|
...plannerWarningBlocks(result),
|
|
739
|
-
...operationBlocks$
|
|
756
|
+
...operationBlocks$2(result),
|
|
740
757
|
...fallbackMarkerBlocks(result),
|
|
741
758
|
...advanced === null || advanced === void 0 ? [] : [{
|
|
742
759
|
kind: "summary",
|
|
@@ -750,7 +767,7 @@ function applyBlocks(result) {
|
|
|
750
767
|
}
|
|
751
768
|
/** What a `db init` / `db update` result looks like in human mode. */
|
|
752
769
|
function migrationResultBlocks(result) {
|
|
753
|
-
return result.mode === "plan" ? planBlocks(result) : applyBlocks(result);
|
|
770
|
+
return result.mode === "plan" ? planBlocks$1(result) : applyBlocks(result);
|
|
754
771
|
}
|
|
755
772
|
/** Where the user goes next, as typed actions rather than trailing prose. */
|
|
756
773
|
function migrationResultNextActions(result, applyCommand) {
|
|
@@ -769,6 +786,16 @@ function appMigrationsDirFor(config, cwd) {
|
|
|
769
786
|
return spaceMigrationDirectory(migrationsDirFor(config, cwd), APP_SPACE_ID);
|
|
770
787
|
}
|
|
771
788
|
/**
|
|
789
|
+
* The config file an operation should anchor its project paths on. The engine
|
|
790
|
+
* loads the config and hands a handler the value but not the path, and
|
|
791
|
+
* `--config` is an engine flag the handler never sees, so a handler names the
|
|
792
|
+
* invocation directory's file. That equals the loaded file for every
|
|
793
|
+
* invocation whose config sits in the invocation directory.
|
|
794
|
+
*/
|
|
795
|
+
function projectConfigPathFor(cwd) {
|
|
796
|
+
return resolve(cwd, "prisma-next.config.ts");
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
772
799
|
* Where refs live. The framework keeps them under the app subspace rather than
|
|
773
800
|
* at the migrations root.
|
|
774
801
|
*/
|
|
@@ -842,7 +869,7 @@ async function prepareMigrationRun(inputs) {
|
|
|
842
869
|
refsDir: appRefsDirFor(config, cwd)
|
|
843
870
|
});
|
|
844
871
|
}
|
|
845
|
-
function initPresentations(inputs) {
|
|
872
|
+
function initPresentations$1(inputs) {
|
|
846
873
|
const { document, database, dryRun } = inputs;
|
|
847
874
|
return {
|
|
848
875
|
human: () => [{
|
|
@@ -974,7 +1001,7 @@ function createDbInitCommand(createClient) {
|
|
|
974
1001
|
severity: "verbose",
|
|
975
1002
|
text: `Total time: ${document.timings.total}ms`
|
|
976
1003
|
});
|
|
977
|
-
return ok(ctx.present({ data: document }, initPresentations({
|
|
1004
|
+
return ok(ctx.present({ data: document }, initPresentations$1({
|
|
978
1005
|
document,
|
|
979
1006
|
contractPath: prepared.value.contractDisplayPath,
|
|
980
1007
|
database: prepared.value.database,
|
|
@@ -1090,7 +1117,7 @@ function treeNode(node) {
|
|
|
1090
1117
|
...ifDefined("children", node.children === void 0 || node.children.length === 0 ? void 0 : node.children.map(treeNode))
|
|
1091
1118
|
};
|
|
1092
1119
|
}
|
|
1093
|
-
function schemaPresentations(inputs) {
|
|
1120
|
+
function schemaPresentations$1(inputs) {
|
|
1094
1121
|
const { document, schemaView, database } = inputs;
|
|
1095
1122
|
return {
|
|
1096
1123
|
human: () => [...database === void 0 ? [] : [{
|
|
@@ -1167,7 +1194,7 @@ function createDbSchemaCommand(createClient) {
|
|
|
1167
1194
|
meta: { ...ifDefined("dbUrl", database) },
|
|
1168
1195
|
timings: { total: Date.now() - startedAt }
|
|
1169
1196
|
};
|
|
1170
|
-
return ok(ctx.present({ data: document }, schemaPresentations({
|
|
1197
|
+
return ok(ctx.present({ data: document }, schemaPresentations$1({
|
|
1171
1198
|
document,
|
|
1172
1199
|
schemaView,
|
|
1173
1200
|
database
|
|
@@ -1177,6 +1204,372 @@ function createDbSchemaCommand(createClient) {
|
|
|
1177
1204
|
}
|
|
1178
1205
|
const dbSchemaCommand = createDbSchemaCommand(createControlClient);
|
|
1179
1206
|
/**
|
|
1207
|
+
* Reads the emitted contract and hydrates it through the family's
|
|
1208
|
+
* `deserializeContract` seam, which is where every other on-disk contract read
|
|
1209
|
+
* in this CLI crosses into family types.
|
|
1210
|
+
*/
|
|
1211
|
+
async function readEmittedContract(inputs) {
|
|
1212
|
+
const path = contractPathFor(inputs.config, inputs.cwd);
|
|
1213
|
+
if (path === void 0) return notOk(normalizeError(errorConfigValidation$1("contract.output", {
|
|
1214
|
+
why: `${inputs.commandName} reads the emitted contract from config.contract.output; the config has no value to read.`,
|
|
1215
|
+
section: "contract"
|
|
1216
|
+
})));
|
|
1217
|
+
const relativePath = displayPath(path, inputs.cwd);
|
|
1218
|
+
let content;
|
|
1219
|
+
try {
|
|
1220
|
+
content = await readFile(path, "utf-8");
|
|
1221
|
+
} catch (error) {
|
|
1222
|
+
return notOk(normalizeError(Reflect.get(Object(error), "code") === "ENOENT" ? errorFileNotFound(path, {
|
|
1223
|
+
why: `Contract file not found at ${path}`,
|
|
1224
|
+
fix: `Run \`prisma-next contract emit\` to generate ${relativePath}, or update \`contract.output\` in prisma-next.config.ts`
|
|
1225
|
+
}) : errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}` })));
|
|
1226
|
+
}
|
|
1227
|
+
const familyInstance = inputs.config.family.create(createControlStack(inputs.config));
|
|
1228
|
+
try {
|
|
1229
|
+
return ok({
|
|
1230
|
+
contract: familyInstance.deserializeContract(castAs(JSON.parse(content))),
|
|
1231
|
+
path,
|
|
1232
|
+
displayPath: relativePath
|
|
1233
|
+
});
|
|
1234
|
+
} catch (error) {
|
|
1235
|
+
return notOk(normalizeError(errorContractValidationFailed(`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`, { where: { path } })));
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* The connection and driver both verification commands need. Returns the
|
|
1240
|
+
* resolved connection or the precondition failure that stopped the run.
|
|
1241
|
+
*/
|
|
1242
|
+
function requireVerifyConnection(inputs) {
|
|
1243
|
+
const dbConnection = inputs.db ?? inputs.config.db?.connection;
|
|
1244
|
+
if (typeof dbConnection !== "string" || dbConnection.length === 0) return notOk(normalizeError(errorDatabaseConnectionRequired({
|
|
1245
|
+
why: `Database connection is required for ${inputs.invocation} (set db.connection in prisma-next.config.ts, or pass --db <url>)`,
|
|
1246
|
+
missingFlags: ["--db"],
|
|
1247
|
+
retryCommand: `prisma-next ${inputs.invocation} --db <url>`
|
|
1248
|
+
})));
|
|
1249
|
+
if (inputs.config.driver === void 0) return notOk(normalizeError(errorDriverRequired({ why: `Config.driver is required for ${inputs.invocation}` })));
|
|
1250
|
+
return ok(dbConnection);
|
|
1251
|
+
}
|
|
1252
|
+
/**
|
|
1253
|
+
* A failure the verification could not recover from — a dropped connection, a
|
|
1254
|
+
* driver throw — as a settlement the user can act on. Connection strings are
|
|
1255
|
+
* stripped from the prose whichever path the value took. A driver error
|
|
1256
|
+
* carrying `ECONNREFUSED`, `ENOTFOUND` or a SQLSTATE has a `code` and takes
|
|
1257
|
+
* the first path, and its message is exactly the one likely to quote the URL.
|
|
1258
|
+
*/
|
|
1259
|
+
function verificationThrow(inputs) {
|
|
1260
|
+
const { error } = inputs;
|
|
1261
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1262
|
+
return withoutConnectionString(typeof error === "object" && error !== null && "code" in error ? normalizeError(error) : normalizeError(errorUnexpected(message, { why: `Unexpected error during ${inputs.invocation}: ${message}` })), inputs.connection);
|
|
1263
|
+
}
|
|
1264
|
+
/**
|
|
1265
|
+
* The same envelope with the connection string stripped from every field the
|
|
1266
|
+
* settlement serializes: the prose, each next action's strings, and every
|
|
1267
|
+
* string reachable through `meta`. A driver error quotes the URL wherever it
|
|
1268
|
+
* pleases, so nothing user-facing passes through unstripped.
|
|
1269
|
+
*/
|
|
1270
|
+
function withoutConnectionString(error, connection) {
|
|
1271
|
+
const clean = (text) => sanitizeErrorMessage(text, connection);
|
|
1272
|
+
return new CliStructuredError$1(error.code, clean(error.message), {
|
|
1273
|
+
severity: error.severity,
|
|
1274
|
+
nextActions: error.nextActions.map((action) => cleanNextAction(action, clean)),
|
|
1275
|
+
...ifDefined("why", error.why === void 0 ? void 0 : clean(error.why)),
|
|
1276
|
+
...ifDefined("where", error.where),
|
|
1277
|
+
...ifDefined("meta", error.meta === void 0 ? void 0 : cleanMetaRecord(error.meta, clean)),
|
|
1278
|
+
...ifDefined("docsUrl", error.docsUrl),
|
|
1279
|
+
cause: error.cause
|
|
1280
|
+
});
|
|
1281
|
+
}
|
|
1282
|
+
function cleanNextAction(action, clean) {
|
|
1283
|
+
return {
|
|
1284
|
+
kind: action.kind,
|
|
1285
|
+
label: clean(action.label),
|
|
1286
|
+
...ifDefined("command", action.command === void 0 ? void 0 : clean(action.command)),
|
|
1287
|
+
...ifDefined("commands", action.commands?.map(clean)),
|
|
1288
|
+
...ifDefined("url", action.url === void 0 ? void 0 : clean(action.url)),
|
|
1289
|
+
...ifDefined("reason", action.reason === void 0 ? void 0 : clean(action.reason))
|
|
1290
|
+
};
|
|
1291
|
+
}
|
|
1292
|
+
function cleanMetaValue(value, clean) {
|
|
1293
|
+
if (typeof value === "string") return clean(value);
|
|
1294
|
+
if (Array.isArray(value)) return value.map((entry) => cleanMetaValue(entry, clean));
|
|
1295
|
+
if (typeof value === "object" && value !== null) return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, cleanMetaValue(entry, clean)]));
|
|
1296
|
+
return value;
|
|
1297
|
+
}
|
|
1298
|
+
function cleanMetaRecord(meta, clean) {
|
|
1299
|
+
return Object.fromEntries(Object.entries(meta).map(([key, value]) => [key, cleanMetaValue(value, clean)]));
|
|
1300
|
+
}
|
|
1301
|
+
const OUTCOME_LABEL = {
|
|
1302
|
+
"not-found": "missing",
|
|
1303
|
+
"not-expected": "extra",
|
|
1304
|
+
"not-equal": "mismatch"
|
|
1305
|
+
};
|
|
1306
|
+
/** What a diff issue says, in the words the commander shell used. */
|
|
1307
|
+
function issueLabel(issue) {
|
|
1308
|
+
return `${OUTCOME_LABEL[issueOutcome(issue)]}: ${issue.path.join("/")}`;
|
|
1309
|
+
}
|
|
1310
|
+
function issueNodes(issues, status) {
|
|
1311
|
+
return issues.map((issue) => ({
|
|
1312
|
+
label: issueLabel(issue),
|
|
1313
|
+
status
|
|
1314
|
+
}));
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* The drift, laid out as a tree the engine draws: one root per finding family,
|
|
1318
|
+
* one child per element. The engine owns the connectors and the status glyphs,
|
|
1319
|
+
* so nothing here carries an escape sequence.
|
|
1320
|
+
*/
|
|
1321
|
+
function schemaFindingBlocks(inputs) {
|
|
1322
|
+
const roots = [];
|
|
1323
|
+
const issues = inputs.result.schema.issues;
|
|
1324
|
+
if (issues.length > 0) roots.push({
|
|
1325
|
+
label: "Schema issues",
|
|
1326
|
+
status: "error",
|
|
1327
|
+
children: issueNodes(issues, "error")
|
|
1328
|
+
});
|
|
1329
|
+
const warnings = inputs.result.schema.warnings?.issues ?? [];
|
|
1330
|
+
if (warnings.length > 0) roots.push({
|
|
1331
|
+
label: "Schema warnings",
|
|
1332
|
+
status: "warn",
|
|
1333
|
+
children: issueNodes(warnings, "warn")
|
|
1334
|
+
});
|
|
1335
|
+
if (inputs.unclaimed.length > 0) {
|
|
1336
|
+
const status = inputs.strict ? "error" : "warn";
|
|
1337
|
+
roots.push({
|
|
1338
|
+
label: "Unclaimed elements (declared by no contract)",
|
|
1339
|
+
status,
|
|
1340
|
+
children: inputs.unclaimed.map((name) => ({
|
|
1341
|
+
label: name,
|
|
1342
|
+
status
|
|
1343
|
+
}))
|
|
1344
|
+
});
|
|
1345
|
+
}
|
|
1346
|
+
return roots.length === 0 ? [] : [{
|
|
1347
|
+
kind: "tree",
|
|
1348
|
+
roots
|
|
1349
|
+
}];
|
|
1350
|
+
}
|
|
1351
|
+
/**
|
|
1352
|
+
* A failed schema-verification verdict as one envelope diagnostic. `error` is
|
|
1353
|
+
* the honest severity: the database does not satisfy the contract. It is legal
|
|
1354
|
+
* because both commands settle at exit 4 — the engine refuses a
|
|
1355
|
+
* severity-`error` diagnostic only on a run that exits 0.
|
|
1356
|
+
*/
|
|
1357
|
+
function schemaVerdictDiagnostic(inputs) {
|
|
1358
|
+
const code = inputs.result.code;
|
|
1359
|
+
const dotted = code !== void 0 && isStructuredErrorCode(code);
|
|
1360
|
+
const issues = inputs.result.schema.issues.map(issueLabel);
|
|
1361
|
+
return {
|
|
1362
|
+
code: dotted ? code : "CONTRACT.VERIFY_FAILED",
|
|
1363
|
+
severity: "error",
|
|
1364
|
+
summary: inputs.result.summary,
|
|
1365
|
+
...issues.length === 0 ? {} : { why: `The live schema differs: ${issues.join("; ")}.` },
|
|
1366
|
+
nextActions: inputs.nextActions,
|
|
1367
|
+
meta: {
|
|
1368
|
+
...inputs.space === void 0 ? {} : { space: inputs.space },
|
|
1369
|
+
issues,
|
|
1370
|
+
...dotted || code === void 0 ? {} : { code }
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
/**
|
|
1375
|
+
* Verification ran and refused the signature. The command completed — it
|
|
1376
|
+
* answered the question "may this database be signed?" — so the refusal is a
|
|
1377
|
+
* diagnostic on a completed envelope rather than an error.
|
|
1378
|
+
*/
|
|
1379
|
+
const FINDINGS_EXIT_CODE$2 = 4;
|
|
1380
|
+
/** The config file this bin reads; the handler is not told which one was loaded. */
|
|
1381
|
+
const CONFIG_DISPLAY_PATH = "prisma-next.config.ts";
|
|
1382
|
+
function headerBlock$1(inputs) {
|
|
1383
|
+
return {
|
|
1384
|
+
kind: "fields",
|
|
1385
|
+
rail: true,
|
|
1386
|
+
rows: [{
|
|
1387
|
+
label: "contract",
|
|
1388
|
+
value: inputs.contract
|
|
1389
|
+
}, {
|
|
1390
|
+
label: "database",
|
|
1391
|
+
value: inputs.database
|
|
1392
|
+
}]
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
function signPresentations(inputs) {
|
|
1396
|
+
const marker = inputs.document.marker;
|
|
1397
|
+
return {
|
|
1398
|
+
human: () => [
|
|
1399
|
+
inputs.header,
|
|
1400
|
+
{
|
|
1401
|
+
kind: "summary",
|
|
1402
|
+
status: "ok",
|
|
1403
|
+
text: "Database signed"
|
|
1404
|
+
},
|
|
1405
|
+
{
|
|
1406
|
+
kind: "fields",
|
|
1407
|
+
rows: [{
|
|
1408
|
+
label: "from",
|
|
1409
|
+
value: marker.previous?.storageHash === void 0 ? [{
|
|
1410
|
+
text: "none",
|
|
1411
|
+
tone: "muted"
|
|
1412
|
+
}] : [{
|
|
1413
|
+
text: marker.previous.storageHash,
|
|
1414
|
+
tone: "identifier"
|
|
1415
|
+
}]
|
|
1416
|
+
}, {
|
|
1417
|
+
label: "to",
|
|
1418
|
+
value: [{
|
|
1419
|
+
text: inputs.document.contract.storageHash,
|
|
1420
|
+
tone: "identifier"
|
|
1421
|
+
}]
|
|
1422
|
+
}]
|
|
1423
|
+
}
|
|
1424
|
+
],
|
|
1425
|
+
json: () => inputs.document
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
function refusedPresentations(inputs) {
|
|
1429
|
+
return {
|
|
1430
|
+
human: () => [
|
|
1431
|
+
inputs.header,
|
|
1432
|
+
...schemaFindingBlocks({
|
|
1433
|
+
result: inputs.document,
|
|
1434
|
+
unclaimed: [],
|
|
1435
|
+
strict: false
|
|
1436
|
+
}),
|
|
1437
|
+
{
|
|
1438
|
+
kind: "summary",
|
|
1439
|
+
status: "error",
|
|
1440
|
+
text: inputs.document.summary
|
|
1441
|
+
}
|
|
1442
|
+
],
|
|
1443
|
+
json: () => inputs.document
|
|
1444
|
+
};
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Builds the command with its control-client factory injected, so tests mount
|
|
1448
|
+
* the same tree over a fake client instead of mocking the client module.
|
|
1449
|
+
*/
|
|
1450
|
+
function createDbSignCommand(createClient = createControlClient) {
|
|
1451
|
+
return defineOrmCommand({
|
|
1452
|
+
help: {
|
|
1453
|
+
summary: "Sign the database with your contract so you can safely run queries",
|
|
1454
|
+
description: "Verifies that your database schema satisfies the emitted contract, and if\nso, writes or updates the database signature. Idempotent and safe to run\nin CI or a deployment pipeline. The signature records that this database\ninstance is aligned with a specific contract version.\nExit codes: 0 = signed, 2 = the command could not run (unresolvable\ncontract reference, no emitted contract, unreachable database),\n4 = schema verification failed and no signature was written.",
|
|
1455
|
+
examples: [
|
|
1456
|
+
"db sign",
|
|
1457
|
+
"db sign --db $DATABASE_URL",
|
|
1458
|
+
"db sign production --db $DATABASE_URL",
|
|
1459
|
+
"db sign --contract production --db $DATABASE_URL"
|
|
1460
|
+
]
|
|
1461
|
+
},
|
|
1462
|
+
args: {
|
|
1463
|
+
positionals: { contract: positional.optionalString({
|
|
1464
|
+
brief: "Contract reference (hash, prefix, ref name, or migration dir name)",
|
|
1465
|
+
placeholder: "contract"
|
|
1466
|
+
}) },
|
|
1467
|
+
flags: {
|
|
1468
|
+
db: dbFlag,
|
|
1469
|
+
contract: flag.string({
|
|
1470
|
+
brief: "Contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)",
|
|
1471
|
+
placeholder: "contract"
|
|
1472
|
+
})
|
|
1473
|
+
}
|
|
1474
|
+
},
|
|
1475
|
+
needs: { config: ormConfigSection },
|
|
1476
|
+
exitCodes: { 4: "schema verification failed; no signature was written" },
|
|
1477
|
+
handler: async (args, ctx) => {
|
|
1478
|
+
const positionalContract = args.positionals.contract;
|
|
1479
|
+
const flagContract = args.flags.contract;
|
|
1480
|
+
if (positionalContract !== void 0 && flagContract !== void 0) return notOk(normalizeError(errorContractArgConflict({
|
|
1481
|
+
positional: positionalContract,
|
|
1482
|
+
flag: flagContract
|
|
1483
|
+
})));
|
|
1484
|
+
const contractRef = positionalContract ?? flagContract;
|
|
1485
|
+
const emitted = await readEmittedContract({
|
|
1486
|
+
config: ctx.config,
|
|
1487
|
+
cwd: ctx.cwd,
|
|
1488
|
+
commandName: "db sign"
|
|
1489
|
+
});
|
|
1490
|
+
if (!emitted.ok) return notOk(emitted.failure);
|
|
1491
|
+
let contractInput = emitted.value.contract;
|
|
1492
|
+
if (contractRef !== void 0) {
|
|
1493
|
+
const resolvedRef = await resolveContractRefToSnapshot({
|
|
1494
|
+
config: ctx.config,
|
|
1495
|
+
migrationsDir: migrationsDirFor(ctx.config, ctx.cwd),
|
|
1496
|
+
refInput: contractRef,
|
|
1497
|
+
contractPathAbsolute: emitted.value.path,
|
|
1498
|
+
fallbackToEmitted: true
|
|
1499
|
+
});
|
|
1500
|
+
if (!resolvedRef.ok) return notOk(normalizeError(resolvedRef.failure));
|
|
1501
|
+
contractInput = resolvedRef.value.contractJson;
|
|
1502
|
+
}
|
|
1503
|
+
const connection = requireVerifyConnection({
|
|
1504
|
+
config: ctx.config,
|
|
1505
|
+
db: args.flags.db,
|
|
1506
|
+
invocation: "db sign"
|
|
1507
|
+
});
|
|
1508
|
+
if (!connection.ok) return notOk(connection.failure);
|
|
1509
|
+
const dbConnection = connection.value;
|
|
1510
|
+
const header = headerBlock$1({
|
|
1511
|
+
contract: contractRef ?? emitted.value.displayPath,
|
|
1512
|
+
database: maskConnectionUrl(dbConnection)
|
|
1513
|
+
});
|
|
1514
|
+
const client = createClient({
|
|
1515
|
+
family: ctx.config.family,
|
|
1516
|
+
target: ctx.config.target,
|
|
1517
|
+
adapter: ctx.config.adapter,
|
|
1518
|
+
...ifDefined("driver", ctx.config.driver),
|
|
1519
|
+
extensions: ctx.config.extensions ?? []
|
|
1520
|
+
});
|
|
1521
|
+
const onProgress = controlProgressReporter(ctx.report);
|
|
1522
|
+
try {
|
|
1523
|
+
const verified = await client.schemaVerify({
|
|
1524
|
+
contract: contractInput,
|
|
1525
|
+
strict: false,
|
|
1526
|
+
connection: dbConnection,
|
|
1527
|
+
onProgress
|
|
1528
|
+
});
|
|
1529
|
+
if (!verified.ok) {
|
|
1530
|
+
const document = verified;
|
|
1531
|
+
return ok(ctx.present({
|
|
1532
|
+
data: document,
|
|
1533
|
+
exitCode: FINDINGS_EXIT_CODE$2,
|
|
1534
|
+
diagnostics: [schemaVerdictDiagnostic({
|
|
1535
|
+
result: verified,
|
|
1536
|
+
space: void 0,
|
|
1537
|
+
nextActions: [runCommandAction("Bring the database up to the contract, then sign again", "prisma-next db update")]
|
|
1538
|
+
})]
|
|
1539
|
+
}, refusedPresentations({
|
|
1540
|
+
document,
|
|
1541
|
+
header
|
|
1542
|
+
})));
|
|
1543
|
+
}
|
|
1544
|
+
const signed = await client.sign({
|
|
1545
|
+
contract: contractInput,
|
|
1546
|
+
contractPath: displayPath(emitted.value.path, ctx.cwd),
|
|
1547
|
+
configPath: CONFIG_DISPLAY_PATH,
|
|
1548
|
+
onProgress
|
|
1549
|
+
});
|
|
1550
|
+
if (!signed.ok) throw new InternalError(`The family returned a sign result that did not sign: ${signed.summary}`);
|
|
1551
|
+
return ok(ctx.present({
|
|
1552
|
+
data: signed,
|
|
1553
|
+
exitCode: 0
|
|
1554
|
+
}, signPresentations({
|
|
1555
|
+
document: signed,
|
|
1556
|
+
header
|
|
1557
|
+
})));
|
|
1558
|
+
} catch (error) {
|
|
1559
|
+
if (isInternalError(error)) throw error;
|
|
1560
|
+
return notOk(verificationThrow({
|
|
1561
|
+
error,
|
|
1562
|
+
invocation: "db sign",
|
|
1563
|
+
connection: dbConnection
|
|
1564
|
+
}));
|
|
1565
|
+
} finally {
|
|
1566
|
+
await closeQuietly(client);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
});
|
|
1570
|
+
}
|
|
1571
|
+
const dbSignCommand = createDbSignCommand();
|
|
1572
|
+
/**
|
|
1180
1573
|
* A `db update` failure as the CLI's structured error.
|
|
1181
1574
|
*
|
|
1182
1575
|
* `DESTRUCTIVE_CHANGES` is handled by the command, which asks for consent and
|
|
@@ -1440,85 +1833,1503 @@ function createDbUpdateCommand(createClient) {
|
|
|
1440
1833
|
});
|
|
1441
1834
|
}
|
|
1442
1835
|
const dbUpdateCommand = createDbUpdateCommand(createControlClient);
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1836
|
+
/**
|
|
1837
|
+
* Verification ran and the database does not match the contract. The run did
|
|
1838
|
+
* the job it was asked to do, so this is a completed settlement carrying
|
|
1839
|
+
* diagnostics rather than an error.
|
|
1840
|
+
*/
|
|
1841
|
+
const FINDINGS_EXIT_CODE$1 = 4;
|
|
1842
|
+
const PUSH_THE_CONTRACT = runCommandAction("Push the contract to the database", "prisma-next db update");
|
|
1843
|
+
const RECONCILE_BY_HAND = {
|
|
1844
|
+
kind: "user-choice",
|
|
1845
|
+
label: "Or reconcile the differences by hand and verify again"
|
|
1846
|
+
};
|
|
1847
|
+
function errorInvalidVerifyMode(options) {
|
|
1848
|
+
return new CliStructuredError$1("CLI.INVALID_VERIFY_MODE", "Invalid verify mode", {
|
|
1849
|
+
why: options.why,
|
|
1850
|
+
nextActions: [{
|
|
1851
|
+
kind: "user-choice",
|
|
1852
|
+
label: options.choose
|
|
1458
1853
|
}],
|
|
1459
|
-
|
|
1460
|
-
};
|
|
1461
|
-
}
|
|
1462
|
-
function createFormatCommand$1(execute = executeFormat) {
|
|
1463
|
-
return defineOrmCommand({
|
|
1464
|
-
help: {
|
|
1465
|
-
summary: "Format your PSL contract source",
|
|
1466
|
-
description: "Formats the Prisma schema (PSL) contract source declared in your config\n(contract.source.inputs[0]) in place. Only runs when contract.source.format\nis 'psl'; a TypeScript or unset source is left untouched. Indent and newline\nare read from the optional formatter config section, defaulting to two\nspaces and the system newline.",
|
|
1467
|
-
examples: ["format", "format --json"]
|
|
1468
|
-
},
|
|
1469
|
-
needs: { config: ormConfigSection },
|
|
1470
|
-
handler: async (_args, ctx) => {
|
|
1471
|
-
const result = await execute({
|
|
1472
|
-
config: ctx.config,
|
|
1473
|
-
cwd: ctx.cwd
|
|
1474
|
-
});
|
|
1475
|
-
if (!result.ok) return notOk(normalizeError(result.failure));
|
|
1476
|
-
return ok(ctx.present({ data: result.value }, formatPresentations(result.value, ctx.cwd)));
|
|
1477
|
-
}
|
|
1854
|
+
docsUrl: "https://pris.ly/db-verify"
|
|
1478
1855
|
});
|
|
1479
1856
|
}
|
|
1480
|
-
|
|
1481
|
-
|
|
1857
|
+
function resolveMode(flags) {
|
|
1858
|
+
if (flags.markerOnly && flags.schemaOnly) return notOk(errorInvalidVerifyMode({
|
|
1859
|
+
why: "`--marker-only` and `--schema-only` cannot be used together",
|
|
1860
|
+
choose: "Choose one mode: omit both to check the marker and schema, use `--marker-only` to check only the marker, or use `--schema-only` to check only the live schema."
|
|
1861
|
+
}));
|
|
1862
|
+
if (flags.markerOnly && flags.strict) return notOk(errorInvalidVerifyMode({
|
|
1863
|
+
why: "`--strict` requires schema verification, but `--marker-only` skips it",
|
|
1864
|
+
choose: "Remove `--strict`, or use `db verify` / `db verify --schema-only` when you want to check the live schema in strict mode."
|
|
1865
|
+
}));
|
|
1866
|
+
if (flags.schemaOnly) return ok("schema-only");
|
|
1867
|
+
return ok(flags.markerOnly ? "marker-only" : "full");
|
|
1868
|
+
}
|
|
1869
|
+
function modeLabel(mode, strict) {
|
|
1870
|
+
if (mode === "marker-only") return "marker only";
|
|
1871
|
+
const strictness = strict ? "strict" : "tolerant";
|
|
1872
|
+
return mode === "schema-only" ? `schema only (${strictness})` : `full (marker + schema, ${strictness})`;
|
|
1873
|
+
}
|
|
1874
|
+
function invocation(mode, strict) {
|
|
1875
|
+
return [
|
|
1876
|
+
"db verify",
|
|
1877
|
+
...mode === "marker-only" ? ["--marker-only"] : [],
|
|
1878
|
+
...mode === "schema-only" ? ["--schema-only"] : [],
|
|
1879
|
+
...strict ? ["--strict"] : []
|
|
1880
|
+
].join(" ");
|
|
1881
|
+
}
|
|
1882
|
+
/** The marker verdict as a finding, keeping the code the commander raised. */
|
|
1883
|
+
function markerFindingDiagnostic(result) {
|
|
1884
|
+
const { ok: _ok, ...diagnostic } = normalizeError(markerFindingError(result)).toEnvelope();
|
|
1885
|
+
return diagnostic;
|
|
1886
|
+
}
|
|
1482
1887
|
/**
|
|
1483
|
-
* The
|
|
1484
|
-
*
|
|
1485
|
-
*
|
|
1888
|
+
* The aggregate verifier's per-space marker drift as a finding on a completed
|
|
1889
|
+
* run, keeping the `MIGRATION.CONTRACT_SPACE_VIOLATION` envelope it was
|
|
1890
|
+
* raised with.
|
|
1486
1891
|
*/
|
|
1487
|
-
function
|
|
1488
|
-
const {
|
|
1489
|
-
|
|
1892
|
+
function markerDriftDiagnostic(drift) {
|
|
1893
|
+
const { ok: _ok, ...diagnostic } = normalizeError(drift).toEnvelope();
|
|
1894
|
+
return diagnostic;
|
|
1895
|
+
}
|
|
1896
|
+
function markerFindingError(result) {
|
|
1897
|
+
if (result.code === VERIFY_CODE_MARKER_MISSING) return errorMarkerMissing();
|
|
1898
|
+
if (result.code === VERIFY_CODE_HASH_MISMATCH) {
|
|
1899
|
+
if (!(result.marker?.storageHash === result.contract.storageHash)) return errorHashMismatch({
|
|
1900
|
+
why: "Contract storageHash does not match database marker",
|
|
1901
|
+
expected: result.contract.storageHash,
|
|
1902
|
+
...ifDefined("actual", result.marker?.storageHash)
|
|
1903
|
+
});
|
|
1904
|
+
return errorHashMismatch({
|
|
1905
|
+
why: !result.contract.profileHash || result.marker?.profileHash === result.contract.profileHash ? "Contract hash does not match database marker" : "Contract profileHash does not match database marker",
|
|
1906
|
+
...ifDefined("expected", result.contract.profileHash),
|
|
1907
|
+
...ifDefined("actual", result.marker?.profileHash)
|
|
1908
|
+
});
|
|
1909
|
+
}
|
|
1910
|
+
if (result.code === VERIFY_CODE_TARGET_MISMATCH) return errorTargetMismatch(result.target.expected, result.target.actual ?? "unknown");
|
|
1911
|
+
return errorRuntime$1("CONTRACT.VERIFY_FAILED", result.summary, {
|
|
1912
|
+
why: "Verification failed",
|
|
1913
|
+
fix: "Check contract and database state"
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
function verifyDocument(inputs) {
|
|
1917
|
+
return {
|
|
1918
|
+
ok: inputs.ok,
|
|
1919
|
+
summary: inputs.summary,
|
|
1920
|
+
mode: inputs.mode,
|
|
1921
|
+
contract: inputs.verified.contract,
|
|
1922
|
+
...ifDefined("marker", inputs.verified.marker),
|
|
1923
|
+
target: inputs.verified.target,
|
|
1924
|
+
...ifDefined("missingCodecs", inputs.verified.missingCodecs),
|
|
1925
|
+
...ifDefined("codecCoverageSkipped", inputs.verified.codecCoverageSkipped),
|
|
1926
|
+
...ifDefined("schema", inputs.schema),
|
|
1927
|
+
...ifDefined("unclaimed", inputs.unclaimed),
|
|
1928
|
+
...ifDefined("warning", inputs.warning),
|
|
1929
|
+
meta: {
|
|
1930
|
+
...inputs.verified.meta ?? {},
|
|
1931
|
+
schemaVerification: inputs.schemaVerification
|
|
1932
|
+
},
|
|
1933
|
+
timings: { total: inputs.elapsed }
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
function schemaSummary(combined) {
|
|
1937
|
+
return {
|
|
1938
|
+
summary: combined.result.summary,
|
|
1939
|
+
strict: combined.result.meta?.strict ?? false,
|
|
1940
|
+
warnings: (combined.result.schema.warnings?.issues ?? []).map((issue) => issue.path.join("/"))
|
|
1941
|
+
};
|
|
1942
|
+
}
|
|
1943
|
+
function headerBlock(inputs) {
|
|
1944
|
+
return {
|
|
1945
|
+
kind: "fields",
|
|
1946
|
+
rail: true,
|
|
1947
|
+
rows: [
|
|
1948
|
+
{
|
|
1949
|
+
label: "contract",
|
|
1950
|
+
value: inputs.contractPath
|
|
1951
|
+
},
|
|
1952
|
+
{
|
|
1953
|
+
label: "mode",
|
|
1954
|
+
value: modeLabel(inputs.mode, inputs.strict)
|
|
1955
|
+
},
|
|
1956
|
+
{
|
|
1957
|
+
label: "database",
|
|
1958
|
+
value: inputs.database
|
|
1959
|
+
}
|
|
1960
|
+
]
|
|
1961
|
+
};
|
|
1962
|
+
}
|
|
1963
|
+
function verifyPresentations(inputs) {
|
|
1964
|
+
const document = inputs.document;
|
|
1965
|
+
const warnings = document.schema?.warnings ?? [];
|
|
1966
|
+
const unclaimed = document.unclaimed ?? [];
|
|
1490
1967
|
return {
|
|
1491
1968
|
human: () => [
|
|
1969
|
+
inputs.header,
|
|
1492
1970
|
{
|
|
1493
|
-
kind: "
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
{
|
|
1497
|
-
label: "migrations",
|
|
1498
|
-
value: inputs.migrationsDir
|
|
1499
|
-
},
|
|
1500
|
-
...inputs.database === void 0 ? [] : [{
|
|
1501
|
-
label: "database",
|
|
1502
|
-
value: inputs.database
|
|
1503
|
-
}],
|
|
1504
|
-
...inputs.from === void 0 ? [] : [{
|
|
1505
|
-
label: "from",
|
|
1506
|
-
value: inputs.from
|
|
1507
|
-
}],
|
|
1508
|
-
...inputs.to === void 0 ? [] : [{
|
|
1509
|
-
label: "to",
|
|
1510
|
-
value: inputs.to
|
|
1511
|
-
}]
|
|
1512
|
-
]
|
|
1971
|
+
kind: "summary",
|
|
1972
|
+
status: document.ok ? "ok" : "error",
|
|
1973
|
+
text: document.summary
|
|
1513
1974
|
},
|
|
1514
|
-
...
|
|
1515
|
-
kind: "
|
|
1516
|
-
|
|
1975
|
+
...document.ok ? [{
|
|
1976
|
+
kind: "fields",
|
|
1977
|
+
rows: [{
|
|
1978
|
+
label: "storageHash",
|
|
1979
|
+
value: document.contract.storageHash
|
|
1980
|
+
}, ...document.contract.profileHash === void 0 ? [] : [{
|
|
1981
|
+
label: "profileHash",
|
|
1982
|
+
value: document.contract.profileHash
|
|
1983
|
+
}]]
|
|
1984
|
+
}] : [],
|
|
1985
|
+
...warnings.length === 0 && unclaimed.length === 0 ? [] : [{
|
|
1986
|
+
kind: "tree",
|
|
1987
|
+
roots: [...warnings.length === 0 ? [] : [{
|
|
1988
|
+
label: "Schema warnings",
|
|
1989
|
+
status: "warn",
|
|
1990
|
+
children: warnings.map((message) => ({
|
|
1991
|
+
label: message,
|
|
1992
|
+
status: "warn"
|
|
1993
|
+
}))
|
|
1994
|
+
}], ...unclaimed.length === 0 ? [] : [{
|
|
1995
|
+
label: "Unclaimed elements (declared by no contract)",
|
|
1996
|
+
status: "warn",
|
|
1997
|
+
children: unclaimed.map((name) => ({
|
|
1998
|
+
label: name,
|
|
1999
|
+
status: "warn"
|
|
2000
|
+
}))
|
|
2001
|
+
}]]
|
|
1517
2002
|
}],
|
|
1518
|
-
...
|
|
2003
|
+
...document.warning === void 0 ? [] : [{
|
|
1519
2004
|
kind: "summary",
|
|
1520
|
-
status: "
|
|
1521
|
-
text: document.
|
|
2005
|
+
status: "warn",
|
|
2006
|
+
text: document.warning
|
|
2007
|
+
}]
|
|
2008
|
+
],
|
|
2009
|
+
json: () => document
|
|
2010
|
+
};
|
|
2011
|
+
}
|
|
2012
|
+
function schemaPresentations(inputs) {
|
|
2013
|
+
return {
|
|
2014
|
+
human: () => [
|
|
2015
|
+
inputs.header,
|
|
2016
|
+
...schemaFindingBlocks({
|
|
2017
|
+
result: inputs.document,
|
|
2018
|
+
unclaimed: inputs.document.unclaimed,
|
|
2019
|
+
strict: inputs.strict
|
|
2020
|
+
}),
|
|
2021
|
+
{
|
|
2022
|
+
kind: "summary",
|
|
2023
|
+
status: inputs.document.ok ? "ok" : "error",
|
|
2024
|
+
text: inputs.document.summary
|
|
2025
|
+
}
|
|
2026
|
+
],
|
|
2027
|
+
json: () => inputs.document
|
|
2028
|
+
};
|
|
2029
|
+
}
|
|
2030
|
+
/**
|
|
2031
|
+
* One diagnostic per contract space whose live schema does not satisfy its
|
|
2032
|
+
* contract, plus the combined verdict when only the unclaimed list failed it.
|
|
2033
|
+
*/
|
|
2034
|
+
function driftDiagnostics(inputs) {
|
|
2035
|
+
const perSpace = [...inputs.perSpace].filter(([, result]) => !result.ok).map(([space, result]) => schemaVerdictDiagnostic({
|
|
2036
|
+
result,
|
|
2037
|
+
space,
|
|
2038
|
+
nextActions: [PUSH_THE_CONTRACT, RECONCILE_BY_HAND]
|
|
2039
|
+
}));
|
|
2040
|
+
if (perSpace.length > 0) return perSpace;
|
|
2041
|
+
return [schemaVerdictDiagnostic({
|
|
2042
|
+
result: inputs.combined.result,
|
|
2043
|
+
space: void 0,
|
|
2044
|
+
nextActions: [{
|
|
2045
|
+
kind: "user-choice",
|
|
2046
|
+
label: "Declare the unclaimed elements in a contract, or drop them from the database"
|
|
2047
|
+
}]
|
|
2048
|
+
})];
|
|
2049
|
+
}
|
|
2050
|
+
/**
|
|
2051
|
+
* Builds the command with its control-client factory injected, so tests mount
|
|
2052
|
+
* the same tree over a fake client instead of mocking the client module.
|
|
2053
|
+
*/
|
|
2054
|
+
function createDbVerifyCommand(createClient = createControlClient) {
|
|
2055
|
+
return defineOrmCommand({
|
|
2056
|
+
help: {
|
|
2057
|
+
summary: "Check whether the database marker and live schema match your contract",
|
|
2058
|
+
description: "Verifies the database marker first, then checks the database schema\nmatches your contract. Use `--marker-only` for marker-only verification,\n`--schema-only` to skip marker checks and inspect only the live schema,\nand `--strict` to fail if the database includes elements not present in\nthe contract.\nExit codes: 0 = the database matches the contract, 2 = the check could\nnot run (conflicting mode flags, no emitted contract, unreachable\ndatabase), 4 = drift or a marker finding.",
|
|
2059
|
+
examples: [
|
|
2060
|
+
"db verify",
|
|
2061
|
+
"db verify --db $DATABASE_URL",
|
|
2062
|
+
"db verify --strict",
|
|
2063
|
+
"db verify --schema-only",
|
|
2064
|
+
"db verify --marker-only",
|
|
2065
|
+
"db verify --json"
|
|
2066
|
+
]
|
|
2067
|
+
},
|
|
2068
|
+
args: { flags: {
|
|
2069
|
+
db: dbFlag,
|
|
2070
|
+
markerOnly: flag.boolean({ brief: "Skip schema verification and only check the database marker" }),
|
|
2071
|
+
schemaOnly: flag.boolean({ brief: "Skip marker verification and only check whether the live schema satisfies the contract" }),
|
|
2072
|
+
strict: flag.boolean({ brief: "Treat schema elements not present in the contract as an error" })
|
|
2073
|
+
} },
|
|
2074
|
+
needs: { config: ormConfigSection },
|
|
2075
|
+
exitCodes: { 4: "verification found drift or marker findings" },
|
|
2076
|
+
handler: async (args, ctx) => {
|
|
2077
|
+
const strict = args.flags.strict;
|
|
2078
|
+
const resolved = resolveMode({
|
|
2079
|
+
markerOnly: args.flags.markerOnly,
|
|
2080
|
+
schemaOnly: args.flags.schemaOnly,
|
|
2081
|
+
strict
|
|
2082
|
+
});
|
|
2083
|
+
if (!resolved.ok) return notOk(resolved.failure);
|
|
2084
|
+
const mode = resolved.value;
|
|
2085
|
+
const commandInvocation = invocation(mode, strict);
|
|
2086
|
+
const emitted = await readEmittedContract({
|
|
2087
|
+
config: ctx.config,
|
|
2088
|
+
cwd: ctx.cwd,
|
|
2089
|
+
commandName: commandInvocation
|
|
2090
|
+
});
|
|
2091
|
+
if (!emitted.ok) return notOk(emitted.failure);
|
|
2092
|
+
const connection = requireVerifyConnection({
|
|
2093
|
+
config: ctx.config,
|
|
2094
|
+
db: args.flags.db,
|
|
2095
|
+
invocation: commandInvocation
|
|
2096
|
+
});
|
|
2097
|
+
if (!connection.ok) return notOk(connection.failure);
|
|
2098
|
+
const dbConnection = connection.value;
|
|
2099
|
+
const header = headerBlock({
|
|
2100
|
+
contractPath: emitted.value.displayPath,
|
|
2101
|
+
mode,
|
|
2102
|
+
strict,
|
|
2103
|
+
database: maskConnectionUrl(dbConnection)
|
|
2104
|
+
});
|
|
2105
|
+
const migrationsDir = migrationsDirFor(ctx.config, ctx.cwd);
|
|
2106
|
+
const client = createClient({
|
|
2107
|
+
family: ctx.config.family,
|
|
2108
|
+
target: ctx.config.target,
|
|
2109
|
+
adapter: ctx.config.adapter,
|
|
2110
|
+
...ifDefined("driver", ctx.config.driver),
|
|
2111
|
+
extensions: ctx.config.extensions ?? []
|
|
2112
|
+
});
|
|
2113
|
+
const onProgress = controlProgressReporter(ctx.report);
|
|
2114
|
+
const startedAt = Date.now();
|
|
2115
|
+
try {
|
|
2116
|
+
if (mode === "schema-only") {
|
|
2117
|
+
await client.connect(dbConnection);
|
|
2118
|
+
const aggregate = await client.dbVerify({
|
|
2119
|
+
contract: emitted.value.contract,
|
|
2120
|
+
migrationsDir,
|
|
2121
|
+
strict,
|
|
2122
|
+
skipSchema: false,
|
|
2123
|
+
skipMarker: true,
|
|
2124
|
+
onProgress
|
|
2125
|
+
});
|
|
2126
|
+
if (!aggregate.ok) return notOk(normalizeError(aggregate.failure));
|
|
2127
|
+
const combined = combineVerifyResults(aggregate.value.schemaResults, aggregate.value.appSpaceId, strict, aggregate.value.unclaimed);
|
|
2128
|
+
const document = {
|
|
2129
|
+
...combined.result,
|
|
2130
|
+
unclaimed: combined.unclaimed
|
|
2131
|
+
};
|
|
2132
|
+
return ok(ctx.present({
|
|
2133
|
+
data: document,
|
|
2134
|
+
exitCode: combined.result.ok ? 0 : FINDINGS_EXIT_CODE$1,
|
|
2135
|
+
diagnostics: combined.result.ok ? [] : driftDiagnostics({
|
|
2136
|
+
perSpace: aggregate.value.schemaResults,
|
|
2137
|
+
combined
|
|
2138
|
+
})
|
|
2139
|
+
}, schemaPresentations({
|
|
2140
|
+
document,
|
|
2141
|
+
header,
|
|
2142
|
+
strict
|
|
2143
|
+
})));
|
|
2144
|
+
}
|
|
2145
|
+
const verified = await client.verify({
|
|
2146
|
+
contract: emitted.value.contract,
|
|
2147
|
+
connection: dbConnection,
|
|
2148
|
+
onProgress
|
|
2149
|
+
});
|
|
2150
|
+
if (!verified.ok) {
|
|
2151
|
+
const document = verifyDocument({
|
|
2152
|
+
ok: false,
|
|
2153
|
+
mode,
|
|
2154
|
+
summary: verified.summary,
|
|
2155
|
+
verified,
|
|
2156
|
+
schema: void 0,
|
|
2157
|
+
schemaVerification: "skipped",
|
|
2158
|
+
unclaimed: void 0,
|
|
2159
|
+
warning: void 0,
|
|
2160
|
+
elapsed: Date.now() - startedAt
|
|
2161
|
+
});
|
|
2162
|
+
return ok(ctx.present({
|
|
2163
|
+
data: document,
|
|
2164
|
+
exitCode: FINDINGS_EXIT_CODE$1,
|
|
2165
|
+
diagnostics: [markerFindingDiagnostic(verified)]
|
|
2166
|
+
}, verifyPresentations({
|
|
2167
|
+
document,
|
|
2168
|
+
header
|
|
2169
|
+
})));
|
|
2170
|
+
}
|
|
2171
|
+
const aggregate = await client.dbVerify({
|
|
2172
|
+
contract: emitted.value.contract,
|
|
2173
|
+
migrationsDir,
|
|
2174
|
+
strict,
|
|
2175
|
+
skipSchema: mode === "marker-only",
|
|
2176
|
+
skipMarker: false,
|
|
2177
|
+
onProgress
|
|
2178
|
+
});
|
|
2179
|
+
if (!aggregate.ok) return notOk(normalizeError(aggregate.failure));
|
|
2180
|
+
const drift = aggregate.value.markerDrift;
|
|
2181
|
+
if (drift !== null) {
|
|
2182
|
+
const driftCombined = mode === "marker-only" ? void 0 : combineVerifyResults(aggregate.value.schemaResults, aggregate.value.appSpaceId, strict, aggregate.value.unclaimed);
|
|
2183
|
+
const document = verifyDocument({
|
|
2184
|
+
ok: false,
|
|
2185
|
+
mode,
|
|
2186
|
+
summary: normalizeError(drift).message,
|
|
2187
|
+
verified,
|
|
2188
|
+
schema: driftCombined === void 0 ? void 0 : schemaSummary(driftCombined),
|
|
2189
|
+
schemaVerification: driftCombined === void 0 ? "skipped" : "performed",
|
|
2190
|
+
unclaimed: driftCombined?.unclaimed,
|
|
2191
|
+
warning: void 0,
|
|
2192
|
+
elapsed: Date.now() - startedAt
|
|
2193
|
+
});
|
|
2194
|
+
return ok(ctx.present({
|
|
2195
|
+
data: document,
|
|
2196
|
+
exitCode: FINDINGS_EXIT_CODE$1,
|
|
2197
|
+
diagnostics: [markerDriftDiagnostic(drift), ...driftCombined !== void 0 && !driftCombined.result.ok ? driftDiagnostics({
|
|
2198
|
+
perSpace: aggregate.value.schemaResults,
|
|
2199
|
+
combined: driftCombined
|
|
2200
|
+
}) : []]
|
|
2201
|
+
}, verifyPresentations({
|
|
2202
|
+
document,
|
|
2203
|
+
header
|
|
2204
|
+
})));
|
|
2205
|
+
}
|
|
2206
|
+
if (mode === "marker-only") {
|
|
2207
|
+
const document = verifyDocument({
|
|
2208
|
+
ok: true,
|
|
2209
|
+
mode,
|
|
2210
|
+
summary: "Database marker matches contract",
|
|
2211
|
+
verified,
|
|
2212
|
+
schema: void 0,
|
|
2213
|
+
schemaVerification: "skipped",
|
|
2214
|
+
unclaimed: void 0,
|
|
2215
|
+
warning: "Schema verification skipped because --marker-only was provided",
|
|
2216
|
+
elapsed: Date.now() - startedAt
|
|
2217
|
+
});
|
|
2218
|
+
return ok(ctx.present({
|
|
2219
|
+
data: document,
|
|
2220
|
+
exitCode: 0
|
|
2221
|
+
}, verifyPresentations({
|
|
2222
|
+
document,
|
|
2223
|
+
header
|
|
2224
|
+
})));
|
|
2225
|
+
}
|
|
2226
|
+
const combined = combineVerifyResults(aggregate.value.schemaResults, aggregate.value.appSpaceId, strict, aggregate.value.unclaimed);
|
|
2227
|
+
if (!combined.result.ok) {
|
|
2228
|
+
const document = {
|
|
2229
|
+
...combined.result,
|
|
2230
|
+
unclaimed: combined.unclaimed
|
|
2231
|
+
};
|
|
2232
|
+
return ok(ctx.present({
|
|
2233
|
+
data: document,
|
|
2234
|
+
exitCode: FINDINGS_EXIT_CODE$1,
|
|
2235
|
+
diagnostics: driftDiagnostics({
|
|
2236
|
+
perSpace: aggregate.value.schemaResults,
|
|
2237
|
+
combined
|
|
2238
|
+
})
|
|
2239
|
+
}, schemaPresentations({
|
|
2240
|
+
document,
|
|
2241
|
+
header,
|
|
2242
|
+
strict
|
|
2243
|
+
})));
|
|
2244
|
+
}
|
|
2245
|
+
const document = verifyDocument({
|
|
2246
|
+
ok: true,
|
|
2247
|
+
mode,
|
|
2248
|
+
summary: "Database marker and schema match contract",
|
|
2249
|
+
verified,
|
|
2250
|
+
schema: schemaSummary(combined),
|
|
2251
|
+
schemaVerification: "performed",
|
|
2252
|
+
unclaimed: combined.unclaimed,
|
|
2253
|
+
warning: void 0,
|
|
2254
|
+
elapsed: Date.now() - startedAt
|
|
2255
|
+
});
|
|
2256
|
+
return ok(ctx.present({
|
|
2257
|
+
data: document,
|
|
2258
|
+
exitCode: 0
|
|
2259
|
+
}, verifyPresentations({
|
|
2260
|
+
document,
|
|
2261
|
+
header
|
|
2262
|
+
})));
|
|
2263
|
+
} catch (error) {
|
|
2264
|
+
if (isInternalError(error)) throw error;
|
|
2265
|
+
return notOk(verificationThrow({
|
|
2266
|
+
error,
|
|
2267
|
+
invocation: commandInvocation,
|
|
2268
|
+
connection: dbConnection
|
|
2269
|
+
}));
|
|
2270
|
+
} finally {
|
|
2271
|
+
await closeQuietly(client);
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
});
|
|
2275
|
+
}
|
|
2276
|
+
const dbVerifyCommand = createDbVerifyCommand();
|
|
2277
|
+
const NOTHING_TO_FORMAT = "Nothing to format (contract source is not PSL).";
|
|
2278
|
+
function formatPresentations(document, cwd) {
|
|
2279
|
+
const path = document.path;
|
|
2280
|
+
return {
|
|
2281
|
+
human: () => document.formatted && path !== void 0 ? [{
|
|
2282
|
+
kind: "summary",
|
|
2283
|
+
status: "ok",
|
|
2284
|
+
text: [{ text: "Formatted " }, {
|
|
2285
|
+
text: relative(cwd, path),
|
|
2286
|
+
tone: "identifier"
|
|
2287
|
+
}]
|
|
2288
|
+
}] : [{
|
|
2289
|
+
kind: "summary",
|
|
2290
|
+
status: "info",
|
|
2291
|
+
text: NOTHING_TO_FORMAT
|
|
2292
|
+
}],
|
|
2293
|
+
json: () => document
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
function createFormatCommand$1(execute = executeFormat) {
|
|
2297
|
+
return defineOrmCommand({
|
|
2298
|
+
help: {
|
|
2299
|
+
summary: "Format your PSL contract source",
|
|
2300
|
+
description: "Formats the Prisma schema (PSL) contract source declared in your config\n(contract.source.inputs[0]) in place. Only runs when contract.source.format\nis 'psl'; a TypeScript or unset source is left untouched. Indent and newline\nare read from the optional formatter config section, defaulting to two\nspaces and the system newline.",
|
|
2301
|
+
examples: ["format", "format --json"]
|
|
2302
|
+
},
|
|
2303
|
+
needs: { config: ormConfigSection },
|
|
2304
|
+
handler: async (_args, ctx) => {
|
|
2305
|
+
const result = await execute({
|
|
2306
|
+
config: ctx.config,
|
|
2307
|
+
cwd: ctx.cwd
|
|
2308
|
+
});
|
|
2309
|
+
if (!result.ok) return notOk(normalizeError(result.failure));
|
|
2310
|
+
return ok(ctx.present({ data: result.value }, formatPresentations(result.value, ctx.cwd)));
|
|
2311
|
+
}
|
|
2312
|
+
});
|
|
2313
|
+
}
|
|
2314
|
+
const formatCommand = createFormatCommand$1();
|
|
2315
|
+
/** The invocation that finishes what a failed `init` phase started. */
|
|
2316
|
+
const EMIT_COMMAND = "prisma-next contract emit";
|
|
2317
|
+
/**
|
|
2318
|
+
* The command's own record of a phase that failed after the scaffold was
|
|
2319
|
+
* written. The scaffold is on disk and the run has a result to report, so each
|
|
2320
|
+
* of these rides a completed envelope as a finding rather than replacing it.
|
|
2321
|
+
*/
|
|
2322
|
+
function initFinding(code, summary, parts) {
|
|
2323
|
+
return {
|
|
2324
|
+
code,
|
|
2325
|
+
severity: "error",
|
|
2326
|
+
summary,
|
|
2327
|
+
why: parts.why,
|
|
2328
|
+
nextActions: parts.nextActions,
|
|
2329
|
+
meta: parts.meta,
|
|
2330
|
+
docsUrl: docsUrlFor(code)
|
|
2331
|
+
};
|
|
2332
|
+
}
|
|
2333
|
+
/**
|
|
2334
|
+
* The dependency install failed. The engine composed and announced the command
|
|
2335
|
+
* line, so its own next action — which carries the redacted spelling — is the
|
|
2336
|
+
* one that tells the user what to run; `init` adds the emit that was waiting
|
|
2337
|
+
* on it.
|
|
2338
|
+
*/
|
|
2339
|
+
function installFailedFinding(failure, filesWritten) {
|
|
2340
|
+
return initFinding("CLI.INIT_INSTALL_FAILED", "Failed to install dependencies", {
|
|
2341
|
+
why: failure.why ?? failure.message,
|
|
2342
|
+
nextActions: [...failure.nextActions, runCommandAction("Emit the contract once the dependencies are installed", EMIT_COMMAND)],
|
|
2343
|
+
meta: {
|
|
2344
|
+
filesWritten,
|
|
2345
|
+
install: failure.meta ?? {}
|
|
2346
|
+
}
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
/** The first emit failed against the freshly written scaffold. */
|
|
2350
|
+
function emitFailedFinding(cause, filesWritten) {
|
|
2351
|
+
return initFinding("CLI.INIT_EMIT_FAILED", "Failed to emit contract", {
|
|
2352
|
+
why: `\`${EMIT_COMMAND}\` failed: ${cause}`,
|
|
2353
|
+
nextActions: [chooseAction("Fix the problem the contract source reports, then emit again"), runCommandAction("Emit the contract", EMIT_COMMAND)],
|
|
2354
|
+
meta: {
|
|
2355
|
+
filesWritten,
|
|
2356
|
+
cause
|
|
2357
|
+
}
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
/**
|
|
2361
|
+
* An agent-skill install failed; the project itself is complete without it.
|
|
2362
|
+
* The remedy is the skill-install commands themselves, which run against any
|
|
2363
|
+
* directory — advising a re-run of `init` would put the user's schema at risk
|
|
2364
|
+
* to fix something `init` no longer needs to be involved in.
|
|
2365
|
+
*/
|
|
2366
|
+
function skillInstallFailedFinding(failure, filesWritten, skillCommands) {
|
|
2367
|
+
return initFinding("CLI.INIT_SKILL_INSTALL_FAILED", "Failed to install Prisma Next skills", {
|
|
2368
|
+
why: failure.why ?? failure.message,
|
|
2369
|
+
nextActions: [...failure.nextActions, ...skillCommands.map((command) => runCommandAction("Install the Prisma Next skills", command))],
|
|
2370
|
+
meta: {
|
|
2371
|
+
filesWritten,
|
|
2372
|
+
skillInstall: failure.meta ?? {}
|
|
2373
|
+
}
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
function fileNodes(paths) {
|
|
2377
|
+
return paths.map((path) => ({
|
|
2378
|
+
label: path,
|
|
2379
|
+
tone: "identifier"
|
|
2380
|
+
}));
|
|
2381
|
+
}
|
|
2382
|
+
function scaffoldTree(document) {
|
|
2383
|
+
const roots = [{
|
|
2384
|
+
label: "written",
|
|
2385
|
+
tone: "heading",
|
|
2386
|
+
children: fileNodes(document.filesWritten)
|
|
2387
|
+
}];
|
|
2388
|
+
if (document.filesDeleted.length > 0) roots.push({
|
|
2389
|
+
label: "removed (stale artifacts and retired skill directories)",
|
|
2390
|
+
tone: "heading",
|
|
2391
|
+
children: fileNodes(document.filesDeleted)
|
|
2392
|
+
});
|
|
2393
|
+
const installed = document.packagesInstalled;
|
|
2394
|
+
if (installed.status === "installed") roots.push({
|
|
2395
|
+
label: "installed",
|
|
2396
|
+
tone: "heading",
|
|
2397
|
+
children: [...fileNodes(installed.deps), ...installed.devDeps.map((dep) => ({
|
|
2398
|
+
label: `${dep} (dev)`,
|
|
2399
|
+
tone: "identifier"
|
|
2400
|
+
}))]
|
|
2401
|
+
});
|
|
2402
|
+
return {
|
|
2403
|
+
kind: "tree",
|
|
2404
|
+
roots
|
|
2405
|
+
};
|
|
2406
|
+
}
|
|
2407
|
+
/**
|
|
2408
|
+
* The scaffold's follow-up steps, typed. `nextSteps` in the result document
|
|
2409
|
+
* keeps its numbered prose for consumers that already read it; these are the
|
|
2410
|
+
* same advice in the shape an agent can act on.
|
|
2411
|
+
*/
|
|
2412
|
+
function buildInitNextActions(inputs) {
|
|
2413
|
+
const actions = [chooseAction("Set DATABASE_URL in your environment (export it or add it to .env)")];
|
|
2414
|
+
if (!inputs.contractEmitted) actions.push(runCommandAction("Emit the contract", EMIT_COMMAND));
|
|
2415
|
+
actions.push({
|
|
2416
|
+
kind: "edit-file",
|
|
2417
|
+
label: `Edit your schema at ${inputs.schemaPath}, then emit again`
|
|
2418
|
+
});
|
|
2419
|
+
actions.push(chooseAction("Open prisma-next.md for a quick reference on writing your first typed query"));
|
|
2420
|
+
if (inputs.skillRegistered) actions.push(chooseAction("Prisma Next skills are registered with your agent runtime — ask it to add a model, run a query, or plan a migration"));
|
|
2421
|
+
return actions;
|
|
2422
|
+
}
|
|
2423
|
+
const DONE_TEXT = "Done. Open prisma-next.md to get started.";
|
|
2424
|
+
const INCOMPLETE_TEXT = "Scaffold written. Finish the steps above to complete setup.";
|
|
2425
|
+
/**
|
|
2426
|
+
* `init` writes files rather than data another program reads, so it supplies
|
|
2427
|
+
* no `stdout` payload: human mode writes nothing to stdout.
|
|
2428
|
+
*/
|
|
2429
|
+
function initPresentations(inputs) {
|
|
2430
|
+
const { document } = inputs;
|
|
2431
|
+
return {
|
|
2432
|
+
human: () => [
|
|
2433
|
+
{
|
|
2434
|
+
kind: "fields",
|
|
2435
|
+
rail: true,
|
|
2436
|
+
rows: [
|
|
2437
|
+
{
|
|
2438
|
+
label: "target",
|
|
2439
|
+
value: document.target
|
|
2440
|
+
},
|
|
2441
|
+
{
|
|
2442
|
+
label: "authoring",
|
|
2443
|
+
value: document.authoring
|
|
2444
|
+
},
|
|
2445
|
+
{
|
|
2446
|
+
label: "schema",
|
|
2447
|
+
value: document.schemaPath
|
|
2448
|
+
}
|
|
2449
|
+
]
|
|
2450
|
+
},
|
|
2451
|
+
scaffoldTree(document),
|
|
2452
|
+
inputs.complete ? {
|
|
2453
|
+
kind: "summary",
|
|
2454
|
+
status: "ok",
|
|
2455
|
+
text: DONE_TEXT
|
|
2456
|
+
} : {
|
|
2457
|
+
kind: "summary",
|
|
2458
|
+
status: "warn",
|
|
2459
|
+
text: INCOMPLETE_TEXT
|
|
2460
|
+
}
|
|
2461
|
+
],
|
|
2462
|
+
json: () => document,
|
|
2463
|
+
next: () => inputs.nextActions
|
|
2464
|
+
};
|
|
2465
|
+
}
|
|
2466
|
+
/**
|
|
2467
|
+
* Emits the contract for the project `init` has just scaffolded.
|
|
2468
|
+
*
|
|
2469
|
+
* The config file is the one this run wrote, so it is read here rather than
|
|
2470
|
+
* through `needs.config`: `init` runs where no config exists yet, and the
|
|
2471
|
+
* engine loads a section before the handler starts. The emitter and the config
|
|
2472
|
+
* loader are imported at execution time so a run that skips the install — and
|
|
2473
|
+
* therefore skips the emit — never pays for either.
|
|
2474
|
+
*/
|
|
2475
|
+
async function emitScaffoldedContract(ctx) {
|
|
2476
|
+
const { executeContractEmit } = await import("./contract-emit-DDWBBvF1-BeGZCRrb.mjs");
|
|
2477
|
+
const { loadConfigForSections } = await import("./exports-BZLNbtkY.mjs").then((n) => n.t);
|
|
2478
|
+
const configPath = join(ctx.cwd, "prisma-next.config.ts");
|
|
2479
|
+
const loaded = await loadConfigForSections(configPath, [
|
|
2480
|
+
"contract",
|
|
2481
|
+
"family",
|
|
2482
|
+
"target",
|
|
2483
|
+
"adapter",
|
|
2484
|
+
"extensions"
|
|
2485
|
+
]);
|
|
2486
|
+
if (!loaded.ok) throw loaded.failure;
|
|
2487
|
+
await executeContractEmit({
|
|
2488
|
+
config: loaded.value,
|
|
2489
|
+
cwd: ctx.cwd,
|
|
2490
|
+
configPath
|
|
2491
|
+
});
|
|
2492
|
+
}
|
|
2493
|
+
const CONFIG_FILE = "prisma-next.config.ts";
|
|
2494
|
+
const QUICK_REFERENCE_FILE = "prisma-next.md";
|
|
2495
|
+
const ENV_EXAMPLE_FILE = ".env.example";
|
|
2496
|
+
/**
|
|
2497
|
+
* The generated files a run replaces outright, in the order it writes them.
|
|
2498
|
+
* Everything else the scaffold touches it merges (`tsconfig.json`,
|
|
2499
|
+
* `package.json`, the git files) or keeps (`.env`, `README.md`), so replacing
|
|
2500
|
+
* one of these is the destructive act consent is asked for.
|
|
2501
|
+
*
|
|
2502
|
+
* `.env.example` is written the same way but deliberately left out: it is a
|
|
2503
|
+
* conventional filename in projects that have never seen Prisma Next, and a
|
|
2504
|
+
* consent token demanded for it would fire on first runs. The scaffold warns
|
|
2505
|
+
* when it replaces one instead.
|
|
2506
|
+
*/
|
|
2507
|
+
function generatedFilesInitReplaces(schemaPath) {
|
|
2508
|
+
return [
|
|
2509
|
+
schemaPath,
|
|
2510
|
+
CONFIG_FILE,
|
|
2511
|
+
join(dirname(schemaPath), "db.ts"),
|
|
2512
|
+
QUICK_REFERENCE_FILE
|
|
2513
|
+
];
|
|
2514
|
+
}
|
|
2515
|
+
const MANAGERS = /* @__PURE__ */ new Set([
|
|
2516
|
+
"pnpm",
|
|
2517
|
+
"npm",
|
|
2518
|
+
"yarn",
|
|
2519
|
+
"bun",
|
|
2520
|
+
"deno"
|
|
2521
|
+
]);
|
|
2522
|
+
function isPackageManager(name) {
|
|
2523
|
+
return MANAGERS.has(name);
|
|
2524
|
+
}
|
|
2525
|
+
/**
|
|
2526
|
+
* The manager the scaffolded quick reference and README name in their example
|
|
2527
|
+
* commands. It resolves the way the engine's package operations resolve theirs
|
|
2528
|
+
* — the project at `cwd` first, then the manager that invoked this process —
|
|
2529
|
+
* so the documentation `init` writes names the manager `init` drove. The
|
|
2530
|
+
* capability itself reports no manager back on success, which is why this is
|
|
2531
|
+
* worked out a second time here.
|
|
2532
|
+
*/
|
|
2533
|
+
async function resolveScaffoldPackageManager(ctx) {
|
|
2534
|
+
const detected = await detect({ cwd: ctx.cwd });
|
|
2535
|
+
if (detected !== null && isPackageManager(detected.name)) return detected.name;
|
|
2536
|
+
const invoking = ctx.env["npm_config_user_agent"]?.split("/")[0];
|
|
2537
|
+
return invoking !== void 0 && isPackageManager(invoking) ? invoking : "npm";
|
|
2538
|
+
}
|
|
2539
|
+
/**
|
|
2540
|
+
* True when the parsed manifest declares `name` directly in `dependencies` or
|
|
2541
|
+
* `devDependencies`. Transitive presence is deliberately ignored: detecting it
|
|
2542
|
+
* needs the lockfile, and the realistic risk is a direct pin being clobbered.
|
|
2543
|
+
*/
|
|
2544
|
+
function hasDirectDep(parsed, name) {
|
|
2545
|
+
for (const field of ["dependencies", "devDependencies"]) {
|
|
2546
|
+
const value = parsed[field];
|
|
2547
|
+
if (value !== null && typeof value === "object" && name in value) return true;
|
|
2548
|
+
}
|
|
2549
|
+
return false;
|
|
2550
|
+
}
|
|
2551
|
+
/**
|
|
2552
|
+
* npm package names are lowercase and URL-safe; `basename(cwd)` happily
|
|
2553
|
+
* returns "My Project" or ".hidden", both of which npm refuses to read.
|
|
2554
|
+
*/
|
|
2555
|
+
function sanitisePackageName(raw) {
|
|
2556
|
+
const trimmed = raw.toLowerCase().replace(/[^a-z0-9._~-]/g, "-").replace(/^[._-]+/, "").replace(/-+/g, "-");
|
|
2557
|
+
return trimmed.length > 0 ? trimmed : "my-app";
|
|
2558
|
+
}
|
|
2559
|
+
/**
|
|
2560
|
+
* The manifest `init` writes for a directory that has none. Private, so a
|
|
2561
|
+
* stray publish cannot leak the placeholder, and ESM so the scaffolded import
|
|
2562
|
+
* attributes load under Node's loader.
|
|
2563
|
+
*/
|
|
2564
|
+
function defaultPackageJsonContent(rawName) {
|
|
2565
|
+
return `${JSON.stringify({
|
|
2566
|
+
name: sanitisePackageName(rawName),
|
|
2567
|
+
version: "0.0.0",
|
|
2568
|
+
private: true,
|
|
2569
|
+
type: "module"
|
|
2570
|
+
}, null, 2)}\n`;
|
|
2571
|
+
}
|
|
2572
|
+
function hasProjectManifest(cwd) {
|
|
2573
|
+
return existsSync(join(cwd, "package.json")) || existsSync(join(cwd, "deno.json")) || existsSync(join(cwd, "deno.jsonc"));
|
|
2574
|
+
}
|
|
2575
|
+
/**
|
|
2576
|
+
* Computes every write before touching disk: reads and parses each file the
|
|
2577
|
+
* scaffold merges with, so a malformed manifest or tsconfig fails with the
|
|
2578
|
+
* user's project byte-identical to how it started.
|
|
2579
|
+
*/
|
|
2580
|
+
function planScaffold(ctx) {
|
|
2581
|
+
const { cwd, inputs, packageManager, resolveImportSpecifier } = ctx;
|
|
2582
|
+
const warnings = [];
|
|
2583
|
+
const schemaDir = dirname(inputs.schemaPath);
|
|
2584
|
+
const configContractPath = isAbsolute(inputs.schemaPath) ? inputs.schemaPath : `./${inputs.schemaPath}`;
|
|
2585
|
+
const runPrefix = formatRunCommand(packageManager, "prisma-next", "").trimEnd();
|
|
2586
|
+
const files = [
|
|
2587
|
+
{
|
|
2588
|
+
path: inputs.schemaPath,
|
|
2589
|
+
content: starterSchema(inputs.target, inputs.authoring, resolveImportSpecifier)
|
|
2590
|
+
},
|
|
2591
|
+
{
|
|
2592
|
+
path: CONFIG_FILE,
|
|
2593
|
+
content: configFile(inputs.target, configContractPath, resolveImportSpecifier)
|
|
2594
|
+
},
|
|
2595
|
+
{
|
|
2596
|
+
path: join(schemaDir, "db.ts"),
|
|
2597
|
+
content: dbFile(inputs.target, resolveImportSpecifier)
|
|
2598
|
+
},
|
|
2599
|
+
{
|
|
2600
|
+
path: QUICK_REFERENCE_FILE,
|
|
2601
|
+
content: quickReferenceMd(inputs.target, inputs.authoring, inputs.schemaPath, runPrefix, resolveImportSpecifier)
|
|
2602
|
+
},
|
|
2603
|
+
{
|
|
2604
|
+
path: ENV_EXAMPLE_FILE,
|
|
2605
|
+
content: envExampleContent(inputs.target)
|
|
2606
|
+
}
|
|
2607
|
+
];
|
|
2608
|
+
if (existsSync(join(cwd, ENV_EXAMPLE_FILE))) warnings.push(`${ENV_EXAMPLE_FILE} already existed and was replaced with the Prisma Next template.`);
|
|
2609
|
+
const filesToDelete = inputs.reinit ? [...findStaleArtifacts(cwd, schemaDir)] : [];
|
|
2610
|
+
const dirsToDelete = legacySkillDirs().filter((rel) => existsSync(join(cwd, rel)));
|
|
2611
|
+
if (inputs.writeEnv) if (existsSync(join(cwd, ".env"))) warnings.push(".env already exists; leaving it untouched. Compare with .env.example for any new keys.");
|
|
2612
|
+
else files.push({
|
|
2613
|
+
path: ".env",
|
|
2614
|
+
content: envFileContent(inputs.target)
|
|
2615
|
+
});
|
|
2616
|
+
const tsconfigPath = join(cwd, "tsconfig.json");
|
|
2617
|
+
if (existsSync(tsconfigPath)) {
|
|
2618
|
+
const existing = readFileSync(tsconfigPath, "utf-8");
|
|
2619
|
+
let merged;
|
|
2620
|
+
try {
|
|
2621
|
+
merged = mergeTsConfig(existing);
|
|
2622
|
+
} catch (error) {
|
|
2623
|
+
if (error instanceof TsConfigParseError) throw errorInitInvalidTsconfig({
|
|
2624
|
+
path: "tsconfig.json",
|
|
2625
|
+
cause: error.message
|
|
2626
|
+
});
|
|
2627
|
+
throw error;
|
|
2628
|
+
}
|
|
2629
|
+
files.push({
|
|
2630
|
+
path: "tsconfig.json",
|
|
2631
|
+
content: merged,
|
|
2632
|
+
note: "Updated tsconfig.json with required compiler options."
|
|
2633
|
+
});
|
|
2634
|
+
} else files.push({
|
|
2635
|
+
path: "tsconfig.json",
|
|
2636
|
+
content: defaultTsConfig()
|
|
2637
|
+
});
|
|
2638
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
2639
|
+
const nextGitignore = mergeGitignore(existsSync(gitignorePath) ? readFileSync(gitignorePath, "utf-8") : void 0);
|
|
2640
|
+
if (nextGitignore !== null) files.push({
|
|
2641
|
+
path: ".gitignore",
|
|
2642
|
+
content: nextGitignore
|
|
2643
|
+
});
|
|
2644
|
+
const gitattributesPath = join(cwd, ".gitattributes");
|
|
2645
|
+
const nextGitattributes = mergeGitattributes(existsSync(gitattributesPath) ? readFileSync(gitattributesPath, "utf-8") : void 0, requiredGitattributesLines(schemaDir, inputs.target));
|
|
2646
|
+
if (nextGitattributes !== null) files.push({
|
|
2647
|
+
path: ".gitattributes",
|
|
2648
|
+
content: nextGitattributes
|
|
2649
|
+
});
|
|
2650
|
+
const manifestPath = join(cwd, "package.json");
|
|
2651
|
+
const manifestExisted = existsSync(manifestPath);
|
|
2652
|
+
const synthesiseManifest = !manifestExisted && !hasProjectManifest(cwd);
|
|
2653
|
+
let parsedManifest = null;
|
|
2654
|
+
if (manifestExisted || synthesiseManifest) {
|
|
2655
|
+
const raw = manifestExisted ? readFileSync(manifestPath, "utf-8") : defaultPackageJsonContent(basename(cwd));
|
|
2656
|
+
try {
|
|
2657
|
+
parsedManifest = JSON.parse(raw);
|
|
2658
|
+
} catch (error) {
|
|
2659
|
+
if (error instanceof SyntaxError) throw errorInitInvalidManifest({
|
|
2660
|
+
path: "package.json",
|
|
2661
|
+
cause: error.message
|
|
2662
|
+
});
|
|
2663
|
+
throw error;
|
|
2664
|
+
}
|
|
2665
|
+
let working = raw;
|
|
2666
|
+
let changed = synthesiseManifest;
|
|
2667
|
+
if (inputs.removePreviousFacade !== null) {
|
|
2668
|
+
const next = removeDependency(working, inputs.removePreviousFacade);
|
|
2669
|
+
if (next !== null) {
|
|
2670
|
+
working = next;
|
|
2671
|
+
changed = true;
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
const { content: withScripts, warnings: scriptWarnings } = mergePackageScripts(working, REQUIRED_SCRIPTS);
|
|
2675
|
+
if (withScripts !== null) {
|
|
2676
|
+
working = withScripts;
|
|
2677
|
+
changed = true;
|
|
2678
|
+
}
|
|
2679
|
+
const { content: withType, warning: typeWarning } = ensureEsmModuleType(working);
|
|
2680
|
+
if (withType !== null) {
|
|
2681
|
+
working = withType;
|
|
2682
|
+
changed = true;
|
|
2683
|
+
}
|
|
2684
|
+
if (changed) files.push({
|
|
2685
|
+
path: "package.json",
|
|
2686
|
+
content: working
|
|
2687
|
+
});
|
|
2688
|
+
warnings.push(...scriptWarnings);
|
|
2689
|
+
if (typeWarning !== null) warnings.push(typeWarning);
|
|
2690
|
+
if (synthesiseManifest) warnings.push("No package.json found in the target directory; created a minimal one. Edit `name` / `version` to taste.");
|
|
2691
|
+
}
|
|
2692
|
+
if (existsSync(join(cwd, "src/index.ts"))) if (existsSync(join(cwd, "README.md"))) warnings.push("README.md already exists; leaving it untouched.");
|
|
2693
|
+
else {
|
|
2694
|
+
const declaredName = parsedManifest !== null && typeof parsedManifest["name"] === "string" ? parsedManifest["name"] : basename(cwd);
|
|
2695
|
+
files.push({
|
|
2696
|
+
path: "README.md",
|
|
2697
|
+
content: minimalProjectReadmeMd(inputs.target, inputs.schemaPath, sanitisePackageName(declaredName), packageManager)
|
|
2698
|
+
});
|
|
2699
|
+
}
|
|
2700
|
+
return {
|
|
2701
|
+
files,
|
|
2702
|
+
filesToDelete,
|
|
2703
|
+
dirsToDelete,
|
|
2704
|
+
warnings,
|
|
2705
|
+
hasTypesNode: parsedManifest !== null && hasDirectDep(parsedManifest, "@types/node")
|
|
2706
|
+
};
|
|
2707
|
+
}
|
|
2708
|
+
/**
|
|
2709
|
+
* Plans every write, then performs them. Everything that can fail on the
|
|
2710
|
+
* user's existing files fails during planning, so a failure leaves the project
|
|
2711
|
+
* as it was; after the first write, the scaffold is on disk to keep, and a
|
|
2712
|
+
* write that fails partway names the files that already landed.
|
|
2713
|
+
*/
|
|
2714
|
+
function scaffoldProject(ctx) {
|
|
2715
|
+
const resolveImportSpecifier = scaffoldSpecifierResolverFor(ctx.inputs.target);
|
|
2716
|
+
const plan = planScaffold({
|
|
2717
|
+
...ctx,
|
|
2718
|
+
resolveImportSpecifier
|
|
2719
|
+
});
|
|
2720
|
+
const filesWritten = [];
|
|
2721
|
+
const filesDeleted = [];
|
|
2722
|
+
const notes = [];
|
|
2723
|
+
for (const file of plan.files) {
|
|
2724
|
+
const target = join(ctx.cwd, file.path);
|
|
2725
|
+
try {
|
|
2726
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
2727
|
+
writeFileSync(target, file.content, "utf-8");
|
|
2728
|
+
} catch (error) {
|
|
2729
|
+
throw errorInitWriteFailed({
|
|
2730
|
+
path: file.path,
|
|
2731
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
2732
|
+
filesWritten
|
|
2733
|
+
});
|
|
2734
|
+
}
|
|
2735
|
+
filesWritten.push(file.path);
|
|
2736
|
+
if (file.note !== void 0) notes.push(file.note);
|
|
2737
|
+
}
|
|
2738
|
+
for (const rel of plan.filesToDelete) {
|
|
2739
|
+
const target = join(ctx.cwd, rel);
|
|
2740
|
+
if (!existsSync(target)) continue;
|
|
2741
|
+
try {
|
|
2742
|
+
unlinkSync(target);
|
|
2743
|
+
filesDeleted.push(rel);
|
|
2744
|
+
} catch (error) {
|
|
2745
|
+
if (!(error instanceof Error && Reflect.get(error, "code") === "ENOENT")) throw error;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
for (const rel of plan.dirsToDelete) {
|
|
2749
|
+
rmSync(join(ctx.cwd, rel), {
|
|
2750
|
+
recursive: true,
|
|
2751
|
+
force: true
|
|
2752
|
+
});
|
|
2753
|
+
filesDeleted.push(rel);
|
|
2754
|
+
}
|
|
2755
|
+
return {
|
|
2756
|
+
filesWritten,
|
|
2757
|
+
filesDeleted,
|
|
2758
|
+
warnings: plan.warnings,
|
|
2759
|
+
notes,
|
|
2760
|
+
hasTypesNode: plan.hasTypesNode,
|
|
2761
|
+
resolveImportSpecifier
|
|
2762
|
+
};
|
|
2763
|
+
}
|
|
2764
|
+
const REQUIRED_FLAG_PROMPTS = /* @__PURE__ */ new Set(["target", "authoring"]);
|
|
2765
|
+
function formatFileList(files) {
|
|
2766
|
+
const last = files.at(-1) ?? "";
|
|
2767
|
+
return files.length <= 1 ? last : `${files.slice(0, -1).join(", ")} and ${last}`;
|
|
2768
|
+
}
|
|
2769
|
+
/**
|
|
2770
|
+
* Names the files at stake rather than the category they belong to: the user
|
|
2771
|
+
* knows `contract.prisma`, not "every generated file".
|
|
2772
|
+
*/
|
|
2773
|
+
function consentQuestion(replaced) {
|
|
2774
|
+
const written = replaced.length === 1 ? "it" : "them";
|
|
2775
|
+
return `Re-initializing replaces ${formatFileList(replaced)} with a fresh scaffold, losing anything you wrote in ${written}.`;
|
|
2776
|
+
}
|
|
2777
|
+
/**
|
|
2778
|
+
* The engine raises this when a prompt has no default and the session cannot
|
|
2779
|
+
* show it. For the two prompts that stand in for a required flag, that is the
|
|
2780
|
+
* same condition `init` has always reported as a missing flag.
|
|
2781
|
+
*/
|
|
2782
|
+
function isPromptRequired(error) {
|
|
2783
|
+
return error instanceof Error && Reflect.get(error, "code") === "CLI.PROMPT_REQUIRED";
|
|
2784
|
+
}
|
|
2785
|
+
/**
|
|
2786
|
+
* `--confirm` matches a token, so the token has to be something the user can
|
|
2787
|
+
* see and type. The working directory's name is it; a directory with no name
|
|
2788
|
+
* of its own (the filesystem root) falls back to its path.
|
|
2789
|
+
*
|
|
2790
|
+
* Trimmed, because the engine compares the typed answer trimmed: an untrimmed
|
|
2791
|
+
* token from a directory named `my app ` is one no keystroke sequence matches.
|
|
2792
|
+
*/
|
|
2793
|
+
function consentToken(cwd) {
|
|
2794
|
+
const name = basename(cwd).trim();
|
|
2795
|
+
return name.length > 0 ? name : cwd.trim();
|
|
2796
|
+
}
|
|
2797
|
+
async function askTarget(prompt) {
|
|
2798
|
+
return prompt.select("What database are you using?", [{
|
|
2799
|
+
value: "postgres",
|
|
2800
|
+
label: "PostgreSQL"
|
|
2801
|
+
}, {
|
|
2802
|
+
value: "mongo",
|
|
2803
|
+
label: "MongoDB"
|
|
2804
|
+
}]);
|
|
2805
|
+
}
|
|
2806
|
+
async function askAuthoring(prompt) {
|
|
2807
|
+
return prompt.select("How do you want to write your schema?", [{
|
|
2808
|
+
value: "psl",
|
|
2809
|
+
label: "Prisma Schema Language (.prisma)"
|
|
2810
|
+
}, {
|
|
2811
|
+
value: "typescript",
|
|
2812
|
+
label: "TypeScript (.ts)"
|
|
2813
|
+
}]);
|
|
2814
|
+
}
|
|
2815
|
+
/**
|
|
2816
|
+
* The schema path the user typed goes through the same validator the flag
|
|
2817
|
+
* does, so an answer the engine accepted still has to agree with the authoring
|
|
2818
|
+
* style.
|
|
2819
|
+
*/
|
|
2820
|
+
async function askSchemaPath(prompt, authoring) {
|
|
2821
|
+
return validateSchemaPath(await prompt.text("Where should the schema file go?", { default: defaultSchemaPath(authoring) }), authoring);
|
|
2822
|
+
}
|
|
2823
|
+
/**
|
|
2824
|
+
* The facade a previous `init` installed for the other target, when this run
|
|
2825
|
+
* switches targets and one is still declared. Reads every name a previous run
|
|
2826
|
+
* could have written, because older versions scaffolded the workspace name.
|
|
2827
|
+
*/
|
|
2828
|
+
function previousFacade(cwd, target) {
|
|
2829
|
+
const manifestPath = join(cwd, "package.json");
|
|
2830
|
+
if (!existsSync(manifestPath)) return;
|
|
2831
|
+
const otherTarget = target === "postgres" ? "mongo" : "postgres";
|
|
2832
|
+
const candidates = [targetPackageName(otherTarget, scaffoldSpecifierResolverFor(otherTarget)), targetPackageName(otherTarget)];
|
|
2833
|
+
let parsed;
|
|
2834
|
+
try {
|
|
2835
|
+
parsed = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
2836
|
+
} catch {
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
const deps = parsed["dependencies"];
|
|
2840
|
+
if (deps === null || typeof deps !== "object" || Array.isArray(deps)) return;
|
|
2841
|
+
const declared = deps;
|
|
2842
|
+
return candidates.find((name) => Object.hasOwn(declared, name));
|
|
2843
|
+
}
|
|
2844
|
+
async function resolveRemovePreviousFacade(ctx) {
|
|
2845
|
+
if (!ctx.reinit || ctx.keepPreviousFacade) return null;
|
|
2846
|
+
const facade = previousFacade(ctx.cwd, ctx.target);
|
|
2847
|
+
if (facade === void 0) return null;
|
|
2848
|
+
const otherTarget = ctx.target === "postgres" ? "mongo" : "postgres";
|
|
2849
|
+
return await ctx.prompt.confirm(`Switching from ${targetLabel(otherTarget)} to ${targetLabel(ctx.target)} — remove ${facade} from package.json dependencies?`, { default: true }) ? facade : null;
|
|
2850
|
+
}
|
|
2851
|
+
/**
|
|
2852
|
+
* Resolves every input from the flags and, where a flag is absent, from the
|
|
2853
|
+
* engine's prompt surface.
|
|
2854
|
+
*
|
|
2855
|
+
* Order matters twice. Every flag value is validated before anything is asked,
|
|
2856
|
+
* so a typo costs a typo rather than a typed consent token. And consent comes
|
|
2857
|
+
* as soon as the schema path is known, because that is the point at which the
|
|
2858
|
+
* question can name the files it is about — and before any question whose
|
|
2859
|
+
* answer only matters if the run proceeds.
|
|
2860
|
+
*/
|
|
2861
|
+
async function resolveInitInputs(ctx) {
|
|
2862
|
+
const { cwd, flags, prompt } = ctx;
|
|
2863
|
+
if (flags.strictProbe && !flags.probeDb) throw errorInitStrictProbeWithoutProbe();
|
|
2864
|
+
const flagTarget = resolveTarget(flags.target);
|
|
2865
|
+
const flagAuthoring = resolveAuthoring(flags.authoring);
|
|
2866
|
+
let target;
|
|
2867
|
+
let authoring;
|
|
2868
|
+
try {
|
|
2869
|
+
target = flagTarget ?? await askTarget(prompt);
|
|
2870
|
+
authoring = flagAuthoring ?? await askAuthoring(prompt);
|
|
2871
|
+
} catch (error) {
|
|
2872
|
+
if (!isPromptRequired(error)) throw error;
|
|
2873
|
+
throw errorInitMissingFlags({
|
|
2874
|
+
missing: [...flagTarget === void 0 ? ["target"] : [], ...flagAuthoring === void 0 ? ["authoring"] : []].filter((flag) => REQUIRED_FLAG_PROMPTS.has(flag)),
|
|
2875
|
+
why: "This session cannot prompt, so the answers have to arrive as flags."
|
|
2876
|
+
});
|
|
2877
|
+
}
|
|
2878
|
+
const schemaPath = flags.schemaPath !== void 0 ? validateSchemaPath(flags.schemaPath, authoring) : await askSchemaPath(prompt, authoring);
|
|
2879
|
+
const replaced = generatedFilesInitReplaces(schemaPath).filter((relative) => existsSync(join(cwd, relative)));
|
|
2880
|
+
const reinit = replaced.length > 0;
|
|
2881
|
+
if (reinit) {
|
|
2882
|
+
if (!await prompt.consent(consentQuestion(replaced), { token: consentToken(cwd) })) throw errorInitUserAborted();
|
|
2883
|
+
}
|
|
2884
|
+
const writeEnv = flags.writeEnv || await prompt.confirm("Also write a .env file from .env.example? (gitignored)", { default: false });
|
|
2885
|
+
const removePreviousFacade = await resolveRemovePreviousFacade({
|
|
2886
|
+
cwd,
|
|
2887
|
+
target,
|
|
2888
|
+
reinit,
|
|
2889
|
+
keepPreviousFacade: flags.keepPreviousFacade,
|
|
2890
|
+
prompt
|
|
2891
|
+
});
|
|
2892
|
+
return {
|
|
2893
|
+
target,
|
|
2894
|
+
authoring,
|
|
2895
|
+
schemaPath,
|
|
2896
|
+
install: !flags.skipInstall,
|
|
2897
|
+
writeEnv,
|
|
2898
|
+
probeDb: flags.probeDb,
|
|
2899
|
+
strictProbe: flags.strictProbe,
|
|
2900
|
+
reinit,
|
|
2901
|
+
removePreviousFacade,
|
|
2902
|
+
installProjectSkill: !flags.skipSkills
|
|
2903
|
+
};
|
|
2904
|
+
}
|
|
2905
|
+
function metaString(failure, key) {
|
|
2906
|
+
const value = failure.meta?.[key];
|
|
2907
|
+
return typeof value === "string" ? value : "";
|
|
2908
|
+
}
|
|
2909
|
+
/**
|
|
2910
|
+
* pnpm reported a specifier the published artifact leaked, which npm installs
|
|
2911
|
+
* happily. The engine redacts credentials out of the stderr it returns but
|
|
2912
|
+
* leaves error codes alone, which is what this reads.
|
|
2913
|
+
*/
|
|
2914
|
+
function pnpmLeakedASpecifier(failure) {
|
|
2915
|
+
return metaString(failure, "manager") === "pnpm" && isRecognisedPnpmResolutionError(metaString(failure, "stderrTail"));
|
|
2916
|
+
}
|
|
2917
|
+
/**
|
|
2918
|
+
* The engine redacts the stderr it hands back, and this redacts it again
|
|
2919
|
+
* before quoting it: what the engine strips is its own business, and registry
|
|
2920
|
+
* auth material is not all URL-shaped.
|
|
2921
|
+
*/
|
|
2922
|
+
function fallbackWarning(failure) {
|
|
2923
|
+
const firstLine = redactSecrets(metaString(failure, "stderrTail")).trim().split("\n")[0] ?? "";
|
|
2924
|
+
return [
|
|
2925
|
+
"pnpm could not install: a published Prisma Next dependency leaked a `workspace:*` or `catalog:` specifier.",
|
|
2926
|
+
"Falling back to npm so init can complete.",
|
|
2927
|
+
firstLine === "" ? "" : ` pnpm error: ${firstLine}`,
|
|
2928
|
+
"Both installs ran under npm, which writes a package-lock.json beside the pnpm lockfile — delete whichever of the two you do not want to keep.",
|
|
2929
|
+
"Once the offending package republishes a clean version, re-run `pnpm install` to switch back."
|
|
2930
|
+
].filter((line) => line !== "").join("\n");
|
|
2931
|
+
}
|
|
2932
|
+
/** The pnpm failure the npm retry was meant to recover from, kept alongside the npm one. */
|
|
2933
|
+
function retriedWarning(failure) {
|
|
2934
|
+
const firstLine = redactSecrets(metaString(failure, "stderrTail")).trim().split("\n")[0] ?? "";
|
|
2935
|
+
return ["pnpm failed first with a leaked `workspace:*` or `catalog:` specifier, so init retried with npm.", firstLine === "" ? "" : ` pnpm error: ${firstLine}`].filter((line) => line !== "").join("\n");
|
|
2936
|
+
}
|
|
2937
|
+
/**
|
|
2938
|
+
* Adds the runtime and development dependencies through the engine's package
|
|
2939
|
+
* manager. The retry is `init`'s alone: the engine spells and runs the
|
|
2940
|
+
* command, and this decides — from the stderr it returned — that another
|
|
2941
|
+
* manager is worth a try.
|
|
2942
|
+
*/
|
|
2943
|
+
async function installProjectDependencies(ctx) {
|
|
2944
|
+
const pair = async (manager) => {
|
|
2945
|
+
const runtimeDeps = await ctx.packages.install({
|
|
2946
|
+
packages: ctx.deps,
|
|
2947
|
+
cwd: ctx.cwd,
|
|
2948
|
+
...ifDefined("manager", manager)
|
|
2949
|
+
});
|
|
2950
|
+
if (!runtimeDeps.ok) return runtimeDeps.failure;
|
|
2951
|
+
const developmentDeps = await ctx.packages.install({
|
|
2952
|
+
packages: ctx.devDeps,
|
|
2953
|
+
dev: true,
|
|
2954
|
+
cwd: ctx.cwd,
|
|
2955
|
+
...ifDefined("manager", manager)
|
|
2956
|
+
});
|
|
2957
|
+
return developmentDeps.ok ? void 0 : developmentDeps.failure;
|
|
2958
|
+
};
|
|
2959
|
+
const failure = await pair();
|
|
2960
|
+
if (failure === void 0) return {
|
|
2961
|
+
failure: void 0,
|
|
2962
|
+
manager: void 0,
|
|
2963
|
+
warnings: ctx.catalogWarnings
|
|
2964
|
+
};
|
|
2965
|
+
if (!pnpmLeakedASpecifier(failure)) return {
|
|
2966
|
+
failure,
|
|
2967
|
+
manager: void 0,
|
|
2968
|
+
warnings: []
|
|
2969
|
+
};
|
|
2970
|
+
const retryFailure = await pair("npm");
|
|
2971
|
+
if (retryFailure !== void 0) return {
|
|
2972
|
+
failure: retryFailure,
|
|
2973
|
+
manager: void 0,
|
|
2974
|
+
warnings: [retriedWarning(failure)]
|
|
2975
|
+
};
|
|
2976
|
+
return {
|
|
2977
|
+
failure: void 0,
|
|
2978
|
+
manager: "npm",
|
|
2979
|
+
warnings: [fallbackWarning(failure)]
|
|
2980
|
+
};
|
|
2981
|
+
}
|
|
2982
|
+
function skillArgs(url, skill) {
|
|
2983
|
+
return [
|
|
2984
|
+
"add",
|
|
2985
|
+
url,
|
|
2986
|
+
"--agent",
|
|
2987
|
+
...DEFAULT_SKILL_AGENTS,
|
|
2988
|
+
"--skill",
|
|
2989
|
+
skill,
|
|
2990
|
+
"-y"
|
|
2991
|
+
];
|
|
2992
|
+
}
|
|
2993
|
+
/**
|
|
2994
|
+
* Registers every Prisma Next skill with the agent runtimes, in order. The
|
|
2995
|
+
* first failure stops the loop: the user opted into Prisma Next by running
|
|
2996
|
+
* `init`, and a half-installed skill set leaves the project ambiguous.
|
|
2997
|
+
*/
|
|
2998
|
+
async function installAgentSkills(ctx) {
|
|
2999
|
+
for (const source of DEFAULT_SKILL_SOURCES) {
|
|
3000
|
+
const result = await ctx.packages.run({
|
|
3001
|
+
package: "skills@latest",
|
|
3002
|
+
args: skillArgs(formatSkillSourceUrl(source, ctx.env), source.skill),
|
|
3003
|
+
cwd: ctx.cwd,
|
|
3004
|
+
...ifDefined("manager", ctx.manager)
|
|
3005
|
+
});
|
|
3006
|
+
if (!result.ok) return result.failure;
|
|
3007
|
+
}
|
|
3008
|
+
}
|
|
3009
|
+
/** The scaffold is on disk from here on, so each of these is a finding. */
|
|
3010
|
+
const INIT_EXIT_CODES = {
|
|
3011
|
+
4: "scaffold written; dependency install failed",
|
|
3012
|
+
5: "scaffold written and installed; contract emit failed",
|
|
3013
|
+
6: "scaffold complete; agent-skill install failed"
|
|
3014
|
+
};
|
|
3015
|
+
function probeWarning(outcome, strictProbe) {
|
|
3016
|
+
switch (outcome.kind) {
|
|
3017
|
+
case "ok": return {
|
|
3018
|
+
warning: void 0,
|
|
3019
|
+
fatal: void 0
|
|
3020
|
+
};
|
|
3021
|
+
case "below-minimum": return {
|
|
3022
|
+
warning: outcome.message,
|
|
3023
|
+
fatal: void 0
|
|
3024
|
+
};
|
|
3025
|
+
case "no-database-url":
|
|
3026
|
+
case "connection-failed":
|
|
3027
|
+
case "driver-missing": return strictProbe ? {
|
|
3028
|
+
warning: void 0,
|
|
3029
|
+
fatal: outcome.message
|
|
3030
|
+
} : {
|
|
3031
|
+
warning: outcome.message,
|
|
3032
|
+
fatal: void 0
|
|
3033
|
+
};
|
|
3034
|
+
}
|
|
3035
|
+
}
|
|
3036
|
+
function causeMessage(error) {
|
|
3037
|
+
return error instanceof Error ? error.message : String(error);
|
|
3038
|
+
}
|
|
3039
|
+
const createInitCommand = (injected) => defineOrmCommand({
|
|
3040
|
+
help: {
|
|
3041
|
+
summary: "Initialize a new Prisma Next project",
|
|
3042
|
+
description: "Scaffolds config, schema, and runtime files, installs dependencies,\nand emits the contract. Gets you from zero to typed queries in one step.\n\nRun it interactively for a guided setup, or supply --target and --authoring\nfor a fully scriptable run (CI, AI coding agents, automation).",
|
|
3043
|
+
examples: [
|
|
3044
|
+
"init",
|
|
3045
|
+
"init --target postgres --authoring psl",
|
|
3046
|
+
"init --target mongodb --authoring typescript --json",
|
|
3047
|
+
"init --skip-install",
|
|
3048
|
+
"init --skip-skills",
|
|
3049
|
+
"init --target postgres --keep-previous-facade"
|
|
3050
|
+
]
|
|
3051
|
+
},
|
|
3052
|
+
args: { flags: {
|
|
3053
|
+
target: flag.string({
|
|
3054
|
+
brief: "Database target: postgres or mongodb",
|
|
3055
|
+
placeholder: "db"
|
|
3056
|
+
}),
|
|
3057
|
+
authoring: flag.string({
|
|
3058
|
+
brief: "Schema authoring style: psl or typescript",
|
|
3059
|
+
placeholder: "style"
|
|
3060
|
+
}),
|
|
3061
|
+
schemaPath: flag.string({
|
|
3062
|
+
brief: "Where to write the starter schema",
|
|
3063
|
+
placeholder: "path"
|
|
3064
|
+
}),
|
|
3065
|
+
writeEnv: flag.boolean({ brief: "Write a .env file from .env.example (gitignored)" }),
|
|
3066
|
+
probeDb: flag.boolean({ brief: "Connect to DATABASE_URL once and check the server version" }),
|
|
3067
|
+
strictProbe: flag.boolean({ brief: "Treat a failed --probe-db as fatal" }),
|
|
3068
|
+
skipInstall: flag.boolean({ brief: "Skip dependency installation and contract emission" }),
|
|
3069
|
+
skipSkills: flag.boolean({ brief: "Skip the Prisma Next agent-skill install" }),
|
|
3070
|
+
keepPreviousFacade: flag.boolean({ brief: "Keep the previous target package in package.json when switching targets" })
|
|
3071
|
+
} },
|
|
3072
|
+
exitCodes: INIT_EXIT_CODES,
|
|
3073
|
+
installsPackages: true,
|
|
3074
|
+
handler: async (args, ctx) => {
|
|
3075
|
+
const warnings = [];
|
|
3076
|
+
const warn = (text) => {
|
|
3077
|
+
warnings.push(text);
|
|
3078
|
+
ctx.report({
|
|
3079
|
+
kind: "message",
|
|
3080
|
+
severity: "warn",
|
|
3081
|
+
text
|
|
3082
|
+
});
|
|
3083
|
+
};
|
|
3084
|
+
const inputs = await resolveInitInputs({
|
|
3085
|
+
cwd: ctx.cwd,
|
|
3086
|
+
flags: args.flags,
|
|
3087
|
+
prompt: ctx.prompt
|
|
3088
|
+
});
|
|
3089
|
+
const packageManager = await resolveScaffoldPackageManager({
|
|
3090
|
+
cwd: ctx.cwd,
|
|
3091
|
+
env: ctx.env
|
|
3092
|
+
});
|
|
3093
|
+
const scaffold = scaffoldProject({
|
|
3094
|
+
cwd: ctx.cwd,
|
|
3095
|
+
inputs,
|
|
3096
|
+
packageManager
|
|
3097
|
+
});
|
|
3098
|
+
for (const warning of scaffold.warnings) warn(warning);
|
|
3099
|
+
for (const note of scaffold.notes) ctx.report({
|
|
3100
|
+
kind: "message",
|
|
3101
|
+
severity: "info",
|
|
3102
|
+
text: note
|
|
3103
|
+
});
|
|
3104
|
+
const deps = [targetPackageName(inputs.target, scaffold.resolveImportSpecifier), "dotenv"];
|
|
3105
|
+
const devDeps = scaffold.hasTypesNode ? ["prisma-next"] : ["prisma-next", "@types/node"];
|
|
3106
|
+
const findings = [];
|
|
3107
|
+
const extraActions = [];
|
|
3108
|
+
let installedManager;
|
|
3109
|
+
let packagesInstalled = "skipped";
|
|
3110
|
+
let contractEmitted = false;
|
|
3111
|
+
let skillRegistered = false;
|
|
3112
|
+
const settle = (exitCode) => {
|
|
3113
|
+
const installed = packagesInstalled === "installed";
|
|
3114
|
+
const document = {
|
|
3115
|
+
ok: true,
|
|
3116
|
+
target: inputs.target === "mongo" ? "mongodb" : "postgres",
|
|
3117
|
+
authoring: inputs.authoring,
|
|
3118
|
+
schemaPath: inputs.schemaPath,
|
|
3119
|
+
filesWritten: scaffold.filesWritten,
|
|
3120
|
+
filesDeleted: scaffold.filesDeleted,
|
|
3121
|
+
packagesInstalled: {
|
|
3122
|
+
status: packagesInstalled,
|
|
3123
|
+
deps: installed ? deps : [],
|
|
3124
|
+
devDeps: installed ? devDeps : []
|
|
3125
|
+
},
|
|
3126
|
+
contractEmitted,
|
|
3127
|
+
nextSteps: buildNextSteps({
|
|
3128
|
+
target: inputs.target === "mongo" ? "mongodb" : "postgres",
|
|
3129
|
+
packagesInstalled,
|
|
3130
|
+
contractEmitted,
|
|
3131
|
+
emitCommand: EMIT_COMMAND,
|
|
3132
|
+
schemaPath: inputs.schemaPath,
|
|
3133
|
+
skillRegistered
|
|
3134
|
+
}),
|
|
3135
|
+
warnings
|
|
3136
|
+
};
|
|
3137
|
+
const validated = InitOutputSchema(document);
|
|
3138
|
+
if (validated instanceof type.errors) return notOk(new CliStructuredError$1("CLI.INIT_INVALID_OUTPUT_DOCUMENT", "Init produced an invalid output document", {
|
|
3139
|
+
why: `The success document failed schema validation: ${String(validated)}`,
|
|
3140
|
+
nextActions: [chooseAction("This is a bug in prisma-next. Please report it with `-v` output.")],
|
|
3141
|
+
docsUrl: docsUrlFor("CLI.INIT_INVALID_OUTPUT_DOCUMENT")
|
|
3142
|
+
}));
|
|
3143
|
+
return ok(ctx.present({
|
|
3144
|
+
data: document,
|
|
3145
|
+
exitCode,
|
|
3146
|
+
diagnostics: findings
|
|
3147
|
+
}, initPresentations({
|
|
3148
|
+
document,
|
|
3149
|
+
complete: exitCode === 0,
|
|
3150
|
+
nextActions: [...extraActions, ...buildInitNextActions({
|
|
3151
|
+
contractEmitted,
|
|
3152
|
+
schemaPath: inputs.schemaPath,
|
|
3153
|
+
skillRegistered
|
|
3154
|
+
})]
|
|
3155
|
+
})));
|
|
3156
|
+
};
|
|
3157
|
+
if (inputs.install) {
|
|
3158
|
+
const outcome = await installProjectDependencies({
|
|
3159
|
+
packages: ctx.packages,
|
|
3160
|
+
cwd: ctx.cwd,
|
|
3161
|
+
deps,
|
|
3162
|
+
devDeps,
|
|
3163
|
+
catalogWarnings: packageManager === "pnpm" ? buildCatalogWarnings(ctx.cwd, [...deps, ...devDeps]) : []
|
|
3164
|
+
});
|
|
3165
|
+
for (const warning of outcome.warnings) warn(warning);
|
|
3166
|
+
if (outcome.failure !== void 0) {
|
|
3167
|
+
packagesInstalled = "failed";
|
|
3168
|
+
findings.push(installFailedFinding(outcome.failure, scaffold.filesWritten));
|
|
3169
|
+
return settle(4);
|
|
3170
|
+
}
|
|
3171
|
+
packagesInstalled = "installed";
|
|
3172
|
+
installedManager = outcome.manager;
|
|
3173
|
+
const emitStep = "Emit the contract";
|
|
3174
|
+
ctx.report({
|
|
3175
|
+
kind: "step-started",
|
|
3176
|
+
step: emitStep
|
|
3177
|
+
});
|
|
3178
|
+
try {
|
|
3179
|
+
await injected.emitScaffoldedContract({ cwd: ctx.cwd });
|
|
3180
|
+
contractEmitted = true;
|
|
3181
|
+
ctx.report({
|
|
3182
|
+
kind: "step-finished",
|
|
3183
|
+
step: emitStep,
|
|
3184
|
+
outcome: "ok"
|
|
3185
|
+
});
|
|
3186
|
+
} catch (error) {
|
|
3187
|
+
ctx.report({
|
|
3188
|
+
kind: "step-finished",
|
|
3189
|
+
step: emitStep,
|
|
3190
|
+
outcome: "failed"
|
|
3191
|
+
});
|
|
3192
|
+
findings.push(emitFailedFinding(causeMessage(error), scaffold.filesWritten));
|
|
3193
|
+
return settle(5);
|
|
3194
|
+
}
|
|
3195
|
+
} else extraActions.push(chooseAction(`Install the project dependencies with your package manager: ${deps.join(", ")} (and ${devDeps.join(", ")} as development dependencies)`));
|
|
3196
|
+
if (inputs.probeDb) {
|
|
3197
|
+
const probe = probeWarning(await probeServerVersion({
|
|
3198
|
+
baseDir: ctx.cwd,
|
|
3199
|
+
target: inputs.target,
|
|
3200
|
+
databaseUrl: ctx.env["DATABASE_URL"],
|
|
3201
|
+
minVersion: MIN_SERVER_VERSION[inputs.target]
|
|
3202
|
+
}, {}), inputs.strictProbe);
|
|
3203
|
+
if (probe.warning !== void 0) warn(probe.warning);
|
|
3204
|
+
if (probe.fatal !== void 0) return notOk(normalizeError(errorInitProbeFailed({
|
|
3205
|
+
cause: probe.fatal,
|
|
3206
|
+
filesWritten: scaffold.filesWritten
|
|
3207
|
+
})));
|
|
3208
|
+
}
|
|
3209
|
+
const skillCommands = resolveProjectSkillInstallCommands(installedManager ?? packageManager, ctx.env);
|
|
3210
|
+
if (inputs.installProjectSkill) {
|
|
3211
|
+
const failure = await installAgentSkills({
|
|
3212
|
+
packages: ctx.packages,
|
|
3213
|
+
cwd: ctx.cwd,
|
|
3214
|
+
env: ctx.env,
|
|
3215
|
+
manager: installedManager
|
|
3216
|
+
});
|
|
3217
|
+
if (failure !== void 0) {
|
|
3218
|
+
findings.push(skillInstallFailedFinding(failure, scaffold.filesWritten, skillCommands));
|
|
3219
|
+
return settle(6);
|
|
3220
|
+
}
|
|
3221
|
+
skillRegistered = true;
|
|
3222
|
+
} else warn(`Skipped the Prisma Next agent-skill install (--skip-skills). To install them later, run: ${skillCommands.map((command) => `\`${command}\``).join(" && ")}`);
|
|
3223
|
+
return settle(0);
|
|
3224
|
+
}
|
|
3225
|
+
});
|
|
3226
|
+
const initCommand = createInitCommand({ emitScaffoldedContract });
|
|
3227
|
+
/**
|
|
3228
|
+
* R-S5-12 asks every command to end at 143 for SIGTERM and 130 otherwise, and
|
|
3229
|
+
* a server command is the one kind the engine leaves to do it: it settles the
|
|
3230
|
+
* handler's code verbatim, on the reasoning that a protocol conclusion was
|
|
3231
|
+
* never the CLI's to author. The two readings are in tension — this one wins
|
|
3232
|
+
* here, and the tension is recorded for the engine's owners, because it means
|
|
3233
|
+
* a SIGTERM landing after a clean `shutdown` reports 143 for a conversation
|
|
3234
|
+
* that ended correctly at 0.
|
|
3235
|
+
*/
|
|
3236
|
+
function signalExitCode(reason) {
|
|
3237
|
+
return reason === "SIGTERM" ? 143 : 130;
|
|
3238
|
+
}
|
|
3239
|
+
const lspCommand = defineServerCommand({
|
|
3240
|
+
help: {
|
|
3241
|
+
summary: "Start the Prisma Next language server",
|
|
3242
|
+
description: "Launches a Language Server Protocol server that publishes PSL parse diagnostics\nand handles whole-document PSL formatting for the schema inputs declared in\nyour config (contract.source.inputs). Formatting uses the Prisma Next PSL\nformatter and the formatter block from the project config.\nCommunicates over stdio; intended to be spawned by an\neditor, not run interactively. The server keeps running until the editor client\ndisconnects.",
|
|
3243
|
+
examples: ["lsp"]
|
|
3244
|
+
},
|
|
3245
|
+
args: { flags: {
|
|
3246
|
+
stdio: flag.boolean({ brief: "Communicate with the editor over stdio (the default and only transport)" }),
|
|
3247
|
+
clientProcessId: flag.number({
|
|
3248
|
+
brief: "Process id of the editor that spawned the server; the server ends when it dies",
|
|
3249
|
+
placeholder: "pid"
|
|
3250
|
+
})
|
|
3251
|
+
} },
|
|
3252
|
+
handler: async (args, io) => {
|
|
3253
|
+
const exitCode = await withoutClientProcessIdInArgv(async () => {
|
|
3254
|
+
const { startServer } = await import("./exports-B3xY_FHp.mjs");
|
|
3255
|
+
return startServer({
|
|
3256
|
+
stdin: io.stdin,
|
|
3257
|
+
stdout: io.stdout,
|
|
3258
|
+
stderr: io.stderr,
|
|
3259
|
+
signal: io.signal,
|
|
3260
|
+
...ifDefined("clientProcessId", args.flags.clientProcessId)
|
|
3261
|
+
});
|
|
3262
|
+
});
|
|
3263
|
+
return io.signal.aborted ? signalExitCode(io.signal.reason) : exitCode;
|
|
3264
|
+
}
|
|
3265
|
+
});
|
|
3266
|
+
/**
|
|
3267
|
+
* Importing `vscode-languageserver/node` reads `--clientProcessId` off
|
|
3268
|
+
* `process.argv` and arms a parent-watch interval that is never unref'd, so a
|
|
3269
|
+
* settled run would leave the process hanging. The server watches the client
|
|
3270
|
+
* process itself (from the parsed flag), so the argument is hidden for the
|
|
3271
|
+
* import and restored after.
|
|
3272
|
+
*/
|
|
3273
|
+
async function withoutClientProcessIdInArgv(run) {
|
|
3274
|
+
const original = process.argv;
|
|
3275
|
+
const scrubbed = [];
|
|
3276
|
+
for (let index = 0; index < original.length; index += 1) {
|
|
3277
|
+
const token = original[index];
|
|
3278
|
+
if (token === "--clientProcessId") {
|
|
3279
|
+
index += 1;
|
|
3280
|
+
continue;
|
|
3281
|
+
}
|
|
3282
|
+
if (token === void 0 || token.startsWith("--clientProcessId=")) continue;
|
|
3283
|
+
scrubbed.push(token);
|
|
3284
|
+
}
|
|
3285
|
+
process.argv = scrubbed;
|
|
3286
|
+
try {
|
|
3287
|
+
return await run();
|
|
3288
|
+
} finally {
|
|
3289
|
+
process.argv = original;
|
|
3290
|
+
}
|
|
3291
|
+
}
|
|
3292
|
+
const STATUS_ACTION = runCommandAction("Check every space against the database", "prisma-next migration status");
|
|
3293
|
+
/**
|
|
3294
|
+
* The route the migration will take. The graph is a drawing — the engine cannot
|
|
3295
|
+
* derive a lane gutter — and the ordered list under it is drawn with the same
|
|
3296
|
+
* widths so every arrow lands in one column.
|
|
3297
|
+
*/
|
|
3298
|
+
function showPresentations$1(inputs) {
|
|
3299
|
+
const { document, graph, runList } = inputs;
|
|
3300
|
+
const count = document.migrations.length;
|
|
3301
|
+
return {
|
|
3302
|
+
human: () => [
|
|
3303
|
+
{
|
|
3304
|
+
kind: "fields",
|
|
3305
|
+
rail: true,
|
|
3306
|
+
rows: [
|
|
3307
|
+
{
|
|
3308
|
+
label: "migrations",
|
|
3309
|
+
value: inputs.migrationsDir
|
|
3310
|
+
},
|
|
3311
|
+
...inputs.database === void 0 ? [] : [{
|
|
3312
|
+
label: "database",
|
|
3313
|
+
value: inputs.database
|
|
3314
|
+
}],
|
|
3315
|
+
...inputs.from === void 0 ? [] : [{
|
|
3316
|
+
label: "from",
|
|
3317
|
+
value: inputs.from
|
|
3318
|
+
}],
|
|
3319
|
+
...inputs.to === void 0 ? [] : [{
|
|
3320
|
+
label: "to",
|
|
3321
|
+
value: inputs.to
|
|
3322
|
+
}]
|
|
3323
|
+
]
|
|
3324
|
+
},
|
|
3325
|
+
...graph.length === 0 ? [] : [{
|
|
3326
|
+
kind: "drawing",
|
|
3327
|
+
lines: toneDrawing(graph)
|
|
3328
|
+
}],
|
|
3329
|
+
...count === 0 ? [{
|
|
3330
|
+
kind: "summary",
|
|
3331
|
+
status: "info",
|
|
3332
|
+
text: document.summary
|
|
1522
3333
|
}] : [{
|
|
1523
3334
|
kind: "summary",
|
|
1524
3335
|
status: "info",
|
|
@@ -1799,6 +3610,146 @@ function createMigrateCommand(createClient) {
|
|
|
1799
3610
|
}
|
|
1800
3611
|
const migrateCommand = createMigrateCommand(createControlClient);
|
|
1801
3612
|
/**
|
|
3613
|
+
* The check completed and the artifacts it read are not internally consistent.
|
|
3614
|
+
* The findings ride the envelope as diagnostics; the run itself did its job,
|
|
3615
|
+
* which is why this is not an errored settlement.
|
|
3616
|
+
*/
|
|
3617
|
+
const FINDINGS_EXIT_CODE = 4;
|
|
3618
|
+
/**
|
|
3619
|
+
* One integrity failure as an envelope diagnostic. `error` is the honest
|
|
3620
|
+
* severity: nothing about the artifacts is merely worth a look, and exit 4
|
|
3621
|
+
* makes it legal — the engine refuses a severity-`error` diagnostic only on a
|
|
3622
|
+
* run that exits 0.
|
|
3623
|
+
*
|
|
3624
|
+
* The summary leads with the path because the engine's diagnostic renderer
|
|
3625
|
+
* prints `code`, `summary`, `why` and the next actions and never prints
|
|
3626
|
+
* `where`. Several failure texts name no package on their own — a hash
|
|
3627
|
+
* mismatch reads "Stored hash X does not match recomputed hash Y" — so
|
|
3628
|
+
* without the path a project with fifty migrations cannot be told which one
|
|
3629
|
+
* is broken. `where` still carries the path on its own for a `--json`
|
|
3630
|
+
* consumer.
|
|
3631
|
+
*/
|
|
3632
|
+
function failureDiagnostic(failure) {
|
|
3633
|
+
if (!isStructuredErrorCode(failure.code)) throw new InternalError(`migration check produced the failure code "${failure.code}", which is not a dotted NAMESPACE.SUBCODE.`);
|
|
3634
|
+
return {
|
|
3635
|
+
code: failure.code,
|
|
3636
|
+
severity: "error",
|
|
3637
|
+
summary: `${failure.where}: ${failure.why}`,
|
|
3638
|
+
nextActions: failure.nextActions,
|
|
3639
|
+
where: { path: failure.where },
|
|
3640
|
+
meta: { space: failure.space }
|
|
3641
|
+
};
|
|
3642
|
+
}
|
|
3643
|
+
function checkPresentations(inputs) {
|
|
3644
|
+
const summary = inputs.resolvedSpaceId === void 0 ? inputs.document.summary : `${inputs.document.summary} (space: ${inputs.resolvedSpaceId})`;
|
|
3645
|
+
return {
|
|
3646
|
+
human: () => [{
|
|
3647
|
+
kind: "fields",
|
|
3648
|
+
rail: true,
|
|
3649
|
+
rows: [
|
|
3650
|
+
{
|
|
3651
|
+
label: "migrations",
|
|
3652
|
+
value: inputs.migrationsRelative
|
|
3653
|
+
},
|
|
3654
|
+
...inputs.target === void 0 ? [] : [{
|
|
3655
|
+
label: "target",
|
|
3656
|
+
value: inputs.target
|
|
3657
|
+
}],
|
|
3658
|
+
...inputs.space === void 0 ? [] : [{
|
|
3659
|
+
label: "space",
|
|
3660
|
+
value: inputs.space
|
|
3661
|
+
}]
|
|
3662
|
+
]
|
|
3663
|
+
}, {
|
|
3664
|
+
kind: "summary",
|
|
3665
|
+
status: inputs.document.ok ? "ok" : "error",
|
|
3666
|
+
text: summary
|
|
3667
|
+
}],
|
|
3668
|
+
json: () => inputs.document
|
|
3669
|
+
};
|
|
3670
|
+
}
|
|
3671
|
+
const migrationCheckCommand = defineOrmCommand({
|
|
3672
|
+
help: {
|
|
3673
|
+
summary: "Verify artifact and graph integrity",
|
|
3674
|
+
description: "Validates that on-disk migration packages are internally consistent\n(hashes match, manifests are complete) and that the graph is well-formed\n(edges connect, refs point at valid nodes). The whole-graph check spans\nevery contract space by default; pass --space <id> to narrow to one. A\nmigration reference checks a single package, resolved across all contract\nspaces (narrow with --space; an ambiguous reference cannot be checked).\nOffline — does not consult the database.\nExit codes: 0 = all checks passed, 2 = precondition failed (unresolved\ntarget or unknown --space), 4 = integrity failure(s) found.",
|
|
3675
|
+
examples: [
|
|
3676
|
+
"migration check",
|
|
3677
|
+
"migration check --space app",
|
|
3678
|
+
"migration check 20260101-add-users",
|
|
3679
|
+
"migration check 20260101-add-users --space app",
|
|
3680
|
+
"migration check --json"
|
|
3681
|
+
]
|
|
3682
|
+
},
|
|
3683
|
+
args: {
|
|
3684
|
+
positionals: { target: positional.optionalString({
|
|
3685
|
+
brief: "Migration reference: directory name, hash/prefix, ref, or path",
|
|
3686
|
+
placeholder: "target"
|
|
3687
|
+
}) },
|
|
3688
|
+
flags: { space: flag.string({
|
|
3689
|
+
brief: "Narrow output to a single contract space",
|
|
3690
|
+
placeholder: "id"
|
|
3691
|
+
}) }
|
|
3692
|
+
},
|
|
3693
|
+
needs: { config: ormConfigSection },
|
|
3694
|
+
exitCodes: { 4: "integrity check found failures" },
|
|
3695
|
+
handler: async (args, ctx) => {
|
|
3696
|
+
const { target } = args.positionals;
|
|
3697
|
+
const spaceFilter = args.flags.space;
|
|
3698
|
+
const migrationsDir = migrationsDirFor(ctx.config, ctx.cwd);
|
|
3699
|
+
const appMigrationsDir = appMigrationsDirFor(ctx.config, ctx.cwd);
|
|
3700
|
+
const appMigrationsRelative = displayPath(appMigrationsDir, ctx.cwd);
|
|
3701
|
+
const loaded = await buildReadAggregate(ctx.config, { migrationsDir });
|
|
3702
|
+
if (!loaded.ok) return notOk(normalizeError(loaded.failure));
|
|
3703
|
+
const spaces = await enumerateCheckSpaces(loaded.value.aggregate, migrationsDir, ctx.cwd);
|
|
3704
|
+
let document;
|
|
3705
|
+
let resolvedSpaceId;
|
|
3706
|
+
if (target !== void 0) {
|
|
3707
|
+
const outcome = await checkSingleTarget(target, {
|
|
3708
|
+
spaces,
|
|
3709
|
+
...ifDefined("spaceFilter", spaceFilter),
|
|
3710
|
+
appMigrationsDir,
|
|
3711
|
+
appMigrationsRelative,
|
|
3712
|
+
cwd: ctx.cwd
|
|
3713
|
+
});
|
|
3714
|
+
if (outcome.error !== void 0) return notOk(normalizeError(outcome.error));
|
|
3715
|
+
const result = outcome.result;
|
|
3716
|
+
if (result === void 0 || outcome.exitCode === 2) return notOk(normalizeError(errorMigrationPackageNotFound(result?.summary ?? `Migration package for "${target}" not found on disk`)));
|
|
3717
|
+
document = result;
|
|
3718
|
+
resolvedSpaceId = outcome.resolvedSpaceId;
|
|
3719
|
+
} else {
|
|
3720
|
+
const checked = await runMigrationCheck({
|
|
3721
|
+
spaces,
|
|
3722
|
+
...ifDefined("spaceFilter", spaceFilter)
|
|
3723
|
+
});
|
|
3724
|
+
if (!checked.ok) return notOk(normalizeError(checked.failure));
|
|
3725
|
+
const violations = await loadAggregateIntegrityViolations(ctx.config, migrationsDir);
|
|
3726
|
+
const scoped = spaceFilter === void 0 ? violations : violations.filter((violation) => violation.kind !== "disjointness" && violation.spaceId === spaceFilter);
|
|
3727
|
+
const failures = [...checked.value.failures, ...scoped.map((violation) => integrityViolationToCheckFailure(violation, migrationsDir))];
|
|
3728
|
+
document = failures.length === 0 ? {
|
|
3729
|
+
ok: true,
|
|
3730
|
+
failures: [],
|
|
3731
|
+
summary: "All checks passed"
|
|
3732
|
+
} : {
|
|
3733
|
+
ok: false,
|
|
3734
|
+
failures: [...failures],
|
|
3735
|
+
summary: `${failures.length} integrity failure(s)`
|
|
3736
|
+
};
|
|
3737
|
+
}
|
|
3738
|
+
const diagnostics = document.failures.map(failureDiagnostic);
|
|
3739
|
+
return ok(ctx.present({
|
|
3740
|
+
data: document,
|
|
3741
|
+
exitCode: diagnostics.length === 0 ? 0 : FINDINGS_EXIT_CODE,
|
|
3742
|
+
diagnostics
|
|
3743
|
+
}, checkPresentations({
|
|
3744
|
+
document,
|
|
3745
|
+
migrationsRelative: appMigrationsRelative,
|
|
3746
|
+
target,
|
|
3747
|
+
space: spaceFilter,
|
|
3748
|
+
resolvedSpaceId
|
|
3749
|
+
})));
|
|
3750
|
+
}
|
|
3751
|
+
});
|
|
3752
|
+
/**
|
|
1802
3753
|
* The tree is a drawing for a reader; the DOT document is a graph description
|
|
1803
3754
|
* for another program, so it is the one thing here that belongs on stdout.
|
|
1804
3755
|
*/
|
|
@@ -2020,85 +3971,392 @@ function ledgerGrid(entries, options) {
|
|
|
2020
3971
|
})
|
|
2021
3972
|
};
|
|
2022
3973
|
}
|
|
2023
|
-
function logPresentations(inputs) {
|
|
2024
|
-
const grid = inputs.grid;
|
|
3974
|
+
function logPresentations(inputs) {
|
|
3975
|
+
const grid = inputs.grid;
|
|
3976
|
+
return {
|
|
3977
|
+
human: () => [...inputs.database === void 0 ? [] : [{
|
|
3978
|
+
kind: "fields",
|
|
3979
|
+
rail: true,
|
|
3980
|
+
rows: [{
|
|
3981
|
+
label: "database",
|
|
3982
|
+
value: inputs.database
|
|
3983
|
+
}]
|
|
3984
|
+
}], ...grid === void 0 ? [{
|
|
3985
|
+
kind: "summary",
|
|
3986
|
+
status: "info",
|
|
3987
|
+
text: MIGRATION_LOG_EMPTY_MESSAGE
|
|
3988
|
+
}] : [{
|
|
3989
|
+
kind: "table",
|
|
3990
|
+
columns: grid.headings,
|
|
3991
|
+
rows: grid.rows
|
|
3992
|
+
}]],
|
|
3993
|
+
json: () => inputs.document
|
|
3994
|
+
};
|
|
3995
|
+
}
|
|
3996
|
+
const migrationLogCommand = defineOrmCommand({
|
|
3997
|
+
help: {
|
|
3998
|
+
summary: "Show executed migration history",
|
|
3999
|
+
description: "Reads the database ledger and displays every applied migration edge in\nchronological order, including rollbacks and re-applies, merged across\nall contract spaces. Requires a database connection.",
|
|
4000
|
+
examples: [
|
|
4001
|
+
"migration log",
|
|
4002
|
+
"migration log --db $DATABASE_URL",
|
|
4003
|
+
"migration log --utc",
|
|
4004
|
+
"migration log --json"
|
|
4005
|
+
]
|
|
4006
|
+
},
|
|
4007
|
+
args: { flags: {
|
|
4008
|
+
db: dbFlag,
|
|
4009
|
+
utc: flag.boolean({ brief: "Render human timestamps in UTC instead of local time" }),
|
|
4010
|
+
ascii: flag.boolean({ brief: "Use ASCII glyphs (pipe-friendly)" })
|
|
4011
|
+
} },
|
|
4012
|
+
needs: { config: ormConfigSection },
|
|
4013
|
+
handler: async (args, ctx) => {
|
|
4014
|
+
const dbConnection = args.flags.db ?? ctx.config.db?.connection;
|
|
4015
|
+
const missingDb = requireLiveDatabase({
|
|
4016
|
+
dbConnection,
|
|
4017
|
+
hasDriver: ctx.config.driver !== void 0,
|
|
4018
|
+
why: "migration log needs a database connection and driver to read the ledger (set db.connection in prisma-next.config.ts, or pass --db <url>)",
|
|
4019
|
+
commandName: "migration log"
|
|
4020
|
+
});
|
|
4021
|
+
if (missingDb !== null) return notOk(normalizeError(missingDb));
|
|
4022
|
+
if (!targetSupportsMigrations(ctx.config.target)) return notOk(normalizeError(errorTargetMigrationNotSupported({ why: "migration log reads the ledger a migration runner writes, and the configured target provides no runner." })));
|
|
4023
|
+
const client = createControlClient({
|
|
4024
|
+
family: ctx.config.family,
|
|
4025
|
+
target: ctx.config.target,
|
|
4026
|
+
adapter: ctx.config.adapter,
|
|
4027
|
+
...ifDefined("driver", ctx.config.driver),
|
|
4028
|
+
extensions: ctx.config.extensions ?? []
|
|
4029
|
+
});
|
|
4030
|
+
let entries;
|
|
4031
|
+
try {
|
|
4032
|
+
await client.connect(dbConnection);
|
|
4033
|
+
entries = await client.readLedger();
|
|
4034
|
+
} catch (error) {
|
|
4035
|
+
return notOk(normalizeError(mapCaughtMigrationError(error) ?? errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read migration log: ${error instanceof Error ? error.message : String(error)}` })));
|
|
4036
|
+
} finally {
|
|
4037
|
+
await closeQuietly(client);
|
|
4038
|
+
}
|
|
4039
|
+
const records = serializeLedgerEntriesForJson(entries);
|
|
4040
|
+
const document = {
|
|
4041
|
+
ok: true,
|
|
4042
|
+
records,
|
|
4043
|
+
summary: `${records.length} migration(s) applied`
|
|
4044
|
+
};
|
|
4045
|
+
const glyphMode = args.flags.ascii ? "ascii" : "unicode";
|
|
4046
|
+
return ok(ctx.present({ data: document }, logPresentations({
|
|
4047
|
+
document,
|
|
4048
|
+
grid: entries.length === 0 ? void 0 : ledgerGrid(entries, {
|
|
4049
|
+
utc: args.flags.utc,
|
|
4050
|
+
glyphMode
|
|
4051
|
+
}),
|
|
4052
|
+
database: typeof dbConnection === "string" ? maskConnectionUrl(dbConnection) : void 0
|
|
4053
|
+
})));
|
|
4054
|
+
}
|
|
4055
|
+
});
|
|
4056
|
+
function newPresentations(inputs) {
|
|
4057
|
+
const { document } = inputs;
|
|
4058
|
+
const migrationTs = join(document.dir, "migration.ts");
|
|
4059
|
+
return {
|
|
4060
|
+
human: () => [
|
|
4061
|
+
{
|
|
4062
|
+
kind: "fields",
|
|
4063
|
+
rail: true,
|
|
4064
|
+
rows: [{
|
|
4065
|
+
label: "contract",
|
|
4066
|
+
value: inputs.contractPath
|
|
4067
|
+
}, {
|
|
4068
|
+
label: "migrations",
|
|
4069
|
+
value: inputs.appMigrationsRelative
|
|
4070
|
+
}]
|
|
4071
|
+
},
|
|
4072
|
+
{
|
|
4073
|
+
kind: "summary",
|
|
4074
|
+
status: "ok",
|
|
4075
|
+
text: document.summary
|
|
4076
|
+
},
|
|
4077
|
+
{
|
|
4078
|
+
kind: "fields",
|
|
4079
|
+
rows: [{
|
|
4080
|
+
label: "from",
|
|
4081
|
+
value: document.from === null ? [{
|
|
4082
|
+
text: "(baseline)",
|
|
4083
|
+
tone: "muted"
|
|
4084
|
+
}] : [{
|
|
4085
|
+
text: document.from,
|
|
4086
|
+
tone: "identifier"
|
|
4087
|
+
}]
|
|
4088
|
+
}, {
|
|
4089
|
+
label: "to",
|
|
4090
|
+
value: [{
|
|
4091
|
+
text: document.to,
|
|
4092
|
+
tone: "identifier"
|
|
4093
|
+
}]
|
|
4094
|
+
}]
|
|
4095
|
+
}
|
|
4096
|
+
],
|
|
4097
|
+
json: () => document,
|
|
4098
|
+
next: () => [{
|
|
4099
|
+
kind: "edit-file",
|
|
4100
|
+
label: `Write the migration body in ${migrationTs}`
|
|
4101
|
+
}, runCommandAction("Run it to self-emit ops.json and attest the package", `node "${migrationTs}"`)]
|
|
4102
|
+
};
|
|
4103
|
+
}
|
|
4104
|
+
const migrationNewCommand = defineOrmCommand({
|
|
4105
|
+
help: {
|
|
4106
|
+
summary: "Scaffold a new migration for manual authoring",
|
|
4107
|
+
description: "Creates a migration package with a migration.ts file for manual authoring.\nWrite the migration body in migration.ts, then run the file with Node\n(`node migration.ts`) to self-emit ops.json and attest the package.\nOffline — does not consult the database.",
|
|
4108
|
+
examples: [
|
|
4109
|
+
"migration new --name split-name",
|
|
4110
|
+
"migration new --name custom-fk --from abc123",
|
|
4111
|
+
"migration new --json"
|
|
4112
|
+
]
|
|
4113
|
+
},
|
|
4114
|
+
args: { flags: {
|
|
4115
|
+
name: flag.string({
|
|
4116
|
+
brief: "Migration name (used in directory name)",
|
|
4117
|
+
placeholder: "slug"
|
|
4118
|
+
}),
|
|
4119
|
+
from: flag.string({
|
|
4120
|
+
brief: "Starting contract hash (default: latest migration target)",
|
|
4121
|
+
placeholder: "hash"
|
|
4122
|
+
})
|
|
4123
|
+
} },
|
|
4124
|
+
needs: { config: ormConfigSection },
|
|
4125
|
+
handler: async (args, ctx) => {
|
|
4126
|
+
const scaffolded = await executeMigrationNewCommand({
|
|
4127
|
+
config: ctx.config,
|
|
4128
|
+
cwd: ctx.cwd,
|
|
4129
|
+
configPath: projectConfigPathFor(ctx.cwd),
|
|
4130
|
+
...ifDefined("name", args.flags.name),
|
|
4131
|
+
...ifDefined("from", args.flags.from)
|
|
4132
|
+
});
|
|
4133
|
+
if (!scaffolded.ok) return notOk(normalizeError(scaffolded.failure));
|
|
4134
|
+
const contractPath = contractPathFor(ctx.config, ctx.cwd);
|
|
4135
|
+
return ok(ctx.present({ data: scaffolded.value }, newPresentations({
|
|
4136
|
+
document: scaffolded.value,
|
|
4137
|
+
contractPath: contractPath === void 0 ? "(unset)" : displayPath(contractPath, ctx.cwd),
|
|
4138
|
+
appMigrationsRelative: displayPath(appMigrationsDirFor(ctx.config, ctx.cwd), ctx.cwd)
|
|
4139
|
+
})));
|
|
4140
|
+
}
|
|
4141
|
+
});
|
|
4142
|
+
function hashRow(label, hash) {
|
|
4143
|
+
return {
|
|
4144
|
+
label,
|
|
4145
|
+
value: hash === null ? [{
|
|
4146
|
+
text: "(baseline)",
|
|
4147
|
+
tone: "muted"
|
|
4148
|
+
}] : [{
|
|
4149
|
+
text: hash,
|
|
4150
|
+
tone: "identifier"
|
|
4151
|
+
}]
|
|
4152
|
+
};
|
|
4153
|
+
}
|
|
4154
|
+
/**
|
|
4155
|
+
* Where the run put things: the contract edge it planned, then every directory
|
|
4156
|
+
* it wrote — the app-space package, the auto-baseline package that precedes it,
|
|
4157
|
+
* and any extension-space package the seed phase materialised.
|
|
4158
|
+
*/
|
|
4159
|
+
function outcomeFields(result, migrationsRelative) {
|
|
4160
|
+
return {
|
|
4161
|
+
kind: "fields",
|
|
4162
|
+
rows: [
|
|
4163
|
+
hashRow("from", result.from),
|
|
4164
|
+
hashRow("to", result.to),
|
|
4165
|
+
...result.baselineDir === void 0 ? [] : [{
|
|
4166
|
+
label: "baseline",
|
|
4167
|
+
value: result.baselineDir
|
|
4168
|
+
}],
|
|
4169
|
+
...result.dir === void 0 ? [] : [{
|
|
4170
|
+
label: "app space",
|
|
4171
|
+
value: result.dir
|
|
4172
|
+
}],
|
|
4173
|
+
...result.emittedExtensionDirs.map((entry) => ({
|
|
4174
|
+
label: `space ${entry.spaceId}`,
|
|
4175
|
+
value: join(migrationsRelative, entry.spaceId, entry.dirName)
|
|
4176
|
+
}))
|
|
4177
|
+
]
|
|
4178
|
+
};
|
|
4179
|
+
}
|
|
4180
|
+
function operationNodes$1(result) {
|
|
4181
|
+
return result.operations.map((operation) => operation.operationClass === "destructive" ? {
|
|
4182
|
+
label: operation.label,
|
|
4183
|
+
status: "warn"
|
|
4184
|
+
} : { label: operation.label });
|
|
4185
|
+
}
|
|
4186
|
+
function operationBlocks$1(result) {
|
|
4187
|
+
if (result.operations.length === 0) return [];
|
|
4188
|
+
const destructive = result.operations.some((operation) => operation.operationClass === "destructive");
|
|
4189
|
+
return [{
|
|
4190
|
+
kind: "tree",
|
|
4191
|
+
roots: [{
|
|
4192
|
+
label: result.dir ?? "operations",
|
|
4193
|
+
children: operationNodes$1(result)
|
|
4194
|
+
}]
|
|
4195
|
+
}, ...destructive ? [{
|
|
4196
|
+
kind: "summary",
|
|
4197
|
+
status: "warn",
|
|
4198
|
+
text: "This migration contains destructive operations that may cause data loss."
|
|
4199
|
+
}] : []];
|
|
4200
|
+
}
|
|
4201
|
+
/** Statements a database would run, printed verbatim rather than laid out. */
|
|
4202
|
+
function previewBlocks$1(result) {
|
|
4203
|
+
const preview = result.preview;
|
|
4204
|
+
if (preview === void 0) return [];
|
|
4205
|
+
const statements = preview.statements.map((statement) => statement.text.trim()).filter((text) => text.length > 0).map((text) => text.endsWith(";") ? text : `${text};`);
|
|
4206
|
+
if (statements.length === 0) return [];
|
|
4207
|
+
return [{
|
|
4208
|
+
kind: "summary",
|
|
4209
|
+
status: "info",
|
|
4210
|
+
tone: "muted",
|
|
4211
|
+
text: previewBlockHeader(preview)
|
|
4212
|
+
}, {
|
|
4213
|
+
kind: "drawing",
|
|
4214
|
+
lines: statements
|
|
4215
|
+
}];
|
|
4216
|
+
}
|
|
4217
|
+
function planBlocks(result, migrationsRelative) {
|
|
4218
|
+
const outcome = outcomeFields(result, migrationsRelative);
|
|
4219
|
+
if (result.noOp) return [{
|
|
4220
|
+
kind: "summary",
|
|
4221
|
+
status: "ok",
|
|
4222
|
+
text: "No changes detected"
|
|
4223
|
+
}, outcome];
|
|
4224
|
+
if (result.pendingPlaceholders === true) return [{
|
|
4225
|
+
kind: "summary",
|
|
4226
|
+
status: "warn",
|
|
4227
|
+
text: result.summary
|
|
4228
|
+
}, outcome];
|
|
4229
|
+
return [
|
|
4230
|
+
{
|
|
4231
|
+
kind: "summary",
|
|
4232
|
+
status: "ok",
|
|
4233
|
+
text: result.summary
|
|
4234
|
+
},
|
|
4235
|
+
...operationBlocks$1(result),
|
|
4236
|
+
outcome,
|
|
4237
|
+
...previewBlocks$1(result)
|
|
4238
|
+
];
|
|
4239
|
+
}
|
|
4240
|
+
function planNextActions(result, migrationsRelative) {
|
|
4241
|
+
if (result.pendingPlaceholders === true) {
|
|
4242
|
+
const migrationTs = join(result.dir ?? "<dir>", "migration.ts");
|
|
4243
|
+
return [{
|
|
4244
|
+
kind: "edit-file",
|
|
4245
|
+
label: `Replace each placeholder(...) call in ${migrationTs} with your query`
|
|
4246
|
+
}, runCommandAction("Run it to self-emit ops.json and attest the package", `node "${migrationTs}"`)];
|
|
4247
|
+
}
|
|
4248
|
+
const written = [
|
|
4249
|
+
...result.baselineDir === void 0 ? [] : [result.baselineDir],
|
|
4250
|
+
...result.dir === void 0 ? [] : [result.dir],
|
|
4251
|
+
...result.emittedExtensionDirs.map((entry) => join(migrationsRelative, entry.spaceId, entry.dirName))
|
|
4252
|
+
];
|
|
4253
|
+
if (written.length === 0) return [];
|
|
4254
|
+
return [{
|
|
4255
|
+
kind: "edit-file",
|
|
4256
|
+
label: `Review ${written.join(" and ")}`
|
|
4257
|
+
}, runCommandAction("Apply the migration", "prisma-next migrate")];
|
|
4258
|
+
}
|
|
4259
|
+
function planPresentations(inputs) {
|
|
2025
4260
|
return {
|
|
2026
|
-
human: () => [
|
|
4261
|
+
human: () => [{
|
|
2027
4262
|
kind: "fields",
|
|
2028
4263
|
rail: true,
|
|
2029
|
-
rows: [
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
4264
|
+
rows: [
|
|
4265
|
+
{
|
|
4266
|
+
label: "contract",
|
|
4267
|
+
value: inputs.contractPath
|
|
4268
|
+
},
|
|
4269
|
+
{
|
|
4270
|
+
label: "migrations",
|
|
4271
|
+
value: inputs.appMigrationsRelative
|
|
4272
|
+
},
|
|
4273
|
+
...inputs.from === void 0 ? [] : [{
|
|
4274
|
+
label: "from",
|
|
4275
|
+
value: inputs.from
|
|
4276
|
+
}],
|
|
4277
|
+
...inputs.to === void 0 ? [] : [{
|
|
4278
|
+
label: "to",
|
|
4279
|
+
value: inputs.to
|
|
4280
|
+
}],
|
|
4281
|
+
...inputs.name === void 0 ? [] : [{
|
|
4282
|
+
label: "name",
|
|
4283
|
+
value: inputs.name
|
|
4284
|
+
}]
|
|
4285
|
+
]
|
|
4286
|
+
}, ...planBlocks(inputs.document, inputs.migrationsRelative)],
|
|
4287
|
+
json: () => inputs.document,
|
|
4288
|
+
next: () => planNextActions(inputs.document, inputs.migrationsRelative)
|
|
2043
4289
|
};
|
|
2044
4290
|
}
|
|
2045
|
-
const
|
|
4291
|
+
const migrationPlanCommand = defineOrmCommand({
|
|
2046
4292
|
help: {
|
|
2047
|
-
summary: "
|
|
2048
|
-
description: "
|
|
4293
|
+
summary: "Plan a migration from contract changes",
|
|
4294
|
+
description: "Compares the emitted contract against the latest on-disk migration state\nand produces a new migration package with the required operations.\nOffline — does not consult the database.",
|
|
2049
4295
|
examples: [
|
|
2050
|
-
"migration
|
|
2051
|
-
"migration
|
|
2052
|
-
"migration
|
|
2053
|
-
"migration
|
|
4296
|
+
"migration plan",
|
|
4297
|
+
"migration plan --name add-users-table",
|
|
4298
|
+
"migration plan --to <migration-dir>^ --name rollback",
|
|
4299
|
+
"migration plan --json"
|
|
2054
4300
|
]
|
|
2055
4301
|
},
|
|
2056
4302
|
args: { flags: {
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
4303
|
+
name: flag.string({
|
|
4304
|
+
brief: "Name slug for the migration directory",
|
|
4305
|
+
placeholder: "slug"
|
|
4306
|
+
}),
|
|
4307
|
+
from: flag.string({
|
|
4308
|
+
brief: "Starting contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)",
|
|
4309
|
+
placeholder: "contract"
|
|
4310
|
+
}),
|
|
4311
|
+
to: flag.string({
|
|
4312
|
+
brief: "Destination contract reference; defaults to the emitted contract. Same grammar as --from",
|
|
4313
|
+
placeholder: "contract"
|
|
4314
|
+
})
|
|
2060
4315
|
} },
|
|
2061
4316
|
needs: { config: ormConfigSection },
|
|
2062
4317
|
handler: async (args, ctx) => {
|
|
2063
|
-
const
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
await client.connect(dbConnection);
|
|
2082
|
-
entries = await client.readLedger();
|
|
2083
|
-
} catch (error) {
|
|
2084
|
-
return notOk(normalizeError(mapCaughtMigrationError(error) ?? errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read migration log: ${error instanceof Error ? error.message : String(error)}` })));
|
|
2085
|
-
} finally {
|
|
2086
|
-
await closeQuietly(client);
|
|
2087
|
-
}
|
|
2088
|
-
const records = serializeLedgerEntriesForJson(entries);
|
|
2089
|
-
const document = {
|
|
2090
|
-
ok: true,
|
|
2091
|
-
records,
|
|
2092
|
-
summary: `${records.length} migration(s) applied`
|
|
4318
|
+
const seeded = (record) => {
|
|
4319
|
+
if (record.action !== "updated") return;
|
|
4320
|
+
const step = `Seed contract space ${record.spaceId}`;
|
|
4321
|
+
ctx.report({
|
|
4322
|
+
kind: "step-started",
|
|
4323
|
+
step,
|
|
4324
|
+
id: record.spaceId
|
|
4325
|
+
});
|
|
4326
|
+
ctx.report({
|
|
4327
|
+
kind: "step-finished",
|
|
4328
|
+
step,
|
|
4329
|
+
id: record.spaceId,
|
|
4330
|
+
outcome: "ok",
|
|
4331
|
+
data: {
|
|
4332
|
+
newHash: record.newHash,
|
|
4333
|
+
newMigrationDirs: record.newMigrationDirs
|
|
4334
|
+
}
|
|
4335
|
+
});
|
|
2093
4336
|
};
|
|
2094
|
-
const
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
4337
|
+
const planned = await executeMigrationPlanCommand({
|
|
4338
|
+
config: ctx.config,
|
|
4339
|
+
cwd: ctx.cwd,
|
|
4340
|
+
configPath: projectConfigPathFor(ctx.cwd),
|
|
4341
|
+
...ifDefined("name", args.flags.name),
|
|
4342
|
+
...ifDefined("from", args.flags.from),
|
|
4343
|
+
...ifDefined("to", args.flags.to)
|
|
4344
|
+
}, Date.now(), { onSeeded: seeded });
|
|
4345
|
+
if (!planned.ok) return notOk(normalizeError(planned.failure));
|
|
4346
|
+
ctx.report({
|
|
4347
|
+
kind: "message",
|
|
4348
|
+
severity: "verbose",
|
|
4349
|
+
text: `Total time: ${planned.value.timings.total}ms`
|
|
4350
|
+
});
|
|
4351
|
+
const contractPath = contractPathFor(ctx.config, ctx.cwd);
|
|
4352
|
+
return ok(ctx.present({ data: planned.value }, planPresentations({
|
|
4353
|
+
document: planned.value,
|
|
4354
|
+
contractPath: contractPath === void 0 ? "(unset)" : displayPath(contractPath, ctx.cwd),
|
|
4355
|
+
appMigrationsRelative: displayPath(appMigrationsDirFor(ctx.config, ctx.cwd), ctx.cwd),
|
|
4356
|
+
migrationsRelative: displayPath(migrationsDirFor(ctx.config, ctx.cwd), ctx.cwd),
|
|
4357
|
+
from: args.flags.from,
|
|
4358
|
+
to: args.flags.to,
|
|
4359
|
+
name: args.flags.name
|
|
2102
4360
|
})));
|
|
2103
4361
|
}
|
|
2104
4362
|
});
|
|
@@ -2336,6 +4594,426 @@ const migrationShowCommand = defineOrmCommand({
|
|
|
2336
4594
|
})));
|
|
2337
4595
|
}
|
|
2338
4596
|
});
|
|
4597
|
+
const EMIT_CONTRACT = runCommandAction("Regenerate the contract", "prisma-next contract emit");
|
|
4598
|
+
function contractUnreadableFinding(reason) {
|
|
4599
|
+
const message = `Could not read contract: ${reason}`;
|
|
4600
|
+
return {
|
|
4601
|
+
document: {
|
|
4602
|
+
code: "CONTRACT.UNREADABLE",
|
|
4603
|
+
severity: "warn",
|
|
4604
|
+
message,
|
|
4605
|
+
hints: ["Run 'prisma-next contract emit' to generate a valid contract"]
|
|
4606
|
+
},
|
|
4607
|
+
diagnostic: {
|
|
4608
|
+
code: "CONTRACT.UNREADABLE",
|
|
4609
|
+
severity: "warn",
|
|
4610
|
+
summary: message,
|
|
4611
|
+
why: "The status tree falls back to the on-disk migration graph when the emitted contract cannot be read.",
|
|
4612
|
+
nextActions: [EMIT_CONTRACT]
|
|
4613
|
+
}
|
|
4614
|
+
};
|
|
4615
|
+
}
|
|
4616
|
+
function markerNotInHistoryFinding(space) {
|
|
4617
|
+
const message = `Database was updated outside the migration system (marker for space "${space}" does not match any migration)`;
|
|
4618
|
+
return {
|
|
4619
|
+
document: {
|
|
4620
|
+
code: "MIGRATION.MARKER_NOT_IN_HISTORY",
|
|
4621
|
+
severity: "warn",
|
|
4622
|
+
message,
|
|
4623
|
+
hints: ["Run 'prisma-next db sign' to overwrite the marker if the database already matches the contract", "Run 'prisma-next db update' to push the current contract to the database"]
|
|
4624
|
+
},
|
|
4625
|
+
diagnostic: {
|
|
4626
|
+
code: "MIGRATION.MARKER_NOT_IN_HISTORY",
|
|
4627
|
+
severity: "warn",
|
|
4628
|
+
summary: message,
|
|
4629
|
+
why: "The marker the database carries names no contract in the on-disk migration graph.",
|
|
4630
|
+
meta: { space },
|
|
4631
|
+
nextActions: [runCommandAction("Overwrite the marker if the database already matches the contract", "prisma-next db sign"), runCommandAction("Or push the current contract to the database", "prisma-next db update")]
|
|
4632
|
+
}
|
|
4633
|
+
};
|
|
4634
|
+
}
|
|
4635
|
+
function missingInvariantsFinding(inputs) {
|
|
4636
|
+
const message = `missing invariant(s): ${inputs.missing.join(", ")}`;
|
|
4637
|
+
return {
|
|
4638
|
+
document: {
|
|
4639
|
+
code: "MIGRATION.MISSING_INVARIANTS",
|
|
4640
|
+
severity: "warn",
|
|
4641
|
+
...ifDefined("ref", inputs.refName),
|
|
4642
|
+
invariants: [...inputs.missing],
|
|
4643
|
+
message
|
|
4644
|
+
},
|
|
4645
|
+
diagnostic: {
|
|
4646
|
+
code: "MIGRATION.MISSING_INVARIANTS",
|
|
4647
|
+
severity: "warn",
|
|
4648
|
+
summary: message,
|
|
4649
|
+
why: inputs.refName === void 0 ? "The database marker does not carry every invariant the target requires." : `The database marker does not carry every invariant \`${inputs.refName}\` requires.`,
|
|
4650
|
+
nextActions: [runCommandAction("Apply the migrations that provide them", "prisma-next migrate")],
|
|
4651
|
+
meta: {
|
|
4652
|
+
invariants: [...inputs.missing],
|
|
4653
|
+
...ifDefined("ref", inputs.refName)
|
|
4654
|
+
}
|
|
4655
|
+
}
|
|
4656
|
+
};
|
|
4657
|
+
}
|
|
4658
|
+
const SHORT_HASH_LENGTH = 12;
|
|
4659
|
+
function shortDisplayHash(hash) {
|
|
4660
|
+
return hash.slice(0, SHORT_HASH_LENGTH);
|
|
4661
|
+
}
|
|
4662
|
+
const NO_DATABASE_STATE = {
|
|
4663
|
+
markersBySpace: /* @__PURE__ */ new Map(),
|
|
4664
|
+
ledgersBySpace: /* @__PURE__ */ new Map()
|
|
4665
|
+
};
|
|
4666
|
+
async function readDatabaseState(inputs) {
|
|
4667
|
+
const { client } = inputs;
|
|
4668
|
+
try {
|
|
4669
|
+
await client.connect(inputs.connection);
|
|
4670
|
+
const markersBySpace = new Map(await client.readAllMarkers());
|
|
4671
|
+
const ledgersBySpace = /* @__PURE__ */ new Map();
|
|
4672
|
+
for (const spaceId of inputs.spaceIds) ledgersBySpace.set(spaceId, await client.readLedger(spaceId));
|
|
4673
|
+
return ok({
|
|
4674
|
+
markersBySpace,
|
|
4675
|
+
ledgersBySpace
|
|
4676
|
+
});
|
|
4677
|
+
} catch (error) {
|
|
4678
|
+
return notOk(normalizeError(CliStructuredError$1.is(error) ? error : errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read database state: ${error instanceof Error ? error.message : String(error)}` })));
|
|
4679
|
+
} finally {
|
|
4680
|
+
await closeQuietly(client);
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
function buildNoPathSummary(args) {
|
|
4684
|
+
const markerPart = args.markerHash !== void 0 ? `the database state (${shortDisplayHash(args.markerHash)})` : "the database state";
|
|
4685
|
+
const targetShort = shortDisplayHash(args.targetHash);
|
|
4686
|
+
if (!args.explicitTarget) return `No migration path from ${markerPart} to the application's contract (${targetShort}). Run \`prisma-next migration plan --name <name>\` to author one.`;
|
|
4687
|
+
return `No migration path from ${markerPart} to ${args.refName !== void 0 ? `the target (${targetShort} via \`${args.refName}\`)` : `the target (${targetShort})`}. Run \`prisma-next migration plan --name <name>\` to author one, or pass \`--to <contract>\` to pick a reachable target.`;
|
|
4688
|
+
}
|
|
4689
|
+
function buildStatusHeadline(args) {
|
|
4690
|
+
if (args.markerDiverged && args.markerHash !== void 0) return `Database marker ${shortDisplayHash(args.markerHash)} is not in the on-disk migration graph`;
|
|
4691
|
+
if (args.pendingCount === 0) return "Up to date";
|
|
4692
|
+
return `${args.pendingCount} pending — run \`prisma-next migrate --to ${shortDisplayHash(args.targetHash)}\``;
|
|
4693
|
+
}
|
|
4694
|
+
/**
|
|
4695
|
+
* One space's section: its heading, where more than one space is in scope, on
|
|
4696
|
+
* top of its tree. Heading and tree are one drawing so the heading stays a
|
|
4697
|
+
* bare label — a `summary` would put a status glyph in front of it.
|
|
4698
|
+
*/
|
|
4699
|
+
function sectionBlock(section) {
|
|
4700
|
+
const heading = section.showHeading ? [[{
|
|
4701
|
+
text: `${section.space}:`,
|
|
4702
|
+
tone: "heading"
|
|
4703
|
+
}]] : [];
|
|
4704
|
+
const body = section.tree.length === 0 ? [[{
|
|
4705
|
+
text: "(no migrations)",
|
|
4706
|
+
tone: "muted"
|
|
4707
|
+
}]] : toneDrawing(section.tree);
|
|
4708
|
+
return {
|
|
4709
|
+
kind: "drawing",
|
|
4710
|
+
lines: [...heading, ...body]
|
|
4711
|
+
};
|
|
4712
|
+
}
|
|
4713
|
+
function statusPresentations(inputs) {
|
|
4714
|
+
const legend = inputs.legend;
|
|
4715
|
+
return {
|
|
4716
|
+
human: () => [
|
|
4717
|
+
{
|
|
4718
|
+
kind: "fields",
|
|
4719
|
+
rail: true,
|
|
4720
|
+
rows: [
|
|
4721
|
+
{
|
|
4722
|
+
label: "migrations",
|
|
4723
|
+
value: inputs.migrationsDir
|
|
4724
|
+
},
|
|
4725
|
+
...inputs.database === void 0 ? [] : [{
|
|
4726
|
+
label: "database",
|
|
4727
|
+
value: inputs.database
|
|
4728
|
+
}],
|
|
4729
|
+
...inputs.ref === void 0 ? [] : [{
|
|
4730
|
+
label: "ref",
|
|
4731
|
+
value: inputs.ref
|
|
4732
|
+
}],
|
|
4733
|
+
...inputs.from === void 0 ? [] : [{
|
|
4734
|
+
label: "from",
|
|
4735
|
+
value: inputs.from
|
|
4736
|
+
}],
|
|
4737
|
+
...inputs.space === void 0 ? [] : [{
|
|
4738
|
+
label: "space",
|
|
4739
|
+
value: inputs.space
|
|
4740
|
+
}]
|
|
4741
|
+
]
|
|
4742
|
+
},
|
|
4743
|
+
...legend === void 0 ? [] : [{
|
|
4744
|
+
kind: "drawing",
|
|
4745
|
+
lines: toneDrawing(legend)
|
|
4746
|
+
}],
|
|
4747
|
+
...inputs.sections.map(sectionBlock),
|
|
4748
|
+
inputs.headline
|
|
4749
|
+
],
|
|
4750
|
+
json: () => inputs.document
|
|
4751
|
+
};
|
|
4752
|
+
}
|
|
4753
|
+
const migrationStatusCommand = defineOrmCommand({
|
|
4754
|
+
help: {
|
|
4755
|
+
summary: "Show migration path and pending status",
|
|
4756
|
+
description: "Shows which migrations are pending between the database marker and the\ntarget contract. Requires a database connection. Pass --from for an\noffline path preview without a database. Use `migration graph` for\ntopology, `migration log` for history, and `migration list` for on-disk\nenumeration.",
|
|
4757
|
+
examples: [
|
|
4758
|
+
"migration status",
|
|
4759
|
+
"migration status --db $DATABASE_URL",
|
|
4760
|
+
"migration status --to production",
|
|
4761
|
+
"migration status --from abc123 --to production",
|
|
4762
|
+
"migration status --ascii",
|
|
4763
|
+
"migration status --legend"
|
|
4764
|
+
]
|
|
4765
|
+
},
|
|
4766
|
+
args: { flags: {
|
|
4767
|
+
db: dbFlag,
|
|
4768
|
+
space: flag.string({
|
|
4769
|
+
brief: "Narrow output to a single contract space",
|
|
4770
|
+
placeholder: "id"
|
|
4771
|
+
}),
|
|
4772
|
+
to: flag.string({
|
|
4773
|
+
brief: "Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)",
|
|
4774
|
+
placeholder: "contract"
|
|
4775
|
+
}),
|
|
4776
|
+
from: flag.string({
|
|
4777
|
+
brief: "Origin contract reference; same grammar as --to. Supplying it switches to offline path computation",
|
|
4778
|
+
placeholder: "contract"
|
|
4779
|
+
}),
|
|
4780
|
+
legend: flag.boolean({ brief: "Print a key for the tree glyphs and lane colors" }),
|
|
4781
|
+
ascii: flag.boolean({ brief: "Use ASCII glyphs (pipe-friendly)" })
|
|
4782
|
+
} },
|
|
4783
|
+
needs: { config: ormConfigSection },
|
|
4784
|
+
handler: async (args, ctx) => {
|
|
4785
|
+
const migrationsDir = migrationsDirFor(ctx.config, ctx.cwd);
|
|
4786
|
+
const dbConnection = args.flags.db ?? ctx.config.db?.connection;
|
|
4787
|
+
const hasDriver = ctx.config.driver !== void 0;
|
|
4788
|
+
const usingFromOverride = args.flags.from !== void 0;
|
|
4789
|
+
if (!usingFromOverride) {
|
|
4790
|
+
const missingDb = requireLiveDatabase({
|
|
4791
|
+
dbConnection,
|
|
4792
|
+
hasDriver,
|
|
4793
|
+
why: "migration status needs a database connection to read the marker and ledger (or pass --from for an offline path preview)",
|
|
4794
|
+
retryCommand: "prisma-next migration status --from <contract>"
|
|
4795
|
+
});
|
|
4796
|
+
if (missingDb !== null) return notOk(normalizeError(missingDb));
|
|
4797
|
+
}
|
|
4798
|
+
const refsResult = await readMigrationRefs(appRefsDirFor(ctx.config, ctx.cwd));
|
|
4799
|
+
if (!refsResult.ok) return notOk(normalizeError(refsResult.failure));
|
|
4800
|
+
const refs = refsResult.value;
|
|
4801
|
+
const findings = [];
|
|
4802
|
+
const loaded = await buildReadAggregate(ctx.config, { migrationsDir });
|
|
4803
|
+
if (!loaded.ok) return notOk(normalizeError(loaded.failure));
|
|
4804
|
+
const { aggregate, contractHash } = loaded.value;
|
|
4805
|
+
const contractConfig = { contract: ifDefined("output", contractPathFor(ctx.config, ctx.cwd)) };
|
|
4806
|
+
try {
|
|
4807
|
+
await readContractEnvelope(contractConfig);
|
|
4808
|
+
} catch (error) {
|
|
4809
|
+
findings.push(contractUnreadableFinding(error instanceof Error ? error.message : "unknown error"));
|
|
4810
|
+
}
|
|
4811
|
+
if (await loadContractRawSafely(contractConfig) !== null) {
|
|
4812
|
+
const corruption = refusePackageCorruptionOnAggregate(aggregate);
|
|
4813
|
+
if (corruption) return notOk(normalizeError(corruption));
|
|
4814
|
+
}
|
|
4815
|
+
const appGraph = aggregate.app.graph();
|
|
4816
|
+
let activeRefHash;
|
|
4817
|
+
let activeRefName;
|
|
4818
|
+
let activeRefEntry;
|
|
4819
|
+
if (args.flags.to !== void 0) {
|
|
4820
|
+
const resolved = resolveContractRef(args.flags.to, {
|
|
4821
|
+
graph: appGraph,
|
|
4822
|
+
refs
|
|
4823
|
+
});
|
|
4824
|
+
if (!resolved.ok) return notOk(normalizeError(resolved.failure));
|
|
4825
|
+
activeRefHash = resolved.value.hash;
|
|
4826
|
+
if (resolved.value.provenance.kind === "ref") {
|
|
4827
|
+
activeRefName = resolved.value.provenance.refName;
|
|
4828
|
+
activeRefEntry = refs[activeRefName];
|
|
4829
|
+
}
|
|
4830
|
+
}
|
|
4831
|
+
let fromOverrideHash;
|
|
4832
|
+
if (args.flags.from !== void 0) {
|
|
4833
|
+
const resolved = resolveContractRef(args.flags.from, {
|
|
4834
|
+
graph: appGraph,
|
|
4835
|
+
refs
|
|
4836
|
+
});
|
|
4837
|
+
if (!resolved.ok) return notOk(normalizeError(resolved.failure));
|
|
4838
|
+
fromOverrideHash = resolved.value.hash;
|
|
4839
|
+
}
|
|
4840
|
+
const listed = runMigrationList({
|
|
4841
|
+
spaces: await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir),
|
|
4842
|
+
...ifDefined("spaceFilter", args.flags.space)
|
|
4843
|
+
});
|
|
4844
|
+
if (!listed.ok) return notOk(normalizeError(listed.failure));
|
|
4845
|
+
const scopedSpaces = listed.value.spaces;
|
|
4846
|
+
const connects = dbConnection !== void 0 && hasDriver && !usingFromOverride;
|
|
4847
|
+
let database = NO_DATABASE_STATE;
|
|
4848
|
+
if (connects) {
|
|
4849
|
+
const read = await readDatabaseState({
|
|
4850
|
+
client: createControlClient({
|
|
4851
|
+
family: ctx.config.family,
|
|
4852
|
+
target: ctx.config.target,
|
|
4853
|
+
adapter: ctx.config.adapter,
|
|
4854
|
+
...ifDefined("driver", ctx.config.driver),
|
|
4855
|
+
extensions: ctx.config.extensions ?? []
|
|
4856
|
+
}),
|
|
4857
|
+
connection: dbConnection,
|
|
4858
|
+
spaceIds: scopedSpaces.map((entry) => entry.space)
|
|
4859
|
+
});
|
|
4860
|
+
if (!read.ok) return notOk(read.failure);
|
|
4861
|
+
database = read.value;
|
|
4862
|
+
}
|
|
4863
|
+
const appMarker = database.markersBySpace.get(aggregate.app.spaceId);
|
|
4864
|
+
if (activeRefEntry !== void 0 && activeRefEntry.invariants.length > 0 && connects) {
|
|
4865
|
+
const unknown = refuseUnknownInvariants({
|
|
4866
|
+
graph: appGraph,
|
|
4867
|
+
markerInvariants: appMarker?.invariants ?? [],
|
|
4868
|
+
refInvariants: activeRefEntry.invariants,
|
|
4869
|
+
...ifDefined("refName", activeRefName)
|
|
4870
|
+
});
|
|
4871
|
+
if (unknown) return notOk(normalizeError(unknown));
|
|
4872
|
+
}
|
|
4873
|
+
const glyphMode = args.flags.ascii ? "ascii" : "unicode";
|
|
4874
|
+
const styler = createToneMigrationListStyler();
|
|
4875
|
+
const showSpaceHeadings = scopedSpaces.length > 1;
|
|
4876
|
+
const statusSpaces = [];
|
|
4877
|
+
const renderInputs = [];
|
|
4878
|
+
const emptySpaces = [];
|
|
4879
|
+
let divergedMarker;
|
|
4880
|
+
let noPath;
|
|
4881
|
+
let headlineTargetHash = activeRefHash ?? contractHash;
|
|
4882
|
+
let totalPending = 0;
|
|
4883
|
+
for (const entry of scopedSpaces) {
|
|
4884
|
+
const space = aggregate.space(entry.space);
|
|
4885
|
+
if (space === void 0) continue;
|
|
4886
|
+
const graph = space.graph();
|
|
4887
|
+
const spaceContractHash = space.contract().storage.storageHash;
|
|
4888
|
+
const targetHash = activeRefHash ?? spaceContractHash;
|
|
4889
|
+
if (entry.space === aggregate.app.spaceId) headlineTargetHash = targetHash;
|
|
4890
|
+
const markerHash = usingFromOverride ? fromOverrideHash : database.markersBySpace.get(entry.space)?.storageHash;
|
|
4891
|
+
const originHash = originHashForStatus(markerHash);
|
|
4892
|
+
const markerInGraph = markerHash === void 0 || graph.nodes.has(markerHash) || markerHash === spaceContractHash;
|
|
4893
|
+
if (connects && markerInGraph && originHash !== targetHash && noPath === void 0 && !hasMigrationPath(graph, originHash, targetHash)) noPath = {
|
|
4894
|
+
markerHash,
|
|
4895
|
+
targetHash
|
|
4896
|
+
};
|
|
4897
|
+
if (connects && markerHash !== void 0 && !markerInGraph) {
|
|
4898
|
+
divergedMarker ??= {
|
|
4899
|
+
space: entry.space,
|
|
4900
|
+
markerHash
|
|
4901
|
+
};
|
|
4902
|
+
findings.push(markerNotInHistoryFinding(entry.space));
|
|
4903
|
+
}
|
|
4904
|
+
const ledger = database.ledgersBySpace.get(entry.space) ?? [];
|
|
4905
|
+
const annotations = deriveStatusEdgeAnnotations({
|
|
4906
|
+
graph,
|
|
4907
|
+
targetHash,
|
|
4908
|
+
originHash,
|
|
4909
|
+
appliedMigrationHashes: connects ? appliedHashesFromLedger(ledger) : /* @__PURE__ */ new Set(),
|
|
4910
|
+
showAppliedOverlay: connects
|
|
4911
|
+
});
|
|
4912
|
+
const migrations = entry.migrations.map((migration) => ({
|
|
4913
|
+
...migration,
|
|
4914
|
+
status: statusForMigrationHash(migration.hash, annotations)
|
|
4915
|
+
}));
|
|
4916
|
+
totalPending += migrations.filter((migration) => migration.status === "pending").length;
|
|
4917
|
+
statusSpaces.push({
|
|
4918
|
+
space: entry.space,
|
|
4919
|
+
currentContract: markerHash ?? null,
|
|
4920
|
+
targetContract: targetHash,
|
|
4921
|
+
migrations
|
|
4922
|
+
});
|
|
4923
|
+
if (graph.nodes.size === 0) {
|
|
4924
|
+
emptySpaces.push(entry.space);
|
|
4925
|
+
continue;
|
|
4926
|
+
}
|
|
4927
|
+
renderInputs.push({
|
|
4928
|
+
spaceId: entry.space,
|
|
4929
|
+
graph,
|
|
4930
|
+
migrations: entry.migrations,
|
|
4931
|
+
liveContractHash: contractHash,
|
|
4932
|
+
refsByHash: listRefsByContractHash(space),
|
|
4933
|
+
statusOverlayByHash: annotations,
|
|
4934
|
+
colorize: true,
|
|
4935
|
+
glyphMode,
|
|
4936
|
+
styler,
|
|
4937
|
+
palette: TONE_MIGRATION_GRAPH_PALETTE,
|
|
4938
|
+
isAppSpace: entry.space === aggregate.app.spaceId,
|
|
4939
|
+
...connects && markerHash !== void 0 ? { dbHash: markerHash } : {}
|
|
4940
|
+
});
|
|
4941
|
+
}
|
|
4942
|
+
const requiredInvariants = [...activeRefEntry?.invariants ?? []].sort();
|
|
4943
|
+
if (connects && requiredInvariants.length > 0) {
|
|
4944
|
+
const held = new Set(appMarker?.invariants ?? []);
|
|
4945
|
+
const missing = requiredInvariants.filter((id) => !held.has(id));
|
|
4946
|
+
if (missing.length > 0) {
|
|
4947
|
+
findings.push(missingInvariantsFinding({
|
|
4948
|
+
missing,
|
|
4949
|
+
refName: activeRefName
|
|
4950
|
+
}));
|
|
4951
|
+
if (activeRefHash !== void 0) {
|
|
4952
|
+
const unreachable = refuseMissingInvariantPath({
|
|
4953
|
+
graph: appGraph,
|
|
4954
|
+
originHash: originHashForStatus(appMarker?.storageHash),
|
|
4955
|
+
targetHash: activeRefHash,
|
|
4956
|
+
missing,
|
|
4957
|
+
...ifDefined("refName", activeRefName)
|
|
4958
|
+
});
|
|
4959
|
+
if (unreachable) return notOk(normalizeError(unreachable));
|
|
4960
|
+
}
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4963
|
+
const rendered = renderMigrationGraphSpaceTrees(renderInputs);
|
|
4964
|
+
const treesBySpace = new Map(renderInputs.map((input, index) => [input.spaceId, rendered[index] ?? ""]));
|
|
4965
|
+
const sections = scopedSpaces.map((entry) => {
|
|
4966
|
+
const tree = treesBySpace.get(entry.space) ?? "";
|
|
4967
|
+
return {
|
|
4968
|
+
space: entry.space,
|
|
4969
|
+
tree: showSpaceHeadings && tree.length > 0 ? indentMigrationGraphTreeBlock(tree, " ") : tree,
|
|
4970
|
+
showHeading: showSpaceHeadings
|
|
4971
|
+
};
|
|
4972
|
+
});
|
|
4973
|
+
const summary = scopedSpaces.every((entry) => entry.migrations.length === 0) ? "No migrations found" : noPath !== void 0 ? buildNoPathSummary({
|
|
4974
|
+
markerHash: noPath.markerHash,
|
|
4975
|
+
targetHash: noPath.targetHash,
|
|
4976
|
+
explicitTarget: args.flags.to !== void 0,
|
|
4977
|
+
refName: activeRefName
|
|
4978
|
+
}) : buildStatusHeadline({
|
|
4979
|
+
pendingCount: totalPending,
|
|
4980
|
+
targetHash: headlineTargetHash,
|
|
4981
|
+
markerDiverged: divergedMarker !== void 0,
|
|
4982
|
+
markerHash: divergedMarker?.markerHash
|
|
4983
|
+
});
|
|
4984
|
+
const diagnostics = findings.map((finding) => finding.diagnostic);
|
|
4985
|
+
const document = {
|
|
4986
|
+
ok: true,
|
|
4987
|
+
spaces: statusSpaces,
|
|
4988
|
+
summary,
|
|
4989
|
+
diagnostics: findings.map((finding) => finding.document)
|
|
4990
|
+
};
|
|
4991
|
+
const alarming = divergedMarker !== void 0 || totalPending > 0 || noPath !== void 0;
|
|
4992
|
+
return ok(ctx.present({
|
|
4993
|
+
data: document,
|
|
4994
|
+
diagnostics
|
|
4995
|
+
}, statusPresentations({
|
|
4996
|
+
document,
|
|
4997
|
+
sections,
|
|
4998
|
+
headline: {
|
|
4999
|
+
kind: "summary",
|
|
5000
|
+
status: alarming ? "warn" : "ok",
|
|
5001
|
+
text: summary
|
|
5002
|
+
},
|
|
5003
|
+
migrationsDir: displayPath(migrationsDir, ctx.cwd),
|
|
5004
|
+
database: connects && typeof dbConnection === "string" ? maskConnectionUrl(dbConnection) : void 0,
|
|
5005
|
+
ref: activeRefName,
|
|
5006
|
+
from: args.flags.from,
|
|
5007
|
+
space: args.flags.space,
|
|
5008
|
+
legend: args.flags.legend ? renderMigrationGraphLegend({
|
|
5009
|
+
colorize: true,
|
|
5010
|
+
glyphMode,
|
|
5011
|
+
styler,
|
|
5012
|
+
palette: TONE_MIGRATION_GRAPH_PALETTE
|
|
5013
|
+
}) : void 0
|
|
5014
|
+
})));
|
|
5015
|
+
}
|
|
5016
|
+
});
|
|
2339
5017
|
function deletePresentations(document) {
|
|
2340
5018
|
return {
|
|
2341
5019
|
human: () => [{
|
|
@@ -2507,27 +5185,62 @@ const ormCommandFamily = defineCommandFamily({
|
|
|
2507
5185
|
"contract infer": contractInferCommand,
|
|
2508
5186
|
"db init": dbInitCommand,
|
|
2509
5187
|
"db schema": dbSchemaCommand,
|
|
5188
|
+
"db sign": dbSignCommand,
|
|
2510
5189
|
"db update": dbUpdateCommand,
|
|
5190
|
+
"db verify": dbVerifyCommand,
|
|
2511
5191
|
format: formatCommand,
|
|
5192
|
+
init: initCommand,
|
|
5193
|
+
lsp: lspCommand,
|
|
2512
5194
|
migrate: migrateCommand,
|
|
5195
|
+
"migration check": migrationCheckCommand,
|
|
2513
5196
|
"migration graph": migrationGraphCommand,
|
|
2514
5197
|
"migration list": migrationListCommand,
|
|
2515
5198
|
"migration log": migrationLogCommand,
|
|
5199
|
+
"migration new": migrationNewCommand,
|
|
5200
|
+
"migration plan": migrationPlanCommand,
|
|
2516
5201
|
"migration show": migrationShowCommand,
|
|
5202
|
+
"migration status": migrationStatusCommand,
|
|
2517
5203
|
"ref delete": refDeleteCommand,
|
|
2518
5204
|
"ref list": refListCommand,
|
|
2519
5205
|
"ref set": refSetCommand
|
|
2520
5206
|
},
|
|
2521
5207
|
docsBaseUrl: DOCS_BASE_URL,
|
|
2522
|
-
redirects: [
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
5208
|
+
redirects: [
|
|
5209
|
+
{
|
|
5210
|
+
from: "migration apply",
|
|
5211
|
+
replacement: "{bin} migrate --to <contract>",
|
|
5212
|
+
reason: "Applying a migration is a move to a target contract, not a verb of its own."
|
|
5213
|
+
},
|
|
5214
|
+
{
|
|
5215
|
+
from: "migration ref",
|
|
5216
|
+
replacement: "{bin} ref set|list|delete",
|
|
5217
|
+
reason: "Refs are managed by their own command, for every space rather than migrations alone."
|
|
5218
|
+
},
|
|
5219
|
+
{
|
|
5220
|
+
from: "migration status",
|
|
5221
|
+
flag: "graph",
|
|
5222
|
+
replacement: "{bin} migration graph",
|
|
5223
|
+
reason: "Topology is its own command rather than a mode of the status report."
|
|
5224
|
+
},
|
|
5225
|
+
{
|
|
5226
|
+
from: "migration status",
|
|
5227
|
+
flag: "all",
|
|
5228
|
+
replacement: "{bin} migration log --db <url>",
|
|
5229
|
+
reason: "Execution history is read from the database ledger, which is what migration log does."
|
|
5230
|
+
},
|
|
5231
|
+
{
|
|
5232
|
+
from: "migration status",
|
|
5233
|
+
flag: "limit",
|
|
5234
|
+
replacement: "{bin} migration log --db <url>",
|
|
5235
|
+
reason: "Execution history is read from the database ledger, which is what migration log does."
|
|
5236
|
+
},
|
|
5237
|
+
{
|
|
5238
|
+
from: "migration status",
|
|
5239
|
+
flag: "ref",
|
|
5240
|
+
replacement: "{bin} migration status --to <contract>",
|
|
5241
|
+
reason: "One flag names the target contract, whether it is a ref, a hash or a migration."
|
|
5242
|
+
}
|
|
5243
|
+
]
|
|
2531
5244
|
});
|
|
2532
5245
|
/** The file the ORM's loader reads when `--config` names none. */
|
|
2533
5246
|
const ORM_CONFIG_FILENAME = "prisma-next.config.ts";
|