@prisma-next/cli 0.3.0-dev.12 → 0.3.0-dev.122

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 (215) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +470 -134
  3. package/dist/cli-errors-ByGuoqNj.mjs +3 -0
  4. package/dist/cli-errors-D6HxRn3A.d.mts +2 -0
  5. package/dist/cli.d.mts +1 -0
  6. package/dist/cli.js +1 -2350
  7. package/dist/cli.mjs +242 -0
  8. package/dist/cli.mjs.map +1 -0
  9. package/dist/client-612RJJD_.mjs +1069 -0
  10. package/dist/client-612RJJD_.mjs.map +1 -0
  11. package/dist/commands/contract-emit.d.mts +7 -0
  12. package/dist/commands/contract-emit.d.mts.map +1 -0
  13. package/dist/commands/contract-emit.mjs +8 -0
  14. package/dist/commands/contract-infer.d.mts +7 -0
  15. package/dist/commands/contract-infer.d.mts.map +1 -0
  16. package/dist/commands/contract-infer.mjs +9 -0
  17. package/dist/commands/db-init.d.mts +7 -0
  18. package/dist/commands/db-init.d.mts.map +1 -0
  19. package/dist/commands/db-init.mjs +125 -0
  20. package/dist/commands/db-init.mjs.map +1 -0
  21. package/dist/commands/db-schema.d.mts +7 -0
  22. package/dist/commands/db-schema.d.mts.map +1 -0
  23. package/dist/commands/db-schema.mjs +55 -0
  24. package/dist/commands/db-schema.mjs.map +1 -0
  25. package/dist/commands/db-sign.d.mts +7 -0
  26. package/dist/commands/db-sign.d.mts.map +1 -0
  27. package/dist/commands/db-sign.mjs +136 -0
  28. package/dist/commands/db-sign.mjs.map +1 -0
  29. package/dist/commands/db-update.d.mts +7 -0
  30. package/dist/commands/db-update.d.mts.map +1 -0
  31. package/dist/commands/db-update.mjs +122 -0
  32. package/dist/commands/db-update.mjs.map +1 -0
  33. package/dist/commands/db-verify.d.mts +7 -0
  34. package/dist/commands/db-verify.d.mts.map +1 -0
  35. package/dist/commands/db-verify.mjs +311 -0
  36. package/dist/commands/db-verify.mjs.map +1 -0
  37. package/dist/commands/migration-apply.d.mts +36 -0
  38. package/dist/commands/migration-apply.d.mts.map +1 -0
  39. package/dist/commands/migration-apply.mjs +241 -0
  40. package/dist/commands/migration-apply.mjs.map +1 -0
  41. package/dist/commands/migration-plan.d.mts +47 -0
  42. package/dist/commands/migration-plan.d.mts.map +1 -0
  43. package/dist/commands/migration-plan.mjs +288 -0
  44. package/dist/commands/migration-plan.mjs.map +1 -0
  45. package/dist/commands/migration-ref.d.mts +43 -0
  46. package/dist/commands/migration-ref.d.mts.map +1 -0
  47. package/dist/commands/migration-ref.mjs +194 -0
  48. package/dist/commands/migration-ref.mjs.map +1 -0
  49. package/dist/commands/migration-show.d.mts +28 -0
  50. package/dist/commands/migration-show.d.mts.map +1 -0
  51. package/dist/commands/migration-show.mjs +139 -0
  52. package/dist/commands/migration-show.mjs.map +1 -0
  53. package/dist/commands/migration-status.d.mts +85 -0
  54. package/dist/commands/migration-status.d.mts.map +1 -0
  55. package/dist/commands/migration-status.mjs +8 -0
  56. package/dist/commands/migration-verify.d.mts +16 -0
  57. package/dist/commands/migration-verify.d.mts.map +1 -0
  58. package/dist/commands/migration-verify.mjs +87 -0
  59. package/dist/commands/migration-verify.mjs.map +1 -0
  60. package/dist/config-loader-d_KF19Tw.mjs +43 -0
  61. package/dist/config-loader-d_KF19Tw.mjs.map +1 -0
  62. package/dist/{config-loader.d.ts → config-loader.d.mts} +8 -3
  63. package/dist/config-loader.d.mts.map +1 -0
  64. package/dist/config-loader.mjs +3 -0
  65. package/dist/contract-emit-CVv7dbQ9.mjs +187 -0
  66. package/dist/contract-emit-CVv7dbQ9.mjs.map +1 -0
  67. package/dist/contract-infer-Bvw8u8Eu.mjs +83 -0
  68. package/dist/contract-infer-Bvw8u8Eu.mjs.map +1 -0
  69. package/dist/exports/config-types.d.mts +2 -0
  70. package/dist/exports/config-types.mjs +3 -0
  71. package/dist/exports/control-api.d.mts +626 -0
  72. package/dist/exports/control-api.d.mts.map +1 -0
  73. package/dist/exports/control-api.mjs +107 -0
  74. package/dist/exports/control-api.mjs.map +1 -0
  75. package/dist/{load-ts-contract.d.ts → exports/index.d.mts} +10 -5
  76. package/dist/exports/index.d.mts.map +1 -0
  77. package/dist/exports/index.mjs +134 -0
  78. package/dist/exports/index.mjs.map +1 -0
  79. package/dist/extract-sql-ddl-Jf5blEO0.mjs +26 -0
  80. package/dist/extract-sql-ddl-Jf5blEO0.mjs.map +1 -0
  81. package/dist/framework-components-M2j-qPfr.mjs +59 -0
  82. package/dist/framework-components-M2j-qPfr.mjs.map +1 -0
  83. package/dist/inspect-live-schema-BQe5i4YE.mjs +90 -0
  84. package/dist/inspect-live-schema-BQe5i4YE.mjs.map +1 -0
  85. package/dist/migration-command-scaffold-SLrjcKXS.mjs +104 -0
  86. package/dist/migration-command-scaffold-SLrjcKXS.mjs.map +1 -0
  87. package/dist/migration-status-B7OVZ-Ka.mjs +1576 -0
  88. package/dist/migration-status-B7OVZ-Ka.mjs.map +1 -0
  89. package/dist/migrations-Db_ea9eE.mjs +173 -0
  90. package/dist/migrations-Db_ea9eE.mjs.map +1 -0
  91. package/dist/progress-adapter-DRNe2idZ.mjs +43 -0
  92. package/dist/progress-adapter-DRNe2idZ.mjs.map +1 -0
  93. package/dist/terminal-ui-DAcMBRKf.mjs +980 -0
  94. package/dist/terminal-ui-DAcMBRKf.mjs.map +1 -0
  95. package/dist/verify-DXKxBFvU.mjs +385 -0
  96. package/dist/verify-DXKxBFvU.mjs.map +1 -0
  97. package/package.json +85 -40
  98. package/src/cli.ts +109 -58
  99. package/src/commands/contract-emit.ts +236 -143
  100. package/src/commands/contract-infer-paths.ts +32 -0
  101. package/src/commands/contract-infer.ts +131 -0
  102. package/src/commands/db-init.ts +211 -425
  103. package/src/commands/db-schema.ts +77 -0
  104. package/src/commands/db-sign.ts +207 -228
  105. package/src/commands/db-update.ts +236 -0
  106. package/src/commands/db-verify.ts +484 -186
  107. package/src/commands/inspect-live-schema.ts +171 -0
  108. package/src/commands/migration-apply.ts +416 -0
  109. package/src/commands/migration-plan.ts +451 -0
  110. package/src/commands/migration-ref.ts +305 -0
  111. package/src/commands/migration-show.ts +246 -0
  112. package/src/commands/migration-status.ts +838 -0
  113. package/src/commands/migration-verify.ts +134 -0
  114. package/src/config-loader.ts +13 -3
  115. package/src/control-api/client.ts +614 -0
  116. package/src/control-api/contract-enrichment.ts +135 -0
  117. package/src/control-api/errors.ts +9 -0
  118. package/src/control-api/operations/contract-emit.ts +173 -0
  119. package/src/control-api/operations/db-init.ts +286 -0
  120. package/src/control-api/operations/db-update.ts +221 -0
  121. package/src/control-api/operations/extract-sql-ddl.ts +47 -0
  122. package/src/control-api/operations/migration-apply.ts +194 -0
  123. package/src/control-api/operations/migration-helpers.ts +49 -0
  124. package/src/control-api/types.ts +683 -0
  125. package/src/exports/config-types.ts +4 -3
  126. package/src/exports/control-api.ts +56 -0
  127. package/src/load-ts-contract.ts +16 -11
  128. package/src/utils/cli-errors.ts +5 -2
  129. package/src/utils/command-helpers.ts +293 -3
  130. package/src/utils/formatters/emit.ts +67 -0
  131. package/src/utils/formatters/errors.ts +82 -0
  132. package/src/utils/formatters/graph-migration-mapper.ts +220 -0
  133. package/src/utils/formatters/graph-render.ts +1317 -0
  134. package/src/utils/formatters/graph-types.ts +114 -0
  135. package/src/utils/formatters/help.ts +380 -0
  136. package/src/utils/formatters/helpers.ts +28 -0
  137. package/src/utils/formatters/migrations.ts +346 -0
  138. package/src/utils/formatters/styled.ts +212 -0
  139. package/src/utils/formatters/verify.ts +620 -0
  140. package/src/utils/global-flags.ts +41 -23
  141. package/src/utils/migration-command-scaffold.ts +187 -0
  142. package/src/utils/migration-types.ts +12 -0
  143. package/src/utils/progress-adapter.ts +75 -0
  144. package/src/utils/result-handler.ts +12 -13
  145. package/src/utils/shutdown.ts +92 -0
  146. package/src/utils/suggest-command.ts +31 -0
  147. package/src/utils/terminal-ui.ts +276 -0
  148. package/dist/chunk-BZMBKEEQ.js +0 -997
  149. package/dist/chunk-BZMBKEEQ.js.map +0 -1
  150. package/dist/chunk-CVNWLFXO.js +0 -91
  151. package/dist/chunk-CVNWLFXO.js.map +0 -1
  152. package/dist/chunk-HWYQOCAJ.js +0 -47
  153. package/dist/chunk-HWYQOCAJ.js.map +0 -1
  154. package/dist/chunk-QUPBU4KV.js +0 -131
  155. package/dist/chunk-QUPBU4KV.js.map +0 -1
  156. package/dist/cli.d.ts +0 -2
  157. package/dist/cli.d.ts.map +0 -1
  158. package/dist/cli.js.map +0 -1
  159. package/dist/commands/contract-emit.d.ts +0 -3
  160. package/dist/commands/contract-emit.d.ts.map +0 -1
  161. package/dist/commands/contract-emit.js +0 -9
  162. package/dist/commands/contract-emit.js.map +0 -1
  163. package/dist/commands/db-init.d.ts +0 -3
  164. package/dist/commands/db-init.d.ts.map +0 -1
  165. package/dist/commands/db-init.js +0 -337
  166. package/dist/commands/db-init.js.map +0 -1
  167. package/dist/commands/db-introspect.d.ts +0 -3
  168. package/dist/commands/db-introspect.d.ts.map +0 -1
  169. package/dist/commands/db-introspect.js +0 -186
  170. package/dist/commands/db-introspect.js.map +0 -1
  171. package/dist/commands/db-schema-verify.d.ts +0 -3
  172. package/dist/commands/db-schema-verify.d.ts.map +0 -1
  173. package/dist/commands/db-schema-verify.js +0 -160
  174. package/dist/commands/db-schema-verify.js.map +0 -1
  175. package/dist/commands/db-sign.d.ts +0 -3
  176. package/dist/commands/db-sign.d.ts.map +0 -1
  177. package/dist/commands/db-sign.js +0 -195
  178. package/dist/commands/db-sign.js.map +0 -1
  179. package/dist/commands/db-verify.d.ts +0 -3
  180. package/dist/commands/db-verify.d.ts.map +0 -1
  181. package/dist/commands/db-verify.js +0 -169
  182. package/dist/commands/db-verify.js.map +0 -1
  183. package/dist/config-loader.d.ts.map +0 -1
  184. package/dist/config-loader.js +0 -7
  185. package/dist/config-loader.js.map +0 -1
  186. package/dist/exports/config-types.d.ts +0 -3
  187. package/dist/exports/config-types.d.ts.map +0 -1
  188. package/dist/exports/config-types.js +0 -6
  189. package/dist/exports/config-types.js.map +0 -1
  190. package/dist/exports/index.d.ts +0 -4
  191. package/dist/exports/index.d.ts.map +0 -1
  192. package/dist/exports/index.js +0 -175
  193. package/dist/exports/index.js.map +0 -1
  194. package/dist/load-ts-contract.d.ts.map +0 -1
  195. package/dist/utils/action.d.ts +0 -16
  196. package/dist/utils/action.d.ts.map +0 -1
  197. package/dist/utils/cli-errors.d.ts +0 -7
  198. package/dist/utils/cli-errors.d.ts.map +0 -1
  199. package/dist/utils/command-helpers.d.ts +0 -12
  200. package/dist/utils/command-helpers.d.ts.map +0 -1
  201. package/dist/utils/framework-components.d.ts +0 -70
  202. package/dist/utils/framework-components.d.ts.map +0 -1
  203. package/dist/utils/global-flags.d.ts +0 -25
  204. package/dist/utils/global-flags.d.ts.map +0 -1
  205. package/dist/utils/output.d.ts +0 -142
  206. package/dist/utils/output.d.ts.map +0 -1
  207. package/dist/utils/result-handler.d.ts +0 -15
  208. package/dist/utils/result-handler.d.ts.map +0 -1
  209. package/dist/utils/spinner.d.ts +0 -29
  210. package/dist/utils/spinner.d.ts.map +0 -1
  211. package/src/commands/db-introspect.ts +0 -256
  212. package/src/commands/db-schema-verify.ts +0 -232
  213. package/src/utils/action.ts +0 -43
  214. package/src/utils/output.ts +0 -1471
  215. package/src/utils/spinner.ts +0 -67
@@ -0,0 +1,241 @@
1
+ import { t as loadConfig } from "../config-loader-d_KF19Tw.mjs";
2
+ import { _ as errorUnexpected, c as errorDriverRequired, h as errorTargetMigrationNotSupported, m as errorRuntime, o as errorDatabaseConnectionRequired, t as CliStructuredError } from "../cli-errors-ByGuoqNj.mjs";
3
+ import "../framework-components-M2j-qPfr.mjs";
4
+ import { t as createControlClient } from "../client-612RJJD_.mjs";
5
+ import { c as readContractEnvelope, f as setCommandDescriptions, g as parseGlobalFlags, h as toPathDecisionResult, i as addGlobalOptions, m as targetSupportsMigrations, o as loadMigrationBundles, p as setCommandExamples, r as handleResult, s as maskConnectionUrl, t as TerminalUI, u as resolveMigrationPaths, y as formatStyledHeader } from "../terminal-ui-DAcMBRKf.mjs";
6
+ import { t as formatMigrationApplyCommandOutput } from "../migrations-Db_ea9eE.mjs";
7
+ import { Command } from "commander";
8
+ import { notOk, ok } from "@prisma-next/utils/result";
9
+ import { EMPTY_CONTRACT_HASH } from "@prisma-next/core-control-plane/constants";
10
+ import { findPathWithDecision } from "@prisma-next/migration-tools/dag";
11
+ import { MigrationToolsError } from "@prisma-next/migration-tools/types";
12
+ import { readRefs, resolveRef } from "@prisma-next/migration-tools/refs";
13
+
14
+ //#region src/commands/migration-apply.ts
15
+ function mapMigrationToolsError(error) {
16
+ if (MigrationToolsError.is(error)) return errorRuntime(error.message, {
17
+ why: error.why,
18
+ fix: error.fix,
19
+ meta: {
20
+ code: error.code,
21
+ ...error.details ?? {}
22
+ }
23
+ });
24
+ return errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}` });
25
+ }
26
+ function mapApplyFailure(failure) {
27
+ return errorRuntime(failure.summary, {
28
+ why: failure.why ?? "Migration runner failed",
29
+ fix: "Fix the issue and re-run `prisma-next migration apply` — previously applied migrations are preserved.",
30
+ meta: failure.meta ?? {}
31
+ });
32
+ }
33
+ function packageToStep(pkg) {
34
+ return {
35
+ dirName: pkg.dirName,
36
+ from: pkg.manifest.from,
37
+ to: pkg.manifest.to,
38
+ toContract: pkg.manifest.toContract,
39
+ operations: pkg.ops
40
+ };
41
+ }
42
+ async function executeMigrationApplyCommand(options, flags, ui, startTime) {
43
+ const config = await loadConfig(options.config);
44
+ const { configPath, migrationsDir, migrationsRelative, refsPath } = resolveMigrationPaths(options.config, config);
45
+ const dbConnection = options.db ?? config.db?.connection;
46
+ if (!dbConnection) return notOk(errorDatabaseConnectionRequired({
47
+ why: `Database connection is required for migration apply (set db.connection in ${configPath}, or pass --db <url>)`,
48
+ commandName: "migration apply"
49
+ }));
50
+ if (!config.driver) return notOk(errorDriverRequired({ why: "Config.driver is required for migration apply" }));
51
+ if (!targetSupportsMigrations(config.target)) return notOk(errorTargetMigrationNotSupported({ why: `Target "${config.target.id}" does not support migrations` }));
52
+ let destinationHash;
53
+ let refName;
54
+ if (options.ref) {
55
+ refName = options.ref;
56
+ try {
57
+ destinationHash = resolveRef(await readRefs(refsPath), refName);
58
+ } catch (error) {
59
+ if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
60
+ throw error;
61
+ }
62
+ } else try {
63
+ destinationHash = (await readContractEnvelope(config)).storageHash;
64
+ } catch (error) {
65
+ return notOk(errorRuntime("Current contract is unavailable", {
66
+ why: `Failed to read contract: ${error instanceof Error ? error.message : String(error)}`,
67
+ fix: "Run `prisma-next contract emit` to generate a valid contract.json, then retry apply."
68
+ }));
69
+ }
70
+ if (!flags.json && !flags.quiet) {
71
+ const details = [{
72
+ label: "config",
73
+ value: configPath
74
+ }, {
75
+ label: "migrations",
76
+ value: migrationsRelative
77
+ }];
78
+ if (typeof dbConnection === "string") details.push({
79
+ label: "database",
80
+ value: maskConnectionUrl(dbConnection)
81
+ });
82
+ if (refName) details.push({
83
+ label: "ref",
84
+ value: refName
85
+ });
86
+ const header = formatStyledHeader({
87
+ command: "migration apply",
88
+ description: "Apply planned migrations to the database",
89
+ url: "https://pris.ly/migration-apply",
90
+ details,
91
+ flags
92
+ });
93
+ ui.stderr(header);
94
+ }
95
+ let bundles;
96
+ let graph;
97
+ try {
98
+ ({bundles, graph} = await loadMigrationBundles(migrationsDir));
99
+ } catch (error) {
100
+ if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
101
+ throw error;
102
+ }
103
+ const client = createControlClient({
104
+ family: config.family,
105
+ target: config.target,
106
+ adapter: config.adapter,
107
+ driver: config.driver,
108
+ extensionPacks: config.extensionPacks ?? []
109
+ });
110
+ try {
111
+ await client.connect(dbConnection);
112
+ const marker = await client.readMarker();
113
+ if (bundles.length === 0) {
114
+ if (marker?.storageHash) return notOk(errorRuntime("Database has state but no migrations exist", {
115
+ why: `The database marker hash "${marker.storageHash}" exists but no attested migrations were found in ${migrationsRelative}`,
116
+ fix: "Ensure the migrations directory is correct. If the database was managed with `db init` or `db update`, run `prisma-next db sign` to update the marker.",
117
+ meta: {
118
+ markerHash: marker.storageHash,
119
+ migrationsDir: migrationsRelative
120
+ }
121
+ }));
122
+ if (destinationHash !== EMPTY_CONTRACT_HASH) return notOk(errorRuntime("Current contract has no planned migrations", {
123
+ why: `No attested migrations were found in ${migrationsRelative}, but current contract hash is "${destinationHash}"`,
124
+ fix: "Run `prisma-next migration plan` to create an attested migration for the current contract.",
125
+ meta: {
126
+ destinationHash,
127
+ migrationsDir: migrationsRelative
128
+ }
129
+ }));
130
+ return ok({
131
+ ok: true,
132
+ migrationsApplied: 0,
133
+ migrationsTotal: 0,
134
+ markerHash: EMPTY_CONTRACT_HASH,
135
+ applied: [],
136
+ summary: "No attested migrations found",
137
+ timings: { total: Date.now() - startTime }
138
+ });
139
+ }
140
+ if (marker?.storageHash === EMPTY_CONTRACT_HASH) return notOk(errorRuntime("Database marker contains the empty sentinel hash", {
141
+ why: `The marker row exists but contains the empty sentinel value "${EMPTY_CONTRACT_HASH}". This should never happen — the marker should contain the hash of the last applied contract.`,
142
+ fix: "The marker is corrupted. Run `prisma-next db sign` to overwrite it with the correct contract hash, or drop and recreate the database.",
143
+ meta: { markerHash: EMPTY_CONTRACT_HASH }
144
+ }));
145
+ const markerHash = marker?.storageHash;
146
+ if (markerHash !== void 0 && !graph.nodes.has(markerHash)) return notOk(errorRuntime("Database marker does not match any known migration", {
147
+ why: `The database marker hash "${markerHash}" is not found in the migration history at ${migrationsRelative}`,
148
+ fix: "Ensure the migrations directory matches this database. If the database was managed with `db init` or `db update`, run `prisma-next db sign` to update the marker.",
149
+ meta: {
150
+ markerHash,
151
+ knownNodes: [...graph.nodes]
152
+ }
153
+ }));
154
+ if (!graph.nodes.has(destinationHash)) return notOk(errorRuntime("Current contract has no planned migration path", {
155
+ why: `Current contract hash "${destinationHash}" is not present in the migration history at ${migrationsRelative}`,
156
+ fix: "Run `prisma-next migration plan` to create a migration for the current contract, then re-run apply.",
157
+ meta: {
158
+ destinationHash,
159
+ knownNodes: [...graph.nodes]
160
+ }
161
+ }));
162
+ const originHash = markerHash ?? EMPTY_CONTRACT_HASH;
163
+ const decision = findPathWithDecision(graph, originHash, destinationHash, refName);
164
+ if (!decision) return notOk(errorRuntime("No migration path from current state to target", {
165
+ why: `Cannot find a path from "${originHash}" to target "${destinationHash}"`,
166
+ fix: "Check the migration history for gaps or inconsistencies.",
167
+ meta: {
168
+ markerHash: originHash,
169
+ destinationHash
170
+ }
171
+ }));
172
+ const pendingPath = decision.selectedPath;
173
+ const pathDecision = toPathDecisionResult(decision);
174
+ if (pendingPath.length === 0) return ok({
175
+ ok: true,
176
+ migrationsApplied: 0,
177
+ migrationsTotal: 0,
178
+ markerHash: originHash,
179
+ applied: [],
180
+ summary: "Already up to date",
181
+ pathDecision,
182
+ timings: { total: Date.now() - startTime }
183
+ });
184
+ const bundleByDir = new Map(bundles.map((b) => [b.dirName, b]));
185
+ const pendingMigrations = [];
186
+ for (const migration of pendingPath) {
187
+ const pkg = bundleByDir.get(migration.dirName);
188
+ if (!pkg) return notOk(errorRuntime(`Migration package not found: ${migration.dirName}`, {
189
+ why: `The migration directory for path segment ${migration.from} → ${migration.to} was not found`,
190
+ fix: "Ensure all migration directories are present and intact."
191
+ }));
192
+ pendingMigrations.push(packageToStep(pkg));
193
+ }
194
+ if (!flags.quiet && !flags.json) for (const migration of pendingMigrations) ui.step(`Pending ${migration.dirName}`);
195
+ const applyResult = await client.migrationApply({
196
+ originHash,
197
+ destinationHash,
198
+ pendingMigrations
199
+ });
200
+ if (!applyResult.ok) return notOk(mapApplyFailure(applyResult.failure));
201
+ const { value } = applyResult;
202
+ return ok({
203
+ ok: true,
204
+ migrationsApplied: value.migrationsApplied,
205
+ migrationsTotal: pendingPath.length,
206
+ markerHash: value.markerHash,
207
+ applied: value.applied,
208
+ summary: value.summary,
209
+ pathDecision,
210
+ timings: { total: Date.now() - startTime }
211
+ });
212
+ } catch (error) {
213
+ if (CliStructuredError.is(error)) return notOk(error);
214
+ return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}` }));
215
+ } finally {
216
+ await client.close();
217
+ }
218
+ }
219
+ function createMigrationApplyCommand() {
220
+ const command = new Command("apply");
221
+ setCommandDescriptions(command, "Apply planned migrations to the database", "Applies previously planned migrations (created by `migration plan`) to a live database.\nCompares the database marker against the migration history to determine which\nmigrations are pending, then executes them sequentially. Each migration runs\nin its own transaction. Does not plan new migrations — run `migration plan` first.");
222
+ setCommandExamples(command, ["prisma-next migration apply --db $DATABASE_URL"]);
223
+ addGlobalOptions(command).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--ref <name>", "Target ref name from migrations/refs.json").action(async (options) => {
224
+ const flags = parseGlobalFlags(options);
225
+ const startTime = Date.now();
226
+ const ui = new TerminalUI({
227
+ color: flags.color,
228
+ interactive: flags.interactive
229
+ });
230
+ const exitCode = handleResult(await executeMigrationApplyCommand(options, flags, ui, startTime), flags, ui, (applyResult) => {
231
+ if (flags.json) ui.output(JSON.stringify(applyResult, null, 2));
232
+ else if (!flags.quiet) ui.log(formatMigrationApplyCommandOutput(applyResult, flags));
233
+ });
234
+ process.exit(exitCode);
235
+ });
236
+ return command;
237
+ }
238
+
239
+ //#endregion
240
+ export { createMigrationApplyCommand };
241
+ //# sourceMappingURL=migration-apply.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-apply.mjs","names":["destinationHash: string","refName: string | undefined","details: Array<{ label: string; value: string }>","bundles: readonly AttestedMigrationBundle[]","graph: MigrationGraph","pendingMigrations: MigrationApplyStep[]"],"sources":["../../src/commands/migration-apply.ts"],"sourcesContent":["import { EMPTY_CONTRACT_HASH } from '@prisma-next/core-control-plane/constants';\nimport { findPathWithDecision } from '@prisma-next/migration-tools/dag';\nimport { readRefs, resolveRef } from '@prisma-next/migration-tools/refs';\nimport type { AttestedMigrationBundle, MigrationGraph } from '@prisma-next/migration-tools/types';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/types';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { Command } from 'commander';\n\nimport { loadConfig } from '../config-loader';\nimport { createControlClient } from '../control-api/client';\nimport type { MigrationApplyFailure, MigrationApplyStep } from '../control-api/types';\nimport {\n CliStructuredError,\n type CliStructuredError as CliStructuredErrorType,\n errorDatabaseConnectionRequired,\n errorDriverRequired,\n errorRuntime,\n errorTargetMigrationNotSupported,\n errorUnexpected,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n loadMigrationBundles,\n maskConnectionUrl,\n readContractEnvelope,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n targetSupportsMigrations,\n toPathDecisionResult,\n} from '../utils/command-helpers';\nimport { formatMigrationApplyCommandOutput } from '../utils/formatters/migrations';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';\nimport { handleResult } from '../utils/result-handler';\nimport { TerminalUI } from '../utils/terminal-ui';\n\ninterface MigrationApplyCommandOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n readonly ref?: string;\n}\n\nexport interface MigrationApplyResult {\n readonly ok: boolean;\n readonly migrationsApplied: number;\n readonly migrationsTotal: number;\n readonly markerHash: string;\n readonly applied: readonly {\n readonly dirName: string;\n readonly from: string;\n readonly to: string;\n readonly operationsExecuted: number;\n }[];\n readonly summary: string;\n readonly pathDecision?: {\n readonly fromHash: string;\n readonly toHash: string;\n readonly alternativeCount: number;\n readonly tieBreakReasons: readonly string[];\n readonly refName?: string;\n readonly selectedPath: readonly {\n readonly dirName: string;\n readonly migrationId: string | null;\n readonly from: string;\n readonly to: string;\n }[];\n };\n readonly timings: {\n readonly total: number;\n };\n}\n\nfunction mapMigrationToolsError(error: unknown): CliStructuredErrorType {\n if (MigrationToolsError.is(error)) {\n return errorRuntime(error.message, {\n why: error.why,\n fix: error.fix,\n meta: { code: error.code, ...(error.details ?? {}) },\n });\n }\n return errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}`,\n });\n}\n\nfunction mapApplyFailure(failure: MigrationApplyFailure): CliStructuredErrorType {\n return errorRuntime(failure.summary, {\n why: failure.why ?? 'Migration runner failed',\n fix: 'Fix the issue and re-run `prisma-next migration apply` — previously applied migrations are preserved.',\n meta: failure.meta ?? {},\n });\n}\n\nfunction packageToStep(pkg: AttestedMigrationBundle): MigrationApplyStep {\n return {\n dirName: pkg.dirName,\n from: pkg.manifest.from,\n to: pkg.manifest.to,\n toContract: pkg.manifest.toContract,\n operations: pkg.ops,\n };\n}\n\nasync function executeMigrationApplyCommand(\n options: MigrationApplyCommandOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n startTime: number,\n): Promise<Result<MigrationApplyResult, CliStructuredErrorType>> {\n const config = await loadConfig(options.config);\n const { configPath, migrationsDir, migrationsRelative, refsPath } = resolveMigrationPaths(\n options.config,\n config,\n );\n\n const dbConnection = options.db ?? config.db?.connection;\n if (!dbConnection) {\n return notOk(\n errorDatabaseConnectionRequired({\n why: `Database connection is required for migration apply (set db.connection in ${configPath}, or pass --db <url>)`,\n commandName: 'migration apply',\n }),\n );\n }\n\n if (!config.driver) {\n return notOk(\n errorDriverRequired({\n why: 'Config.driver is required for migration apply',\n }),\n );\n }\n\n if (!targetSupportsMigrations(config.target)) {\n return notOk(\n errorTargetMigrationNotSupported({\n why: `Target \"${config.target.id}\" does not support migrations`,\n }),\n );\n }\n\n let destinationHash: string;\n let refName: string | undefined;\n\n if (options.ref) {\n refName = options.ref;\n try {\n const refs = await readRefs(refsPath);\n destinationHash = resolveRef(refs, refName);\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n } else {\n try {\n const envelope = await readContractEnvelope(config);\n destinationHash = envelope.storageHash;\n } catch (error) {\n return notOk(\n errorRuntime('Current contract is unavailable', {\n why: `Failed to read contract: ${error instanceof Error ? error.message : String(error)}`,\n fix: 'Run `prisma-next contract emit` to generate a valid contract.json, then retry apply.',\n }),\n );\n }\n }\n\n if (!flags.json && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'migrations', value: migrationsRelative },\n ];\n if (typeof dbConnection === 'string') {\n details.push({\n label: 'database',\n value: maskConnectionUrl(dbConnection),\n });\n }\n if (refName) {\n details.push({ label: 'ref', value: refName });\n }\n const header = formatStyledHeader({\n command: 'migration apply',\n description: 'Apply planned migrations to the database',\n url: 'https://pris.ly/migration-apply',\n details,\n flags,\n });\n ui.stderr(header);\n }\n\n // Read migrations and build migration chain model (offline — no DB needed)\n let bundles: readonly AttestedMigrationBundle[];\n let graph: MigrationGraph;\n try {\n ({ bundles, graph } = await loadMigrationBundles(migrationsDir));\n } catch (error) {\n if (MigrationToolsError.is(error)) {\n return notOk(mapMigrationToolsError(error));\n }\n throw error;\n }\n\n const client = createControlClient({\n family: config.family,\n target: config.target,\n adapter: config.adapter,\n driver: config.driver,\n extensionPacks: config.extensionPacks ?? [],\n });\n\n try {\n await client.connect(dbConnection);\n const marker = await client.readMarker();\n\n // --- No attested migrations on disk ---\n if (bundles.length === 0) {\n if (marker?.storageHash) {\n return notOk(\n errorRuntime('Database has state but no migrations exist', {\n why: `The database marker hash \"${marker.storageHash}\" exists but no attested migrations were found in ${migrationsRelative}`,\n fix: 'Ensure the migrations directory is correct. If the database was managed with `db init` or `db update`, run `prisma-next db sign` to update the marker.',\n meta: { markerHash: marker.storageHash, migrationsDir: migrationsRelative },\n }),\n );\n }\n // Non-empty contract + no migrations = user needs to plan first.\n if (destinationHash !== EMPTY_CONTRACT_HASH) {\n return notOk(\n errorRuntime('Current contract has no planned migrations', {\n why: `No attested migrations were found in ${migrationsRelative}, but current contract hash is \"${destinationHash}\"`,\n fix: 'Run `prisma-next migration plan` to create an attested migration for the current contract.',\n meta: { destinationHash, migrationsDir: migrationsRelative },\n }),\n );\n }\n // Empty contract + no migrations = nothing to do.\n return ok({\n ok: true,\n migrationsApplied: 0,\n migrationsTotal: 0,\n markerHash: EMPTY_CONTRACT_HASH,\n applied: [],\n summary: 'No attested migrations found',\n timings: { total: Date.now() - startTime },\n });\n }\n\n // --- Validate marker state ---\n\n // The empty sentinel should never appear in a real marker row — if it does,\n // the marker was corrupted and replaying all migrations would be dangerous.\n if (marker?.storageHash === EMPTY_CONTRACT_HASH) {\n return notOk(\n errorRuntime('Database marker contains the empty sentinel hash', {\n why: `The marker row exists but contains the empty sentinel value \"${EMPTY_CONTRACT_HASH}\". This should never happen — the marker should contain the hash of the last applied contract.`,\n fix: 'The marker is corrupted. Run `prisma-next db sign` to overwrite it with the correct contract hash, or drop and recreate the database.',\n meta: { markerHash: EMPTY_CONTRACT_HASH },\n }),\n );\n }\n\n const markerHash = marker?.storageHash;\n\n if (markerHash !== undefined && !graph.nodes.has(markerHash)) {\n return notOk(\n errorRuntime('Database marker does not match any known migration', {\n why: `The database marker hash \"${markerHash}\" is not found in the migration history at ${migrationsRelative}`,\n fix: 'Ensure the migrations directory matches this database. If the database was managed with `db init` or `db update`, run `prisma-next db sign` to update the marker.',\n meta: { markerHash, knownNodes: [...graph.nodes] },\n }),\n );\n }\n\n if (!graph.nodes.has(destinationHash)) {\n return notOk(\n errorRuntime('Current contract has no planned migration path', {\n why: `Current contract hash \"${destinationHash}\" is not present in the migration history at ${migrationsRelative}`,\n fix: 'Run `prisma-next migration plan` to create a migration for the current contract, then re-run apply.',\n meta: { destinationHash, knownNodes: [...graph.nodes] },\n }),\n );\n }\n\n // --- Resolve path and apply ---\n\n // \"No marker\" means the database is fresh — start from the empty contract hash.\n const originHash = markerHash ?? EMPTY_CONTRACT_HASH;\n\n const decision = findPathWithDecision(graph, originHash, destinationHash, refName);\n if (!decision) {\n return notOk(\n errorRuntime('No migration path from current state to target', {\n why: `Cannot find a path from \"${originHash}\" to target \"${destinationHash}\"`,\n fix: 'Check the migration history for gaps or inconsistencies.',\n meta: { markerHash: originHash, destinationHash },\n }),\n );\n }\n\n const pendingPath = decision.selectedPath;\n const pathDecision = toPathDecisionResult(decision);\n\n if (pendingPath.length === 0) {\n return ok({\n ok: true,\n migrationsApplied: 0,\n migrationsTotal: 0,\n markerHash: originHash,\n applied: [],\n summary: 'Already up to date',\n pathDecision,\n timings: { total: Date.now() - startTime },\n });\n }\n\n const bundleByDir = new Map(bundles.map((b) => [b.dirName, b]));\n const pendingMigrations: MigrationApplyStep[] = [];\n for (const migration of pendingPath) {\n const pkg = bundleByDir.get(migration.dirName);\n if (!pkg) {\n return notOk(\n errorRuntime(`Migration package not found: ${migration.dirName}`, {\n why: `The migration directory for path segment ${migration.from} → ${migration.to} was not found`,\n fix: 'Ensure all migration directories are present and intact.',\n }),\n );\n }\n pendingMigrations.push(packageToStep(pkg));\n }\n\n if (!flags.quiet && !flags.json) {\n for (const migration of pendingMigrations) {\n ui.step(`Pending ${migration.dirName}`);\n }\n }\n\n const applyResult = await client.migrationApply({\n originHash,\n destinationHash,\n pendingMigrations,\n });\n\n if (!applyResult.ok) {\n return notOk(mapApplyFailure(applyResult.failure));\n }\n\n const { value } = applyResult;\n\n return ok({\n ok: true,\n migrationsApplied: value.migrationsApplied,\n migrationsTotal: pendingPath.length,\n markerHash: value.markerHash,\n applied: value.applied,\n summary: value.summary,\n pathDecision,\n timings: { total: Date.now() - startTime },\n });\n } catch (error) {\n if (CliStructuredError.is(error)) {\n return notOk(error);\n }\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n } finally {\n await client.close();\n }\n}\n\nexport function createMigrationApplyCommand(): Command {\n const command = new Command('apply');\n setCommandDescriptions(\n command,\n 'Apply planned migrations to the database',\n 'Applies previously planned migrations (created by `migration plan`) to a live database.\\n' +\n 'Compares the database marker against the migration history to determine which\\n' +\n 'migrations are pending, then executes them sequentially. Each migration runs\\n' +\n 'in its own transaction. Does not plan new migrations — run `migration plan` first.',\n );\n setCommandExamples(command, ['prisma-next migration apply --db $DATABASE_URL']);\n addGlobalOptions(command)\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--ref <name>', 'Target ref name from migrations/refs.json')\n .action(async (options: MigrationApplyCommandOptions) => {\n const flags = parseGlobalFlags(options);\n const startTime = Date.now();\n\n const ui = new TerminalUI({\n color: flags.color,\n interactive: flags.interactive,\n });\n\n const result = await executeMigrationApplyCommand(options, flags, ui, startTime);\n\n const exitCode = handleResult(result, flags, ui, (applyResult) => {\n if (flags.json) {\n ui.output(JSON.stringify(applyResult, null, 2));\n } else if (!flags.quiet) {\n ui.log(formatMigrationApplyCommandOutput(applyResult, flags));\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import { Command } from "commander";
2
+ import { Result } from "@prisma-next/utils/result";
3
+
4
+ //#region src/commands/migration-plan.d.ts
5
+ interface MigrationPlanResult {
6
+ readonly ok: boolean;
7
+ readonly noOp: boolean;
8
+ readonly from: string;
9
+ readonly to: string;
10
+ readonly migrationId?: string;
11
+ readonly dir?: string;
12
+ readonly operations: readonly {
13
+ readonly id: string;
14
+ readonly label: string;
15
+ readonly operationClass: string;
16
+ }[];
17
+ readonly sql?: readonly string[];
18
+ readonly summary: string;
19
+ readonly timings: {
20
+ readonly total: number;
21
+ };
22
+ }
23
+ declare function createMigrationPlanCommand(): Command;
24
+ type PrefixResolutionFailure = {
25
+ reason: 'ambiguous';
26
+ count: number;
27
+ } | {
28
+ reason: 'not-found';
29
+ };
30
+ /**
31
+ * Resolve a migration bundle by exact hash or prefix match.
32
+ *
33
+ * Tries exact match first, then prefix match (auto-prepending `sha256:` when
34
+ * the needle omits the scheme). Returns the matched bundle on success, or a
35
+ * discriminated failure indicating whether the prefix was ambiguous or simply
36
+ * not found.
37
+ *
38
+ * @internal Exported for testing only.
39
+ */
40
+ declare function resolveBundleByPrefix<T extends {
41
+ manifest: {
42
+ to: string;
43
+ };
44
+ }>(bundles: readonly T[], needle: string): Result<T, PrefixResolutionFailure>;
45
+ //#endregion
46
+ export { MigrationPlanResult, PrefixResolutionFailure, createMigrationPlanCommand, resolveBundleByPrefix };
47
+ //# sourceMappingURL=migration-plan.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration-plan.d.mts","names":[],"sources":["../../src/commands/migration-plan.ts"],"sourcesContent":[],"mappings":";;;;UA6CiB,mBAAA;;EAAA,SAAA,IAAA,EAAA,OAAmB;EA8QpB,SAAA,IAAA,EAAA,MAAA;EA4GJ,SAAA,EAAA,EAAA,MAAA;EAcI,SAAA,WAAA,CAAA,EAAA,MAAqB;EACjB,SAAA,GAAA,CAAA,EAAA,MAAA;EAEV,SAAA,UAAA,EAAA,SAAA;IAAG,SAAA,EAAA,EAAA,MAAA;IAAV,SAAA,KAAA,EAAA,MAAA;IAAM,SAAA,cAAA,EAAA,MAAA;;;;;;;;iBA7HO,0BAAA,CAAA,GAA8B;KA4GlC,uBAAA;;;;;;;;;;;;;;;;iBAcI;;;;qBACI,sBAEjB,OAAO,GAAG"}
@@ -0,0 +1,288 @@
1
+ import { t as loadConfig } from "../config-loader-d_KF19Tw.mjs";
2
+ import { _ as errorUnexpected, a as errorContractValidationFailed, f as errorMigrationPlanningFailed, h as errorTargetMigrationNotSupported, l as errorFileNotFound, m as errorRuntime } from "../cli-errors-ByGuoqNj.mjs";
3
+ import { t as assertFrameworkComponentsCompatible } from "../framework-components-M2j-qPfr.mjs";
4
+ import { t as extractSqlDdl } from "../extract-sql-ddl-Jf5blEO0.mjs";
5
+ import { a as getTargetMigrations, f as setCommandDescriptions, g as parseGlobalFlags, i as addGlobalOptions, l as resolveContractPath, o as loadMigrationBundles, p as setCommandExamples, r as handleResult, t as TerminalUI, u as resolveMigrationPaths, y as formatStyledHeader } from "../terminal-ui-DAcMBRKf.mjs";
6
+ import { Command } from "commander";
7
+ import { notOk, ok } from "@prisma-next/utils/result";
8
+ import { join, relative } from "pathe";
9
+ import { createControlPlaneStack } from "@prisma-next/core-control-plane/stack";
10
+ import { EMPTY_CONTRACT_HASH } from "@prisma-next/core-control-plane/constants";
11
+ import { readFile } from "node:fs/promises";
12
+ import { findLatestMigration } from "@prisma-next/migration-tools/dag";
13
+ import { formatMigrationDirName, writeMigrationPackage } from "@prisma-next/migration-tools/io";
14
+ import { MigrationToolsError } from "@prisma-next/migration-tools/types";
15
+ import { attestMigration } from "@prisma-next/migration-tools/attestation";
16
+
17
+ //#region src/commands/migration-plan.ts
18
+ function mapMigrationToolsError(error) {
19
+ if (MigrationToolsError.is(error)) return errorRuntime(error.message, {
20
+ why: error.why,
21
+ fix: error.fix,
22
+ meta: {
23
+ code: error.code,
24
+ ...error.details ?? {}
25
+ }
26
+ });
27
+ return errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Unexpected error during migration plan: ${error instanceof Error ? error.message : String(error)}` });
28
+ }
29
+ async function executeMigrationPlanCommand(options, flags, ui, startTime) {
30
+ const config = await loadConfig(options.config);
31
+ const { configPath, migrationsDir, migrationsRelative } = resolveMigrationPaths(options.config, config);
32
+ const contractPathAbsolute = resolveContractPath(config);
33
+ const contractPath = relative(process.cwd(), contractPathAbsolute);
34
+ if (!flags.json && !flags.quiet) {
35
+ const details = [
36
+ {
37
+ label: "config",
38
+ value: configPath
39
+ },
40
+ {
41
+ label: "contract",
42
+ value: contractPath
43
+ },
44
+ {
45
+ label: "migrations",
46
+ value: migrationsRelative
47
+ }
48
+ ];
49
+ if (options.from) details.push({
50
+ label: "from",
51
+ value: options.from
52
+ });
53
+ if (options.name) details.push({
54
+ label: "name",
55
+ value: options.name
56
+ });
57
+ const header = formatStyledHeader({
58
+ command: "migration plan",
59
+ description: "Plan a migration from contract changes",
60
+ url: "https://pris.ly/migration-plan",
61
+ details,
62
+ flags
63
+ });
64
+ ui.stderr(header);
65
+ }
66
+ let contractJsonContent;
67
+ try {
68
+ contractJsonContent = await readFile(contractPathAbsolute, "utf-8");
69
+ } catch (error) {
70
+ if (error instanceof Error && error.code === "ENOENT") return notOk(errorFileNotFound(contractPathAbsolute, {
71
+ why: `Contract file not found at ${contractPathAbsolute}`,
72
+ fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
73
+ }));
74
+ return notOk(errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}` }));
75
+ }
76
+ let toContractJson;
77
+ try {
78
+ toContractJson = JSON.parse(contractJsonContent);
79
+ } catch (error) {
80
+ return notOk(errorContractValidationFailed(`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`, { where: { path: contractPathAbsolute } }));
81
+ }
82
+ const toStorageHash = toContractJson["storageHash"];
83
+ if (!toStorageHash) return notOk(errorContractValidationFailed("Contract is missing storageHash", { where: { path: contractPathAbsolute } }));
84
+ let fromContract = null;
85
+ let fromHash = EMPTY_CONTRACT_HASH;
86
+ try {
87
+ const { bundles, graph } = await loadMigrationBundles(migrationsDir);
88
+ if (options.from) {
89
+ const resolved = resolveBundleByPrefix(bundles, options.from);
90
+ if (!resolved.ok) {
91
+ const f = resolved.failure;
92
+ return notOk(f.reason === "ambiguous" ? errorRuntime("Multiple matching migrations found", {
93
+ why: `Prefix "${options.from}" matches ${f.count} migrations in ${migrationsRelative}`,
94
+ fix: "Provide a longer prefix to disambiguate, or omit --from to use the latest migration target."
95
+ }) : errorRuntime("Starting contract not found", {
96
+ why: `No migration with to hash matching "${options.from}" exists in ${migrationsRelative}`,
97
+ fix: "Check that the --from hash matches a known migration target hash, or omit --from to use the latest migration target."
98
+ }));
99
+ }
100
+ fromHash = resolved.value.manifest.to;
101
+ fromContract = resolved.value.manifest.toContract;
102
+ } else {
103
+ const latestMigration = findLatestMigration(graph);
104
+ if (latestMigration) {
105
+ fromHash = latestMigration.to;
106
+ const leafPkg = bundles.find((p) => p.manifest.migrationId === latestMigration.migrationId);
107
+ if (leafPkg) fromContract = leafPkg.manifest.toContract;
108
+ }
109
+ }
110
+ } catch (error) {
111
+ if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
112
+ throw error;
113
+ }
114
+ if (fromHash === toStorageHash) return ok({
115
+ ok: true,
116
+ noOp: true,
117
+ from: fromHash,
118
+ to: toStorageHash,
119
+ operations: [],
120
+ summary: "No changes detected between contracts",
121
+ timings: { total: Date.now() - startTime }
122
+ });
123
+ const migrations = getTargetMigrations(config.target);
124
+ if (!migrations) return notOk(errorTargetMigrationNotSupported({ why: `Target "${config.target.id}" does not support migrations` }));
125
+ const stack = createControlPlaneStack({
126
+ target: config.target,
127
+ adapter: config.adapter,
128
+ extensionPacks: config.extensionPacks ?? []
129
+ });
130
+ const familyInstance = config.family.create(stack);
131
+ const frameworkComponents = assertFrameworkComponentsCompatible(config.family.familyId, config.target.targetId, [
132
+ config.target,
133
+ config.adapter,
134
+ ...config.extensionPacks ?? []
135
+ ]);
136
+ const planner = migrations.createPlanner(familyInstance);
137
+ const fromSchemaIR = migrations.contractToSchema(fromContract, frameworkComponents);
138
+ const plannerResult = planner.plan({
139
+ contract: toContractJson,
140
+ schema: fromSchemaIR,
141
+ policy: { allowedOperationClasses: [
142
+ "additive",
143
+ "widening",
144
+ "destructive"
145
+ ] },
146
+ frameworkComponents
147
+ });
148
+ if (plannerResult.kind === "failure") return notOk(errorMigrationPlanningFailed({ conflicts: plannerResult.conflicts }));
149
+ if (plannerResult.plan.operations.length === 0) return notOk(errorMigrationPlanningFailed({ conflicts: [{
150
+ kind: "unsupportedChange",
151
+ summary: "Contract changed but planner produced no operations. This indicates unsupported or ignored changes (e.g. removals, type changes, or a planner/contract mismatch)."
152
+ }] }));
153
+ const timestamp = /* @__PURE__ */ new Date();
154
+ const packageDir = join(migrationsDir, formatMigrationDirName(timestamp, options.name ?? "migration"));
155
+ const manifest = {
156
+ from: fromHash,
157
+ to: toStorageHash,
158
+ migrationId: null,
159
+ kind: "regular",
160
+ fromContract,
161
+ toContract: toContractJson,
162
+ hints: {
163
+ used: [],
164
+ applied: [],
165
+ plannerVersion: "1.0.0",
166
+ planningStrategy: "diff"
167
+ },
168
+ labels: [],
169
+ createdAt: timestamp.toISOString()
170
+ };
171
+ try {
172
+ await writeMigrationPackage(packageDir, manifest, plannerResult.plan.operations);
173
+ const migrationId = await attestMigration(packageDir);
174
+ const sql = extractSqlDdl(plannerResult.plan.operations);
175
+ return ok({
176
+ ok: true,
177
+ noOp: false,
178
+ from: fromHash,
179
+ to: toStorageHash,
180
+ migrationId,
181
+ dir: relative(process.cwd(), packageDir),
182
+ operations: plannerResult.plan.operations.map((op) => ({
183
+ id: op.id,
184
+ label: op.label,
185
+ operationClass: op.operationClass
186
+ })),
187
+ sql,
188
+ summary: `Planned ${plannerResult.plan.operations.length} operation(s)`,
189
+ timings: { total: Date.now() - startTime }
190
+ });
191
+ } catch (error) {
192
+ return notOk(mapMigrationToolsError(error));
193
+ }
194
+ }
195
+ function createMigrationPlanCommand() {
196
+ const command = new Command("plan");
197
+ 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.");
198
+ setCommandExamples(command, ["prisma-next migration plan", "prisma-next migration plan --name add-users-table"]);
199
+ 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) => {
200
+ const flags = parseGlobalFlags(options);
201
+ const startTime = Date.now();
202
+ const ui = new TerminalUI({
203
+ color: flags.color,
204
+ interactive: flags.interactive
205
+ });
206
+ const exitCode = handleResult(await executeMigrationPlanCommand(options, flags, ui, startTime), flags, ui, (planResult) => {
207
+ if (flags.json) ui.output(JSON.stringify(planResult, null, 2));
208
+ else if (!flags.quiet) ui.log(formatMigrationPlanOutput(planResult, flags));
209
+ });
210
+ process.exit(exitCode);
211
+ });
212
+ return command;
213
+ }
214
+ function formatMigrationPlanOutput(result, flags) {
215
+ const lines = [];
216
+ const useColor = flags.color !== false;
217
+ const green_ = useColor ? (s) => `\x1b[32m${s}\x1b[0m` : (s) => s;
218
+ const yellow_ = useColor ? (s) => `\x1b[33m${s}\x1b[0m` : (s) => s;
219
+ const dim_ = useColor ? (s) => `\x1b[2m${s}\x1b[0m` : (s) => s;
220
+ if (result.noOp) {
221
+ lines.push(`${green_("✔")} No changes detected`);
222
+ lines.push(dim_(` from: ${result.from}`));
223
+ lines.push(dim_(` to: ${result.to}`));
224
+ return lines.join("\n");
225
+ }
226
+ lines.push(`${green_("✔")} ${result.summary}`);
227
+ lines.push("");
228
+ if (result.operations.length > 0) {
229
+ lines.push(dim_("│"));
230
+ for (let i = 0; i < result.operations.length; i++) {
231
+ const op = result.operations[i];
232
+ const treeChar = i === result.operations.length - 1 ? "└" : "├";
233
+ const opClassLabel = op.operationClass === "destructive" ? yellow_(`[${op.operationClass}]`) : dim_(`[${op.operationClass}]`);
234
+ lines.push(`${dim_(treeChar)}─ ${op.label} ${opClassLabel}`);
235
+ }
236
+ if (result.operations.some((op) => op.operationClass === "destructive")) {
237
+ lines.push("");
238
+ lines.push(`${yellow_("⚠")} This migration contains destructive operations that may cause data loss.`);
239
+ }
240
+ lines.push("");
241
+ }
242
+ lines.push(dim_(`from: ${result.from}`));
243
+ lines.push(dim_(`to: ${result.to}`));
244
+ if (result.migrationId) lines.push(dim_(`migrationId: ${result.migrationId}`));
245
+ if (result.dir) lines.push(dim_(`dir: ${result.dir}`));
246
+ if (result.sql && result.sql.length > 0) {
247
+ lines.push("");
248
+ lines.push(dim_("DDL preview"));
249
+ lines.push("");
250
+ for (const statement of result.sql) {
251
+ const trimmed = statement.trim();
252
+ if (!trimmed) continue;
253
+ const line = trimmed.endsWith(";") ? trimmed : `${trimmed};`;
254
+ lines.push(line);
255
+ }
256
+ }
257
+ if (flags.verbose && result.timings) {
258
+ lines.push("");
259
+ lines.push(dim_(`Total time: ${result.timings.total}ms`));
260
+ }
261
+ return lines.join("\n");
262
+ }
263
+ /**
264
+ * Resolve a migration bundle by exact hash or prefix match.
265
+ *
266
+ * Tries exact match first, then prefix match (auto-prepending `sha256:` when
267
+ * the needle omits the scheme). Returns the matched bundle on success, or a
268
+ * discriminated failure indicating whether the prefix was ambiguous or simply
269
+ * not found.
270
+ *
271
+ * @internal Exported for testing only.
272
+ */
273
+ function resolveBundleByPrefix(bundles, needle) {
274
+ const exact = bundles.find((p) => p.manifest.to === needle);
275
+ if (exact) return ok(exact);
276
+ const prefixWithScheme = needle.startsWith("sha256:") ? needle : `sha256:${needle}`;
277
+ const candidates = bundles.filter((p) => p.manifest.to.startsWith(prefixWithScheme));
278
+ if (candidates.length === 1) return ok(candidates[0]);
279
+ if (candidates.length > 1) return notOk({
280
+ reason: "ambiguous",
281
+ count: candidates.length
282
+ });
283
+ return notOk({ reason: "not-found" });
284
+ }
285
+
286
+ //#endregion
287
+ export { createMigrationPlanCommand, resolveBundleByPrefix };
288
+ //# sourceMappingURL=migration-plan.mjs.map