@prisma-next/cli 0.5.0-dev.9 → 0.5.1

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 (185) hide show
  1. package/README.md +61 -26
  2. package/dist/cli-errors-B9OBbled.d.mts +3 -0
  3. package/dist/cli-errors-D3_sMh2K.mjs +33 -0
  4. package/dist/cli-errors-D3_sMh2K.mjs.map +1 -0
  5. package/dist/cli.mjs +16 -78
  6. package/dist/cli.mjs.map +1 -1
  7. package/dist/client-BCnP7cHo.mjs +1485 -0
  8. package/dist/client-BCnP7cHo.mjs.map +1 -0
  9. package/dist/{result-handler-Ba3zWQsI.mjs → command-helpers-BeZHkxV8.mjs} +70 -47
  10. package/dist/command-helpers-BeZHkxV8.mjs.map +1 -0
  11. package/dist/commands/contract-emit.d.mts.map +1 -1
  12. package/dist/commands/contract-emit.mjs +2 -4
  13. package/dist/commands/contract-infer.d.mts.map +1 -1
  14. package/dist/commands/contract-infer.mjs +2 -4
  15. package/dist/commands/db-init.d.mts.map +1 -1
  16. package/dist/commands/db-init.mjs +16 -13
  17. package/dist/commands/db-init.mjs.map +1 -1
  18. package/dist/commands/db-schema.d.mts.map +1 -1
  19. package/dist/commands/db-schema.mjs +6 -7
  20. package/dist/commands/db-schema.mjs.map +1 -1
  21. package/dist/commands/db-sign.d.mts.map +1 -1
  22. package/dist/commands/db-sign.mjs +9 -9
  23. package/dist/commands/db-sign.mjs.map +1 -1
  24. package/dist/commands/db-update.d.mts.map +1 -1
  25. package/dist/commands/db-update.mjs +15 -13
  26. package/dist/commands/db-update.mjs.map +1 -1
  27. package/dist/commands/db-verify.d.mts.map +1 -1
  28. package/dist/commands/db-verify.mjs +1 -321
  29. package/dist/commands/migration-apply.d.mts +28 -13
  30. package/dist/commands/migration-apply.d.mts.map +1 -1
  31. package/dist/commands/migration-apply.mjs +55 -151
  32. package/dist/commands/migration-apply.mjs.map +1 -1
  33. package/dist/commands/migration-new.d.mts +0 -1
  34. package/dist/commands/migration-new.d.mts.map +1 -1
  35. package/dist/commands/migration-new.mjs +34 -40
  36. package/dist/commands/migration-new.mjs.map +1 -1
  37. package/dist/commands/migration-plan.d.mts +33 -6
  38. package/dist/commands/migration-plan.d.mts.map +1 -1
  39. package/dist/commands/migration-plan.mjs +2 -348
  40. package/dist/commands/migration-ref.d.mts +1 -1
  41. package/dist/commands/migration-ref.d.mts.map +1 -1
  42. package/dist/commands/migration-ref.mjs +8 -12
  43. package/dist/commands/migration-ref.mjs.map +1 -1
  44. package/dist/commands/migration-show.d.mts +64 -10
  45. package/dist/commands/migration-show.d.mts.map +1 -1
  46. package/dist/commands/migration-show.mjs +166 -60
  47. package/dist/commands/migration-show.mjs.map +1 -1
  48. package/dist/commands/migration-status.d.mts +126 -5
  49. package/dist/commands/migration-status.d.mts.map +1 -1
  50. package/dist/commands/migration-status.mjs +2 -4
  51. package/dist/{config-loader-C25b63rJ.mjs → config-loader-B6sJjXTv.mjs} +3 -5
  52. package/dist/config-loader-B6sJjXTv.mjs.map +1 -0
  53. package/dist/config-loader.d.mts +0 -1
  54. package/dist/config-loader.d.mts.map +1 -1
  55. package/dist/config-loader.mjs +2 -3
  56. package/dist/contract-emit-9DBda5Ou.mjs +150 -0
  57. package/dist/contract-emit-9DBda5Ou.mjs.map +1 -0
  58. package/dist/contract-emit-B77TsJqf.mjs +327 -0
  59. package/dist/contract-emit-B77TsJqf.mjs.map +1 -0
  60. package/dist/{contract-enrichment-CAOELa-H.mjs → contract-enrichment-Dani0mMW.mjs} +4 -6
  61. package/dist/contract-enrichment-Dani0mMW.mjs.map +1 -0
  62. package/dist/{contract-infer-D9cC3rJm.mjs → contract-infer-ByxhPjpW.mjs} +13 -22
  63. package/dist/contract-infer-ByxhPjpW.mjs.map +1 -0
  64. package/dist/contract-space-aggregate-loader-BrwKK6Q6.mjs +160 -0
  65. package/dist/contract-space-aggregate-loader-BrwKK6Q6.mjs.map +1 -0
  66. package/dist/db-verify-Czm5T-J4.mjs +404 -0
  67. package/dist/db-verify-Czm5T-J4.mjs.map +1 -0
  68. package/dist/exports/config-types.mjs +1 -2
  69. package/dist/exports/control-api.d.mts +101 -586
  70. package/dist/exports/control-api.d.mts.map +1 -1
  71. package/dist/exports/control-api.mjs +4 -6
  72. package/dist/exports/index.d.mts.map +1 -1
  73. package/dist/exports/index.mjs +28 -30
  74. package/dist/exports/index.mjs.map +1 -1
  75. package/dist/exports/init-output.d.mts +2 -4
  76. package/dist/exports/init-output.d.mts.map +1 -1
  77. package/dist/exports/init-output.mjs +2 -3
  78. package/dist/{framework-components-Cr--XBKy.mjs → framework-components-ChqVUxR-.mjs} +3 -4
  79. package/dist/{framework-components-Cr--XBKy.mjs.map → framework-components-ChqVUxR-.mjs.map} +1 -1
  80. package/dist/global-flags-Icqpxk23.d.mts +12 -0
  81. package/dist/global-flags-Icqpxk23.d.mts.map +1 -0
  82. package/dist/helpers-eqdN8tH6.mjs +25 -0
  83. package/dist/helpers-eqdN8tH6.mjs.map +1 -0
  84. package/dist/{init-C5220SY9.mjs → init-DETSgw3h.mjs} +40 -49
  85. package/dist/init-DETSgw3h.mjs.map +1 -0
  86. package/dist/{inspect-live-schema-yrHAvG71.mjs → inspect-live-schema-DxdBd4Er.mjs} +10 -11
  87. package/dist/inspect-live-schema-DxdBd4Er.mjs.map +1 -0
  88. package/dist/migration-cli.d.mts +41 -12
  89. package/dist/migration-cli.d.mts.map +1 -1
  90. package/dist/migration-cli.mjs +309 -86
  91. package/dist/migration-cli.mjs.map +1 -1
  92. package/dist/{migration-command-scaffold-B3B09et6.mjs → migration-command-scaffold-BdV8JYXV.mjs} +8 -9
  93. package/dist/migration-command-scaffold-BdV8JYXV.mjs.map +1 -0
  94. package/dist/migration-plan-mRu5K81L.mjs +494 -0
  95. package/dist/migration-plan-mRu5K81L.mjs.map +1 -0
  96. package/dist/{migration-status-DUMiH8_G.mjs → migration-status-By9G5p2H.mjs} +270 -65
  97. package/dist/migration-status-By9G5p2H.mjs.map +1 -0
  98. package/dist/migrations-CTsyBXCA.mjs +229 -0
  99. package/dist/migrations-CTsyBXCA.mjs.map +1 -0
  100. package/dist/{output-BpcQrnnq.mjs → output-B16Kefzx.mjs} +9 -3
  101. package/dist/output-B16Kefzx.mjs.map +1 -0
  102. package/dist/{progress-adapter-DvQWB1nK.mjs → progress-adapter-DFfvZcYL.mjs} +2 -2
  103. package/dist/{progress-adapter-DvQWB1nK.mjs.map → progress-adapter-DFfvZcYL.mjs.map} +1 -1
  104. package/dist/result-handler-rmPVKIP2.mjs +25 -0
  105. package/dist/result-handler-rmPVKIP2.mjs.map +1 -0
  106. package/dist/rolldown-runtime-twds-ZHy.mjs +14 -0
  107. package/dist/{terminal-ui-C3ZLwQxK.mjs → terminal-ui-C_hFNbAn.mjs} +4 -28
  108. package/dist/terminal-ui-C_hFNbAn.mjs.map +1 -0
  109. package/dist/types-LItU7E4l.d.mts +856 -0
  110. package/dist/types-LItU7E4l.d.mts.map +1 -0
  111. package/dist/{verify-Bkycc-Tf.mjs → verify-CiwNWM9N.mjs} +3 -4
  112. package/dist/verify-CiwNWM9N.mjs.map +1 -0
  113. package/package.json +28 -26
  114. package/src/cli.ts +32 -6
  115. package/src/commands/contract-emit.ts +67 -163
  116. package/src/commands/contract-infer.ts +7 -20
  117. package/src/commands/db-init.ts +15 -3
  118. package/src/commands/db-update.ts +9 -4
  119. package/src/commands/db-verify.ts +47 -15
  120. package/src/commands/init/index.ts +1 -1
  121. package/src/commands/init/init.ts +2 -2
  122. package/src/commands/init/templates/code-templates.ts +26 -18
  123. package/src/commands/inspect-live-schema.ts +10 -5
  124. package/src/commands/migration-apply.ts +114 -212
  125. package/src/commands/migration-new.ts +42 -45
  126. package/src/commands/migration-plan.ts +213 -75
  127. package/src/commands/migration-ref.ts +8 -7
  128. package/src/commands/migration-show.ts +274 -70
  129. package/src/commands/migration-status.ts +491 -64
  130. package/src/config-path-validation.ts +0 -1
  131. package/src/control-api/client.ts +85 -5
  132. package/src/control-api/contract-enrichment.ts +6 -4
  133. package/src/control-api/operations/apply-aggregate.ts +290 -0
  134. package/src/control-api/operations/contract-emit.ts +198 -115
  135. package/src/control-api/operations/db-apply-aggregate.ts +399 -0
  136. package/src/control-api/operations/db-init.ts +51 -253
  137. package/src/control-api/operations/db-update.ts +66 -183
  138. package/src/control-api/operations/db-verify.ts +342 -0
  139. package/src/control-api/operations/migration-apply.ts +430 -131
  140. package/src/control-api/types.ts +278 -29
  141. package/src/exports/control-api.ts +15 -3
  142. package/src/load-ts-contract.ts +28 -26
  143. package/src/migration-cli.ts +445 -122
  144. package/src/utils/cli-errors.ts +49 -2
  145. package/src/utils/combine-schema-results.ts +84 -0
  146. package/src/utils/command-helpers.ts +69 -25
  147. package/src/utils/contract-space-aggregate-loader.ts +177 -0
  148. package/src/utils/contract-space-seed-phase.ts +201 -0
  149. package/src/utils/emit-queue.ts +26 -0
  150. package/src/utils/extension-pack-inputs.ts +162 -0
  151. package/src/utils/formatters/graph-migration-mapper.ts +7 -3
  152. package/src/utils/formatters/migrations.ts +255 -77
  153. package/src/utils/publish-contract-artifact-pair.ts +134 -0
  154. package/dist/cli-errors-BFYgBH3L.d.mts +0 -4
  155. package/dist/cli-errors-Cd79vmTH.mjs +0 -5
  156. package/dist/client-CrsnY58k.mjs +0 -997
  157. package/dist/client-CrsnY58k.mjs.map +0 -1
  158. package/dist/commands/db-verify.mjs.map +0 -1
  159. package/dist/commands/migration-plan.mjs.map +0 -1
  160. package/dist/config-loader-C25b63rJ.mjs.map +0 -1
  161. package/dist/contract-emit--feXyNd7.mjs +0 -4
  162. package/dist/contract-emit-NJ01hiiv.mjs +0 -195
  163. package/dist/contract-emit-NJ01hiiv.mjs.map +0 -1
  164. package/dist/contract-emit-V5SSitUT.mjs +0 -122
  165. package/dist/contract-emit-V5SSitUT.mjs.map +0 -1
  166. package/dist/contract-enrichment-CAOELa-H.mjs.map +0 -1
  167. package/dist/contract-infer-D9cC3rJm.mjs.map +0 -1
  168. package/dist/extract-operation-statements-DsFfxXVZ.mjs +0 -13
  169. package/dist/extract-operation-statements-DsFfxXVZ.mjs.map +0 -1
  170. package/dist/extract-sql-ddl-D9UbZDyz.mjs +0 -26
  171. package/dist/extract-sql-ddl-D9UbZDyz.mjs.map +0 -1
  172. package/dist/init-C5220SY9.mjs.map +0 -1
  173. package/dist/inspect-live-schema-yrHAvG71.mjs.map +0 -1
  174. package/dist/migration-command-scaffold-B3B09et6.mjs.map +0 -1
  175. package/dist/migration-status-DUMiH8_G.mjs.map +0 -1
  176. package/dist/migrations-Bo5WtTla.mjs +0 -153
  177. package/dist/migrations-Bo5WtTla.mjs.map +0 -1
  178. package/dist/output-BpcQrnnq.mjs.map +0 -1
  179. package/dist/result-handler-Ba3zWQsI.mjs.map +0 -1
  180. package/dist/terminal-ui-C3ZLwQxK.mjs.map +0 -1
  181. package/dist/validate-contract-deps-B_Cs29TL.mjs +0 -37
  182. package/dist/validate-contract-deps-B_Cs29TL.mjs.map +0 -1
  183. package/dist/verify-Bkycc-Tf.mjs.map +0 -1
  184. package/src/control-api/operations/extract-operation-statements.ts +0 -14
  185. package/src/control-api/operations/extract-sql-ddl.ts +0 -47
@@ -1,8 +1,43 @@
1
- import { green, yellow } from 'colorette';
1
+ import type { OperationPreview } from '@prisma-next/framework-components/control';
2
+ import { cyan, green, yellow } from 'colorette';
2
3
 
4
+ import type { AggregatePerSpaceExecutionEntry } from '../../control-api/types';
3
5
  import type { GlobalFlags } from '../global-flags';
4
6
  import { createColorFormatter, formatDim, isVerbose } from './helpers';
5
7
 
8
+ /**
9
+ * Render a single statement of an `OperationPreview` for the human-readable
10
+ * preview block. SQL statements get a trailing `;` if missing so the rendered
11
+ * preview is byte-identical to the legacy `string[]`-based renderer for SQL
12
+ * targets. Other languages (`'mongodb-shell'`) render verbatim.
13
+ */
14
+ function renderPreviewStatement(text: string, language: string): string | undefined {
15
+ const trimmed = text.trim();
16
+ if (!trimmed) return undefined;
17
+ if (language === 'sql') {
18
+ return trimmed.endsWith(';') ? trimmed : `${trimmed};`;
19
+ }
20
+ return trimmed;
21
+ }
22
+
23
+ /**
24
+ * Choose the header label for a preview block. SQL-only previews keep the
25
+ * legacy `DDL preview` label so the rendered output is byte-identical to the
26
+ * pre-aggregate SQL CLI; previews from any other family — or a mix that
27
+ * includes any non-SQL language — use the family-agnostic `Operation preview`
28
+ * label.
29
+ *
30
+ * An empty `statements` array deliberately renders as `Operation preview`
31
+ * rather than `DDL preview`: `Array.prototype.every` is vacuously true for
32
+ * empty arrays, but we have no evidence the preview is SQL-only when no
33
+ * statements are present, so the family-agnostic label is the safer default.
34
+ */
35
+ export function previewBlockHeader(preview: OperationPreview): string {
36
+ const allSql =
37
+ preview.statements.length > 0 && preview.statements.every((s) => s.language === 'sql');
38
+ return allSql ? 'DDL preview' : 'Operation preview';
39
+ }
40
+
6
41
  // ============================================================================
7
42
  // Migration Command Output Formatters (shared by db init and db update)
8
43
  // ============================================================================
@@ -24,7 +59,12 @@ export interface MigrationCommandResult {
24
59
  readonly label: string;
25
60
  readonly operationClass: string;
26
61
  }[];
27
- readonly sql?: readonly string[];
62
+ /**
63
+ * Family-agnostic textual preview of the planned operations. Replaces the
64
+ * previous `sql?: readonly string[]`. Consumers should read
65
+ * `plan.preview?.statements`.
66
+ */
67
+ readonly preview?: OperationPreview;
28
68
  };
29
69
  readonly execution?: {
30
70
  readonly operationsPlanned: number;
@@ -34,12 +74,67 @@ export interface MigrationCommandResult {
34
74
  readonly storageHash: string;
35
75
  readonly profileHash?: string;
36
76
  };
77
+ /**
78
+ * Per-space execution breakdown in canonical schedule order
79
+ * (extensions alphabetically, then app). Surfaces per-space markers
80
+ * and the ops grouped by space, so the CLI summary can name which
81
+ * space each op and marker belongs to instead of flattening them
82
+ * into a single ambiguous list. See {@link AggregatePerSpaceExecutionEntry}.
83
+ */
84
+ readonly perSpace?: ReadonlyArray<AggregatePerSpaceExecutionEntry>;
37
85
  readonly summary: string;
38
86
  readonly timings: {
39
87
  readonly total: number;
40
88
  };
41
89
  }
42
90
 
91
+ /**
92
+ * Render the shared per-space execution block consumed by the `db init`
93
+ * / `db update` / `migration apply` summaries. Always shows: space
94
+ * label (`Extension space: <id>` or `App space`) → per-op lines under
95
+ * each space → per-space marker hash (when known).
96
+ *
97
+ * `mode` controls the marker label phrasing — `'apply'` shows
98
+ * `marker → <hash>` (post-apply), `'plan'` omits the marker line
99
+ * entirely (no marker has been written yet).
100
+ */
101
+ export function formatPerSpaceBlock(
102
+ perSpace: ReadonlyArray<AggregatePerSpaceExecutionEntry>,
103
+ mode: 'plan' | 'apply',
104
+ useColor: boolean,
105
+ ): readonly string[] {
106
+ const formatYellow = createColorFormatter(useColor, yellow);
107
+ const formatCyan = createColorFormatter(useColor, cyan);
108
+ const formatDimText = (text: string) => formatDim(useColor, text);
109
+
110
+ const lines: string[] = [];
111
+ for (let s = 0; s < perSpace.length; s++) {
112
+ const space = perSpace[s]!;
113
+ if (s > 0) lines.push('');
114
+ const header =
115
+ space.kind === 'app'
116
+ ? formatCyan('App space')
117
+ : formatCyan(`Extension space: ${space.spaceId}`);
118
+ lines.push(header);
119
+ if (space.operations.length === 0) {
120
+ lines.push(` ${formatDimText('(no operations)')}`);
121
+ } else {
122
+ for (let i = 0; i < space.operations.length; i++) {
123
+ const op = space.operations[i]!;
124
+ const isLast = i === space.operations.length - 1;
125
+ const treeChar = isLast ? '└' : '├';
126
+ const destructiveMarker =
127
+ op.operationClass === 'destructive' ? ` ${formatYellow('(destructive)')}` : '';
128
+ lines.push(` ${formatDimText(treeChar)}─ ${op.label}${destructiveMarker}`);
129
+ }
130
+ }
131
+ if (mode === 'apply' && space.marker) {
132
+ lines.push(` ${formatDimText(`marker: ${space.marker.storageHash}`)}`);
133
+ }
134
+ }
135
+ return lines;
136
+ }
137
+
43
138
  /**
44
139
  * Formats human-readable output for migration commands (db init, db update) in plan mode.
45
140
  */
@@ -59,22 +154,43 @@ export function formatMigrationPlanOutput(
59
154
 
60
155
  // Plan summary
61
156
  const operationCount = result.plan?.operations.length ?? 0;
62
- lines.push(`${formatGreen('✔')} Planned ${operationCount} operation(s)`);
157
+ const spaceCount = result.perSpace?.length ?? 0;
158
+ if (spaceCount > 0) {
159
+ lines.push(
160
+ `${formatGreen('✔')} Planned ${operationCount} operation(s) across ${spaceCount} contract space${spaceCount === 1 ? '' : 's'}`,
161
+ );
162
+ } else {
163
+ lines.push(`${formatGreen('✔')} Planned ${operationCount} operation(s)`);
164
+ }
165
+
166
+ const formatYellow = createColorFormatter(useColor, yellow);
63
167
 
64
- // Show operations tree
65
- if (result.plan?.operations && result.plan.operations.length > 0) {
66
- const formatYellow = createColorFormatter(useColor, yellow);
168
+ // Per-space breakdown takes precedence over the flat ops tree when
169
+ // the aggregate flow surfaced one.
170
+ if (result.perSpace && result.perSpace.length > 0) {
171
+ lines.push('');
172
+ lines.push(...formatPerSpaceBlock(result.perSpace, 'plan', useColor));
173
+ const hasDestructive = result.perSpace.some((s) =>
174
+ s.operations.some((op) => op.operationClass === 'destructive'),
175
+ );
176
+ if (hasDestructive) {
177
+ lines.push('');
178
+ lines.push(
179
+ `${formatYellow('⚠')} This migration contains destructive operations that may cause data loss.`,
180
+ );
181
+ }
182
+ } else if (result.plan?.operations && result.plan.operations.length > 0) {
183
+ // Single-space fallback (no aggregate breakdown). Same flat tree
184
+ // we've always rendered.
67
185
  lines.push(`${formatDimText('│')}`);
68
186
  for (let i = 0; i < result.plan.operations.length; i++) {
69
187
  const op = result.plan.operations[i];
70
188
  if (!op) continue;
71
189
  const isLast = i === result.plan.operations.length - 1;
72
190
  const treeChar = isLast ? '└' : '├';
73
- const opClassLabel =
74
- op.operationClass === 'destructive'
75
- ? formatYellow(`[${op.operationClass}]`)
76
- : formatDimText(`[${op.operationClass}]`);
77
- lines.push(`${formatDimText(treeChar)}─ ${op.label} ${opClassLabel}`);
191
+ const destructiveMarker =
192
+ op.operationClass === 'destructive' ? ` ${formatYellow('(destructive)')}` : '';
193
+ lines.push(`${formatDimText(treeChar)}─ ${op.label}${destructiveMarker}`);
78
194
  }
79
195
 
80
196
  const hasDestructive = result.plan.operations.some((op) => op.operationClass === 'destructive');
@@ -92,20 +208,20 @@ export function formatMigrationPlanOutput(
92
208
  lines.push(`${formatDimText(`Destination hash: ${result.plan.destination.storageHash}`)}`);
93
209
  }
94
210
 
95
- // SQL DDL preview (SQL family only)
96
- const planSql = result.plan?.sql;
97
- if (planSql) {
211
+ // Statement preview (any family that implements OperationPreviewCapable)
212
+ const preview = result.plan?.preview;
213
+ if (preview) {
98
214
  lines.push('');
99
- lines.push(`${formatDimText('DDL preview')}`);
100
- if (planSql.length === 0) {
101
- lines.push(`${formatDimText('No DDL operations.')}`);
215
+ lines.push(`${formatDimText(previewBlockHeader(preview))}`);
216
+ if (preview.statements.length === 0) {
217
+ lines.push(`${formatDimText('No operations.')}`);
102
218
  } else {
103
219
  lines.push('');
104
- for (const statement of planSql) {
105
- const trimmed = statement.trim();
106
- if (!trimmed) continue;
107
- const line = trimmed.endsWith(';') ? trimmed : `${trimmed};`;
108
- lines.push(`${line}`);
220
+ for (const statement of preview.statements) {
221
+ const rendered = renderPreviewStatement(statement.text, statement.language);
222
+ if (rendered) {
223
+ lines.push(rendered);
224
+ }
109
225
  }
110
226
  }
111
227
  }
@@ -127,10 +243,20 @@ export interface MigrationApplyCommandOutputResult {
127
243
  readonly migrationsApplied: number;
128
244
  readonly markerHash: string;
129
245
  readonly applied: readonly {
130
- readonly dirName: string;
246
+ readonly spaceId: string;
247
+ readonly dirName?: string;
248
+ readonly migrationHash?: string;
249
+ readonly from?: string;
250
+ readonly to?: string;
131
251
  readonly operationsExecuted: number;
132
252
  }[];
133
253
  readonly summary: string;
254
+ /**
255
+ * Per-space breakdown in canonical schedule order (extensions
256
+ * alphabetically, then app). Always present for the aggregate-walking
257
+ * `migration apply` command.
258
+ */
259
+ readonly perSpace: readonly AggregatePerSpaceExecutionEntry[];
134
260
  readonly timings?: {
135
261
  readonly total: number;
136
262
  };
@@ -149,26 +275,17 @@ export function formatMigrationApplyCommandOutput(
149
275
  const formatGreen = createColorFormatter(useColor, green);
150
276
  const formatDimText = (text: string) => formatDim(useColor, text);
151
277
 
152
- if (result.migrationsApplied === 0) {
153
- lines.push(`${formatGreen('✔')} ${result.summary}`);
154
- lines.push(formatDimText(` marker: ${result.markerHash}`));
155
- return lines.join('\n');
156
- }
157
-
158
278
  lines.push(`${formatGreen('✔')} ${result.summary}`);
159
- lines.push('');
160
279
 
161
- for (let i = 0; i < result.applied.length; i++) {
162
- const migration = result.applied[i]!;
163
- const isLast = i === result.applied.length - 1;
164
- const treeChar = isLast ? '└' : '├';
165
- lines.push(
166
- `${formatDimText(treeChar)}─ ${migration.dirName} ${formatDimText(`[${migration.operationsExecuted} op(s)]`)}`,
167
- );
280
+ if (result.perSpace.length > 0) {
281
+ lines.push('');
282
+ for (const line of formatPerSpaceBlock(result.perSpace, 'apply', useColor)) {
283
+ lines.push(line);
284
+ }
168
285
  }
169
286
 
170
287
  lines.push('');
171
- lines.push(formatDimText(`marker: ${result.markerHash}`));
288
+ lines.push(formatDimText('Next: prisma-next migration status'));
172
289
 
173
290
  if (isVerbose(flags, 1) && result.timings) {
174
291
  lines.push('');
@@ -178,59 +295,66 @@ export function formatMigrationApplyCommandOutput(
178
295
  return lines.join('\n');
179
296
  }
180
297
 
181
- interface MigrationShowResult {
298
+ interface MigrationShowSpacePresent {
299
+ readonly kind: 'present';
300
+ readonly spaceId: string;
182
301
  readonly dirName: string;
183
302
  readonly dirPath: string;
184
- readonly from: string;
303
+ readonly from: string | null;
185
304
  readonly to: string;
186
- readonly migrationId: string;
187
- readonly kind: string;
305
+ readonly migrationHash: string;
188
306
  readonly createdAt: string;
189
307
  readonly operations: readonly {
190
308
  readonly id: string;
191
309
  readonly label: string;
192
310
  readonly operationClass: string;
193
311
  }[];
194
- readonly sql: readonly string[];
312
+ readonly preview: OperationPreview;
195
313
  readonly summary: string;
196
314
  }
197
315
 
198
- export function formatMigrationShowOutput(result: MigrationShowResult, flags: GlobalFlags): string {
199
- if (flags.quiet) {
200
- return '';
201
- }
316
+ interface MigrationShowSpaceMissing {
317
+ readonly kind: 'missing';
318
+ readonly spaceId: string;
319
+ readonly summary: string;
320
+ }
202
321
 
203
- const lines: string[] = [];
322
+ type MigrationShowSpaceResult = MigrationShowSpacePresent | MigrationShowSpaceMissing;
204
323
 
205
- const useColor = flags.color !== false;
324
+ interface MigrationShowResult {
325
+ readonly spaces: readonly MigrationShowSpaceResult[];
326
+ }
327
+
328
+ function formatSpaceShowBlock(
329
+ space: MigrationShowSpacePresent,
330
+ useColor: boolean,
331
+ ): readonly string[] {
206
332
  const formatGreen = createColorFormatter(useColor, green);
207
333
  const formatYellow = createColorFormatter(useColor, yellow);
208
334
  const formatDimText = (text: string) => formatDim(useColor, text);
209
335
 
210
- lines.push(`${formatGreen('✔')} ${result.dirName}`);
211
- lines.push(`${formatDimText(` kind: ${result.kind}`)}`);
212
- lines.push(`${formatDimText(` from: ${result.from}`)}`);
213
- lines.push(`${formatDimText(` to: ${result.to}`)}`);
214
- lines.push(`${formatDimText(` migrationId: ${result.migrationId}`)}`);
215
- lines.push(`${formatDimText(` created: ${result.createdAt}`)}`);
336
+ const lines: string[] = [];
337
+ lines.push(`${formatGreen('✔')} ${space.dirName}`);
338
+ lines.push(`${formatDimText(` from: ${space.from ?? '(baseline)'}`)}`);
339
+ lines.push(`${formatDimText(` to: ${space.to}`)}`);
340
+ lines.push(`${formatDimText(` migrationHash: ${space.migrationHash}`)}`);
341
+ lines.push(`${formatDimText(` created: ${space.createdAt}`)}`);
216
342
 
217
343
  lines.push('');
218
- lines.push(`${result.operations.length} operation(s)`);
344
+ lines.push(`${space.operations.length} operation(s)`);
219
345
 
220
- if (result.operations.length > 0) {
346
+ if (space.operations.length > 0) {
221
347
  lines.push(`${formatDimText('│')}`);
222
- for (let i = 0; i < result.operations.length; i++) {
223
- const op = result.operations[i]!;
224
- const isLast = i === result.operations.length - 1;
348
+ for (let i = 0; i < space.operations.length; i++) {
349
+ const op = space.operations[i]!;
350
+ const isLast = i === space.operations.length - 1;
225
351
  const treeChar = isLast ? '└' : '├';
226
- const opClassLabel =
227
- op.operationClass === 'destructive'
228
- ? formatYellow(`[${op.operationClass}]`)
229
- : formatDimText(`[${op.operationClass}]`);
230
- lines.push(`${formatDimText(treeChar)}─ ${op.label} ${opClassLabel}`);
352
+ const destructiveMarker =
353
+ op.operationClass === 'destructive' ? ` ${formatYellow('(destructive)')}` : '';
354
+ lines.push(`${formatDimText(treeChar)}─ ${op.label}${destructiveMarker}`);
231
355
  }
232
356
 
233
- const hasDestructive = result.operations.some((op) => op.operationClass === 'destructive');
357
+ const hasDestructive = space.operations.some((op) => op.operationClass === 'destructive');
234
358
  if (hasDestructive) {
235
359
  lines.push('');
236
360
  lines.push(
@@ -239,15 +363,45 @@ export function formatMigrationShowOutput(result: MigrationShowResult, flags: Gl
239
363
  }
240
364
  }
241
365
 
242
- if (result.sql.length > 0) {
366
+ if (space.preview.statements.length > 0) {
243
367
  lines.push('');
244
- lines.push(`${formatDimText('DDL preview')}`);
368
+ lines.push(`${formatDimText(previewBlockHeader(space.preview))}`);
245
369
  lines.push('');
246
- for (const statement of result.sql) {
247
- const trimmed = statement.trim();
248
- if (!trimmed) continue;
249
- const line = trimmed.endsWith(';') ? trimmed : `${trimmed};`;
250
- lines.push(`${line}`);
370
+ for (const statement of space.preview.statements) {
371
+ const rendered = renderPreviewStatement(statement.text, statement.language);
372
+ if (rendered) {
373
+ lines.push(rendered);
374
+ }
375
+ }
376
+ }
377
+
378
+ return lines;
379
+ }
380
+
381
+ export function formatMigrationShowOutput(result: MigrationShowResult, flags: GlobalFlags): string {
382
+ if (flags.quiet) {
383
+ return '';
384
+ }
385
+
386
+ const useColor = flags.color !== false;
387
+ const formatDimText = (text: string) => formatDim(useColor, text);
388
+ const multipleSpaces = result.spaces.length > 1;
389
+ const lines: string[] = [];
390
+
391
+ for (let i = 0; i < result.spaces.length; i++) {
392
+ const space = result.spaces[i]!;
393
+ if (multipleSpaces) {
394
+ lines.push(formatDimText(`── ${space.spaceId} ──`));
395
+ }
396
+ if (space.kind === 'missing') {
397
+ lines.push(formatDimText(` ${space.summary}`));
398
+ } else {
399
+ for (const line of formatSpaceShowBlock(space, useColor)) {
400
+ lines.push(line);
401
+ }
402
+ }
403
+ if (i < result.spaces.length - 1) {
404
+ lines.push('');
251
405
  }
252
406
  }
253
407
 
@@ -274,15 +428,39 @@ export function formatMigrationApplyOutput(
274
428
  if (result.ok) {
275
429
  // Success summary
276
430
  const executed = result.execution?.operationsExecuted ?? 0;
431
+ const spaceCount = result.perSpace?.length ?? 0;
432
+
277
433
  if (executed === 0) {
278
- lines.push(`${formatGreen('✔')} Database already matches contract`);
434
+ const acrossClause =
435
+ spaceCount > 0 ? ` across ${spaceCount} contract space${spaceCount === 1 ? '' : 's'}` : '';
436
+ lines.push(`${formatGreen('✔')} Database already matches contract${acrossClause}`);
437
+ } else if (spaceCount > 0) {
438
+ lines.push(
439
+ `${formatGreen('✔')} Applied ${executed} operation(s) across ${spaceCount} contract space${spaceCount === 1 ? '' : 's'}`,
440
+ );
279
441
  } else {
280
442
  lines.push(`${formatGreen('✔')} Applied ${executed} operation(s)`);
281
443
  }
282
444
 
283
- // Marker info
284
- if (result.marker) {
285
- lines.push(`${formatDimText(` Signature: ${result.marker.storageHash}`)}`);
445
+ // Per-space breakdown — replaces the single ambiguous `Signature:`
446
+ // line with a per-space marker + ops listing.
447
+ if (result.perSpace && result.perSpace.length > 0) {
448
+ lines.push('');
449
+ lines.push(...formatPerSpaceBlock(result.perSpace, 'apply', useColor));
450
+ lines.push('');
451
+ lines.push(
452
+ formatDimText(
453
+ `Run 'prisma-next migration status' to confirm ${
454
+ spaceCount === 1 ? 'the space is' : 'all spaces are'
455
+ } up to date.`,
456
+ ),
457
+ );
458
+ } else if (result.marker) {
459
+ // Single-space fallback (no aggregate breakdown surfaced — e.g.
460
+ // older callers / non-aggregate code paths). The label is
461
+ // `App-space marker` (not `Signature`) so that when only one
462
+ // marker is observable we still name what it covers explicitly.
463
+ lines.push(`${formatDimText(` App-space marker: ${result.marker.storageHash}`)}`);
286
464
  if (result.marker.profileHash) {
287
465
  lines.push(`${formatDimText(` Profile hash: ${result.marker.profileHash}`)}`);
288
466
  }
@@ -0,0 +1,134 @@
1
+ import { readFile, rename, rm, writeFile } from 'node:fs/promises';
2
+ import { basename, dirname, join } from 'pathe';
3
+
4
+ function isRecord(value: unknown): value is Record<string, unknown> {
5
+ return typeof value === 'object' && value !== null;
6
+ }
7
+
8
+ function createTempArtifactPath(path: string, publicationToken: string, phase: string): string {
9
+ return join(dirname(path), `.${basename(path)}.${process.pid}.${publicationToken}.${phase}.tmp`);
10
+ }
11
+
12
+ type PreviousArtifact = { readonly content: string } | 'remove';
13
+
14
+ async function readExistingArtifact(path: string): Promise<PreviousArtifact> {
15
+ try {
16
+ return { content: await readFile(path, 'utf-8') };
17
+ } catch (error) {
18
+ if (isRecord(error) && error['code'] === 'ENOENT') {
19
+ return 'remove';
20
+ }
21
+ throw error;
22
+ }
23
+ }
24
+
25
+ async function restoreArtifact(
26
+ path: string,
27
+ previous: PreviousArtifact,
28
+ publicationToken: string,
29
+ ): Promise<void> {
30
+ if (previous === 'remove') {
31
+ await rm(path, { force: true });
32
+ return;
33
+ }
34
+
35
+ const restorePath = createTempArtifactPath(path, publicationToken, 'rollback');
36
+ await writeFile(restorePath, previous.content, 'utf-8');
37
+ try {
38
+ await rename(restorePath, path);
39
+ } finally {
40
+ await rm(restorePath, { force: true });
41
+ }
42
+ }
43
+
44
+ interface PublishEntry {
45
+ readonly tempPath: string;
46
+ readonly outputPath: string;
47
+ readonly previous: PreviousArtifact;
48
+ }
49
+
50
+ function withRollbackFailureCause(error: unknown, rollbackFailures: readonly unknown[]): Error {
51
+ const rollbackCause = new AggregateError(
52
+ rollbackFailures,
53
+ 'Failed to restore published artifacts',
54
+ );
55
+
56
+ if (error instanceof Error) {
57
+ Object.defineProperty(error, 'cause', {
58
+ value: rollbackCause,
59
+ configurable: true,
60
+ writable: true,
61
+ });
62
+ return error;
63
+ }
64
+
65
+ return new Error(String(error), { cause: rollbackCause });
66
+ }
67
+
68
+ async function publishPairWithRollback(
69
+ entries: readonly PublishEntry[],
70
+ publicationToken: string,
71
+ ): Promise<void> {
72
+ const replaced: PublishEntry[] = [];
73
+ try {
74
+ for (const entry of entries) {
75
+ await rename(entry.tempPath, entry.outputPath);
76
+ replaced.push(entry);
77
+ }
78
+ } catch (error) {
79
+ const rollbackResults = await Promise.allSettled(
80
+ replaced.map((entry) => restoreArtifact(entry.outputPath, entry.previous, publicationToken)),
81
+ );
82
+ const rollbackFailures = rollbackResults.flatMap((result) =>
83
+ result.status === 'rejected' ? [result.reason] : [],
84
+ );
85
+
86
+ if (rollbackFailures.length > 0) {
87
+ throw withRollbackFailureCause(error, rollbackFailures);
88
+ }
89
+
90
+ throw error;
91
+ }
92
+ }
93
+
94
+ export async function publishContractArtifactPair({
95
+ outputJsonPath,
96
+ outputDtsPath,
97
+ contractJson,
98
+ contractDts,
99
+ publicationToken,
100
+ beforePublish,
101
+ }: {
102
+ readonly outputJsonPath: string;
103
+ readonly outputDtsPath: string;
104
+ readonly contractJson: string;
105
+ readonly contractDts: string;
106
+ readonly publicationToken: string;
107
+ readonly beforePublish?: () => Promise<boolean> | boolean;
108
+ }): Promise<boolean> {
109
+ const tempJsonPath = createTempArtifactPath(outputJsonPath, publicationToken, 'next');
110
+ const tempDtsPath = createTempArtifactPath(outputDtsPath, publicationToken, 'next');
111
+
112
+ try {
113
+ await writeFile(tempJsonPath, contractJson, 'utf-8');
114
+ await writeFile(tempDtsPath, contractDts, 'utf-8');
115
+
116
+ if ((await beforePublish?.()) === false) {
117
+ return false;
118
+ }
119
+
120
+ const previousJson = await readExistingArtifact(outputJsonPath);
121
+ const previousDts = await readExistingArtifact(outputDtsPath);
122
+
123
+ await publishPairWithRollback(
124
+ [
125
+ { tempPath: tempDtsPath, outputPath: outputDtsPath, previous: previousDts },
126
+ { tempPath: tempJsonPath, outputPath: outputJsonPath, previous: previousJson },
127
+ ],
128
+ publicationToken,
129
+ );
130
+ return true;
131
+ } finally {
132
+ await Promise.allSettled([rm(tempJsonPath, { force: true }), rm(tempDtsPath, { force: true })]);
133
+ }
134
+ }
@@ -1,4 +0,0 @@
1
- import { CliStructuredError as CliStructuredError$1 } from "@prisma-next/errors/control";
2
- import "@prisma-next/errors/execution";
3
- import "@prisma-next/errors/migration";
4
- export { CliStructuredError$1 as t };
@@ -1,5 +0,0 @@
1
- import { CliStructuredError as CliStructuredError$1, errorConfigValidation as errorConfigValidation$1, errorContractConfigMissing as errorContractConfigMissing$1, errorContractValidationFailed, errorDatabaseConnectionRequired, errorDriverRequired, errorFileNotFound, errorMigrationPlanningFailed, errorTargetMigrationNotSupported, errorUnexpected as errorUnexpected$1 } from "@prisma-next/errors/control";
2
- import { ERROR_CODE_DESTRUCTIVE_CHANGES, errorDestructiveChanges, errorHashMismatch, errorMarkerMissing, errorRunnerFailed, errorRuntime as errorRuntime$1, errorTargetMismatch } from "@prisma-next/errors/execution";
3
- import "@prisma-next/errors/migration";
4
-
5
- export { errorUnexpected$1 as _, errorContractValidationFailed as a, errorDriverRequired as c, errorMarkerMissing as d, errorMigrationPlanningFailed as f, errorTargetMismatch as g, errorTargetMigrationNotSupported as h, errorContractConfigMissing$1 as i, errorFileNotFound as l, errorRuntime$1 as m, ERROR_CODE_DESTRUCTIVE_CHANGES as n, errorDatabaseConnectionRequired as o, errorRunnerFailed as p, errorConfigValidation$1 as r, errorDestructiveChanges as s, CliStructuredError$1 as t, errorHashMismatch as u };