@prisma-next/cli 0.10.0 → 0.11.0-dev.10

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 (166) hide show
  1. package/README.md +1 -1
  2. package/dist/cli-errors-Bw2GlweY.mjs +175 -0
  3. package/dist/cli-errors-Bw2GlweY.mjs.map +1 -0
  4. package/dist/cli.mjs +400 -13
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/{client-Brv4qlfB.mjs → client-UnIveZxZ.mjs} +6 -5
  7. package/dist/client-UnIveZxZ.mjs.map +1 -0
  8. package/dist/{command-helpers-D3vL5yi8.mjs → command-helpers-CRfjbZRz.mjs} +109 -12
  9. package/dist/command-helpers-CRfjbZRz.mjs.map +1 -0
  10. package/dist/commands/contract-emit.d.mts.map +1 -1
  11. package/dist/commands/contract-emit.mjs +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 +47 -21
  15. package/dist/commands/db-init.mjs.map +1 -1
  16. package/dist/commands/db-schema.mjs +6 -10
  17. package/dist/commands/db-schema.mjs.map +1 -1
  18. package/dist/commands/db-sign.mjs +8 -12
  19. package/dist/commands/db-sign.mjs.map +1 -1
  20. package/dist/commands/db-update.d.mts.map +1 -1
  21. package/dist/commands/db-update.mjs +47 -19
  22. package/dist/commands/db-update.mjs.map +1 -1
  23. package/dist/commands/db-verify.mjs +1 -1
  24. package/dist/commands/migrate.d.mts +5 -1
  25. package/dist/commands/migrate.d.mts.map +1 -1
  26. package/dist/commands/migrate.mjs +47 -15
  27. package/dist/commands/migrate.mjs.map +1 -1
  28. package/dist/commands/migration-check.mjs +5 -8
  29. package/dist/commands/migration-check.mjs.map +1 -1
  30. package/dist/commands/migration-graph.d.mts +1 -1
  31. package/dist/commands/migration-graph.mjs +6 -10
  32. package/dist/commands/migration-graph.mjs.map +1 -1
  33. package/dist/commands/migration-list.mjs +5 -9
  34. package/dist/commands/migration-list.mjs.map +1 -1
  35. package/dist/commands/migration-log.d.mts.map +1 -1
  36. package/dist/commands/migration-log.mjs +7 -10
  37. package/dist/commands/migration-log.mjs.map +1 -1
  38. package/dist/commands/migration-new.mjs +6 -10
  39. package/dist/commands/migration-new.mjs.map +1 -1
  40. package/dist/commands/migration-plan.d.mts +2 -1
  41. package/dist/commands/migration-plan.d.mts.map +1 -1
  42. package/dist/commands/migration-plan.mjs +1 -1
  43. package/dist/commands/migration-show.d.mts +1 -1
  44. package/dist/commands/migration-show.mjs +9 -13
  45. package/dist/commands/migration-show.mjs.map +1 -1
  46. package/dist/commands/migration-status.d.mts +1 -1
  47. package/dist/commands/migration-status.d.mts.map +1 -1
  48. package/dist/commands/migration-status.mjs +37 -15
  49. package/dist/commands/migration-status.mjs.map +1 -1
  50. package/dist/commands/ref.d.mts +1 -1
  51. package/dist/commands/ref.d.mts.map +1 -1
  52. package/dist/commands/ref.mjs +41 -25
  53. package/dist/commands/ref.mjs.map +1 -1
  54. package/dist/{contract-emit-iynA3BCA.mjs → contract-emit-C6rlsljO.mjs} +7 -6
  55. package/dist/contract-emit-C6rlsljO.mjs.map +1 -0
  56. package/dist/{contract-emit-C3STUIBg.mjs → contract-emit-mqXmapxB.mjs} +22 -20
  57. package/dist/contract-emit-mqXmapxB.mjs.map +1 -0
  58. package/dist/{contract-infer-Cnj8G1E2.mjs → contract-infer-C4jxc1aZ.mjs} +9 -14
  59. package/dist/contract-infer-C4jxc1aZ.mjs.map +1 -0
  60. package/dist/{contract-space-aggregate-loader-pAc8CDfY.mjs → contract-space-aggregate-loader-CGakRlKM.mjs} +2 -2
  61. package/dist/{contract-space-aggregate-loader-pAc8CDfY.mjs.map → contract-space-aggregate-loader-CGakRlKM.mjs.map} +1 -1
  62. package/dist/{db-verify-D7cyH_zz.mjs → db-verify-1d8tDoFN.mjs} +9 -13
  63. package/dist/db-verify-1d8tDoFN.mjs.map +1 -0
  64. package/dist/exports/control-api.d.mts +1 -1
  65. package/dist/exports/control-api.mjs +2 -2
  66. package/dist/exports/index.mjs +2 -2
  67. package/dist/exports/init-output.mjs +1 -1
  68. package/dist/{framework-components-xFLFpZUO.mjs → framework-components-Bexd0f4E.mjs} +2 -2
  69. package/dist/{framework-components-xFLFpZUO.mjs.map → framework-components-Bexd0f4E.mjs.map} +1 -1
  70. package/dist/{global-flags-DGmw6Kqg.d.mts → global-flags-CdE7M0d9.d.mts} +4 -1
  71. package/dist/global-flags-CdE7M0d9.d.mts.map +1 -0
  72. package/dist/{graph-render-eJDcLWny.mjs → graph-render-BE8vmJ_7.mjs} +1 -1
  73. package/dist/{graph-render-eJDcLWny.mjs.map → graph-render-BE8vmJ_7.mjs.map} +1 -1
  74. package/dist/{init-eh2z5Tl6.mjs → init-ByoeQphC.mjs} +528 -627
  75. package/dist/init-ByoeQphC.mjs.map +1 -0
  76. package/dist/{inspect-live-schema-CWLK_lgs.mjs → inspect-live-schema-B1Q49RF0.mjs} +4 -4
  77. package/dist/{inspect-live-schema-CWLK_lgs.mjs.map → inspect-live-schema-B1Q49RF0.mjs.map} +1 -1
  78. package/dist/{migration-command-scaffold-CmXXC1UZ.mjs → migration-command-scaffold-oY4P1Qto.mjs} +4 -4
  79. package/dist/{migration-command-scaffold-CmXXC1UZ.mjs.map → migration-command-scaffold-oY4P1Qto.mjs.map} +1 -1
  80. package/dist/{migration-plan-CHyUlBV0.mjs → migration-plan-jdAHg_gK.mjs} +349 -94
  81. package/dist/migration-plan-jdAHg_gK.mjs.map +1 -0
  82. package/dist/{migration-types-D2FW63pr.d.mts → migration-types-BXWvz12q.d.mts} +1 -1
  83. package/dist/{migration-types-D2FW63pr.d.mts.map → migration-types-BXWvz12q.d.mts.map} +1 -1
  84. package/dist/{migrations-DyUf5lTt.mjs → migrations-B7n518mT.mjs} +11 -2
  85. package/dist/migrations-B7n518mT.mjs.map +1 -0
  86. package/dist/{output-B60Gw5fu.mjs → output-CUIdfYo5.mjs} +1 -1
  87. package/dist/{output-B60Gw5fu.mjs.map → output-CUIdfYo5.mjs.map} +1 -1
  88. package/dist/quick-reference-mongo.md +1 -1
  89. package/dist/quick-reference-postgres.md +1 -1
  90. package/dist/readme-mongo.md +35 -0
  91. package/dist/readme-postgres.md +34 -0
  92. package/dist/ref-advancement-DRh5Nquq.mjs +50 -0
  93. package/dist/ref-advancement-DRh5Nquq.mjs.map +1 -0
  94. package/dist/{terminal-ui-XtOQsqe9.mjs → terminal-ui-BiB_8KNo.mjs} +131 -24
  95. package/dist/terminal-ui-BiB_8KNo.mjs.map +1 -0
  96. package/dist/{types-0aS865QN.d.mts → types-UWB2-rrw.d.mts} +12 -4
  97. package/dist/types-UWB2-rrw.d.mts.map +1 -0
  98. package/dist/{verify-D7ypCCe6.mjs → verify-C5UvbrF1.mjs} +2 -2
  99. package/dist/{verify-D7ypCCe6.mjs.map → verify-C5UvbrF1.mjs.map} +1 -1
  100. package/package.json +20 -18
  101. package/src/cli.ts +42 -0
  102. package/src/commands/contract-emit.ts +23 -11
  103. package/src/commands/contract-infer.ts +7 -7
  104. package/src/commands/db-init.ts +61 -7
  105. package/src/commands/db-schema.ts +4 -4
  106. package/src/commands/db-sign.ts +4 -4
  107. package/src/commands/db-update.ts +58 -5
  108. package/src/commands/db-verify.ts +5 -5
  109. package/src/commands/init/detect-package-manager.ts +15 -0
  110. package/src/commands/init/errors.ts +33 -2
  111. package/src/commands/init/hygiene-gitattributes.ts +2 -2
  112. package/src/commands/init/index.ts +15 -6
  113. package/src/commands/init/init.ts +61 -32
  114. package/src/commands/init/inputs.ts +82 -5
  115. package/src/commands/init/output.ts +1 -1
  116. package/src/commands/init/{agent-skill-install.ts → skill-install.ts} +42 -31
  117. package/src/commands/init/templates/code-templates.ts +26 -24
  118. package/src/commands/init/templates/env.ts +8 -1
  119. package/src/commands/init/templates/quick-reference-mongo.md +1 -1
  120. package/src/commands/init/templates/quick-reference-postgres.md +1 -1
  121. package/src/commands/init/templates/readme-mongo.md +35 -0
  122. package/src/commands/init/templates/readme-postgres.md +34 -0
  123. package/src/commands/init/templates/readme.ts +62 -0
  124. package/src/commands/migrate.ts +77 -10
  125. package/src/commands/migration-check.ts +4 -4
  126. package/src/commands/migration-graph.ts +4 -4
  127. package/src/commands/migration-list.ts +4 -4
  128. package/src/commands/migration-log.ts +6 -5
  129. package/src/commands/migration-new.ts +4 -4
  130. package/src/commands/migration-plan.ts +369 -132
  131. package/src/commands/migration-show.ts +4 -4
  132. package/src/commands/migration-status.ts +49 -6
  133. package/src/commands/ref.ts +54 -14
  134. package/src/control-api/operations/apply-aggregate.ts +1 -0
  135. package/src/control-api/operations/contract-emit.ts +7 -4
  136. package/src/control-api/types.ts +7 -0
  137. package/src/utils/cli-errors.ts +177 -0
  138. package/src/utils/command-helpers.ts +14 -6
  139. package/src/utils/formatters/migrations.ts +25 -0
  140. package/src/utils/global-flags.ts +105 -16
  141. package/src/utils/is-ci.ts +18 -0
  142. package/src/utils/plan-resolution.ts +257 -0
  143. package/src/utils/ref-advancement.ts +68 -0
  144. package/src/utils/telemetry.ts +141 -0
  145. package/src/utils/terminal-ui.ts +44 -23
  146. package/dist/cli-errors-CF60g2cG.mjs +0 -71
  147. package/dist/cli-errors-CF60g2cG.mjs.map +0 -1
  148. package/dist/cli-errors-DdcjVLJV.d.mts +0 -3
  149. package/dist/client-Brv4qlfB.mjs.map +0 -1
  150. package/dist/command-helpers-D3vL5yi8.mjs.map +0 -1
  151. package/dist/contract-emit-C3STUIBg.mjs.map +0 -1
  152. package/dist/contract-emit-iynA3BCA.mjs.map +0 -1
  153. package/dist/contract-infer-Cnj8G1E2.mjs.map +0 -1
  154. package/dist/db-verify-D7cyH_zz.mjs.map +0 -1
  155. package/dist/errors-Cw6kyTyV.mjs +0 -56
  156. package/dist/errors-Cw6kyTyV.mjs.map +0 -1
  157. package/dist/global-flags-DGmw6Kqg.d.mts.map +0 -1
  158. package/dist/helpers-eqdN8tH6.mjs +0 -25
  159. package/dist/helpers-eqdN8tH6.mjs.map +0 -1
  160. package/dist/init-eh2z5Tl6.mjs.map +0 -1
  161. package/dist/migration-plan-CHyUlBV0.mjs.map +0 -1
  162. package/dist/migrations-DyUf5lTt.mjs.map +0 -1
  163. package/dist/result-handler-Bm_6dDYg.mjs +0 -25
  164. package/dist/result-handler-Bm_6dDYg.mjs.map +0 -1
  165. package/dist/terminal-ui-XtOQsqe9.mjs.map +0 -1
  166. package/dist/types-0aS865QN.d.mts.map +0 -1
@@ -40,7 +40,14 @@ function envPlaceholderBody(target: TargetId): string {
40
40
  if (target === 'postgres') {
41
41
  lines.push('DATABASE_URL="postgresql://user:password@localhost:5432/mydb"');
42
42
  } else {
43
- lines.push('DATABASE_URL="mongodb://localhost:27017/mydb"');
43
+ lines.push(
44
+ '# Standalone local mongod / `docker run mongo:7` — no replica set required for first-run queries.',
45
+ );
46
+ lines.push(
47
+ '# Transactions and change streams need a replica set; add ?replicaSet=... only after initiating one.',
48
+ );
49
+ lines.push('');
50
+ lines.push('DATABASE_URL="mongodb://user:password@localhost:27017/mydb"');
44
51
  }
45
52
  lines.push('');
46
53
  return lines.join('\n');
@@ -22,7 +22,7 @@ const user = await db.orm.users
22
22
  .first();
23
23
 
24
24
  // Your editor will show the type of user as
25
- // { _id: ObjectId; email: string; name: string | null; posts: Post[] } | null
25
+ // { _id: ObjectId; email: string; username: string | null; name: string | null; posts: Post[] } | null
26
26
  ```
27
27
 
28
28
  `db` connects to MongoDB lazily on the first query, so there is no manual `connect(...)` step in your application code. Call `await db.close()` if you need to release the underlying connection (typically only in tests or short-lived scripts). After `close()` the client is single-shot — any further query, `connect()`, or `runtime()` call rejects with `"Mongo client is closed"`. Construct a new `mongo({...})` if you need to use it again.
@@ -22,7 +22,7 @@ const user = await db.orm.User
22
22
  .first();
23
23
 
24
24
  // Your editor will show the type of user as
25
- // { id: number; email: string; createdAt: Date, name: string, posts: Post[] } | null
25
+ // { id: number; email: string; username: string | null; name: string | null; createdAt: Date; posts: Post[] } | null
26
26
  ```
27
27
 
28
28
  Your contract has two companion files in the same directory:
@@ -0,0 +1,35 @@
1
+ # {{projectName}}
2
+
3
+ Generated by `prisma-next init` with the Minimal template.
4
+
5
+ ## First run
6
+
7
+ 1. `{{runDbUp}}` — start the local MongoDB replica set with `mongodb-memory-server`. Data persists in `.mongo-data/` across restarts.
8
+ 2. `{{runDev}}` — runs `src/index.ts`, which writes one row and reads it back.
9
+
10
+ ## Available scripts
11
+
12
+ - `{{runDev}}` — run the Prisma Next sample script
13
+
14
+ ### Database and migrations
15
+
16
+ - `{{runContractEmit}}` — emit contract artifacts after contract changes
17
+ - `{{runDbUp}}` — start the local MongoDB replica set with `mongodb-memory-server`. Data persists in `.mongo-data/` across restarts.
18
+ - `{{runDbDown}}` — stop the local MongoDB process (data preserved)
19
+ - `{{runDbReset}}` — stop and wipe `.mongo-data/` for a clean slate
20
+ - `{{runMigrationPlan}}` — create a MongoDB migration plan
21
+ - `{{runMigrate}}` — apply the planned MongoDB migration
22
+ - `{{runDbSeed}}` — insert sample users manually
23
+
24
+ ## Prisma Next
25
+
26
+ Prisma Next setup is scaffolded in:
27
+
28
+ - `{{contractPath}}`
29
+ - `prisma-next.config.ts`
30
+ - `prisma/db.ts`
31
+ - `src/lib/prisma.ts`
32
+
33
+ For provider-specific Prisma Next reference docs, see `prisma-next.md`. Prisma Next skills live in the upstream `skills/` directory: https://github.com/prisma/prisma-next/tree/main/skills.
34
+
35
+ Node-based Prisma Next projects expect Node.js 24 LTS or newer.
@@ -0,0 +1,34 @@
1
+ # {{projectName}}
2
+
3
+ Generated by `prisma-next init` with the Minimal template.
4
+
5
+ ## First run
6
+
7
+ 1. `{{runDbInit}}` — applies your contract to the database (creates tables, signs the marker).
8
+ 2. `{{runDev}}` — runs `src/index.ts`, which writes one row and reads it back.
9
+
10
+ ## Available scripts
11
+
12
+ - `{{runDev}}` — run the Prisma Next sample script
13
+
14
+ ### Database and migrations
15
+
16
+ - `{{runContractEmit}}` — emit contract artifacts after contract changes
17
+ - `{{runDbInit}}` — initialize database state manually
18
+ - `{{runDbUpdate}}` — update database state manually
19
+ - `{{runMigrationPlan}}` — create a migration plan
20
+ - `{{runMigrate}}` — apply a planned migration
21
+ - `{{runDbSeed}}` — insert sample users manually (run after `db:init`)
22
+
23
+ ## Prisma Next
24
+
25
+ Prisma Next setup is scaffolded in:
26
+
27
+ - `{{contractPath}}`
28
+ - `prisma-next.config.ts`
29
+ - `prisma/db.ts`
30
+ - `src/lib/prisma.ts`
31
+
32
+ For provider-specific Prisma Next reference docs, see `prisma-next.md`. Prisma Next skills live in the upstream `skills/` directory: https://github.com/prisma/prisma-next/tree/main/skills.
33
+
34
+ Node-based Prisma Next projects expect Node.js 24 LTS or newer.
@@ -0,0 +1,62 @@
1
+ import { formatRunScriptCommand, type PackageManager } from '../detect-package-manager';
2
+ import type { TargetId } from './code-templates';
3
+ import { renderTemplate } from './render';
4
+
5
+ const sharedVariables = ['projectName', 'contractPath', 'runDev', 'runContractEmit'] as const;
6
+
7
+ export const postgresVariables = [
8
+ ...sharedVariables,
9
+ 'runDbInit',
10
+ 'runDbUpdate',
11
+ 'runMigrationPlan',
12
+ 'runMigrate',
13
+ 'runDbSeed',
14
+ ] as const;
15
+
16
+ export const mongoVariables = [
17
+ ...sharedVariables,
18
+ 'runDbUp',
19
+ 'runDbDown',
20
+ 'runDbReset',
21
+ 'runMigrationPlan',
22
+ 'runMigrate',
23
+ 'runDbSeed',
24
+ ] as const;
25
+
26
+ type PostgresVars = Record<(typeof postgresVariables)[number], string>;
27
+ type MongoVars = Record<(typeof mongoVariables)[number], string>;
28
+
29
+ export function minimalProjectReadmeMd(
30
+ target: TargetId,
31
+ schemaPath: string,
32
+ projectName: string,
33
+ pm: PackageManager,
34
+ ): string {
35
+ const run = (script: string): string => formatRunScriptCommand(pm, script);
36
+ const shared = {
37
+ projectName,
38
+ contractPath: schemaPath,
39
+ runDev: run('dev'),
40
+ runContractEmit: run('contract:emit'),
41
+ runMigrationPlan: run('migration:plan'),
42
+ runMigrate: run('migrate'),
43
+ runDbSeed: run('db:seed'),
44
+ };
45
+
46
+ if (target === 'mongo') {
47
+ const vars: MongoVars = {
48
+ ...shared,
49
+ runDbUp: run('db:up'),
50
+ runDbDown: run('db:down'),
51
+ runDbReset: run('db:reset'),
52
+ };
53
+ return renderTemplate('readme-mongo.md', mongoVariables, vars);
54
+ }
55
+
56
+ const vars: PostgresVars = {
57
+ ...shared,
58
+ runDbInit: run('db:init'),
59
+ runDbUpdate: run('db:update'),
60
+ };
61
+ return renderTemplate('readme-postgres.md', postgresVariables, vars);
62
+ }
@@ -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,
@@ -43,14 +46,16 @@ import {
43
46
  import { formatMigrationApplyCommandOutput } from '../utils/formatters/migrations';
44
47
  import { formatStyledHeader } from '../utils/formatters/styled';
45
48
  import type { CommonCommandOptions } from '../utils/global-flags';
46
- import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
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
- import { TerminalUI } from '../utils/terminal-ui';
52
+ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
49
53
 
50
54
  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,14 +387,12 @@ 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
- const flags = parseGlobalFlags(options);
392
+ const flags = parseGlobalFlagsOrExit(options);
323
393
  const startTime = Date.now();
324
394
 
325
- const ui = new TerminalUI({
326
- color: flags.color,
327
- interactive: flags.interactive,
328
- });
395
+ const ui = createTerminalUI(flags);
329
396
 
330
397
  const result = await executeMigrateCommand(options, flags, ui, startTime);
331
398
 
@@ -17,8 +17,8 @@ import {
17
17
  } from '../utils/command-helpers';
18
18
  import { formatStyledHeader } from '../utils/formatters/styled';
19
19
  import type { CommonCommandOptions } from '../utils/global-flags';
20
- import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
21
- import { TerminalUI } from '../utils/terminal-ui';
20
+ import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
21
+ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
22
22
  import { INTEGRITY_FAILED, OK, PRECONDITION } from './migration-check/exit-codes';
23
23
 
24
24
  interface MigrationCheckOptions extends CommonCommandOptions {
@@ -335,8 +335,8 @@ export function createMigrationCheckCommand(): Command {
335
335
  .argument('[migration]', 'Migration reference (directory name or hash) to check')
336
336
  .option('--config <path>', 'Path to prisma-next.config.ts')
337
337
  .action(async (target: string | undefined, options: MigrationCheckOptions) => {
338
- const flags = parseGlobalFlags(options);
339
- const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
338
+ const flags = parseGlobalFlagsOrExit(options);
339
+ const ui = createTerminalUI(flags);
340
340
 
341
341
  let result: MigrationCheckResult;
342
342
  let exitCode: number;
@@ -23,10 +23,10 @@ import { migrationGraphToRenderInput } from '../utils/formatters/graph-migration
23
23
  import { graphRenderer } from '../utils/formatters/graph-render';
24
24
  import { formatStyledHeader } from '../utils/formatters/styled';
25
25
  import type { CommonCommandOptions } from '../utils/global-flags';
26
- import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
26
+ import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
27
27
  import type { StatusRef } from '../utils/migration-types';
28
28
  import { handleResult } from '../utils/result-handler';
29
- import { TerminalUI } from '../utils/terminal-ui';
29
+ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
30
30
 
31
31
  interface MigrationGraphOptions extends CommonCommandOptions {
32
32
  readonly config?: string;
@@ -130,8 +130,8 @@ export function createMigrationGraphCommand(): Command {
130
130
  .option('--config <path>', 'Path to prisma-next.config.ts')
131
131
  .option('--dot', 'Output in Graphviz DOT format')
132
132
  .action(async (options: MigrationGraphOptions) => {
133
- const flags = parseGlobalFlags(options);
134
- const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
133
+ const flags = parseGlobalFlagsOrExit(options);
134
+ const ui = createTerminalUI(flags);
135
135
  const result = await executeMigrationGraphCommand(options, flags, ui);
136
136
  const exitCode = handleResult(result, flags, ui, (graphResult) => {
137
137
  // Explicit format flags win over the auto-JSON default. `flags.json`
@@ -20,9 +20,9 @@ import {
20
20
  } from '../utils/command-helpers';
21
21
  import { formatStyledHeader } from '../utils/formatters/styled';
22
22
  import type { CommonCommandOptions } from '../utils/global-flags';
23
- import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
23
+ import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
24
24
  import { handleResult } from '../utils/result-handler';
25
- import { TerminalUI } from '../utils/terminal-ui';
25
+ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
26
26
 
27
27
  interface MigrationListOptions extends CommonCommandOptions {
28
28
  readonly config?: string;
@@ -130,8 +130,8 @@ export function createMigrationListCommand(): Command {
130
130
  addGlobalOptions(command)
131
131
  .option('--config <path>', 'Path to prisma-next.config.ts')
132
132
  .action(async (options: MigrationListOptions) => {
133
- const flags = parseGlobalFlags(options);
134
- const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
133
+ const flags = parseGlobalFlagsOrExit(options);
134
+ const ui = createTerminalUI(flags);
135
135
  const result = await executeMigrationListCommand(options, flags, ui);
136
136
  const exitCode = handleResult(result, flags, ui, (listResult) => {
137
137
  if (flags.json) {
@@ -8,7 +8,7 @@ import { Command } from 'commander';
8
8
  import { loadConfig } from '../config-loader';
9
9
  import { createControlClient } from '../control-api/client';
10
10
  import {
11
- type CliStructuredError,
11
+ CliStructuredError,
12
12
  errorDatabaseConnectionRequired,
13
13
  errorDriverRequired,
14
14
  errorUnexpected,
@@ -26,9 +26,9 @@ import {
26
26
  } from '../utils/command-helpers';
27
27
  import { formatStyledHeader } from '../utils/formatters/styled';
28
28
  import type { CommonCommandOptions } from '../utils/global-flags';
29
- import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
29
+ import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
30
30
  import { handleResult } from '../utils/result-handler';
31
- import { TerminalUI } from '../utils/terminal-ui';
31
+ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
32
32
 
33
33
  interface MigrationLogOptions extends CommonCommandOptions {
34
34
  readonly db?: string;
@@ -159,6 +159,7 @@ async function executeMigrationLogCommand(
159
159
  summary: `${entries.length} migration(s) applied`,
160
160
  });
161
161
  } catch (error) {
162
+ if (CliStructuredError.is(error)) return notOk(error);
162
163
  if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
163
164
  return notOk(
164
165
  errorUnexpected(error instanceof Error ? error.message : String(error), {
@@ -192,8 +193,8 @@ export function createMigrationLogCommand(): Command {
192
193
  .option('--db <url>', 'Database connection string')
193
194
  .option('--config <path>', 'Path to prisma-next.config.ts')
194
195
  .action(async (options: MigrationLogOptions) => {
195
- const flags = parseGlobalFlags(options);
196
- const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
196
+ const flags = parseGlobalFlagsOrExit(options);
197
+ const ui = createTerminalUI(flags);
197
198
  const result = await executeMigrationLogCommand(options, flags, ui);
198
199
  const exitCode = handleResult(result, flags, ui, (logResult) => {
199
200
  if (flags.json) {
@@ -49,9 +49,9 @@ import {
49
49
  import { formatStyledHeader } from '../utils/formatters/styled';
50
50
  import { assertFrameworkComponentsCompatible } from '../utils/framework-components';
51
51
  import type { CommonCommandOptions } from '../utils/global-flags';
52
- import { parseGlobalFlags } from '../utils/global-flags';
52
+ import { parseGlobalFlagsOrExit } from '../utils/global-flags';
53
53
  import { handleResult } from '../utils/result-handler';
54
- import { TerminalUI } from '../utils/terminal-ui';
54
+ import { createTerminalUI } from '../utils/terminal-ui';
55
55
 
56
56
  interface MigrationNewOptions extends CommonCommandOptions {
57
57
  readonly name?: string;
@@ -287,8 +287,8 @@ export function createMigrationNewCommand(): Command {
287
287
  .option('--from <hash>', 'Starting contract hash (default: latest migration target)')
288
288
  .option('--config <path>', 'Path to prisma-next.config.ts')
289
289
  .action(async (options: MigrationNewOptions) => {
290
- const flags = parseGlobalFlags(options);
291
- const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
290
+ const flags = parseGlobalFlagsOrExit(options);
291
+ const ui = createTerminalUI(flags);
292
292
 
293
293
  if (!flags.json && !flags.quiet) {
294
294
  const header = formatStyledHeader({