@prisma-next/cli 0.8.0-dev.7 → 0.8.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 (140) hide show
  1. package/README.md +7 -8
  2. package/dist/{cli-errors-D3_sMh2K.mjs → cli-errors-CF60g2cG.mjs} +40 -2
  3. package/dist/cli-errors-CF60g2cG.mjs.map +1 -0
  4. package/dist/cli.mjs +66 -18
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/{client-4d26awB-.mjs → client-XkUw4xD0.mjs} +10 -9
  7. package/dist/client-XkUw4xD0.mjs.map +1 -0
  8. package/dist/{command-helpers-BeZHkxV8.mjs → command-helpers-D3vL5yi8.mjs} +29 -6
  9. package/dist/command-helpers-D3vL5yi8.mjs.map +1 -0
  10. package/dist/commands/contract-emit.mjs +1 -1
  11. package/dist/commands/contract-infer.mjs +1 -1
  12. package/dist/commands/db-init.mjs +7 -7
  13. package/dist/commands/db-schema.mjs +5 -5
  14. package/dist/commands/db-sign.d.mts.map +1 -1
  15. package/dist/commands/db-sign.mjs +67 -25
  16. package/dist/commands/db-sign.mjs.map +1 -1
  17. package/dist/commands/db-update.d.mts.map +1 -1
  18. package/dist/commands/db-update.mjs +37 -9
  19. package/dist/commands/db-update.mjs.map +1 -1
  20. package/dist/commands/db-verify.mjs +1 -1
  21. package/dist/commands/migrate.d.mts +28 -0
  22. package/dist/commands/migrate.d.mts.map +1 -0
  23. package/dist/commands/{migration-apply.mjs → migrate.mjs} +54 -36
  24. package/dist/commands/migrate.mjs.map +1 -0
  25. package/dist/commands/migration-check.d.mts +18 -0
  26. package/dist/commands/migration-check.d.mts.map +1 -0
  27. package/dist/commands/migration-check.mjs +284 -0
  28. package/dist/commands/migration-check.mjs.map +1 -0
  29. package/dist/commands/migration-graph.d.mts +16 -0
  30. package/dist/commands/migration-graph.d.mts.map +1 -0
  31. package/dist/commands/migration-graph.mjs +141 -0
  32. package/dist/commands/migration-graph.mjs.map +1 -0
  33. package/dist/commands/migration-list.d.mts +20 -0
  34. package/dist/commands/migration-list.d.mts.map +1 -0
  35. package/dist/commands/migration-list.mjs +107 -0
  36. package/dist/commands/migration-list.mjs.map +1 -0
  37. package/dist/commands/migration-log.d.mts +21 -0
  38. package/dist/commands/migration-log.d.mts.map +1 -0
  39. package/dist/commands/migration-log.mjs +146 -0
  40. package/dist/commands/migration-log.mjs.map +1 -0
  41. package/dist/commands/migration-new.mjs +5 -5
  42. package/dist/commands/migration-plan.d.mts +2 -2
  43. package/dist/commands/migration-plan.d.mts.map +1 -1
  44. package/dist/commands/migration-plan.mjs +1 -1
  45. package/dist/commands/migration-show.d.mts +1 -1
  46. package/dist/commands/migration-show.d.mts.map +1 -1
  47. package/dist/commands/migration-show.mjs +85 -47
  48. package/dist/commands/migration-show.mjs.map +1 -1
  49. package/dist/commands/migration-status.d.mts +3 -15
  50. package/dist/commands/migration-status.d.mts.map +1 -1
  51. package/dist/commands/migration-status.mjs +732 -1
  52. package/dist/commands/migration-status.mjs.map +1 -0
  53. package/dist/commands/ref.d.mts +34 -0
  54. package/dist/commands/ref.d.mts.map +1 -0
  55. package/dist/commands/{migration-ref.mjs → ref.mjs} +28 -57
  56. package/dist/commands/ref.mjs.map +1 -0
  57. package/dist/{contract-emit-DLc5GYbr.mjs → contract-emit-CgoFk9AU.mjs} +3 -3
  58. package/dist/{contract-emit-DLc5GYbr.mjs.map → contract-emit-CgoFk9AU.mjs.map} +1 -1
  59. package/dist/{contract-emit-BhKR-D9Y.mjs → contract-emit-GpxW5RLe.mjs} +6 -6
  60. package/dist/{contract-emit-BhKR-D9Y.mjs.map → contract-emit-GpxW5RLe.mjs.map} +1 -1
  61. package/dist/{contract-infer-Bnla2kuK.mjs → contract-infer-D8edZOCi.mjs} +5 -5
  62. package/dist/{contract-infer-Bnla2kuK.mjs.map → contract-infer-D8edZOCi.mjs.map} +1 -1
  63. package/dist/{contract-space-aggregate-loader-BrwKK6Q6.mjs → contract-space-aggregate-loader-D68YpuPR.mjs} +3 -3
  64. package/dist/{contract-space-aggregate-loader-BrwKK6Q6.mjs.map → contract-space-aggregate-loader-D68YpuPR.mjs.map} +1 -1
  65. package/dist/{db-verify-DitNxDiE.mjs → db-verify-DtRB9iHJ.mjs} +7 -7
  66. package/dist/{db-verify-DitNxDiE.mjs.map → db-verify-DtRB9iHJ.mjs.map} +1 -1
  67. package/dist/errors-Cw6kyTyV.mjs +56 -0
  68. package/dist/errors-Cw6kyTyV.mjs.map +1 -0
  69. package/dist/exports/control-api.d.mts +1 -1
  70. package/dist/exports/control-api.mjs +2 -2
  71. package/dist/exports/index.mjs +1 -1
  72. package/dist/exports/init-output.mjs +1 -1
  73. package/dist/{framework-components-ChqVUxR-.mjs → framework-components-xFLFpZUO.mjs} +2 -2
  74. package/dist/{framework-components-ChqVUxR-.mjs.map → framework-components-xFLFpZUO.mjs.map} +1 -1
  75. package/dist/{global-flags-Icqpxk23.d.mts → global-flags-DGmw6Kqg.d.mts} +1 -1
  76. package/dist/{global-flags-Icqpxk23.d.mts.map → global-flags-DGmw6Kqg.d.mts.map} +1 -1
  77. package/dist/{migration-status-Do4Ei0i_.mjs → graph-render-eJDcLWny.mjs} +3 -692
  78. package/dist/graph-render-eJDcLWny.mjs.map +1 -0
  79. package/dist/{init-CcDCJMLk.mjs → init-e5Q4s1_5.mjs} +7 -6
  80. package/dist/{init-CcDCJMLk.mjs.map → init-e5Q4s1_5.mjs.map} +1 -1
  81. package/dist/{inspect-live-schema-CyzAzPzF.mjs → inspect-live-schema-CPPqCips.mjs} +4 -4
  82. package/dist/{inspect-live-schema-CyzAzPzF.mjs.map → inspect-live-schema-CPPqCips.mjs.map} +1 -1
  83. package/dist/migration-cli.mjs +1 -1
  84. package/dist/migration-cli.mjs.map +1 -1
  85. package/dist/{migration-command-scaffold-Jp1rosw8.mjs → migration-command-scaffold-B_ezTTwX.mjs} +4 -4
  86. package/dist/{migration-command-scaffold-Jp1rosw8.mjs.map → migration-command-scaffold-B_ezTTwX.mjs.map} +1 -1
  87. package/dist/{migration-plan-DTwYi61q.mjs → migration-plan-DWB-NTxH.mjs} +26 -24
  88. package/dist/migration-plan-DWB-NTxH.mjs.map +1 -0
  89. package/dist/migration-types-D2FW63pr.d.mts +15 -0
  90. package/dist/migration-types-D2FW63pr.d.mts.map +1 -0
  91. package/dist/{migrations-CTsyBXCA.mjs → migrations-DyUf5lTt.mjs} +2 -2
  92. package/dist/migrations-DyUf5lTt.mjs.map +1 -0
  93. package/dist/{output-DEg3SSnJ.mjs → output-B60Gw5fu.mjs} +1 -1
  94. package/dist/{output-DEg3SSnJ.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
  95. package/dist/{result-handler-rmPVKIP2.mjs → result-handler-Bm_6dDYg.mjs} +2 -2
  96. package/dist/{result-handler-rmPVKIP2.mjs.map → result-handler-Bm_6dDYg.mjs.map} +1 -1
  97. package/dist/{terminal-ui-C_hFNbAn.mjs → terminal-ui-XtOQsqe9.mjs} +2 -54
  98. package/dist/terminal-ui-XtOQsqe9.mjs.map +1 -0
  99. package/dist/{types-LItU7E4l.d.mts → types-BS_wpjAY.d.mts} +2 -2
  100. package/dist/{types-LItU7E4l.d.mts.map → types-BS_wpjAY.d.mts.map} +1 -1
  101. package/dist/{verify-CiwNWM9N.mjs → verify-D7ypCCe6.mjs} +1 -1
  102. package/dist/{verify-CiwNWM9N.mjs.map → verify-D7ypCCe6.mjs.map} +1 -1
  103. package/package.json +39 -23
  104. package/src/cli.ts +78 -15
  105. package/src/commands/db-sign.ts +102 -32
  106. package/src/commands/db-update.ts +56 -4
  107. package/src/commands/{migration-apply.ts → migrate.ts} +54 -70
  108. package/src/commands/migration-check/exit-codes.ts +3 -0
  109. package/src/commands/migration-check.ts +369 -0
  110. package/src/commands/migration-graph.ts +184 -0
  111. package/src/commands/migration-list.ts +155 -0
  112. package/src/commands/migration-log.ts +218 -0
  113. package/src/commands/migration-plan.ts +29 -22
  114. package/src/commands/migration-show.ts +132 -60
  115. package/src/commands/migration-status.ts +77 -64
  116. package/src/commands/{migration-ref.ts → ref.ts} +32 -86
  117. package/src/control-api/operations/apply-aggregate.ts +4 -4
  118. package/src/control-api/operations/db-apply-aggregate.ts +3 -2
  119. package/src/control-api/operations/migration-apply.ts +4 -3
  120. package/src/migration-cli.ts +1 -1
  121. package/src/utils/cli-errors.ts +37 -0
  122. package/src/utils/command-helpers.ts +28 -3
  123. package/src/utils/contract-space-aggregate-loader.ts +2 -2
  124. package/src/utils/contract-space-seed-phase.ts +1 -1
  125. package/src/utils/formatters/help.ts +12 -2
  126. package/src/utils/formatters/migrations.ts +2 -2
  127. package/dist/cli-errors-D3_sMh2K.mjs.map +0 -1
  128. package/dist/client-4d26awB-.mjs.map +0 -1
  129. package/dist/command-helpers-BeZHkxV8.mjs.map +0 -1
  130. package/dist/commands/migration-apply.d.mts +0 -51
  131. package/dist/commands/migration-apply.d.mts.map +0 -1
  132. package/dist/commands/migration-apply.mjs.map +0 -1
  133. package/dist/commands/migration-ref.d.mts +0 -45
  134. package/dist/commands/migration-ref.d.mts.map +0 -1
  135. package/dist/commands/migration-ref.mjs.map +0 -1
  136. package/dist/migration-plan-DTwYi61q.mjs.map +0 -1
  137. package/dist/migration-status-Do4Ei0i_.mjs.map +0 -1
  138. package/dist/migrations-CTsyBXCA.mjs.map +0 -1
  139. package/dist/terminal-ui-C_hFNbAn.mjs.map +0 -1
  140. /package/dist/{cli-errors-B9OBbled.d.mts → cli-errors-DdcjVLJV.d.mts} +0 -0
@@ -1,6 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import type { Contract } from '@prisma-next/contract/types';
3
3
  import {
4
+ APP_SPACE_ID,
4
5
  createControlStack,
5
6
  type MigrationPlanOperation,
6
7
  type OperationPreview,
@@ -12,6 +13,8 @@ import {
12
13
  reconstructGraph,
13
14
  } from '@prisma-next/migration-tools/migration-graph';
14
15
  import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
16
+ import { parseMigrationRef } from '@prisma-next/migration-tools/ref-resolution';
17
+ import { readRefs } from '@prisma-next/migration-tools/refs';
15
18
  import { spaceMigrationDirectory } from '@prisma-next/migration-tools/spaces';
16
19
  import { ifDefined } from '@prisma-next/utils/defined';
17
20
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
@@ -26,6 +29,7 @@ import {
26
29
  errorRuntime,
27
30
  errorUnexpected,
28
31
  mapMigrationToolsError,
32
+ mapRefResolutionError,
29
33
  } from '../utils/cli-errors';
30
34
  import {
31
35
  addGlobalOptions,
@@ -33,6 +37,7 @@ import {
33
37
  resolveMigrationPaths,
34
38
  setCommandDescriptions,
35
39
  setCommandExamples,
40
+ setCommandSeeAlso,
36
41
  } from '../utils/command-helpers';
37
42
  import { buildContractSpaceAggregate } from '../utils/contract-space-aggregate-loader';
38
43
  import { formatMigrationShowOutput } from '../utils/formatters/migrations';
@@ -237,7 +242,7 @@ async function executeMigrationShowCommand(
237
242
  ui: TerminalUI,
238
243
  ): Promise<Result<MigrationShowResult, CliStructuredError>> {
239
244
  const config = await loadConfig(options.config);
240
- const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative } =
245
+ const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative, refsDir } =
241
246
  resolveMigrationPaths(options.config, config);
242
247
 
243
248
  const contractPathAbsolute = resolveContractPath(config);
@@ -261,7 +266,91 @@ async function executeMigrationShowCommand(
261
266
  ui.stderr(header);
262
267
  }
263
268
 
264
- // Load the app contract so the aggregate loader can validate it.
269
+ // `migration show` is an offline command; the control client is constructed
270
+ // purely to dispatch the family-specific `toOperationPreview` capability and
271
+ // is not connected to a database.
272
+ const client = createControlClient({
273
+ family: config.family,
274
+ target: config.target,
275
+ adapter: config.adapter,
276
+ ...ifDefined('driver', config.driver),
277
+ extensionPacks: config.extensionPacks ?? [],
278
+ });
279
+
280
+ // Explicit-target path. Read the app-space migrations directory directly
281
+ // and resolve `target` against the app graph. We deliberately skip
282
+ // `buildContractSpaceAggregate` here for two reasons:
283
+ //
284
+ // 1. Functional: the user asked about ONE specific migration. They don't
285
+ // need extension-space enumeration; resolving + rendering the named
286
+ // package is enough.
287
+ // 2. UX: the aggregate's layout-integrity check (PN-MIG-5001) fires when
288
+ // an extension is declared but its migrations directory hasn't been
289
+ // materialised. Gating an offline read-only inspect command on that
290
+ // check forces users to run `migrate` against a database before they
291
+ // can see what a migration contains — which contradicts what an
292
+ // offline read-only verb should require.
293
+ //
294
+ // Same pattern as `migration list`, `migration graph`, `migration check`:
295
+ // those verbs read `appMigrationsDir` directly without ever consulting
296
+ // the aggregate.
297
+ if (target) {
298
+ try {
299
+ let appPkg: OnDiskMigrationPackage;
300
+ if (looksLikePath(target)) {
301
+ const resolved = resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative);
302
+ if (!resolved.ok) return resolved;
303
+ appPkg = await readMigrationPackage(resolved.value);
304
+ } else {
305
+ const allPackages = await readMigrationsDir(appMigrationsDir);
306
+ if (allPackages.length === 0) {
307
+ return notOk(
308
+ errorRuntime('No migrations found', {
309
+ why: `No migration packages found in ${appMigrationsRelative}`,
310
+ fix: 'Run `prisma-next migration plan` to create a migration first.',
311
+ }),
312
+ );
313
+ }
314
+ const graph = reconstructGraph(allPackages);
315
+ const refs = await readRefs(refsDir);
316
+ const migResult = parseMigrationRef(target, { graph, refs });
317
+ if (!migResult.ok) {
318
+ return notOk(mapRefResolutionError(migResult.failure));
319
+ }
320
+ const matchedPkg = allPackages.find(
321
+ (p) => p.metadata.migrationHash === migResult.value.migrationHash,
322
+ );
323
+ if (!matchedPkg) {
324
+ return notOk(
325
+ errorRuntime('Migration package not found', {
326
+ why: `Resolved migration "${migResult.value.dirName}" but the package was not loaded`,
327
+ fix: 'The migrations directory may be corrupted. Inspect the migration.json files.',
328
+ }),
329
+ );
330
+ }
331
+ appPkg = matchedPkg;
332
+ }
333
+ return ok({
334
+ ok: true,
335
+ spaces: [pkgToSpaceResult(APP_SPACE_ID, appPkg, client)],
336
+ });
337
+ } catch (error) {
338
+ if (MigrationToolsError.is(error)) {
339
+ return notOk(mapMigrationToolsError(error));
340
+ }
341
+ return notOk(
342
+ errorUnexpected(error instanceof Error ? error.message : String(error), {
343
+ why: `Failed to read app-space migration: ${error instanceof Error ? error.message : String(error)}`,
344
+ }),
345
+ );
346
+ }
347
+ }
348
+
349
+ // No-target path. Enumerate the latest migration per space (app +
350
+ // extensions). The aggregate-loader is needed here because we need to
351
+ // know which extension spaces are declared; its layout-integrity check
352
+ // is appropriate at this entry point because the user is asking the
353
+ // system to report on every loaded space.
265
354
  let contractJsonContent: string;
266
355
  try {
267
356
  contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');
@@ -293,7 +382,6 @@ async function executeMigrationShowCommand(
293
382
  );
294
383
  }
295
384
 
296
- // Build the aggregate against current disk state to enumerate all spaces.
297
385
  const stack = createControlStack(config);
298
386
  const familyInstance = config.family.create(stack);
299
387
  const aggregateResult = await buildContractSpaceAggregate({
@@ -308,66 +396,41 @@ async function executeMigrationShowCommand(
308
396
  }
309
397
  const aggregate = aggregateResult.value;
310
398
 
311
- // `migration show` is an offline command; the control client is constructed
312
- // purely to dispatch the family-specific `toOperationPreview` capability and
313
- // is not connected to a database.
314
- const client = createControlClient({
315
- family: config.family,
316
- target: config.target,
317
- adapter: config.adapter,
318
- ...ifDefined('driver', config.driver),
319
- extensionPacks: config.extensionPacks ?? [],
320
- });
321
-
322
399
  const spaces: MigrationShowSpaceResult[] = [];
323
400
 
324
- // App space: honour the `target` argument (path or hash prefix) when provided.
401
+ // App space: latest leaf.
325
402
  try {
326
- let appPkg: OnDiskMigrationPackage;
327
- if (target && looksLikePath(target)) {
328
- const resolved = resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative);
329
- if (!resolved.ok) return resolved;
330
- appPkg = await readMigrationPackage(resolved.value);
331
- } else {
332
- const allPackages = await readMigrationsDir(appMigrationsDir);
333
- if (allPackages.length === 0) {
334
- return notOk(
335
- errorRuntime('No migrations found', {
336
- why: `No migration packages found in ${appMigrationsRelative}`,
337
- fix: 'Run `prisma-next migration plan` to create a migration first.',
338
- }),
339
- );
340
- }
341
- if (target) {
342
- const resolved = resolveByHashPrefix(allPackages, target);
343
- if (!resolved.ok) return resolved;
344
- appPkg = resolved.value;
345
- } else {
346
- const graph = reconstructGraph(allPackages);
347
- const latestMigration = findLatestMigration(graph);
348
- if (!latestMigration) {
349
- return notOk(
350
- errorRuntime('Could not resolve latest migration', {
351
- why: 'No latest migration found in the migration history',
352
- fix: 'The migrations directory may be corrupted. Inspect the migration.json files.',
353
- }),
354
- );
355
- }
356
- const leafPkg = allPackages.find(
357
- (p) => p.metadata.migrationHash === latestMigration.migrationHash,
358
- );
359
- if (!leafPkg) {
360
- return notOk(
361
- errorRuntime('Could not resolve latest migration', {
362
- why: `Latest migration ${latestMigration.dirName} does not match any package`,
363
- fix: 'The migrations directory may be corrupted. Inspect the migration.json files.',
364
- }),
365
- );
366
- }
367
- appPkg = leafPkg;
368
- }
403
+ const allPackages = await readMigrationsDir(appMigrationsDir);
404
+ if (allPackages.length === 0) {
405
+ return notOk(
406
+ errorRuntime('No migrations found', {
407
+ why: `No migration packages found in ${appMigrationsRelative}`,
408
+ fix: 'Run `prisma-next migration plan` to create a migration first.',
409
+ }),
410
+ );
411
+ }
412
+ const graph = reconstructGraph(allPackages);
413
+ const latestMigration = findLatestMigration(graph);
414
+ if (!latestMigration) {
415
+ return notOk(
416
+ errorRuntime('Could not resolve latest migration', {
417
+ why: 'No latest migration found in the migration history',
418
+ fix: 'The migrations directory may be corrupted. Inspect the migration.json files.',
419
+ }),
420
+ );
369
421
  }
370
- spaces.push(pkgToSpaceResult(aggregate.app.spaceId, appPkg, client));
422
+ const leafPkg = allPackages.find(
423
+ (p) => p.metadata.migrationHash === latestMigration.migrationHash,
424
+ );
425
+ if (!leafPkg) {
426
+ return notOk(
427
+ errorRuntime('Could not resolve latest migration', {
428
+ why: `Latest migration ${latestMigration.dirName} does not match any package`,
429
+ fix: 'The migrations directory may be corrupted. Inspect the migration.json files.',
430
+ }),
431
+ );
432
+ }
433
+ spaces.push(pkgToSpaceResult(aggregate.app.spaceId, leafPkg, client));
371
434
  } catch (error) {
372
435
  if (MigrationToolsError.is(error)) {
373
436
  return notOk(mapMigrationToolsError(error));
@@ -415,8 +478,17 @@ export function createMigrationShowCommand(): Command {
415
478
  'prisma-next migration show',
416
479
  'prisma-next migration show sha256:a1b2c3',
417
480
  ]);
481
+ setCommandSeeAlso(command, [
482
+ { verb: 'migration status', oneLiner: 'Show migration path and pending status' },
483
+ { verb: 'migration log', oneLiner: 'Show executed migration history' },
484
+ { verb: 'migration list', oneLiner: 'List on-disk migrations' },
485
+ { verb: 'migration graph', oneLiner: 'Show the migration graph topology' },
486
+ ]);
418
487
  addGlobalOptions(command)
419
- .argument('[target]', 'App-space migration path or migrationHash prefix (defaults to latest)')
488
+ .argument(
489
+ '[target]',
490
+ 'Migration reference: directory name, hash/prefix, or path (defaults to latest)',
491
+ )
420
492
  .option('--config <path>', 'Path to prisma-next.config.ts')
421
493
  .action(async (target: string | undefined, options: MigrationShowOptions) => {
422
494
  const flags = parseGlobalFlags(options);
@@ -19,8 +19,9 @@ import {
19
19
  findReachableLeaves,
20
20
  } from '@prisma-next/migration-tools/migration-graph';
21
21
  import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
22
+ import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
22
23
  import type { RefEntry, Refs } from '@prisma-next/migration-tools/refs';
23
- import { readRefs, resolveRef } from '@prisma-next/migration-tools/refs';
24
+ import { readRefs } from '@prisma-next/migration-tools/refs';
24
25
  import { ifDefined } from '@prisma-next/utils/defined';
25
26
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
26
27
  import { cyan, dim, magenta, yellow } from 'colorette';
@@ -33,6 +34,7 @@ import {
33
34
  errorRuntime,
34
35
  errorUnexpected,
35
36
  mapMigrationToolsError,
37
+ mapRefResolutionError,
36
38
  } from '../utils/cli-errors';
37
39
  import {
38
40
  addGlobalOptions,
@@ -43,6 +45,7 @@ import {
43
45
  resolveMigrationPaths,
44
46
  setCommandDescriptions,
45
47
  setCommandExamples,
48
+ setCommandSeeAlso,
46
49
  toPathDecisionResult,
47
50
  toStructuralEdge,
48
51
  } from '../utils/command-helpers';
@@ -70,10 +73,8 @@ import { TerminalUI } from '../utils/terminal-ui';
70
73
  interface MigrationStatusOptions extends CommonCommandOptions {
71
74
  readonly db?: string;
72
75
  readonly config?: string;
73
- readonly ref?: string;
74
- readonly graph?: boolean;
75
- readonly limit?: string;
76
- readonly all?: boolean;
76
+ readonly to?: string;
77
+ readonly from?: string;
77
78
  }
78
79
 
79
80
  export interface MigrationStatusEntry {
@@ -94,7 +95,7 @@ export interface MigrationStatusEntry {
94
95
  *
95
96
  * - `headHash`: the on-disk head ref's hash (where the space is going).
96
97
  * - `markerHash`: the live marker hash for the space, or null if no
97
- * marker has been written yet (greenfield, or pre-`migration apply`).
98
+ * marker has been written yet (greenfield, or pre-`migrate`).
98
99
  * - `pendingCount`: number of migration edges between marker and head.
99
100
  * Computed via {@link graphWalkStrategy}; 0 means the space is
100
101
  * already at head.
@@ -420,23 +421,6 @@ function resolveDisplayChain(
420
421
  return toTarget;
421
422
  }
422
423
 
423
- const DEFAULT_LIMIT = 10;
424
-
425
- function determineLimit(opts: MigrationStatusOptions) {
426
- if (opts.all) {
427
- // No limit
428
- return;
429
- }
430
- if (!opts.limit) {
431
- return DEFAULT_LIMIT;
432
- }
433
- const parsed = Number.parseInt(opts.limit, 10);
434
- if (Number.isNaN(parsed)) {
435
- return DEFAULT_LIMIT;
436
- }
437
- return parsed;
438
- }
439
-
440
424
  /**
441
425
  * Build the aggregate enumeration of contract spaces for the status
442
426
  * output. Loads the aggregate from disk (lossy on failure — extension
@@ -581,11 +565,32 @@ async function executeMigrationStatusCommand(
581
565
  throw error;
582
566
  }
583
567
 
584
- if (options.ref) {
585
- activeRefName = options.ref;
568
+ let fromOverrideHash: string | undefined;
569
+
570
+ if (options.to || options.from) {
586
571
  try {
587
- activeRefEntry = resolveRef(allRefs, activeRefName);
588
- activeRefHash = activeRefEntry.hash;
572
+ const { graph: earlyGraph } = await loadMigrationPackages(appMigrationsDir);
573
+
574
+ if (options.to) {
575
+ const refResult = parseContractRef(options.to, { graph: earlyGraph, refs: allRefs });
576
+ if (!refResult.ok) {
577
+ return notOk(mapRefResolutionError(refResult.failure));
578
+ }
579
+ activeRefHash = refResult.value.hash;
580
+ if (refResult.value.provenance.kind === 'ref') {
581
+ const resolvedRefName = refResult.value.provenance.refName;
582
+ activeRefName = resolvedRefName;
583
+ activeRefEntry = allRefs[resolvedRefName];
584
+ }
585
+ }
586
+
587
+ if (options.from) {
588
+ const fromResult = parseContractRef(options.from, { graph: earlyGraph, refs: allRefs });
589
+ if (!fromResult.ok) {
590
+ return notOk(mapRefResolutionError(fromResult.failure));
591
+ }
592
+ fromOverrideHash = fromResult.value.hash;
593
+ }
589
594
  } catch (error) {
590
595
  if (MigrationToolsError.is(error)) {
591
596
  return notOk(mapMigrationToolsError(error));
@@ -613,6 +618,9 @@ async function executeMigrationStatusCommand(
613
618
  if (activeRefName) {
614
619
  details.push({ label: 'ref', value: activeRefName });
615
620
  }
621
+ if (options.from) {
622
+ details.push({ label: 'from', value: options.from });
623
+ }
616
624
  if (activeRefEntry && activeRefEntry.invariants.length > 0) {
617
625
  details.push({
618
626
  label: 'required',
@@ -696,8 +704,8 @@ async function executeMigrationStatusCommand(
696
704
  severity: 'warn',
697
705
  message: 'There are multiple valid migration paths — you must select a target',
698
706
  hints: [
699
- "Use '--ref <name>' to select a target",
700
- "Or 'prisma-next migration ref set <name> <hash>' to create one",
707
+ "Use '--to <contract>' to select a target",
708
+ "Or 'prisma-next ref set <name> <hash>' to create one",
701
709
  ],
702
710
  });
703
711
  }
@@ -751,6 +759,12 @@ async function executeMigrationStatusCommand(
751
759
  }
752
760
  }
753
761
 
762
+ if (fromOverrideHash !== undefined) {
763
+ markerHash = fromOverrideHash;
764
+ mode = 'offline';
765
+ allMarkers = null;
766
+ }
767
+
754
768
  // Build the aggregate enumeration of contract spaces. Lossy on
755
769
  // failure (extensions are simply omitted) so the existing
756
770
  // single-space app pipeline below still runs even if extensions
@@ -863,7 +877,7 @@ async function executeMigrationStatusCommand(
863
877
  code: 'MIGRATION.NO_MARKER',
864
878
  severity: 'warn',
865
879
  message: 'Database has not been initialized — no migration marker found',
866
- hints: ["Run 'prisma-next migration apply' to apply pending migrations"],
880
+ hints: ["Run 'prisma-next migrate' to apply pending migrations"],
867
881
  });
868
882
  }
869
883
 
@@ -923,7 +937,7 @@ async function executeMigrationStatusCommand(
923
937
  let missingInvariants: readonly string[] | undefined;
924
938
  let effectiveRequired = new Set<string>();
925
939
  if (mode === 'online') {
926
- // Mirrors `migration-apply.ts`: compute `effectiveRequired = required −
940
+ // Mirrors `migrate.ts`: compute `effectiveRequired = required −
927
941
  // marker.invariants` directly, then derive the display fields from it.
928
942
  // `appliedInvariants` is the intersection (`required ∩ marker`), which
929
943
  // is what JSON consumers see for the active ref; the unfiltered set
@@ -945,17 +959,17 @@ async function executeMigrationStatusCommand(
945
959
  if (mode === 'online') {
946
960
  if (markerHash !== undefined && !graph.nodes.has(markerHash) && markerHash === contractHash) {
947
961
  summary = `${bundles.length} migration(s) on disk`;
948
- } else if (activeRefHash && markerHash !== undefined) {
949
- const distance = summarizeRefDistance(graph, markerHash, activeRefHash, activeRefName!);
962
+ } else if (activeRefHash && activeRefName && markerHash !== undefined) {
963
+ const distance = summarizeRefDistance(graph, markerHash, activeRefHash, activeRefName);
950
964
  summary = hasInvariantWork ? `${distance} — missing invariant(s): ${missingList}` : distance;
951
965
  } else if (pendingCount === 0 && !hasInvariantWork) {
952
966
  summary = `Database is up to date (${appliedCount} migration${appliedCount !== 1 ? 's' : ''} applied)`;
953
967
  } else if (pendingCount === 0 && hasInvariantWork) {
954
- summary = `Missing invariant(s): ${missingList} — run 'prisma-next migration apply --ref ${activeRefName ?? '<ref>'}' to apply`;
968
+ summary = `Missing invariant(s): ${missingList} — run 'prisma-next migrate --to ${activeRefName ?? '<ref>'}' to apply`;
955
969
  } else if (markerHash === undefined) {
956
970
  summary = `${pendingCount} pending migration(s) — database has no marker`;
957
971
  } else {
958
- summary = `${pendingCount} pending migration(s) — run 'prisma-next migration apply' to apply`;
972
+ summary = `${pendingCount} pending migration(s) — run 'prisma-next migrate' to apply`;
959
973
  }
960
974
  } else {
961
975
  summary = `${entries.length} migration(s) on disk`;
@@ -997,8 +1011,7 @@ async function executeMigrationStatusCommand(
997
1011
  diagnostics.push({
998
1012
  code: 'MIGRATION.MARKER_NOT_IN_HISTORY',
999
1013
  severity: 'warn',
1000
- message:
1001
- 'Database matches the current contract but was updated directly (not via migration apply)',
1014
+ message: 'Database matches the current contract but was updated directly (not via migrate)',
1002
1015
  hints: ["Run 'prisma-next migration plan' to plan a migration to your current contract"],
1003
1016
  });
1004
1017
  } else if (pendingCount > 0) {
@@ -1006,7 +1019,7 @@ async function executeMigrationStatusCommand(
1006
1019
  code: 'MIGRATION.DATABASE_BEHIND',
1007
1020
  severity: 'info',
1008
1021
  message: `${pendingCount} migration(s) pending`,
1009
- hints: ["Run 'prisma-next migration apply' to apply pending migrations"],
1022
+ hints: ["Run 'prisma-next migrate' to apply pending migrations"],
1010
1023
  });
1011
1024
  } else if (hasInvariantWork) {
1012
1025
  diagnostics.push({
@@ -1014,7 +1027,7 @@ async function executeMigrationStatusCommand(
1014
1027
  severity: 'info',
1015
1028
  message: `Missing required invariant(s): ${missingList}`,
1016
1029
  hints: [
1017
- `Run 'prisma-next migration apply --ref ${activeRefName ?? '<ref>'}' to apply a path that covers the required invariants`,
1030
+ `Run 'prisma-next migrate --to ${activeRefName ?? '<ref>'}' to apply a path that covers the required invariants`,
1018
1031
  ],
1019
1032
  });
1020
1033
  } else if (!routingUnreachable) {
@@ -1056,37 +1069,41 @@ export function createMigrationStatusCommand(): Command {
1056
1069
  const command = new Command('status');
1057
1070
  setCommandDescriptions(
1058
1071
  command,
1059
- 'Show migration history and applied status',
1060
- 'Displays the migration history in order. When a database connection\n' +
1061
- 'is available, shows which migrations are applied and which are pending.\n' +
1062
- 'Without a database connection, shows the history from disk only.',
1072
+ 'Show migration path and pending status',
1073
+ 'Shows which migrations are pending between the database marker and\n' +
1074
+ 'the target contract. Requires a database connection for live status.\n' +
1075
+ 'Use `migration graph` for topology, `migration log` for history,\n' +
1076
+ 'and `migration list` for on-disk enumeration.',
1063
1077
  );
1064
1078
  setCommandExamples(command, [
1065
- 'prisma-next migration status',
1066
1079
  'prisma-next migration status --db $DATABASE_URL',
1080
+ 'prisma-next migration status --to production --db $DATABASE_URL',
1081
+ ]);
1082
+ setCommandSeeAlso(command, [
1083
+ { verb: 'migration log', oneLiner: 'Show executed migration history' },
1084
+ { verb: 'migration list', oneLiner: 'List on-disk migrations' },
1085
+ { verb: 'migration graph', oneLiner: 'Show the migration graph topology' },
1086
+ { verb: 'migration show', oneLiner: 'Display migration package contents' },
1067
1087
  ]);
1068
1088
  addGlobalOptions(command)
1069
1089
  .option('--db <url>', 'Database connection string')
1070
1090
  .option('--config <path>', 'Path to prisma-next.config.ts')
1071
- .option('--ref <name>', 'Target ref name from migrations/refs/')
1072
- .option('--graph', 'Show the full migration graph with all branches')
1073
- .option('--limit <n>', 'Maximum number of migrations to display (default: 10)')
1074
- .option('--all', 'Show full history (disables truncation)')
1091
+ .option(
1092
+ '--to <contract>',
1093
+ 'Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
1094
+ )
1095
+ .option(
1096
+ '--from <contract>',
1097
+ 'Origin contract reference; same grammar as --to. Supplying --from switches to offline path computation.',
1098
+ )
1075
1099
  .action(async (options: MigrationStatusOptions) => {
1076
1100
  const flags = parseGlobalFlags(options);
1077
-
1078
1101
  const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
1079
1102
 
1080
1103
  const result = await executeMigrationStatusCommand(options, flags, ui);
1081
1104
 
1082
1105
  const exitCode = handleResult(result, flags, ui, (statusResult) => {
1083
1106
  if (flags.json) {
1084
- // Strip non-JSON-shape fields before emitting. These belong to
1085
- // the in-memory result so the human renderer can avoid
1086
- // recomputing them, but they would either bloat the wire format
1087
- // (graph, bundles, edgeStatuses) or expose internals
1088
- // (activeRefHash, activeRefName, diverged) that consumers should
1089
- // read off `pathDecision` / `refs` instead.
1090
1107
  const {
1091
1108
  graph: _graph,
1092
1109
  bundles: _bundles,
@@ -1101,7 +1118,6 @@ export function createMigrationStatusCommand(): Command {
1101
1118
  const colorize = flags.color !== false;
1102
1119
 
1103
1120
  if (statusResult.graph) {
1104
- const limit = determineLimit(options);
1105
1121
  const renderInput = migrationGraphToRenderInput({
1106
1122
  graph: statusResult.graph,
1107
1123
  mode: statusResult.mode,
@@ -1113,16 +1129,13 @@ export function createMigrationStatusCommand(): Command {
1113
1129
  edgeStatuses: statusResult.edgeStatuses,
1114
1130
  });
1115
1131
 
1116
- const graphToRender =
1117
- options.graph || statusResult.diverged
1118
- ? renderInput.graph
1119
- : extractRelevantSubgraph(renderInput.graph, renderInput.relevantPaths);
1120
- const dagreOptions =
1121
- !options.graph && isLinearGraph(graphToRender) ? { ranksep: 1 } : undefined;
1132
+ const graphToRender = statusResult.diverged
1133
+ ? renderInput.graph
1134
+ : extractRelevantSubgraph(renderInput.graph, renderInput.relevantPaths);
1135
+ const dagreOptions = isLinearGraph(graphToRender) ? { ranksep: 1 } : undefined;
1122
1136
  const renderOptions = {
1123
1137
  ...renderInput.options,
1124
1138
  colorize,
1125
- ...ifDefined('limit', limit),
1126
1139
  ...ifDefined('dagreOptions', dagreOptions),
1127
1140
  };
1128
1141
  const graphOutput = graphRenderer.render(graphToRender, renderOptions);
@@ -1209,7 +1222,7 @@ export function formatStatusSummary(result: MigrationStatusResult, colorize: boo
1209
1222
  if (total > 0) {
1210
1223
  lines.push('');
1211
1224
  lines.push(
1212
- `${c(yellow, '⧗')} ${total} pending migration(s) across ${result.spaces.length} space(s) — run 'prisma-next migration apply' to apply`,
1225
+ `${c(yellow, '⧗')} ${total} pending migration(s) across ${result.spaces.length} space(s) — run 'prisma-next migrate' to apply`,
1213
1226
  );
1214
1227
  }
1215
1228
  }