@prisma-next/cli 0.8.0 → 0.9.0-dev.2

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 (163) hide show
  1. package/README.md +8 -9
  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 +67 -19
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/{client-BCnP7cHo.mjs → client-Brv4qlfB.mjs} +28 -30
  7. package/dist/client-Brv4qlfB.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.d.mts.map +1 -1
  21. package/dist/commands/db-verify.mjs +1 -1
  22. package/dist/commands/migrate.d.mts +28 -0
  23. package/dist/commands/migrate.d.mts.map +1 -0
  24. package/dist/commands/{migration-apply.mjs → migrate.mjs} +65 -39
  25. package/dist/commands/migrate.mjs.map +1 -0
  26. package/dist/commands/migration-check.d.mts +18 -0
  27. package/dist/commands/migration-check.d.mts.map +1 -0
  28. package/dist/commands/migration-check.mjs +284 -0
  29. package/dist/commands/migration-check.mjs.map +1 -0
  30. package/dist/commands/migration-graph.d.mts +16 -0
  31. package/dist/commands/migration-graph.d.mts.map +1 -0
  32. package/dist/commands/migration-graph.mjs +141 -0
  33. package/dist/commands/migration-graph.mjs.map +1 -0
  34. package/dist/commands/migration-list.d.mts +20 -0
  35. package/dist/commands/migration-list.d.mts.map +1 -0
  36. package/dist/commands/migration-list.mjs +107 -0
  37. package/dist/commands/migration-list.mjs.map +1 -0
  38. package/dist/commands/migration-log.d.mts +21 -0
  39. package/dist/commands/migration-log.d.mts.map +1 -0
  40. package/dist/commands/migration-log.mjs +146 -0
  41. package/dist/commands/migration-log.mjs.map +1 -0
  42. package/dist/commands/migration-new.d.mts.map +1 -1
  43. package/dist/commands/migration-new.mjs +30 -29
  44. package/dist/commands/migration-new.mjs.map +1 -1
  45. package/dist/commands/migration-plan.d.mts +2 -2
  46. package/dist/commands/migration-plan.d.mts.map +1 -1
  47. package/dist/commands/migration-plan.mjs +1 -1
  48. package/dist/commands/migration-show.d.mts +1 -1
  49. package/dist/commands/migration-show.d.mts.map +1 -1
  50. package/dist/commands/migration-show.mjs +90 -52
  51. package/dist/commands/migration-show.mjs.map +1 -1
  52. package/dist/commands/migration-status.d.mts +5 -17
  53. package/dist/commands/migration-status.d.mts.map +1 -1
  54. package/dist/commands/migration-status.mjs +732 -1
  55. package/dist/commands/migration-status.mjs.map +1 -0
  56. package/dist/commands/ref.d.mts +34 -0
  57. package/dist/commands/ref.d.mts.map +1 -0
  58. package/dist/commands/{migration-ref.mjs → ref.mjs} +28 -57
  59. package/dist/commands/ref.mjs.map +1 -0
  60. package/dist/{contract-emit-9DBda5Ou.mjs → contract-emit-C3STUIBg.mjs} +6 -6
  61. package/dist/{contract-emit-9DBda5Ou.mjs.map → contract-emit-C3STUIBg.mjs.map} +1 -1
  62. package/dist/{contract-emit-B77TsJqf.mjs → contract-emit-iynA3BCA.mjs} +9 -5
  63. package/dist/contract-emit-iynA3BCA.mjs.map +1 -0
  64. package/dist/{contract-infer-ByxhPjpW.mjs → contract-infer-Cnj8G1E2.mjs} +5 -5
  65. package/dist/{contract-infer-ByxhPjpW.mjs.map → contract-infer-Cnj8G1E2.mjs.map} +1 -1
  66. package/dist/{contract-space-aggregate-loader-BrwKK6Q6.mjs → contract-space-aggregate-loader-pAc8CDfY.mjs} +4 -4
  67. package/dist/{contract-space-aggregate-loader-BrwKK6Q6.mjs.map → contract-space-aggregate-loader-pAc8CDfY.mjs.map} +1 -1
  68. package/dist/{db-verify-Czm5T-J4.mjs → db-verify-D7cyH_zz.mjs} +12 -9
  69. package/dist/db-verify-D7cyH_zz.mjs.map +1 -0
  70. package/dist/errors-Cw6kyTyV.mjs +56 -0
  71. package/dist/errors-Cw6kyTyV.mjs.map +1 -0
  72. package/dist/exports/control-api.d.mts +1 -1
  73. package/dist/exports/control-api.d.mts.map +1 -1
  74. package/dist/exports/control-api.mjs +2 -2
  75. package/dist/exports/index.mjs +1 -1
  76. package/dist/exports/index.mjs.map +1 -1
  77. package/dist/exports/init-output.mjs +1 -1
  78. package/dist/{framework-components-ChqVUxR-.mjs → framework-components-xFLFpZUO.mjs} +2 -2
  79. package/dist/{framework-components-ChqVUxR-.mjs.map → framework-components-xFLFpZUO.mjs.map} +1 -1
  80. package/dist/{global-flags-Icqpxk23.d.mts → global-flags-DGmw6Kqg.d.mts} +1 -1
  81. package/dist/{global-flags-Icqpxk23.d.mts.map → global-flags-DGmw6Kqg.d.mts.map} +1 -1
  82. package/dist/{migration-status-By9G5p2H.mjs → graph-render-eJDcLWny.mjs} +3 -692
  83. package/dist/graph-render-eJDcLWny.mjs.map +1 -0
  84. package/dist/{init-B-k3a1Qw.mjs → init-DEe-IimY.mjs} +133 -61
  85. package/dist/init-DEe-IimY.mjs.map +1 -0
  86. package/dist/{inspect-live-schema-DxdBd4Er.mjs → inspect-live-schema-CWLK_lgs.mjs} +4 -4
  87. package/dist/{inspect-live-schema-DxdBd4Er.mjs.map → inspect-live-schema-CWLK_lgs.mjs.map} +1 -1
  88. package/dist/migration-cli.mjs +1 -1
  89. package/dist/migration-cli.mjs.map +1 -1
  90. package/dist/{migration-command-scaffold-BdV8JYXV.mjs → migration-command-scaffold-CmXXC1UZ.mjs} +4 -4
  91. package/dist/{migration-command-scaffold-BdV8JYXV.mjs.map → migration-command-scaffold-CmXXC1UZ.mjs.map} +1 -1
  92. package/dist/{migration-plan-mRu5K81L.mjs → migration-plan-CHyUlBV0.mjs} +76 -37
  93. package/dist/migration-plan-CHyUlBV0.mjs.map +1 -0
  94. package/dist/migration-types-D2FW63pr.d.mts +15 -0
  95. package/dist/migration-types-D2FW63pr.d.mts.map +1 -0
  96. package/dist/{migrations-CTsyBXCA.mjs → migrations-DyUf5lTt.mjs} +2 -2
  97. package/dist/migrations-DyUf5lTt.mjs.map +1 -0
  98. package/dist/{output-BVj6a971.mjs → output-B60Gw5fu.mjs} +12 -11
  99. package/dist/{output-BVj6a971.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
  100. package/dist/{result-handler-rmPVKIP2.mjs → result-handler-Bm_6dDYg.mjs} +2 -2
  101. package/dist/{result-handler-rmPVKIP2.mjs.map → result-handler-Bm_6dDYg.mjs.map} +1 -1
  102. package/dist/{terminal-ui-C_hFNbAn.mjs → terminal-ui-XtOQsqe9.mjs} +2 -54
  103. package/dist/terminal-ui-XtOQsqe9.mjs.map +1 -0
  104. package/dist/{types-LItU7E4l.d.mts → types-0aS865QN.d.mts} +14 -8
  105. package/dist/types-0aS865QN.d.mts.map +1 -0
  106. package/dist/{verify-CiwNWM9N.mjs → verify-D7ypCCe6.mjs} +1 -1
  107. package/dist/{verify-CiwNWM9N.mjs.map → verify-D7ypCCe6.mjs.map} +1 -1
  108. package/package.json +39 -23
  109. package/src/cli.ts +78 -15
  110. package/src/commands/db-sign.ts +102 -32
  111. package/src/commands/db-update.ts +56 -4
  112. package/src/commands/db-verify.ts +19 -3
  113. package/src/commands/init/agent-skill-install.ts +145 -43
  114. package/src/commands/init/errors.ts +2 -2
  115. package/src/commands/init/exit-codes.ts +2 -2
  116. package/src/commands/init/index.ts +1 -1
  117. package/src/commands/init/init.ts +15 -6
  118. package/src/commands/init/inputs.ts +1 -1
  119. package/src/commands/init/output.ts +22 -17
  120. package/src/commands/{migration-apply.ts → migrate.ts} +77 -73
  121. package/src/commands/migration-check/exit-codes.ts +3 -0
  122. package/src/commands/migration-check.ts +369 -0
  123. package/src/commands/migration-graph.ts +184 -0
  124. package/src/commands/migration-list.ts +155 -0
  125. package/src/commands/migration-log.ts +218 -0
  126. package/src/commands/migration-new.ts +30 -22
  127. package/src/commands/migration-plan.ts +104 -35
  128. package/src/commands/migration-show.ts +141 -65
  129. package/src/commands/migration-status.ts +82 -69
  130. package/src/commands/{migration-ref.ts → ref.ts} +32 -86
  131. package/src/control-api/client.ts +30 -21
  132. package/src/control-api/operations/apply-aggregate.ts +4 -4
  133. package/src/control-api/operations/contract-emit.ts +26 -3
  134. package/src/control-api/operations/db-apply-aggregate.ts +4 -3
  135. package/src/control-api/operations/db-verify.ts +2 -2
  136. package/src/control-api/operations/migration-apply.ts +5 -4
  137. package/src/control-api/types.ts +12 -7
  138. package/src/load-ts-contract.ts +9 -1
  139. package/src/migration-cli.ts +1 -1
  140. package/src/utils/cli-errors.ts +37 -0
  141. package/src/utils/command-helpers.ts +28 -3
  142. package/src/utils/contract-space-aggregate-loader.ts +4 -4
  143. package/src/utils/contract-space-seed-phase.ts +2 -2
  144. package/src/utils/formatters/help.ts +12 -2
  145. package/src/utils/formatters/migrations.ts +2 -2
  146. package/dist/cli-errors-D3_sMh2K.mjs.map +0 -1
  147. package/dist/client-BCnP7cHo.mjs.map +0 -1
  148. package/dist/command-helpers-BeZHkxV8.mjs.map +0 -1
  149. package/dist/commands/migration-apply.d.mts +0 -51
  150. package/dist/commands/migration-apply.d.mts.map +0 -1
  151. package/dist/commands/migration-apply.mjs.map +0 -1
  152. package/dist/commands/migration-ref.d.mts +0 -45
  153. package/dist/commands/migration-ref.d.mts.map +0 -1
  154. package/dist/commands/migration-ref.mjs.map +0 -1
  155. package/dist/contract-emit-B77TsJqf.mjs.map +0 -1
  156. package/dist/db-verify-Czm5T-J4.mjs.map +0 -1
  157. package/dist/init-B-k3a1Qw.mjs.map +0 -1
  158. package/dist/migration-plan-mRu5K81L.mjs.map +0 -1
  159. package/dist/migration-status-By9G5p2H.mjs.map +0 -1
  160. package/dist/migrations-CTsyBXCA.mjs.map +0 -1
  161. package/dist/terminal-ui-C_hFNbAn.mjs.map +0 -1
  162. package/dist/types-LItU7E4l.d.mts.map +0 -1
  163. /package/dist/{cli-errors-B9OBbled.d.mts → cli-errors-DdcjVLJV.d.mts} +0 -0
@@ -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
@@ -453,15 +437,15 @@ export async function loadAggregateStatusSpaces(args: {
453
437
  readonly migrationsDir: string;
454
438
  readonly appContractRaw: unknown;
455
439
  readonly extensionPacks: BuildAggregateInputs<string, string>['extensionPacks'];
456
- readonly validateContract: BuildAggregateInputs<string, string>['validateContract'];
440
+ readonly deserializeContract: BuildAggregateInputs<string, string>['deserializeContract'];
457
441
  readonly markersBySpace: ReadonlyMap<string, ContractMarkerRecordLike> | null;
458
442
  }): Promise<readonly MigrationStatusSpaceEntry[]> {
459
443
  const loadInputs: BuildAggregateInputs<string, string> = {
460
444
  targetId: args.targetId,
461
445
  migrationsDir: args.migrationsDir,
462
- appContract: args.validateContract(args.appContractRaw),
446
+ appContract: args.deserializeContract(args.appContractRaw),
463
447
  extensionPacks: args.extensionPacks,
464
- validateContract: args.validateContract,
448
+ deserializeContract: args.deserializeContract,
465
449
  };
466
450
 
467
451
  const loaded = await buildContractSpaceAggregate(loadInputs);
@@ -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
@@ -760,7 +774,7 @@ async function executeMigrationStatusCommand(
760
774
  let aggregateSpaces: readonly MigrationStatusSpaceEntry[] = [];
761
775
  if (contractRawForAggregate !== null) {
762
776
  // The aggregate loader needs a typed-Contract producer. Build a
763
- // real control stack so `validateContract` runs against a fully
777
+ // real control stack so `deserializeContract` runs against a fully
764
778
  // composed family instance — descriptors that read stack members
765
779
  // during construction (e.g. codec lookups) get a consistent view.
766
780
  const stack = createControlStack(config);
@@ -771,7 +785,7 @@ async function executeMigrationStatusCommand(
771
785
  migrationsDir,
772
786
  appContractRaw: contractRawForAggregate,
773
787
  extensionPacks: config.extensionPacks ?? [],
774
- validateContract: (json: unknown) => familyInstance.validateContract(json),
788
+ deserializeContract: (json: unknown) => familyInstance.deserializeContract(json),
775
789
  markersBySpace: allMarkers,
776
790
  });
777
791
  } catch {
@@ -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
  }
@@ -1,8 +1,8 @@
1
1
  import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
2
+ import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
2
3
  import type { RefEntry } from '@prisma-next/migration-tools/refs';
3
4
  import {
4
5
  deleteRef,
5
- readRef,
6
6
  readRefs,
7
7
  validateRefName,
8
8
  validateRefValue,
@@ -16,9 +16,11 @@ import {
16
16
  errorRuntime,
17
17
  errorUnexpected,
18
18
  mapMigrationToolsError,
19
+ mapRefResolutionError,
19
20
  } from '../utils/cli-errors';
20
21
  import {
21
22
  addGlobalOptions,
23
+ loadMigrationPackages,
22
24
  resolveMigrationPaths,
23
25
  setCommandDescriptions,
24
26
  } from '../utils/command-helpers';
@@ -34,13 +36,6 @@ interface RefSetResult {
34
36
  readonly invariants: readonly string[];
35
37
  }
36
38
 
37
- interface RefGetResult {
38
- readonly ok: true;
39
- readonly ref: string;
40
- readonly hash: string;
41
- readonly invariants: readonly string[];
42
- }
43
-
44
39
  interface RefDeleteResult {
45
40
  readonly ok: true;
46
41
  readonly ref: string;
@@ -66,53 +61,42 @@ function cliErrorInvalidRefName(name: string): CliStructuredError {
66
61
  });
67
62
  }
68
63
 
69
- function cliErrorInvalidRefValue(hash: string): CliStructuredError {
70
- return errorRuntime(`Invalid contract hash "${hash}"`, {
71
- why: `"${hash}" is not a valid contract hash`,
72
- fix: 'Contract hashes must match the format "sha256:<64 hex chars>". Copy the hash from `prisma-next contract emit` or `migration status --json`.',
73
- });
74
- }
75
-
76
- async function executeRefSetCommand(
64
+ export async function executeRefSetCommand(
77
65
  name: string,
78
- hash: string,
66
+ contractInput: string,
79
67
  options: { config?: string },
80
68
  ): Promise<Result<RefSetResult, CliStructuredError>> {
81
69
  if (!validateRefName(name)) {
82
70
  return notOk(cliErrorInvalidRefName(name));
83
71
  }
84
- if (!validateRefValue(hash)) {
85
- return notOk(cliErrorInvalidRefValue(hash));
86
- }
87
72
 
88
73
  try {
89
74
  const config = await loadConfig(options.config);
90
- const { refsDir } = resolveMigrationPaths(options.config, config);
91
- const entry: RefEntry = { hash, invariants: [] };
75
+ const { appMigrationsDir, refsDir } = resolveMigrationPaths(options.config, config);
76
+
77
+ let resolvedHash: string;
78
+ if (validateRefValue(contractInput)) {
79
+ resolvedHash = contractInput;
80
+ } else {
81
+ const { graph } = await loadMigrationPackages(appMigrationsDir);
82
+ const refs = await readRefs(refsDir);
83
+ const refResult = parseContractRef(contractInput, { graph, refs });
84
+ if (!refResult.ok) {
85
+ return notOk(mapRefResolutionError(refResult.failure));
86
+ }
87
+ resolvedHash = refResult.value.hash;
88
+ }
89
+
90
+ const entry: RefEntry = { hash: resolvedHash, invariants: [] };
92
91
  await writeRef(refsDir, name, entry);
93
- return ok({ ok: true as const, ref: name, hash, invariants: [] });
94
- } catch (error) {
95
- if (error instanceof CliStructuredError) return notOk(error);
96
- return notOk(mapError(error));
97
- }
98
- }
99
-
100
- async function executeRefGetCommand(
101
- name: string,
102
- options: { config?: string },
103
- ): Promise<Result<RefGetResult, CliStructuredError>> {
104
- try {
105
- const config = await loadConfig(options.config);
106
- const { refsDir } = resolveMigrationPaths(options.config, config);
107
- const entry = await readRef(refsDir, name);
108
- return ok({ ok: true as const, ref: name, hash: entry.hash, invariants: entry.invariants });
92
+ return ok({ ok: true as const, ref: name, hash: resolvedHash, invariants: [] });
109
93
  } catch (error) {
110
94
  if (error instanceof CliStructuredError) return notOk(error);
111
95
  return notOk(mapError(error));
112
96
  }
113
97
  }
114
98
 
115
- async function executeRefDeleteCommand(
99
+ export async function executeRefDeleteCommand(
116
100
  name: string,
117
101
  options: { config?: string },
118
102
  ): Promise<Result<RefDeleteResult, CliStructuredError>> {
@@ -127,7 +111,7 @@ async function executeRefDeleteCommand(
127
111
  }
128
112
  }
129
113
 
130
- async function executeRefListCommand(options: {
114
+ export async function executeRefListCommand(options: {
131
115
  config?: string;
132
116
  }): Promise<Result<RefListResult, CliStructuredError>> {
133
117
  try {
@@ -145,12 +129,15 @@ function createRefSetCommand(): Command {
145
129
  const command = new Command('set');
146
130
  setCommandDescriptions(
147
131
  command,
148
- 'Set a ref to a contract hash',
149
- 'Sets a named ref to point to a contract hash in migrations/refs/.',
132
+ 'Set a ref to a contract reference',
133
+ 'Sets a named ref to point to a resolved contract reference (hash, alias, or path) in migrations/refs/.',
150
134
  );
151
135
  addGlobalOptions(command)
152
136
  .argument('<name>', 'Ref name (e.g., staging, production)')
153
- .argument('<hash>', 'Contract hash to point to')
137
+ .argument(
138
+ '<contract>',
139
+ 'Contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
140
+ )
154
141
  .option('--config <path>', 'Path to prisma-next.config.ts')
155
142
  .action(
156
143
  async (
@@ -174,37 +161,6 @@ function createRefSetCommand(): Command {
174
161
  return command;
175
162
  }
176
163
 
177
- function createRefGetCommand(): Command {
178
- const command = new Command('get');
179
- setCommandDescriptions(
180
- command,
181
- 'Get the hash for a ref',
182
- 'Reads a named ref from migrations/refs/ and prints its contract hash.',
183
- );
184
- addGlobalOptions(command)
185
- .argument('<name>', 'Ref name to look up')
186
- .option('--config <path>', 'Path to prisma-next.config.ts')
187
- .action(
188
- async (
189
- name: string,
190
- options: { config?: string; json?: string | boolean; quiet?: boolean },
191
- ) => {
192
- const flags = parseGlobalFlags(options);
193
- const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
194
- const result = await executeRefGetCommand(name, options);
195
- const exitCode = handleResult(result, flags, ui, (value) => {
196
- if (flags.json) {
197
- ui.output(JSON.stringify(value));
198
- } else {
199
- ui.output(value.hash);
200
- }
201
- });
202
- process.exit(exitCode);
203
- },
204
- );
205
- return command;
206
- }
207
-
208
164
  function createRefDeleteCommand(): Command {
209
165
  const command = new Command('delete');
210
166
  setCommandDescriptions(command, 'Delete a ref', 'Removes a named ref from migrations/refs/.');
@@ -262,20 +218,11 @@ function createRefListCommand(): Command {
262
218
  return command;
263
219
  }
264
220
 
265
- export {
266
- cliErrorInvalidRefName,
267
- cliErrorInvalidRefValue,
268
- executeRefDeleteCommand,
269
- executeRefGetCommand,
270
- executeRefListCommand,
271
- executeRefSetCommand,
272
- };
273
-
274
- export function createMigrationRefCommand(): Command {
221
+ export function createRefCommand(): Command {
275
222
  const command = new Command('ref');
276
223
  setCommandDescriptions(
277
224
  command,
278
- 'Manage migration refs',
225
+ 'Manage contract refs',
279
226
  'Manage named refs in migrations/refs/. Refs map logical environment\n' +
280
227
  'names (e.g., staging, production) to contract hashes.',
281
228
  );
@@ -284,7 +231,6 @@ export function createMigrationRefCommand(): Command {
284
231
  subcommandDescription: () => '',
285
232
  });
286
233
  command.addCommand(createRefSetCommand());
287
- command.addCommand(createRefGetCommand());
288
234
  command.addCommand(createRefDeleteCommand());
289
235
  command.addCommand(createRefListCommand());
290
236
  return command;
@@ -201,7 +201,7 @@ class ControlClientImpl implements ControlClient {
201
201
  // Validate contract using family instance
202
202
  let contract: Contract;
203
203
  try {
204
- contract = familyInstance.validateContract(options.contract);
204
+ contract = familyInstance.deserializeContract(options.contract);
205
205
  } catch (error) {
206
206
  const message = error instanceof Error ? error.message : String(error);
207
207
  throw new ContractValidationError(message, error);
@@ -254,7 +254,7 @@ class ControlClientImpl implements ControlClient {
254
254
  // Validate contract using family instance
255
255
  let contract: Contract;
256
256
  try {
257
- contract = familyInstance.validateContract(options.contract);
257
+ contract = familyInstance.deserializeContract(options.contract);
258
258
  } catch (error) {
259
259
  const message = error instanceof Error ? error.message : String(error);
260
260
  throw new ContractValidationError(message, error);
@@ -269,12 +269,15 @@ class ControlClientImpl implements ControlClient {
269
269
  });
270
270
 
271
271
  try {
272
- // Delegate to family instance schemaVerify method
273
- const result = await familyInstance.schemaVerify({
274
- driver,
272
+ // Introspect the live schema, then verify the contract against
273
+ // it. Composing the two primitives here keeps the family
274
+ // interface a single synchronous verifier and gives callers
275
+ // (and tests) explicit control over the introspected schema.
276
+ const schema = await familyInstance.introspect({ driver, contract });
277
+ const result = familyInstance.verifySchema({
275
278
  contract,
279
+ schema,
276
280
  strict: options.strict ?? false,
277
- contractPath: '',
278
281
  frameworkComponents,
279
282
  });
280
283
 
@@ -305,7 +308,7 @@ class ControlClientImpl implements ControlClient {
305
308
  // Validate contract using family instance
306
309
  let contract: Contract;
307
310
  try {
308
- contract = familyInstance.validateContract(options.contract);
311
+ contract = familyInstance.deserializeContract(options.contract);
309
312
  } catch (error) {
310
313
  const message = error instanceof Error ? error.message : String(error);
311
314
  throw new ContractValidationError(message, error);
@@ -358,7 +361,7 @@ class ControlClientImpl implements ControlClient {
358
361
 
359
362
  let contract: Contract;
360
363
  try {
361
- contract = familyInstance.validateContract(options.contract);
364
+ contract = familyInstance.deserializeContract(options.contract);
362
365
  } catch (error) {
363
366
  const message = error instanceof Error ? error.message : String(error);
364
367
  throw new ContractValidationError(message, error);
@@ -389,7 +392,7 @@ class ControlClientImpl implements ControlClient {
389
392
 
390
393
  let contract: Contract;
391
394
  try {
392
- contract = familyInstance.validateContract(options.contract);
395
+ contract = familyInstance.deserializeContract(options.contract);
393
396
  } catch (error) {
394
397
  const message = error instanceof Error ? error.message : String(error);
395
398
  throw new ContractValidationError(message, error);
@@ -415,18 +418,10 @@ class ControlClientImpl implements ControlClient {
415
418
  await this.connectWithProgress(options.connection, 'dbVerify', onProgress);
416
419
  const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
417
420
 
418
- let contract: Contract;
419
- try {
420
- contract = familyInstance.validateContract(options.contract);
421
- } catch (error) {
422
- const message = error instanceof Error ? error.message : String(error);
423
- throw new ContractValidationError(message, error);
424
- }
425
-
426
421
  return executeDbVerify({
427
422
  driver,
428
423
  familyInstance,
429
- contract,
424
+ contract: options.contract,
430
425
  migrationsDir: options.migrationsDir,
431
426
  targetId: this.options.target.targetId,
432
427
  extensionPacks: this.options.extensionPacks ?? [],
@@ -463,7 +458,7 @@ class ControlClientImpl implements ControlClient {
463
458
 
464
459
  let contract: Contract;
465
460
  try {
466
- contract = familyInstance.validateContract(options.contract);
461
+ contract = familyInstance.deserializeContract(options.contract);
467
462
  } catch (error) {
468
463
  const message = error instanceof Error ? error.message : String(error);
469
464
  throw new ContractValidationError(message, error);
@@ -638,10 +633,20 @@ class ControlClientImpl implements ControlClient {
638
633
  });
639
634
 
640
635
  try {
641
- const enrichedIR = enrichContract(contractRaw as Contract, this.frameworkComponents ?? []);
636
+ // Blind cast: `contractRaw` is the unverified provider
637
+ // payload — `enrichContract` only adds capability + extension
638
+ // metadata onto whatever shape it receives. The structural
639
+ // check happens immediately afterwards via
640
+ // `familyInstance.deserializeContract(enrichedIR)`, which is
641
+ // the seam-of-record and the only thing that may surface
642
+ // structural errors to the caller.
643
+ const enrichedIR = enrichContract(
644
+ contractRaw as unknown as Contract,
645
+ this.frameworkComponents ?? [],
646
+ );
642
647
 
643
648
  try {
644
- this.familyInstance.validateContract(enrichedIR);
649
+ this.familyInstance.deserializeContract(enrichedIR);
645
650
  } catch (error) {
646
651
  onProgress?.({
647
652
  action: 'emit',
@@ -662,6 +667,10 @@ class ControlClientImpl implements ControlClient {
662
667
  enrichedIR,
663
668
  this.stack!,
664
669
  this.options.family.emission,
670
+ {
671
+ serializeContract: (contract) =>
672
+ this.options.target.contractSerializer.serializeContract(contract),
673
+ },
665
674
  );
666
675
 
667
676
  onProgress?.({