@prisma-next/cli 0.4.1 → 0.4.3

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 (170) hide show
  1. package/README.md +56 -26
  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-DDeVsP2Y.d.mts +5 -0
  7. package/dist/cli.mjs +131 -15
  8. package/dist/cli.mjs.map +1 -1
  9. package/dist/{client-DGKrciLM.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 +7 -2
  13. package/dist/commands/contract-infer.d.mts.map +1 -1
  14. package/dist/commands/contract-infer.mjs +8 -2
  15. package/dist/commands/db-init.d.mts.map +1 -1
  16. package/dist/commands/db-init.mjs +11 -9
  17. package/dist/commands/db-init.mjs.map +1 -1
  18. package/dist/commands/db-schema.mjs +8 -5
  19. package/dist/commands/db-schema.mjs.map +1 -1
  20. package/dist/commands/db-sign.mjs +8 -7
  21. package/dist/commands/db-sign.mjs.map +1 -1
  22. package/dist/commands/db-update.mjs +10 -9
  23. package/dist/commands/db-update.mjs.map +1 -1
  24. package/dist/commands/db-verify.mjs +10 -9
  25. package/dist/commands/db-verify.mjs.map +1 -1
  26. package/dist/commands/migration-apply.d.mts +5 -2
  27. package/dist/commands/migration-apply.d.mts.map +1 -1
  28. package/dist/commands/migration-apply.mjs +57 -57
  29. package/dist/commands/migration-apply.mjs.map +1 -1
  30. package/dist/commands/migration-new.d.mts.map +1 -1
  31. package/dist/commands/migration-new.mjs +26 -32
  32. package/dist/commands/migration-new.mjs.map +1 -1
  33. package/dist/commands/migration-plan.d.mts +14 -5
  34. package/dist/commands/migration-plan.d.mts.map +1 -1
  35. package/dist/commands/migration-plan.mjs +45 -48
  36. package/dist/commands/migration-plan.mjs.map +1 -1
  37. package/dist/commands/migration-ref.d.mts +6 -4
  38. package/dist/commands/migration-ref.d.mts.map +1 -1
  39. package/dist/commands/migration-ref.mjs +31 -40
  40. package/dist/commands/migration-ref.mjs.map +1 -1
  41. package/dist/commands/migration-show.d.mts +13 -7
  42. package/dist/commands/migration-show.d.mts.map +1 -1
  43. package/dist/commands/migration-show.mjs +28 -29
  44. package/dist/commands/migration-show.mjs.map +1 -1
  45. package/dist/commands/migration-status.d.mts +23 -5
  46. package/dist/commands/migration-status.d.mts.map +1 -1
  47. package/dist/commands/migration-status.mjs +8 -3
  48. package/dist/{config-loader-_xQZsw0i.mjs → config-loader-ih8ViDb_.mjs} +2 -2
  49. package/dist/config-loader-ih8ViDb_.mjs.map +1 -0
  50. package/dist/config-loader.mjs +1 -1
  51. package/dist/contract-emit-DS5NzZh2.mjs +6 -0
  52. package/dist/contract-emit-RZBWzkop.mjs +329 -0
  53. package/dist/contract-emit-RZBWzkop.mjs.map +1 -0
  54. package/dist/contract-emit-rt_Nmdwq.mjs +150 -0
  55. package/dist/contract-emit-rt_Nmdwq.mjs.map +1 -0
  56. package/dist/{contract-enrichment-BV4KpbNW.mjs → contract-enrichment-4Ptgw3Pe.mjs} +1 -1
  57. package/dist/{contract-enrichment-BV4KpbNW.mjs.map → contract-enrichment-4Ptgw3Pe.mjs.map} +1 -1
  58. package/dist/{contract-infer-CUbiWGX0.mjs → contract-infer-Cf5J2wVg.mjs} +11 -19
  59. package/dist/contract-infer-Cf5J2wVg.mjs.map +1 -0
  60. package/dist/exports/control-api.d.mts +86 -21
  61. package/dist/exports/control-api.d.mts.map +1 -1
  62. package/dist/exports/control-api.mjs +7 -5
  63. package/dist/exports/index.mjs +8 -3
  64. package/dist/exports/index.mjs.map +1 -1
  65. package/dist/exports/init-output.d.mts +39 -0
  66. package/dist/exports/init-output.d.mts.map +1 -0
  67. package/dist/exports/init-output.mjs +3 -0
  68. package/dist/{framework-components-B__p--vT.mjs → framework-components-Bgcre3Z6.mjs} +2 -2
  69. package/dist/{framework-components-B__p--vT.mjs.map → framework-components-Bgcre3Z6.mjs.map} +1 -1
  70. package/dist/init-DAbQMxIR.mjs +2062 -0
  71. package/dist/init-DAbQMxIR.mjs.map +1 -0
  72. package/dist/{inspect-live-schema-wIYBTdL3.mjs → inspect-live-schema-LWtXfxm_.mjs} +9 -9
  73. package/dist/inspect-live-schema-LWtXfxm_.mjs.map +1 -0
  74. package/dist/migration-cli.d.mts +80 -0
  75. package/dist/migration-cli.d.mts.map +1 -0
  76. package/dist/migration-cli.mjs +408 -0
  77. package/dist/migration-cli.mjs.map +1 -0
  78. package/dist/{migration-command-scaffold-BC73xQSo.mjs → migration-command-scaffold-CU452v9h.mjs} +7 -7
  79. package/dist/{migration-command-scaffold-BC73xQSo.mjs.map → migration-command-scaffold-CU452v9h.mjs.map} +1 -1
  80. package/dist/{migration-status-CXBbScH5.mjs → migration-status-DoPrFIOQ.mjs} +119 -64
  81. package/dist/migration-status-DoPrFIOQ.mjs.map +1 -0
  82. package/dist/{migrations-DYRAjiVh.mjs → migrations-MEoKMiV5.mjs} +42 -21
  83. package/dist/migrations-MEoKMiV5.mjs.map +1 -0
  84. package/dist/output-BpcQrnnq.mjs +103 -0
  85. package/dist/output-BpcQrnnq.mjs.map +1 -0
  86. package/dist/{progress-adapter-Bwouy73-.mjs → progress-adapter-DgRGldpT.mjs} +1 -1
  87. package/dist/{progress-adapter-Bwouy73-.mjs.map → progress-adapter-DgRGldpT.mjs.map} +1 -1
  88. package/dist/quick-reference-mongo.md +34 -13
  89. package/dist/quick-reference-postgres.md +11 -9
  90. package/dist/{result-handler-CGohaH1o.mjs → result-handler-Ch6hVnOo.mjs} +36 -94
  91. package/dist/result-handler-Ch6hVnOo.mjs.map +1 -0
  92. package/dist/{terminal-ui-BuPXVRFY.mjs → terminal-ui-u2YgKghu.mjs} +76 -2
  93. package/dist/terminal-ui-u2YgKghu.mjs.map +1 -0
  94. package/dist/{verify-Cm2UFuZA.mjs → verify-BT9tgCOH.mjs} +2 -2
  95. package/dist/{verify-Cm2UFuZA.mjs.map → verify-BT9tgCOH.mjs.map} +1 -1
  96. package/package.json +27 -17
  97. package/src/cli.ts +32 -6
  98. package/src/commands/contract-emit.ts +67 -163
  99. package/src/commands/contract-infer.ts +7 -20
  100. package/src/commands/db-init.ts +1 -0
  101. package/src/commands/db-update.ts +1 -1
  102. package/src/commands/init/detect-pnpm-catalog.ts +141 -0
  103. package/src/commands/init/errors.ts +254 -0
  104. package/src/commands/init/exit-codes.ts +62 -0
  105. package/src/commands/init/hygiene-gitattributes.ts +97 -0
  106. package/src/commands/init/hygiene-gitignore.ts +48 -0
  107. package/src/commands/init/hygiene-package-scripts.ts +91 -0
  108. package/src/commands/init/index.ts +112 -7
  109. package/src/commands/init/init.ts +766 -144
  110. package/src/commands/init/inputs.ts +421 -0
  111. package/src/commands/init/output.ts +147 -0
  112. package/src/commands/init/probe-db.ts +308 -0
  113. package/src/commands/init/reinit-cleanup.ts +83 -0
  114. package/src/commands/init/templates/agent-skill-mongo.md +63 -31
  115. package/src/commands/init/templates/agent-skill-postgres.md +1 -1
  116. package/src/commands/init/templates/agent-skill.ts +25 -3
  117. package/src/commands/init/templates/code-templates.ts +125 -32
  118. package/src/commands/init/templates/env.ts +80 -0
  119. package/src/commands/init/templates/quick-reference-mongo.md +34 -13
  120. package/src/commands/init/templates/quick-reference-postgres.md +11 -9
  121. package/src/commands/init/templates/quick-reference.ts +42 -3
  122. package/src/commands/init/templates/tsconfig.ts +167 -5
  123. package/src/commands/inspect-live-schema.ts +10 -5
  124. package/src/commands/migration-apply.ts +86 -65
  125. package/src/commands/migration-new.ts +28 -34
  126. package/src/commands/migration-plan.ts +80 -56
  127. package/src/commands/migration-ref.ts +40 -54
  128. package/src/commands/migration-show.ts +53 -36
  129. package/src/commands/migration-status.ts +202 -71
  130. package/src/config-path-validation.ts +0 -1
  131. package/src/control-api/client.ts +21 -0
  132. package/src/control-api/operations/contract-emit.ts +198 -115
  133. package/src/control-api/operations/db-init.ts +10 -6
  134. package/src/control-api/operations/db-update.ts +10 -6
  135. package/src/control-api/operations/migration-apply.ts +30 -9
  136. package/src/control-api/types.ts +69 -7
  137. package/src/exports/control-api.ts +2 -1
  138. package/src/exports/init-output.ts +10 -0
  139. package/src/migration-cli.ts +577 -0
  140. package/src/utils/cli-errors.ts +50 -2
  141. package/src/utils/command-helpers.ts +48 -26
  142. package/src/utils/emit-queue.ts +26 -0
  143. package/src/utils/formatters/graph-migration-mapper.ts +7 -3
  144. package/src/utils/formatters/migrations.ts +62 -26
  145. package/src/utils/publish-contract-artifact-pair.ts +134 -0
  146. package/dist/cli-errors-CznZA5-d.mjs +0 -5
  147. package/dist/cli-errors-z37sV3eR.d.mts +0 -4
  148. package/dist/client-DGKrciLM.mjs.map +0 -1
  149. package/dist/config-loader-_xQZsw0i.mjs.map +0 -1
  150. package/dist/contract-emit-304WZtZJ.mjs +0 -4
  151. package/dist/contract-emit-DgeWdonT.mjs +0 -122
  152. package/dist/contract-emit-DgeWdonT.mjs.map +0 -1
  153. package/dist/contract-emit-mU1_B_m9.mjs +0 -195
  154. package/dist/contract-emit-mU1_B_m9.mjs.map +0 -1
  155. package/dist/contract-infer-CUbiWGX0.mjs.map +0 -1
  156. package/dist/extract-operation-statements-DWWFz1PK.mjs +0 -13
  157. package/dist/extract-operation-statements-DWWFz1PK.mjs.map +0 -1
  158. package/dist/extract-sql-ddl-7zn_AFS8.mjs +0 -26
  159. package/dist/extract-sql-ddl-7zn_AFS8.mjs.map +0 -1
  160. package/dist/init-DRquYpPa.mjs +0 -430
  161. package/dist/init-DRquYpPa.mjs.map +0 -1
  162. package/dist/inspect-live-schema-wIYBTdL3.mjs.map +0 -1
  163. package/dist/migration-status-CXBbScH5.mjs.map +0 -1
  164. package/dist/migrations-DYRAjiVh.mjs.map +0 -1
  165. package/dist/result-handler-CGohaH1o.mjs.map +0 -1
  166. package/dist/terminal-ui-BuPXVRFY.mjs.map +0 -1
  167. package/dist/validate-contract-deps-DZqv9m7H.mjs +0 -37
  168. package/dist/validate-contract-deps-DZqv9m7H.mjs.map +0 -1
  169. package/src/control-api/operations/extract-operation-statements.ts +0 -14
  170. 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,17 +1,27 @@
1
+ import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
2
+ import type { RefEntry } from '@prisma-next/migration-tools/refs';
1
3
  import {
4
+ deleteRef,
5
+ readRef,
2
6
  readRefs,
3
- resolveRef,
4
7
  validateRefName,
5
8
  validateRefValue,
6
- writeRefs,
9
+ writeRef,
7
10
  } from '@prisma-next/migration-tools/refs';
8
- import { MigrationToolsError } from '@prisma-next/migration-tools/types';
9
11
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
10
12
  import { Command } from 'commander';
11
- import { resolve } from 'pathe';
12
13
  import { loadConfig } from '../config-loader';
13
- import { CliStructuredError, errorRuntime, errorUnexpected } from '../utils/cli-errors';
14
- import { addGlobalOptions, setCommandDescriptions } from '../utils/command-helpers';
14
+ import {
15
+ CliStructuredError,
16
+ errorRuntime,
17
+ errorUnexpected,
18
+ mapMigrationToolsError,
19
+ } from '../utils/cli-errors';
20
+ import {
21
+ addGlobalOptions,
22
+ resolveMigrationPaths,
23
+ setCommandDescriptions,
24
+ } from '../utils/command-helpers';
15
25
  import { formatCommandHelp } from '../utils/formatters/help';
16
26
  import { parseGlobalFlags } from '../utils/global-flags';
17
27
  import { handleResult } from '../utils/result-handler';
@@ -21,12 +31,14 @@ interface RefSetResult {
21
31
  readonly ok: true;
22
32
  readonly ref: string;
23
33
  readonly hash: string;
34
+ readonly invariants: readonly string[];
24
35
  }
25
36
 
26
37
  interface RefGetResult {
27
38
  readonly ok: true;
28
39
  readonly ref: string;
29
40
  readonly hash: string;
41
+ readonly invariants: readonly string[];
30
42
  }
31
43
 
32
44
  interface RefDeleteResult {
@@ -37,21 +49,12 @@ interface RefDeleteResult {
37
49
 
38
50
  interface RefListResult {
39
51
  readonly ok: true;
40
- readonly refs: Record<string, string>;
41
- }
42
-
43
- function resolveRefsPath(configPath?: string, config?: { migrations?: { dir?: string } }): string {
44
- const base = configPath ? resolve(configPath, '..') : process.cwd();
45
- return resolve(base, config?.migrations?.dir ?? 'migrations', 'refs.json');
52
+ readonly refs: Record<string, RefEntry>;
46
53
  }
47
54
 
48
55
  function mapError(error: unknown): CliStructuredError {
49
56
  if (MigrationToolsError.is(error)) {
50
- return errorRuntime(error.message, {
51
- why: error.why,
52
- fix: error.fix,
53
- meta: { code: error.code },
54
- });
57
+ return mapMigrationToolsError(error);
55
58
  }
56
59
  return errorUnexpected(error instanceof Error ? error.message : String(error));
57
60
  }
@@ -70,13 +73,6 @@ function cliErrorInvalidRefValue(hash: string): CliStructuredError {
70
73
  });
71
74
  }
72
75
 
73
- function errorRefNotFound(name: string): CliStructuredError {
74
- return errorRuntime(`Ref "${name}" does not exist`, {
75
- why: `No ref named "${name}" found in refs.json`,
76
- fix: `Run \`prisma-next migration ref list\` to see available refs, or \`prisma-next migration ref set ${name} <hash>\` to create it`,
77
- });
78
- }
79
-
80
76
  async function executeRefSetCommand(
81
77
  name: string,
82
78
  hash: string,
@@ -91,11 +87,10 @@ async function executeRefSetCommand(
91
87
 
92
88
  try {
93
89
  const config = await loadConfig(options.config);
94
- const refsPath = resolveRefsPath(options.config, config);
95
- const refs = await readRefs(refsPath);
96
- const updated = { ...refs, [name]: hash };
97
- await writeRefs(refsPath, updated);
98
- return ok({ ok: true as const, ref: name, hash });
90
+ const { refsDir } = resolveMigrationPaths(options.config, config);
91
+ const entry: RefEntry = { hash, invariants: [] };
92
+ await writeRef(refsDir, name, entry);
93
+ return ok({ ok: true as const, ref: name, hash, invariants: [] });
99
94
  } catch (error) {
100
95
  if (error instanceof CliStructuredError) return notOk(error);
101
96
  return notOk(mapError(error));
@@ -108,10 +103,9 @@ async function executeRefGetCommand(
108
103
  ): Promise<Result<RefGetResult, CliStructuredError>> {
109
104
  try {
110
105
  const config = await loadConfig(options.config);
111
- const refsPath = resolveRefsPath(options.config, config);
112
- const refs = await readRefs(refsPath);
113
- const hash = resolveRef(refs, name);
114
- return ok({ ok: true as const, ref: name, hash });
106
+ const { refsDir } = resolveMigrationPaths(options.config, config);
107
+ const entry = await readRef(refsDir, name);
108
+ return ok({ ok: true as const, ref: name, hash: entry.hash, invariants: entry.invariants });
115
109
  } catch (error) {
116
110
  if (error instanceof CliStructuredError) return notOk(error);
117
111
  return notOk(mapError(error));
@@ -124,13 +118,8 @@ async function executeRefDeleteCommand(
124
118
  ): Promise<Result<RefDeleteResult, CliStructuredError>> {
125
119
  try {
126
120
  const config = await loadConfig(options.config);
127
- const refsPath = resolveRefsPath(options.config, config);
128
- const refs = await readRefs(refsPath);
129
- if (!Object.hasOwn(refs, name)) {
130
- return notOk(errorRefNotFound(name));
131
- }
132
- const { [name]: _, ...remaining } = refs;
133
- await writeRefs(refsPath, remaining);
121
+ const { refsDir } = resolveMigrationPaths(options.config, config);
122
+ await deleteRef(refsDir, name);
134
123
  return ok({ ok: true as const, ref: name, deleted: true as const });
135
124
  } catch (error) {
136
125
  if (error instanceof CliStructuredError) return notOk(error);
@@ -143,8 +132,8 @@ async function executeRefListCommand(options: {
143
132
  }): Promise<Result<RefListResult, CliStructuredError>> {
144
133
  try {
145
134
  const config = await loadConfig(options.config);
146
- const refsPath = resolveRefsPath(options.config, config);
147
- const refs = await readRefs(refsPath);
135
+ const { refsDir } = resolveMigrationPaths(options.config, config);
136
+ const refs = await readRefs(refsDir);
148
137
  return ok({ ok: true as const, refs });
149
138
  } catch (error) {
150
139
  if (error instanceof CliStructuredError) return notOk(error);
@@ -157,7 +146,7 @@ function createRefSetCommand(): Command {
157
146
  setCommandDescriptions(
158
147
  command,
159
148
  'Set a ref to a contract hash',
160
- 'Sets a named ref to point to a contract hash in migrations/refs.json.',
149
+ 'Sets a named ref to point to a contract hash in migrations/refs/.',
161
150
  );
162
151
  addGlobalOptions(command)
163
152
  .argument('<name>', 'Ref name (e.g., staging, production)')
@@ -190,7 +179,7 @@ function createRefGetCommand(): Command {
190
179
  setCommandDescriptions(
191
180
  command,
192
181
  'Get the hash for a ref',
193
- 'Reads a named ref from migrations/refs.json and prints its contract hash.',
182
+ 'Reads a named ref from migrations/refs/ and prints its contract hash.',
194
183
  );
195
184
  addGlobalOptions(command)
196
185
  .argument('<name>', 'Ref name to look up')
@@ -218,7 +207,7 @@ function createRefGetCommand(): Command {
218
207
 
219
208
  function createRefDeleteCommand(): Command {
220
209
  const command = new Command('delete');
221
- setCommandDescriptions(command, 'Delete a ref', 'Removes a named ref from migrations/refs.json.');
210
+ setCommandDescriptions(command, 'Delete a ref', 'Removes a named ref from migrations/refs/.');
222
211
  addGlobalOptions(command)
223
212
  .argument('<name>', 'Ref name to delete')
224
213
  .option('--config <path>', 'Path to prisma-next.config.ts')
@@ -245,11 +234,7 @@ function createRefDeleteCommand(): Command {
245
234
 
246
235
  function createRefListCommand(): Command {
247
236
  const command = new Command('list');
248
- setCommandDescriptions(
249
- command,
250
- 'List all refs',
251
- 'Lists all named refs from migrations/refs.json.',
252
- );
237
+ setCommandDescriptions(command, 'List all refs', 'Lists all named refs from migrations/refs/.');
253
238
  addGlobalOptions(command)
254
239
  .option('--config <path>', 'Path to prisma-next.config.ts')
255
240
  .action(async (options: { config?: string; json?: string | boolean; quiet?: boolean }) => {
@@ -264,8 +249,10 @@ function createRefListCommand(): Command {
264
249
  if (entries.length === 0) {
265
250
  ui.output('No refs defined');
266
251
  } else {
267
- for (const [refName, hash] of entries) {
268
- ui.output(`${refName} ${hash}`);
252
+ for (const [refName, entry] of entries) {
253
+ const invariantsSuffix =
254
+ entry.invariants.length > 0 ? ` [invariants: ${entry.invariants.join(', ')}]` : '';
255
+ ui.output(`${refName} → ${entry.hash}${invariantsSuffix}`);
269
256
  }
270
257
  }
271
258
  }
@@ -282,7 +269,6 @@ export {
282
269
  executeRefListCommand,
283
270
  cliErrorInvalidRefName,
284
271
  cliErrorInvalidRefValue,
285
- errorRefNotFound,
286
272
  };
287
273
 
288
274
  export function createMigrationRefCommand(): Command {
@@ -290,7 +276,7 @@ export function createMigrationRefCommand(): Command {
290
276
  setCommandDescriptions(
291
277
  command,
292
278
  'Manage migration refs',
293
- 'Manage named refs in migrations/refs.json. Refs map logical environment\n' +
279
+ 'Manage named refs in migrations/refs/. Refs map logical environment\n' +
294
280
  'names (e.g., staging, production) to contract hashes.',
295
281
  );
296
282
  addGlobalOptions(command).configureHelp({