@prisma-next/cli 0.3.0-dev.16 → 0.3.0-dev.162

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 (242) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +381 -128
  3. package/dist/cli-errors-BDCYR5ap.mjs +4 -0
  4. package/dist/cli-errors-Dzs7Oxz7.d.mts +3 -0
  5. package/dist/cli.d.mts +1 -0
  6. package/dist/cli.js +1 -2671
  7. package/dist/cli.mjs +245 -0
  8. package/dist/cli.mjs.map +1 -0
  9. package/dist/client-yYtotiSX.mjs +1063 -0
  10. package/dist/client-yYtotiSX.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 +322 -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 +244 -0
  40. package/dist/commands/migration-apply.mjs.map +1 -0
  41. package/dist/commands/migration-new.d.mts +8 -0
  42. package/dist/commands/migration-new.d.mts.map +1 -0
  43. package/dist/commands/migration-new.mjs +151 -0
  44. package/dist/commands/migration-new.mjs.map +1 -0
  45. package/dist/commands/migration-plan.d.mts +47 -0
  46. package/dist/commands/migration-plan.d.mts.map +1 -0
  47. package/dist/commands/migration-plan.mjs +312 -0
  48. package/dist/commands/migration-plan.mjs.map +1 -0
  49. package/dist/commands/migration-ref.d.mts +43 -0
  50. package/dist/commands/migration-ref.d.mts.map +1 -0
  51. package/dist/commands/migration-ref.mjs +194 -0
  52. package/dist/commands/migration-ref.mjs.map +1 -0
  53. package/dist/commands/migration-show.d.mts +28 -0
  54. package/dist/commands/migration-show.d.mts.map +1 -0
  55. package/dist/commands/migration-show.mjs +139 -0
  56. package/dist/commands/migration-show.mjs.map +1 -0
  57. package/dist/commands/migration-status.d.mts +86 -0
  58. package/dist/commands/migration-status.d.mts.map +1 -0
  59. package/dist/commands/migration-status.mjs +8 -0
  60. package/dist/commands/migration-verify.d.mts +16 -0
  61. package/dist/commands/migration-verify.d.mts.map +1 -0
  62. package/dist/commands/migration-verify.mjs +109 -0
  63. package/dist/commands/migration-verify.mjs.map +1 -0
  64. package/dist/config-loader-C4VXKl8f.mjs +43 -0
  65. package/dist/config-loader-C4VXKl8f.mjs.map +1 -0
  66. package/dist/{config-loader.d.ts → config-loader.d.mts} +8 -3
  67. package/dist/config-loader.d.mts.map +1 -0
  68. package/dist/config-loader.mjs +3 -0
  69. package/dist/contract-emit-Bk_eEDKu.mjs +187 -0
  70. package/dist/contract-emit-Bk_eEDKu.mjs.map +1 -0
  71. package/dist/contract-infer-suMDmFSG.mjs +89 -0
  72. package/dist/contract-infer-suMDmFSG.mjs.map +1 -0
  73. package/dist/exports/config-types.d.mts +2 -0
  74. package/dist/exports/config-types.mjs +3 -0
  75. package/dist/exports/control-api.d.mts +624 -0
  76. package/dist/exports/control-api.d.mts.map +1 -0
  77. package/dist/exports/control-api.mjs +109 -0
  78. package/dist/exports/control-api.mjs.map +1 -0
  79. package/dist/{load-ts-contract.d.ts → exports/index.d.mts} +12 -7
  80. package/dist/exports/index.d.mts.map +1 -0
  81. package/dist/exports/index.mjs +141 -0
  82. package/dist/exports/index.mjs.map +1 -0
  83. package/dist/extract-operation-statements-BVlb3jxp.mjs +13 -0
  84. package/dist/extract-operation-statements-BVlb3jxp.mjs.map +1 -0
  85. package/dist/extract-sql-ddl-6EVSOThm.mjs +26 -0
  86. package/dist/extract-sql-ddl-6EVSOThm.mjs.map +1 -0
  87. package/dist/framework-components-BAsliT4V.mjs +59 -0
  88. package/dist/framework-components-BAsliT4V.mjs.map +1 -0
  89. package/dist/inspect-live-schema-HMutsJYh.mjs +91 -0
  90. package/dist/inspect-live-schema-HMutsJYh.mjs.map +1 -0
  91. package/dist/migration-command-scaffold-Dg7CKKCg.mjs +105 -0
  92. package/dist/migration-command-scaffold-Dg7CKKCg.mjs.map +1 -0
  93. package/dist/migration-status-BqfVmC0w.mjs +1582 -0
  94. package/dist/migration-status-BqfVmC0w.mjs.map +1 -0
  95. package/dist/migrations-Bv8oeiY_.mjs +173 -0
  96. package/dist/migrations-Bv8oeiY_.mjs.map +1 -0
  97. package/dist/progress-adapter-D4x8SbJa.mjs +43 -0
  98. package/dist/progress-adapter-D4x8SbJa.mjs.map +1 -0
  99. package/dist/terminal-ui-N5tR-ob5.mjs +967 -0
  100. package/dist/terminal-ui-N5tR-ob5.mjs.map +1 -0
  101. package/dist/verify-WARh5TjK.mjs +385 -0
  102. package/dist/verify-WARh5TjK.mjs.map +1 -0
  103. package/package.json +88 -42
  104. package/src/cli.ts +113 -58
  105. package/src/commands/contract-emit.ts +237 -144
  106. package/src/commands/contract-infer-paths.ts +32 -0
  107. package/src/commands/contract-infer.ts +143 -0
  108. package/src/commands/db-init.ts +97 -219
  109. package/src/commands/db-schema.ts +77 -0
  110. package/src/commands/db-sign.ts +208 -229
  111. package/src/commands/db-update.ts +236 -0
  112. package/src/commands/db-verify.ts +504 -184
  113. package/src/commands/inspect-live-schema.ts +170 -0
  114. package/src/commands/migration-apply.ts +427 -0
  115. package/src/commands/migration-new.ts +260 -0
  116. package/src/commands/migration-plan.ts +519 -0
  117. package/src/commands/migration-ref.ts +305 -0
  118. package/src/commands/migration-show.ts +246 -0
  119. package/src/commands/migration-status.ts +864 -0
  120. package/src/commands/migration-verify.ts +180 -0
  121. package/src/config-loader.ts +13 -3
  122. package/src/control-api/client.ts +424 -72
  123. package/src/control-api/contract-enrichment.ts +119 -0
  124. package/src/control-api/errors.ts +9 -0
  125. package/src/control-api/operations/contract-emit.ts +174 -0
  126. package/src/control-api/operations/db-init.ts +53 -49
  127. package/src/control-api/operations/db-update.ts +220 -0
  128. package/src/control-api/operations/extract-operation-statements.ts +14 -0
  129. package/src/control-api/operations/extract-sql-ddl.ts +47 -0
  130. package/src/control-api/operations/migration-apply.ts +191 -0
  131. package/src/control-api/operations/migration-helpers.ts +49 -0
  132. package/src/control-api/types.ts +388 -18
  133. package/src/exports/config-types.ts +4 -3
  134. package/src/exports/control-api.ts +21 -2
  135. package/src/load-ts-contract.ts +30 -19
  136. package/src/utils/cli-errors.ts +14 -8
  137. package/src/utils/command-helpers.ts +302 -3
  138. package/src/utils/formatters/emit.ts +67 -0
  139. package/src/utils/formatters/errors.ts +82 -0
  140. package/src/utils/formatters/graph-migration-mapper.ts +240 -0
  141. package/src/utils/formatters/graph-render.ts +1323 -0
  142. package/src/utils/formatters/graph-types.ts +120 -0
  143. package/src/utils/formatters/help.ts +380 -0
  144. package/src/utils/formatters/helpers.ts +28 -0
  145. package/src/utils/formatters/migrations.ts +346 -0
  146. package/src/utils/formatters/styled.ts +212 -0
  147. package/src/utils/formatters/verify.ts +621 -0
  148. package/src/utils/framework-components.ts +13 -10
  149. package/src/utils/global-flags.ts +41 -23
  150. package/src/utils/migration-command-scaffold.ts +184 -0
  151. package/src/utils/migration-types.ts +12 -0
  152. package/src/utils/progress-adapter.ts +18 -29
  153. package/src/utils/result-handler.ts +12 -13
  154. package/src/utils/shutdown.ts +92 -0
  155. package/src/utils/suggest-command.ts +31 -0
  156. package/src/utils/terminal-ui.ts +276 -0
  157. package/dist/chunk-5MPKZYVI.js +0 -47
  158. package/dist/chunk-5MPKZYVI.js.map +0 -1
  159. package/dist/chunk-6EPKRATC.js +0 -91
  160. package/dist/chunk-6EPKRATC.js.map +0 -1
  161. package/dist/chunk-74IELXRA.js +0 -371
  162. package/dist/chunk-74IELXRA.js.map +0 -1
  163. package/dist/chunk-HWYQOCAJ.js +0 -47
  164. package/dist/chunk-HWYQOCAJ.js.map +0 -1
  165. package/dist/chunk-U6QI3AZ3.js +0 -133
  166. package/dist/chunk-U6QI3AZ3.js.map +0 -1
  167. package/dist/chunk-VI2YETW7.js +0 -38
  168. package/dist/chunk-VI2YETW7.js.map +0 -1
  169. package/dist/chunk-ZG5T6OB5.js +0 -923
  170. package/dist/chunk-ZG5T6OB5.js.map +0 -1
  171. package/dist/cli.d.ts +0 -2
  172. package/dist/cli.d.ts.map +0 -1
  173. package/dist/cli.js.map +0 -1
  174. package/dist/commands/contract-emit.d.ts +0 -3
  175. package/dist/commands/contract-emit.d.ts.map +0 -1
  176. package/dist/commands/contract-emit.js +0 -11
  177. package/dist/commands/contract-emit.js.map +0 -1
  178. package/dist/commands/db-init.d.ts +0 -3
  179. package/dist/commands/db-init.d.ts.map +0 -1
  180. package/dist/commands/db-init.js +0 -302
  181. package/dist/commands/db-init.js.map +0 -1
  182. package/dist/commands/db-introspect.d.ts +0 -3
  183. package/dist/commands/db-introspect.d.ts.map +0 -1
  184. package/dist/commands/db-introspect.js +0 -185
  185. package/dist/commands/db-introspect.js.map +0 -1
  186. package/dist/commands/db-schema-verify.d.ts +0 -3
  187. package/dist/commands/db-schema-verify.d.ts.map +0 -1
  188. package/dist/commands/db-schema-verify.js +0 -163
  189. package/dist/commands/db-schema-verify.js.map +0 -1
  190. package/dist/commands/db-sign.d.ts +0 -3
  191. package/dist/commands/db-sign.d.ts.map +0 -1
  192. package/dist/commands/db-sign.js +0 -198
  193. package/dist/commands/db-sign.js.map +0 -1
  194. package/dist/commands/db-verify.d.ts +0 -3
  195. package/dist/commands/db-verify.d.ts.map +0 -1
  196. package/dist/commands/db-verify.js +0 -172
  197. package/dist/commands/db-verify.js.map +0 -1
  198. package/dist/config-loader.d.ts.map +0 -1
  199. package/dist/config-loader.js +0 -7
  200. package/dist/config-loader.js.map +0 -1
  201. package/dist/control-api/client.d.ts +0 -13
  202. package/dist/control-api/client.d.ts.map +0 -1
  203. package/dist/control-api/operations/db-init.d.ts +0 -29
  204. package/dist/control-api/operations/db-init.d.ts.map +0 -1
  205. package/dist/control-api/types.d.ts +0 -256
  206. package/dist/control-api/types.d.ts.map +0 -1
  207. package/dist/exports/config-types.d.ts +0 -3
  208. package/dist/exports/config-types.d.ts.map +0 -1
  209. package/dist/exports/config-types.js +0 -6
  210. package/dist/exports/config-types.js.map +0 -1
  211. package/dist/exports/control-api.d.ts +0 -13
  212. package/dist/exports/control-api.d.ts.map +0 -1
  213. package/dist/exports/control-api.js +0 -9
  214. package/dist/exports/control-api.js.map +0 -1
  215. package/dist/exports/index.d.ts +0 -4
  216. package/dist/exports/index.d.ts.map +0 -1
  217. package/dist/exports/index.js +0 -177
  218. package/dist/exports/index.js.map +0 -1
  219. package/dist/load-ts-contract.d.ts.map +0 -1
  220. package/dist/utils/action.d.ts +0 -16
  221. package/dist/utils/action.d.ts.map +0 -1
  222. package/dist/utils/cli-errors.d.ts +0 -7
  223. package/dist/utils/cli-errors.d.ts.map +0 -1
  224. package/dist/utils/command-helpers.d.ts +0 -12
  225. package/dist/utils/command-helpers.d.ts.map +0 -1
  226. package/dist/utils/framework-components.d.ts +0 -70
  227. package/dist/utils/framework-components.d.ts.map +0 -1
  228. package/dist/utils/global-flags.d.ts +0 -25
  229. package/dist/utils/global-flags.d.ts.map +0 -1
  230. package/dist/utils/output.d.ts +0 -142
  231. package/dist/utils/output.d.ts.map +0 -1
  232. package/dist/utils/progress-adapter.d.ts +0 -26
  233. package/dist/utils/progress-adapter.d.ts.map +0 -1
  234. package/dist/utils/result-handler.d.ts +0 -15
  235. package/dist/utils/result-handler.d.ts.map +0 -1
  236. package/dist/utils/spinner.d.ts +0 -29
  237. package/dist/utils/spinner.d.ts.map +0 -1
  238. package/src/commands/db-introspect.ts +0 -254
  239. package/src/commands/db-schema-verify.ts +0 -231
  240. package/src/utils/action.ts +0 -43
  241. package/src/utils/output.ts +0 -1471
  242. package/src/utils/spinner.ts +0 -67
@@ -0,0 +1,170 @@
1
+ import type { CoreSchemaView } from '@prisma-next/framework-components/control';
2
+ import { validatePrintableSqlSchemaIR } from '@prisma-next/psl-printer';
3
+ import { notOk, ok, type Result } from '@prisma-next/utils/result';
4
+ import { relative, resolve } from 'pathe';
5
+ import { loadConfig } from '../config-loader';
6
+ import { createControlClient } from '../control-api/client';
7
+ import {
8
+ CliStructuredError,
9
+ errorDatabaseConnectionRequired,
10
+ errorDriverRequired,
11
+ errorUnexpected,
12
+ } from '../utils/cli-errors';
13
+ import { maskConnectionUrl, sanitizeErrorMessage } from '../utils/command-helpers';
14
+ import { formatStyledHeader } from '../utils/formatters/styled';
15
+ import type { CommonCommandOptions, GlobalFlags } from '../utils/global-flags';
16
+ import { createProgressAdapter } from '../utils/progress-adapter';
17
+ import type { TerminalUI } from '../utils/terminal-ui';
18
+
19
+ export interface InspectLiveSchemaOptions extends CommonCommandOptions {
20
+ readonly db?: string;
21
+ readonly config?: string;
22
+ }
23
+
24
+ interface InspectLiveSchemaContext {
25
+ readonly commandName: string;
26
+ readonly description: string;
27
+ readonly url: string;
28
+ }
29
+
30
+ type LoadedCliConfig = Awaited<ReturnType<typeof loadConfig>>;
31
+
32
+ export interface InspectLiveSchemaResult {
33
+ readonly config: LoadedCliConfig;
34
+ readonly schema: unknown;
35
+ readonly schemaView: CoreSchemaView | undefined;
36
+ readonly target: {
37
+ readonly familyId: string;
38
+ readonly id: string;
39
+ };
40
+ readonly meta: {
41
+ readonly configPath?: string;
42
+ readonly dbUrl?: string;
43
+ };
44
+ readonly timings: {
45
+ readonly total: number;
46
+ };
47
+ }
48
+
49
+ export async function inspectLiveSchema(
50
+ options: InspectLiveSchemaOptions,
51
+ flags: GlobalFlags,
52
+ ui: TerminalUI,
53
+ startTime: number,
54
+ context: InspectLiveSchemaContext,
55
+ ): Promise<Result<InspectLiveSchemaResult, CliStructuredError>> {
56
+ let config: LoadedCliConfig;
57
+ try {
58
+ config = await loadConfig(options.config);
59
+ } catch (error) {
60
+ if (CliStructuredError.is(error)) {
61
+ return notOk(error);
62
+ }
63
+
64
+ return notOk(
65
+ errorUnexpected(error instanceof Error ? error.message : String(error), {
66
+ why: 'Failed to load config',
67
+ }),
68
+ );
69
+ }
70
+
71
+ const configPath = options.config
72
+ ? relative(process.cwd(), resolve(options.config))
73
+ : 'prisma-next.config.ts';
74
+
75
+ if (!flags.json && !flags.quiet) {
76
+ const details: Array<{ label: string; value: string }> = [
77
+ { label: 'config', value: configPath },
78
+ ];
79
+
80
+ if (options.db) {
81
+ details.push({ label: 'database', value: maskConnectionUrl(options.db) });
82
+ } else if (config.db?.connection && typeof config.db.connection === 'string') {
83
+ details.push({ label: 'database', value: maskConnectionUrl(config.db.connection) });
84
+ }
85
+
86
+ ui.stderr(
87
+ formatStyledHeader({
88
+ command: context.commandName,
89
+ description: context.description,
90
+ url: context.url,
91
+ details,
92
+ flags,
93
+ }),
94
+ );
95
+ }
96
+
97
+ const dbConnection = options.db ?? config.db?.connection;
98
+ if (!dbConnection) {
99
+ return notOk(
100
+ errorDatabaseConnectionRequired({
101
+ why: `Database connection is required for ${context.commandName} (set db.connection in ${configPath}, or pass --db <url>)`,
102
+ commandName: context.commandName,
103
+ }),
104
+ );
105
+ }
106
+
107
+ if (!config.driver) {
108
+ return notOk(
109
+ errorDriverRequired({
110
+ why: `Config.driver is required for ${context.commandName}`,
111
+ }),
112
+ );
113
+ }
114
+
115
+ const client = createControlClient({
116
+ family: config.family,
117
+ target: config.target,
118
+ adapter: config.adapter,
119
+ driver: config.driver,
120
+ extensionPacks: config.extensionPacks ?? [],
121
+ });
122
+ const onProgress = createProgressAdapter({ ui, flags });
123
+
124
+ try {
125
+ const schemaIR = await client.introspect({
126
+ connection: dbConnection,
127
+ onProgress,
128
+ });
129
+ // TODO(TML-2251): Remove SQL-specific branching — SQL should use the same family-agnostic path as Mongo.
130
+ const schema =
131
+ config.family.familyId === 'sql' ? validatePrintableSqlSchemaIR(schemaIR) : schemaIR;
132
+ const schemaView = client.toSchemaView(schema);
133
+
134
+ const dbUrl = typeof dbConnection === 'string' ? maskConnectionUrl(dbConnection) : undefined;
135
+
136
+ return ok({
137
+ config,
138
+ schema,
139
+ schemaView,
140
+ target: {
141
+ familyId: config.family.familyId,
142
+ id: config.target.targetId,
143
+ },
144
+ meta: {
145
+ configPath,
146
+ ...(dbUrl ? { dbUrl } : {}),
147
+ },
148
+ timings: {
149
+ total: Date.now() - startTime,
150
+ },
151
+ });
152
+ } catch (error) {
153
+ if (CliStructuredError.is(error)) {
154
+ return notOk(error);
155
+ }
156
+
157
+ const rawMessage = error instanceof Error ? error.message : String(error);
158
+ const safeMessage = sanitizeErrorMessage(
159
+ rawMessage,
160
+ typeof dbConnection === 'string' ? dbConnection : undefined,
161
+ );
162
+ return notOk(
163
+ errorUnexpected(safeMessage, {
164
+ why: `Unexpected error during ${context.commandName}: ${safeMessage}`,
165
+ }),
166
+ );
167
+ } finally {
168
+ await client.close();
169
+ }
170
+ }
@@ -0,0 +1,427 @@
1
+ import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
2
+ import { findPathWithDecision } from '@prisma-next/migration-tools/dag';
3
+ import { readRefs, resolveRef } from '@prisma-next/migration-tools/refs';
4
+ import type { AttestedMigrationBundle } from '@prisma-next/migration-tools/types';
5
+ import { MigrationToolsError } from '@prisma-next/migration-tools/types';
6
+ import { notOk, ok, type Result } from '@prisma-next/utils/result';
7
+ import { Command } from 'commander';
8
+
9
+ import { loadConfig } from '../config-loader';
10
+ import { createControlClient } from '../control-api/client';
11
+ import type { MigrationApplyFailure, MigrationApplyStep } from '../control-api/types';
12
+ import {
13
+ CliStructuredError,
14
+ type CliStructuredError as CliStructuredErrorType,
15
+ errorDatabaseConnectionRequired,
16
+ errorDriverRequired,
17
+ errorRuntime,
18
+ errorTargetMigrationNotSupported,
19
+ errorUnexpected,
20
+ } from '../utils/cli-errors';
21
+ import {
22
+ addGlobalOptions,
23
+ loadAllBundles,
24
+ type MigrationBundleSet,
25
+ maskConnectionUrl,
26
+ readContractEnvelope,
27
+ resolveMigrationPaths,
28
+ setCommandDescriptions,
29
+ setCommandExamples,
30
+ targetSupportsMigrations,
31
+ toPathDecisionResult,
32
+ } from '../utils/command-helpers';
33
+ import { formatMigrationApplyCommandOutput } from '../utils/formatters/migrations';
34
+ import { formatStyledHeader } from '../utils/formatters/styled';
35
+ import type { CommonCommandOptions } from '../utils/global-flags';
36
+ import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
37
+ import { handleResult } from '../utils/result-handler';
38
+ import { TerminalUI } from '../utils/terminal-ui';
39
+
40
+ interface MigrationApplyCommandOptions extends CommonCommandOptions {
41
+ readonly db?: string;
42
+ readonly config?: string;
43
+ readonly ref?: string;
44
+ }
45
+
46
+ export interface MigrationApplyResult {
47
+ readonly ok: boolean;
48
+ readonly migrationsApplied: number;
49
+ readonly migrationsTotal: number;
50
+ readonly markerHash: string;
51
+ readonly applied: readonly {
52
+ readonly dirName: string;
53
+ readonly from: string;
54
+ readonly to: string;
55
+ readonly operationsExecuted: number;
56
+ }[];
57
+ readonly summary: string;
58
+ readonly pathDecision?: {
59
+ readonly fromHash: string;
60
+ readonly toHash: string;
61
+ readonly alternativeCount: number;
62
+ readonly tieBreakReasons: readonly string[];
63
+ readonly refName?: string;
64
+ readonly selectedPath: readonly {
65
+ readonly dirName: string;
66
+ readonly migrationId: string | null;
67
+ readonly from: string;
68
+ readonly to: string;
69
+ }[];
70
+ };
71
+ readonly timings: {
72
+ readonly total: number;
73
+ };
74
+ }
75
+
76
+ function mapMigrationToolsError(error: unknown): CliStructuredErrorType {
77
+ if (MigrationToolsError.is(error)) {
78
+ return errorRuntime(error.message, {
79
+ why: error.why,
80
+ fix: error.fix,
81
+ meta: { code: error.code, ...(error.details ?? {}) },
82
+ });
83
+ }
84
+ return errorUnexpected(error instanceof Error ? error.message : String(error), {
85
+ why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}`,
86
+ });
87
+ }
88
+
89
+ function mapApplyFailure(failure: MigrationApplyFailure): CliStructuredErrorType {
90
+ return errorRuntime(failure.summary, {
91
+ why: failure.why ?? 'Migration runner failed',
92
+ fix: 'Fix the issue and re-run `prisma-next migration apply` — previously applied migrations are preserved.',
93
+ meta: failure.meta ?? {},
94
+ });
95
+ }
96
+
97
+ function packageToStep(pkg: AttestedMigrationBundle): MigrationApplyStep {
98
+ return {
99
+ dirName: pkg.dirName,
100
+ from: pkg.manifest.from,
101
+ to: pkg.manifest.to,
102
+ toContract: pkg.manifest.toContract,
103
+ operations: pkg.ops,
104
+ };
105
+ }
106
+
107
+ async function executeMigrationApplyCommand(
108
+ options: MigrationApplyCommandOptions,
109
+ flags: GlobalFlags,
110
+ ui: TerminalUI,
111
+ startTime: number,
112
+ ): Promise<Result<MigrationApplyResult, CliStructuredErrorType>> {
113
+ const config = await loadConfig(options.config);
114
+ const { configPath, migrationsDir, migrationsRelative, refsPath } = resolveMigrationPaths(
115
+ options.config,
116
+ config,
117
+ );
118
+
119
+ const dbConnection = options.db ?? config.db?.connection;
120
+ if (!dbConnection) {
121
+ return notOk(
122
+ errorDatabaseConnectionRequired({
123
+ why: `Database connection is required for migration apply (set db.connection in ${configPath}, or pass --db <url>)`,
124
+ commandName: 'migration apply',
125
+ }),
126
+ );
127
+ }
128
+
129
+ if (!config.driver) {
130
+ return notOk(
131
+ errorDriverRequired({
132
+ why: 'Config.driver is required for migration apply',
133
+ }),
134
+ );
135
+ }
136
+
137
+ if (!targetSupportsMigrations(config.target)) {
138
+ return notOk(
139
+ errorTargetMigrationNotSupported({
140
+ why: `Target "${config.target.id}" does not support migrations`,
141
+ }),
142
+ );
143
+ }
144
+
145
+ let destinationHash: string;
146
+ let refName: string | undefined;
147
+
148
+ if (options.ref) {
149
+ refName = options.ref;
150
+ try {
151
+ const refs = await readRefs(refsPath);
152
+ destinationHash = resolveRef(refs, refName);
153
+ } catch (error) {
154
+ if (MigrationToolsError.is(error)) {
155
+ return notOk(mapMigrationToolsError(error));
156
+ }
157
+ throw error;
158
+ }
159
+ } else {
160
+ try {
161
+ const envelope = await readContractEnvelope(config);
162
+ destinationHash = envelope.storageHash;
163
+ } catch (error) {
164
+ return notOk(
165
+ errorRuntime('Current contract is unavailable', {
166
+ why: `Failed to read contract: ${error instanceof Error ? error.message : String(error)}`,
167
+ fix: 'Run `prisma-next contract emit` to generate a valid contract.json, then retry apply.',
168
+ }),
169
+ );
170
+ }
171
+ }
172
+
173
+ if (!flags.json && !flags.quiet) {
174
+ const details: Array<{ label: string; value: string }> = [
175
+ { label: 'config', value: configPath },
176
+ { label: 'migrations', value: migrationsRelative },
177
+ ];
178
+ if (typeof dbConnection === 'string') {
179
+ details.push({
180
+ label: 'database',
181
+ value: maskConnectionUrl(dbConnection),
182
+ });
183
+ }
184
+ if (refName) {
185
+ details.push({ label: 'ref', value: refName });
186
+ }
187
+ const header = formatStyledHeader({
188
+ command: 'migration apply',
189
+ description: 'Apply planned migrations to the database',
190
+ url: 'https://pris.ly/migration-apply',
191
+ details,
192
+ flags,
193
+ });
194
+ ui.stderr(header);
195
+ }
196
+
197
+ // Read migrations and build migration chain model (offline — no DB needed)
198
+ let migrations: MigrationBundleSet;
199
+ try {
200
+ migrations = await loadAllBundles(migrationsDir);
201
+ if (migrations.drafts.length > 0 && !flags.quiet) {
202
+ ui.warn(
203
+ `${migrations.drafts.length} draft migration(s) found: ${migrations.drafts.map((d) => d.dirName).join(', ')}. ` +
204
+ "Run 'prisma-next migration verify --dir <path>' to attest before applying.",
205
+ );
206
+ }
207
+ } catch (error) {
208
+ if (MigrationToolsError.is(error)) {
209
+ return notOk(mapMigrationToolsError(error));
210
+ }
211
+ throw error;
212
+ }
213
+
214
+ const client = createControlClient({
215
+ family: config.family,
216
+ target: config.target,
217
+ adapter: config.adapter,
218
+ driver: config.driver,
219
+ extensionPacks: config.extensionPacks ?? [],
220
+ });
221
+
222
+ try {
223
+ await client.connect(dbConnection);
224
+ const marker = await client.readMarker();
225
+
226
+ // --- No attested migrations on disk ---
227
+ if (migrations.attested.length === 0) {
228
+ if (marker?.storageHash) {
229
+ return notOk(
230
+ errorRuntime('Database has state but no migrations exist', {
231
+ why: `The database marker hash "${marker.storageHash}" exists but no attested migrations were found in ${migrationsRelative}`,
232
+ 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.',
233
+ meta: { markerHash: marker.storageHash, migrationsDir: migrationsRelative },
234
+ }),
235
+ );
236
+ }
237
+ // Non-empty contract + no migrations = user needs to plan first.
238
+ if (destinationHash !== EMPTY_CONTRACT_HASH) {
239
+ return notOk(
240
+ errorRuntime('Current contract has no planned migrations', {
241
+ why: `No attested migrations were found in ${migrationsRelative}, but current contract hash is "${destinationHash}"`,
242
+ fix: 'Run `prisma-next migration plan` to create an attested migration for the current contract.',
243
+ meta: { destinationHash, migrationsDir: migrationsRelative },
244
+ }),
245
+ );
246
+ }
247
+ // Empty contract + no migrations = nothing to do.
248
+ return ok({
249
+ ok: true,
250
+ migrationsApplied: 0,
251
+ migrationsTotal: 0,
252
+ markerHash: EMPTY_CONTRACT_HASH,
253
+ applied: [],
254
+ summary: 'No attested migrations found',
255
+ timings: { total: Date.now() - startTime },
256
+ });
257
+ }
258
+
259
+ // --- Validate marker state ---
260
+
261
+ // The empty sentinel should never appear in a real marker row — if it does,
262
+ // the marker was corrupted and replaying all migrations would be dangerous.
263
+ if (marker?.storageHash === EMPTY_CONTRACT_HASH) {
264
+ return notOk(
265
+ errorRuntime('Database marker contains the empty sentinel hash', {
266
+ 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.`,
267
+ fix: 'The marker is corrupted. Run `prisma-next db sign` to overwrite it with the correct contract hash, or drop and recreate the database.',
268
+ meta: { markerHash: EMPTY_CONTRACT_HASH },
269
+ }),
270
+ );
271
+ }
272
+
273
+ const markerHash = marker?.storageHash;
274
+
275
+ if (markerHash !== undefined && !migrations.graph.nodes.has(markerHash)) {
276
+ return notOk(
277
+ errorRuntime('Database marker does not match any known migration', {
278
+ why: `The database marker hash "${markerHash}" is not found in the migration history at ${migrationsRelative}`,
279
+ 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.',
280
+ meta: { markerHash, knownNodes: [...migrations.graph.nodes] },
281
+ }),
282
+ );
283
+ }
284
+
285
+ if (!migrations.graph.nodes.has(destinationHash)) {
286
+ const matchingDraft = migrations.drafts.find((d) => d.manifest.to === destinationHash);
287
+ return notOk(
288
+ errorRuntime('Current contract has no planned migration path', {
289
+ why: matchingDraft
290
+ ? `A draft migration exists at "${matchingDraft.dirName}" but has not been attested`
291
+ : `Current contract hash "${destinationHash}" is not present in the migration history at ${migrationsRelative}`,
292
+ fix: matchingDraft
293
+ ? `Run 'prisma-next migration verify --dir ${migrationsRelative}/${matchingDraft.dirName}' to attest, then re-run apply.`
294
+ : 'Run `prisma-next migration plan` to create a migration for the current contract, then re-run apply.',
295
+ meta: { destinationHash, knownNodes: [...migrations.graph.nodes] },
296
+ }),
297
+ );
298
+ }
299
+
300
+ // --- Resolve path and apply ---
301
+
302
+ // "No marker" means the database is fresh — start from the empty contract hash.
303
+ const originHash = markerHash ?? EMPTY_CONTRACT_HASH;
304
+
305
+ const decision = findPathWithDecision(migrations.graph, originHash, destinationHash, refName);
306
+ if (!decision) {
307
+ return notOk(
308
+ errorRuntime('No migration path from current state to target', {
309
+ why: `Cannot find a path from "${originHash}" to target "${destinationHash}"`,
310
+ fix: 'Check the migration history for gaps or inconsistencies.',
311
+ meta: { markerHash: originHash, destinationHash },
312
+ }),
313
+ );
314
+ }
315
+
316
+ const pendingPath = decision.selectedPath;
317
+ const pathDecision = toPathDecisionResult(decision);
318
+
319
+ if (pendingPath.length === 0) {
320
+ return ok({
321
+ ok: true,
322
+ migrationsApplied: 0,
323
+ migrationsTotal: 0,
324
+ markerHash: originHash,
325
+ applied: [],
326
+ summary: 'Already up to date',
327
+ pathDecision,
328
+ timings: { total: Date.now() - startTime },
329
+ });
330
+ }
331
+
332
+ const bundleByDir = new Map(migrations.attested.map((b) => [b.dirName, b]));
333
+ const pendingMigrations: MigrationApplyStep[] = [];
334
+ for (const migration of pendingPath) {
335
+ const pkg = bundleByDir.get(migration.dirName);
336
+ if (!pkg) {
337
+ return notOk(
338
+ errorRuntime(`Migration package not found: ${migration.dirName}`, {
339
+ why: `The migration directory for path segment ${migration.from} → ${migration.to} was not found`,
340
+ fix: 'Ensure all migration directories are present and intact.',
341
+ }),
342
+ );
343
+ }
344
+ pendingMigrations.push(packageToStep(pkg));
345
+ }
346
+
347
+ if (!flags.quiet && !flags.json) {
348
+ for (const migration of pendingMigrations) {
349
+ ui.step(`Pending ${migration.dirName}`);
350
+ }
351
+ }
352
+
353
+ const applyResult = await client.migrationApply({
354
+ originHash,
355
+ destinationHash,
356
+ pendingMigrations,
357
+ });
358
+
359
+ if (!applyResult.ok) {
360
+ return notOk(mapApplyFailure(applyResult.failure));
361
+ }
362
+
363
+ const { value } = applyResult;
364
+
365
+ return ok({
366
+ ok: true,
367
+ migrationsApplied: value.migrationsApplied,
368
+ migrationsTotal: pendingPath.length,
369
+ markerHash: value.markerHash,
370
+ applied: value.applied,
371
+ summary: value.summary,
372
+ pathDecision,
373
+ timings: { total: Date.now() - startTime },
374
+ });
375
+ } catch (error) {
376
+ if (CliStructuredError.is(error)) {
377
+ return notOk(error);
378
+ }
379
+ return notOk(
380
+ errorUnexpected(error instanceof Error ? error.message : String(error), {
381
+ why: `Unexpected error during migration apply: ${error instanceof Error ? error.message : String(error)}`,
382
+ }),
383
+ );
384
+ } finally {
385
+ await client.close();
386
+ }
387
+ }
388
+
389
+ export function createMigrationApplyCommand(): Command {
390
+ const command = new Command('apply');
391
+ setCommandDescriptions(
392
+ command,
393
+ 'Apply planned migrations to the database',
394
+ 'Applies previously planned migrations (created by `migration plan`) to a live database.\n' +
395
+ 'Compares the database marker against the migration history to determine which\n' +
396
+ 'migrations are pending, then executes them sequentially. Each migration runs\n' +
397
+ 'in its own transaction. Does not plan new migrations — run `migration plan` first.',
398
+ );
399
+ setCommandExamples(command, ['prisma-next migration apply --db $DATABASE_URL']);
400
+ addGlobalOptions(command)
401
+ .option('--db <url>', 'Database connection string')
402
+ .option('--config <path>', 'Path to prisma-next.config.ts')
403
+ .option('--ref <name>', 'Target ref name from migrations/refs/')
404
+ .action(async (options: MigrationApplyCommandOptions) => {
405
+ const flags = parseGlobalFlags(options);
406
+ const startTime = Date.now();
407
+
408
+ const ui = new TerminalUI({
409
+ color: flags.color,
410
+ interactive: flags.interactive,
411
+ });
412
+
413
+ const result = await executeMigrationApplyCommand(options, flags, ui, startTime);
414
+
415
+ const exitCode = handleResult(result, flags, ui, (applyResult) => {
416
+ if (flags.json) {
417
+ ui.output(JSON.stringify(applyResult, null, 2));
418
+ } else if (!flags.quiet) {
419
+ ui.log(formatMigrationApplyCommandOutput(applyResult, flags));
420
+ }
421
+ });
422
+
423
+ process.exit(exitCode);
424
+ });
425
+
426
+ return command;
427
+ }