@prisma-next/cli 0.11.0-dev.7 → 0.11.0-dev.8

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 (89) hide show
  1. package/dist/cli-errors-Bw2GlweY.mjs +175 -0
  2. package/dist/cli-errors-Bw2GlweY.mjs.map +1 -0
  3. package/dist/cli.mjs +7 -7
  4. package/dist/{client-oXO2WCPD.mjs → client-UnIveZxZ.mjs} +4 -4
  5. package/dist/{client-oXO2WCPD.mjs.map → client-UnIveZxZ.mjs.map} +1 -1
  6. package/dist/{command-helpers-DtavI0wJ.mjs → command-helpers-CRfjbZRz.mjs} +2 -2
  7. package/dist/{command-helpers-DtavI0wJ.mjs.map → command-helpers-CRfjbZRz.mjs.map} +1 -1
  8. package/dist/commands/contract-emit.mjs +1 -1
  9. package/dist/commands/contract-infer.mjs +1 -1
  10. package/dist/commands/db-init.d.mts.map +1 -1
  11. package/dist/commands/db-init.mjs +33 -6
  12. package/dist/commands/db-init.mjs.map +1 -1
  13. package/dist/commands/db-schema.mjs +3 -3
  14. package/dist/commands/db-sign.mjs +5 -5
  15. package/dist/commands/db-update.d.mts.map +1 -1
  16. package/dist/commands/db-update.mjs +36 -7
  17. package/dist/commands/db-update.mjs.map +1 -1
  18. package/dist/commands/db-verify.mjs +1 -1
  19. package/dist/commands/migrate.d.mts +5 -1
  20. package/dist/commands/migrate.d.mts.map +1 -1
  21. package/dist/commands/migrate.mjs +44 -8
  22. package/dist/commands/migrate.mjs.map +1 -1
  23. package/dist/commands/migration-check.mjs +2 -2
  24. package/dist/commands/migration-graph.mjs +3 -3
  25. package/dist/commands/migration-list.mjs +2 -2
  26. package/dist/commands/migration-log.mjs +3 -3
  27. package/dist/commands/migration-new.mjs +3 -3
  28. package/dist/commands/migration-plan.d.mts +1 -0
  29. package/dist/commands/migration-plan.d.mts.map +1 -1
  30. package/dist/commands/migration-plan.mjs +1 -1
  31. package/dist/commands/migration-show.d.mts +1 -1
  32. package/dist/commands/migration-show.mjs +6 -6
  33. package/dist/commands/migration-status.mjs +7 -7
  34. package/dist/commands/ref.d.mts +1 -1
  35. package/dist/commands/ref.d.mts.map +1 -1
  36. package/dist/commands/ref.mjs +34 -8
  37. package/dist/commands/ref.mjs.map +1 -1
  38. package/dist/{contract-emit-o-8VmdQX.mjs → contract-emit-C6rlsljO.mjs} +3 -3
  39. package/dist/{contract-emit-o-8VmdQX.mjs.map → contract-emit-C6rlsljO.mjs.map} +1 -1
  40. package/dist/{contract-emit-CmsklifJ.mjs → contract-emit-mqXmapxB.mjs} +4 -4
  41. package/dist/{contract-emit-CmsklifJ.mjs.map → contract-emit-mqXmapxB.mjs.map} +1 -1
  42. package/dist/{contract-infer-pKkiCt7C.mjs → contract-infer-C4jxc1aZ.mjs} +3 -3
  43. package/dist/{contract-infer-pKkiCt7C.mjs.map → contract-infer-C4jxc1aZ.mjs.map} +1 -1
  44. package/dist/{contract-space-aggregate-loader-BmNQwlws.mjs → contract-space-aggregate-loader-CGakRlKM.mjs} +2 -2
  45. package/dist/{contract-space-aggregate-loader-BmNQwlws.mjs.map → contract-space-aggregate-loader-CGakRlKM.mjs.map} +1 -1
  46. package/dist/{db-verify-AoIUriL4.mjs → db-verify-1d8tDoFN.mjs} +5 -5
  47. package/dist/{db-verify-AoIUriL4.mjs.map → db-verify-1d8tDoFN.mjs.map} +1 -1
  48. package/dist/exports/control-api.d.mts +1 -1
  49. package/dist/exports/control-api.mjs +2 -2
  50. package/dist/exports/index.mjs +1 -1
  51. package/dist/exports/init-output.mjs +1 -1
  52. package/dist/{framework-components-65gOHkHB.mjs → framework-components-Bexd0f4E.mjs} +2 -2
  53. package/dist/{framework-components-65gOHkHB.mjs.map → framework-components-Bexd0f4E.mjs.map} +1 -1
  54. package/dist/{graph-render-DJVv0_uf.mjs → graph-render-BE8vmJ_7.mjs} +1 -1
  55. package/dist/{graph-render-DJVv0_uf.mjs.map → graph-render-BE8vmJ_7.mjs.map} +1 -1
  56. package/dist/{init-Db5Itt5r.mjs → init-ByoeQphC.mjs} +4 -4
  57. package/dist/{init-Db5Itt5r.mjs.map → init-ByoeQphC.mjs.map} +1 -1
  58. package/dist/{inspect-live-schema-LeWvkZVz.mjs → inspect-live-schema-B1Q49RF0.mjs} +4 -4
  59. package/dist/{inspect-live-schema-LeWvkZVz.mjs.map → inspect-live-schema-B1Q49RF0.mjs.map} +1 -1
  60. package/dist/{migration-command-scaffold-BtkunvFQ.mjs → migration-command-scaffold-oY4P1Qto.mjs} +4 -4
  61. package/dist/{migration-command-scaffold-BtkunvFQ.mjs.map → migration-command-scaffold-oY4P1Qto.mjs.map} +1 -1
  62. package/dist/{migration-plan-C2jeH1J5.mjs → migration-plan-jdAHg_gK.mjs} +346 -87
  63. package/dist/migration-plan-jdAHg_gK.mjs.map +1 -0
  64. package/dist/{migrations-CwZMa1Ck.mjs → migrations-B7n518mT.mjs} +10 -1
  65. package/dist/migrations-B7n518mT.mjs.map +1 -0
  66. package/dist/{output-BlsrGMEF.mjs → output-CUIdfYo5.mjs} +1 -1
  67. package/dist/{output-BlsrGMEF.mjs.map → output-CUIdfYo5.mjs.map} +1 -1
  68. package/dist/ref-advancement-DRh5Nquq.mjs +50 -0
  69. package/dist/ref-advancement-DRh5Nquq.mjs.map +1 -0
  70. package/dist/{types-C9FfXb1l.d.mts → types-UWB2-rrw.d.mts} +5 -4
  71. package/dist/types-UWB2-rrw.d.mts.map +1 -0
  72. package/dist/{verify-Bom75OYI.mjs → verify-C5UvbrF1.mjs} +1 -1
  73. package/dist/{verify-Bom75OYI.mjs.map → verify-C5UvbrF1.mjs.map} +1 -1
  74. package/package.json +18 -18
  75. package/src/commands/db-init.ts +48 -2
  76. package/src/commands/db-update.ts +45 -0
  77. package/src/commands/migrate.ts +73 -3
  78. package/src/commands/migration-plan.ts +365 -128
  79. package/src/commands/ref.ts +46 -6
  80. package/src/utils/cli-errors.ts +173 -0
  81. package/src/utils/formatters/migrations.ts +25 -0
  82. package/src/utils/plan-resolution.ts +257 -0
  83. package/src/utils/ref-advancement.ts +68 -0
  84. package/dist/cli-errors-Czmx92Zy.d.mts +0 -3
  85. package/dist/cli-errors-Djtz98Vm.mjs +0 -71
  86. package/dist/cli-errors-Djtz98Vm.mjs.map +0 -1
  87. package/dist/migration-plan-C2jeH1J5.mjs.map +0 -1
  88. package/dist/migrations-CwZMa1Ck.mjs.map +0 -1
  89. package/dist/types-C9FfXb1l.d.mts.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma-next/cli",
3
- "version": "0.11.0-dev.7",
3
+ "version": "0.11.0-dev.8",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -14,15 +14,15 @@
14
14
  "dependencies": {
15
15
  "@clack/prompts": "^1.4.0",
16
16
  "@dagrejs/dagre": "^3.0.0",
17
- "@prisma-next/config": "0.11.0-dev.7",
18
- "@prisma-next/contract": "0.11.0-dev.7",
19
- "@prisma-next/emitter": "0.11.0-dev.7",
20
- "@prisma-next/errors": "0.11.0-dev.7",
21
- "@prisma-next/framework-components": "0.11.0-dev.7",
22
- "@prisma-next/migration-tools": "0.11.0-dev.7",
23
- "@prisma-next/psl-printer": "0.11.0-dev.7",
24
- "@prisma-next/cli-telemetry": "0.11.0-dev.7",
25
- "@prisma-next/utils": "0.11.0-dev.7",
17
+ "@prisma-next/config": "0.11.0-dev.8",
18
+ "@prisma-next/contract": "0.11.0-dev.8",
19
+ "@prisma-next/emitter": "0.11.0-dev.8",
20
+ "@prisma-next/errors": "0.11.0-dev.8",
21
+ "@prisma-next/framework-components": "0.11.0-dev.8",
22
+ "@prisma-next/migration-tools": "0.11.0-dev.8",
23
+ "@prisma-next/psl-printer": "0.11.0-dev.8",
24
+ "@prisma-next/cli-telemetry": "0.11.0-dev.8",
25
+ "@prisma-next/utils": "0.11.0-dev.8",
26
26
  "arktype": "^2.2.0",
27
27
  "c12": "^3.3.4",
28
28
  "ci-info": "^4.3.1",
@@ -39,14 +39,14 @@
39
39
  "wrap-ansi": "^10.0.0"
40
40
  },
41
41
  "devDependencies": {
42
- "@prisma-next/sql-contract": "0.11.0-dev.7",
43
- "@prisma-next/sql-contract-emitter": "0.11.0-dev.7",
44
- "@prisma-next/sql-contract-ts": "0.11.0-dev.7",
45
- "@prisma-next/sql-operations": "0.11.0-dev.7",
46
- "@prisma-next/sql-runtime": "0.11.0-dev.7",
47
- "@prisma-next/test-utils": "0.11.0-dev.7",
48
- "@prisma-next/tsconfig": "0.11.0-dev.7",
49
- "@prisma-next/tsdown": "0.11.0-dev.7",
42
+ "@prisma-next/sql-contract": "0.11.0-dev.8",
43
+ "@prisma-next/sql-contract-emitter": "0.11.0-dev.8",
44
+ "@prisma-next/sql-contract-ts": "0.11.0-dev.8",
45
+ "@prisma-next/sql-operations": "0.11.0-dev.8",
46
+ "@prisma-next/sql-runtime": "0.11.0-dev.8",
47
+ "@prisma-next/test-utils": "0.11.0-dev.8",
48
+ "@prisma-next/tsconfig": "0.11.0-dev.8",
49
+ "@prisma-next/tsdown": "0.11.0-dev.8",
50
50
  "@types/node": "24.10.4",
51
51
  "tsdown": "0.22.0",
52
52
  "typescript": "5.9.3",
@@ -1,3 +1,4 @@
1
+ import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
1
2
  import { ifDefined } from '@prisma-next/utils/defined';
2
3
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
3
4
  import { Command } from 'commander';
@@ -10,6 +11,7 @@ import {
10
11
  errorRunnerFailed,
11
12
  errorRuntime,
12
13
  errorUnexpected,
14
+ mapMigrationToolsError,
13
15
  } from '../utils/cli-errors';
14
16
  import type { MigrationCommandOptions } from '../utils/command-helpers';
15
17
  import {
@@ -29,10 +31,18 @@ import {
29
31
  addMigrationCommandOptions,
30
32
  prepareMigrationContext,
31
33
  } from '../utils/migration-command-scaffold';
34
+ import {
35
+ buildRefAdvancementFields,
36
+ computeRefAdvancementName,
37
+ type RefAdvancementFields,
38
+ readContractIR,
39
+ } from '../utils/ref-advancement';
32
40
  import { handleResult } from '../utils/result-handler';
33
41
  import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
34
42
 
35
- type DbInitOptions = MigrationCommandOptions;
43
+ interface DbInitOptions extends MigrationCommandOptions {
44
+ readonly advanceRef?: string;
45
+ }
36
46
 
37
47
  /**
38
48
  * Maps a DbInitFailure to a CliStructuredError for consistent error handling.
@@ -128,7 +138,7 @@ async function executeDbInitCommand(
128
138
  // per-space precheck + marker-check helpers are no longer needed at
129
139
  // this surface. Marker-vs-on-disk drift surfaces through the planner's
130
140
  // graph-walk strategy.
131
- const { migrationsDir } = resolveMigrationPaths(options.config, config);
141
+ const { migrationsDir, refsDir } = resolveMigrationPaths(options.config, config);
132
142
 
133
143
  try {
134
144
  await client.connect(dbConnection);
@@ -145,6 +155,39 @@ async function executeDbInitCommand(
145
155
  return notOk(mapDbInitFailure(result.failure));
146
156
  }
147
157
 
158
+ const advancementHash =
159
+ result.value.mode === 'apply'
160
+ ? (result.value.marker?.storageHash ?? result.value.destination.storageHash)
161
+ : result.value.destination.storageHash;
162
+
163
+ let refAdvancementFields: RefAdvancementFields = {
164
+ advancedRef: null,
165
+ plannedAdvanceRef: null,
166
+ };
167
+ if (
168
+ computeRefAdvancementName({
169
+ ...ifDefined('advanceRef', options.advanceRef),
170
+ ...ifDefined('db', options.db),
171
+ }) !== null
172
+ ) {
173
+ try {
174
+ const contractIR = await readContractIR(contractJson, contractPathAbsolute);
175
+ refAdvancementFields = await buildRefAdvancementFields({
176
+ ...ifDefined('advanceRef', options.advanceRef),
177
+ ...ifDefined('db', options.db),
178
+ refsDir,
179
+ contractIR,
180
+ mode: result.value.mode,
181
+ hash: advancementHash,
182
+ });
183
+ } catch (error) {
184
+ if (MigrationToolsError.is(error)) {
185
+ return notOk(mapMigrationToolsError(error));
186
+ }
187
+ throw error;
188
+ }
189
+ }
190
+
148
191
  // Convert success result to CLI output format
149
192
  const dbInitResult: MigrationCommandResult = {
150
193
  ok: true,
@@ -179,6 +222,8 @@ async function executeDbInitCommand(
179
222
  }
180
223
  : {}),
181
224
  ...ifDefined('perSpace', result.value.perSpace),
225
+ advancedRef: refAdvancementFields.advancedRef,
226
+ plannedAdvanceRef: refAdvancementFields.plannedAdvanceRef,
182
227
  summary: result.value.summary,
183
228
  timings: { total: Date.now() - startTime },
184
229
  };
@@ -229,6 +274,7 @@ export function createDbInitCommand(): Command {
229
274
  'prisma-next db init --db $DATABASE_URL --dry-run',
230
275
  ]);
231
276
  addMigrationCommandOptions(command);
277
+ command.option('--advance-ref <name>', 'Ref to advance to the post-command contract hash');
232
278
  command.action(async (options: DbInitOptions) => {
233
279
  const flags = parseGlobalFlagsOrExit(options);
234
280
  const startTime = Date.now();
@@ -38,11 +38,18 @@ import {
38
38
  addMigrationCommandOptions,
39
39
  prepareMigrationContext,
40
40
  } from '../utils/migration-command-scaffold';
41
+ import {
42
+ buildRefAdvancementFields,
43
+ computeRefAdvancementName,
44
+ type RefAdvancementFields,
45
+ readContractIR,
46
+ } from '../utils/ref-advancement';
41
47
  import { handleResult } from '../utils/result-handler';
42
48
  import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
43
49
 
44
50
  interface DbUpdateOptions extends MigrationCommandOptions {
45
51
  readonly to?: string;
52
+ readonly advanceRef?: string;
46
53
  }
47
54
 
48
55
  /**
@@ -101,6 +108,7 @@ async function executeDbUpdateCommand(
101
108
  }
102
109
  const { client, config, dbConnection, onProgress, contractPathAbsolute } = ctxResult.value;
103
110
  let { contractJson } = ctxResult.value;
111
+ let contractJsonPathForSnapshot = contractPathAbsolute;
104
112
  const { migrationsDir, appMigrationsDir, refsDir } = resolveMigrationPaths(
105
113
  options.config,
106
114
  config,
@@ -130,6 +138,7 @@ async function executeDbUpdateCommand(
130
138
  const endContractPath = join(matchingBundle.dirPath, 'end-contract.json');
131
139
  const raw = await readFile(endContractPath, 'utf-8');
132
140
  contractJson = JSON.parse(raw) as Record<string, unknown>;
141
+ contractJsonPathForSnapshot = endContractPath;
133
142
  } catch (error) {
134
143
  if (MigrationToolsError.is(error)) {
135
144
  return notOk(mapMigrationToolsError(error));
@@ -157,6 +166,39 @@ async function executeDbUpdateCommand(
157
166
  return notOk(mapDbUpdateFailure(result.failure));
158
167
  }
159
168
 
169
+ const advancementHash =
170
+ result.value.mode === 'apply'
171
+ ? (result.value.marker?.storageHash ?? result.value.destination.storageHash)
172
+ : result.value.destination.storageHash;
173
+
174
+ let refAdvancementFields: RefAdvancementFields = {
175
+ advancedRef: null,
176
+ plannedAdvanceRef: null,
177
+ };
178
+ if (
179
+ computeRefAdvancementName({
180
+ ...ifDefined('advanceRef', options.advanceRef),
181
+ ...ifDefined('db', options.db),
182
+ }) !== null
183
+ ) {
184
+ try {
185
+ const contractIR = await readContractIR(contractJson, contractJsonPathForSnapshot);
186
+ refAdvancementFields = await buildRefAdvancementFields({
187
+ ...ifDefined('advanceRef', options.advanceRef),
188
+ ...ifDefined('db', options.db),
189
+ refsDir,
190
+ contractIR,
191
+ mode: result.value.mode,
192
+ hash: advancementHash,
193
+ });
194
+ } catch (error) {
195
+ if (MigrationToolsError.is(error)) {
196
+ return notOk(mapMigrationToolsError(error));
197
+ }
198
+ throw error;
199
+ }
200
+ }
201
+
160
202
  // Convert success result to CLI output format
161
203
  const dbUpdateResult: MigrationCommandResult = {
162
204
  ok: true,
@@ -193,6 +235,8 @@ async function executeDbUpdateCommand(
193
235
  : undefined,
194
236
  ),
195
237
  ...ifDefined('perSpace', result.value.perSpace),
238
+ advancedRef: refAdvancementFields.advancedRef,
239
+ plannedAdvanceRef: refAdvancementFields.plannedAdvanceRef,
196
240
  summary: result.value.summary,
197
241
  timings: { total: Date.now() - startTime },
198
242
  };
@@ -245,6 +289,7 @@ export function createDbUpdateCommand(): Command {
245
289
  '--to <contract>',
246
290
  'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
247
291
  );
292
+ command.option('--advance-ref <name>', 'Ref to advance to the post-command contract hash');
248
293
  command.action(async (options: DbUpdateOptions) => {
249
294
  const flags = parseGlobalFlagsOrExit(options);
250
295
  const startTime = Date.now();
@@ -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,
@@ -44,6 +47,7 @@ import { formatMigrationApplyCommandOutput } from '../utils/formatters/migration
44
47
  import { formatStyledHeader } from '../utils/formatters/styled';
45
48
  import type { CommonCommandOptions } from '../utils/global-flags';
46
49
  import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
50
+ import { executeRefAdvancement, readContractIR } from '../utils/ref-advancement';
47
51
  import { handleResult } from '../utils/result-handler';
48
52
  import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
49
53
 
@@ -51,6 +55,7 @@ interface MigrateCommandOptions extends CommonCommandOptions {
51
55
  readonly db?: string;
52
56
  readonly config?: string;
53
57
  readonly to?: string;
58
+ readonly advanceRef?: string;
54
59
  }
55
60
 
56
61
  export interface MigrateResult {
@@ -72,9 +77,13 @@ export interface MigrateResult {
72
77
  readonly timings: {
73
78
  readonly total: number;
74
79
  };
80
+ readonly advancedRef?: { readonly name: string; readonly hash: string } | null;
75
81
  }
76
82
 
77
83
  function mapApplyFailure(failure: MigrationApplyFailure): CliStructuredErrorType {
84
+ if (failure.code === 'MIGRATION_PATH_NOT_FOUND') {
85
+ return errorPathUnreachable(failure);
86
+ }
78
87
  return errorRuntime(failure.summary, {
79
88
  why: failure.why ?? 'Migration runner failed',
80
89
  fix: 'Fix the issue and re-run `prisma-next migrate --to <contract>` — previously applied migrations are preserved.',
@@ -229,9 +238,21 @@ async function executeMigrateCommand(
229
238
  try {
230
239
  await client.connect(dbConnection);
231
240
 
241
+ const allMarkers = await client.readAllMarkers();
242
+ const appMarker = allMarkers.get('app') ?? null;
243
+ const { graph } = appPackages;
244
+
245
+ if (appMarker !== null && !isGraphNode(appMarker.storageHash, graph)) {
246
+ return notOk(
247
+ errorMarkerMismatch(
248
+ appMarker.storageHash,
249
+ [...graph.nodes].sort(),
250
+ findLatestMigration(graph)?.to ?? null,
251
+ ),
252
+ );
253
+ }
254
+
232
255
  if (refEntry && refEntry.invariants.length > 0) {
233
- const allMarkers = await client.readAllMarkers();
234
- const appMarker = allMarkers.get('app') ?? null;
235
256
  const declared = collectDeclaredInvariants(appPackages.graph);
236
257
  const known = new Set<string>(declared);
237
258
  for (const id of appMarker?.invariants ?? []) known.add(id);
@@ -268,6 +289,53 @@ async function executeMigrateCommand(
268
289
 
269
290
  const { value } = applyResult;
270
291
 
292
+ let advancedRef: { name: string; hash: string } | null = null;
293
+ if (options.advanceRef !== undefined) {
294
+ let contractJsonPathForSnapshot = contractPathAbsolute;
295
+ let contractJsonForSnapshot: Record<string, unknown> = JSON.parse(contractContent) as Record<
296
+ string,
297
+ unknown
298
+ >;
299
+ if (toArg && refEntry) {
300
+ const matchingBundle = appPackages.bundles.find((p) => p.metadata.to === refEntry.hash);
301
+ if (matchingBundle) {
302
+ const endContractPath = join(matchingBundle.dirPath, 'end-contract.json');
303
+ contractJsonPathForSnapshot = endContractPath;
304
+ try {
305
+ const raw = await readFile(endContractPath, 'utf-8');
306
+ contractJsonForSnapshot = JSON.parse(raw) as Record<string, unknown>;
307
+ } catch (error) {
308
+ if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {
309
+ return notOk(
310
+ errorFileNotFound(endContractPath, {
311
+ why: `Bundle end-contract not found at ${endContractPath}`,
312
+ fix: 'Re-emit the migration bundle or pick a different --to target.',
313
+ }),
314
+ );
315
+ }
316
+ throw error;
317
+ }
318
+ }
319
+ }
320
+ try {
321
+ const contractIR = await readContractIR(
322
+ contractJsonForSnapshot,
323
+ contractJsonPathForSnapshot,
324
+ );
325
+ advancedRef = await executeRefAdvancement(
326
+ refsDir,
327
+ options.advanceRef,
328
+ value.markerHash,
329
+ contractIR,
330
+ );
331
+ } catch (error) {
332
+ if (MigrationToolsError.is(error)) {
333
+ return notOk(mapMigrationToolsError(error));
334
+ }
335
+ throw error;
336
+ }
337
+ }
338
+
271
339
  return ok({
272
340
  ok: true,
273
341
  migrationsApplied: value.migrationsApplied,
@@ -278,6 +346,7 @@ async function executeMigrateCommand(
278
346
  perSpace: value.perSpace,
279
347
  ...ifDefined('pathDecision', value.pathDecision),
280
348
  timings: { total: Date.now() - startTime },
349
+ advancedRef,
281
350
  });
282
351
  } catch (error) {
283
352
  if (CliStructuredError.is(error)) {
@@ -318,6 +387,7 @@ export function createMigrateCommand(): Command {
318
387
  '--to <contract>',
319
388
  'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
320
389
  )
390
+ .option('--advance-ref <name>', 'Advance the named ref to the post-apply marker after success')
321
391
  .action(async (options: MigrateCommandOptions) => {
322
392
  const flags = parseGlobalFlagsOrExit(options);
323
393
  const startTime = Date.now();