@prisma/orm-toolchain 8.0.0-rc.1-dev.37 → 8.0.0-rc.1-dev.39
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 +23 -316
- package/dist/bin__prisma-next.mjs.map +1 -1
- package/dist/{caught-errors-BZ-9WASe-DH4DaDha.mjs → caught-errors-DLPzfzIW-CQ9BoXD-.mjs} +3 -3
- package/dist/{caught-errors-BZ-9WASe-DH4DaDha.mjs.map → caught-errors-DLPzfzIW-CQ9BoXD-.mjs.map} +1 -1
- package/dist/{cli-errors-C6_pH59O-CzD4SYcJ.mjs → cli-errors-BG6bItjG-DwdKIeGz.mjs} +4 -4
- package/dist/cli-errors-BG6bItjG-DwdKIeGz.mjs.map +1 -0
- package/dist/{cli-errors-WMw54cyq-DC2OYnd7.d.mts → cli-errors-BiKBrOXc-JPxQqO58.d.mts} +45 -5
- package/dist/cli-errors-BiKBrOXc-JPxQqO58.d.mts.map +1 -0
- package/dist/cli.mjs +2118 -146
- 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 +12 -4
- package/dist/cli__control-api.d.mts.map +1 -1
- package/dist/cli__control-api.mjs +15 -15
- package/dist/cli__control-api__testing.d.mts +1 -1
- package/dist/cli__init-output.d.mts +1 -1
- package/dist/cli__init-output.mjs +1 -1
- package/dist/{client-BqhVohc5-DFRVWFps.mjs → client-DU9qtBxW-BjVHiKzE.mjs} +57 -11
- package/dist/client-DU9qtBxW-BjVHiKzE.mjs.map +1 -0
- package/dist/code-templates-CSLH8721-CWRA4Lru.mjs +295 -0
- package/dist/code-templates-CSLH8721-CWRA4Lru.mjs.map +1 -0
- package/dist/{command-helpers-kD9CO1hd-BPhnkBin.mjs → command-helpers-CwLvFwMB-BWwUQhlT.mjs} +3 -3
- package/dist/{command-helpers-kD9CO1hd-BPhnkBin.mjs.map → command-helpers-CwLvFwMB-BWwUQhlT.mjs.map} +1 -1
- package/dist/{contract-at-errors-CLFX1-Gc-MNPXDx1t.mjs → contract-at-errors-Dptf8RE_-B5gr04Sc.mjs} +3 -3
- package/dist/{contract-at-errors-CLFX1-Gc-MNPXDx1t.mjs.map → contract-at-errors-Dptf8RE_-B5gr04Sc.mjs.map} +1 -1
- package/dist/{contract-emit-Cpj5Qz7z-BXt3HGPC.mjs → contract-emit-BQFpcb2A-BwT9iVdb.mjs} +5 -5
- package/dist/{contract-emit-Cpj5Qz7z-BXt3HGPC.mjs.map → contract-emit-BQFpcb2A-BwT9iVdb.mjs.map} +1 -1
- package/dist/contract-emit-Bzt11N4v-BQ2Pr8KG.mjs +2 -0
- package/dist/{contract-emit-Dp1pe06_-Cdpvh5DH.mjs → contract-emit-CZQZ1i4z-p9LSGmmQ.mjs} +5 -5
- package/dist/{contract-emit-Dp1pe06_-Cdpvh5DH.mjs.map → contract-emit-CZQZ1i4z-p9LSGmmQ.mjs.map} +1 -1
- package/dist/{contract-infer-C8kLphsm-C7-AIYMc.mjs → contract-infer-C6cYOihE-CIUCgmw0.mjs} +4 -4
- package/dist/{contract-infer-C8kLphsm-C7-AIYMc.mjs.map → contract-infer-C6cYOihE-CIUCgmw0.mjs.map} +1 -1
- package/dist/{contract-snapshot-resolution-BUUdIcgG-8mbs6-OD.mjs → contract-snapshot-resolution-DvZvbNcZ-DHFCWe_k.mjs} +4 -4
- package/dist/{contract-snapshot-resolution-BUUdIcgG-8mbs6-OD.mjs.map → contract-snapshot-resolution-DvZvbNcZ-DHFCWe_k.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-BpdB-mHG-BiU0IUeb.mjs → contract-space-aggregate-loader-CoLTf3Ce-pQDru2w0.mjs} +4 -4
- package/dist/{contract-space-aggregate-loader-BpdB-mHG-BiU0IUeb.mjs.map → contract-space-aggregate-loader-CoLTf3Ce-pQDru2w0.mjs.map} +1 -1
- package/dist/{db-init-CyNsXRAB-D3xWQ-vy.mjs → db-init-DypCy1e3-CBrYfhHF.mjs} +6 -6
- package/dist/{db-init-CyNsXRAB-D3xWQ-vy.mjs.map → db-init-DypCy1e3-CBrYfhHF.mjs.map} +1 -1
- package/dist/{db-init-failure-XYOVL4o7-BitY_JYn.mjs → db-init-failure-BmCcHUfc-BqDxyrJE.mjs} +3 -3
- package/dist/{db-init-failure-XYOVL4o7-BitY_JYn.mjs.map → db-init-failure-BmCcHUfc-BqDxyrJE.mjs.map} +1 -1
- package/dist/{db-schema-7zNGHgYN-BVaRHPxg.mjs → db-schema-DHfPM-5V-JeCjrQ55.mjs} +4 -4
- package/dist/{db-schema-7zNGHgYN-BVaRHPxg.mjs.map → db-schema-DHfPM-5V-JeCjrQ55.mjs.map} +1 -1
- package/dist/{db-sign-DAs6J3XG-xxP2CCnO.mjs → db-sign-DbAxRcZh-C-nT9kcG.mjs} +6 -6
- package/dist/{db-sign-DAs6J3XG-xxP2CCnO.mjs.map → db-sign-DbAxRcZh-C-nT9kcG.mjs.map} +1 -1
- package/dist/{db-update-BX1sOuQk-CpeLgPeN.mjs → db-update-J59yROwL-DCP-_z2W.mjs} +16 -7
- package/dist/db-update-J59yROwL-DCP-_z2W.mjs.map +1 -0
- package/dist/{db-verify-CN4nmxxF-DSRWae0u.mjs → db-verify-0TpDMziA-CJDKutMN.mjs} +5 -5
- package/dist/{db-verify-CN4nmxxF-DSRWae0u.mjs.map → db-verify-0TpDMziA-CJDKutMN.mjs.map} +1 -1
- package/dist/{format-DceifGAK-BJhTdkcO.mjs → format-cbJY7l6v-CjMUDTAs.mjs} +5 -5
- package/dist/{format-DceifGAK-BJhTdkcO.mjs.map → format-cbJY7l6v-CjMUDTAs.mjs.map} +1 -1
- package/dist/{framework-components-CPFWl2Ix-CrIZL3jG.mjs → framework-components-C3K7SBeC-Bj5gx85-.mjs} +3 -3
- package/dist/{framework-components-CPFWl2Ix-CrIZL3jG.mjs.map → framework-components-C3K7SBeC-Bj5gx85-.mjs.map} +1 -1
- package/dist/init-CcJxAIeS-CITTa8YU.mjs +800 -0
- package/dist/init-CcJxAIeS-CITTa8YU.mjs.map +1 -0
- package/dist/{inspect-live-schema-BaLAtOgO-Bs0gs_yP.mjs → inspect-live-schema-Cf77j9Vd-i5fzNW4T.mjs} +5 -5
- package/dist/{inspect-live-schema-BaLAtOgO-Bs0gs_yP.mjs.map → inspect-live-schema-Cf77j9Vd-i5fzNW4T.mjs.map} +1 -1
- package/dist/{invariants-1L2zfc6--BpedlAbF.mjs → invariants-1zL5-9Mc-BCeOLxd_.mjs} +4 -4
- package/dist/{invariants-1L2zfc6--BpedlAbF.mjs.map → invariants-1zL5-9Mc-BCeOLxd_.mjs.map} +1 -1
- package/dist/{legend-aXC5H1jH-Dg1yWonK.mjs → legend-BQcPKTLe-bn9e4_ZU.mjs} +3 -3
- package/dist/{legend-aXC5H1jH-Dg1yWonK.mjs.map → legend-BQcPKTLe-bn9e4_ZU.mjs.map} +1 -1
- package/dist/{migrate-DRiUHRCS-Cci6EEZv.mjs → migrate-BVEyoBor-CN_EOCzx.mjs} +12 -12
- package/dist/{migrate-DRiUHRCS-Cci6EEZv.mjs.map → migrate-BVEyoBor-CN_EOCzx.mjs.map} +1 -1
- package/dist/{migrate-failure-Bdr3AJee-C9yNeQXy.mjs → migrate-failure-CKMEnA_x-ByHkYjn5.mjs} +5 -5
- package/dist/{migrate-failure-Bdr3AJee-C9yNeQXy.mjs.map → migrate-failure-CKMEnA_x-ByHkYjn5.mjs.map} +1 -1
- package/dist/{migrate-show-DRfCIamX-BK6CN3RI.d.mts → migrate-show-Dlo-YzOk-BXo-DmwI.d.mts} +3 -3
- package/dist/{migrate-show-DRfCIamX-BK6CN3RI.d.mts.map → migrate-show-Dlo-YzOk-BXo-DmwI.d.mts.map} +1 -1
- package/dist/{migrate-show-bdXjSFgh-CQxl5yNp.mjs → migrate-show-kVUG1xdo-bVTW8tu4.mjs} +6 -6
- package/dist/{migrate-show-bdXjSFgh-CQxl5yNp.mjs.map → migrate-show-kVUG1xdo-bVTW8tu4.mjs.map} +1 -1
- package/dist/{migration-check-BHiINj74-CVazN1LV.mjs → migration-check-BVK9vfan-Dfg8lbeB.mjs} +6 -6
- package/dist/{migration-check-BHiINj74-CVazN1LV.mjs.map → migration-check-BVK9vfan-Dfg8lbeB.mjs.map} +1 -1
- package/dist/{migration-check-of-RPYRk-HuXtDO8i.mjs → migration-check-CBMoMDX2-DhSEOGNg.mjs} +6 -6
- package/dist/{migration-check-of-RPYRk-HuXtDO8i.mjs.map → migration-check-CBMoMDX2-DhSEOGNg.mjs.map} +1 -1
- package/dist/{migration-command-scaffold-zJFTGn5k-5AGczeSx.mjs → migration-command-scaffold-B1BiWKLI-Bskk5UKX.mjs} +5 -5
- package/dist/{migration-command-scaffold-zJFTGn5k-5AGczeSx.mjs.map → migration-command-scaffold-B1BiWKLI-Bskk5UKX.mjs.map} +1 -1
- package/dist/{migration-graph-DmdVVJoZ-DDBKjUMz.mjs → migration-graph-B30X5398-BH3zB9Mn.mjs} +3 -3
- package/dist/{migration-graph-DmdVVJoZ-DDBKjUMz.mjs.map → migration-graph-B30X5398-BH3zB9Mn.mjs.map} +1 -1
- package/dist/{migration-graph-D0KHjNJ--CWzX9Zzc.mjs → migration-graph-D4sgTiXa-BLtiLIk7.mjs} +9 -9
- package/dist/{migration-graph-D0KHjNJ--CWzX9Zzc.mjs.map → migration-graph-D4sgTiXa-BLtiLIk7.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-BWcFCOL6-_G0Wb4QR.mjs → migration-graph-sections-B8WSWSkJ-CyaRo0T6.mjs} +4 -4
- package/dist/{migration-graph-sections-BWcFCOL6-_G0Wb4QR.mjs.map → migration-graph-sections-B8WSWSkJ-CyaRo0T6.mjs.map} +1 -1
- package/dist/{migration-list-B0SANIeM-DiXQ9LyS.mjs → migration-list-BIL-re1m-CZY4m3d0.mjs} +7 -7
- package/dist/{migration-list-B0SANIeM-DiXQ9LyS.mjs.map → migration-list-BIL-re1m-CZY4m3d0.mjs.map} +1 -1
- package/dist/{migration-list-W0dqabIj-Dc2NH_LY.mjs → migration-list-BQTfSGbT-DoNqbNsu.mjs} +3 -3
- package/dist/{migration-list-W0dqabIj-Dc2NH_LY.mjs.map → migration-list-BQTfSGbT-DoNqbNsu.mjs.map} +1 -1
- package/dist/{migration-log-B3iiP8ns-B4tdLSye.mjs → migration-log-BdBKYAUW-DTPxKZtD.mjs} +8 -8
- package/dist/{migration-log-B3iiP8ns-B4tdLSye.mjs.map → migration-log-BdBKYAUW-DTPxKZtD.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-YSdtJTRp-Dc06LNJe.mjs → migration-new-Bp5dPKnF-D-Fb6vEp.mjs} +7 -7
- package/dist/{migration-new-YSdtJTRp-Dc06LNJe.mjs.map → migration-new-Bp5dPKnF-D-Fb6vEp.mjs.map} +1 -1
- package/dist/{migration-new-Bf5K9fNK-Cd90rYmm.mjs → migration-new-CXLgOLXz-2C5s_q7n.mjs} +4 -4
- package/dist/{migration-new-Bf5K9fNK-Cd90rYmm.mjs.map → migration-new-CXLgOLXz-2C5s_q7n.mjs.map} +1 -1
- package/dist/{migration-path-target-5r1leSW5-B0EMgvEv.mjs → migration-path-target-BNTSTPAc-D7dQ4K7x.mjs} +3 -3
- package/dist/{migration-path-target-5r1leSW5-B0EMgvEv.mjs.map → migration-path-target-BNTSTPAc-D7dQ4K7x.mjs.map} +1 -1
- package/dist/{migration-plan-oterbw8h-_GrKbsRb.mjs → migration-plan-BxfF1Hdo-BKn0xMlY.mjs} +4 -4
- package/dist/{migration-plan-oterbw8h-_GrKbsRb.mjs.map → migration-plan-BxfF1Hdo-BKn0xMlY.mjs.map} +1 -1
- package/dist/{migration-plan-CZY8HVCh-BbfQvxnb.mjs → migration-plan-C6KrFHJT-CRkTsE7L.mjs} +8 -8
- package/dist/{migration-plan-CZY8HVCh-BbfQvxnb.mjs.map → migration-plan-C6KrFHJT-CRkTsE7L.mjs.map} +1 -1
- package/dist/{migration-plan-DPp3Orhx-6edvA2e2.d.mts → migration-plan-CharRhoN-BIB3WL65.d.mts} +3 -3
- package/dist/{migration-plan-DPp3Orhx-6edvA2e2.d.mts.map → migration-plan-CharRhoN-BIB3WL65.d.mts.map} +1 -1
- package/dist/{migration-show-iX2cduR4-C3-FENR4.mjs → migration-show-Dhnlx_GU-DhWLLt3Z.mjs} +8 -8
- package/dist/{migration-show-iX2cduR4-C3-FENR4.mjs.map → migration-show-Dhnlx_GU-DhWLLt3Z.mjs.map} +1 -1
- package/dist/{migration-status-BZ6gOxyJ-sbhPgABr.mjs → migration-status-DzPVkkB3-BA7jjITd.mjs} +12 -11
- package/dist/{migration-status-BZ6gOxyJ-sbhPgABr.mjs.map → migration-status-DzPVkkB3-BA7jjITd.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-BPuwl2j1-DTX9G73z.mjs → project-import-root-nwdoPWap--5vXe4zp.mjs} +3 -3
- package/dist/{project-import-root-BPuwl2j1-DTX9G73z.mjs.map → project-import-root-nwdoPWap--5vXe4zp.mjs.map} +1 -1
- package/dist/{init-DMiJjbrC-C7-PvDyK.mjs → redact-secrets-kQ0KKV1u-MnVSKe-u.mjs} +741 -1517
- package/dist/redact-secrets-kQ0KKV1u-MnVSKe-u.mjs.map +1 -0
- package/dist/{ref-CVD6j3jA-BAgpZLNM.mjs → ref-C8oaaeky-ColNkWpr.mjs} +5 -5
- package/dist/{ref-CVD6j3jA-BAgpZLNM.mjs.map → ref-C8oaaeky-ColNkWpr.mjs.map} +1 -1
- package/dist/{ref-CaqNqeRF-cjWFAueu.mjs → ref-gZQjdz8I-CW6n9Fc0.mjs} +4 -4
- package/dist/{ref-CaqNqeRF-cjWFAueu.mjs.map → ref-gZQjdz8I-CW6n9Fc0.mjs.map} +1 -1
- package/dist/{ref-resolution-D4fAdXo8-Dfaky0yw.mjs → ref-resolution-DAK4OK3x-B-8ZZc0f.mjs} +3 -3
- package/dist/{ref-resolution-D4fAdXo8-Dfaky0yw.mjs.map → ref-resolution-DAK4OK3x-B-8ZZc0f.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-fKuZOFpu-DNUWfIHo.mjs → telemetry-DPXFYAsz-CPW2USxs.mjs} +3 -3
- package/dist/{telemetry-fKuZOFpu-DNUWfIHo.mjs.map → telemetry-DPXFYAsz-CPW2USxs.mjs.map} +1 -1
- package/dist/vite-plugin-contract-emit.mjs +1 -1
- package/package.json +13 -13
- package/dist/cli-errors-C6_pH59O-CzD4SYcJ.mjs.map +0 -1
- package/dist/cli-errors-WMw54cyq-DC2OYnd7.d.mts.map +0 -1
- package/dist/client-BqhVohc5-DFRVWFps.mjs.map +0 -1
- package/dist/contract-emit-BHZYuA91-wlnU7OrE.mjs +0 -2
- package/dist/db-update-BX1sOuQk-CpeLgPeN.mjs.map +0 -1
- package/dist/init-DMiJjbrC-C7-PvDyK.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,46 +1,55 @@
|
|
|
1
1
|
import { i as loadConfig } from "./exports-BZLNbtkY.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { I as requireLiveDatabase, L as runCommandAction, M as errorTargetMigrationNotSupported, O as errorRunnerFailed, P as errorUnexpected, a as errorConfigValidation$1, b as errorMigrationPlanningFailed, c as errorContractValidationFailed, d as errorDriverRequired, f as errorFileNotFound, g as errorLegendHumanOnly, k as errorRuntime$1, l as errorDatabaseConnectionRequired, o as errorConsentPlanMismatch, r as chooseAction, t as CliStructuredError$1$1, u as errorDestructiveChanges, x as errorNoMigrations, y as errorMigrationPackageNotFound } from "./cli-errors-BG6bItjG-DwdKIeGz.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-CZQZ1i4z-p9LSGmmQ.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-CwLvFwMB-BWwUQhlT.mjs";
|
|
8
|
+
import { t as createContractEmitCommand } from "./contract-emit-BQFpcb2A-BwT9iVdb.mjs";
|
|
9
|
+
import { n as executeFormat, t as createFormatCommand } from "./format-cbJY7l6v-CjMUDTAs.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-CSLH8721-CWRA4Lru.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-CoLTf3Ce-pQDru2w0.mjs";
|
|
12
|
+
import { t as createControlClient } from "./client-DU9qtBxW-BjVHiKzE.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
|
|
15
|
-
import { t as
|
|
16
|
-
import {
|
|
17
|
-
import { t as
|
|
18
|
-
import {
|
|
19
|
-
import { n as
|
|
20
|
-
import {
|
|
21
|
-
import { n as
|
|
22
|
-
import { n as
|
|
23
|
-
import { t as
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import { n as
|
|
14
|
+
import { t as mapDbInitFailure } from "./db-init-failure-BmCcHUfc-BqDxyrJE.mjs";
|
|
15
|
+
import { t as resolveContractRefToSnapshot } from "./contract-snapshot-resolution-DvZvbNcZ-DHFCWe_k.mjs";
|
|
16
|
+
import { t as mapCaughtMigrationError } from "./caught-errors-DLPzfzIW-CQ9BoXD-.mjs";
|
|
17
|
+
import { t as mapContractAtError } from "./contract-at-errors-Dptf8RE_-B5gr04Sc.mjs";
|
|
18
|
+
import { i as refuseUnknownInvariants, n as refuseMarkerOutsideGraph, r as refuseMissingInvariantPath, t as hasMigrationPath } from "./invariants-1zL5-9Mc-BCeOLxd_.mjs";
|
|
19
|
+
import { t as executeMigrateShowPlan } from "./migrate-show-kVUG1xdo-bVTW8tu4.mjs";
|
|
20
|
+
import { n as resolveMigrationRef, t as resolveContractRef } from "./ref-resolution-DAK4OK3x-B-8ZZc0f.mjs";
|
|
21
|
+
import { n as migrationSpaceListEntriesFromAggregate, r as runMigrationList, t as listRefsByContractHash } from "./migration-list-BQTfSGbT-DoNqbNsu.mjs";
|
|
22
|
+
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";
|
|
23
|
+
import { n as migrateShowRunListRows, r as renderMigrateShowGraph, t as mapMigrateFailure } from "./migrate-failure-CKMEnA_x-ByHkYjn5.mjs";
|
|
24
|
+
import { n as looksLikePath, r as resolveAppTargetPath, t as findPackageByDirPath } from "./migration-path-target-BNTSTPAc-D7dQ4K7x.mjs";
|
|
25
|
+
import { t as buildMigrationSpaceGraphEntries } from "./migration-graph-B30X5398-BH3zB9Mn.mjs";
|
|
26
|
+
import { n as renderMigrationGraphDot, r as renderMigrationGraphSections, t as buildMigrationGraphTreeSections } from "./migration-graph-sections-B8WSWSkJ-CyaRo0T6.mjs";
|
|
27
|
+
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";
|
|
28
|
+
import { t as executeMigrationNewCommand } from "./migration-new-Bp5dPKnF-D-Fb6vEp.mjs";
|
|
29
|
+
import { t as executeMigrationPlanCommand } from "./migration-plan-C6KrFHJT-CRkTsE7L.mjs";
|
|
30
|
+
import { a as statusForMigrationHash, i as readMigrationRefs, n as deriveStatusEdgeAnnotations, r as originHashForStatus, t as appliedHashesFromLedger } from "./refs-fQLFcMoq-aTBeUU9H.mjs";
|
|
31
|
+
import { n as executeRefListCommand, r as executeRefSetCommand, t as executeRefDeleteCommand } from "./ref-C8oaaeky-ColNkWpr.mjs";
|
|
32
|
+
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-kQ0KKV1u-MnVSKe-u.mjs";
|
|
33
|
+
import { n as buildNextSteps, t as InitOutputSchema } from "./output-DP3woAjV-BxCr9nHA.mjs";
|
|
27
34
|
import { CliStructuredError } from "@prisma/orm-framework/errors/control";
|
|
28
35
|
import { ifDefined } from "@prisma/orm-framework/utils/defined";
|
|
29
|
-
import { basename, dirname, join, normalize, relative, resolve } from "pathe";
|
|
30
|
-
import { existsSync, unlinkSync, writeFileSync } from "node:fs";
|
|
31
|
-
import { DOCS_BASE, isStructuredError } from "@prisma/orm-framework/utils/structured-error";
|
|
36
|
+
import { basename, dirname, isAbsolute, join, normalize, relative, resolve } from "pathe";
|
|
37
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
38
|
+
import { DOCS_BASE, docsUrlFor, isStructuredError } from "@prisma/orm-framework/utils/structured-error";
|
|
32
39
|
import { blindCast, castAs } from "@prisma/orm-framework/utils/casts";
|
|
33
|
-
import { InternalError } from "@prisma/orm-framework/utils/internal-error";
|
|
40
|
+
import { InternalError, assertNever } from "@prisma/orm-framework/utils/internal-error";
|
|
34
41
|
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
35
42
|
import { APP_SPACE_ID, createControlStack } from "@prisma/orm-framework/components/control";
|
|
43
|
+
import { type } from "arktype";
|
|
36
44
|
import { collectConfigIssues } from "@prisma/orm-framework/config/config-validation";
|
|
37
45
|
import { tmpdir } from "node:os";
|
|
46
|
+
import { detect } from "package-manager-detector/detect";
|
|
38
47
|
import { printPsl } from "@prisma/orm-framework/psl-printer";
|
|
39
48
|
import { defineCommand, defineCommandFamily, defineConfigSection, flag, positional } from "@prisma/cli-engine";
|
|
40
49
|
import { CliStructuredError as CliStructuredError$1, notOk, ok } from "@prisma/cli-engine/protocol";
|
|
41
50
|
import { pathToFileURL } from "node:url";
|
|
42
51
|
import { build } from "esbuild";
|
|
43
|
-
//#region ../../../1-framework/3-tooling/cli/dist/load-config-
|
|
52
|
+
//#region ../../../1-framework/3-tooling/cli/dist/load-config-DWFCZvpn.mjs
|
|
44
53
|
const MISSING_CONFIG_ACTION = {
|
|
45
54
|
kind: "run-command",
|
|
46
55
|
label: "Create a config file",
|
|
@@ -607,7 +616,7 @@ function perSpaceBlocks(perSpace, mode) {
|
|
|
607
616
|
roots: spaceNodes(perSpace, mode)
|
|
608
617
|
}, ...destructiveWarningBlocks(hasDestructive)];
|
|
609
618
|
}
|
|
610
|
-
function operationBlocks$
|
|
619
|
+
function operationBlocks$2(result) {
|
|
611
620
|
const perSpace = result.perSpace;
|
|
612
621
|
if (perSpace !== void 0 && perSpace.length > 0) return perSpaceBlocks(perSpace, result.mode);
|
|
613
622
|
if (result.plan.operations.length === 0) return [];
|
|
@@ -618,7 +627,7 @@ function operationBlocks$1(result) {
|
|
|
618
627
|
}, ...destructiveWarningBlocks(hasDestructive)];
|
|
619
628
|
}
|
|
620
629
|
/** The statements a database would run, printed verbatim rather than laid out. */
|
|
621
|
-
function previewBlocks$
|
|
630
|
+
function previewBlocks$2(preview) {
|
|
622
631
|
if (preview === void 0) return [];
|
|
623
632
|
const header = {
|
|
624
633
|
kind: "summary",
|
|
@@ -637,6 +646,22 @@ function previewBlocks$1(preview) {
|
|
|
637
646
|
lines: statements
|
|
638
647
|
}];
|
|
639
648
|
}
|
|
649
|
+
/**
|
|
650
|
+
* What the planner flagged without refusing to plan it. Only `db update`
|
|
651
|
+
* produces these — the additive-only policies cannot reach the branch.
|
|
652
|
+
*/
|
|
653
|
+
function plannerWarningBlocks(result) {
|
|
654
|
+
const warnings = result.warnings ?? [];
|
|
655
|
+
if (warnings.length === 0) return [];
|
|
656
|
+
return [{
|
|
657
|
+
kind: "summary",
|
|
658
|
+
status: "warn",
|
|
659
|
+
text: "Planner warnings"
|
|
660
|
+
}, {
|
|
661
|
+
kind: "list",
|
|
662
|
+
items: warnings.map((warning) => warning.summary)
|
|
663
|
+
}];
|
|
664
|
+
}
|
|
640
665
|
function planSummaryText(result) {
|
|
641
666
|
const operations = result.plan.operations.length;
|
|
642
667
|
const spaces = result.perSpace?.length ?? 0;
|
|
@@ -649,7 +674,7 @@ function applySummaryText(result) {
|
|
|
649
674
|
if (executed === 0) return `Database already matches contract${across}`;
|
|
650
675
|
return `Applied ${executed} operation(s)${across}`;
|
|
651
676
|
}
|
|
652
|
-
function planBlocks(result) {
|
|
677
|
+
function planBlocks$1(result) {
|
|
653
678
|
const planned = result.plannedAdvanceRef;
|
|
654
679
|
return [
|
|
655
680
|
{
|
|
@@ -657,7 +682,8 @@ function planBlocks(result) {
|
|
|
657
682
|
status: "ok",
|
|
658
683
|
text: planSummaryText(result)
|
|
659
684
|
},
|
|
660
|
-
...
|
|
685
|
+
...plannerWarningBlocks(result),
|
|
686
|
+
...operationBlocks$2(result),
|
|
661
687
|
{
|
|
662
688
|
kind: "fields",
|
|
663
689
|
rows: [{
|
|
@@ -676,7 +702,7 @@ function planBlocks(result) {
|
|
|
676
702
|
tone: "identifier"
|
|
677
703
|
}]
|
|
678
704
|
}],
|
|
679
|
-
...previewBlocks$
|
|
705
|
+
...previewBlocks$2(result.plan.preview),
|
|
680
706
|
{
|
|
681
707
|
kind: "summary",
|
|
682
708
|
status: "info",
|
|
@@ -717,7 +743,8 @@ function applyBlocks(result) {
|
|
|
717
743
|
status: "ok",
|
|
718
744
|
text: applySummaryText(result)
|
|
719
745
|
},
|
|
720
|
-
...
|
|
746
|
+
...plannerWarningBlocks(result),
|
|
747
|
+
...operationBlocks$2(result),
|
|
721
748
|
...fallbackMarkerBlocks(result),
|
|
722
749
|
...advanced === null || advanced === void 0 ? [] : [{
|
|
723
750
|
kind: "summary",
|
|
@@ -731,7 +758,7 @@ function applyBlocks(result) {
|
|
|
731
758
|
}
|
|
732
759
|
/** What a `db init` / `db update` result looks like in human mode. */
|
|
733
760
|
function migrationResultBlocks(result) {
|
|
734
|
-
return result.mode === "plan" ? planBlocks(result) : applyBlocks(result);
|
|
761
|
+
return result.mode === "plan" ? planBlocks$1(result) : applyBlocks(result);
|
|
735
762
|
}
|
|
736
763
|
/** Where the user goes next, as typed actions rather than trailing prose. */
|
|
737
764
|
function migrationResultNextActions(result, applyCommand) {
|
|
@@ -750,6 +777,16 @@ function appMigrationsDirFor(config, cwd) {
|
|
|
750
777
|
return spaceMigrationDirectory(migrationsDirFor(config, cwd), APP_SPACE_ID);
|
|
751
778
|
}
|
|
752
779
|
/**
|
|
780
|
+
* The config file an operation should anchor its project paths on. The engine
|
|
781
|
+
* loads the config and hands a handler the value but not the path, and
|
|
782
|
+
* `--config` is an engine flag the handler never sees, so a handler names the
|
|
783
|
+
* invocation directory's file. That equals the loaded file for every
|
|
784
|
+
* invocation whose config sits in the invocation directory.
|
|
785
|
+
*/
|
|
786
|
+
function projectConfigPathFor(cwd) {
|
|
787
|
+
return resolve(cwd, "prisma-next.config.ts");
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
753
790
|
* Where refs live. The framework keeps them under the app subspace rather than
|
|
754
791
|
* at the migrations root.
|
|
755
792
|
*/
|
|
@@ -823,7 +860,7 @@ async function prepareMigrationRun(inputs) {
|
|
|
823
860
|
refsDir: appRefsDirFor(config, cwd)
|
|
824
861
|
});
|
|
825
862
|
}
|
|
826
|
-
function initPresentations(inputs) {
|
|
863
|
+
function initPresentations$1(inputs) {
|
|
827
864
|
const { document, database, dryRun } = inputs;
|
|
828
865
|
return {
|
|
829
866
|
human: () => [{
|
|
@@ -955,7 +992,7 @@ function createDbInitCommand(createClient) {
|
|
|
955
992
|
severity: "verbose",
|
|
956
993
|
text: `Total time: ${document.timings.total}ms`
|
|
957
994
|
});
|
|
958
|
-
return ok(ctx.present({ data: document }, initPresentations({
|
|
995
|
+
return ok(ctx.present({ data: document }, initPresentations$1({
|
|
959
996
|
document,
|
|
960
997
|
contractPath: prepared.value.contractDisplayPath,
|
|
961
998
|
database: prepared.value.database,
|
|
@@ -1157,6 +1194,270 @@ function createDbSchemaCommand(createClient) {
|
|
|
1157
1194
|
});
|
|
1158
1195
|
}
|
|
1159
1196
|
const dbSchemaCommand = createDbSchemaCommand(createControlClient);
|
|
1197
|
+
/**
|
|
1198
|
+
* A `db update` failure as the CLI's structured error.
|
|
1199
|
+
*
|
|
1200
|
+
* `DESTRUCTIVE_CHANGES` is handled by the command, which asks for consent and
|
|
1201
|
+
* calls again; it is mapped here for the case the control API returns it from a
|
|
1202
|
+
* call that already carried consent, which would otherwise degrade silently.
|
|
1203
|
+
*
|
|
1204
|
+
* The `assertNever` is deliberate: a control-API failure code this does not
|
|
1205
|
+
* handle must stop the command rather than degrade into a generic message.
|
|
1206
|
+
*/
|
|
1207
|
+
function mapDbUpdateFailure(failure) {
|
|
1208
|
+
if (failure.code === "PLANNING_FAILED") return errorMigrationPlanningFailed({ conflicts: failure.conflicts ?? [] });
|
|
1209
|
+
if (failure.code === "RUNNER_FAILED") {
|
|
1210
|
+
const fix = (typeof failure.meta?.["runnerErrorCode"] === "string" ? failure.meta["runnerErrorCode"] : void 0) === "MIGRATION.LEGACY_MARKER_SHAPE" ? "Legacy marker-table shape detected. Drop `prisma_contract.marker` (Postgres) or `_prisma_marker` (SQLite) and re-run `prisma-next db init` to recreate it with the current per-space schema." : "Inspect the reported conflict, reconcile schema drift if needed, then re-run `prisma-next db update`";
|
|
1211
|
+
return errorRunnerFailed(failure.summary, {
|
|
1212
|
+
why: failure.why ?? "Migration runner failed",
|
|
1213
|
+
fix,
|
|
1214
|
+
meta: {
|
|
1215
|
+
...failure.meta,
|
|
1216
|
+
...failure.warnings && failure.warnings.length > 0 ? { plannerWarnings: failure.warnings } : {}
|
|
1217
|
+
},
|
|
1218
|
+
...ifDefined("cause", failure.cause)
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
if (failure.code === "DESTRUCTIVE_CHANGES") return errorDestructiveChanges(failure.summary, {
|
|
1222
|
+
...ifDefined("why", failure.why),
|
|
1223
|
+
fix: "Re-run `prisma-next db update` and type the database name when asked, or pass `--no-interactive --confirm <database>` where there is nobody to ask. Use `--dry-run` to preview the operations first.",
|
|
1224
|
+
...failure.destructiveChanges ? { meta: {
|
|
1225
|
+
destructiveOperations: failure.destructiveChanges.destructiveOperations,
|
|
1226
|
+
databaseName: failure.destructiveChanges.databaseName,
|
|
1227
|
+
planHash: failure.destructiveChanges.planHash
|
|
1228
|
+
} } : {}
|
|
1229
|
+
});
|
|
1230
|
+
if (failure.code === "CONSENT_PLAN_MISMATCH") return errorConsentPlanMismatch({
|
|
1231
|
+
consentedPlanHash: failure.consentPlanMismatch?.consentedPlanHash ?? "",
|
|
1232
|
+
planHash: failure.consentPlanMismatch?.planHash ?? "",
|
|
1233
|
+
...ifDefined("why", failure.why)
|
|
1234
|
+
});
|
|
1235
|
+
const exhaustive = failure.code;
|
|
1236
|
+
return assertNever(exhaustive, `Unhandled DbUpdateFailure code: ${String(exhaustive)}`);
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* A token nobody can type is not consent: an empty one makes the engine's
|
|
1240
|
+
* type-to-confirm accept a bare Enter, and `--confirm ""` grant outright.
|
|
1241
|
+
*/
|
|
1242
|
+
function errorConsentTokenUnresolved(targetId) {
|
|
1243
|
+
return new CliStructuredError$1("CLI.CONSENT_TOKEN_UNRESOLVED", "The database this run would change has no name to confirm.", {
|
|
1244
|
+
why: `The connected database reports no name, and the target id "${targetId}" yields none either, so the consent prompt has nothing to ask you to type and the destructive operations cannot be authorised.`,
|
|
1245
|
+
nextActions: [{
|
|
1246
|
+
kind: "user-choice",
|
|
1247
|
+
label: "Name the database in `db.connection` (or pass `--db <url>`) and run `prisma-next db update` again."
|
|
1248
|
+
}]
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
/** A prompt that names nothing cannot be consented to knowingly. */
|
|
1252
|
+
function errorConsentOperationsMissing() {
|
|
1253
|
+
return new CliStructuredError$1("CLI.CONSENT_OPERATIONS_MISSING", "The plan was refused as destructive but named no operations to confirm.", {
|
|
1254
|
+
why: "The consent prompt has to list what is about to be destroyed, and the refusal carried no operations to list.",
|
|
1255
|
+
nextActions: [{
|
|
1256
|
+
kind: "run-command",
|
|
1257
|
+
label: "Preview the plan",
|
|
1258
|
+
command: "prisma-next db update --dry-run"
|
|
1259
|
+
}]
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
/** The question the user answers before anything is dropped. */
|
|
1263
|
+
function destructiveConsentQuestion(operations, token) {
|
|
1264
|
+
const listed = operations.map((operation) => ` - ${operation.label}`).join("\n");
|
|
1265
|
+
return [`Apply ${operations.length} destructive operation(s) to ${token}? Data they remove cannot be recovered:`, listed].join("\n");
|
|
1266
|
+
}
|
|
1267
|
+
function updatePresentations(inputs) {
|
|
1268
|
+
const { document, database, dryRun } = inputs;
|
|
1269
|
+
return {
|
|
1270
|
+
human: () => [{
|
|
1271
|
+
kind: "fields",
|
|
1272
|
+
rail: true,
|
|
1273
|
+
rows: [
|
|
1274
|
+
{
|
|
1275
|
+
label: "contract",
|
|
1276
|
+
value: inputs.contractPath
|
|
1277
|
+
},
|
|
1278
|
+
...database === void 0 ? [] : [{
|
|
1279
|
+
label: "database",
|
|
1280
|
+
value: database
|
|
1281
|
+
}],
|
|
1282
|
+
...inputs.to === void 0 ? [] : [{
|
|
1283
|
+
label: "to",
|
|
1284
|
+
value: inputs.to
|
|
1285
|
+
}],
|
|
1286
|
+
...dryRun ? [{
|
|
1287
|
+
label: "mode",
|
|
1288
|
+
value: "dry run"
|
|
1289
|
+
}] : []
|
|
1290
|
+
]
|
|
1291
|
+
}, ...migrationResultBlocks(document)],
|
|
1292
|
+
json: () => document,
|
|
1293
|
+
next: () => migrationResultNextActions(document, "prisma-next db update")
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
function updateDocument(inputs) {
|
|
1297
|
+
const { value } = inputs;
|
|
1298
|
+
return {
|
|
1299
|
+
ok: true,
|
|
1300
|
+
mode: value.mode,
|
|
1301
|
+
plan: {
|
|
1302
|
+
targetId: inputs.targetId,
|
|
1303
|
+
destination: {
|
|
1304
|
+
storageHash: value.destination.storageHash,
|
|
1305
|
+
...ifDefined("profileHash", value.destination.profileHash)
|
|
1306
|
+
},
|
|
1307
|
+
operations: value.plan.operations.map((operation) => ({
|
|
1308
|
+
id: operation.id,
|
|
1309
|
+
label: operation.label,
|
|
1310
|
+
operationClass: operation.operationClass
|
|
1311
|
+
})),
|
|
1312
|
+
...ifDefined("preview", value.plan.preview)
|
|
1313
|
+
},
|
|
1314
|
+
...value.execution === void 0 ? {} : { execution: {
|
|
1315
|
+
operationsPlanned: value.execution.operationsPlanned,
|
|
1316
|
+
operationsExecuted: value.execution.operationsExecuted
|
|
1317
|
+
} },
|
|
1318
|
+
...value.marker === void 0 ? {} : { marker: {
|
|
1319
|
+
storageHash: value.marker.storageHash,
|
|
1320
|
+
...ifDefined("profileHash", value.marker.profileHash)
|
|
1321
|
+
} },
|
|
1322
|
+
...ifDefined("perSpace", value.perSpace),
|
|
1323
|
+
...ifDefined("warnings", value.warnings),
|
|
1324
|
+
advancedRef: inputs.advancedRef,
|
|
1325
|
+
plannedAdvanceRef: inputs.plannedAdvanceRef,
|
|
1326
|
+
summary: value.summary,
|
|
1327
|
+
timings: { total: Date.now() - inputs.startedAt }
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
function createDbUpdateCommand(createClient) {
|
|
1331
|
+
return defineOrmCommand({
|
|
1332
|
+
help: {
|
|
1333
|
+
summary: "Update your database schema to match your contract",
|
|
1334
|
+
description: "Compares the database to the emitted contract and applies the changes that\nclose the gap, whether or not the database was bootstrapped with `db init`.\nAn operation that would destroy data is applied only with your consent: the\ncommand asks you to type the database name. A run with nobody to ask — a CI\njob, or `--no-interactive` — takes it from `--confirm <database>` instead.\nUse --dry-run to see the operations without applying them.",
|
|
1335
|
+
examples: [
|
|
1336
|
+
"db update",
|
|
1337
|
+
"db update --dry-run",
|
|
1338
|
+
"db update --no-interactive --confirm appdb",
|
|
1339
|
+
"db update --to production"
|
|
1340
|
+
]
|
|
1341
|
+
},
|
|
1342
|
+
args: { flags: {
|
|
1343
|
+
db: dbFlag,
|
|
1344
|
+
dryRun: flag.boolean({ brief: "Preview the planned operations without applying them" }),
|
|
1345
|
+
to: flag.string({
|
|
1346
|
+
brief: "Contract to update to (hash, prefix, ref name, migration dir name, or ./path)",
|
|
1347
|
+
placeholder: "contract"
|
|
1348
|
+
}),
|
|
1349
|
+
advanceRef: flag.string({
|
|
1350
|
+
brief: "Advance the named ref to the post-command contract hash",
|
|
1351
|
+
placeholder: "name"
|
|
1352
|
+
})
|
|
1353
|
+
} },
|
|
1354
|
+
needs: { config: ormConfigSection },
|
|
1355
|
+
handler: async (args, ctx) => {
|
|
1356
|
+
const startedAt = Date.now();
|
|
1357
|
+
const prepared = await prepareMigrationRun({
|
|
1358
|
+
config: ctx.config,
|
|
1359
|
+
cwd: ctx.cwd,
|
|
1360
|
+
db: args.flags.db,
|
|
1361
|
+
commandName: "db update",
|
|
1362
|
+
createClient
|
|
1363
|
+
});
|
|
1364
|
+
if (!prepared.ok) return notOk(prepared.failure);
|
|
1365
|
+
const { client, contractPath, dbConnection, migrationsDir, refsDir } = prepared.value;
|
|
1366
|
+
let contractJson = prepared.value.contractJson;
|
|
1367
|
+
let snapshotContractPath = contractPath;
|
|
1368
|
+
if (args.flags.to !== void 0) {
|
|
1369
|
+
const resolved = await resolveContractRefToSnapshot({
|
|
1370
|
+
config: ctx.config,
|
|
1371
|
+
migrationsDir,
|
|
1372
|
+
refInput: args.flags.to,
|
|
1373
|
+
contractPathAbsolute: contractPath,
|
|
1374
|
+
fallbackToEmitted: false,
|
|
1375
|
+
missingBundleFlag: "--to"
|
|
1376
|
+
});
|
|
1377
|
+
if (!resolved.ok) return notOk(normalizeError(resolved.failure));
|
|
1378
|
+
contractJson = resolved.value.contractJson;
|
|
1379
|
+
snapshotContractPath = resolved.value.contractJsonPath;
|
|
1380
|
+
}
|
|
1381
|
+
const mode = args.flags.dryRun ? "plan" : "apply";
|
|
1382
|
+
let document;
|
|
1383
|
+
try {
|
|
1384
|
+
await client.connect(dbConnection);
|
|
1385
|
+
const update = (consent) => client.dbUpdate({
|
|
1386
|
+
contract: contractJson,
|
|
1387
|
+
mode,
|
|
1388
|
+
migrationsDir,
|
|
1389
|
+
...consent === void 0 ? {} : { consent },
|
|
1390
|
+
onProgress: controlProgressReporter(ctx.report)
|
|
1391
|
+
});
|
|
1392
|
+
const reported = /* @__PURE__ */ new Set();
|
|
1393
|
+
const reportPlannerWarnings = (warnings) => {
|
|
1394
|
+
for (const warning of warnings) if (!reported.has(warning.summary)) {
|
|
1395
|
+
reported.add(warning.summary);
|
|
1396
|
+
ctx.report({
|
|
1397
|
+
kind: "message",
|
|
1398
|
+
severity: "warn",
|
|
1399
|
+
text: warning.summary
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
let result = await update();
|
|
1404
|
+
if (mode === "apply" && !result.ok && result.failure.code === "DESTRUCTIVE_CHANGES") {
|
|
1405
|
+
reportPlannerWarnings(result.failure.warnings ?? []);
|
|
1406
|
+
const refusal = result.failure.destructiveChanges;
|
|
1407
|
+
if (refusal === void 0 || refusal.destructiveOperations.length === 0) return notOk(normalizeError(errorConsentOperationsMissing()));
|
|
1408
|
+
const token = refusal.databaseName ?? "";
|
|
1409
|
+
if (token.trim().length === 0) return notOk(normalizeError(errorConsentTokenUnresolved(ctx.config.target.targetId)));
|
|
1410
|
+
if (!await ctx.prompt.consent(destructiveConsentQuestion(refusal.destructiveOperations, token), { token })) return notOk(normalizeError(mapDbUpdateFailure(result.failure)));
|
|
1411
|
+
result = await update({ planHash: refusal.planHash });
|
|
1412
|
+
}
|
|
1413
|
+
if (!result.ok) {
|
|
1414
|
+
reportPlannerWarnings(result.failure.warnings ?? []);
|
|
1415
|
+
return notOk(normalizeError(mapDbUpdateFailure(result.failure)));
|
|
1416
|
+
}
|
|
1417
|
+
const advancementHash = result.value.mode === "apply" ? result.value.marker?.storageHash ?? result.value.destination.storageHash : result.value.destination.storageHash;
|
|
1418
|
+
const advancement = await resolveRefAdvancementFields({
|
|
1419
|
+
...ifDefined("advanceRef", args.flags.advanceRef),
|
|
1420
|
+
...ifDefined("db", args.flags.db),
|
|
1421
|
+
refsDir,
|
|
1422
|
+
migrationsDir,
|
|
1423
|
+
contractJson,
|
|
1424
|
+
contractJsonPath: snapshotContractPath,
|
|
1425
|
+
mode: result.value.mode,
|
|
1426
|
+
hash: advancementHash
|
|
1427
|
+
});
|
|
1428
|
+
if (!advancement.ok) return notOk(normalizeError(advancement.failure));
|
|
1429
|
+
document = updateDocument({
|
|
1430
|
+
value: result.value,
|
|
1431
|
+
targetId: ctx.config.target.targetId,
|
|
1432
|
+
advancedRef: advancement.value.advancedRef,
|
|
1433
|
+
plannedAdvanceRef: advancement.value.plannedAdvanceRef,
|
|
1434
|
+
startedAt
|
|
1435
|
+
});
|
|
1436
|
+
} catch (error) {
|
|
1437
|
+
if (error instanceof CliStructuredError$1) throw error;
|
|
1438
|
+
if (CliStructuredError$1$1.is(error)) return notOk(normalizeError(error));
|
|
1439
|
+
if (isStructuredError(error) && error.code === "CONTRACT.VALIDATION_FAILED") return notOk(normalizeError(errorContractValidationFailed(`Contract validation failed: ${error.message}`, { where: { path: contractPath } })));
|
|
1440
|
+
const safeMessage = sanitizeErrorMessage(error instanceof Error ? error.message : String(error), typeof dbConnection === "string" ? dbConnection : void 0);
|
|
1441
|
+
return notOk(normalizeError(errorUnexpected(safeMessage, { why: `Unexpected error during db update: ${safeMessage}` })));
|
|
1442
|
+
} finally {
|
|
1443
|
+
await closeQuietly(client);
|
|
1444
|
+
}
|
|
1445
|
+
ctx.report({
|
|
1446
|
+
kind: "message",
|
|
1447
|
+
severity: "verbose",
|
|
1448
|
+
text: `Total time: ${document.timings.total}ms`
|
|
1449
|
+
});
|
|
1450
|
+
return ok(ctx.present({ data: document }, updatePresentations({
|
|
1451
|
+
document,
|
|
1452
|
+
contractPath: prepared.value.contractDisplayPath,
|
|
1453
|
+
database: prepared.value.database,
|
|
1454
|
+
to: args.flags.to,
|
|
1455
|
+
dryRun: args.flags.dryRun
|
|
1456
|
+
})));
|
|
1457
|
+
}
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
const dbUpdateCommand = createDbUpdateCommand(createControlClient);
|
|
1160
1461
|
const NOTHING_TO_FORMAT = "Nothing to format (contract source is not PSL).";
|
|
1161
1462
|
function formatPresentations(document, cwd) {
|
|
1162
1463
|
const path = document.path;
|
|
@@ -1195,15 +1496,122 @@ function createFormatCommand$1(execute = executeFormat) {
|
|
|
1195
1496
|
});
|
|
1196
1497
|
}
|
|
1197
1498
|
const formatCommand = createFormatCommand$1();
|
|
1198
|
-
|
|
1499
|
+
/** The invocation that finishes what a failed `init` phase started. */
|
|
1500
|
+
const EMIT_COMMAND = "prisma-next contract emit";
|
|
1199
1501
|
/**
|
|
1200
|
-
* The
|
|
1201
|
-
*
|
|
1202
|
-
*
|
|
1502
|
+
* The command's own record of a phase that failed after the scaffold was
|
|
1503
|
+
* written. The scaffold is on disk and the run has a result to report, so each
|
|
1504
|
+
* of these rides a completed envelope as a finding rather than replacing it.
|
|
1203
1505
|
*/
|
|
1204
|
-
function
|
|
1205
|
-
|
|
1206
|
-
|
|
1506
|
+
function initFinding(code, summary, parts) {
|
|
1507
|
+
return {
|
|
1508
|
+
code,
|
|
1509
|
+
severity: "error",
|
|
1510
|
+
summary,
|
|
1511
|
+
why: parts.why,
|
|
1512
|
+
nextActions: parts.nextActions,
|
|
1513
|
+
meta: parts.meta,
|
|
1514
|
+
docsUrl: docsUrlFor(code)
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
/**
|
|
1518
|
+
* The dependency install failed. The engine composed and announced the command
|
|
1519
|
+
* line, so its own next action — which carries the redacted spelling — is the
|
|
1520
|
+
* one that tells the user what to run; `init` adds the emit that was waiting
|
|
1521
|
+
* on it.
|
|
1522
|
+
*/
|
|
1523
|
+
function installFailedFinding(failure, filesWritten) {
|
|
1524
|
+
return initFinding("CLI.INIT_INSTALL_FAILED", "Failed to install dependencies", {
|
|
1525
|
+
why: failure.why ?? failure.message,
|
|
1526
|
+
nextActions: [...failure.nextActions, runCommandAction("Emit the contract once the dependencies are installed", EMIT_COMMAND)],
|
|
1527
|
+
meta: {
|
|
1528
|
+
filesWritten,
|
|
1529
|
+
install: failure.meta ?? {}
|
|
1530
|
+
}
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
/** The first emit failed against the freshly written scaffold. */
|
|
1534
|
+
function emitFailedFinding(cause, filesWritten) {
|
|
1535
|
+
return initFinding("CLI.INIT_EMIT_FAILED", "Failed to emit contract", {
|
|
1536
|
+
why: `\`${EMIT_COMMAND}\` failed: ${cause}`,
|
|
1537
|
+
nextActions: [chooseAction("Fix the problem the contract source reports, then emit again"), runCommandAction("Emit the contract", EMIT_COMMAND)],
|
|
1538
|
+
meta: {
|
|
1539
|
+
filesWritten,
|
|
1540
|
+
cause
|
|
1541
|
+
}
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* An agent-skill install failed; the project itself is complete without it.
|
|
1546
|
+
* The remedy is the skill-install commands themselves, which run against any
|
|
1547
|
+
* directory — advising a re-run of `init` would put the user's schema at risk
|
|
1548
|
+
* to fix something `init` no longer needs to be involved in.
|
|
1549
|
+
*/
|
|
1550
|
+
function skillInstallFailedFinding(failure, filesWritten, skillCommands) {
|
|
1551
|
+
return initFinding("CLI.INIT_SKILL_INSTALL_FAILED", "Failed to install Prisma Next skills", {
|
|
1552
|
+
why: failure.why ?? failure.message,
|
|
1553
|
+
nextActions: [...failure.nextActions, ...skillCommands.map((command) => runCommandAction("Install the Prisma Next skills", command))],
|
|
1554
|
+
meta: {
|
|
1555
|
+
filesWritten,
|
|
1556
|
+
skillInstall: failure.meta ?? {}
|
|
1557
|
+
}
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
function fileNodes(paths) {
|
|
1561
|
+
return paths.map((path) => ({
|
|
1562
|
+
label: path,
|
|
1563
|
+
tone: "identifier"
|
|
1564
|
+
}));
|
|
1565
|
+
}
|
|
1566
|
+
function scaffoldTree(document) {
|
|
1567
|
+
const roots = [{
|
|
1568
|
+
label: "written",
|
|
1569
|
+
tone: "heading",
|
|
1570
|
+
children: fileNodes(document.filesWritten)
|
|
1571
|
+
}];
|
|
1572
|
+
if (document.filesDeleted.length > 0) roots.push({
|
|
1573
|
+
label: "removed (stale artifacts and retired skill directories)",
|
|
1574
|
+
tone: "heading",
|
|
1575
|
+
children: fileNodes(document.filesDeleted)
|
|
1576
|
+
});
|
|
1577
|
+
const installed = document.packagesInstalled;
|
|
1578
|
+
if (installed.status === "installed") roots.push({
|
|
1579
|
+
label: "installed",
|
|
1580
|
+
tone: "heading",
|
|
1581
|
+
children: [...fileNodes(installed.deps), ...installed.devDeps.map((dep) => ({
|
|
1582
|
+
label: `${dep} (dev)`,
|
|
1583
|
+
tone: "identifier"
|
|
1584
|
+
}))]
|
|
1585
|
+
});
|
|
1586
|
+
return {
|
|
1587
|
+
kind: "tree",
|
|
1588
|
+
roots
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* The scaffold's follow-up steps, typed. `nextSteps` in the result document
|
|
1593
|
+
* keeps its numbered prose for consumers that already read it; these are the
|
|
1594
|
+
* same advice in the shape an agent can act on.
|
|
1595
|
+
*/
|
|
1596
|
+
function buildInitNextActions(inputs) {
|
|
1597
|
+
const actions = [chooseAction("Set DATABASE_URL in your environment (export it or add it to .env)")];
|
|
1598
|
+
if (!inputs.contractEmitted) actions.push(runCommandAction("Emit the contract", EMIT_COMMAND));
|
|
1599
|
+
actions.push({
|
|
1600
|
+
kind: "edit-file",
|
|
1601
|
+
label: `Edit your schema at ${inputs.schemaPath}, then emit again`
|
|
1602
|
+
});
|
|
1603
|
+
actions.push(chooseAction("Open prisma-next.md for a quick reference on writing your first typed query"));
|
|
1604
|
+
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"));
|
|
1605
|
+
return actions;
|
|
1606
|
+
}
|
|
1607
|
+
const DONE_TEXT = "Done. Open prisma-next.md to get started.";
|
|
1608
|
+
const INCOMPLETE_TEXT = "Scaffold written. Finish the steps above to complete setup.";
|
|
1609
|
+
/**
|
|
1610
|
+
* `init` writes files rather than data another program reads, so it supplies
|
|
1611
|
+
* no `stdout` payload: human mode writes nothing to stdout.
|
|
1612
|
+
*/
|
|
1613
|
+
function initPresentations(inputs) {
|
|
1614
|
+
const { document } = inputs;
|
|
1207
1615
|
return {
|
|
1208
1616
|
human: () => [
|
|
1209
1617
|
{
|
|
@@ -1211,44 +1619,849 @@ function showPresentations$1(inputs) {
|
|
|
1211
1619
|
rail: true,
|
|
1212
1620
|
rows: [
|
|
1213
1621
|
{
|
|
1214
|
-
label: "
|
|
1215
|
-
value:
|
|
1622
|
+
label: "target",
|
|
1623
|
+
value: document.target
|
|
1216
1624
|
},
|
|
1217
|
-
|
|
1218
|
-
label: "
|
|
1219
|
-
value:
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
label: "
|
|
1223
|
-
value:
|
|
1224
|
-
}
|
|
1225
|
-
...inputs.to === void 0 ? [] : [{
|
|
1226
|
-
label: "to",
|
|
1227
|
-
value: inputs.to
|
|
1228
|
-
}]
|
|
1625
|
+
{
|
|
1626
|
+
label: "authoring",
|
|
1627
|
+
value: document.authoring
|
|
1628
|
+
},
|
|
1629
|
+
{
|
|
1630
|
+
label: "schema",
|
|
1631
|
+
value: document.schemaPath
|
|
1632
|
+
}
|
|
1229
1633
|
]
|
|
1230
1634
|
},
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
lines: toneDrawing(graph)
|
|
1234
|
-
}],
|
|
1235
|
-
...count === 0 ? [{
|
|
1635
|
+
scaffoldTree(document),
|
|
1636
|
+
inputs.complete ? {
|
|
1236
1637
|
kind: "summary",
|
|
1237
|
-
status: "
|
|
1238
|
-
text:
|
|
1239
|
-
}
|
|
1638
|
+
status: "ok",
|
|
1639
|
+
text: DONE_TEXT
|
|
1640
|
+
} : {
|
|
1240
1641
|
kind: "summary",
|
|
1241
|
-
status: "
|
|
1242
|
-
text:
|
|
1243
|
-
}
|
|
1244
|
-
kind: "drawing",
|
|
1245
|
-
lines: toneDrawing(runList.join("\n"))
|
|
1246
|
-
}]
|
|
1642
|
+
status: "warn",
|
|
1643
|
+
text: INCOMPLETE_TEXT
|
|
1644
|
+
}
|
|
1247
1645
|
],
|
|
1248
|
-
json: () => document
|
|
1646
|
+
json: () => document,
|
|
1647
|
+
next: () => inputs.nextActions
|
|
1249
1648
|
};
|
|
1250
1649
|
}
|
|
1251
|
-
|
|
1650
|
+
/**
|
|
1651
|
+
* Emits the contract for the project `init` has just scaffolded.
|
|
1652
|
+
*
|
|
1653
|
+
* The config file is the one this run wrote, so it is read here rather than
|
|
1654
|
+
* through `needs.config`: `init` runs where no config exists yet, and the
|
|
1655
|
+
* engine loads a section before the handler starts. The emitter and the config
|
|
1656
|
+
* loader are imported at execution time so a run that skips the install — and
|
|
1657
|
+
* therefore skips the emit — never pays for either.
|
|
1658
|
+
*/
|
|
1659
|
+
async function emitScaffoldedContract(ctx) {
|
|
1660
|
+
const { executeContractEmit } = await import("./contract-emit-Bzt11N4v-BQ2Pr8KG.mjs");
|
|
1661
|
+
const { loadConfigForSections } = await import("./exports-BZLNbtkY.mjs").then((n) => n.t);
|
|
1662
|
+
const configPath = join(ctx.cwd, "prisma-next.config.ts");
|
|
1663
|
+
const loaded = await loadConfigForSections(configPath, [
|
|
1664
|
+
"contract",
|
|
1665
|
+
"family",
|
|
1666
|
+
"target",
|
|
1667
|
+
"adapter",
|
|
1668
|
+
"extensions"
|
|
1669
|
+
]);
|
|
1670
|
+
if (!loaded.ok) throw loaded.failure;
|
|
1671
|
+
await executeContractEmit({
|
|
1672
|
+
config: loaded.value,
|
|
1673
|
+
cwd: ctx.cwd,
|
|
1674
|
+
configPath
|
|
1675
|
+
});
|
|
1676
|
+
}
|
|
1677
|
+
const CONFIG_FILE = "prisma-next.config.ts";
|
|
1678
|
+
const QUICK_REFERENCE_FILE = "prisma-next.md";
|
|
1679
|
+
const ENV_EXAMPLE_FILE = ".env.example";
|
|
1680
|
+
/**
|
|
1681
|
+
* The generated files a run replaces outright, in the order it writes them.
|
|
1682
|
+
* Everything else the scaffold touches it merges (`tsconfig.json`,
|
|
1683
|
+
* `package.json`, the git files) or keeps (`.env`, `README.md`), so replacing
|
|
1684
|
+
* one of these is the destructive act consent is asked for.
|
|
1685
|
+
*
|
|
1686
|
+
* `.env.example` is written the same way but deliberately left out: it is a
|
|
1687
|
+
* conventional filename in projects that have never seen Prisma Next, and a
|
|
1688
|
+
* consent token demanded for it would fire on first runs. The scaffold warns
|
|
1689
|
+
* when it replaces one instead.
|
|
1690
|
+
*/
|
|
1691
|
+
function generatedFilesInitReplaces(schemaPath) {
|
|
1692
|
+
return [
|
|
1693
|
+
schemaPath,
|
|
1694
|
+
CONFIG_FILE,
|
|
1695
|
+
join(dirname(schemaPath), "db.ts"),
|
|
1696
|
+
QUICK_REFERENCE_FILE
|
|
1697
|
+
];
|
|
1698
|
+
}
|
|
1699
|
+
const MANAGERS = /* @__PURE__ */ new Set([
|
|
1700
|
+
"pnpm",
|
|
1701
|
+
"npm",
|
|
1702
|
+
"yarn",
|
|
1703
|
+
"bun",
|
|
1704
|
+
"deno"
|
|
1705
|
+
]);
|
|
1706
|
+
function isPackageManager(name) {
|
|
1707
|
+
return MANAGERS.has(name);
|
|
1708
|
+
}
|
|
1709
|
+
/**
|
|
1710
|
+
* The manager the scaffolded quick reference and README name in their example
|
|
1711
|
+
* commands. It resolves the way the engine's package operations resolve theirs
|
|
1712
|
+
* — the project at `cwd` first, then the manager that invoked this process —
|
|
1713
|
+
* so the documentation `init` writes names the manager `init` drove. The
|
|
1714
|
+
* capability itself reports no manager back on success, which is why this is
|
|
1715
|
+
* worked out a second time here.
|
|
1716
|
+
*/
|
|
1717
|
+
async function resolveScaffoldPackageManager(ctx) {
|
|
1718
|
+
const detected = await detect({ cwd: ctx.cwd });
|
|
1719
|
+
if (detected !== null && isPackageManager(detected.name)) return detected.name;
|
|
1720
|
+
const invoking = ctx.env["npm_config_user_agent"]?.split("/")[0];
|
|
1721
|
+
return invoking !== void 0 && isPackageManager(invoking) ? invoking : "npm";
|
|
1722
|
+
}
|
|
1723
|
+
/**
|
|
1724
|
+
* True when the parsed manifest declares `name` directly in `dependencies` or
|
|
1725
|
+
* `devDependencies`. Transitive presence is deliberately ignored: detecting it
|
|
1726
|
+
* needs the lockfile, and the realistic risk is a direct pin being clobbered.
|
|
1727
|
+
*/
|
|
1728
|
+
function hasDirectDep(parsed, name) {
|
|
1729
|
+
for (const field of ["dependencies", "devDependencies"]) {
|
|
1730
|
+
const value = parsed[field];
|
|
1731
|
+
if (value !== null && typeof value === "object" && name in value) return true;
|
|
1732
|
+
}
|
|
1733
|
+
return false;
|
|
1734
|
+
}
|
|
1735
|
+
/**
|
|
1736
|
+
* npm package names are lowercase and URL-safe; `basename(cwd)` happily
|
|
1737
|
+
* returns "My Project" or ".hidden", both of which npm refuses to read.
|
|
1738
|
+
*/
|
|
1739
|
+
function sanitisePackageName(raw) {
|
|
1740
|
+
const trimmed = raw.toLowerCase().replace(/[^a-z0-9._~-]/g, "-").replace(/^[._-]+/, "").replace(/-+/g, "-");
|
|
1741
|
+
return trimmed.length > 0 ? trimmed : "my-app";
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* The manifest `init` writes for a directory that has none. Private, so a
|
|
1745
|
+
* stray publish cannot leak the placeholder, and ESM so the scaffolded import
|
|
1746
|
+
* attributes load under Node's loader.
|
|
1747
|
+
*/
|
|
1748
|
+
function defaultPackageJsonContent(rawName) {
|
|
1749
|
+
return `${JSON.stringify({
|
|
1750
|
+
name: sanitisePackageName(rawName),
|
|
1751
|
+
version: "0.0.0",
|
|
1752
|
+
private: true,
|
|
1753
|
+
type: "module"
|
|
1754
|
+
}, null, 2)}\n`;
|
|
1755
|
+
}
|
|
1756
|
+
function hasProjectManifest(cwd) {
|
|
1757
|
+
return existsSync(join(cwd, "package.json")) || existsSync(join(cwd, "deno.json")) || existsSync(join(cwd, "deno.jsonc"));
|
|
1758
|
+
}
|
|
1759
|
+
/**
|
|
1760
|
+
* Computes every write before touching disk: reads and parses each file the
|
|
1761
|
+
* scaffold merges with, so a malformed manifest or tsconfig fails with the
|
|
1762
|
+
* user's project byte-identical to how it started.
|
|
1763
|
+
*/
|
|
1764
|
+
function planScaffold(ctx) {
|
|
1765
|
+
const { cwd, inputs, packageManager, resolveImportSpecifier } = ctx;
|
|
1766
|
+
const warnings = [];
|
|
1767
|
+
const schemaDir = dirname(inputs.schemaPath);
|
|
1768
|
+
const configContractPath = isAbsolute(inputs.schemaPath) ? inputs.schemaPath : `./${inputs.schemaPath}`;
|
|
1769
|
+
const runPrefix = formatRunCommand(packageManager, "prisma-next", "").trimEnd();
|
|
1770
|
+
const files = [
|
|
1771
|
+
{
|
|
1772
|
+
path: inputs.schemaPath,
|
|
1773
|
+
content: starterSchema(inputs.target, inputs.authoring, resolveImportSpecifier)
|
|
1774
|
+
},
|
|
1775
|
+
{
|
|
1776
|
+
path: CONFIG_FILE,
|
|
1777
|
+
content: configFile(inputs.target, configContractPath, resolveImportSpecifier)
|
|
1778
|
+
},
|
|
1779
|
+
{
|
|
1780
|
+
path: join(schemaDir, "db.ts"),
|
|
1781
|
+
content: dbFile(inputs.target, resolveImportSpecifier)
|
|
1782
|
+
},
|
|
1783
|
+
{
|
|
1784
|
+
path: QUICK_REFERENCE_FILE,
|
|
1785
|
+
content: quickReferenceMd(inputs.target, inputs.authoring, inputs.schemaPath, runPrefix, resolveImportSpecifier)
|
|
1786
|
+
},
|
|
1787
|
+
{
|
|
1788
|
+
path: ENV_EXAMPLE_FILE,
|
|
1789
|
+
content: envExampleContent(inputs.target)
|
|
1790
|
+
}
|
|
1791
|
+
];
|
|
1792
|
+
if (existsSync(join(cwd, ENV_EXAMPLE_FILE))) warnings.push(`${ENV_EXAMPLE_FILE} already existed and was replaced with the Prisma Next template.`);
|
|
1793
|
+
const filesToDelete = inputs.reinit ? [...findStaleArtifacts(cwd, schemaDir)] : [];
|
|
1794
|
+
const dirsToDelete = legacySkillDirs().filter((rel) => existsSync(join(cwd, rel)));
|
|
1795
|
+
if (inputs.writeEnv) if (existsSync(join(cwd, ".env"))) warnings.push(".env already exists; leaving it untouched. Compare with .env.example for any new keys.");
|
|
1796
|
+
else files.push({
|
|
1797
|
+
path: ".env",
|
|
1798
|
+
content: envFileContent(inputs.target)
|
|
1799
|
+
});
|
|
1800
|
+
const tsconfigPath = join(cwd, "tsconfig.json");
|
|
1801
|
+
if (existsSync(tsconfigPath)) {
|
|
1802
|
+
const existing = readFileSync(tsconfigPath, "utf-8");
|
|
1803
|
+
let merged;
|
|
1804
|
+
try {
|
|
1805
|
+
merged = mergeTsConfig(existing);
|
|
1806
|
+
} catch (error) {
|
|
1807
|
+
if (error instanceof TsConfigParseError) throw errorInitInvalidTsconfig({
|
|
1808
|
+
path: "tsconfig.json",
|
|
1809
|
+
cause: error.message
|
|
1810
|
+
});
|
|
1811
|
+
throw error;
|
|
1812
|
+
}
|
|
1813
|
+
files.push({
|
|
1814
|
+
path: "tsconfig.json",
|
|
1815
|
+
content: merged,
|
|
1816
|
+
note: "Updated tsconfig.json with required compiler options."
|
|
1817
|
+
});
|
|
1818
|
+
} else files.push({
|
|
1819
|
+
path: "tsconfig.json",
|
|
1820
|
+
content: defaultTsConfig()
|
|
1821
|
+
});
|
|
1822
|
+
const gitignorePath = join(cwd, ".gitignore");
|
|
1823
|
+
const nextGitignore = mergeGitignore(existsSync(gitignorePath) ? readFileSync(gitignorePath, "utf-8") : void 0);
|
|
1824
|
+
if (nextGitignore !== null) files.push({
|
|
1825
|
+
path: ".gitignore",
|
|
1826
|
+
content: nextGitignore
|
|
1827
|
+
});
|
|
1828
|
+
const gitattributesPath = join(cwd, ".gitattributes");
|
|
1829
|
+
const nextGitattributes = mergeGitattributes(existsSync(gitattributesPath) ? readFileSync(gitattributesPath, "utf-8") : void 0, requiredGitattributesLines(schemaDir, inputs.target));
|
|
1830
|
+
if (nextGitattributes !== null) files.push({
|
|
1831
|
+
path: ".gitattributes",
|
|
1832
|
+
content: nextGitattributes
|
|
1833
|
+
});
|
|
1834
|
+
const manifestPath = join(cwd, "package.json");
|
|
1835
|
+
const manifestExisted = existsSync(manifestPath);
|
|
1836
|
+
const synthesiseManifest = !manifestExisted && !hasProjectManifest(cwd);
|
|
1837
|
+
let parsedManifest = null;
|
|
1838
|
+
if (manifestExisted || synthesiseManifest) {
|
|
1839
|
+
const raw = manifestExisted ? readFileSync(manifestPath, "utf-8") : defaultPackageJsonContent(basename(cwd));
|
|
1840
|
+
try {
|
|
1841
|
+
parsedManifest = JSON.parse(raw);
|
|
1842
|
+
} catch (error) {
|
|
1843
|
+
if (error instanceof SyntaxError) throw errorInitInvalidManifest({
|
|
1844
|
+
path: "package.json",
|
|
1845
|
+
cause: error.message
|
|
1846
|
+
});
|
|
1847
|
+
throw error;
|
|
1848
|
+
}
|
|
1849
|
+
let working = raw;
|
|
1850
|
+
let changed = synthesiseManifest;
|
|
1851
|
+
if (inputs.removePreviousFacade !== null) {
|
|
1852
|
+
const next = removeDependency(working, inputs.removePreviousFacade);
|
|
1853
|
+
if (next !== null) {
|
|
1854
|
+
working = next;
|
|
1855
|
+
changed = true;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
const { content: withScripts, warnings: scriptWarnings } = mergePackageScripts(working, REQUIRED_SCRIPTS);
|
|
1859
|
+
if (withScripts !== null) {
|
|
1860
|
+
working = withScripts;
|
|
1861
|
+
changed = true;
|
|
1862
|
+
}
|
|
1863
|
+
const { content: withType, warning: typeWarning } = ensureEsmModuleType(working);
|
|
1864
|
+
if (withType !== null) {
|
|
1865
|
+
working = withType;
|
|
1866
|
+
changed = true;
|
|
1867
|
+
}
|
|
1868
|
+
if (changed) files.push({
|
|
1869
|
+
path: "package.json",
|
|
1870
|
+
content: working
|
|
1871
|
+
});
|
|
1872
|
+
warnings.push(...scriptWarnings);
|
|
1873
|
+
if (typeWarning !== null) warnings.push(typeWarning);
|
|
1874
|
+
if (synthesiseManifest) warnings.push("No package.json found in the target directory; created a minimal one. Edit `name` / `version` to taste.");
|
|
1875
|
+
}
|
|
1876
|
+
if (existsSync(join(cwd, "src/index.ts"))) if (existsSync(join(cwd, "README.md"))) warnings.push("README.md already exists; leaving it untouched.");
|
|
1877
|
+
else {
|
|
1878
|
+
const declaredName = parsedManifest !== null && typeof parsedManifest["name"] === "string" ? parsedManifest["name"] : basename(cwd);
|
|
1879
|
+
files.push({
|
|
1880
|
+
path: "README.md",
|
|
1881
|
+
content: minimalProjectReadmeMd(inputs.target, inputs.schemaPath, sanitisePackageName(declaredName), packageManager)
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1884
|
+
return {
|
|
1885
|
+
files,
|
|
1886
|
+
filesToDelete,
|
|
1887
|
+
dirsToDelete,
|
|
1888
|
+
warnings,
|
|
1889
|
+
hasTypesNode: parsedManifest !== null && hasDirectDep(parsedManifest, "@types/node")
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Plans every write, then performs them. Everything that can fail on the
|
|
1894
|
+
* user's existing files fails during planning, so a failure leaves the project
|
|
1895
|
+
* as it was; after the first write, the scaffold is on disk to keep, and a
|
|
1896
|
+
* write that fails partway names the files that already landed.
|
|
1897
|
+
*/
|
|
1898
|
+
function scaffoldProject(ctx) {
|
|
1899
|
+
const resolveImportSpecifier = scaffoldSpecifierResolverFor(ctx.inputs.target);
|
|
1900
|
+
const plan = planScaffold({
|
|
1901
|
+
...ctx,
|
|
1902
|
+
resolveImportSpecifier
|
|
1903
|
+
});
|
|
1904
|
+
const filesWritten = [];
|
|
1905
|
+
const filesDeleted = [];
|
|
1906
|
+
const notes = [];
|
|
1907
|
+
for (const file of plan.files) {
|
|
1908
|
+
const target = join(ctx.cwd, file.path);
|
|
1909
|
+
try {
|
|
1910
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
1911
|
+
writeFileSync(target, file.content, "utf-8");
|
|
1912
|
+
} catch (error) {
|
|
1913
|
+
throw errorInitWriteFailed({
|
|
1914
|
+
path: file.path,
|
|
1915
|
+
cause: error instanceof Error ? error.message : String(error),
|
|
1916
|
+
filesWritten
|
|
1917
|
+
});
|
|
1918
|
+
}
|
|
1919
|
+
filesWritten.push(file.path);
|
|
1920
|
+
if (file.note !== void 0) notes.push(file.note);
|
|
1921
|
+
}
|
|
1922
|
+
for (const rel of plan.filesToDelete) {
|
|
1923
|
+
const target = join(ctx.cwd, rel);
|
|
1924
|
+
if (!existsSync(target)) continue;
|
|
1925
|
+
try {
|
|
1926
|
+
unlinkSync(target);
|
|
1927
|
+
filesDeleted.push(rel);
|
|
1928
|
+
} catch (error) {
|
|
1929
|
+
if (!(error instanceof Error && Reflect.get(error, "code") === "ENOENT")) throw error;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
for (const rel of plan.dirsToDelete) {
|
|
1933
|
+
rmSync(join(ctx.cwd, rel), {
|
|
1934
|
+
recursive: true,
|
|
1935
|
+
force: true
|
|
1936
|
+
});
|
|
1937
|
+
filesDeleted.push(rel);
|
|
1938
|
+
}
|
|
1939
|
+
return {
|
|
1940
|
+
filesWritten,
|
|
1941
|
+
filesDeleted,
|
|
1942
|
+
warnings: plan.warnings,
|
|
1943
|
+
notes,
|
|
1944
|
+
hasTypesNode: plan.hasTypesNode,
|
|
1945
|
+
resolveImportSpecifier
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
const REQUIRED_FLAG_PROMPTS = /* @__PURE__ */ new Set(["target", "authoring"]);
|
|
1949
|
+
function formatFileList(files) {
|
|
1950
|
+
const last = files.at(-1) ?? "";
|
|
1951
|
+
return files.length <= 1 ? last : `${files.slice(0, -1).join(", ")} and ${last}`;
|
|
1952
|
+
}
|
|
1953
|
+
/**
|
|
1954
|
+
* Names the files at stake rather than the category they belong to: the user
|
|
1955
|
+
* knows `contract.prisma`, not "every generated file".
|
|
1956
|
+
*/
|
|
1957
|
+
function consentQuestion(replaced) {
|
|
1958
|
+
const written = replaced.length === 1 ? "it" : "them";
|
|
1959
|
+
return `Re-initializing replaces ${formatFileList(replaced)} with a fresh scaffold, losing anything you wrote in ${written}.`;
|
|
1960
|
+
}
|
|
1961
|
+
/**
|
|
1962
|
+
* The engine raises this when a prompt has no default and the session cannot
|
|
1963
|
+
* show it. For the two prompts that stand in for a required flag, that is the
|
|
1964
|
+
* same condition `init` has always reported as a missing flag.
|
|
1965
|
+
*/
|
|
1966
|
+
function isPromptRequired(error) {
|
|
1967
|
+
return error instanceof Error && Reflect.get(error, "code") === "CLI.PROMPT_REQUIRED";
|
|
1968
|
+
}
|
|
1969
|
+
/**
|
|
1970
|
+
* `--confirm` matches a token, so the token has to be something the user can
|
|
1971
|
+
* see and type. The working directory's name is it; a directory with no name
|
|
1972
|
+
* of its own (the filesystem root) falls back to its path.
|
|
1973
|
+
*
|
|
1974
|
+
* Trimmed, because the engine compares the typed answer trimmed: an untrimmed
|
|
1975
|
+
* token from a directory named `my app ` is one no keystroke sequence matches.
|
|
1976
|
+
*/
|
|
1977
|
+
function consentToken(cwd) {
|
|
1978
|
+
const name = basename(cwd).trim();
|
|
1979
|
+
return name.length > 0 ? name : cwd.trim();
|
|
1980
|
+
}
|
|
1981
|
+
async function askTarget(prompt) {
|
|
1982
|
+
return prompt.select("What database are you using?", [{
|
|
1983
|
+
value: "postgres",
|
|
1984
|
+
label: "PostgreSQL"
|
|
1985
|
+
}, {
|
|
1986
|
+
value: "mongo",
|
|
1987
|
+
label: "MongoDB"
|
|
1988
|
+
}]);
|
|
1989
|
+
}
|
|
1990
|
+
async function askAuthoring(prompt) {
|
|
1991
|
+
return prompt.select("How do you want to write your schema?", [{
|
|
1992
|
+
value: "psl",
|
|
1993
|
+
label: "Prisma Schema Language (.prisma)"
|
|
1994
|
+
}, {
|
|
1995
|
+
value: "typescript",
|
|
1996
|
+
label: "TypeScript (.ts)"
|
|
1997
|
+
}]);
|
|
1998
|
+
}
|
|
1999
|
+
/**
|
|
2000
|
+
* The schema path the user typed goes through the same validator the flag
|
|
2001
|
+
* does, so an answer the engine accepted still has to agree with the authoring
|
|
2002
|
+
* style.
|
|
2003
|
+
*/
|
|
2004
|
+
async function askSchemaPath(prompt, authoring) {
|
|
2005
|
+
return validateSchemaPath(await prompt.text("Where should the schema file go?", { default: defaultSchemaPath(authoring) }), authoring);
|
|
2006
|
+
}
|
|
2007
|
+
/**
|
|
2008
|
+
* The facade a previous `init` installed for the other target, when this run
|
|
2009
|
+
* switches targets and one is still declared. Reads every name a previous run
|
|
2010
|
+
* could have written, because older versions scaffolded the workspace name.
|
|
2011
|
+
*/
|
|
2012
|
+
function previousFacade(cwd, target) {
|
|
2013
|
+
const manifestPath = join(cwd, "package.json");
|
|
2014
|
+
if (!existsSync(manifestPath)) return;
|
|
2015
|
+
const otherTarget = target === "postgres" ? "mongo" : "postgres";
|
|
2016
|
+
const candidates = [targetPackageName(otherTarget, scaffoldSpecifierResolverFor(otherTarget)), targetPackageName(otherTarget)];
|
|
2017
|
+
let parsed;
|
|
2018
|
+
try {
|
|
2019
|
+
parsed = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
2020
|
+
} catch {
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
const deps = parsed["dependencies"];
|
|
2024
|
+
if (deps === null || typeof deps !== "object" || Array.isArray(deps)) return;
|
|
2025
|
+
const declared = deps;
|
|
2026
|
+
return candidates.find((name) => Object.hasOwn(declared, name));
|
|
2027
|
+
}
|
|
2028
|
+
async function resolveRemovePreviousFacade(ctx) {
|
|
2029
|
+
if (!ctx.reinit || ctx.keepPreviousFacade) return null;
|
|
2030
|
+
const facade = previousFacade(ctx.cwd, ctx.target);
|
|
2031
|
+
if (facade === void 0) return null;
|
|
2032
|
+
const otherTarget = ctx.target === "postgres" ? "mongo" : "postgres";
|
|
2033
|
+
return await ctx.prompt.confirm(`Switching from ${targetLabel(otherTarget)} to ${targetLabel(ctx.target)} — remove ${facade} from package.json dependencies?`, { default: true }) ? facade : null;
|
|
2034
|
+
}
|
|
2035
|
+
/**
|
|
2036
|
+
* Resolves every input from the flags and, where a flag is absent, from the
|
|
2037
|
+
* engine's prompt surface.
|
|
2038
|
+
*
|
|
2039
|
+
* Order matters twice. Every flag value is validated before anything is asked,
|
|
2040
|
+
* so a typo costs a typo rather than a typed consent token. And consent comes
|
|
2041
|
+
* as soon as the schema path is known, because that is the point at which the
|
|
2042
|
+
* question can name the files it is about — and before any question whose
|
|
2043
|
+
* answer only matters if the run proceeds.
|
|
2044
|
+
*/
|
|
2045
|
+
async function resolveInitInputs(ctx) {
|
|
2046
|
+
const { cwd, flags, prompt } = ctx;
|
|
2047
|
+
if (flags.strictProbe && !flags.probeDb) throw errorInitStrictProbeWithoutProbe();
|
|
2048
|
+
const flagTarget = resolveTarget(flags.target);
|
|
2049
|
+
const flagAuthoring = resolveAuthoring(flags.authoring);
|
|
2050
|
+
let target;
|
|
2051
|
+
let authoring;
|
|
2052
|
+
try {
|
|
2053
|
+
target = flagTarget ?? await askTarget(prompt);
|
|
2054
|
+
authoring = flagAuthoring ?? await askAuthoring(prompt);
|
|
2055
|
+
} catch (error) {
|
|
2056
|
+
if (!isPromptRequired(error)) throw error;
|
|
2057
|
+
throw errorInitMissingFlags({
|
|
2058
|
+
missing: [...flagTarget === void 0 ? ["target"] : [], ...flagAuthoring === void 0 ? ["authoring"] : []].filter((flag) => REQUIRED_FLAG_PROMPTS.has(flag)),
|
|
2059
|
+
why: "This session cannot prompt, so the answers have to arrive as flags."
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
const schemaPath = flags.schemaPath !== void 0 ? validateSchemaPath(flags.schemaPath, authoring) : await askSchemaPath(prompt, authoring);
|
|
2063
|
+
const replaced = generatedFilesInitReplaces(schemaPath).filter((relative) => existsSync(join(cwd, relative)));
|
|
2064
|
+
const reinit = replaced.length > 0;
|
|
2065
|
+
if (reinit) {
|
|
2066
|
+
if (!await prompt.consent(consentQuestion(replaced), { token: consentToken(cwd) })) throw errorInitUserAborted();
|
|
2067
|
+
}
|
|
2068
|
+
const writeEnv = flags.writeEnv || await prompt.confirm("Also write a .env file from .env.example? (gitignored)", { default: false });
|
|
2069
|
+
const removePreviousFacade = await resolveRemovePreviousFacade({
|
|
2070
|
+
cwd,
|
|
2071
|
+
target,
|
|
2072
|
+
reinit,
|
|
2073
|
+
keepPreviousFacade: flags.keepPreviousFacade,
|
|
2074
|
+
prompt
|
|
2075
|
+
});
|
|
2076
|
+
return {
|
|
2077
|
+
target,
|
|
2078
|
+
authoring,
|
|
2079
|
+
schemaPath,
|
|
2080
|
+
install: !flags.skipInstall,
|
|
2081
|
+
writeEnv,
|
|
2082
|
+
probeDb: flags.probeDb,
|
|
2083
|
+
strictProbe: flags.strictProbe,
|
|
2084
|
+
reinit,
|
|
2085
|
+
removePreviousFacade,
|
|
2086
|
+
installProjectSkill: !flags.skipSkills
|
|
2087
|
+
};
|
|
2088
|
+
}
|
|
2089
|
+
function metaString(failure, key) {
|
|
2090
|
+
const value = failure.meta?.[key];
|
|
2091
|
+
return typeof value === "string" ? value : "";
|
|
2092
|
+
}
|
|
2093
|
+
/**
|
|
2094
|
+
* pnpm reported a specifier the published artifact leaked, which npm installs
|
|
2095
|
+
* happily. The engine redacts credentials out of the stderr it returns but
|
|
2096
|
+
* leaves error codes alone, which is what this reads.
|
|
2097
|
+
*/
|
|
2098
|
+
function pnpmLeakedASpecifier(failure) {
|
|
2099
|
+
return metaString(failure, "manager") === "pnpm" && isRecognisedPnpmResolutionError(metaString(failure, "stderrTail"));
|
|
2100
|
+
}
|
|
2101
|
+
/**
|
|
2102
|
+
* The engine redacts the stderr it hands back, and this redacts it again
|
|
2103
|
+
* before quoting it: what the engine strips is its own business, and registry
|
|
2104
|
+
* auth material is not all URL-shaped.
|
|
2105
|
+
*/
|
|
2106
|
+
function fallbackWarning(failure) {
|
|
2107
|
+
const firstLine = redactSecrets(metaString(failure, "stderrTail")).trim().split("\n")[0] ?? "";
|
|
2108
|
+
return [
|
|
2109
|
+
"pnpm could not install: a published Prisma Next dependency leaked a `workspace:*` or `catalog:` specifier.",
|
|
2110
|
+
"Falling back to npm so init can complete.",
|
|
2111
|
+
firstLine === "" ? "" : ` pnpm error: ${firstLine}`,
|
|
2112
|
+
"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.",
|
|
2113
|
+
"Once the offending package republishes a clean version, re-run `pnpm install` to switch back."
|
|
2114
|
+
].filter((line) => line !== "").join("\n");
|
|
2115
|
+
}
|
|
2116
|
+
/** The pnpm failure the npm retry was meant to recover from, kept alongside the npm one. */
|
|
2117
|
+
function retriedWarning(failure) {
|
|
2118
|
+
const firstLine = redactSecrets(metaString(failure, "stderrTail")).trim().split("\n")[0] ?? "";
|
|
2119
|
+
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");
|
|
2120
|
+
}
|
|
2121
|
+
/**
|
|
2122
|
+
* Adds the runtime and development dependencies through the engine's package
|
|
2123
|
+
* manager. The retry is `init`'s alone: the engine spells and runs the
|
|
2124
|
+
* command, and this decides — from the stderr it returned — that another
|
|
2125
|
+
* manager is worth a try.
|
|
2126
|
+
*/
|
|
2127
|
+
async function installProjectDependencies(ctx) {
|
|
2128
|
+
const pair = async (manager) => {
|
|
2129
|
+
const runtimeDeps = await ctx.packages.install({
|
|
2130
|
+
packages: ctx.deps,
|
|
2131
|
+
cwd: ctx.cwd,
|
|
2132
|
+
...ifDefined("manager", manager)
|
|
2133
|
+
});
|
|
2134
|
+
if (!runtimeDeps.ok) return runtimeDeps.failure;
|
|
2135
|
+
const developmentDeps = await ctx.packages.install({
|
|
2136
|
+
packages: ctx.devDeps,
|
|
2137
|
+
dev: true,
|
|
2138
|
+
cwd: ctx.cwd,
|
|
2139
|
+
...ifDefined("manager", manager)
|
|
2140
|
+
});
|
|
2141
|
+
return developmentDeps.ok ? void 0 : developmentDeps.failure;
|
|
2142
|
+
};
|
|
2143
|
+
const failure = await pair();
|
|
2144
|
+
if (failure === void 0) return {
|
|
2145
|
+
failure: void 0,
|
|
2146
|
+
manager: void 0,
|
|
2147
|
+
warnings: ctx.catalogWarnings
|
|
2148
|
+
};
|
|
2149
|
+
if (!pnpmLeakedASpecifier(failure)) return {
|
|
2150
|
+
failure,
|
|
2151
|
+
manager: void 0,
|
|
2152
|
+
warnings: []
|
|
2153
|
+
};
|
|
2154
|
+
const retryFailure = await pair("npm");
|
|
2155
|
+
if (retryFailure !== void 0) return {
|
|
2156
|
+
failure: retryFailure,
|
|
2157
|
+
manager: void 0,
|
|
2158
|
+
warnings: [retriedWarning(failure)]
|
|
2159
|
+
};
|
|
2160
|
+
return {
|
|
2161
|
+
failure: void 0,
|
|
2162
|
+
manager: "npm",
|
|
2163
|
+
warnings: [fallbackWarning(failure)]
|
|
2164
|
+
};
|
|
2165
|
+
}
|
|
2166
|
+
function skillArgs(url, skill) {
|
|
2167
|
+
return [
|
|
2168
|
+
"add",
|
|
2169
|
+
url,
|
|
2170
|
+
"--agent",
|
|
2171
|
+
...DEFAULT_SKILL_AGENTS,
|
|
2172
|
+
"--skill",
|
|
2173
|
+
skill,
|
|
2174
|
+
"-y"
|
|
2175
|
+
];
|
|
2176
|
+
}
|
|
2177
|
+
/**
|
|
2178
|
+
* Registers every Prisma Next skill with the agent runtimes, in order. The
|
|
2179
|
+
* first failure stops the loop: the user opted into Prisma Next by running
|
|
2180
|
+
* `init`, and a half-installed skill set leaves the project ambiguous.
|
|
2181
|
+
*/
|
|
2182
|
+
async function installAgentSkills(ctx) {
|
|
2183
|
+
for (const source of DEFAULT_SKILL_SOURCES) {
|
|
2184
|
+
const result = await ctx.packages.run({
|
|
2185
|
+
package: "skills@latest",
|
|
2186
|
+
args: skillArgs(formatSkillSourceUrl(source, ctx.env), source.skill),
|
|
2187
|
+
cwd: ctx.cwd,
|
|
2188
|
+
...ifDefined("manager", ctx.manager)
|
|
2189
|
+
});
|
|
2190
|
+
if (!result.ok) return result.failure;
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
/** The scaffold is on disk from here on, so each of these is a finding. */
|
|
2194
|
+
const INIT_EXIT_CODES = {
|
|
2195
|
+
4: "scaffold written; dependency install failed",
|
|
2196
|
+
5: "scaffold written and installed; contract emit failed",
|
|
2197
|
+
6: "scaffold complete; agent-skill install failed"
|
|
2198
|
+
};
|
|
2199
|
+
function probeWarning(outcome, strictProbe) {
|
|
2200
|
+
switch (outcome.kind) {
|
|
2201
|
+
case "ok": return {
|
|
2202
|
+
warning: void 0,
|
|
2203
|
+
fatal: void 0
|
|
2204
|
+
};
|
|
2205
|
+
case "below-minimum": return {
|
|
2206
|
+
warning: outcome.message,
|
|
2207
|
+
fatal: void 0
|
|
2208
|
+
};
|
|
2209
|
+
case "no-database-url":
|
|
2210
|
+
case "connection-failed":
|
|
2211
|
+
case "driver-missing": return strictProbe ? {
|
|
2212
|
+
warning: void 0,
|
|
2213
|
+
fatal: outcome.message
|
|
2214
|
+
} : {
|
|
2215
|
+
warning: outcome.message,
|
|
2216
|
+
fatal: void 0
|
|
2217
|
+
};
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
function causeMessage(error) {
|
|
2221
|
+
return error instanceof Error ? error.message : String(error);
|
|
2222
|
+
}
|
|
2223
|
+
const createInitCommand = (injected) => defineOrmCommand({
|
|
2224
|
+
help: {
|
|
2225
|
+
summary: "Initialize a new Prisma Next project",
|
|
2226
|
+
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).",
|
|
2227
|
+
examples: [
|
|
2228
|
+
"init",
|
|
2229
|
+
"init --target postgres --authoring psl",
|
|
2230
|
+
"init --target mongodb --authoring typescript --json",
|
|
2231
|
+
"init --skip-install",
|
|
2232
|
+
"init --skip-skills",
|
|
2233
|
+
"init --target postgres --keep-previous-facade"
|
|
2234
|
+
]
|
|
2235
|
+
},
|
|
2236
|
+
args: { flags: {
|
|
2237
|
+
target: flag.string({
|
|
2238
|
+
brief: "Database target: postgres or mongodb",
|
|
2239
|
+
placeholder: "db"
|
|
2240
|
+
}),
|
|
2241
|
+
authoring: flag.string({
|
|
2242
|
+
brief: "Schema authoring style: psl or typescript",
|
|
2243
|
+
placeholder: "style"
|
|
2244
|
+
}),
|
|
2245
|
+
schemaPath: flag.string({
|
|
2246
|
+
brief: "Where to write the starter schema",
|
|
2247
|
+
placeholder: "path"
|
|
2248
|
+
}),
|
|
2249
|
+
writeEnv: flag.boolean({ brief: "Write a .env file from .env.example (gitignored)" }),
|
|
2250
|
+
probeDb: flag.boolean({ brief: "Connect to DATABASE_URL once and check the server version" }),
|
|
2251
|
+
strictProbe: flag.boolean({ brief: "Treat a failed --probe-db as fatal" }),
|
|
2252
|
+
skipInstall: flag.boolean({ brief: "Skip dependency installation and contract emission" }),
|
|
2253
|
+
skipSkills: flag.boolean({ brief: "Skip the Prisma Next agent-skill install" }),
|
|
2254
|
+
keepPreviousFacade: flag.boolean({ brief: "Keep the previous target package in package.json when switching targets" })
|
|
2255
|
+
} },
|
|
2256
|
+
exitCodes: INIT_EXIT_CODES,
|
|
2257
|
+
installsPackages: true,
|
|
2258
|
+
handler: async (args, ctx) => {
|
|
2259
|
+
const warnings = [];
|
|
2260
|
+
const warn = (text) => {
|
|
2261
|
+
warnings.push(text);
|
|
2262
|
+
ctx.report({
|
|
2263
|
+
kind: "message",
|
|
2264
|
+
severity: "warn",
|
|
2265
|
+
text
|
|
2266
|
+
});
|
|
2267
|
+
};
|
|
2268
|
+
const inputs = await resolveInitInputs({
|
|
2269
|
+
cwd: ctx.cwd,
|
|
2270
|
+
flags: args.flags,
|
|
2271
|
+
prompt: ctx.prompt
|
|
2272
|
+
});
|
|
2273
|
+
const packageManager = await resolveScaffoldPackageManager({
|
|
2274
|
+
cwd: ctx.cwd,
|
|
2275
|
+
env: ctx.env
|
|
2276
|
+
});
|
|
2277
|
+
const scaffold = scaffoldProject({
|
|
2278
|
+
cwd: ctx.cwd,
|
|
2279
|
+
inputs,
|
|
2280
|
+
packageManager
|
|
2281
|
+
});
|
|
2282
|
+
for (const warning of scaffold.warnings) warn(warning);
|
|
2283
|
+
for (const note of scaffold.notes) ctx.report({
|
|
2284
|
+
kind: "message",
|
|
2285
|
+
severity: "info",
|
|
2286
|
+
text: note
|
|
2287
|
+
});
|
|
2288
|
+
const deps = [targetPackageName(inputs.target, scaffold.resolveImportSpecifier), "dotenv"];
|
|
2289
|
+
const devDeps = scaffold.hasTypesNode ? ["prisma-next"] : ["prisma-next", "@types/node"];
|
|
2290
|
+
const findings = [];
|
|
2291
|
+
const extraActions = [];
|
|
2292
|
+
let installedManager;
|
|
2293
|
+
let packagesInstalled = "skipped";
|
|
2294
|
+
let contractEmitted = false;
|
|
2295
|
+
let skillRegistered = false;
|
|
2296
|
+
const settle = (exitCode) => {
|
|
2297
|
+
const installed = packagesInstalled === "installed";
|
|
2298
|
+
const document = {
|
|
2299
|
+
ok: true,
|
|
2300
|
+
target: inputs.target === "mongo" ? "mongodb" : "postgres",
|
|
2301
|
+
authoring: inputs.authoring,
|
|
2302
|
+
schemaPath: inputs.schemaPath,
|
|
2303
|
+
filesWritten: scaffold.filesWritten,
|
|
2304
|
+
filesDeleted: scaffold.filesDeleted,
|
|
2305
|
+
packagesInstalled: {
|
|
2306
|
+
status: packagesInstalled,
|
|
2307
|
+
deps: installed ? deps : [],
|
|
2308
|
+
devDeps: installed ? devDeps : []
|
|
2309
|
+
},
|
|
2310
|
+
contractEmitted,
|
|
2311
|
+
nextSteps: buildNextSteps({
|
|
2312
|
+
target: inputs.target === "mongo" ? "mongodb" : "postgres",
|
|
2313
|
+
packagesInstalled,
|
|
2314
|
+
contractEmitted,
|
|
2315
|
+
emitCommand: EMIT_COMMAND,
|
|
2316
|
+
schemaPath: inputs.schemaPath,
|
|
2317
|
+
skillRegistered
|
|
2318
|
+
}),
|
|
2319
|
+
warnings
|
|
2320
|
+
};
|
|
2321
|
+
const validated = InitOutputSchema(document);
|
|
2322
|
+
if (validated instanceof type.errors) return notOk(new CliStructuredError$1("CLI.INIT_INVALID_OUTPUT_DOCUMENT", "Init produced an invalid output document", {
|
|
2323
|
+
why: `The success document failed schema validation: ${String(validated)}`,
|
|
2324
|
+
nextActions: [chooseAction("This is a bug in prisma-next. Please report it with `-v` output.")],
|
|
2325
|
+
docsUrl: docsUrlFor("CLI.INIT_INVALID_OUTPUT_DOCUMENT")
|
|
2326
|
+
}));
|
|
2327
|
+
return ok(ctx.present({
|
|
2328
|
+
data: document,
|
|
2329
|
+
exitCode,
|
|
2330
|
+
diagnostics: findings
|
|
2331
|
+
}, initPresentations({
|
|
2332
|
+
document,
|
|
2333
|
+
complete: exitCode === 0,
|
|
2334
|
+
nextActions: [...extraActions, ...buildInitNextActions({
|
|
2335
|
+
contractEmitted,
|
|
2336
|
+
schemaPath: inputs.schemaPath,
|
|
2337
|
+
skillRegistered
|
|
2338
|
+
})]
|
|
2339
|
+
})));
|
|
2340
|
+
};
|
|
2341
|
+
if (inputs.install) {
|
|
2342
|
+
const outcome = await installProjectDependencies({
|
|
2343
|
+
packages: ctx.packages,
|
|
2344
|
+
cwd: ctx.cwd,
|
|
2345
|
+
deps,
|
|
2346
|
+
devDeps,
|
|
2347
|
+
catalogWarnings: packageManager === "pnpm" ? buildCatalogWarnings(ctx.cwd, [...deps, ...devDeps]) : []
|
|
2348
|
+
});
|
|
2349
|
+
for (const warning of outcome.warnings) warn(warning);
|
|
2350
|
+
if (outcome.failure !== void 0) {
|
|
2351
|
+
packagesInstalled = "failed";
|
|
2352
|
+
findings.push(installFailedFinding(outcome.failure, scaffold.filesWritten));
|
|
2353
|
+
return settle(4);
|
|
2354
|
+
}
|
|
2355
|
+
packagesInstalled = "installed";
|
|
2356
|
+
installedManager = outcome.manager;
|
|
2357
|
+
const emitStep = "Emit the contract";
|
|
2358
|
+
ctx.report({
|
|
2359
|
+
kind: "step-started",
|
|
2360
|
+
step: emitStep
|
|
2361
|
+
});
|
|
2362
|
+
try {
|
|
2363
|
+
await injected.emitScaffoldedContract({ cwd: ctx.cwd });
|
|
2364
|
+
contractEmitted = true;
|
|
2365
|
+
ctx.report({
|
|
2366
|
+
kind: "step-finished",
|
|
2367
|
+
step: emitStep,
|
|
2368
|
+
outcome: "ok"
|
|
2369
|
+
});
|
|
2370
|
+
} catch (error) {
|
|
2371
|
+
ctx.report({
|
|
2372
|
+
kind: "step-finished",
|
|
2373
|
+
step: emitStep,
|
|
2374
|
+
outcome: "failed"
|
|
2375
|
+
});
|
|
2376
|
+
findings.push(emitFailedFinding(causeMessage(error), scaffold.filesWritten));
|
|
2377
|
+
return settle(5);
|
|
2378
|
+
}
|
|
2379
|
+
} else extraActions.push(chooseAction(`Install the project dependencies with your package manager: ${deps.join(", ")} (and ${devDeps.join(", ")} as development dependencies)`));
|
|
2380
|
+
if (inputs.probeDb) {
|
|
2381
|
+
const probe = probeWarning(await probeServerVersion({
|
|
2382
|
+
baseDir: ctx.cwd,
|
|
2383
|
+
target: inputs.target,
|
|
2384
|
+
databaseUrl: ctx.env["DATABASE_URL"],
|
|
2385
|
+
minVersion: MIN_SERVER_VERSION[inputs.target]
|
|
2386
|
+
}, {}), inputs.strictProbe);
|
|
2387
|
+
if (probe.warning !== void 0) warn(probe.warning);
|
|
2388
|
+
if (probe.fatal !== void 0) return notOk(normalizeError(errorInitProbeFailed({
|
|
2389
|
+
cause: probe.fatal,
|
|
2390
|
+
filesWritten: scaffold.filesWritten
|
|
2391
|
+
})));
|
|
2392
|
+
}
|
|
2393
|
+
const skillCommands = resolveProjectSkillInstallCommands(installedManager ?? packageManager, ctx.env);
|
|
2394
|
+
if (inputs.installProjectSkill) {
|
|
2395
|
+
const failure = await installAgentSkills({
|
|
2396
|
+
packages: ctx.packages,
|
|
2397
|
+
cwd: ctx.cwd,
|
|
2398
|
+
env: ctx.env,
|
|
2399
|
+
manager: installedManager
|
|
2400
|
+
});
|
|
2401
|
+
if (failure !== void 0) {
|
|
2402
|
+
findings.push(skillInstallFailedFinding(failure, scaffold.filesWritten, skillCommands));
|
|
2403
|
+
return settle(6);
|
|
2404
|
+
}
|
|
2405
|
+
skillRegistered = true;
|
|
2406
|
+
} else warn(`Skipped the Prisma Next agent-skill install (--skip-skills). To install them later, run: ${skillCommands.map((command) => `\`${command}\``).join(" && ")}`);
|
|
2407
|
+
return settle(0);
|
|
2408
|
+
}
|
|
2409
|
+
});
|
|
2410
|
+
const initCommand = createInitCommand({ emitScaffoldedContract });
|
|
2411
|
+
const STATUS_ACTION = runCommandAction("Check every space against the database", "prisma-next migration status");
|
|
2412
|
+
/**
|
|
2413
|
+
* The route the migration will take. The graph is a drawing — the engine cannot
|
|
2414
|
+
* derive a lane gutter — and the ordered list under it is drawn with the same
|
|
2415
|
+
* widths so every arrow lands in one column.
|
|
2416
|
+
*/
|
|
2417
|
+
function showPresentations$1(inputs) {
|
|
2418
|
+
const { document, graph, runList } = inputs;
|
|
2419
|
+
const count = document.migrations.length;
|
|
2420
|
+
return {
|
|
2421
|
+
human: () => [
|
|
2422
|
+
{
|
|
2423
|
+
kind: "fields",
|
|
2424
|
+
rail: true,
|
|
2425
|
+
rows: [
|
|
2426
|
+
{
|
|
2427
|
+
label: "migrations",
|
|
2428
|
+
value: inputs.migrationsDir
|
|
2429
|
+
},
|
|
2430
|
+
...inputs.database === void 0 ? [] : [{
|
|
2431
|
+
label: "database",
|
|
2432
|
+
value: inputs.database
|
|
2433
|
+
}],
|
|
2434
|
+
...inputs.from === void 0 ? [] : [{
|
|
2435
|
+
label: "from",
|
|
2436
|
+
value: inputs.from
|
|
2437
|
+
}],
|
|
2438
|
+
...inputs.to === void 0 ? [] : [{
|
|
2439
|
+
label: "to",
|
|
2440
|
+
value: inputs.to
|
|
2441
|
+
}]
|
|
2442
|
+
]
|
|
2443
|
+
},
|
|
2444
|
+
...graph.length === 0 ? [] : [{
|
|
2445
|
+
kind: "drawing",
|
|
2446
|
+
lines: toneDrawing(graph)
|
|
2447
|
+
}],
|
|
2448
|
+
...count === 0 ? [{
|
|
2449
|
+
kind: "summary",
|
|
2450
|
+
status: "info",
|
|
2451
|
+
text: document.summary
|
|
2452
|
+
}] : [{
|
|
2453
|
+
kind: "summary",
|
|
2454
|
+
status: "info",
|
|
2455
|
+
text: `The following ${count} migration${count === 1 ? "" : "s"} will run:`
|
|
2456
|
+
}, {
|
|
2457
|
+
kind: "drawing",
|
|
2458
|
+
lines: toneDrawing(runList.join("\n"))
|
|
2459
|
+
}]
|
|
2460
|
+
],
|
|
2461
|
+
json: () => document
|
|
2462
|
+
};
|
|
2463
|
+
}
|
|
2464
|
+
function applyPresentations(inputs) {
|
|
1252
2465
|
const { document } = inputs;
|
|
1253
2466
|
const advanced = document.advancedRef;
|
|
1254
2467
|
return {
|
|
@@ -1737,85 +2950,392 @@ function ledgerGrid(entries, options) {
|
|
|
1737
2950
|
})
|
|
1738
2951
|
};
|
|
1739
2952
|
}
|
|
1740
|
-
function logPresentations(inputs) {
|
|
1741
|
-
const grid = inputs.grid;
|
|
2953
|
+
function logPresentations(inputs) {
|
|
2954
|
+
const grid = inputs.grid;
|
|
2955
|
+
return {
|
|
2956
|
+
human: () => [...inputs.database === void 0 ? [] : [{
|
|
2957
|
+
kind: "fields",
|
|
2958
|
+
rail: true,
|
|
2959
|
+
rows: [{
|
|
2960
|
+
label: "database",
|
|
2961
|
+
value: inputs.database
|
|
2962
|
+
}]
|
|
2963
|
+
}], ...grid === void 0 ? [{
|
|
2964
|
+
kind: "summary",
|
|
2965
|
+
status: "info",
|
|
2966
|
+
text: MIGRATION_LOG_EMPTY_MESSAGE
|
|
2967
|
+
}] : [{
|
|
2968
|
+
kind: "table",
|
|
2969
|
+
columns: grid.headings,
|
|
2970
|
+
rows: grid.rows
|
|
2971
|
+
}]],
|
|
2972
|
+
json: () => inputs.document
|
|
2973
|
+
};
|
|
2974
|
+
}
|
|
2975
|
+
const migrationLogCommand = defineOrmCommand({
|
|
2976
|
+
help: {
|
|
2977
|
+
summary: "Show executed migration history",
|
|
2978
|
+
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.",
|
|
2979
|
+
examples: [
|
|
2980
|
+
"migration log",
|
|
2981
|
+
"migration log --db $DATABASE_URL",
|
|
2982
|
+
"migration log --utc",
|
|
2983
|
+
"migration log --json"
|
|
2984
|
+
]
|
|
2985
|
+
},
|
|
2986
|
+
args: { flags: {
|
|
2987
|
+
db: dbFlag,
|
|
2988
|
+
utc: flag.boolean({ brief: "Render human timestamps in UTC instead of local time" }),
|
|
2989
|
+
ascii: flag.boolean({ brief: "Use ASCII glyphs (pipe-friendly)" })
|
|
2990
|
+
} },
|
|
2991
|
+
needs: { config: ormConfigSection },
|
|
2992
|
+
handler: async (args, ctx) => {
|
|
2993
|
+
const dbConnection = args.flags.db ?? ctx.config.db?.connection;
|
|
2994
|
+
const missingDb = requireLiveDatabase({
|
|
2995
|
+
dbConnection,
|
|
2996
|
+
hasDriver: ctx.config.driver !== void 0,
|
|
2997
|
+
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>)",
|
|
2998
|
+
commandName: "migration log"
|
|
2999
|
+
});
|
|
3000
|
+
if (missingDb !== null) return notOk(normalizeError(missingDb));
|
|
3001
|
+
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." })));
|
|
3002
|
+
const client = createControlClient({
|
|
3003
|
+
family: ctx.config.family,
|
|
3004
|
+
target: ctx.config.target,
|
|
3005
|
+
adapter: ctx.config.adapter,
|
|
3006
|
+
...ifDefined("driver", ctx.config.driver),
|
|
3007
|
+
extensions: ctx.config.extensions ?? []
|
|
3008
|
+
});
|
|
3009
|
+
let entries;
|
|
3010
|
+
try {
|
|
3011
|
+
await client.connect(dbConnection);
|
|
3012
|
+
entries = await client.readLedger();
|
|
3013
|
+
} catch (error) {
|
|
3014
|
+
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)}` })));
|
|
3015
|
+
} finally {
|
|
3016
|
+
await closeQuietly(client);
|
|
3017
|
+
}
|
|
3018
|
+
const records = serializeLedgerEntriesForJson(entries);
|
|
3019
|
+
const document = {
|
|
3020
|
+
ok: true,
|
|
3021
|
+
records,
|
|
3022
|
+
summary: `${records.length} migration(s) applied`
|
|
3023
|
+
};
|
|
3024
|
+
const glyphMode = args.flags.ascii ? "ascii" : "unicode";
|
|
3025
|
+
return ok(ctx.present({ data: document }, logPresentations({
|
|
3026
|
+
document,
|
|
3027
|
+
grid: entries.length === 0 ? void 0 : ledgerGrid(entries, {
|
|
3028
|
+
utc: args.flags.utc,
|
|
3029
|
+
glyphMode
|
|
3030
|
+
}),
|
|
3031
|
+
database: typeof dbConnection === "string" ? maskConnectionUrl(dbConnection) : void 0
|
|
3032
|
+
})));
|
|
3033
|
+
}
|
|
3034
|
+
});
|
|
3035
|
+
function newPresentations(inputs) {
|
|
3036
|
+
const { document } = inputs;
|
|
3037
|
+
const migrationTs = join(document.dir, "migration.ts");
|
|
3038
|
+
return {
|
|
3039
|
+
human: () => [
|
|
3040
|
+
{
|
|
3041
|
+
kind: "fields",
|
|
3042
|
+
rail: true,
|
|
3043
|
+
rows: [{
|
|
3044
|
+
label: "contract",
|
|
3045
|
+
value: inputs.contractPath
|
|
3046
|
+
}, {
|
|
3047
|
+
label: "migrations",
|
|
3048
|
+
value: inputs.appMigrationsRelative
|
|
3049
|
+
}]
|
|
3050
|
+
},
|
|
3051
|
+
{
|
|
3052
|
+
kind: "summary",
|
|
3053
|
+
status: "ok",
|
|
3054
|
+
text: document.summary
|
|
3055
|
+
},
|
|
3056
|
+
{
|
|
3057
|
+
kind: "fields",
|
|
3058
|
+
rows: [{
|
|
3059
|
+
label: "from",
|
|
3060
|
+
value: document.from === null ? [{
|
|
3061
|
+
text: "(baseline)",
|
|
3062
|
+
tone: "muted"
|
|
3063
|
+
}] : [{
|
|
3064
|
+
text: document.from,
|
|
3065
|
+
tone: "identifier"
|
|
3066
|
+
}]
|
|
3067
|
+
}, {
|
|
3068
|
+
label: "to",
|
|
3069
|
+
value: [{
|
|
3070
|
+
text: document.to,
|
|
3071
|
+
tone: "identifier"
|
|
3072
|
+
}]
|
|
3073
|
+
}]
|
|
3074
|
+
}
|
|
3075
|
+
],
|
|
3076
|
+
json: () => document,
|
|
3077
|
+
next: () => [{
|
|
3078
|
+
kind: "edit-file",
|
|
3079
|
+
label: `Write the migration body in ${migrationTs}`
|
|
3080
|
+
}, runCommandAction("Run it to self-emit ops.json and attest the package", `node "${migrationTs}"`)]
|
|
3081
|
+
};
|
|
3082
|
+
}
|
|
3083
|
+
const migrationNewCommand = defineOrmCommand({
|
|
3084
|
+
help: {
|
|
3085
|
+
summary: "Scaffold a new migration for manual authoring",
|
|
3086
|
+
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.",
|
|
3087
|
+
examples: [
|
|
3088
|
+
"migration new --name split-name",
|
|
3089
|
+
"migration new --name custom-fk --from abc123",
|
|
3090
|
+
"migration new --json"
|
|
3091
|
+
]
|
|
3092
|
+
},
|
|
3093
|
+
args: { flags: {
|
|
3094
|
+
name: flag.string({
|
|
3095
|
+
brief: "Migration name (used in directory name)",
|
|
3096
|
+
placeholder: "slug"
|
|
3097
|
+
}),
|
|
3098
|
+
from: flag.string({
|
|
3099
|
+
brief: "Starting contract hash (default: latest migration target)",
|
|
3100
|
+
placeholder: "hash"
|
|
3101
|
+
})
|
|
3102
|
+
} },
|
|
3103
|
+
needs: { config: ormConfigSection },
|
|
3104
|
+
handler: async (args, ctx) => {
|
|
3105
|
+
const scaffolded = await executeMigrationNewCommand({
|
|
3106
|
+
config: ctx.config,
|
|
3107
|
+
cwd: ctx.cwd,
|
|
3108
|
+
configPath: projectConfigPathFor(ctx.cwd),
|
|
3109
|
+
...ifDefined("name", args.flags.name),
|
|
3110
|
+
...ifDefined("from", args.flags.from)
|
|
3111
|
+
});
|
|
3112
|
+
if (!scaffolded.ok) return notOk(normalizeError(scaffolded.failure));
|
|
3113
|
+
const contractPath = contractPathFor(ctx.config, ctx.cwd);
|
|
3114
|
+
return ok(ctx.present({ data: scaffolded.value }, newPresentations({
|
|
3115
|
+
document: scaffolded.value,
|
|
3116
|
+
contractPath: contractPath === void 0 ? "(unset)" : displayPath(contractPath, ctx.cwd),
|
|
3117
|
+
appMigrationsRelative: displayPath(appMigrationsDirFor(ctx.config, ctx.cwd), ctx.cwd)
|
|
3118
|
+
})));
|
|
3119
|
+
}
|
|
3120
|
+
});
|
|
3121
|
+
function hashRow(label, hash) {
|
|
3122
|
+
return {
|
|
3123
|
+
label,
|
|
3124
|
+
value: hash === null ? [{
|
|
3125
|
+
text: "(baseline)",
|
|
3126
|
+
tone: "muted"
|
|
3127
|
+
}] : [{
|
|
3128
|
+
text: hash,
|
|
3129
|
+
tone: "identifier"
|
|
3130
|
+
}]
|
|
3131
|
+
};
|
|
3132
|
+
}
|
|
3133
|
+
/**
|
|
3134
|
+
* Where the run put things: the contract edge it planned, then every directory
|
|
3135
|
+
* it wrote — the app-space package, the auto-baseline package that precedes it,
|
|
3136
|
+
* and any extension-space package the seed phase materialised.
|
|
3137
|
+
*/
|
|
3138
|
+
function outcomeFields(result, migrationsRelative) {
|
|
3139
|
+
return {
|
|
3140
|
+
kind: "fields",
|
|
3141
|
+
rows: [
|
|
3142
|
+
hashRow("from", result.from),
|
|
3143
|
+
hashRow("to", result.to),
|
|
3144
|
+
...result.baselineDir === void 0 ? [] : [{
|
|
3145
|
+
label: "baseline",
|
|
3146
|
+
value: result.baselineDir
|
|
3147
|
+
}],
|
|
3148
|
+
...result.dir === void 0 ? [] : [{
|
|
3149
|
+
label: "app space",
|
|
3150
|
+
value: result.dir
|
|
3151
|
+
}],
|
|
3152
|
+
...result.emittedExtensionDirs.map((entry) => ({
|
|
3153
|
+
label: `space ${entry.spaceId}`,
|
|
3154
|
+
value: join(migrationsRelative, entry.spaceId, entry.dirName)
|
|
3155
|
+
}))
|
|
3156
|
+
]
|
|
3157
|
+
};
|
|
3158
|
+
}
|
|
3159
|
+
function operationNodes$1(result) {
|
|
3160
|
+
return result.operations.map((operation) => operation.operationClass === "destructive" ? {
|
|
3161
|
+
label: operation.label,
|
|
3162
|
+
status: "warn"
|
|
3163
|
+
} : { label: operation.label });
|
|
3164
|
+
}
|
|
3165
|
+
function operationBlocks$1(result) {
|
|
3166
|
+
if (result.operations.length === 0) return [];
|
|
3167
|
+
const destructive = result.operations.some((operation) => operation.operationClass === "destructive");
|
|
3168
|
+
return [{
|
|
3169
|
+
kind: "tree",
|
|
3170
|
+
roots: [{
|
|
3171
|
+
label: result.dir ?? "operations",
|
|
3172
|
+
children: operationNodes$1(result)
|
|
3173
|
+
}]
|
|
3174
|
+
}, ...destructive ? [{
|
|
3175
|
+
kind: "summary",
|
|
3176
|
+
status: "warn",
|
|
3177
|
+
text: "This migration contains destructive operations that may cause data loss."
|
|
3178
|
+
}] : []];
|
|
3179
|
+
}
|
|
3180
|
+
/** Statements a database would run, printed verbatim rather than laid out. */
|
|
3181
|
+
function previewBlocks$1(result) {
|
|
3182
|
+
const preview = result.preview;
|
|
3183
|
+
if (preview === void 0) return [];
|
|
3184
|
+
const statements = preview.statements.map((statement) => statement.text.trim()).filter((text) => text.length > 0).map((text) => text.endsWith(";") ? text : `${text};`);
|
|
3185
|
+
if (statements.length === 0) return [];
|
|
3186
|
+
return [{
|
|
3187
|
+
kind: "summary",
|
|
3188
|
+
status: "info",
|
|
3189
|
+
tone: "muted",
|
|
3190
|
+
text: previewBlockHeader(preview)
|
|
3191
|
+
}, {
|
|
3192
|
+
kind: "drawing",
|
|
3193
|
+
lines: statements
|
|
3194
|
+
}];
|
|
3195
|
+
}
|
|
3196
|
+
function planBlocks(result, migrationsRelative) {
|
|
3197
|
+
const outcome = outcomeFields(result, migrationsRelative);
|
|
3198
|
+
if (result.noOp) return [{
|
|
3199
|
+
kind: "summary",
|
|
3200
|
+
status: "ok",
|
|
3201
|
+
text: "No changes detected"
|
|
3202
|
+
}, outcome];
|
|
3203
|
+
if (result.pendingPlaceholders === true) return [{
|
|
3204
|
+
kind: "summary",
|
|
3205
|
+
status: "warn",
|
|
3206
|
+
text: result.summary
|
|
3207
|
+
}, outcome];
|
|
3208
|
+
return [
|
|
3209
|
+
{
|
|
3210
|
+
kind: "summary",
|
|
3211
|
+
status: "ok",
|
|
3212
|
+
text: result.summary
|
|
3213
|
+
},
|
|
3214
|
+
...operationBlocks$1(result),
|
|
3215
|
+
outcome,
|
|
3216
|
+
...previewBlocks$1(result)
|
|
3217
|
+
];
|
|
3218
|
+
}
|
|
3219
|
+
function planNextActions(result, migrationsRelative) {
|
|
3220
|
+
if (result.pendingPlaceholders === true) {
|
|
3221
|
+
const migrationTs = join(result.dir ?? "<dir>", "migration.ts");
|
|
3222
|
+
return [{
|
|
3223
|
+
kind: "edit-file",
|
|
3224
|
+
label: `Replace each placeholder(...) call in ${migrationTs} with your query`
|
|
3225
|
+
}, runCommandAction("Run it to self-emit ops.json and attest the package", `node "${migrationTs}"`)];
|
|
3226
|
+
}
|
|
3227
|
+
const written = [
|
|
3228
|
+
...result.baselineDir === void 0 ? [] : [result.baselineDir],
|
|
3229
|
+
...result.dir === void 0 ? [] : [result.dir],
|
|
3230
|
+
...result.emittedExtensionDirs.map((entry) => join(migrationsRelative, entry.spaceId, entry.dirName))
|
|
3231
|
+
];
|
|
3232
|
+
if (written.length === 0) return [];
|
|
3233
|
+
return [{
|
|
3234
|
+
kind: "edit-file",
|
|
3235
|
+
label: `Review ${written.join(" and ")}`
|
|
3236
|
+
}, runCommandAction("Apply the migration", "prisma-next migrate")];
|
|
3237
|
+
}
|
|
3238
|
+
function planPresentations(inputs) {
|
|
1742
3239
|
return {
|
|
1743
|
-
human: () => [
|
|
3240
|
+
human: () => [{
|
|
1744
3241
|
kind: "fields",
|
|
1745
3242
|
rail: true,
|
|
1746
|
-
rows: [
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
3243
|
+
rows: [
|
|
3244
|
+
{
|
|
3245
|
+
label: "contract",
|
|
3246
|
+
value: inputs.contractPath
|
|
3247
|
+
},
|
|
3248
|
+
{
|
|
3249
|
+
label: "migrations",
|
|
3250
|
+
value: inputs.appMigrationsRelative
|
|
3251
|
+
},
|
|
3252
|
+
...inputs.from === void 0 ? [] : [{
|
|
3253
|
+
label: "from",
|
|
3254
|
+
value: inputs.from
|
|
3255
|
+
}],
|
|
3256
|
+
...inputs.to === void 0 ? [] : [{
|
|
3257
|
+
label: "to",
|
|
3258
|
+
value: inputs.to
|
|
3259
|
+
}],
|
|
3260
|
+
...inputs.name === void 0 ? [] : [{
|
|
3261
|
+
label: "name",
|
|
3262
|
+
value: inputs.name
|
|
3263
|
+
}]
|
|
3264
|
+
]
|
|
3265
|
+
}, ...planBlocks(inputs.document, inputs.migrationsRelative)],
|
|
3266
|
+
json: () => inputs.document,
|
|
3267
|
+
next: () => planNextActions(inputs.document, inputs.migrationsRelative)
|
|
1760
3268
|
};
|
|
1761
3269
|
}
|
|
1762
|
-
const
|
|
3270
|
+
const migrationPlanCommand = defineOrmCommand({
|
|
1763
3271
|
help: {
|
|
1764
|
-
summary: "
|
|
1765
|
-
description: "
|
|
3272
|
+
summary: "Plan a migration from contract changes",
|
|
3273
|
+
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.",
|
|
1766
3274
|
examples: [
|
|
1767
|
-
"migration
|
|
1768
|
-
"migration
|
|
1769
|
-
"migration
|
|
1770
|
-
"migration
|
|
3275
|
+
"migration plan",
|
|
3276
|
+
"migration plan --name add-users-table",
|
|
3277
|
+
"migration plan --to <migration-dir>^ --name rollback",
|
|
3278
|
+
"migration plan --json"
|
|
1771
3279
|
]
|
|
1772
3280
|
},
|
|
1773
3281
|
args: { flags: {
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
3282
|
+
name: flag.string({
|
|
3283
|
+
brief: "Name slug for the migration directory",
|
|
3284
|
+
placeholder: "slug"
|
|
3285
|
+
}),
|
|
3286
|
+
from: flag.string({
|
|
3287
|
+
brief: "Starting contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)",
|
|
3288
|
+
placeholder: "contract"
|
|
3289
|
+
}),
|
|
3290
|
+
to: flag.string({
|
|
3291
|
+
brief: "Destination contract reference; defaults to the emitted contract. Same grammar as --from",
|
|
3292
|
+
placeholder: "contract"
|
|
3293
|
+
})
|
|
1777
3294
|
} },
|
|
1778
3295
|
needs: { config: ormConfigSection },
|
|
1779
3296
|
handler: async (args, ctx) => {
|
|
1780
|
-
const
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
await client.connect(dbConnection);
|
|
1799
|
-
entries = await client.readLedger();
|
|
1800
|
-
} catch (error) {
|
|
1801
|
-
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)}` })));
|
|
1802
|
-
} finally {
|
|
1803
|
-
await closeQuietly(client);
|
|
1804
|
-
}
|
|
1805
|
-
const records = serializeLedgerEntriesForJson(entries);
|
|
1806
|
-
const document = {
|
|
1807
|
-
ok: true,
|
|
1808
|
-
records,
|
|
1809
|
-
summary: `${records.length} migration(s) applied`
|
|
3297
|
+
const seeded = (record) => {
|
|
3298
|
+
if (record.action !== "updated") return;
|
|
3299
|
+
const step = `Seed contract space ${record.spaceId}`;
|
|
3300
|
+
ctx.report({
|
|
3301
|
+
kind: "step-started",
|
|
3302
|
+
step,
|
|
3303
|
+
id: record.spaceId
|
|
3304
|
+
});
|
|
3305
|
+
ctx.report({
|
|
3306
|
+
kind: "step-finished",
|
|
3307
|
+
step,
|
|
3308
|
+
id: record.spaceId,
|
|
3309
|
+
outcome: "ok",
|
|
3310
|
+
data: {
|
|
3311
|
+
newHash: record.newHash,
|
|
3312
|
+
newMigrationDirs: record.newMigrationDirs
|
|
3313
|
+
}
|
|
3314
|
+
});
|
|
1810
3315
|
};
|
|
1811
|
-
const
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
3316
|
+
const planned = await executeMigrationPlanCommand({
|
|
3317
|
+
config: ctx.config,
|
|
3318
|
+
cwd: ctx.cwd,
|
|
3319
|
+
configPath: projectConfigPathFor(ctx.cwd),
|
|
3320
|
+
...ifDefined("name", args.flags.name),
|
|
3321
|
+
...ifDefined("from", args.flags.from),
|
|
3322
|
+
...ifDefined("to", args.flags.to)
|
|
3323
|
+
}, Date.now(), { onSeeded: seeded });
|
|
3324
|
+
if (!planned.ok) return notOk(normalizeError(planned.failure));
|
|
3325
|
+
ctx.report({
|
|
3326
|
+
kind: "message",
|
|
3327
|
+
severity: "verbose",
|
|
3328
|
+
text: `Total time: ${planned.value.timings.total}ms`
|
|
3329
|
+
});
|
|
3330
|
+
const contractPath = contractPathFor(ctx.config, ctx.cwd);
|
|
3331
|
+
return ok(ctx.present({ data: planned.value }, planPresentations({
|
|
3332
|
+
document: planned.value,
|
|
3333
|
+
contractPath: contractPath === void 0 ? "(unset)" : displayPath(contractPath, ctx.cwd),
|
|
3334
|
+
appMigrationsRelative: displayPath(appMigrationsDirFor(ctx.config, ctx.cwd), ctx.cwd),
|
|
3335
|
+
migrationsRelative: displayPath(migrationsDirFor(ctx.config, ctx.cwd), ctx.cwd),
|
|
3336
|
+
from: args.flags.from,
|
|
3337
|
+
to: args.flags.to,
|
|
3338
|
+
name: args.flags.name
|
|
1819
3339
|
})));
|
|
1820
3340
|
}
|
|
1821
3341
|
});
|
|
@@ -2053,6 +3573,426 @@ const migrationShowCommand = defineOrmCommand({
|
|
|
2053
3573
|
})));
|
|
2054
3574
|
}
|
|
2055
3575
|
});
|
|
3576
|
+
const EMIT_CONTRACT = runCommandAction("Regenerate the contract", "prisma-next contract emit");
|
|
3577
|
+
function contractUnreadableFinding(reason) {
|
|
3578
|
+
const message = `Could not read contract: ${reason}`;
|
|
3579
|
+
return {
|
|
3580
|
+
document: {
|
|
3581
|
+
code: "CONTRACT.UNREADABLE",
|
|
3582
|
+
severity: "warn",
|
|
3583
|
+
message,
|
|
3584
|
+
hints: ["Run 'prisma-next contract emit' to generate a valid contract"]
|
|
3585
|
+
},
|
|
3586
|
+
diagnostic: {
|
|
3587
|
+
code: "CONTRACT.UNREADABLE",
|
|
3588
|
+
severity: "warn",
|
|
3589
|
+
summary: message,
|
|
3590
|
+
why: "The status tree falls back to the on-disk migration graph when the emitted contract cannot be read.",
|
|
3591
|
+
nextActions: [EMIT_CONTRACT]
|
|
3592
|
+
}
|
|
3593
|
+
};
|
|
3594
|
+
}
|
|
3595
|
+
function markerNotInHistoryFinding(space) {
|
|
3596
|
+
const message = `Database was updated outside the migration system (marker for space "${space}" does not match any migration)`;
|
|
3597
|
+
return {
|
|
3598
|
+
document: {
|
|
3599
|
+
code: "MIGRATION.MARKER_NOT_IN_HISTORY",
|
|
3600
|
+
severity: "warn",
|
|
3601
|
+
message,
|
|
3602
|
+
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"]
|
|
3603
|
+
},
|
|
3604
|
+
diagnostic: {
|
|
3605
|
+
code: "MIGRATION.MARKER_NOT_IN_HISTORY",
|
|
3606
|
+
severity: "warn",
|
|
3607
|
+
summary: message,
|
|
3608
|
+
why: "The marker the database carries names no contract in the on-disk migration graph.",
|
|
3609
|
+
meta: { space },
|
|
3610
|
+
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")]
|
|
3611
|
+
}
|
|
3612
|
+
};
|
|
3613
|
+
}
|
|
3614
|
+
function missingInvariantsFinding(inputs) {
|
|
3615
|
+
const message = `missing invariant(s): ${inputs.missing.join(", ")}`;
|
|
3616
|
+
return {
|
|
3617
|
+
document: {
|
|
3618
|
+
code: "MIGRATION.MISSING_INVARIANTS",
|
|
3619
|
+
severity: "warn",
|
|
3620
|
+
...ifDefined("ref", inputs.refName),
|
|
3621
|
+
invariants: [...inputs.missing],
|
|
3622
|
+
message
|
|
3623
|
+
},
|
|
3624
|
+
diagnostic: {
|
|
3625
|
+
code: "MIGRATION.MISSING_INVARIANTS",
|
|
3626
|
+
severity: "warn",
|
|
3627
|
+
summary: message,
|
|
3628
|
+
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.`,
|
|
3629
|
+
nextActions: [runCommandAction("Apply the migrations that provide them", "prisma-next migrate")],
|
|
3630
|
+
meta: {
|
|
3631
|
+
invariants: [...inputs.missing],
|
|
3632
|
+
...ifDefined("ref", inputs.refName)
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
};
|
|
3636
|
+
}
|
|
3637
|
+
const SHORT_HASH_LENGTH = 12;
|
|
3638
|
+
function shortDisplayHash(hash) {
|
|
3639
|
+
return hash.slice(0, SHORT_HASH_LENGTH);
|
|
3640
|
+
}
|
|
3641
|
+
const NO_DATABASE_STATE = {
|
|
3642
|
+
markersBySpace: /* @__PURE__ */ new Map(),
|
|
3643
|
+
ledgersBySpace: /* @__PURE__ */ new Map()
|
|
3644
|
+
};
|
|
3645
|
+
async function readDatabaseState(inputs) {
|
|
3646
|
+
const { client } = inputs;
|
|
3647
|
+
try {
|
|
3648
|
+
await client.connect(inputs.connection);
|
|
3649
|
+
const markersBySpace = new Map(await client.readAllMarkers());
|
|
3650
|
+
const ledgersBySpace = /* @__PURE__ */ new Map();
|
|
3651
|
+
for (const spaceId of inputs.spaceIds) ledgersBySpace.set(spaceId, await client.readLedger(spaceId));
|
|
3652
|
+
return ok({
|
|
3653
|
+
markersBySpace,
|
|
3654
|
+
ledgersBySpace
|
|
3655
|
+
});
|
|
3656
|
+
} catch (error) {
|
|
3657
|
+
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)}` })));
|
|
3658
|
+
} finally {
|
|
3659
|
+
await closeQuietly(client);
|
|
3660
|
+
}
|
|
3661
|
+
}
|
|
3662
|
+
function buildNoPathSummary(args) {
|
|
3663
|
+
const markerPart = args.markerHash !== void 0 ? `the database state (${shortDisplayHash(args.markerHash)})` : "the database state";
|
|
3664
|
+
const targetShort = shortDisplayHash(args.targetHash);
|
|
3665
|
+
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.`;
|
|
3666
|
+
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.`;
|
|
3667
|
+
}
|
|
3668
|
+
function buildStatusHeadline(args) {
|
|
3669
|
+
if (args.markerDiverged && args.markerHash !== void 0) return `Database marker ${shortDisplayHash(args.markerHash)} is not in the on-disk migration graph`;
|
|
3670
|
+
if (args.pendingCount === 0) return "Up to date";
|
|
3671
|
+
return `${args.pendingCount} pending — run \`prisma-next migrate --to ${shortDisplayHash(args.targetHash)}\``;
|
|
3672
|
+
}
|
|
3673
|
+
/**
|
|
3674
|
+
* One space's section: its heading, where more than one space is in scope, on
|
|
3675
|
+
* top of its tree. Heading and tree are one drawing so the heading stays a
|
|
3676
|
+
* bare label — a `summary` would put a status glyph in front of it.
|
|
3677
|
+
*/
|
|
3678
|
+
function sectionBlock(section) {
|
|
3679
|
+
const heading = section.showHeading ? [[{
|
|
3680
|
+
text: `${section.space}:`,
|
|
3681
|
+
tone: "heading"
|
|
3682
|
+
}]] : [];
|
|
3683
|
+
const body = section.tree.length === 0 ? [[{
|
|
3684
|
+
text: "(no migrations)",
|
|
3685
|
+
tone: "muted"
|
|
3686
|
+
}]] : toneDrawing(section.tree);
|
|
3687
|
+
return {
|
|
3688
|
+
kind: "drawing",
|
|
3689
|
+
lines: [...heading, ...body]
|
|
3690
|
+
};
|
|
3691
|
+
}
|
|
3692
|
+
function statusPresentations(inputs) {
|
|
3693
|
+
const legend = inputs.legend;
|
|
3694
|
+
return {
|
|
3695
|
+
human: () => [
|
|
3696
|
+
{
|
|
3697
|
+
kind: "fields",
|
|
3698
|
+
rail: true,
|
|
3699
|
+
rows: [
|
|
3700
|
+
{
|
|
3701
|
+
label: "migrations",
|
|
3702
|
+
value: inputs.migrationsDir
|
|
3703
|
+
},
|
|
3704
|
+
...inputs.database === void 0 ? [] : [{
|
|
3705
|
+
label: "database",
|
|
3706
|
+
value: inputs.database
|
|
3707
|
+
}],
|
|
3708
|
+
...inputs.ref === void 0 ? [] : [{
|
|
3709
|
+
label: "ref",
|
|
3710
|
+
value: inputs.ref
|
|
3711
|
+
}],
|
|
3712
|
+
...inputs.from === void 0 ? [] : [{
|
|
3713
|
+
label: "from",
|
|
3714
|
+
value: inputs.from
|
|
3715
|
+
}],
|
|
3716
|
+
...inputs.space === void 0 ? [] : [{
|
|
3717
|
+
label: "space",
|
|
3718
|
+
value: inputs.space
|
|
3719
|
+
}]
|
|
3720
|
+
]
|
|
3721
|
+
},
|
|
3722
|
+
...legend === void 0 ? [] : [{
|
|
3723
|
+
kind: "drawing",
|
|
3724
|
+
lines: toneDrawing(legend)
|
|
3725
|
+
}],
|
|
3726
|
+
...inputs.sections.map(sectionBlock),
|
|
3727
|
+
inputs.headline
|
|
3728
|
+
],
|
|
3729
|
+
json: () => inputs.document
|
|
3730
|
+
};
|
|
3731
|
+
}
|
|
3732
|
+
const migrationStatusCommand = defineOrmCommand({
|
|
3733
|
+
help: {
|
|
3734
|
+
summary: "Show migration path and pending status",
|
|
3735
|
+
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.",
|
|
3736
|
+
examples: [
|
|
3737
|
+
"migration status",
|
|
3738
|
+
"migration status --db $DATABASE_URL",
|
|
3739
|
+
"migration status --to production",
|
|
3740
|
+
"migration status --from abc123 --to production",
|
|
3741
|
+
"migration status --ascii",
|
|
3742
|
+
"migration status --legend"
|
|
3743
|
+
]
|
|
3744
|
+
},
|
|
3745
|
+
args: { flags: {
|
|
3746
|
+
db: dbFlag,
|
|
3747
|
+
space: flag.string({
|
|
3748
|
+
brief: "Narrow output to a single contract space",
|
|
3749
|
+
placeholder: "id"
|
|
3750
|
+
}),
|
|
3751
|
+
to: flag.string({
|
|
3752
|
+
brief: "Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)",
|
|
3753
|
+
placeholder: "contract"
|
|
3754
|
+
}),
|
|
3755
|
+
from: flag.string({
|
|
3756
|
+
brief: "Origin contract reference; same grammar as --to. Supplying it switches to offline path computation",
|
|
3757
|
+
placeholder: "contract"
|
|
3758
|
+
}),
|
|
3759
|
+
legend: flag.boolean({ brief: "Print a key for the tree glyphs and lane colors" }),
|
|
3760
|
+
ascii: flag.boolean({ brief: "Use ASCII glyphs (pipe-friendly)" })
|
|
3761
|
+
} },
|
|
3762
|
+
needs: { config: ormConfigSection },
|
|
3763
|
+
handler: async (args, ctx) => {
|
|
3764
|
+
const migrationsDir = migrationsDirFor(ctx.config, ctx.cwd);
|
|
3765
|
+
const dbConnection = args.flags.db ?? ctx.config.db?.connection;
|
|
3766
|
+
const hasDriver = ctx.config.driver !== void 0;
|
|
3767
|
+
const usingFromOverride = args.flags.from !== void 0;
|
|
3768
|
+
if (!usingFromOverride) {
|
|
3769
|
+
const missingDb = requireLiveDatabase({
|
|
3770
|
+
dbConnection,
|
|
3771
|
+
hasDriver,
|
|
3772
|
+
why: "migration status needs a database connection to read the marker and ledger (or pass --from for an offline path preview)",
|
|
3773
|
+
retryCommand: "prisma-next migration status --from <contract>"
|
|
3774
|
+
});
|
|
3775
|
+
if (missingDb !== null) return notOk(normalizeError(missingDb));
|
|
3776
|
+
}
|
|
3777
|
+
const refsResult = await readMigrationRefs(appRefsDirFor(ctx.config, ctx.cwd));
|
|
3778
|
+
if (!refsResult.ok) return notOk(normalizeError(refsResult.failure));
|
|
3779
|
+
const refs = refsResult.value;
|
|
3780
|
+
const findings = [];
|
|
3781
|
+
const loaded = await buildReadAggregate(ctx.config, { migrationsDir });
|
|
3782
|
+
if (!loaded.ok) return notOk(normalizeError(loaded.failure));
|
|
3783
|
+
const { aggregate, contractHash } = loaded.value;
|
|
3784
|
+
const contractConfig = { contract: ifDefined("output", contractPathFor(ctx.config, ctx.cwd)) };
|
|
3785
|
+
try {
|
|
3786
|
+
await readContractEnvelope(contractConfig);
|
|
3787
|
+
} catch (error) {
|
|
3788
|
+
findings.push(contractUnreadableFinding(error instanceof Error ? error.message : "unknown error"));
|
|
3789
|
+
}
|
|
3790
|
+
if (await loadContractRawSafely(contractConfig) !== null) {
|
|
3791
|
+
const corruption = refusePackageCorruptionOnAggregate(aggregate);
|
|
3792
|
+
if (corruption) return notOk(normalizeError(corruption));
|
|
3793
|
+
}
|
|
3794
|
+
const appGraph = aggregate.app.graph();
|
|
3795
|
+
let activeRefHash;
|
|
3796
|
+
let activeRefName;
|
|
3797
|
+
let activeRefEntry;
|
|
3798
|
+
if (args.flags.to !== void 0) {
|
|
3799
|
+
const resolved = resolveContractRef(args.flags.to, {
|
|
3800
|
+
graph: appGraph,
|
|
3801
|
+
refs
|
|
3802
|
+
});
|
|
3803
|
+
if (!resolved.ok) return notOk(normalizeError(resolved.failure));
|
|
3804
|
+
activeRefHash = resolved.value.hash;
|
|
3805
|
+
if (resolved.value.provenance.kind === "ref") {
|
|
3806
|
+
activeRefName = resolved.value.provenance.refName;
|
|
3807
|
+
activeRefEntry = refs[activeRefName];
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
let fromOverrideHash;
|
|
3811
|
+
if (args.flags.from !== void 0) {
|
|
3812
|
+
const resolved = resolveContractRef(args.flags.from, {
|
|
3813
|
+
graph: appGraph,
|
|
3814
|
+
refs
|
|
3815
|
+
});
|
|
3816
|
+
if (!resolved.ok) return notOk(normalizeError(resolved.failure));
|
|
3817
|
+
fromOverrideHash = resolved.value.hash;
|
|
3818
|
+
}
|
|
3819
|
+
const listed = runMigrationList({
|
|
3820
|
+
spaces: await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir),
|
|
3821
|
+
...ifDefined("spaceFilter", args.flags.space)
|
|
3822
|
+
});
|
|
3823
|
+
if (!listed.ok) return notOk(normalizeError(listed.failure));
|
|
3824
|
+
const scopedSpaces = listed.value.spaces;
|
|
3825
|
+
const connects = dbConnection !== void 0 && hasDriver && !usingFromOverride;
|
|
3826
|
+
let database = NO_DATABASE_STATE;
|
|
3827
|
+
if (connects) {
|
|
3828
|
+
const read = await readDatabaseState({
|
|
3829
|
+
client: createControlClient({
|
|
3830
|
+
family: ctx.config.family,
|
|
3831
|
+
target: ctx.config.target,
|
|
3832
|
+
adapter: ctx.config.adapter,
|
|
3833
|
+
...ifDefined("driver", ctx.config.driver),
|
|
3834
|
+
extensions: ctx.config.extensions ?? []
|
|
3835
|
+
}),
|
|
3836
|
+
connection: dbConnection,
|
|
3837
|
+
spaceIds: scopedSpaces.map((entry) => entry.space)
|
|
3838
|
+
});
|
|
3839
|
+
if (!read.ok) return notOk(read.failure);
|
|
3840
|
+
database = read.value;
|
|
3841
|
+
}
|
|
3842
|
+
const appMarker = database.markersBySpace.get(aggregate.app.spaceId);
|
|
3843
|
+
if (activeRefEntry !== void 0 && activeRefEntry.invariants.length > 0 && connects) {
|
|
3844
|
+
const unknown = refuseUnknownInvariants({
|
|
3845
|
+
graph: appGraph,
|
|
3846
|
+
markerInvariants: appMarker?.invariants ?? [],
|
|
3847
|
+
refInvariants: activeRefEntry.invariants,
|
|
3848
|
+
...ifDefined("refName", activeRefName)
|
|
3849
|
+
});
|
|
3850
|
+
if (unknown) return notOk(normalizeError(unknown));
|
|
3851
|
+
}
|
|
3852
|
+
const glyphMode = args.flags.ascii ? "ascii" : "unicode";
|
|
3853
|
+
const styler = createToneMigrationListStyler();
|
|
3854
|
+
const showSpaceHeadings = scopedSpaces.length > 1;
|
|
3855
|
+
const statusSpaces = [];
|
|
3856
|
+
const renderInputs = [];
|
|
3857
|
+
const emptySpaces = [];
|
|
3858
|
+
let divergedMarker;
|
|
3859
|
+
let noPath;
|
|
3860
|
+
let headlineTargetHash = activeRefHash ?? contractHash;
|
|
3861
|
+
let totalPending = 0;
|
|
3862
|
+
for (const entry of scopedSpaces) {
|
|
3863
|
+
const space = aggregate.space(entry.space);
|
|
3864
|
+
if (space === void 0) continue;
|
|
3865
|
+
const graph = space.graph();
|
|
3866
|
+
const spaceContractHash = space.contract().storage.storageHash;
|
|
3867
|
+
const targetHash = activeRefHash ?? spaceContractHash;
|
|
3868
|
+
if (entry.space === aggregate.app.spaceId) headlineTargetHash = targetHash;
|
|
3869
|
+
const markerHash = usingFromOverride ? fromOverrideHash : database.markersBySpace.get(entry.space)?.storageHash;
|
|
3870
|
+
const originHash = originHashForStatus(markerHash);
|
|
3871
|
+
const markerInGraph = markerHash === void 0 || graph.nodes.has(markerHash) || markerHash === spaceContractHash;
|
|
3872
|
+
if (connects && markerInGraph && originHash !== targetHash && noPath === void 0 && !hasMigrationPath(graph, originHash, targetHash)) noPath = {
|
|
3873
|
+
markerHash,
|
|
3874
|
+
targetHash
|
|
3875
|
+
};
|
|
3876
|
+
if (connects && markerHash !== void 0 && !markerInGraph) {
|
|
3877
|
+
divergedMarker ??= {
|
|
3878
|
+
space: entry.space,
|
|
3879
|
+
markerHash
|
|
3880
|
+
};
|
|
3881
|
+
findings.push(markerNotInHistoryFinding(entry.space));
|
|
3882
|
+
}
|
|
3883
|
+
const ledger = database.ledgersBySpace.get(entry.space) ?? [];
|
|
3884
|
+
const annotations = deriveStatusEdgeAnnotations({
|
|
3885
|
+
graph,
|
|
3886
|
+
targetHash,
|
|
3887
|
+
originHash,
|
|
3888
|
+
appliedMigrationHashes: connects ? appliedHashesFromLedger(ledger) : /* @__PURE__ */ new Set(),
|
|
3889
|
+
showAppliedOverlay: connects
|
|
3890
|
+
});
|
|
3891
|
+
const migrations = entry.migrations.map((migration) => ({
|
|
3892
|
+
...migration,
|
|
3893
|
+
status: statusForMigrationHash(migration.hash, annotations)
|
|
3894
|
+
}));
|
|
3895
|
+
totalPending += migrations.filter((migration) => migration.status === "pending").length;
|
|
3896
|
+
statusSpaces.push({
|
|
3897
|
+
space: entry.space,
|
|
3898
|
+
currentContract: markerHash ?? null,
|
|
3899
|
+
targetContract: targetHash,
|
|
3900
|
+
migrations
|
|
3901
|
+
});
|
|
3902
|
+
if (graph.nodes.size === 0) {
|
|
3903
|
+
emptySpaces.push(entry.space);
|
|
3904
|
+
continue;
|
|
3905
|
+
}
|
|
3906
|
+
renderInputs.push({
|
|
3907
|
+
spaceId: entry.space,
|
|
3908
|
+
graph,
|
|
3909
|
+
migrations: entry.migrations,
|
|
3910
|
+
liveContractHash: contractHash,
|
|
3911
|
+
refsByHash: listRefsByContractHash(space),
|
|
3912
|
+
statusOverlayByHash: annotations,
|
|
3913
|
+
colorize: true,
|
|
3914
|
+
glyphMode,
|
|
3915
|
+
styler,
|
|
3916
|
+
palette: TONE_MIGRATION_GRAPH_PALETTE,
|
|
3917
|
+
isAppSpace: entry.space === aggregate.app.spaceId,
|
|
3918
|
+
...connects && markerHash !== void 0 ? { dbHash: markerHash } : {}
|
|
3919
|
+
});
|
|
3920
|
+
}
|
|
3921
|
+
const requiredInvariants = [...activeRefEntry?.invariants ?? []].sort();
|
|
3922
|
+
if (connects && requiredInvariants.length > 0) {
|
|
3923
|
+
const held = new Set(appMarker?.invariants ?? []);
|
|
3924
|
+
const missing = requiredInvariants.filter((id) => !held.has(id));
|
|
3925
|
+
if (missing.length > 0) {
|
|
3926
|
+
findings.push(missingInvariantsFinding({
|
|
3927
|
+
missing,
|
|
3928
|
+
refName: activeRefName
|
|
3929
|
+
}));
|
|
3930
|
+
if (activeRefHash !== void 0) {
|
|
3931
|
+
const unreachable = refuseMissingInvariantPath({
|
|
3932
|
+
graph: appGraph,
|
|
3933
|
+
originHash: originHashForStatus(appMarker?.storageHash),
|
|
3934
|
+
targetHash: activeRefHash,
|
|
3935
|
+
missing,
|
|
3936
|
+
...ifDefined("refName", activeRefName)
|
|
3937
|
+
});
|
|
3938
|
+
if (unreachable) return notOk(normalizeError(unreachable));
|
|
3939
|
+
}
|
|
3940
|
+
}
|
|
3941
|
+
}
|
|
3942
|
+
const rendered = renderMigrationGraphSpaceTrees(renderInputs);
|
|
3943
|
+
const treesBySpace = new Map(renderInputs.map((input, index) => [input.spaceId, rendered[index] ?? ""]));
|
|
3944
|
+
const sections = scopedSpaces.map((entry) => {
|
|
3945
|
+
const tree = treesBySpace.get(entry.space) ?? "";
|
|
3946
|
+
return {
|
|
3947
|
+
space: entry.space,
|
|
3948
|
+
tree: showSpaceHeadings && tree.length > 0 ? indentMigrationGraphTreeBlock(tree, " ") : tree,
|
|
3949
|
+
showHeading: showSpaceHeadings
|
|
3950
|
+
};
|
|
3951
|
+
});
|
|
3952
|
+
const summary = scopedSpaces.every((entry) => entry.migrations.length === 0) ? "No migrations found" : noPath !== void 0 ? buildNoPathSummary({
|
|
3953
|
+
markerHash: noPath.markerHash,
|
|
3954
|
+
targetHash: noPath.targetHash,
|
|
3955
|
+
explicitTarget: args.flags.to !== void 0,
|
|
3956
|
+
refName: activeRefName
|
|
3957
|
+
}) : buildStatusHeadline({
|
|
3958
|
+
pendingCount: totalPending,
|
|
3959
|
+
targetHash: headlineTargetHash,
|
|
3960
|
+
markerDiverged: divergedMarker !== void 0,
|
|
3961
|
+
markerHash: divergedMarker?.markerHash
|
|
3962
|
+
});
|
|
3963
|
+
const diagnostics = findings.map((finding) => finding.diagnostic);
|
|
3964
|
+
const document = {
|
|
3965
|
+
ok: true,
|
|
3966
|
+
spaces: statusSpaces,
|
|
3967
|
+
summary,
|
|
3968
|
+
diagnostics: findings.map((finding) => finding.document)
|
|
3969
|
+
};
|
|
3970
|
+
const alarming = divergedMarker !== void 0 || totalPending > 0 || noPath !== void 0;
|
|
3971
|
+
return ok(ctx.present({
|
|
3972
|
+
data: document,
|
|
3973
|
+
diagnostics
|
|
3974
|
+
}, statusPresentations({
|
|
3975
|
+
document,
|
|
3976
|
+
sections,
|
|
3977
|
+
headline: {
|
|
3978
|
+
kind: "summary",
|
|
3979
|
+
status: alarming ? "warn" : "ok",
|
|
3980
|
+
text: summary
|
|
3981
|
+
},
|
|
3982
|
+
migrationsDir: displayPath(migrationsDir, ctx.cwd),
|
|
3983
|
+
database: connects && typeof dbConnection === "string" ? maskConnectionUrl(dbConnection) : void 0,
|
|
3984
|
+
ref: activeRefName,
|
|
3985
|
+
from: args.flags.from,
|
|
3986
|
+
space: args.flags.space,
|
|
3987
|
+
legend: args.flags.legend ? renderMigrationGraphLegend({
|
|
3988
|
+
colorize: true,
|
|
3989
|
+
glyphMode,
|
|
3990
|
+
styler,
|
|
3991
|
+
palette: TONE_MIGRATION_GRAPH_PALETTE
|
|
3992
|
+
}) : void 0
|
|
3993
|
+
})));
|
|
3994
|
+
}
|
|
3995
|
+
});
|
|
2056
3996
|
function deletePresentations(document) {
|
|
2057
3997
|
return {
|
|
2058
3998
|
human: () => [{
|
|
@@ -2224,26 +4164,58 @@ const ormCommandFamily = defineCommandFamily({
|
|
|
2224
4164
|
"contract infer": contractInferCommand,
|
|
2225
4165
|
"db init": dbInitCommand,
|
|
2226
4166
|
"db schema": dbSchemaCommand,
|
|
4167
|
+
"db update": dbUpdateCommand,
|
|
2227
4168
|
format: formatCommand,
|
|
4169
|
+
init: initCommand,
|
|
2228
4170
|
migrate: migrateCommand,
|
|
2229
4171
|
"migration graph": migrationGraphCommand,
|
|
2230
4172
|
"migration list": migrationListCommand,
|
|
2231
4173
|
"migration log": migrationLogCommand,
|
|
4174
|
+
"migration new": migrationNewCommand,
|
|
4175
|
+
"migration plan": migrationPlanCommand,
|
|
2232
4176
|
"migration show": migrationShowCommand,
|
|
4177
|
+
"migration status": migrationStatusCommand,
|
|
2233
4178
|
"ref delete": refDeleteCommand,
|
|
2234
4179
|
"ref list": refListCommand,
|
|
2235
4180
|
"ref set": refSetCommand
|
|
2236
4181
|
},
|
|
2237
4182
|
docsBaseUrl: DOCS_BASE_URL,
|
|
2238
|
-
redirects: [
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
4183
|
+
redirects: [
|
|
4184
|
+
{
|
|
4185
|
+
from: "migration apply",
|
|
4186
|
+
replacement: "{bin} migrate --to <contract>",
|
|
4187
|
+
reason: "Applying a migration is a move to a target contract, not a verb of its own."
|
|
4188
|
+
},
|
|
4189
|
+
{
|
|
4190
|
+
from: "migration ref",
|
|
4191
|
+
replacement: "{bin} ref set|list|delete",
|
|
4192
|
+
reason: "Refs are managed by their own command, for every space rather than migrations alone."
|
|
4193
|
+
},
|
|
4194
|
+
{
|
|
4195
|
+
from: "migration status",
|
|
4196
|
+
flag: "graph",
|
|
4197
|
+
replacement: "{bin} migration graph",
|
|
4198
|
+
reason: "Topology is its own command rather than a mode of the status report."
|
|
4199
|
+
},
|
|
4200
|
+
{
|
|
4201
|
+
from: "migration status",
|
|
4202
|
+
flag: "all",
|
|
4203
|
+
replacement: "{bin} migration log --db <url>",
|
|
4204
|
+
reason: "Execution history is read from the database ledger, which is what migration log does."
|
|
4205
|
+
},
|
|
4206
|
+
{
|
|
4207
|
+
from: "migration status",
|
|
4208
|
+
flag: "limit",
|
|
4209
|
+
replacement: "{bin} migration log --db <url>",
|
|
4210
|
+
reason: "Execution history is read from the database ledger, which is what migration log does."
|
|
4211
|
+
},
|
|
4212
|
+
{
|
|
4213
|
+
from: "migration status",
|
|
4214
|
+
flag: "ref",
|
|
4215
|
+
replacement: "{bin} migration status --to <contract>",
|
|
4216
|
+
reason: "One flag names the target contract, whether it is a ref, a hash or a migration."
|
|
4217
|
+
}
|
|
4218
|
+
]
|
|
2247
4219
|
});
|
|
2248
4220
|
/** The file the ORM's loader reads when `--config` names none. */
|
|
2249
4221
|
const ORM_CONFIG_FILENAME = "prisma-next.config.ts";
|