@prisma-next/cli 0.5.0-dev.4 → 0.5.0-dev.41

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 (165) hide show
  1. package/README.md +56 -21
  2. package/dist/agent-skill-mongo.md +63 -31
  3. package/dist/agent-skill-postgres.md +1 -1
  4. package/dist/cli-errors-By1iVE3z.mjs +34 -0
  5. package/dist/cli-errors-By1iVE3z.mjs.map +1 -0
  6. package/dist/{cli-errors-C0JhVj0c.d.mts → cli-errors-DDeVsP2Y.d.mts} +1 -0
  7. package/dist/cli.mjs +123 -15
  8. package/dist/cli.mjs.map +1 -1
  9. package/dist/{client-TG7rbCWT.mjs → client-1JqqkiC7.mjs} +45 -20
  10. package/dist/client-1JqqkiC7.mjs.map +1 -0
  11. package/dist/commands/contract-emit.d.mts.map +1 -1
  12. package/dist/commands/contract-emit.mjs +2 -2
  13. package/dist/commands/contract-infer.d.mts.map +1 -1
  14. package/dist/commands/contract-infer.mjs +2 -2
  15. package/dist/commands/db-init.d.mts.map +1 -1
  16. package/dist/commands/db-init.mjs +10 -9
  17. package/dist/commands/db-init.mjs.map +1 -1
  18. package/dist/commands/db-schema.mjs +5 -5
  19. package/dist/commands/db-sign.mjs +7 -7
  20. package/dist/commands/db-update.mjs +9 -9
  21. package/dist/commands/db-update.mjs.map +1 -1
  22. package/dist/commands/db-verify.mjs +9 -9
  23. package/dist/commands/migration-apply.d.mts +5 -2
  24. package/dist/commands/migration-apply.d.mts.map +1 -1
  25. package/dist/commands/migration-apply.mjs +55 -56
  26. package/dist/commands/migration-apply.mjs.map +1 -1
  27. package/dist/commands/migration-new.d.mts.map +1 -1
  28. package/dist/commands/migration-new.mjs +26 -32
  29. package/dist/commands/migration-new.mjs.map +1 -1
  30. package/dist/commands/migration-plan.d.mts +14 -5
  31. package/dist/commands/migration-plan.d.mts.map +1 -1
  32. package/dist/commands/migration-plan.mjs +45 -48
  33. package/dist/commands/migration-plan.mjs.map +1 -1
  34. package/dist/commands/migration-ref.d.mts +1 -1
  35. package/dist/commands/migration-ref.d.mts.map +1 -1
  36. package/dist/commands/migration-ref.mjs +6 -10
  37. package/dist/commands/migration-ref.mjs.map +1 -1
  38. package/dist/commands/migration-show.d.mts +13 -7
  39. package/dist/commands/migration-show.d.mts.map +1 -1
  40. package/dist/commands/migration-show.mjs +27 -29
  41. package/dist/commands/migration-show.mjs.map +1 -1
  42. package/dist/commands/migration-status.d.mts +23 -5
  43. package/dist/commands/migration-status.d.mts.map +1 -1
  44. package/dist/commands/migration-status.mjs +3 -3
  45. package/dist/{config-loader-_W4T21X1.mjs → config-loader-ih8ViDb_.mjs} +2 -2
  46. package/dist/config-loader-ih8ViDb_.mjs.map +1 -0
  47. package/dist/config-loader.mjs +1 -1
  48. package/dist/contract-emit-LjzCoicC.mjs +4 -0
  49. package/dist/contract-emit-RZBWzkop.mjs +329 -0
  50. package/dist/contract-emit-RZBWzkop.mjs.map +1 -0
  51. package/dist/contract-emit-rt_Nmdwq.mjs +150 -0
  52. package/dist/contract-emit-rt_Nmdwq.mjs.map +1 -0
  53. package/dist/{contract-enrichment-CGW6mm-E.mjs → contract-enrichment-4Ptgw3Pe.mjs} +1 -1
  54. package/dist/{contract-enrichment-CGW6mm-E.mjs.map → contract-enrichment-4Ptgw3Pe.mjs.map} +1 -1
  55. package/dist/{contract-infer-BS4kIX9c.mjs → contract-infer-Cf5J2wVg.mjs} +11 -19
  56. package/dist/contract-infer-Cf5J2wVg.mjs.map +1 -0
  57. package/dist/exports/control-api.d.mts +86 -21
  58. package/dist/exports/control-api.d.mts.map +1 -1
  59. package/dist/exports/control-api.mjs +5 -5
  60. package/dist/exports/index.mjs +3 -3
  61. package/dist/exports/init-output.d.mts +39 -0
  62. package/dist/exports/init-output.d.mts.map +1 -0
  63. package/dist/exports/init-output.mjs +3 -0
  64. package/dist/{framework-components-DfZKQBQ2.mjs → framework-components-Bgcre3Z6.mjs} +2 -2
  65. package/dist/{framework-components-DfZKQBQ2.mjs.map → framework-components-Bgcre3Z6.mjs.map} +1 -1
  66. package/dist/init-C7dE9KOJ.mjs +2062 -0
  67. package/dist/init-C7dE9KOJ.mjs.map +1 -0
  68. package/dist/{inspect-live-schema-BsoFVoS1.mjs → inspect-live-schema-LWtXfxm_.mjs} +9 -9
  69. package/dist/inspect-live-schema-LWtXfxm_.mjs.map +1 -0
  70. package/dist/migration-cli.d.mts +41 -11
  71. package/dist/migration-cli.d.mts.map +1 -1
  72. package/dist/migration-cli.mjs +308 -84
  73. package/dist/migration-cli.mjs.map +1 -1
  74. package/dist/{migration-command-scaffold-DOXnheFa.mjs → migration-command-scaffold-CU452v9h.mjs} +7 -7
  75. package/dist/{migration-command-scaffold-DOXnheFa.mjs.map → migration-command-scaffold-CU452v9h.mjs.map} +1 -1
  76. package/dist/{migration-status-Ry3TnEya.mjs → migration-status-DoPrFIOQ.mjs} +114 -57
  77. package/dist/migration-status-DoPrFIOQ.mjs.map +1 -0
  78. package/dist/{migrations-fU0xoKjS.mjs → migrations-MEoKMiV5.mjs} +42 -21
  79. package/dist/migrations-MEoKMiV5.mjs.map +1 -0
  80. package/dist/output-BpcQrnnq.mjs +103 -0
  81. package/dist/output-BpcQrnnq.mjs.map +1 -0
  82. package/dist/{progress-adapter-B-YvmcDu.mjs → progress-adapter-DgRGldpT.mjs} +1 -1
  83. package/dist/{progress-adapter-B-YvmcDu.mjs.map → progress-adapter-DgRGldpT.mjs.map} +1 -1
  84. package/dist/quick-reference-mongo.md +34 -13
  85. package/dist/quick-reference-postgres.md +11 -9
  86. package/dist/{result-handler-BJwA7ufw.mjs → result-handler-Ch6hVnOo.mjs} +35 -93
  87. package/dist/result-handler-Ch6hVnOo.mjs.map +1 -0
  88. package/dist/{terminal-ui-C5k88MmW.mjs → terminal-ui-u2YgKghu.mjs} +76 -2
  89. package/dist/terminal-ui-u2YgKghu.mjs.map +1 -0
  90. package/dist/{verify-bl__PkXk.mjs → verify-BT9tgCOH.mjs} +2 -2
  91. package/dist/{verify-bl__PkXk.mjs.map → verify-BT9tgCOH.mjs.map} +1 -1
  92. package/package.json +22 -16
  93. package/src/cli.ts +32 -6
  94. package/src/commands/contract-emit.ts +67 -163
  95. package/src/commands/contract-infer.ts +7 -20
  96. package/src/commands/db-init.ts +1 -0
  97. package/src/commands/db-update.ts +1 -1
  98. package/src/commands/init/detect-pnpm-catalog.ts +141 -0
  99. package/src/commands/init/errors.ts +254 -0
  100. package/src/commands/init/exit-codes.ts +62 -0
  101. package/src/commands/init/hygiene-gitattributes.ts +97 -0
  102. package/src/commands/init/hygiene-gitignore.ts +48 -0
  103. package/src/commands/init/hygiene-package-scripts.ts +91 -0
  104. package/src/commands/init/index.ts +112 -7
  105. package/src/commands/init/init.ts +766 -144
  106. package/src/commands/init/inputs.ts +421 -0
  107. package/src/commands/init/output.ts +147 -0
  108. package/src/commands/init/probe-db.ts +308 -0
  109. package/src/commands/init/reinit-cleanup.ts +83 -0
  110. package/src/commands/init/templates/agent-skill-mongo.md +63 -31
  111. package/src/commands/init/templates/agent-skill-postgres.md +1 -1
  112. package/src/commands/init/templates/agent-skill.ts +25 -3
  113. package/src/commands/init/templates/code-templates.ts +125 -32
  114. package/src/commands/init/templates/env.ts +80 -0
  115. package/src/commands/init/templates/quick-reference-mongo.md +34 -13
  116. package/src/commands/init/templates/quick-reference-postgres.md +11 -9
  117. package/src/commands/init/templates/quick-reference.ts +42 -3
  118. package/src/commands/init/templates/tsconfig.ts +167 -5
  119. package/src/commands/inspect-live-schema.ts +10 -5
  120. package/src/commands/migration-apply.ts +84 -63
  121. package/src/commands/migration-new.ts +28 -34
  122. package/src/commands/migration-plan.ts +80 -56
  123. package/src/commands/migration-ref.ts +8 -7
  124. package/src/commands/migration-show.ts +53 -36
  125. package/src/commands/migration-status.ts +194 -58
  126. package/src/config-path-validation.ts +0 -1
  127. package/src/control-api/client.ts +21 -0
  128. package/src/control-api/operations/contract-emit.ts +198 -115
  129. package/src/control-api/operations/db-init.ts +10 -6
  130. package/src/control-api/operations/db-update.ts +10 -6
  131. package/src/control-api/operations/migration-apply.ts +30 -9
  132. package/src/control-api/types.ts +69 -7
  133. package/src/exports/control-api.ts +2 -1
  134. package/src/exports/init-output.ts +10 -0
  135. package/src/migration-cli.ts +445 -122
  136. package/src/utils/cli-errors.ts +49 -2
  137. package/src/utils/command-helpers.ts +45 -23
  138. package/src/utils/emit-queue.ts +26 -0
  139. package/src/utils/formatters/graph-migration-mapper.ts +7 -3
  140. package/src/utils/formatters/migrations.ts +62 -26
  141. package/src/utils/publish-contract-artifact-pair.ts +134 -0
  142. package/dist/cli-errors-DHq6GQGu.mjs +0 -5
  143. package/dist/client-TG7rbCWT.mjs.map +0 -1
  144. package/dist/config-loader-_W4T21X1.mjs.map +0 -1
  145. package/dist/contract-emit-CQfj7xJn.mjs +0 -122
  146. package/dist/contract-emit-CQfj7xJn.mjs.map +0 -1
  147. package/dist/contract-emit-DpPjuFy-.mjs +0 -195
  148. package/dist/contract-emit-DpPjuFy-.mjs.map +0 -1
  149. package/dist/contract-emit-fhNwwhkQ.mjs +0 -4
  150. package/dist/contract-infer-BS4kIX9c.mjs.map +0 -1
  151. package/dist/extract-operation-statements-DZUJNmL3.mjs +0 -13
  152. package/dist/extract-operation-statements-DZUJNmL3.mjs.map +0 -1
  153. package/dist/extract-sql-ddl-DDMX-9mz.mjs +0 -26
  154. package/dist/extract-sql-ddl-DDMX-9mz.mjs.map +0 -1
  155. package/dist/init-CQfo_4Ro.mjs +0 -430
  156. package/dist/init-CQfo_4Ro.mjs.map +0 -1
  157. package/dist/inspect-live-schema-BsoFVoS1.mjs.map +0 -1
  158. package/dist/migration-status-Ry3TnEya.mjs.map +0 -1
  159. package/dist/migrations-fU0xoKjS.mjs.map +0 -1
  160. package/dist/result-handler-BJwA7ufw.mjs.map +0 -1
  161. package/dist/terminal-ui-C5k88MmW.mjs.map +0 -1
  162. package/dist/validate-contract-deps-esa-VQ0h.mjs +0 -37
  163. package/dist/validate-contract-deps-esa-VQ0h.mjs.map +0 -1
  164. package/src/control-api/operations/extract-operation-statements.ts +0 -14
  165. package/src/control-api/operations/extract-sql-ddl.ts +0 -47
@@ -3,23 +3,25 @@ import type { Contract } from '@prisma-next/contract/types';
3
3
  import { getEmittedArtifactPaths } from '@prisma-next/emitter';
4
4
  import {
5
5
  createControlStack,
6
+ hasOperationPreview,
6
7
  type MigrationPlanOperation,
8
+ type OperationPreview,
7
9
  } from '@prisma-next/framework-components/control';
8
- import { computeMigrationId } from '@prisma-next/migration-tools/attestation';
9
- import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
10
- import { findLatestMigration } from '@prisma-next/migration-tools/dag';
10
+ import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
11
+ import { computeMigrationHash } from '@prisma-next/migration-tools/hash';
12
+ import { deriveProvidedInvariants } from '@prisma-next/migration-tools/invariants';
11
13
  import {
12
14
  copyFilesWithRename,
13
15
  formatMigrationDirName,
14
16
  writeMigrationPackage,
15
17
  } from '@prisma-next/migration-tools/io';
18
+ import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
19
+ import { findLatestMigration } from '@prisma-next/migration-tools/migration-graph';
16
20
  import { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';
17
- import { type MigrationManifest, MigrationToolsError } from '@prisma-next/migration-tools/types';
18
21
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
19
22
  import { Command } from 'commander';
20
23
  import { join, relative } from 'pathe';
21
24
  import { loadConfig } from '../config-loader';
22
- import { extractSqlDdl } from '../control-api/operations/extract-sql-ddl';
23
25
  import {
24
26
  type CliErrorConflict,
25
27
  CliStructuredError,
@@ -29,11 +31,12 @@ import {
29
31
  errorRuntime,
30
32
  errorTargetMigrationNotSupported,
31
33
  errorUnexpected,
34
+ mapMigrationToolsError,
32
35
  } from '../utils/cli-errors';
33
36
  import {
34
37
  addGlobalOptions,
35
38
  getTargetMigrations,
36
- loadAllBundles,
39
+ loadMigrationPackages,
37
40
  resolveContractPath,
38
41
  resolveMigrationPaths,
39
42
  setCommandDescriptions,
@@ -55,7 +58,7 @@ interface MigrationPlanOptions extends CommonCommandOptions {
55
58
  export interface MigrationPlanResult {
56
59
  readonly ok: boolean;
57
60
  readonly noOp: boolean;
58
- readonly from: string;
61
+ readonly from: string | null;
59
62
  readonly to: string;
60
63
  readonly dir?: string;
61
64
  readonly operations: readonly {
@@ -63,7 +66,12 @@ export interface MigrationPlanResult {
63
66
  readonly label: string;
64
67
  readonly operationClass: string;
65
68
  }[];
66
- readonly sql?: readonly string[];
69
+ /**
70
+ * Family-agnostic textual preview of the migration plan operations.
71
+ * Replaces the previous `sql?: readonly string[]` field; consumers should
72
+ * read `result.preview?.statements`.
73
+ */
74
+ readonly preview?: OperationPreview;
67
75
  readonly summary: string;
68
76
  /**
69
77
  * When true, `migration.ts` was written but contains unfilled
@@ -76,22 +84,6 @@ export interface MigrationPlanResult {
76
84
  };
77
85
  }
78
86
 
79
- function mapMigrationToolsError(error: unknown): CliStructuredError {
80
- if (CliStructuredError.is(error)) {
81
- return error;
82
- }
83
- if (MigrationToolsError.is(error)) {
84
- return errorRuntime(error.message, {
85
- why: error.why,
86
- fix: error.fix,
87
- meta: { code: error.code, ...(error.details ?? {}) },
88
- });
89
- }
90
- return errorUnexpected(error instanceof Error ? error.message : String(error), {
91
- why: `Unexpected error during migration plan: ${error instanceof Error ? error.message : String(error)}`,
92
- });
93
- }
94
-
95
87
  async function executeMigrationPlanCommand(
96
88
  options: MigrationPlanOptions,
97
89
  flags: GlobalFlags,
@@ -173,11 +165,11 @@ async function executeMigrationPlanCommand(
173
165
 
174
166
  // Read existing migrations and determine "from" contract
175
167
  let fromContract: Contract | null = null;
176
- let fromHash: string = EMPTY_CONTRACT_HASH;
168
+ let fromHash: string | null = null;
177
169
  let fromContractSourceDir: string | null = null;
178
170
 
179
171
  try {
180
- const { bundles, graph } = await loadAllBundles(migrationsDir);
172
+ const { bundles, graph } = await loadMigrationPackages(migrationsDir);
181
173
 
182
174
  if (options.from) {
183
175
  const resolved = resolveBundleByPrefix(bundles, options.from);
@@ -195,16 +187,18 @@ async function executeMigrationPlanCommand(
195
187
  }),
196
188
  );
197
189
  }
198
- fromHash = resolved.value.manifest.to;
199
- fromContract = resolved.value.manifest.toContract;
190
+ fromHash = resolved.value.metadata.to;
191
+ fromContract = resolved.value.metadata.toContract;
200
192
  fromContractSourceDir = resolved.value.dirPath;
201
193
  } else {
202
194
  const latestMigration = findLatestMigration(graph);
203
195
  if (latestMigration) {
204
196
  fromHash = latestMigration.to;
205
- const leafPkg = bundles.find((p) => p.manifest.migrationId === latestMigration.migrationId);
197
+ const leafPkg = bundles.find(
198
+ (p) => p.metadata.migrationHash === latestMigration.migrationHash,
199
+ );
206
200
  if (leafPkg) {
207
- fromContract = leafPkg.manifest.toContract;
201
+ fromContract = leafPkg.metadata.toContract;
208
202
  fromContractSourceDir = leafPkg.dirPath;
209
203
  }
210
204
  }
@@ -213,7 +207,16 @@ async function executeMigrationPlanCommand(
213
207
  if (MigrationToolsError.is(error)) {
214
208
  return notOk(mapMigrationToolsError(error));
215
209
  }
216
- throw error;
210
+ // Wrap unexpected (non-MigrationToolsError) failures from the migration
211
+ // load phase in a structured CLI envelope. Letting them throw would
212
+ // bypass `handleResult()` and crash the command — see CLI structured-
213
+ // errors guideline (CliStructuredError + Result pattern).
214
+ const message = error instanceof Error ? error.message : String(error);
215
+ return notOk(
216
+ errorUnexpected(message, {
217
+ why: `Unexpected error while loading migrations: ${message}`,
218
+ }),
219
+ );
217
220
  }
218
221
 
219
222
  // Check for no-op (same hash means no changes)
@@ -251,10 +254,9 @@ async function executeMigrationPlanCommand(
251
254
  const dirName = formatMigrationDirName(timestamp, slug);
252
255
  const packageDir = join(migrationsDir, dirName);
253
256
 
254
- const baseManifest: Omit<MigrationManifest, 'migrationId'> = {
257
+ const baseMetadata: Omit<MigrationMetadata, 'migrationHash' | 'providedInvariants'> = {
255
258
  from: fromHash,
256
259
  to: toStorageHash,
257
- kind: 'regular',
258
260
  fromContract,
259
261
  toContract: toContractJson,
260
262
  hints: {
@@ -275,7 +277,6 @@ async function executeMigrationPlanCommand(
275
277
  contract: toContractJson,
276
278
  schema: fromSchema,
277
279
  policy: { allowedOperationClasses: ['additive', 'widening', 'destructive', 'data'] },
278
- fromHash,
279
280
  fromContract,
280
281
  frameworkComponents,
281
282
  });
@@ -320,18 +321,22 @@ async function executeMigrationPlanCommand(
320
321
 
321
322
  const migrationTsContent = plannerResult.plan.renderTypeScript();
322
323
 
323
- // Always-attest: compute migrationId over (manifest, ops). When
324
- // placeholders blocked lowering, ops is `[]` and the id hashes over
325
- // the empty list — re-emitting after the user fills the placeholder
326
- // produces a different id (over the real ops). This is intentional;
324
+ // Always-attest: compute migrationHash over (metadata, ops). When
325
+ // placeholders blocked lowering, ops is `[]` and the hash is computed
326
+ // over the empty list — re-emitting after the user fills the placeholder
327
+ // produces a different hash (over the real ops). This is intentional;
327
328
  // there is no on-disk "draft" state.
328
329
  const opsForWrite = hasPlaceholders ? [] : plannedOps;
329
- const manifest: MigrationManifest = {
330
- ...baseManifest,
331
- migrationId: computeMigrationId(baseManifest, opsForWrite),
330
+ const metadataWithInvariants: Omit<MigrationMetadata, 'migrationHash'> = {
331
+ ...baseMetadata,
332
+ providedInvariants: deriveProvidedInvariants(opsForWrite),
333
+ };
334
+ const metadata: MigrationMetadata = {
335
+ ...metadataWithInvariants,
336
+ migrationHash: computeMigrationHash(metadataWithInvariants, opsForWrite),
332
337
  };
333
338
 
334
- await writeMigrationPackage(packageDir, manifest, opsForWrite);
339
+ await writeMigrationPackage(packageDir, metadata, opsForWrite);
335
340
  const destinationArtifacts = getEmittedArtifactPaths(contractPathAbsolute);
336
341
  await copyFilesWithRename(packageDir, [
337
342
  { sourcePath: destinationArtifacts.jsonPath, destName: 'end-contract.json' },
@@ -364,7 +369,9 @@ async function executeMigrationPlanCommand(
364
369
  return ok(result);
365
370
  }
366
371
 
367
- const sql = extractSqlDdl(plannedOps);
372
+ const preview = hasOperationPreview(familyInstance)
373
+ ? familyInstance.toOperationPreview(plannedOps)
374
+ : undefined;
368
375
  const result: MigrationPlanResult = {
369
376
  ok: true,
370
377
  noOp: false,
@@ -376,13 +383,24 @@ async function executeMigrationPlanCommand(
376
383
  label: op.label,
377
384
  operationClass: op.operationClass,
378
385
  })),
379
- sql,
386
+ ...(preview !== undefined ? { preview } : {}),
380
387
  summary: `Planned ${plannedOps.length} operation(s)`,
381
388
  timings: { total: Date.now() - startTime },
382
389
  };
383
390
  return ok(result);
384
391
  } catch (error) {
385
- return notOk(mapMigrationToolsError(error));
392
+ if (CliStructuredError.is(error)) {
393
+ return notOk(error);
394
+ }
395
+ if (MigrationToolsError.is(error)) {
396
+ return notOk(mapMigrationToolsError(error));
397
+ }
398
+ const message = error instanceof Error ? error.message : String(error);
399
+ return notOk(
400
+ errorUnexpected(message, {
401
+ why: `Unexpected error during migration plan: ${message}`,
402
+ }),
403
+ );
386
404
  }
387
405
  }
388
406
 
@@ -489,17 +507,20 @@ function formatMigrationPlanOutput(result: MigrationPlanResult, flags: GlobalFla
489
507
 
490
508
  lines.push('');
491
509
  lines.push(
492
- `Next: ${green_(`node ${result.dir ?? '<dir>'}/migration.ts`)} to emit ops.json and attest migrationId before running ${green_('prisma-next migration apply')}.`,
510
+ `Next: review ${green_(result.dir ?? '<dir>')} if needed, then run ${green_('prisma-next migration apply')}.`,
493
511
  );
494
512
 
495
- if (result.sql && result.sql.length > 0) {
513
+ if (result.preview && result.preview.statements.length > 0) {
514
+ // The non-empty length is already guaranteed by the surrounding check, so
515
+ // a plain `every` here is equivalent to the helper in formatters/migrations.ts.
516
+ const allSql = result.preview.statements.every((s) => s.language === 'sql');
496
517
  lines.push('');
497
- lines.push(dim_('DDL preview'));
518
+ lines.push(dim_(allSql ? 'DDL preview' : 'Operation preview'));
498
519
  lines.push('');
499
- for (const statement of result.sql) {
500
- const trimmed = statement.trim();
520
+ for (const statement of result.preview.statements) {
521
+ const trimmed = statement.text.trim();
501
522
  if (!trimmed) continue;
502
- const line = trimmed.endsWith(';') ? trimmed : `${trimmed};`;
523
+ const line = statement.language === 'sql' && !trimmed.endsWith(';') ? `${trimmed};` : trimmed;
503
524
  lines.push(line);
504
525
  }
505
526
  }
@@ -517,24 +538,27 @@ export type PrefixResolutionFailure =
517
538
  | { reason: 'not-found' };
518
539
 
519
540
  /**
520
- * Resolve a migration bundle by exact hash or prefix match.
541
+ * Resolve a migration package by **target contract hash** (`metadata.to`)
542
+ * using exact match or prefix match.
521
543
  *
544
+ * Note: matches `metadata.to` (the contract hash this migration produces),
545
+ * not `metadata.migrationHash` (the package's content-addressed identity).
522
546
  * Tries exact match first, then prefix match (auto-prepending `sha256:` when
523
- * the needle omits the scheme). Returns the matched bundle on success, or a
547
+ * the needle omits the scheme). Returns the matched package on success, or a
524
548
  * discriminated failure indicating whether the prefix was ambiguous or simply
525
549
  * not found.
526
550
  *
527
551
  * @internal Exported for testing only.
528
552
  */
529
- export function resolveBundleByPrefix<T extends { manifest: { to: string } }>(
553
+ export function resolveBundleByPrefix<T extends { metadata: { to: string } }>(
530
554
  bundles: readonly T[],
531
555
  needle: string,
532
556
  ): Result<T, PrefixResolutionFailure> {
533
- const exact = bundles.find((p) => p.manifest.to === needle);
557
+ const exact = bundles.find((p) => p.metadata.to === needle);
534
558
  if (exact) return ok(exact);
535
559
 
536
560
  const prefixWithScheme = needle.startsWith('sha256:') ? needle : `sha256:${needle}`;
537
- const candidates = bundles.filter((p) => p.manifest.to.startsWith(prefixWithScheme));
561
+ const candidates = bundles.filter((p) => p.metadata.to.startsWith(prefixWithScheme));
538
562
 
539
563
  if (candidates.length === 1) return ok(candidates[0]!);
540
564
  if (candidates.length > 1) return notOk({ reason: 'ambiguous', count: candidates.length });
@@ -1,3 +1,4 @@
1
+ import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
1
2
  import type { RefEntry } from '@prisma-next/migration-tools/refs';
2
3
  import {
3
4
  deleteRef,
@@ -7,11 +8,15 @@ import {
7
8
  validateRefValue,
8
9
  writeRef,
9
10
  } from '@prisma-next/migration-tools/refs';
10
- import { MigrationToolsError } from '@prisma-next/migration-tools/types';
11
11
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
12
12
  import { Command } from 'commander';
13
13
  import { loadConfig } from '../config-loader';
14
- import { CliStructuredError, errorRuntime, errorUnexpected } from '../utils/cli-errors';
14
+ import {
15
+ CliStructuredError,
16
+ errorRuntime,
17
+ errorUnexpected,
18
+ mapMigrationToolsError,
19
+ } from '../utils/cli-errors';
15
20
  import {
16
21
  addGlobalOptions,
17
22
  resolveMigrationPaths,
@@ -49,11 +54,7 @@ interface RefListResult {
49
54
 
50
55
  function mapError(error: unknown): CliStructuredError {
51
56
  if (MigrationToolsError.is(error)) {
52
- return errorRuntime(error.message, {
53
- why: error.why,
54
- fix: error.fix,
55
- meta: { code: error.code },
56
- });
57
+ return mapMigrationToolsError(error);
57
58
  }
58
59
  return errorUnexpected(error instanceof Error ? error.message : String(error));
59
60
  }
@@ -1,14 +1,25 @@
1
- import type { MigrationPlanOperation } from '@prisma-next/framework-components/control';
2
- import { findLatestMigration, reconstructGraph } from '@prisma-next/migration-tools/dag';
1
+ import type {
2
+ MigrationPlanOperation,
3
+ OperationPreview,
4
+ } from '@prisma-next/framework-components/control';
5
+ import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
3
6
  import { readMigrationPackage, readMigrationsDir } from '@prisma-next/migration-tools/io';
4
- import type { MigrationBundle } from '@prisma-next/migration-tools/types';
5
- import { MigrationToolsError } from '@prisma-next/migration-tools/types';
7
+ import {
8
+ findLatestMigration,
9
+ reconstructGraph,
10
+ } from '@prisma-next/migration-tools/migration-graph';
11
+ import type { MigrationPackage } from '@prisma-next/migration-tools/package';
6
12
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
7
13
  import { Command } from 'commander';
8
14
  import { relative, resolve } from 'pathe';
9
15
  import { loadConfig } from '../config-loader';
10
- import { extractOperationStatements } from '../control-api/operations/extract-operation-statements';
11
- import { type CliStructuredError, errorRuntime, errorUnexpected } from '../utils/cli-errors';
16
+ import { createControlClient } from '../control-api/client';
17
+ import {
18
+ type CliStructuredError,
19
+ errorRuntime,
20
+ errorUnexpected,
21
+ mapMigrationToolsError,
22
+ } from '../utils/cli-errors';
12
23
  import {
13
24
  addGlobalOptions,
14
25
  setCommandDescriptions,
@@ -29,17 +40,22 @@ export interface MigrationShowResult {
29
40
  readonly ok: true;
30
41
  readonly dirName: string;
31
42
  readonly dirPath: string;
32
- readonly from: string;
43
+ readonly from: string | null;
33
44
  readonly to: string;
34
- readonly migrationId: string;
35
- readonly kind: string;
45
+ readonly migrationHash: string;
36
46
  readonly createdAt: string;
37
47
  readonly operations: readonly {
38
48
  readonly id: string;
39
49
  readonly label: string;
40
50
  readonly operationClass: string;
41
51
  }[];
42
- readonly sql: readonly string[];
52
+ /**
53
+ * Family-agnostic textual preview of the migration's operations. Replaces
54
+ * the previous string-array DDL field. Always defined; statements is empty
55
+ * for a no-op migration or a family that does not implement the
56
+ * `OperationPreviewCapable` capability.
57
+ */
58
+ readonly preview: OperationPreview;
43
59
  readonly summary: string;
44
60
  }
45
61
 
@@ -48,11 +64,11 @@ function looksLikePath(target: string): boolean {
48
64
  }
49
65
 
50
66
  export function resolveByHashPrefix(
51
- packages: readonly MigrationBundle[],
67
+ packages: readonly MigrationPackage[],
52
68
  prefix: string,
53
- ): Result<MigrationBundle, CliStructuredError> {
69
+ ): Result<MigrationPackage, CliStructuredError> {
54
70
  const normalizedPrefix = prefix.startsWith('sha256:') ? prefix : `sha256:${prefix}`;
55
- const matches = packages.filter((p) => p.manifest.migrationId.startsWith(normalizedPrefix));
71
+ const matches = packages.filter((p) => p.metadata.migrationHash.startsWith(normalizedPrefix));
56
72
 
57
73
  if (matches.length === 1) {
58
74
  return ok(matches[0]!);
@@ -61,13 +77,13 @@ export function resolveByHashPrefix(
61
77
  if (matches.length === 0) {
62
78
  return notOk(
63
79
  errorRuntime('No migration found matching prefix', {
64
- why: `No migration has a migrationId starting with "${normalizedPrefix}"`,
80
+ why: `No migration has a migrationHash starting with "${normalizedPrefix}"`,
65
81
  fix: 'Run `prisma-next migration show` (no argument) to see the latest migration, or check the migrations directory for available packages.',
66
82
  }),
67
83
  );
68
84
  }
69
85
 
70
- const candidates = matches.map((p) => ` ${p.dirName} ${p.manifest.migrationId}`).join('\n');
86
+ const candidates = matches.map((p) => ` ${p.dirName} ${p.metadata.migrationHash}`).join('\n');
71
87
  return notOk(
72
88
  errorRuntime('Ambiguous hash prefix', {
73
89
  why: `Multiple migrations match prefix "${normalizedPrefix}":\n${candidates}`,
@@ -110,7 +126,7 @@ async function executeMigrationShowCommand(
110
126
  ui.stderr(header);
111
127
  }
112
128
 
113
- let pkg: MigrationBundle;
129
+ let pkg: MigrationPackage;
114
130
 
115
131
  try {
116
132
  if (target && looksLikePath(target)) {
@@ -142,7 +158,7 @@ async function executeMigrationShowCommand(
142
158
  );
143
159
  }
144
160
  const leafPkg = allPackages.find(
145
- (p) => p.manifest.migrationId === latestMigration.migrationId,
161
+ (p) => p.metadata.migrationHash === latestMigration.migrationHash,
146
162
  );
147
163
  if (!leafPkg) {
148
164
  return notOk(
@@ -157,13 +173,7 @@ async function executeMigrationShowCommand(
157
173
  }
158
174
  } catch (error) {
159
175
  if (MigrationToolsError.is(error)) {
160
- return notOk(
161
- errorRuntime(error.message, {
162
- why: error.why,
163
- fix: error.fix,
164
- meta: { code: error.code, ...(error.details ?? {}) },
165
- }),
166
- );
176
+ return notOk(mapMigrationToolsError(error));
167
177
  }
168
178
  return notOk(
169
179
  errorUnexpected(error instanceof Error ? error.message : String(error), {
@@ -173,23 +183,33 @@ async function executeMigrationShowCommand(
173
183
  }
174
184
 
175
185
  const ops = pkg.ops as readonly MigrationPlanOperation[];
176
- const sql = extractOperationStatements(config.family.familyId, ops) ?? [];
186
+
187
+ // `migration show` is an offline command; the control client is constructed
188
+ // purely to dispatch the family-specific `toOperationPreview` capability and
189
+ // is not connected to a database.
190
+ const client = createControlClient({
191
+ family: config.family,
192
+ target: config.target,
193
+ adapter: config.adapter,
194
+ ...(config.driver ? { driver: config.driver } : {}),
195
+ extensionPacks: config.extensionPacks ?? [],
196
+ });
197
+ const preview: OperationPreview = client.toOperationPreview(ops) ?? { statements: [] };
177
198
 
178
199
  const result: MigrationShowResult = {
179
200
  ok: true,
180
201
  dirName: pkg.dirName,
181
202
  dirPath: relative(process.cwd(), pkg.dirPath),
182
- from: pkg.manifest.from,
183
- to: pkg.manifest.to,
184
- migrationId: pkg.manifest.migrationId,
185
- kind: pkg.manifest.kind,
186
- createdAt: pkg.manifest.createdAt,
203
+ from: pkg.metadata.from,
204
+ to: pkg.metadata.to,
205
+ migrationHash: pkg.metadata.migrationHash,
206
+ createdAt: pkg.metadata.createdAt,
187
207
  operations: ops.map((op) => ({
188
208
  id: op.id,
189
209
  label: op.label,
190
210
  operationClass: op.operationClass,
191
211
  })),
192
- sql,
212
+ preview,
193
213
  summary: `${ops.length} operation(s)`,
194
214
  };
195
215
  return ok(result);
@@ -200,7 +220,7 @@ export function createMigrationShowCommand(): Command {
200
220
  setCommandDescriptions(
201
221
  command,
202
222
  'Display migration package contents',
203
- 'Shows the operations, DDL preview, and metadata for a migration package.\n' +
223
+ 'Shows the operations, statement preview, and metadata for a migration package.\n' +
204
224
  'Accepts a directory path, a hash prefix (git-style), or defaults to the\n' +
205
225
  'latest migration.',
206
226
  );
@@ -209,10 +229,7 @@ export function createMigrationShowCommand(): Command {
209
229
  'prisma-next migration show sha256:a1b2c3',
210
230
  ]);
211
231
  addGlobalOptions(command)
212
- .argument(
213
- '[target]',
214
- 'Migration directory path or migrationId hash prefix (defaults to latest)',
215
- )
232
+ .argument('[target]', 'Migration directory path or migrationHash prefix (defaults to latest)')
216
233
  .option('--config <path>', 'Path to prisma-next.config.ts')
217
234
  .action(async (target: string | undefined, options: MigrationShowOptions) => {
218
235
  const flags = parseGlobalFlags(options);