prisma-next 0.5.0-dev.8 → 0.5.0-dev.80

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/dist/cli-errors-B9OBbled.d.mts +3 -0
  2. package/dist/cli-errors-D3_sMh2K.mjs +33 -0
  3. package/dist/cli-errors-D3_sMh2K.mjs.map +1 -0
  4. package/dist/cli.mjs +16 -78
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/client-qVH-rEgd.mjs +1595 -0
  7. package/dist/client-qVH-rEgd.mjs.map +1 -0
  8. package/dist/{result-handler-Ba3zWQsI.mjs → command-helpers-BeZHkxV8.mjs} +70 -47
  9. package/dist/command-helpers-BeZHkxV8.mjs.map +1 -0
  10. package/dist/commands/contract-emit.d.mts.map +1 -1
  11. package/dist/commands/contract-emit.mjs +2 -4
  12. package/dist/commands/contract-infer.d.mts.map +1 -1
  13. package/dist/commands/contract-infer.mjs +2 -4
  14. package/dist/commands/db-init.d.mts.map +1 -1
  15. package/dist/commands/db-init.mjs +16 -13
  16. package/dist/commands/db-init.mjs.map +1 -1
  17. package/dist/commands/db-schema.d.mts.map +1 -1
  18. package/dist/commands/db-schema.mjs +6 -7
  19. package/dist/commands/db-schema.mjs.map +1 -1
  20. package/dist/commands/db-sign.d.mts.map +1 -1
  21. package/dist/commands/db-sign.mjs +9 -9
  22. package/dist/commands/db-sign.mjs.map +1 -1
  23. package/dist/commands/db-update.d.mts.map +1 -1
  24. package/dist/commands/db-update.mjs +15 -13
  25. package/dist/commands/db-update.mjs.map +1 -1
  26. package/dist/commands/db-verify.d.mts.map +1 -1
  27. package/dist/commands/db-verify.mjs +1 -321
  28. package/dist/commands/migration-apply.d.mts +28 -13
  29. package/dist/commands/migration-apply.d.mts.map +1 -1
  30. package/dist/commands/migration-apply.mjs +55 -151
  31. package/dist/commands/migration-apply.mjs.map +1 -1
  32. package/dist/commands/migration-new.d.mts +0 -1
  33. package/dist/commands/migration-new.d.mts.map +1 -1
  34. package/dist/commands/migration-new.mjs +34 -40
  35. package/dist/commands/migration-new.mjs.map +1 -1
  36. package/dist/commands/migration-plan.d.mts +33 -6
  37. package/dist/commands/migration-plan.d.mts.map +1 -1
  38. package/dist/commands/migration-plan.mjs +2 -348
  39. package/dist/commands/migration-ref.d.mts +1 -1
  40. package/dist/commands/migration-ref.d.mts.map +1 -1
  41. package/dist/commands/migration-ref.mjs +8 -12
  42. package/dist/commands/migration-ref.mjs.map +1 -1
  43. package/dist/commands/migration-show.d.mts +13 -7
  44. package/dist/commands/migration-show.d.mts.map +1 -1
  45. package/dist/commands/migration-show.mjs +35 -36
  46. package/dist/commands/migration-show.mjs.map +1 -1
  47. package/dist/commands/migration-status.d.mts +126 -5
  48. package/dist/commands/migration-status.d.mts.map +1 -1
  49. package/dist/commands/migration-status.mjs +2 -4
  50. package/dist/{config-loader-C25b63rJ.mjs → config-loader-B6sJjXTv.mjs} +3 -5
  51. package/dist/config-loader-B6sJjXTv.mjs.map +1 -0
  52. package/dist/config-loader.d.mts +0 -1
  53. package/dist/config-loader.d.mts.map +1 -1
  54. package/dist/config-loader.mjs +2 -3
  55. package/dist/contract-emit-9DBda5Ou.mjs +150 -0
  56. package/dist/contract-emit-9DBda5Ou.mjs.map +1 -0
  57. package/dist/contract-emit-B77TsJqf.mjs +327 -0
  58. package/dist/contract-emit-B77TsJqf.mjs.map +1 -0
  59. package/dist/{contract-enrichment-CAOELa-H.mjs → contract-enrichment-Dani0mMW.mjs} +4 -6
  60. package/dist/contract-enrichment-Dani0mMW.mjs.map +1 -0
  61. package/dist/{contract-infer-D9cC3rJm.mjs → contract-infer-BK9YFGEG.mjs} +13 -22
  62. package/dist/contract-infer-BK9YFGEG.mjs.map +1 -0
  63. package/dist/db-verify-C0y1PCO2.mjs +404 -0
  64. package/dist/db-verify-C0y1PCO2.mjs.map +1 -0
  65. package/dist/exports/config-types.mjs +1 -2
  66. package/dist/exports/control-api.d.mts +101 -586
  67. package/dist/exports/control-api.d.mts.map +1 -1
  68. package/dist/exports/control-api.mjs +4 -6
  69. package/dist/exports/index.d.mts.map +1 -1
  70. package/dist/exports/index.mjs +28 -30
  71. package/dist/exports/index.mjs.map +1 -1
  72. package/dist/exports/init-output.d.mts +2 -4
  73. package/dist/exports/init-output.d.mts.map +1 -1
  74. package/dist/exports/init-output.mjs +2 -3
  75. package/dist/extension-pack-inputs-C7xgE-vv.mjs +74 -0
  76. package/dist/extension-pack-inputs-C7xgE-vv.mjs.map +1 -0
  77. package/dist/{framework-components-Cr--XBKy.mjs → framework-components-ChqVUxR-.mjs} +3 -4
  78. package/dist/{framework-components-Cr--XBKy.mjs.map → framework-components-ChqVUxR-.mjs.map} +1 -1
  79. package/dist/global-flags-Icqpxk23.d.mts +12 -0
  80. package/dist/global-flags-Icqpxk23.d.mts.map +1 -0
  81. package/dist/helpers-eqdN8tH6.mjs +25 -0
  82. package/dist/helpers-eqdN8tH6.mjs.map +1 -0
  83. package/dist/{init-C5220SY9.mjs → init-CoDVPvQ4.mjs} +26 -35
  84. package/dist/init-CoDVPvQ4.mjs.map +1 -0
  85. package/dist/{inspect-live-schema-yrHAvG71.mjs → inspect-live-schema-CWYxGKlb.mjs} +10 -11
  86. package/dist/inspect-live-schema-CWYxGKlb.mjs.map +1 -0
  87. package/dist/migration-cli.d.mts +41 -12
  88. package/dist/migration-cli.d.mts.map +1 -1
  89. package/dist/migration-cli.mjs +309 -86
  90. package/dist/migration-cli.mjs.map +1 -1
  91. package/dist/{migration-command-scaffold-B3B09et6.mjs → migration-command-scaffold-B5dORFEv.mjs} +8 -9
  92. package/dist/migration-command-scaffold-B5dORFEv.mjs.map +1 -0
  93. package/dist/migration-plan-C6lVaHsO.mjs +554 -0
  94. package/dist/migration-plan-C6lVaHsO.mjs.map +1 -0
  95. package/dist/{migration-status-DUMiH8_G.mjs → migration-status-CZ-D5k7k.mjs} +272 -65
  96. package/dist/migration-status-CZ-D5k7k.mjs.map +1 -0
  97. package/dist/migrations-D_UJnpuW.mjs +216 -0
  98. package/dist/migrations-D_UJnpuW.mjs.map +1 -0
  99. package/dist/{output-BpcQrnnq.mjs → output-B16Kefzx.mjs} +9 -3
  100. package/dist/output-B16Kefzx.mjs.map +1 -0
  101. package/dist/{progress-adapter-DvQWB1nK.mjs → progress-adapter-DFfvZcYL.mjs} +2 -2
  102. package/dist/{progress-adapter-DvQWB1nK.mjs.map → progress-adapter-DFfvZcYL.mjs.map} +1 -1
  103. package/dist/result-handler-rmPVKIP2.mjs +25 -0
  104. package/dist/result-handler-rmPVKIP2.mjs.map +1 -0
  105. package/dist/rolldown-runtime-twds-ZHy.mjs +14 -0
  106. package/dist/{terminal-ui-C3ZLwQxK.mjs → terminal-ui-C_hFNbAn.mjs} +4 -28
  107. package/dist/terminal-ui-C_hFNbAn.mjs.map +1 -0
  108. package/dist/types-D7x-IFLO.d.mts +858 -0
  109. package/dist/types-D7x-IFLO.d.mts.map +1 -0
  110. package/dist/{verify-Bkycc-Tf.mjs → verify-CiwNWM9N.mjs} +3 -4
  111. package/dist/verify-CiwNWM9N.mjs.map +1 -0
  112. package/package.json +19 -17
  113. package/dist/cli-errors-BFYgBH3L.d.mts +0 -4
  114. package/dist/cli-errors-Cd79vmTH.mjs +0 -5
  115. package/dist/client-CrsnY58k.mjs +0 -997
  116. package/dist/client-CrsnY58k.mjs.map +0 -1
  117. package/dist/commands/db-verify.mjs.map +0 -1
  118. package/dist/commands/migration-plan.mjs.map +0 -1
  119. package/dist/config-loader-C25b63rJ.mjs.map +0 -1
  120. package/dist/contract-emit--feXyNd7.mjs +0 -4
  121. package/dist/contract-emit-NJ01hiiv.mjs +0 -195
  122. package/dist/contract-emit-NJ01hiiv.mjs.map +0 -1
  123. package/dist/contract-emit-V5SSitUT.mjs +0 -122
  124. package/dist/contract-emit-V5SSitUT.mjs.map +0 -1
  125. package/dist/contract-enrichment-CAOELa-H.mjs.map +0 -1
  126. package/dist/contract-infer-D9cC3rJm.mjs.map +0 -1
  127. package/dist/extract-operation-statements-DsFfxXVZ.mjs +0 -13
  128. package/dist/extract-operation-statements-DsFfxXVZ.mjs.map +0 -1
  129. package/dist/extract-sql-ddl-D9UbZDyz.mjs +0 -26
  130. package/dist/extract-sql-ddl-D9UbZDyz.mjs.map +0 -1
  131. package/dist/init-C5220SY9.mjs.map +0 -1
  132. package/dist/inspect-live-schema-yrHAvG71.mjs.map +0 -1
  133. package/dist/migration-command-scaffold-B3B09et6.mjs.map +0 -1
  134. package/dist/migration-status-DUMiH8_G.mjs.map +0 -1
  135. package/dist/migrations-Bo5WtTla.mjs +0 -153
  136. package/dist/migrations-Bo5WtTla.mjs.map +0 -1
  137. package/dist/output-BpcQrnnq.mjs.map +0 -1
  138. package/dist/result-handler-Ba3zWQsI.mjs.map +0 -1
  139. package/dist/terminal-ui-C3ZLwQxK.mjs.map +0 -1
  140. package/dist/validate-contract-deps-B_Cs29TL.mjs +0 -37
  141. package/dist/validate-contract-deps-B_Cs29TL.mjs.map +0 -1
  142. package/dist/verify-Bkycc-Tf.mjs.map +0 -1
@@ -0,0 +1,554 @@
1
+ import { t as loadConfig } from "./config-loader-B6sJjXTv.mjs";
2
+ import { _ as errorUnexpected, a as errorContractValidationFailed, f as errorMigrationPlanningFailed, h as errorTargetMigrationNotSupported, l as errorFileNotFound, m as errorRuntime, t as CliStructuredError, v as mapMigrationToolsError } from "./cli-errors-D3_sMh2K.mjs";
3
+ import { t as assertFrameworkComponentsCompatible } from "./framework-components-ChqVUxR-.mjs";
4
+ import { a as loadMigrationPackages, c as resolveContractPath, d as setCommandDescriptions, f as setCommandExamples, g as parseGlobalFlags, i as getTargetMigrations, l as resolveMigrationPaths, t as addGlobalOptions, y as formatStyledHeader } from "./command-helpers-BeZHkxV8.mjs";
5
+ import { t as TerminalUI } from "./terminal-ui-C_hFNbAn.mjs";
6
+ import { t as handleResult } from "./result-handler-rmPVKIP2.mjs";
7
+ import { i as toMigratePassInputs, n as toExtensionInputs, r as toExtensionMigrationsInputs } from "./extension-pack-inputs-C7xgE-vv.mjs";
8
+ import { Command } from "commander";
9
+ import { getEmittedArtifactPaths } from "@prisma-next/emitter";
10
+ import { notOk, ok } from "@prisma-next/utils/result";
11
+ import { join, relative } from "pathe";
12
+ import { readFile } from "node:fs/promises";
13
+ import { APP_SPACE_ID, createControlStack, hasOperationPreview } from "@prisma-next/framework-components/control";
14
+ import { copyFilesWithRename, formatMigrationDirName, materialiseExtensionMigrationPackageIfMissing, writeMigrationPackage } from "@prisma-next/migration-tools/io";
15
+ import { findLatestMigration } from "@prisma-next/migration-tools/migration-graph";
16
+ import { detectSpaceContractDrift, emitContractSpaceArtefacts, planAllSpaces, readContractSpaceHeadRef, spaceMigrationDirectory } from "@prisma-next/migration-tools/spaces";
17
+ import { MigrationToolsError } from "@prisma-next/migration-tools/errors";
18
+ import { computeMigrationHash } from "@prisma-next/migration-tools/hash";
19
+ import { writeMigrationTs } from "@prisma-next/migration-tools/migration-ts";
20
+ import { deriveProvidedInvariants } from "@prisma-next/migration-tools/invariants";
21
+ //#region src/utils/contract-space-extension-migrations-pass.ts
22
+ /**
23
+ * Materialise an extension's pre-built migration packages onto disk
24
+ * under `migrations/<spaceId>/<dirName>/` for every package that does
25
+ * not yet exist there.
26
+ *
27
+ * Helper-location pattern — the per-space "planner" for extension
28
+ * spaces is a no-op that just returns the descriptor's `migrations`
29
+ * verbatim; the value `planAllSpaces` brings to this consumer site is
30
+ * **deterministic ordering** (alphabetical by spaceId) and
31
+ * **duplicate-spaceId detection**. The actual write is performed via
32
+ * `materialiseMigrationPackage` per package.
33
+ *
34
+ * Idempotent: an existing `migrations/<spaceId>/<dirName>/` is left
35
+ * untouched and reported in `result.skipped` — the helper never
36
+ * overwrites authored migration content, ensuring re-running
37
+ * `migrate` does not corrupt or churn extension migration packages.
38
+ *
39
+ * Per-space artefacts (`contract.json`, `contract.d.ts`,
40
+ * `refs/head.json`) are emitted by
41
+ * {@link import('./contract-space-migrate-pass').runContractSpaceMigratePass}
42
+ * separately — they cover the head-pointer side of the ledger. This
43
+ * helper covers the migration-graph side.
44
+ */
45
+ async function runContractSpaceExtensionMigrationsPass(inputs) {
46
+ const planned = planAllSpaces(inputs.extensionPacks.filter((pack) => pack.contractSpace !== void 0).map((pack) => ({
47
+ spaceId: pack.id,
48
+ priorContract: null,
49
+ newContract: pack.contractSpace.contractJson,
50
+ __migrations: pack.contractSpace.migrations
51
+ })), (input) => input.__migrations);
52
+ const emitted = [];
53
+ const skipped = [];
54
+ for (const space of planned) {
55
+ const spaceDir = spaceMigrationDirectory(inputs.migrationsDir, space.spaceId);
56
+ for (const pkg of space.migrationPackages) {
57
+ const { written } = await materialiseExtensionMigrationPackageIfMissing(spaceDir, pkg);
58
+ if (written) emitted.push({
59
+ spaceId: space.spaceId,
60
+ dirName: pkg.dirName
61
+ });
62
+ else skipped.push({
63
+ spaceId: space.spaceId,
64
+ dirName: pkg.dirName
65
+ });
66
+ }
67
+ }
68
+ return {
69
+ emitted,
70
+ skipped
71
+ };
72
+ }
73
+ //#endregion
74
+ //#region src/utils/contract-space-migrate-pass.ts
75
+ /**
76
+ * Run drift detection + on-disk artefact emission for every loaded
77
+ * extension space at `migrate` time.
78
+ *
79
+ * Per sub-spec § 3:
80
+ *
81
+ * - For each declared extension that exposes a `contractSpace`:
82
+ * - Read the on-disk head hash from `migrations/<spaceId>/refs/head.json`
83
+ * (returns `null` on first emit).
84
+ * - Compare against the descriptor's `headRef.hash` via
85
+ * `detectSpaceContractDrift`. The `kind` discriminant decides whether
86
+ * the user sees a warning (`drift`), a no-op silent emit (`firstEmit`,
87
+ * `noDrift`), or nothing at all.
88
+ * - Always re-emit the on-disk artefacts (`contract.json`, `contract.d.ts`,
89
+ * `refs/head.json`). The framework owns these files and the helper is
90
+ * idempotent.
91
+ *
92
+ * Drift warnings are returned to the caller for formatting (TerminalUI,
93
+ * structured-output envelope, etc.) — the helper does not print directly,
94
+ * keeping it framework-neutral and unit-testable.
95
+ *
96
+ * Extension migration packages (the descriptor's pre-canned `migrations`
97
+ * array → `migrations/<spaceId>/<dirName>/`) are intentionally not
98
+ * materialised here — that interaction will be wired in a follow-on round
99
+ * once the runner-side single-tx slice (sub-spec § 6) is in place.
100
+ * On-disk artefacts are sufficient to lock the drift-warning behaviour
101
+ * and the always-on re-emit AC for R2.
102
+ *
103
+ * @see specs/framework-mechanism.spec.md § 3 — Drift detection (T1.9).
104
+ */
105
+ async function runContractSpaceMigratePass(inputs) {
106
+ const drifts = [];
107
+ const emittedSpaceIds = [];
108
+ for (const pack of inputs.extensionPacks) {
109
+ if (pack.contractSpace === void 0) continue;
110
+ const { contractJson, headRef } = pack.contractSpace;
111
+ const onDiskHeadRef = await readContractSpaceHeadRef(inputs.migrationsDir, pack.id);
112
+ const drift = detectSpaceContractDrift(pack.id, {
113
+ descriptorHash: headRef.hash,
114
+ priorHeadHash: onDiskHeadRef?.hash ?? null
115
+ });
116
+ drifts.push(drift);
117
+ await emitContractSpaceArtefacts(inputs.migrationsDir, pack.id, {
118
+ contract: contractJson,
119
+ contractDts: buildPlaceholderContractDts(pack.id),
120
+ headRef: {
121
+ hash: headRef.hash,
122
+ invariants: headRef.invariants
123
+ }
124
+ });
125
+ emittedSpaceIds.push(pack.id);
126
+ }
127
+ return {
128
+ drifts,
129
+ emittedSpaceIds
130
+ };
131
+ }
132
+ /**
133
+ * Format the user-facing drift warning for a single space. Callers
134
+ * funnel this through their preferred output channel (TerminalUI line,
135
+ * structured-output envelope `warnings[]`, etc.).
136
+ *
137
+ * Locks AM7 — drift warning surfaces the extension name and the diff
138
+ * direction (descriptor → on-disk head).
139
+ */
140
+ function formatContractSpaceDriftWarning(drift) {
141
+ if (drift.kind !== "drift") throw new Error(`formatContractSpaceDriftWarning called with non-drift result: ${drift.kind}`);
142
+ return `Contract-space drift detected for "${drift.spaceId}": descriptor hash ${drift.descriptorHash} differs from on-disk head hash ${drift.priorHeadHash ?? "<none>"}. The on-disk artefacts under migrations/${drift.spaceId}/ will be refreshed to match the descriptor.`;
143
+ }
144
+ /**
145
+ * Placeholder `.d.ts` content for an extension space's on-disk mirror.
146
+ *
147
+ * Rendering a fully-typed `.d.ts` for an extension contract requires the
148
+ * SQL-family renderer with the codec / typemap registry threaded
149
+ * through; that integration is tracked under sub-spec Open Question 3
150
+ * (see `projects/extension-contract-spaces/specs/framework-mechanism.spec.md`).
151
+ *
152
+ * Until that ships, the on-disk `.d.ts` is a `@ts-nocheck` stub. The
153
+ * spec gap closing alongside the typed renderer is **AC2 / AC14**
154
+ * (byte-equivalence of per-space artefacts under `migrate`):
155
+ * a placeholder cannot be byte-equal to a fully-rendered `.d.ts` from
156
+ * the same descriptor, so AC2 / AC14 are PARTIAL today and become
157
+ * fully-PASS once OQ3 closes.
158
+ *
159
+ * Scheduled to close in **M3** (cipherstash editor tooling) — that's
160
+ * the milestone where the typed renderer gets its first real
161
+ * extension-space consumer and the byte-equivalence guarantee is
162
+ * practically required.
163
+ */
164
+ function buildPlaceholderContractDts(spaceId) {
165
+ return [
166
+ "// @ts-nocheck",
167
+ "/**",
168
+ ` * Placeholder \`.d.ts\` for extension space "${spaceId}".`,
169
+ " *",
170
+ " * The framework re-emits this file on every `migrate` run alongside",
171
+ " * `contract.json` and `refs/head.json`. A typed `.d.ts` rendering",
172
+ " * pass for extension contracts is tracked under the project's open",
173
+ " * questions; until that ships, consumers should import",
174
+ " * `contract.json` directly with `validateContract<…>(…)`.",
175
+ " */",
176
+ "export {};",
177
+ ""
178
+ ].join("\n");
179
+ }
180
+ //#endregion
181
+ //#region src/commands/migration-plan.ts
182
+ async function executeMigrationPlanCommand(options, flags, ui, startTime) {
183
+ const config = await loadConfig(options.config);
184
+ const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative } = resolveMigrationPaths(options.config, config);
185
+ const contractPathAbsolute = resolveContractPath(config);
186
+ const contractPath = relative(process.cwd(), contractPathAbsolute);
187
+ if (!flags.json && !flags.quiet) {
188
+ const details = [
189
+ {
190
+ label: "config",
191
+ value: configPath
192
+ },
193
+ {
194
+ label: "contract",
195
+ value: contractPath
196
+ },
197
+ {
198
+ label: "migrations",
199
+ value: appMigrationsRelative
200
+ }
201
+ ];
202
+ if (options.from) details.push({
203
+ label: "from",
204
+ value: options.from
205
+ });
206
+ if (options.name) details.push({
207
+ label: "name",
208
+ value: options.name
209
+ });
210
+ const header = formatStyledHeader({
211
+ command: "migration plan",
212
+ description: "Plan a migration from contract changes",
213
+ url: "https://pris.ly/migration-plan",
214
+ details,
215
+ flags
216
+ });
217
+ ui.stderr(header);
218
+ }
219
+ let contractJsonContent;
220
+ try {
221
+ contractJsonContent = await readFile(contractPathAbsolute, "utf-8");
222
+ } catch (error) {
223
+ if (error instanceof Error && error.code === "ENOENT") return notOk(errorFileNotFound(contractPathAbsolute, {
224
+ why: `Contract file not found at ${contractPathAbsolute}`,
225
+ fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
226
+ }));
227
+ return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}` }));
228
+ }
229
+ let toContractJson;
230
+ try {
231
+ toContractJson = JSON.parse(contractJsonContent);
232
+ } catch (error) {
233
+ return notOk(errorContractValidationFailed(`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`, { where: { path: contractPathAbsolute } }));
234
+ }
235
+ const rawStorageHash = toContractJson.storage?.storageHash;
236
+ if (typeof rawStorageHash !== "string") return notOk(errorContractValidationFailed("Contract is missing storageHash", { where: { path: contractPathAbsolute } }));
237
+ const toStorageHash = rawStorageHash;
238
+ let fromContract = null;
239
+ let fromHash = null;
240
+ let fromContractSourceDir = null;
241
+ try {
242
+ const { bundles, graph } = await loadMigrationPackages(appMigrationsDir);
243
+ if (options.from) {
244
+ const resolved = resolveBundleByPrefix(bundles, options.from);
245
+ if (!resolved.ok) {
246
+ const f = resolved.failure;
247
+ return notOk(f.reason === "ambiguous" ? errorRuntime("Multiple matching migrations found", {
248
+ why: `Prefix "${options.from}" matches ${f.count} migrations in ${appMigrationsRelative}`,
249
+ fix: "Provide a longer prefix to disambiguate, or omit --from to use the latest migration target."
250
+ }) : errorRuntime("Starting contract not found", {
251
+ why: `No migration with to hash matching "${options.from}" exists in ${appMigrationsRelative}`,
252
+ fix: "Check that the --from hash matches a known migration target hash, or omit --from to use the latest migration target."
253
+ }));
254
+ }
255
+ fromHash = resolved.value.metadata.to;
256
+ fromContract = resolved.value.metadata.toContract;
257
+ fromContractSourceDir = resolved.value.dirPath;
258
+ } else {
259
+ const latestMigration = findLatestMigration(graph);
260
+ if (latestMigration) {
261
+ fromHash = latestMigration.to;
262
+ const leafPkg = bundles.find((p) => p.metadata.migrationHash === latestMigration.migrationHash);
263
+ if (leafPkg) {
264
+ fromContract = leafPkg.metadata.toContract;
265
+ fromContractSourceDir = leafPkg.dirPath;
266
+ }
267
+ }
268
+ }
269
+ } catch (error) {
270
+ if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
271
+ const message = error instanceof Error ? error.message : String(error);
272
+ return notOk(errorUnexpected(message, { why: `Unexpected error while loading migrations: ${message}` }));
273
+ }
274
+ const canonicalExtensionInputs = toExtensionInputs(config.extensionPacks ?? []);
275
+ const migratePass = await runContractSpaceMigratePass({
276
+ migrationsDir,
277
+ extensionPacks: toMigratePassInputs(canonicalExtensionInputs)
278
+ });
279
+ if (!flags.json && !flags.quiet) {
280
+ for (const drift of migratePass.drifts) if (drift.kind === "drift") ui.stderr(formatContractSpaceDriftWarning(drift));
281
+ }
282
+ const extensionMigrationsResult = await runContractSpaceExtensionMigrationsPass({
283
+ migrationsDir,
284
+ extensionPacks: toExtensionMigrationsInputs(canonicalExtensionInputs)
285
+ });
286
+ if (!flags.json && !flags.quiet) for (const entry of extensionMigrationsResult.emitted) ui.step(`Emitted ${entry.spaceId}/${entry.dirName}`);
287
+ if (fromHash === toStorageHash) return ok({
288
+ ok: true,
289
+ noOp: true,
290
+ from: fromHash,
291
+ to: toStorageHash,
292
+ operations: [],
293
+ emittedExtensionDirs: extensionMigrationsResult.emitted,
294
+ summary: "No changes detected between contracts",
295
+ timings: { total: Date.now() - startTime }
296
+ });
297
+ const migrations = getTargetMigrations(config.target);
298
+ if (!migrations) return notOk(errorTargetMigrationNotSupported({ why: `Target "${config.target.id}" does not support migrations` }));
299
+ const frameworkComponents = assertFrameworkComponentsCompatible(config.family.familyId, config.target.targetId, [
300
+ config.target,
301
+ config.adapter,
302
+ ...config.extensionPacks ?? []
303
+ ]);
304
+ const timestamp = /* @__PURE__ */ new Date();
305
+ const packageDir = join(appMigrationsDir, formatMigrationDirName(timestamp, options.name ?? "migration"));
306
+ const baseMetadata = {
307
+ from: fromHash,
308
+ to: toStorageHash,
309
+ fromContract,
310
+ toContract: toContractJson,
311
+ hints: {
312
+ used: [],
313
+ applied: [],
314
+ plannerVersion: "2.0.0"
315
+ },
316
+ labels: [],
317
+ createdAt: timestamp.toISOString()
318
+ };
319
+ try {
320
+ const stack = createControlStack(config);
321
+ const familyInstance = config.family.create(stack);
322
+ const planner = migrations.createPlanner(familyInstance);
323
+ const fromSchema = migrations.contractToSchema(fromContract, frameworkComponents);
324
+ const plannerResult = planner.plan({
325
+ contract: toContractJson,
326
+ schema: fromSchema,
327
+ policy: { allowedOperationClasses: [
328
+ "additive",
329
+ "widening",
330
+ "destructive",
331
+ "data"
332
+ ] },
333
+ fromContract,
334
+ frameworkComponents,
335
+ spaceId: APP_SPACE_ID
336
+ });
337
+ if (plannerResult.kind === "failure") return notOk(errorMigrationPlanningFailed({ conflicts: plannerResult.conflicts }));
338
+ let plannedOps = [];
339
+ let hasPlaceholders = false;
340
+ try {
341
+ plannedOps = plannerResult.plan.operations;
342
+ if (plannedOps.length === 0) return notOk(errorMigrationPlanningFailed({ conflicts: [{
343
+ kind: "unsupportedChange",
344
+ summary: "Contract changed but planner produced no operations. This indicates unsupported or ignored changes."
345
+ }] }));
346
+ } catch (e) {
347
+ if (CliStructuredError.is(e) && e.domain === "MIG" && e.code === "2001") hasPlaceholders = true;
348
+ else throw e;
349
+ }
350
+ const migrationTsContent = plannerResult.plan.renderTypeScript();
351
+ const opsForWrite = hasPlaceholders ? [] : plannedOps;
352
+ const metadataWithInvariants = {
353
+ ...baseMetadata,
354
+ providedInvariants: deriveProvidedInvariants(opsForWrite)
355
+ };
356
+ await writeMigrationPackage(packageDir, {
357
+ ...metadataWithInvariants,
358
+ migrationHash: computeMigrationHash(metadataWithInvariants, opsForWrite)
359
+ }, opsForWrite);
360
+ const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
361
+ await copyFilesWithRename(packageDir, [{
362
+ sourcePath: destinationArtifacts.jsonPath,
363
+ destName: "end-contract.json"
364
+ }, {
365
+ sourcePath: destinationArtifacts.dtsPath,
366
+ destName: "end-contract.d.ts"
367
+ }]);
368
+ if (fromContractSourceDir !== null) {
369
+ const sourceArtifacts = getEmittedArtifactPaths(join(fromContractSourceDir, "end-contract.json"));
370
+ await copyFilesWithRename(packageDir, [{
371
+ sourcePath: sourceArtifacts.jsonPath,
372
+ destName: "start-contract.json"
373
+ }, {
374
+ sourcePath: sourceArtifacts.dtsPath,
375
+ destName: "start-contract.d.ts"
376
+ }]);
377
+ }
378
+ await writeMigrationTs(packageDir, migrationTsContent);
379
+ if (hasPlaceholders) return ok({
380
+ ok: true,
381
+ noOp: false,
382
+ from: fromHash,
383
+ to: toStorageHash,
384
+ dir: relative(process.cwd(), packageDir),
385
+ operations: [],
386
+ emittedExtensionDirs: extensionMigrationsResult.emitted,
387
+ pendingPlaceholders: true,
388
+ summary: "Planned migration with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit",
389
+ timings: { total: Date.now() - startTime }
390
+ });
391
+ const preview = hasOperationPreview(familyInstance) ? familyInstance.toOperationPreview(plannedOps) : void 0;
392
+ return ok({
393
+ ok: true,
394
+ noOp: false,
395
+ from: fromHash,
396
+ to: toStorageHash,
397
+ dir: relative(process.cwd(), packageDir),
398
+ operations: plannedOps.map((op) => ({
399
+ id: op.id,
400
+ label: op.label,
401
+ operationClass: op.operationClass
402
+ })),
403
+ emittedExtensionDirs: extensionMigrationsResult.emitted,
404
+ ...preview !== void 0 ? { preview } : {},
405
+ summary: buildPlanSummary(plannedOps.length, extensionMigrationsResult.emitted.length),
406
+ timings: { total: Date.now() - startTime }
407
+ });
408
+ } catch (error) {
409
+ if (CliStructuredError.is(error)) return notOk(error);
410
+ if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
411
+ const message = error instanceof Error ? error.message : String(error);
412
+ return notOk(errorUnexpected(message, { why: `Unexpected error during migration plan: ${message}` }));
413
+ }
414
+ }
415
+ function createMigrationPlanCommand() {
416
+ const command = new Command("plan");
417
+ setCommandDescriptions(command, "Plan a migration from contract changes", "Compares the emitted contract against the latest on-disk migration state and\nproduces a new migration package with the required operations. No database\nconnection is needed — this is a fully offline operation.");
418
+ setCommandExamples(command, ["prisma-next migration plan", "prisma-next migration plan --name add-users-table"]);
419
+ addGlobalOptions(command).option("--config <path>", "Path to prisma-next.config.ts").option("--name <slug>", "Name slug for the migration directory", "migration").option("--from <hash>", "Explicit starting contract hash (overrides latest migration target)").action(async (options) => {
420
+ const flags = parseGlobalFlags(options);
421
+ const startTime = Date.now();
422
+ const ui = new TerminalUI({
423
+ color: flags.color,
424
+ interactive: flags.interactive
425
+ });
426
+ const exitCode = handleResult(await executeMigrationPlanCommand(options, flags, ui, startTime), flags, ui, (planResult) => {
427
+ if (flags.json) ui.output(JSON.stringify(planResult, null, 2));
428
+ else if (!flags.quiet) ui.log(formatMigrationPlanOutput(planResult, flags));
429
+ });
430
+ process.exit(exitCode);
431
+ });
432
+ return command;
433
+ }
434
+ /**
435
+ * Compose the success-line summary so the cross-space side effect
436
+ * (extension-space migration packages materialised on disk during
437
+ * this `plan` run) is visible in the top line — not just in the
438
+ * step log above it.
439
+ *
440
+ * Example outputs:
441
+ * - `Planned 3 operation(s)` (app-space-only project)
442
+ * - `Planned 3 operation(s); materialised 1 extension-space migration` (one extension)
443
+ * - `Planned 3 operation(s); materialised 2 extension-space migrations` (two extensions)
444
+ *
445
+ * Locks AC3 at the summary-line level: a reader of the success line
446
+ * can tell that something happened beyond the app space.
447
+ */
448
+ function buildPlanSummary(plannedOpsCount, emittedExtensionDirsCount) {
449
+ const base = `Planned ${plannedOpsCount} operation(s)`;
450
+ if (emittedExtensionDirsCount === 0) return base;
451
+ return `${base}; materialised ${emittedExtensionDirsCount} ${emittedExtensionDirsCount === 1 ? "extension-space migration" : "extension-space migrations"}`;
452
+ }
453
+ function formatMigrationPlanOutput(result, flags) {
454
+ const lines = [];
455
+ const useColor = flags.color !== false;
456
+ const green_ = useColor ? (s) => `\x1b[32m${s}\x1b[0m` : (s) => s;
457
+ const yellow_ = useColor ? (s) => `\x1b[33m${s}\x1b[0m` : (s) => s;
458
+ const dim_ = useColor ? (s) => `\x1b[2m${s}\x1b[0m` : (s) => s;
459
+ function appendEmittedExtensions() {
460
+ if (result.emittedExtensionDirs.length === 0) return;
461
+ lines.push("");
462
+ lines.push(dim_("Emitted extension migrations:"));
463
+ for (const entry of result.emittedExtensionDirs) lines.push(dim_(` ${entry.spaceId} → migrations/${entry.spaceId}/${entry.dirName}`));
464
+ lines.push("");
465
+ lines.push(`Next: review the extension migrations above, then run ${green_("prisma-next migration apply")}.`);
466
+ }
467
+ if (result.noOp) {
468
+ lines.push(`${green_("✔")} No changes detected`);
469
+ lines.push(dim_(` from: ${result.from}`));
470
+ lines.push(dim_(` to: ${result.to}`));
471
+ appendEmittedExtensions();
472
+ return lines.join("\n");
473
+ }
474
+ if (result.pendingPlaceholders) {
475
+ lines.push(`${yellow_("⚠")} ${result.summary}`);
476
+ lines.push("");
477
+ lines.push(dim_(`from: ${result.from}`));
478
+ lines.push(dim_(`to: ${result.to}`));
479
+ if (result.dir) lines.push(dim_(`dir: ${result.dir}`));
480
+ lines.push("");
481
+ lines.push("Open migration.ts and replace each `placeholder(...)` call with your actual query.");
482
+ lines.push(`Then run: ${green_(`node ${result.dir ?? "<dir>"}/migration.ts`)}`);
483
+ appendEmittedExtensions();
484
+ return lines.join("\n");
485
+ }
486
+ lines.push(`${green_("✔")} ${result.summary}`);
487
+ lines.push("");
488
+ if (result.operations.length > 0) {
489
+ lines.push(dim_("│"));
490
+ for (let i = 0; i < result.operations.length; i++) {
491
+ const op = result.operations[i];
492
+ const treeChar = i === result.operations.length - 1 ? "└" : "├";
493
+ const destructiveMarker = op.operationClass === "destructive" ? ` ${yellow_("(destructive)")}` : "";
494
+ lines.push(`${dim_(treeChar)}─ ${op.label}${destructiveMarker}`);
495
+ }
496
+ if (result.operations.some((op) => op.operationClass === "destructive")) {
497
+ lines.push("");
498
+ lines.push(`${yellow_("⚠")} This migration contains destructive operations that may cause data loss.`);
499
+ }
500
+ lines.push("");
501
+ }
502
+ lines.push(dim_(`from: ${result.from}`));
503
+ lines.push(dim_(`to: ${result.to}`));
504
+ if (result.dir) lines.push(dim_(`App space → ${result.dir}`));
505
+ for (const entry of result.emittedExtensionDirs) lines.push(dim_(`Extension space ${entry.spaceId} → migrations/${entry.spaceId}/${entry.dirName}`));
506
+ lines.push("");
507
+ lines.push(`Next: review ${green_(result.dir ?? "<dir>")} if needed, then run ${green_("prisma-next migration apply")}.`);
508
+ if (result.preview && result.preview.statements.length > 0) {
509
+ const allSql = result.preview.statements.every((s) => s.language === "sql");
510
+ lines.push("");
511
+ lines.push(dim_(allSql ? "DDL preview" : "Operation preview"));
512
+ lines.push("");
513
+ for (const statement of result.preview.statements) {
514
+ const trimmed = statement.text.trim();
515
+ if (!trimmed) continue;
516
+ const line = statement.language === "sql" && !trimmed.endsWith(";") ? `${trimmed};` : trimmed;
517
+ lines.push(line);
518
+ }
519
+ }
520
+ if (flags.verbose && result.timings) {
521
+ lines.push("");
522
+ lines.push(dim_(`Total time: ${result.timings.total}ms`));
523
+ }
524
+ return lines.join("\n");
525
+ }
526
+ /**
527
+ * Resolve a migration package by **target contract hash** (`metadata.to`)
528
+ * using exact match or prefix match.
529
+ *
530
+ * Note: matches `metadata.to` (the contract hash this migration produces),
531
+ * not `metadata.migrationHash` (the package's content-addressed identity).
532
+ * Tries exact match first, then prefix match (auto-prepending `sha256:` when
533
+ * the needle omits the scheme). Returns the matched package on success, or a
534
+ * discriminated failure indicating whether the prefix was ambiguous or simply
535
+ * not found.
536
+ *
537
+ * @internal Exported for testing only.
538
+ */
539
+ function resolveBundleByPrefix(bundles, needle) {
540
+ const exact = bundles.find((p) => p.metadata.to === needle);
541
+ if (exact) return ok(exact);
542
+ const prefixWithScheme = needle.startsWith("sha256:") ? needle : `sha256:${needle}`;
543
+ const candidates = bundles.filter((p) => p.metadata.to.startsWith(prefixWithScheme));
544
+ if (candidates.length === 1) return ok(candidates[0]);
545
+ if (candidates.length > 1) return notOk({
546
+ reason: "ambiguous",
547
+ count: candidates.length
548
+ });
549
+ return notOk({ reason: "not-found" });
550
+ }
551
+ //#endregion
552
+ export { formatMigrationPlanOutput as n, resolveBundleByPrefix as r, createMigrationPlanCommand as t };
553
+
554
+ //# sourceMappingURL=migration-plan-C6lVaHsO.mjs.map