@prisma-next/cli 0.11.0-dev.5 → 0.11.0-dev.50

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/dist/cli-errors-DFF1LlfU.mjs +215 -0
  2. package/dist/cli-errors-DFF1LlfU.mjs.map +1 -0
  3. package/dist/cli.mjs +9 -10
  4. package/dist/cli.mjs.map +1 -1
  5. package/dist/{client-oXO2WCPD.mjs → client-5uvDppD8.mjs} +23 -21
  6. package/dist/client-5uvDppD8.mjs.map +1 -0
  7. package/dist/{command-helpers-DtavI0wJ.mjs → command-helpers-4UNsRRc4.mjs} +427 -9
  8. package/dist/command-helpers-4UNsRRc4.mjs.map +1 -0
  9. package/dist/commands/contract-emit.d.mts.map +1 -1
  10. package/dist/commands/contract-emit.mjs +1 -1
  11. package/dist/commands/contract-infer.d.mts.map +1 -1
  12. package/dist/commands/contract-infer.mjs +1 -1
  13. package/dist/commands/db-init.d.mts.map +1 -1
  14. package/dist/commands/db-init.mjs +33 -7
  15. package/dist/commands/db-init.mjs.map +1 -1
  16. package/dist/commands/db-schema.d.mts.map +1 -1
  17. package/dist/commands/db-schema.mjs +3 -4
  18. package/dist/commands/db-schema.mjs.map +1 -1
  19. package/dist/commands/db-sign.d.mts.map +1 -1
  20. package/dist/commands/db-sign.mjs +6 -7
  21. package/dist/commands/db-sign.mjs.map +1 -1
  22. package/dist/commands/db-update.d.mts.map +1 -1
  23. package/dist/commands/db-update.mjs +36 -8
  24. package/dist/commands/db-update.mjs.map +1 -1
  25. package/dist/commands/db-verify.d.mts.map +1 -1
  26. package/dist/commands/db-verify.mjs +1 -1
  27. package/dist/commands/migrate.d.mts +5 -1
  28. package/dist/commands/migrate.d.mts.map +1 -1
  29. package/dist/commands/migrate.mjs +79 -39
  30. package/dist/commands/migrate.mjs.map +1 -1
  31. package/dist/commands/migration-check.d.mts +4 -3
  32. package/dist/commands/migration-check.d.mts.map +1 -1
  33. package/dist/commands/migration-check.mjs +1 -280
  34. package/dist/commands/migration-graph.d.mts +1 -1
  35. package/dist/commands/migration-graph.d.mts.map +1 -1
  36. package/dist/commands/migration-graph.mjs +3 -4
  37. package/dist/commands/migration-graph.mjs.map +1 -1
  38. package/dist/commands/migration-list.d.mts +63 -12
  39. package/dist/commands/migration-list.d.mts.map +1 -1
  40. package/dist/commands/migration-list.mjs +2 -103
  41. package/dist/commands/migration-log.d.mts.map +1 -1
  42. package/dist/commands/migration-log.mjs +3 -4
  43. package/dist/commands/migration-log.mjs.map +1 -1
  44. package/dist/commands/migration-new.d.mts.map +1 -1
  45. package/dist/commands/migration-new.mjs +33 -38
  46. package/dist/commands/migration-new.mjs.map +1 -1
  47. package/dist/commands/migration-plan.d.mts +2 -1
  48. package/dist/commands/migration-plan.d.mts.map +1 -1
  49. package/dist/commands/migration-plan.mjs +1 -1
  50. package/dist/commands/migration-show.d.mts +4 -55
  51. package/dist/commands/migration-show.d.mts.map +1 -1
  52. package/dist/commands/migration-show.mjs +62 -153
  53. package/dist/commands/migration-show.mjs.map +1 -1
  54. package/dist/commands/migration-status.d.mts +5 -40
  55. package/dist/commands/migration-status.d.mts.map +1 -1
  56. package/dist/commands/migration-status.mjs +93 -67
  57. package/dist/commands/migration-status.mjs.map +1 -1
  58. package/dist/commands/ref.d.mts +1 -1
  59. package/dist/commands/ref.d.mts.map +1 -1
  60. package/dist/commands/ref.mjs +34 -9
  61. package/dist/commands/ref.mjs.map +1 -1
  62. package/dist/config-loader-B6sJjXTv.mjs.map +1 -1
  63. package/dist/config-loader.d.mts.map +1 -1
  64. package/dist/{contract-emit-o-8VmdQX.mjs → contract-emit-C-CFGZsI.mjs} +9 -6
  65. package/dist/{contract-emit-o-8VmdQX.mjs.map → contract-emit-C-CFGZsI.mjs.map} +1 -1
  66. package/dist/{contract-emit-CmsklifJ.mjs → contract-emit-CuUzzM46.mjs} +5 -6
  67. package/dist/{contract-emit-CmsklifJ.mjs.map → contract-emit-CuUzzM46.mjs.map} +1 -1
  68. package/dist/{contract-enrichment-Dani0mMW.mjs → contract-enrichment-XmUPhmsS.mjs} +4 -25
  69. package/dist/contract-enrichment-XmUPhmsS.mjs.map +1 -0
  70. package/dist/{contract-infer-pKkiCt7C.mjs → contract-infer-C98ZaRhp.mjs} +3 -4
  71. package/dist/{contract-infer-pKkiCt7C.mjs.map → contract-infer-C98ZaRhp.mjs.map} +1 -1
  72. package/dist/contract-space-aggregate-loader-CVHGuA35.mjs +170 -0
  73. package/dist/contract-space-aggregate-loader-CVHGuA35.mjs.map +1 -0
  74. package/dist/{db-verify-AoIUriL4.mjs → db-verify-BWl1Yxi-.mjs} +6 -7
  75. package/dist/{db-verify-AoIUriL4.mjs.map → db-verify-BWl1Yxi-.mjs.map} +1 -1
  76. package/dist/exports/control-api.d.mts +1 -1
  77. package/dist/exports/control-api.d.mts.map +1 -1
  78. package/dist/exports/control-api.mjs +3 -3
  79. package/dist/exports/index.d.mts.map +1 -1
  80. package/dist/exports/index.mjs +1 -1
  81. package/dist/exports/index.mjs.map +1 -1
  82. package/dist/exports/init-output.d.mts.map +1 -1
  83. package/dist/exports/init-output.mjs +1 -1
  84. package/dist/extension-pack-inputs-BiY86HbQ.mjs +62 -0
  85. package/dist/extension-pack-inputs-BiY86HbQ.mjs.map +1 -0
  86. package/dist/{framework-components-65gOHkHB.mjs → framework-components-DTcjouhS.mjs} +2 -2
  87. package/dist/{framework-components-65gOHkHB.mjs.map → framework-components-DTcjouhS.mjs.map} +1 -1
  88. package/dist/{global-flags-CdE7M0d9.d.mts → global-flags-DWsQ6SSI.d.mts} +1 -1
  89. package/dist/global-flags-DWsQ6SSI.d.mts.map +1 -0
  90. package/dist/glyph-mode-CBB4emzO.d.mts +5 -0
  91. package/dist/glyph-mode-CBB4emzO.d.mts.map +1 -0
  92. package/dist/{graph-render-DJVv0_uf.mjs → graph-render-D2FnLpuK.mjs} +1 -1
  93. package/dist/{graph-render-DJVv0_uf.mjs.map → graph-render-D2FnLpuK.mjs.map} +1 -1
  94. package/dist/{init-Db5Itt5r.mjs → init-C7PvN163.mjs} +5 -5
  95. package/dist/{init-Db5Itt5r.mjs.map → init-C7PvN163.mjs.map} +1 -1
  96. package/dist/{inspect-live-schema-LeWvkZVz.mjs → inspect-live-schema-BRCWQ-Sr.mjs} +5 -5
  97. package/dist/{inspect-live-schema-LeWvkZVz.mjs.map → inspect-live-schema-BRCWQ-Sr.mjs.map} +1 -1
  98. package/dist/migration-check-DoskM1nB.mjs +341 -0
  99. package/dist/migration-check-DoskM1nB.mjs.map +1 -0
  100. package/dist/migration-cli.d.mts.map +1 -1
  101. package/dist/migration-cli.mjs +4 -4
  102. package/dist/migration-cli.mjs.map +1 -1
  103. package/dist/{migration-command-scaffold-BtkunvFQ.mjs → migration-command-scaffold-CXLkoIJx.mjs} +5 -5
  104. package/dist/{migration-command-scaffold-BtkunvFQ.mjs.map → migration-command-scaffold-CXLkoIJx.mjs.map} +1 -1
  105. package/dist/migration-list-B2-iQ5Jd.mjs +646 -0
  106. package/dist/migration-list-B2-iQ5Jd.mjs.map +1 -0
  107. package/dist/{migration-plan-C2jeH1J5.mjs → migration-plan-BqmIKQpZ.mjs} +341 -88
  108. package/dist/migration-plan-BqmIKQpZ.mjs.map +1 -0
  109. package/dist/{migration-types-BXWvz12q.d.mts → migration-types-q64xAI_J.d.mts} +1 -1
  110. package/dist/{migration-types-BXWvz12q.d.mts.map → migration-types-q64xAI_J.d.mts.map} +1 -1
  111. package/dist/{migrations-CwZMa1Ck.mjs → migrations-BcVTutso.mjs} +12 -13
  112. package/dist/migrations-BcVTutso.mjs.map +1 -0
  113. package/dist/{output-BlsrGMEF.mjs → output-B60Gw5fu.mjs} +1 -1
  114. package/dist/{output-BlsrGMEF.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
  115. package/dist/{progress-adapter-DFfvZcYL.mjs → progress-adapter-xASh41wr.mjs} +1 -1
  116. package/dist/{progress-adapter-DFfvZcYL.mjs.map → progress-adapter-xASh41wr.mjs.map} +1 -1
  117. package/dist/ref-advancement-DRh5Nquq.mjs +50 -0
  118. package/dist/ref-advancement-DRh5Nquq.mjs.map +1 -0
  119. package/dist/{types-C9FfXb1l.d.mts → types-CEtm6v6a.d.mts} +5 -11
  120. package/dist/types-CEtm6v6a.d.mts.map +1 -0
  121. package/dist/{verify-Bom75OYI.mjs → verify-DOHbbrub.mjs} +2 -2
  122. package/dist/{verify-Bom75OYI.mjs.map → verify-DOHbbrub.mjs.map} +1 -1
  123. package/package.json +20 -20
  124. package/src/commands/db-init.ts +48 -2
  125. package/src/commands/db-update.ts +45 -0
  126. package/src/commands/migrate.ts +120 -40
  127. package/src/commands/migration-check.ts +43 -83
  128. package/src/commands/migration-list.ts +173 -74
  129. package/src/commands/migration-new.ts +44 -48
  130. package/src/commands/migration-plan.ts +359 -128
  131. package/src/commands/migration-show.ts +65 -284
  132. package/src/commands/migration-status.ts +131 -99
  133. package/src/commands/ref.ts +46 -6
  134. package/src/control-api/client.ts +0 -1
  135. package/src/control-api/contract-enrichment.ts +6 -42
  136. package/src/control-api/operations/contract-emit.ts +7 -2
  137. package/src/control-api/operations/db-verify.ts +9 -5
  138. package/src/control-api/operations/migration-apply.ts +11 -19
  139. package/src/control-api/types.ts +0 -7
  140. package/src/migration-cli.ts +4 -4
  141. package/src/utils/cli-errors.ts +224 -0
  142. package/src/utils/command-helpers.ts +9 -4
  143. package/src/utils/contract-space-aggregate-loader.ts +221 -117
  144. package/src/utils/formatters/migration-list-data-column.ts +115 -0
  145. package/src/utils/formatters/migration-list-graph-layout.ts +268 -0
  146. package/src/utils/formatters/migration-list-graph-render.ts +314 -0
  147. package/src/utils/formatters/migration-list-render.ts +194 -0
  148. package/src/utils/formatters/migration-list-styler.ts +61 -0
  149. package/src/utils/formatters/migrations.ts +29 -38
  150. package/src/utils/glyph-mode.ts +22 -0
  151. package/src/utils/integrity-violation-to-check-failure.ts +130 -0
  152. package/src/utils/plan-resolution.ts +257 -0
  153. package/src/utils/ref-advancement.ts +68 -0
  154. package/src/utils/terminal-ui.ts +42 -1
  155. package/dist/cli-errors-Czmx92Zy.d.mts +0 -3
  156. package/dist/cli-errors-Djtz98Vm.mjs +0 -71
  157. package/dist/cli-errors-Djtz98Vm.mjs.map +0 -1
  158. package/dist/client-oXO2WCPD.mjs.map +0 -1
  159. package/dist/command-helpers-DtavI0wJ.mjs.map +0 -1
  160. package/dist/commands/migration-check.mjs.map +0 -1
  161. package/dist/commands/migration-list.mjs.map +0 -1
  162. package/dist/contract-enrichment-Dani0mMW.mjs.map +0 -1
  163. package/dist/contract-space-aggregate-loader-BmNQwlws.mjs +0 -160
  164. package/dist/contract-space-aggregate-loader-BmNQwlws.mjs.map +0 -1
  165. package/dist/global-flags-CdE7M0d9.d.mts.map +0 -1
  166. package/dist/migration-plan-C2jeH1J5.mjs.map +0 -1
  167. package/dist/migrations-CwZMa1Ck.mjs.map +0 -1
  168. package/dist/terminal-ui-BiB_8KNo.mjs +0 -379
  169. package/dist/terminal-ui-BiB_8KNo.mjs.map +0 -1
  170. package/dist/types-C9FfXb1l.d.mts.map +0 -1
@@ -2,13 +2,14 @@ import { readFile } from 'node:fs/promises';
2
2
  import type { Contract } from '@prisma-next/contract/types';
3
3
  import { createControlStack } from '@prisma-next/framework-components/control';
4
4
  import { errorUnknownInvariant, MigrationToolsError } from '@prisma-next/migration-tools/errors';
5
+ import { findLatestMigration, isGraphNode } from '@prisma-next/migration-tools/migration-graph';
5
6
  import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
6
7
  import type { RefEntry } from '@prisma-next/migration-tools/refs';
7
8
  import { readRefs } from '@prisma-next/migration-tools/refs';
8
9
  import { ifDefined } from '@prisma-next/utils/defined';
9
10
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
10
11
  import { Command } from 'commander';
11
-
12
+ import { join } from 'pathe';
12
13
  import { loadConfig } from '../config-loader';
13
14
  import { createControlClient } from '../control-api/client';
14
15
  import type {
@@ -23,6 +24,8 @@ import {
23
24
  errorDatabaseConnectionRequired,
24
25
  errorDriverRequired,
25
26
  errorFileNotFound,
27
+ errorMarkerMismatch,
28
+ errorPathUnreachable,
26
29
  errorRuntime,
27
30
  errorTargetMigrationNotSupported,
28
31
  errorUnexpected,
@@ -32,7 +35,6 @@ import {
32
35
  import {
33
36
  addGlobalOptions,
34
37
  collectDeclaredInvariants,
35
- loadMigrationPackages,
36
38
  maskConnectionUrl,
37
39
  resolveContractPath,
38
40
  resolveMigrationPaths,
@@ -40,10 +42,16 @@ import {
40
42
  setCommandExamples,
41
43
  targetSupportsMigrations,
42
44
  } from '../utils/command-helpers';
45
+ import {
46
+ loadContractSpaceAggregateForCli,
47
+ refuseContractSpaceIntegrity,
48
+ } from '../utils/contract-space-aggregate-loader';
49
+ import { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';
43
50
  import { formatMigrationApplyCommandOutput } from '../utils/formatters/migrations';
44
51
  import { formatStyledHeader } from '../utils/formatters/styled';
45
52
  import type { CommonCommandOptions } from '../utils/global-flags';
46
53
  import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
54
+ import { executeRefAdvancement, readContractIR } from '../utils/ref-advancement';
47
55
  import { handleResult } from '../utils/result-handler';
48
56
  import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
49
57
 
@@ -51,6 +59,7 @@ interface MigrateCommandOptions extends CommonCommandOptions {
51
59
  readonly db?: string;
52
60
  readonly config?: string;
53
61
  readonly to?: string;
62
+ readonly advanceRef?: string;
54
63
  }
55
64
 
56
65
  export interface MigrateResult {
@@ -72,9 +81,13 @@ export interface MigrateResult {
72
81
  readonly timings: {
73
82
  readonly total: number;
74
83
  };
84
+ readonly advancedRef?: { readonly name: string; readonly hash: string } | null;
75
85
  }
76
86
 
77
87
  function mapApplyFailure(failure: MigrationApplyFailure): CliStructuredErrorType {
88
+ if (failure.code === 'MIGRATION_PATH_NOT_FOUND') {
89
+ return errorPathUnreachable(failure);
90
+ }
78
91
  return errorRuntime(failure.summary, {
79
92
  why: failure.why ?? 'Migration runner failed',
80
93
  fix: 'Fix the issue and re-run `prisma-next migrate --to <contract>` — previously applied migrations are preserved.',
@@ -89,8 +102,10 @@ async function executeMigrateCommand(
89
102
  startTime: number,
90
103
  ): Promise<Result<MigrateResult, CliStructuredErrorType>> {
91
104
  const config = await loadConfig(options.config);
92
- const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative, refsDir } =
93
- resolveMigrationPaths(options.config, config);
105
+ const { configPath, migrationsDir, appMigrationsRelative, refsDir } = resolveMigrationPaths(
106
+ options.config,
107
+ config,
108
+ );
94
109
 
95
110
  const dbConnection = options.db ?? config.db?.connection;
96
111
  if (!dbConnection) {
@@ -118,31 +133,8 @@ async function executeMigrateCommand(
118
133
  );
119
134
  }
120
135
 
121
- let refEntry: RefEntry | undefined;
122
136
  const toArg = options.to;
123
137
 
124
- if (toArg) {
125
- try {
126
- const refs = await readRefs(refsDir);
127
- const { graph } = await loadMigrationPackages(appMigrationsDir);
128
- const refResult = parseContractRef(toArg, { graph, refs });
129
- if (!refResult.ok) {
130
- return notOk(mapRefResolutionError(refResult.failure));
131
- }
132
- if (refResult.value.provenance.kind === 'ref') {
133
- const resolved = refs[refResult.value.provenance.refName];
134
- if (resolved) refEntry = resolved;
135
- } else {
136
- refEntry = { hash: refResult.value.hash, invariants: [] };
137
- }
138
- } catch (error) {
139
- if (MigrationToolsError.is(error)) {
140
- return notOk(mapMigrationToolsError(error));
141
- }
142
- throw error;
143
- }
144
- }
145
-
146
138
  // Construct the family instance up-front so the on-disk contract read
147
139
  // crosses the serializer seam (`familyInstance.deserializeContract`) at
148
140
  // the read site. The downstream `client.migrationApply({ contract })`
@@ -184,6 +176,42 @@ async function executeMigrateCommand(
184
176
  );
185
177
  }
186
178
 
179
+ const loadedAggregate = await loadContractSpaceAggregateForCli({
180
+ targetId: config.target.targetId,
181
+ migrationsDir,
182
+ appContract: contractRaw,
183
+ extensionPacks: config.extensionPacks ?? [],
184
+ deserializeContract: (json) => familyInstance.deserializeContract(json),
185
+ });
186
+ if (!loadedAggregate.ok) {
187
+ return notOk(loadedAggregate.failure);
188
+ }
189
+ const aggregate = loadedAggregate.value;
190
+ const integrityFailure = refuseContractSpaceIntegrity(aggregate, {
191
+ declaredExtensions: toDeclaredExtensionsFromRaw(
192
+ (config.extensionPacks ?? []) as ReadonlyArray<unknown>,
193
+ ),
194
+ checkContracts: true,
195
+ });
196
+ if (integrityFailure) {
197
+ return notOk(integrityFailure);
198
+ }
199
+
200
+ let refEntry: RefEntry | undefined;
201
+ if (toArg) {
202
+ const refs = await readRefs(refsDir);
203
+ const refResult = parseContractRef(toArg, { graph: aggregate.app.graph(), refs });
204
+ if (!refResult.ok) {
205
+ return notOk(mapRefResolutionError(refResult.failure));
206
+ }
207
+ if (refResult.value.provenance.kind === 'ref') {
208
+ const resolved = refs[refResult.value.provenance.refName];
209
+ if (resolved) refEntry = resolved;
210
+ } else {
211
+ refEntry = { hash: refResult.value.hash, invariants: [] };
212
+ }
213
+ }
214
+
187
215
  if (!flags.json && !flags.quiet) {
188
216
  const details: Array<{ label: string; value: string }> = [
189
217
  { label: 'config', value: configPath },
@@ -208,15 +236,8 @@ async function executeMigrateCommand(
208
236
  ui.stderr(header);
209
237
  }
210
238
 
211
- let appPackages: Awaited<ReturnType<typeof loadMigrationPackages>>;
212
- try {
213
- appPackages = await loadMigrationPackages(appMigrationsDir);
214
- } catch (error) {
215
- if (MigrationToolsError.is(error)) {
216
- return notOk(mapMigrationToolsError(error));
217
- }
218
- throw error;
219
- }
239
+ const appGraph = aggregate.app.graph();
240
+ const appBundles = aggregate.app.packages;
220
241
 
221
242
  const client = createControlClient({
222
243
  family: config.family,
@@ -229,10 +250,21 @@ async function executeMigrateCommand(
229
250
  try {
230
251
  await client.connect(dbConnection);
231
252
 
253
+ const allMarkers = await client.readAllMarkers();
254
+ const appMarker = allMarkers.get('app') ?? null;
255
+
256
+ if (appMarker !== null && !isGraphNode(appMarker.storageHash, appGraph)) {
257
+ return notOk(
258
+ errorMarkerMismatch(
259
+ appMarker.storageHash,
260
+ [...appGraph.nodes].sort(),
261
+ findLatestMigration(appGraph)?.to ?? null,
262
+ ),
263
+ );
264
+ }
265
+
232
266
  if (refEntry && refEntry.invariants.length > 0) {
233
- const allMarkers = await client.readAllMarkers();
234
- const appMarker = allMarkers.get('app') ?? null;
235
- const declared = collectDeclaredInvariants(appPackages.graph);
267
+ const declared = collectDeclaredInvariants(appGraph);
236
268
  const known = new Set<string>(declared);
237
269
  for (const id of appMarker?.invariants ?? []) known.add(id);
238
270
  const unknown = refEntry.invariants.filter((id) => !known.has(id));
@@ -256,7 +288,6 @@ async function executeMigrateCommand(
256
288
  const applyResult = await client.migrationApply({
257
289
  contract: contractRaw,
258
290
  migrationsDir,
259
- appMigrationPackages: appPackages.bundles,
260
291
  ...ifDefined('refHash', refEntry?.hash),
261
292
  ...(refEntry?.invariants ? { refInvariants: refEntry.invariants } : {}),
262
293
  ...(refEntry !== undefined ? ifDefined('refName', toArg) : {}),
@@ -268,6 +299,53 @@ async function executeMigrateCommand(
268
299
 
269
300
  const { value } = applyResult;
270
301
 
302
+ let advancedRef: { name: string; hash: string } | null = null;
303
+ if (options.advanceRef !== undefined) {
304
+ let contractJsonPathForSnapshot = contractPathAbsolute;
305
+ let contractJsonForSnapshot: Record<string, unknown> = JSON.parse(contractContent) as Record<
306
+ string,
307
+ unknown
308
+ >;
309
+ if (toArg && refEntry) {
310
+ const matchingBundle = appBundles.find((p) => p.metadata.to === refEntry.hash);
311
+ if (matchingBundle) {
312
+ const endContractPath = join(matchingBundle.dirPath, 'end-contract.json');
313
+ contractJsonPathForSnapshot = endContractPath;
314
+ try {
315
+ const raw = await readFile(endContractPath, 'utf-8');
316
+ contractJsonForSnapshot = JSON.parse(raw) as Record<string, unknown>;
317
+ } catch (error) {
318
+ if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {
319
+ return notOk(
320
+ errorFileNotFound(endContractPath, {
321
+ why: `Bundle end-contract not found at ${endContractPath}`,
322
+ fix: 'Re-emit the migration bundle or pick a different --to target.',
323
+ }),
324
+ );
325
+ }
326
+ throw error;
327
+ }
328
+ }
329
+ }
330
+ try {
331
+ const contractIR = await readContractIR(
332
+ contractJsonForSnapshot,
333
+ contractJsonPathForSnapshot,
334
+ );
335
+ advancedRef = await executeRefAdvancement(
336
+ refsDir,
337
+ options.advanceRef,
338
+ value.markerHash,
339
+ contractIR,
340
+ );
341
+ } catch (error) {
342
+ if (MigrationToolsError.is(error)) {
343
+ return notOk(mapMigrationToolsError(error));
344
+ }
345
+ throw error;
346
+ }
347
+ }
348
+
271
349
  return ok({
272
350
  ok: true,
273
351
  migrationsApplied: value.migrationsApplied,
@@ -278,6 +356,7 @@ async function executeMigrateCommand(
278
356
  perSpace: value.perSpace,
279
357
  ...ifDefined('pathDecision', value.pathDecision),
280
358
  timings: { total: Date.now() - startTime },
359
+ advancedRef,
281
360
  });
282
361
  } catch (error) {
283
362
  if (CliStructuredError.is(error)) {
@@ -318,6 +397,7 @@ export function createMigrateCommand(): Command {
318
397
  '--to <contract>',
319
398
  'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
320
399
  )
400
+ .option('--advance-ref <name>', 'Advance the named ref to the post-apply marker after success')
321
401
  .action(async (options: MigrateCommandOptions) => {
322
402
  const flags = parseGlobalFlagsOrExit(options);
323
403
  const startTime = Date.now();
@@ -1,6 +1,11 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
- import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { createControlStack } from '@prisma-next/framework-components/control';
4
+ import type { IntegrityViolation } from '@prisma-next/migration-tools/aggregate';
5
+ import { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';
3
6
  import { verifyMigrationHash } from '@prisma-next/migration-tools/hash';
7
+ import { readMigrationsDir } from '@prisma-next/migration-tools/io';
8
+ import { reconstructGraph } from '@prisma-next/migration-tools/migration-graph';
4
9
  import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
5
10
  import { parseMigrationRef } from '@prisma-next/migration-tools/ref-resolution';
6
11
  import { readRefs } from '@prisma-next/migration-tools/refs';
@@ -9,15 +14,20 @@ import { join, relative } from 'pathe';
9
14
  import { loadConfig } from '../config-loader';
10
15
  import {
11
16
  addGlobalOptions,
12
- loadMigrationPackages,
17
+ resolveContractPath,
13
18
  resolveMigrationPaths,
14
19
  setCommandDescriptions,
15
20
  setCommandExamples,
16
21
  setCommandSeeAlso,
17
22
  } from '../utils/command-helpers';
23
+ import { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';
18
24
  import { formatStyledHeader } from '../utils/formatters/styled';
19
25
  import type { CommonCommandOptions } from '../utils/global-flags';
20
26
  import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
27
+ import {
28
+ type CheckFailure,
29
+ integrityViolationToCheckFailure,
30
+ } from '../utils/integrity-violation-to-check-failure';
21
31
  import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
22
32
  import { INTEGRITY_FAILED, OK, PRECONDITION } from './migration-check/exit-codes';
23
33
 
@@ -25,12 +35,7 @@ interface MigrationCheckOptions extends CommonCommandOptions {
25
35
  readonly config?: string;
26
36
  }
27
37
 
28
- export interface CheckFailure {
29
- readonly pnCode: string;
30
- readonly where: string;
31
- readonly why: string;
32
- readonly fix: string;
33
- }
38
+ export type { CheckFailure } from '../utils/integrity-violation-to-check-failure';
34
39
 
35
40
  export interface MigrationCheckResult {
36
41
  readonly ok: boolean;
@@ -38,11 +43,6 @@ export interface MigrationCheckResult {
38
43
  readonly summary: string;
39
44
  }
40
45
 
41
- /**
42
- * Canonical user-facing locator for a check failure: the cwd-relative path
43
- * to the migration package directory. Surfacing the same shape across every
44
- * PN code means `--json` consumers can branch uniformly on `where`.
45
- */
46
46
  function migrationPathRelative(dirPath: string): string {
47
47
  return relative(process.cwd(), dirPath);
48
48
  }
@@ -63,21 +63,6 @@ function checkFileExists(dirPath: string, dirName: string, fileName: string): Ch
63
63
  return null;
64
64
  }
65
65
 
66
- /**
67
- * Within-migration snapshot-consistency check (PN-MIG-CHECK-005).
68
- *
69
- * Compares the migration's stored `metadata.to` against the `storageHash`
70
- * recorded in its on-disk `end-contract.json` snapshot. The two values are
71
- * independent on-disk records of the same fact (the migration's destination
72
- * contract); drift between them indicates the package is internally
73
- * corrupt. Cross-migration consistency (one migration's end-contract.json
74
- * agreeing with the next migration's start-contract.json) is a separate
75
- * check that requires shadow execution and is deferred to
76
- * `migration preflight`.
77
- *
78
- * Shared between the graph-wide and per-migration code paths so both report
79
- * the same failure for the same on-disk state.
80
- */
81
66
  function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | null {
82
67
  const endContractPath = join(pkg.dirPath, 'end-contract.json');
83
68
  if (!existsSync(endContractPath)) return null;
@@ -104,6 +89,27 @@ function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | n
104
89
  return null;
105
90
  }
106
91
 
92
+ async function loadAggregateIntegrityViolations(
93
+ config: Awaited<ReturnType<typeof loadConfig>>,
94
+ migrationsDir: string,
95
+ ): Promise<readonly IntegrityViolation[]> {
96
+ try {
97
+ const contractJsonContent = await readFile(resolveContractPath(config), 'utf-8');
98
+ const familyInstance = config.family.create(createControlStack(config));
99
+ const declaredExtensions = toDeclaredExtensionsFromRaw(config.extensionPacks ?? []);
100
+
101
+ const parsedAppContract: unknown = JSON.parse(contractJsonContent);
102
+ const aggregate = await loadContractSpaceAggregate({
103
+ migrationsDir,
104
+ deserializeContract: (json: unknown) => familyInstance.deserializeContract(json),
105
+ appContract: familyInstance.deserializeContract(parsedAppContract),
106
+ });
107
+ return aggregate.checkIntegrity({ declaredExtensions, checkContracts: true });
108
+ } catch {
109
+ return [];
110
+ }
111
+ }
112
+
107
113
  async function executeMigrationCheckCommand(
108
114
  target: string | undefined,
109
115
  options: MigrationCheckOptions,
@@ -111,10 +117,8 @@ async function executeMigrationCheckCommand(
111
117
  ui: TerminalUI,
112
118
  ): Promise<{ result: MigrationCheckResult; exitCode: number }> {
113
119
  const config = await loadConfig(options.config);
114
- const { configPath, appMigrationsDir, appMigrationsRelative, refsDir } = resolveMigrationPaths(
115
- options.config,
116
- config,
117
- );
120
+ const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative, refsDir } =
121
+ resolveMigrationPaths(options.config, config);
118
122
 
119
123
  if (!flags.json && !flags.quiet) {
120
124
  const details: Array<{ label: string; value: string }> = [
@@ -135,37 +139,9 @@ async function executeMigrationCheckCommand(
135
139
 
136
140
  const failures: CheckFailure[] = [];
137
141
 
138
- let bundles: Awaited<ReturnType<typeof loadMigrationPackages>>['bundles'];
139
- let graph: Awaited<ReturnType<typeof loadMigrationPackages>>['graph'];
140
- try {
141
- const loaded = await loadMigrationPackages(appMigrationsDir);
142
- bundles = loaded.bundles;
143
- graph = loaded.graph;
144
- } catch (error) {
145
- if (MigrationToolsError.is(error)) {
146
- const pnCode =
147
- error.code === 'MIGRATION.HASH_MISMATCH' ? 'PN-MIG-CHECK-001' : 'PN-MIG-CHECK-002';
148
- // Normalise to a cwd-relative path. `error.details.dir` is absolute
149
- // (the migration-tools layer doesn't know the caller's cwd); the
150
- // `filePath` fallback is also absolute. Surfacing the relative form
151
- // matches the rest of the command's `where` shape and keeps `--json`
152
- // consumers from having to special-case the bootstrap-failure path.
153
- const rawWhere =
154
- (error.details?.['dir'] as string) ?? (error.details?.['filePath'] as string) ?? null;
155
- const where = rawWhere ? relative(process.cwd(), rawWhere) : 'unknown';
156
- failures.push({
157
- pnCode,
158
- where,
159
- why: error.why,
160
- fix: error.fix,
161
- });
162
- return {
163
- result: { ok: false, failures, summary: `${failures.length} integrity failure(s)` },
164
- exitCode: INTEGRITY_FAILED,
165
- };
166
- }
167
- throw error;
168
- }
142
+ const loaded = await readMigrationsDir(appMigrationsDir);
143
+ const bundles: readonly OnDiskMigrationPackage[] = loaded.packages;
144
+ const graph = reconstructGraph(bundles);
169
145
 
170
146
  if (existsSync(appMigrationsDir)) {
171
147
  const loadedDirNames = new Set(bundles.map((p) => p.dirName));
@@ -236,29 +212,9 @@ async function executeMigrationCheckCommand(
236
212
  });
237
213
  }
238
214
 
239
- // PN-MIG-CHECK-005 must fire per-migration as well as graph-wide; both
240
- // call sites delegate to the shared helper so the same on-disk drift
241
- // produces the same failure regardless of how the user invoked check.
242
215
  const snapshotFailure = checkSnapshotConsistency(matchedPkg);
243
216
  if (snapshotFailure) failures.push(snapshotFailure);
244
217
  } else {
245
- for (const pkg of bundles) {
246
- for (const f of ['migration.json', 'ops.json']) {
247
- const fail = checkFileExists(pkg.dirPath, pkg.dirName, f);
248
- if (fail) failures.push(fail);
249
- }
250
-
251
- const verification = verifyMigrationHash(pkg);
252
- if (!verification.ok) {
253
- failures.push({
254
- pnCode: 'PN-MIG-CHECK-001',
255
- where: migrationFileRelative(pkg.dirPath, 'migration.json'),
256
- why: `Stored hash ${verification.storedHash} does not match recomputed hash ${verification.computedHash}`,
257
- fix: 'Re-emit the migration package or restore from version control.',
258
- });
259
- }
260
- }
261
-
262
218
  for (const pkg of bundles) {
263
219
  const snapshotFailure = checkSnapshotConsistency(pkg);
264
220
  if (snapshotFailure) failures.push(snapshotFailure);
@@ -295,6 +251,10 @@ async function executeMigrationCheckCommand(
295
251
  } catch {
296
252
  // Refs unreadable — skip ref checks
297
253
  }
254
+
255
+ for (const violation of await loadAggregateIntegrityViolations(config, migrationsDir)) {
256
+ failures.push(integrityViolationToCheckFailure(violation, migrationsDir));
257
+ }
298
258
  }
299
259
 
300
260
  if (failures.length === 0) {