prisma-next 0.12.0-dev.2 → 0.12.0-dev.20

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 (90) hide show
  1. package/dist/cli.mjs +177 -160
  2. package/dist/cli.mjs.map +1 -1
  3. package/dist/{client-KgJorIvG.mjs → client-Cdxcme1x.mjs} +21 -8
  4. package/dist/client-Cdxcme1x.mjs.map +1 -0
  5. package/dist/{command-helpers-Bbw1GbwL.mjs → command-helpers-Cmdqyhz9.mjs} +32 -2
  6. package/dist/{command-helpers-Bbw1GbwL.mjs.map → command-helpers-Cmdqyhz9.mjs.map} +1 -1
  7. package/dist/commands/contract-emit.mjs +1 -1
  8. package/dist/commands/contract-infer.mjs +1 -1
  9. package/dist/commands/db-init.mjs +4 -4
  10. package/dist/commands/db-schema.mjs +3 -3
  11. package/dist/commands/db-sign.mjs +4 -4
  12. package/dist/commands/db-update.mjs +5 -5
  13. package/dist/commands/db-verify.mjs +1 -1
  14. package/dist/commands/migrate.d.mts +1 -1
  15. package/dist/commands/migrate.mjs +5 -5
  16. package/dist/commands/migration-check.mjs +1 -1
  17. package/dist/commands/migration-graph.d.mts +23 -5
  18. package/dist/commands/migration-graph.d.mts.map +1 -1
  19. package/dist/commands/migration-graph.mjs +2 -2
  20. package/dist/commands/migration-list.d.mts +3 -3
  21. package/dist/commands/migration-list.mjs +3 -3
  22. package/dist/commands/migration-log.d.mts +3 -3
  23. package/dist/commands/migration-log.mjs +3 -3
  24. package/dist/commands/migration-new.mjs +3 -3
  25. package/dist/commands/migration-plan.d.mts +1 -1
  26. package/dist/commands/migration-plan.mjs +1 -1
  27. package/dist/commands/migration-show.d.mts +1 -1
  28. package/dist/commands/migration-show.mjs +3 -3
  29. package/dist/commands/migration-status.d.mts +1 -1
  30. package/dist/commands/migration-status.mjs +4 -4
  31. package/dist/commands/migration-status.mjs.map +1 -1
  32. package/dist/commands/ref.d.mts +1 -1
  33. package/dist/commands/ref.mjs +2 -2
  34. package/dist/commands/telemetry/index.d.mts +7 -0
  35. package/dist/commands/telemetry/index.d.mts.map +1 -0
  36. package/dist/commands/telemetry/index.mjs +2 -0
  37. package/dist/{contract-at-errors-BxP-TOMl.mjs → contract-at-errors-Cz0z5PJi.mjs} +2 -2
  38. package/dist/{contract-at-errors-BxP-TOMl.mjs.map → contract-at-errors-Cz0z5PJi.mjs.map} +1 -1
  39. package/dist/{contract-emit-D-4jrNve.mjs → contract-emit-CC9jDOmu.mjs} +3 -3
  40. package/dist/{contract-emit-D-4jrNve.mjs.map → contract-emit-CC9jDOmu.mjs.map} +1 -1
  41. package/dist/{contract-emit-DxcGl4Uq.mjs → contract-emit-DPMij44i.mjs} +3 -3
  42. package/dist/{contract-emit-DxcGl4Uq.mjs.map → contract-emit-DPMij44i.mjs.map} +1 -1
  43. package/dist/{contract-infer-D8uEbJuu.mjs → contract-infer-DaFPNrZH.mjs} +3 -3
  44. package/dist/{contract-infer-D8uEbJuu.mjs.map → contract-infer-DaFPNrZH.mjs.map} +1 -1
  45. package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs → contract-space-aggregate-loader-CirAEsM8.mjs} +2 -2
  46. package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs.map → contract-space-aggregate-loader-CirAEsM8.mjs.map} +1 -1
  47. package/dist/{db-verify-v_vUKXTU.mjs → db-verify-BSA1a_W_.mjs} +4 -4
  48. package/dist/{db-verify-v_vUKXTU.mjs.map → db-verify-BSA1a_W_.mjs.map} +1 -1
  49. package/dist/exports/control-api.d.mts +1 -1
  50. package/dist/exports/control-api.d.mts.map +1 -1
  51. package/dist/exports/control-api.mjs +2 -2
  52. package/dist/exports/index.mjs +1 -1
  53. package/dist/exports/init-output.mjs +1 -1
  54. package/dist/{framework-components-fYXjz_in.mjs → framework-components-DynSvww4.mjs} +2 -2
  55. package/dist/{framework-components-fYXjz_in.mjs.map → framework-components-DynSvww4.mjs.map} +1 -1
  56. package/dist/{global-flags-DEHjV8_s.d.mts → global-flags-DG4uY5tV.d.mts} +1 -1
  57. package/dist/{global-flags-DEHjV8_s.d.mts.map → global-flags-DG4uY5tV.d.mts.map} +1 -1
  58. package/dist/{init-Cv9UzWL5.mjs → init-B6kKrmf7.mjs} +5 -58
  59. package/dist/init-B6kKrmf7.mjs.map +1 -0
  60. package/dist/{inspect-live-schema-C6ohV_oQ.mjs → inspect-live-schema-Dn56wDhG.mjs} +3 -3
  61. package/dist/{inspect-live-schema-C6ohV_oQ.mjs.map → inspect-live-schema-Dn56wDhG.mjs.map} +1 -1
  62. package/dist/{migration-check-BiBJoYYW.mjs → migration-check-DzH1u-O1.mjs} +2 -2
  63. package/dist/{migration-check-BiBJoYYW.mjs.map → migration-check-DzH1u-O1.mjs.map} +1 -1
  64. package/dist/{migration-command-scaffold-CjvwO6at.mjs → migration-command-scaffold-V52dV2Tv.mjs} +3 -3
  65. package/dist/{migration-command-scaffold-CjvwO6at.mjs.map → migration-command-scaffold-V52dV2Tv.mjs.map} +1 -1
  66. package/dist/{migration-graph-D7DVUElV.mjs → migration-graph-DKl_IYsF.mjs} +377 -85
  67. package/dist/migration-graph-DKl_IYsF.mjs.map +1 -0
  68. package/dist/{migration-list-styler-BRwF4-gy.mjs → migration-list-styler-COQbZmXk.mjs} +61 -46
  69. package/dist/migration-list-styler-COQbZmXk.mjs.map +1 -0
  70. package/dist/{migration-plan-9DJ7q7_z.mjs → migration-plan-CaeKCKp4.mjs} +5 -5
  71. package/dist/{migration-plan-9DJ7q7_z.mjs.map → migration-plan-CaeKCKp4.mjs.map} +1 -1
  72. package/dist/{migration-types-D2FW63pr.d.mts → migration-types-CAQ-0TEE.d.mts} +1 -1
  73. package/dist/{migration-types-D2FW63pr.d.mts.map → migration-types-CAQ-0TEE.d.mts.map} +1 -1
  74. package/dist/{migrations-Cv2jxNNK.mjs → migrations-DQ1t3XFL.mjs} +2 -2
  75. package/dist/{migrations-Cv2jxNNK.mjs.map → migrations-DQ1t3XFL.mjs.map} +1 -1
  76. package/dist/{output-B60Gw5fu.mjs → output-CF_hqzI-.mjs} +1 -1
  77. package/dist/{output-B60Gw5fu.mjs.map → output-CF_hqzI-.mjs.map} +1 -1
  78. package/dist/telemetry-Q88WHwlv.mjs +122 -0
  79. package/dist/telemetry-Q88WHwlv.mjs.map +1 -0
  80. package/dist/{terminal-ui-5Y6mrg93.d.mts → terminal-ui-C3xGyxW-.d.mts} +1 -1
  81. package/dist/{terminal-ui-5Y6mrg93.d.mts.map → terminal-ui-C3xGyxW-.d.mts.map} +1 -1
  82. package/dist/{types-Dt_SfqFm.d.mts → types-DiC683UW.d.mts} +8 -2
  83. package/dist/{types-Dt_SfqFm.d.mts.map → types-DiC683UW.d.mts.map} +1 -1
  84. package/dist/{verify-DCA9Sldu.mjs → verify-CreSJ1Mz.mjs} +2 -2
  85. package/dist/{verify-DCA9Sldu.mjs.map → verify-CreSJ1Mz.mjs.map} +1 -1
  86. package/package.json +11 -11
  87. package/dist/client-KgJorIvG.mjs.map +0 -1
  88. package/dist/init-Cv9UzWL5.mjs.map +0 -1
  89. package/dist/migration-graph-D7DVUElV.mjs.map +0 -1
  90. package/dist/migration-list-styler-BRwF4-gy.mjs.map +0 -1
@@ -1,11 +1,11 @@
1
1
  import { t as loadConfig } from "./config-loader-B6sJjXTv.mjs";
2
- import { T as formatStyledHeader, _ as createTerminalUI, d as setCommandSeeAlso, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "./command-helpers-Bbw1GbwL.mjs";
3
- import { r as buildReadAggregate } from "./contract-space-aggregate-loader-DvZwdkrr.mjs";
2
+ import { E as formatStyledHeader, _ as parseGlobalFlagsOrExit, b as handleResult, d as setCommandSeeAlso, l as setCommandDescriptions, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, v as createTerminalUI } from "./command-helpers-Cmdqyhz9.mjs";
3
+ import { r as buildReadAggregate } from "./contract-space-aggregate-loader-CirAEsM8.mjs";
4
4
  import { i as migrationGraphToRenderInput, n as graphRenderer } from "./graph-render-rFAqZujX.mjs";
5
- import { a as migrationListEmptySource, n as createAnsiMigrationListStyler, o as migrationListForwardArrow, s as classifyMigrationGraphTopology, t as CONTRACT_MARKER_NAME } from "./migration-list-styler-BRwF4-gy.mjs";
5
+ import { a as migrationListEmptySource, n as createAnsiMigrationListStyler, o as migrationListForwardArrow, s as classifyMigrationGraphTopology, t as CONTRACT_MARKER_NAME } from "./migration-list-styler-COQbZmXk.mjs";
6
6
  import { Command } from "commander";
7
7
  import { ok } from "@prisma-next/utils/result";
8
- import { bold } from "colorette";
8
+ import { bold, createColors } from "colorette";
9
9
  import stringWidth from "string-width";
10
10
  import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
11
11
  //#region src/utils/formatters/migration-graph-layout.ts
@@ -171,7 +171,7 @@ function classifyEdgeAdjacency(edge, position) {
171
171
  function emptyCells(width) {
172
172
  return Array.from({ length: width }, () => ({ kind: "empty" }));
173
173
  }
174
- function buildBranchConnectorCells(startLane, endLane, activeLanes, gridWidth) {
174
+ function buildBranchConnectorCells(startLane, endLane, fanTargetLanes, activeLanes, gridWidth) {
175
175
  const cells = emptyCells(gridWidth);
176
176
  for (let lane = 0; lane < gridWidth; lane++) {
177
177
  if (activeLanes.has(lane) && (lane < startLane || lane > endLane)) {
@@ -180,11 +180,13 @@ function buildBranchConnectorCells(startLane, endLane, activeLanes, gridWidth) {
180
180
  }
181
181
  if (lane === startLane) cells[lane] = { kind: "branch-tee" };
182
182
  else if (lane === endLane) cells[lane] = { kind: "branch-corner" };
183
- else if (lane > startLane && lane < endLane) cells[lane] = { kind: "branch-tee" };
183
+ else if (lane > startLane && lane < endLane) if (fanTargetLanes.has(lane)) cells[lane] = { kind: "branch-tee" };
184
+ else if (activeLanes.has(lane)) cells[lane] = { kind: "arc-crossing" };
185
+ else cells[lane] = { kind: "branch-tee" };
184
186
  }
185
187
  return cells;
186
188
  }
187
- function buildMergeConnectorCells(startLane, endLane, activeLanes, gridWidth) {
189
+ function buildMergeConnectorCells(startLane, endLane, fanTargetLanes, activeLanes, gridWidth) {
188
190
  const cells = emptyCells(gridWidth);
189
191
  for (let lane = 0; lane < gridWidth; lane++) {
190
192
  if (activeLanes.has(lane) && (lane < startLane || lane > endLane)) {
@@ -193,7 +195,9 @@ function buildMergeConnectorCells(startLane, endLane, activeLanes, gridWidth) {
193
195
  }
194
196
  if (lane === startLane) cells[lane] = { kind: "merge-tee" };
195
197
  else if (lane === endLane) cells[lane] = { kind: "merge-corner" };
196
- else if (lane > startLane && lane < endLane) cells[lane] = activeLanes.has(lane) ? { kind: "merge-tee" } : { kind: "horizontal-pass" };
198
+ else if (lane > startLane && lane < endLane) if (fanTargetLanes.has(lane)) cells[lane] = { kind: "merge-tee" };
199
+ else if (activeLanes.has(lane)) cells[lane] = { kind: "arc-crossing" };
200
+ else cells[lane] = { kind: "horizontal-pass" };
197
201
  }
198
202
  return cells;
199
203
  }
@@ -358,6 +362,8 @@ function applySkipRollbackRouting(rows, skipRollbacks, position, nodeColumn, edg
358
362
  edgeColumn.set(edge.migrationHash, backLane);
359
363
  const coSourcedLanes = routes.filter((other) => other.edge.from === edge.from).map((other) => other.backLane);
360
364
  const maxCoSourcedLane = Math.max(...coSourcedLanes);
365
+ const coLandingLanes = routes.filter((other) => other.edge.to === edge.to).map((other) => other.backLane);
366
+ const maxCoLandingLane = Math.max(...coLandingLanes);
361
367
  const sourceRow = result[sourceRowIndex];
362
368
  if (sourceRow !== void 0) {
363
369
  const cells = sourceRow.cells;
@@ -402,7 +408,7 @@ function applySkipRollbackRouting(rows, skipRollbacks, position, nodeColumn, edg
402
408
  const cells = row.cells;
403
409
  ensureCellWidth(cells, backLane + 1);
404
410
  const existing = cells[backLane];
405
- if (existing?.kind !== "arc-land-corner" && existing?.kind !== "arc-land-bridge" && existing?.kind !== "arc-branch-corner" && existing?.kind !== "arc-branch-tee" && existing?.kind !== "arc-crossing") cells[backLane] = { kind: "vertical-pass" };
411
+ if (existing?.kind !== "arc-land-corner" && existing?.kind !== "arc-land-tee" && existing?.kind !== "arc-land-bridge" && existing?.kind !== "arc-branch-corner" && existing?.kind !== "arc-branch-tee" && existing?.kind !== "arc-crossing") cells[backLane] = { kind: "vertical-pass" };
406
412
  }
407
413
  const targetRow = result[targetRowIndex];
408
414
  if (targetRow !== void 0) {
@@ -414,16 +420,20 @@ function applySkipRollbackRouting(rows, skipRollbacks, position, nodeColumn, edg
414
420
  arcLand: true
415
421
  };
416
422
  for (let lane = targetCol + 1; lane < backLane; lane += 1) {
423
+ if (coLandingLanes.includes(lane)) {
424
+ cells[lane] = { kind: "arc-land-tee" };
425
+ continue;
426
+ }
417
427
  const existing = cells[lane];
418
- cells[lane] = existing !== void 0 && existing.kind !== "empty" && existing.kind !== "horizontal-pass" && existing.kind !== "arc-land-bridge" || routes.some((other) => other.edge.migrationHash !== edge.migrationHash && other.backLane === lane && routeCrossesRow(other, targetRowIndex, result)) ? { kind: "arc-crossing" } : { kind: "arc-land-bridge" };
428
+ cells[lane] = existing !== void 0 && existing.kind !== "empty" && existing.kind !== "horizontal-pass" && existing.kind !== "arc-land-bridge" && existing.kind !== "arc-land-tee" || routes.some((other) => other.edge.migrationHash !== edge.migrationHash && other.backLane === lane && routeCrossesRow(other, targetRowIndex, result)) ? { kind: "arc-crossing" } : { kind: "arc-land-bridge" };
419
429
  }
420
- cells[backLane] = { kind: "arc-land-corner" };
430
+ cells[backLane] = backLane < maxCoLandingLane ? { kind: "arc-land-tee" } : { kind: "arc-land-corner" };
421
431
  for (const other of routes) {
422
432
  if (other.backLane <= backLane) continue;
423
433
  if (!routeCrossesRow(other, targetRowIndex, result)) continue;
424
434
  ensureCellWidth(cells, other.backLane + 1);
425
435
  const existing = cells[other.backLane];
426
- if (existing?.kind !== "arc-land-corner" && existing?.kind !== "arc-land-bridge" && existing?.kind !== "node") cells[other.backLane] = { kind: "vertical-pass" };
436
+ if (existing?.kind !== "arc-land-corner" && existing?.kind !== "arc-land-tee" && existing?.kind !== "arc-land-bridge" && existing?.kind !== "node") cells[other.backLane] = { kind: "vertical-pass" };
427
437
  }
428
438
  }
429
439
  }
@@ -504,18 +514,19 @@ function layoutComponent(componentNodes, allEdges) {
504
514
  const endLane = Math.max(...laneIndices);
505
515
  ensureGridWidth(endLane + 1);
506
516
  const activeLanes = new Set(activeLaneIndices());
517
+ const fanTargetLanes = new Set(laneIndices);
507
518
  rows.push({
508
519
  kind: "merge-connector",
509
520
  contractHash,
510
521
  startLane,
511
522
  endLane,
512
523
  branchCount: laneIndices.length,
513
- cells: buildMergeConnectorCells(startLane, endLane, activeLanes, gridWidth)
524
+ cells: buildMergeConnectorCells(startLane, endLane, fanTargetLanes, activeLanes, gridWidth)
514
525
  });
515
526
  for (const index of laneIndices) if (index !== startLane) setLane(index, null);
516
527
  return startLane;
517
528
  }
518
- function emitBranchConnector(contractHash, startLane, endLane, branchCount) {
529
+ function emitBranchConnector(contractHash, startLane, endLane, branchCount, fanTargetLanes) {
519
530
  ensureGridWidth(endLane + 1);
520
531
  const activeLanes = new Set(activeLaneIndices());
521
532
  rows.push({
@@ -524,7 +535,7 @@ function layoutComponent(componentNodes, allEdges) {
524
535
  startLane,
525
536
  endLane,
526
537
  branchCount,
527
- cells: buildBranchConnectorCells(startLane, endLane, activeLanes, gridWidth)
538
+ cells: buildBranchConnectorCells(startLane, endLane, new Set(fanTargetLanes), activeLanes, gridWidth)
528
539
  });
529
540
  }
530
541
  function emitEdgeRow(edge, lane, convergenceProducer) {
@@ -597,7 +608,7 @@ function layoutComponent(componentNodes, allEdges) {
597
608
  }
598
609
  if (groups.length >= 2) {
599
610
  const endLane = Math.max(...laneForGroup);
600
- emitBranchConnector(node, column, endLane, groups.length);
611
+ emitBranchConnector(node, column, endLane, groups.length, laneForGroup);
601
612
  }
602
613
  for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) {
603
614
  const group = groups[groupIndex];
@@ -835,6 +846,30 @@ function buildMigrationGraphRows(graph, options = {}) {
835
846
  };
836
847
  }
837
848
  //#endregion
849
+ //#region src/utils/formatters/migration-graph-lane-colors.ts
850
+ const { magenta: magenta$1, cyan: cyan$1, green: green$1, yellow: yellow$1, blueBright, red: red$1 } = createColors({ useColor: true });
851
+ const LANE_COLOR_CYCLE = [
852
+ magenta$1,
853
+ cyan$1,
854
+ green$1,
855
+ yellow$1,
856
+ blueBright,
857
+ red$1
858
+ ];
859
+ /**
860
+ * The hue for a gutter column. The leftmost lane (column 0) is **neutral** — it
861
+ * has nothing to be told apart from in the common single-lane linear case, so
862
+ * the renderer dims it rather than tinting it; the rotating palette is reserved
863
+ * for columns ≥ 1 (where a second lane exists to distinguish). Callers must dim
864
+ * column 0 themselves; this returns identity for it as a guard. A lane freed and
865
+ * reused by a later branch keeps its column's hue — coloring is by position, not
866
+ * branch identity, exactly like `git log --graph`.
867
+ */
868
+ function laneColorForColumn(column) {
869
+ if (column <= 0) return (text) => text;
870
+ return LANE_COLOR_CYCLE[(column - 1) % LANE_COLOR_CYCLE.length] ?? ((text) => text);
871
+ }
872
+ //#endregion
838
873
  //#region src/utils/formatters/migration-graph-tree-render.ts
839
874
  const LABEL_GAP = 2;
840
875
  /**
@@ -855,6 +890,7 @@ const UNICODE_PALETTE = {
855
890
  arcBranchCorner: "╮ ",
856
891
  arcBranchTee: "┬─",
857
892
  arcLandCorner: "╯ ",
893
+ arcLandTee: "┴─",
858
894
  arcCrossing: "┼─",
859
895
  arcLandBridge: "──",
860
896
  horizontalPass: "──",
@@ -881,6 +917,7 @@ const ASCII_PALETTE = {
881
917
  arcBranchCorner: "\\ ",
882
918
  arcBranchTee: "+-",
883
919
  arcLandCorner: "/ ",
920
+ arcLandTee: "+-",
884
921
  arcCrossing: "+-",
885
922
  arcLandBridge: "--",
886
923
  horizontalPass: "--",
@@ -902,74 +939,271 @@ function arrowForEdgeKind(kind, palette) {
902
939
  return palette.edgeArrow[kind];
903
940
  }
904
941
  /**
942
+ * The leftmost lane (column 0) renders with the neutral dim lane style rather
943
+ * than a palette hue — in the common single-lane case it has nothing to be told
944
+ * apart from. Used as the "no owning arc" sentinel during colour resolution.
945
+ */
946
+ const NEUTRAL_LANE = 0;
947
+ /**
948
+ * Forced bold for branch-coloured names. A branched name pairs its lane hue
949
+ * (also forced, via {@link laneColorForColumn}) with bold; both must emit even
950
+ * when colorette's ambient TTY detection is off, so the colorized branch name
951
+ * is deterministically bold + hue rather than hue-only.
952
+ */
953
+ const { bold: forcedBold } = createColors({ useColor: true });
954
+ /**
955
+ * Resolve per-cell colour columns for a row. Scanning right-to-left lets each
956
+ * arc segment inherit the hue of the arc it leads into.
957
+ *
958
+ * On a converging-landing line (`○◂──────┴─┴─╯`), every horizontal dash segment
959
+ * takes the hue of the **nearest landing anchor** — the next `arc-land-tee` or
960
+ * `arc-land-corner` — to its right, i.e. the branch it leads into: the bridge
961
+ * run leads into the first converging arc, and each tee's trailing `─` leads
962
+ * into the next arc out. Tee/corner junction glyphs keep their own column hue.
963
+ * This mirrors the forward connector's `┬─` rule (see
964
+ * {@link resolveConnectorLaneColors}). A single (non-converging) landing has
965
+ * only the corner as an anchor, so its whole horizontal run reads as one hue.
966
+ *
967
+ * The source side (`○─`, `arc-branch-tee`, `arc-branch-corner`) and pure
968
+ * horizontal passes are unaffected: they track the nearest corner to the right
969
+ * (`arcCorner`), so a routed back-arc's source fan still reads as one hue. A
970
+ * crossing can only be one colour, so it takes the arc owning the horizontal
971
+ * run at this row; the crossed vertical lane is occluded at that one cell and
972
+ * reappears on the next row.
973
+ */
974
+ function resolveRowLaneColors(cells) {
975
+ const lane = new Array(cells.length);
976
+ const connector = new Array(cells.length);
977
+ const dash = new Array(cells.length);
978
+ let arcCorner = NEUTRAL_LANE;
979
+ let landingAnchor = NEUTRAL_LANE;
980
+ for (let column = cells.length - 1; column >= 0; column--) {
981
+ const cell = cells[column];
982
+ connector[column] = landingAnchor !== NEUTRAL_LANE ? landingAnchor : arcCorner;
983
+ switch (cell?.kind) {
984
+ case "arc-branch-corner":
985
+ arcCorner = column;
986
+ lane[column] = column;
987
+ dash[column] = column;
988
+ break;
989
+ case "arc-land-corner":
990
+ arcCorner = column;
991
+ landingAnchor = column;
992
+ lane[column] = column;
993
+ dash[column] = column;
994
+ break;
995
+ case "arc-branch-tee":
996
+ lane[column] = column;
997
+ dash[column] = column;
998
+ break;
999
+ case "arc-land-tee":
1000
+ lane[column] = column;
1001
+ dash[column] = landingAnchor === NEUTRAL_LANE ? column : landingAnchor;
1002
+ landingAnchor = column;
1003
+ break;
1004
+ case "arc-crossing":
1005
+ case "arc-land-bridge": {
1006
+ const served = landingAnchor !== NEUTRAL_LANE ? landingAnchor : arcCorner;
1007
+ lane[column] = served;
1008
+ dash[column] = served;
1009
+ break;
1010
+ }
1011
+ case "horizontal-pass":
1012
+ lane[column] = arcCorner === NEUTRAL_LANE ? column : arcCorner;
1013
+ dash[column] = lane[column] ?? column;
1014
+ break;
1015
+ case "node":
1016
+ lane[column] = column;
1017
+ dash[column] = column;
1018
+ arcCorner = NEUTRAL_LANE;
1019
+ landingAnchor = NEUTRAL_LANE;
1020
+ break;
1021
+ default:
1022
+ lane[column] = column;
1023
+ dash[column] = column;
1024
+ arcCorner = NEUTRAL_LANE;
1025
+ landingAnchor = NEUTRAL_LANE;
1026
+ }
1027
+ }
1028
+ return {
1029
+ lane,
1030
+ connector,
1031
+ dash
1032
+ };
1033
+ }
1034
+ /**
1035
+ * Resolve per-cell connector colours. Scanning right-to-left, a corner or an
1036
+ * intermediate tee anchors its own lane (its junction glyph takes that column),
1037
+ * but a tee's **trailing dash leads into the branch on its right** (the next
1038
+ * branch point), so `┬─` reads as "this lane, then on toward the next" rather
1039
+ * than tinting the dash with the left lane. The leading tee at `startLane` (the
1040
+ * fork/merge origin) and pure horizontal segments inherit the nearest branch
1041
+ * point to their right whole-cell, so the run into a branch — or collapsing
1042
+ * into a merge corner — stays continuous. An `arc-crossing` keeps its junction
1043
+ * glyph at its own column but re-anchors `owner` like an intermediate tee so
1044
+ * dashes on both sides lead into the nearest branch on their right. Pass-through
1045
+ * verticals outside the run keep their own column (column 0 stays neutral).
1046
+ */
1047
+ function resolveConnectorLaneColors(cells, startLane) {
1048
+ const glyph = new Array(cells.length);
1049
+ const dash = new Array(cells.length);
1050
+ let owner = NEUTRAL_LANE;
1051
+ for (let column = cells.length - 1; column >= 0; column--) switch (cells[column]?.kind) {
1052
+ case "branch-corner":
1053
+ case "merge-corner":
1054
+ owner = column;
1055
+ glyph[column] = column;
1056
+ dash[column] = column;
1057
+ break;
1058
+ case "branch-tee":
1059
+ case "merge-tee":
1060
+ if (column === startLane) {
1061
+ const served = owner === NEUTRAL_LANE ? column : owner;
1062
+ glyph[column] = column;
1063
+ dash[column] = served;
1064
+ } else {
1065
+ dash[column] = owner === NEUTRAL_LANE ? column : owner;
1066
+ glyph[column] = column;
1067
+ owner = column;
1068
+ }
1069
+ break;
1070
+ case "arc-crossing":
1071
+ glyph[column] = column;
1072
+ dash[column] = owner === NEUTRAL_LANE ? column : owner;
1073
+ owner = column;
1074
+ break;
1075
+ case "horizontal-pass": {
1076
+ const served = owner === NEUTRAL_LANE ? column : owner;
1077
+ glyph[column] = served;
1078
+ dash[column] = served;
1079
+ break;
1080
+ }
1081
+ default:
1082
+ glyph[column] = column;
1083
+ dash[column] = column;
1084
+ }
1085
+ return {
1086
+ glyph,
1087
+ dash
1088
+ };
1089
+ }
1090
+ /**
1091
+ * Style a structural glyph by its resolved colour column. Column 0 and the
1092
+ * neutral sentinel render dim (`style.lane`); columns ≥ 1 take a palette hue.
1093
+ */
1094
+ function laneStylerForColumn(colorColumn, colorize, style) {
1095
+ if (!colorize || colorColumn <= NEUTRAL_LANE) return (text) => style.lane(text);
1096
+ return laneColorForColumn(colorColumn);
1097
+ }
1098
+ /**
1099
+ * Tint a branch-owned token (direction arrow, migration name) by its edge's
1100
+ * lane so the whole branch row reads in one colour. Column 0 has nothing to be
1101
+ * told apart from in the common linear chain, so it keeps the token's existing
1102
+ * default styling (`fallback`) rather than a palette hue; only lanes ≥ 1 take a
1103
+ * colour. With colour off, the fallback (also colourless) is used unchanged.
1104
+ */
1105
+ function branchStylerOrDefault(column, colorize, fallback) {
1106
+ if (!colorize || column <= NEUTRAL_LANE) return fallback;
1107
+ return laneColorForColumn(column);
1108
+ }
1109
+ /**
1110
+ * Render a connector tee (`├─` / `┬─` / `┴─`) with its junction glyph and its
1111
+ * trailing dash coloured independently: the junction anchors its own lane while
1112
+ * the dash leads into the branch on its right.
1113
+ */
1114
+ function renderConnectorTee(pair, glyphColumn, dashColumn, colorize, style) {
1115
+ const glyph = laneStylerForColumn(glyphColumn, colorize, style);
1116
+ if (glyphColumn === dashColumn) return glyph(pair);
1117
+ return glyph(pair.slice(0, 1)) + laneStylerForColumn(dashColumn, colorize, style)(pair.slice(1));
1118
+ }
1119
+ /**
905
1120
  * A node-marker glyph pair (`○◂`, `○─`, `*<`, `*-`) is the contract node
906
- * marker (`○` / `*`) followed by an arc connector (`◂` / `─` / `<` / `-`).
907
- * The marker is the signal and stays bright (`style.kind`); the connector is
908
- * gutter and stays dim (`style.lane`) consistent with the plain node marker,
909
- * which is never dimmed.
1121
+ * marker (`○` / `*`) followed by an arc connector (`◂` / `─` / `<` / `-`). The
1122
+ * marker takes its own lane's hue (so each node visibly belongs to its branch);
1123
+ * the connector follows the arc it belongs to (its owning back-lane hue).
1124
+ * Direction arrows are handled elsewhere — they take their edge's lane hue too.
910
1125
  */
911
- function renderNodeMarkerPair(pair, style) {
912
- return style.kind(pair.slice(0, 1)) + style.lane(pair.slice(1));
1126
+ function renderNodeMarkerPair(pair, nodeColumn, arcColumn, colorize, style) {
1127
+ const marker = laneStylerForColumn(nodeColumn, colorize, style);
1128
+ const connector = laneStylerForColumn(arcColumn, colorize, style);
1129
+ return marker(pair.slice(0, 1)) + connector(pair.slice(1));
913
1130
  }
914
- function renderCellPair(cell, style, palette) {
1131
+ function renderCellPair(cell, column, colors, colorize, style, palette) {
1132
+ const laneColumn = colors.lane[column] ?? column;
1133
+ const lane = laneStylerForColumn(laneColumn, colorize, style);
915
1134
  switch (cell.kind) {
916
- case "node":
917
- if (cell.arcLand === true) return renderNodeMarkerPair(palette.arcLand, style);
918
- if (cell.arcTee === true) return renderNodeMarkerPair(palette.arcTee, style);
919
- return style.kind(palette.node);
920
- case "vertical-pass": return style.lane(palette.verticalPass);
921
- case "edge-lane": return cell.ownsLabel ? style.lane(palette.verticalPass.trimEnd()) + style.kind(arrowForEdgeKind(cell.edgeKind, palette)) : style.lane(palette.verticalPass);
922
- case "branch-tee": return style.lane(palette.branchTee);
923
- case "merge-tee": return style.lane(palette.mergeTee);
924
- case "branch-corner": return style.lane(palette.branchCorner);
925
- case "merge-corner": return style.lane(palette.mergeCorner);
926
- case "arc-branch-corner": return style.lane(palette.arcBranchCorner);
927
- case "arc-branch-tee": return style.lane(palette.arcBranchTee);
928
- case "arc-land-corner": return style.lane(palette.arcLandCorner);
929
- case "arc-crossing": return style.lane(palette.arcCrossing);
930
- case "arc-land-bridge": return style.lane(palette.arcLandBridge);
931
- case "horizontal-pass": return style.lane(palette.horizontalPass);
1135
+ case "node": {
1136
+ const arcColumn = colors.connector[column] ?? NEUTRAL_LANE;
1137
+ if (cell.arcLand === true) return renderNodeMarkerPair(palette.arcLand, column, arcColumn, colorize, style);
1138
+ if (cell.arcTee === true) return renderNodeMarkerPair(palette.arcTee, column, arcColumn, colorize, style);
1139
+ return lane(palette.node);
1140
+ }
1141
+ case "vertical-pass": return lane(palette.verticalPass);
1142
+ case "edge-lane": return cell.ownsLabel ? lane(palette.verticalPass.trimEnd()) + branchStylerOrDefault(column, colorize, style.kind)(arrowForEdgeKind(cell.edgeKind, palette)) : lane(palette.verticalPass);
1143
+ case "branch-tee": return lane(palette.branchTee);
1144
+ case "merge-tee": return lane(palette.mergeTee);
1145
+ case "branch-corner": return lane(palette.branchCorner);
1146
+ case "merge-corner": return lane(palette.mergeCorner);
1147
+ case "arc-branch-corner": return lane(palette.arcBranchCorner);
1148
+ case "arc-branch-tee": return lane(palette.arcBranchTee);
1149
+ case "arc-land-corner": return lane(palette.arcLandCorner);
1150
+ case "arc-land-tee": return renderConnectorTee(palette.arcLandTee, laneColumn, colors.dash[column] ?? laneColumn, colorize, style);
1151
+ case "arc-crossing": return lane(palette.arcLandBridge);
1152
+ case "arc-land-bridge": return lane(palette.arcLandBridge);
1153
+ case "horizontal-pass": return lane(palette.horizontalPass);
932
1154
  case "empty": return " ";
933
1155
  }
934
1156
  }
935
- function renderConnectorRow(row, gridWidth, style, palette) {
1157
+ function renderConnectorRow(row, gridWidth, colorize, style, palette) {
936
1158
  const isMerge = row.kind === "merge-connector";
937
1159
  if (row.cells.length > 0) {
1160
+ const colors = resolveConnectorLaneColors(row.cells, row.startLane ?? 0);
938
1161
  let seenTee = false;
939
1162
  let out = "";
940
- for (const cell of row.cells) switch (cell.kind) {
941
- case "branch-tee":
942
- out += style.lane(seenTee ? palette.connectorBranchTeeCo : palette.connectorBranchTee);
943
- seenTee = true;
944
- break;
945
- case "merge-tee":
946
- out += style.lane(seenTee ? palette.connectorMergeTeeCo : palette.connectorBranchTee);
947
- seenTee = true;
948
- break;
949
- case "branch-corner":
950
- out += style.lane(palette.branchCorner);
951
- break;
952
- case "merge-corner":
953
- out += style.lane(palette.mergeCorner);
954
- break;
955
- case "vertical-pass":
956
- out += style.lane(palette.verticalPass);
957
- break;
958
- case "horizontal-pass":
959
- out += style.lane(palette.horizontalPass);
960
- break;
961
- default: out += " ";
1163
+ for (let column = 0; column < row.cells.length; column++) {
1164
+ const cell = row.cells[column];
1165
+ if (cell === void 0) continue;
1166
+ const glyphColumn = colors.glyph[column] ?? column;
1167
+ const dashColumn = colors.dash[column] ?? glyphColumn;
1168
+ const lane = laneStylerForColumn(glyphColumn, colorize, style);
1169
+ switch (cell.kind) {
1170
+ case "branch-tee":
1171
+ out += renderConnectorTee(seenTee ? palette.connectorBranchTeeCo : palette.connectorBranchTee, glyphColumn, dashColumn, colorize, style);
1172
+ seenTee = true;
1173
+ break;
1174
+ case "merge-tee":
1175
+ out += renderConnectorTee(seenTee ? palette.connectorMergeTeeCo : palette.connectorBranchTee, glyphColumn, dashColumn, colorize, style);
1176
+ seenTee = true;
1177
+ break;
1178
+ case "branch-corner":
1179
+ out += lane(palette.branchCorner);
1180
+ break;
1181
+ case "merge-corner":
1182
+ out += lane(palette.mergeCorner);
1183
+ break;
1184
+ case "vertical-pass":
1185
+ out += lane(palette.verticalPass);
1186
+ break;
1187
+ case "horizontal-pass":
1188
+ out += lane(palette.horizontalPass);
1189
+ break;
1190
+ case "arc-crossing":
1191
+ out += renderConnectorTee(palette.arcCrossing, glyphColumn, dashColumn, colorize, style);
1192
+ break;
1193
+ default: out += " ";
1194
+ }
962
1195
  }
963
1196
  for (let column = row.cells.length; column < gridWidth; column++) out += " ";
964
1197
  return out;
965
1198
  }
966
1199
  const start = row.startLane ?? 0;
967
1200
  const end = row.endLane ?? start;
1201
+ const runLane = laneStylerForColumn(end, colorize, style);
968
1202
  let out = "";
969
1203
  for (let column = 0; column < gridWidth; column++) if (column < start || column > end) out += " ";
970
- else if (column === start) out += style.lane(palette.connectorBranchTee);
971
- else if (column === end) out += style.lane(isMerge ? palette.mergeCorner : palette.branchCorner);
972
- else out += style.lane(isMerge ? palette.connectorMergeTeeCo : palette.connectorBranchTeeCo);
1204
+ else if (column === start) out += runLane(palette.connectorBranchTee);
1205
+ else if (column === end) out += runLane(isMerge ? palette.mergeCorner : palette.branchCorner);
1206
+ else out += runLane(isMerge ? palette.connectorMergeTeeCo : palette.connectorBranchTeeCo);
973
1207
  return out;
974
1208
  }
975
1209
  function abbreviateHash(hash, hashLength, emptySource) {
@@ -1008,6 +1242,11 @@ function padVisible(text, targetWidth) {
1008
1242
  const padding = Math.max(0, targetWidth - stringWidth(text));
1009
1243
  return text + " ".repeat(padding);
1010
1244
  }
1245
+ const ANSI_ESCAPE = "\x1B";
1246
+ function trimTrailingWhitespace(line) {
1247
+ const trailingSpaceBeforeReset = new RegExp(`[\\t ]+((?:${ANSI_ESCAPE}\\[[0-9;]*m)+)$`);
1248
+ return line.replace(trailingSpaceBeforeReset, "$1").replace(/\s+$/, "");
1249
+ }
1011
1250
  function gridWidthForModel(rows) {
1012
1251
  return rows.reduce((max, row) => row.kind === "node" || row.kind === "edge" ? Math.max(max, row.cells.length) : max, 1);
1013
1252
  }
@@ -1047,20 +1286,24 @@ function renderMigrationGraphTree(model, opts) {
1047
1286
  continue;
1048
1287
  }
1049
1288
  if (row.kind === "branch-connector" || row.kind === "merge-connector") {
1050
- lines.push(renderConnectorRow(row, gridWidth, style, palette).replace(/\s+$/, ""));
1289
+ lines.push(trimTrailingWhitespace(renderConnectorRow(row, gridWidth, opts.colorize, style, palette)));
1051
1290
  continue;
1052
1291
  }
1053
- let gutter = row.cells.map((cell) => renderCellPair(cell, style, palette)).join("");
1054
- const prevRow = model.rows[rowIndex - 1];
1292
+ const cellColors = resolveRowLaneColors(row.cells);
1293
+ let gutter = row.cells.map((cell, column) => renderCellPair(cell, column, cellColors, opts.colorize, style, palette)).join("");
1055
1294
  let laneSpan = row.cells.length;
1056
- if (row.kind === "node") {
1057
- const contractHash = row.contractHash ?? EMPTY_CONTRACT_HASH;
1058
- if (prevRow?.kind === "merge-connector" || contractHash === EMPTY_CONTRACT_HASH) laneSpan = 1;
1059
- else laneSpan = row.cells.length;
1295
+ if (row.kind === "node") if ((row.contractHash ?? EMPTY_CONTRACT_HASH) === EMPTY_CONTRACT_HASH) laneSpan = 1;
1296
+ else {
1297
+ let lastActiveColumn = -1;
1298
+ for (let column = row.cells.length - 1; column >= 0; column--) if (row.cells[column]?.kind !== "empty") {
1299
+ lastActiveColumn = column;
1300
+ break;
1301
+ }
1302
+ laneSpan = lastActiveColumn >= 0 ? lastActiveColumn + 1 : 1;
1060
1303
  }
1061
1304
  const labelColumn = row.kind === "edge" ? edgeLabelColumn(row, wideLabelColumn) : wideLabelColumn !== void 0 && (nodeHasArcDecoration(row) || row.contractHash !== void 0) ? wideLabelColumn : laneSpan * 2 + LABEL_GAP;
1062
- if (row.kind === "edge" && row.edge?.from === EMPTY_CONTRACT_HASH && (row.laneIndex ?? 0) === 0) gutter = row.cells.slice(0, 1).map((cell) => renderCellPair(cell, style, palette)).join("");
1063
- else if (row.kind === "node" && laneSpan < row.cells.length && !nodeHasArcDecoration(row)) gutter = row.cells.slice(0, laneSpan).map((cell) => renderCellPair(cell, style, palette)).join("");
1305
+ if (row.kind === "edge" && row.edge?.from === EMPTY_CONTRACT_HASH && (row.laneIndex ?? 0) === 0) gutter = row.cells.slice(0, 1).map((cell, column) => renderCellPair(cell, column, cellColors, opts.colorize, style, palette)).join("");
1306
+ else if (row.kind === "node" && laneSpan < row.cells.length && !nodeHasArcDecoration(row)) gutter = row.cells.slice(0, laneSpan).map((cell, column) => renderCellPair(cell, column, cellColors, opts.colorize, style, palette)).join("");
1064
1307
  else if (gutter.length < laneSpan * 2) gutter = gutter.padEnd(laneSpan * 2, " ");
1065
1308
  const dirNameWidth = rowDirNameWidth(labelColumn, maxDirNameLen, dirNameGap);
1066
1309
  const dataColumn = labelColumn + dirNameWidth;
@@ -1068,35 +1311,76 @@ function renderMigrationGraphTree(model, opts) {
1068
1311
  if (row.kind === "node") {
1069
1312
  const contractHash = row.contractHash ?? EMPTY_CONTRACT_HASH;
1070
1313
  if (contractHash === EMPTY_CONTRACT_HASH) {
1071
- const trailingLanes = row.cells.slice(1).map((cell) => renderCellPair(cell, style, palette)).join("");
1314
+ const trailingLanes = row.cells.slice(1).map((cell, offset) => renderCellPair(cell, offset + 1, cellColors, opts.colorize, style, palette)).join("");
1072
1315
  const emptyGutter = palette.emptySource.padEnd(2, " ") + trailingLanes;
1073
1316
  const overlayNames = overlayNamesForContract(contractHash, opts);
1074
1317
  if (overlayNames.length === 0) {
1075
- lines.push(emptyGutter.replace(/\s+$/, ""));
1318
+ lines.push(trimTrailingWhitespace(emptyGutter));
1076
1319
  continue;
1077
1320
  }
1078
1321
  const overlay = style.refs(overlayNames);
1079
- lines.push(`${padVisible(emptyGutter, dataColumn)}${overlay}`.replace(/\s+$/, ""));
1322
+ lines.push(trimTrailingWhitespace(`${padVisible(emptyGutter, dataColumn)}${overlay}`));
1080
1323
  continue;
1081
1324
  }
1082
1325
  const hashText = style.sourceHash(abbreviateHash(contractHash, hashLength, palette.emptySource));
1083
1326
  const overlayNames = overlayNamesForContract(contractHash, opts);
1084
1327
  const overlayPad = overlayNames.length > 0 ? " ".repeat(Math.max(0, dataColumn - labelColumn - stringWidth(hashText))) : "";
1085
1328
  const overlay = overlayNames.length > 0 ? style.refs(overlayNames) : "";
1086
- lines.push(`${gutterPad}${hashText}${overlayPad}${overlay}`.replace(/\s+$/, ""));
1329
+ lines.push(trimTrailingWhitespace(`${gutterPad}${hashText}${overlayPad}${overlay}`));
1087
1330
  continue;
1088
1331
  }
1089
1332
  const edge = row.edge;
1090
1333
  if (edge === void 0) continue;
1091
1334
  const dirNamePadding = " ".repeat(Math.max(0, dirNameWidth - edge.dirName.length));
1092
- const dirName = `${style.dirName(edge.dirName)}${dirNamePadding}`;
1335
+ const laneIndex = row.laneIndex ?? 0;
1336
+ const dirName = `${(opts.colorize && laneIndex > NEUTRAL_LANE ? (text) => forcedBold(laneColorForColumn(laneIndex)(text)) : style.dirName)(edge.dirName)}${dirNamePadding}`;
1093
1337
  const hashColumn = formatEdgeHashColumn(edge, style, hashLength, palette);
1094
- lines.push(`${gutterPad}${dirName}${hashColumn}`.replace(/\s+$/, ""));
1338
+ lines.push(trimTrailingWhitespace(`${gutterPad}${dirName}${hashColumn}`));
1095
1339
  }
1096
1340
  return lines.join("\n");
1097
1341
  }
1342
+ /**
1343
+ * A compact key for the `--tree` visual language: the contract marker, the
1344
+ * in-lane direction arrows, the empty baseline, the `(refs)` overlay (including
1345
+ * the reserved `db` live-database and `contract` working-schema markers), and a
1346
+ * worked sample of the data-column `from → to` migration hash arrow.
1347
+ *
1348
+ * Honors the same glyph palette (unicode vs ASCII) and `colorize` gate as the
1349
+ * tree renderer, so the key matches whatever the graph itself drew and stays
1350
+ * pipe-safe (zero ANSI when color is off). The caller adds the trailing blank
1351
+ * line that separates this stderr key from the graph on stdout.
1352
+ */
1353
+ function renderMigrationGraphLegend(opts) {
1354
+ const palette = paletteFor(opts.glyphMode ?? "unicode");
1355
+ const style = createAnsiMigrationListStyler({ useColor: opts.colorize });
1356
+ const node = palette.node.trimEnd();
1357
+ const sampleArrow = `${style.sourceHash("aaaaaa")} ${style.glyph(palette.forwardArrow)} ${style.destHash("bbbbbb")}`;
1358
+ return [
1359
+ "Legend:",
1360
+ ` ${style.kind(node)} ${style.summary("contract")} ${style.kind(palette.edgeArrow.forward)} ${style.summary("forward")} ${style.kind(palette.edgeArrow.rollback)} ${style.summary("rollback")}`,
1361
+ ` ${style.kind(palette.edgeArrow.self)} ${style.summary("migration without schema change")}`,
1362
+ ` ${style.kind(palette.emptySource)} ${style.summary("empty database (baseline)")}`,
1363
+ ` ${style.refs(["refs"])} ${style.summary(`${DB_MARKER_NAME} / ${CONTRACT_MARKER_NAME} markers`)}`,
1364
+ ` ${sampleArrow} ${style.summary("migration from contract aaaaaa to bbbbbb")}`
1365
+ ].join("\n");
1366
+ }
1098
1367
  //#endregion
1099
1368
  //#region src/commands/migration-graph.ts
1369
+ /**
1370
+ * `--legend` describes the `--tree` visual language, so passing it auto-enables
1371
+ * the tree path (it has nothing to say about the legacy dagre default).
1372
+ */
1373
+ function migrationGraphUsesTree(options) {
1374
+ return options.tree === true || options.legend === true;
1375
+ }
1376
+ /**
1377
+ * The legend is decoration printed alongside the command header on stderr, so
1378
+ * it is suppressed for the machine-readable / silent paths (`--json`, `--dot`,
1379
+ * `--quiet`) exactly as the header is.
1380
+ */
1381
+ function migrationGraphShowsLegend(options, flags) {
1382
+ return options.legend === true && options.dot !== true && flags.json !== true && flags.quiet !== true;
1383
+ }
1100
1384
  async function executeMigrationGraphCommand(options, flags, ui) {
1101
1385
  const config = await loadConfig(options.config);
1102
1386
  const { configPath, appMigrationsRelative, migrationsDir } = resolveMigrationPaths(options.config, config);
@@ -1114,6 +1398,13 @@ async function executeMigrationGraphCommand(options, flags, ui) {
1114
1398
  flags
1115
1399
  });
1116
1400
  ui.stderr(header);
1401
+ if (migrationGraphShowsLegend(options, flags)) {
1402
+ ui.stderr(renderMigrationGraphLegend({
1403
+ colorize: flags.color !== false,
1404
+ glyphMode: ui.resolveGlyphMode(options.ascii === true)
1405
+ }));
1406
+ ui.stderr("");
1407
+ }
1117
1408
  }
1118
1409
  const loaded = await buildReadAggregate(config, { migrationsDir });
1119
1410
  if (!loaded.ok) return loaded;
@@ -1139,7 +1430,8 @@ function createMigrationGraphCommand() {
1139
1430
  "prisma-next migration graph --json",
1140
1431
  "prisma-next migration graph --dot",
1141
1432
  "prisma-next migration graph --tree",
1142
- "prisma-next migration graph --tree --ascii"
1433
+ "prisma-next migration graph --tree --ascii",
1434
+ "prisma-next migration graph --legend"
1143
1435
  ]);
1144
1436
  setCommandSeeAlso(command, [
1145
1437
  {
@@ -1159,7 +1451,7 @@ function createMigrationGraphCommand() {
1159
1451
  oneLiner: "Display migration package contents"
1160
1452
  }
1161
1453
  ]);
1162
- addGlobalOptions(command).option("--config <path>", "Path to prisma-next.config.ts").option("--dot", "Output in Graphviz DOT format").option("--tree", "Experimental condensed annotated tree renderer").option("--ascii", "Use ASCII glyphs for --tree (pipe-friendly)").action(async (options) => {
1454
+ addGlobalOptions(command).option("--config <path>", "Path to prisma-next.config.ts").option("--dot", "Output in Graphviz DOT format").option("--tree", "Experimental condensed annotated tree renderer").option("--ascii", "Use ASCII glyphs for --tree (pipe-friendly)").option("--legend", "Print a key for the --tree glyphs and lane colors (implies --tree)").action(async (options) => {
1163
1455
  const flags = parseGlobalFlagsOrExit(options);
1164
1456
  const ui = createTerminalUI(flags);
1165
1457
  const exitCode = handleResult(await executeMigrationGraphCommand(options, flags, ui), flags, ui, (graphResult) => {
@@ -1186,7 +1478,7 @@ function createMigrationGraphCommand() {
1186
1478
  edges,
1187
1479
  summary: graphResult.summary
1188
1480
  }, null, 2));
1189
- } else if (!flags.quiet) if (options.tree) {
1481
+ } else if (!flags.quiet) if (migrationGraphUsesTree(options)) {
1190
1482
  const refsByHash = /* @__PURE__ */ new Map();
1191
1483
  for (const ref of graphResult.refs) {
1192
1484
  const existing = refsByHash.get(ref.hash);
@@ -1227,6 +1519,6 @@ function createMigrationGraphCommand() {
1227
1519
  return command;
1228
1520
  }
1229
1521
  //#endregion
1230
- export { executeMigrationGraphCommand as n, createMigrationGraphCommand as t };
1522
+ export { migrationGraphUsesTree as i, executeMigrationGraphCommand as n, migrationGraphShowsLegend as r, createMigrationGraphCommand as t };
1231
1523
 
1232
- //# sourceMappingURL=migration-graph-D7DVUElV.mjs.map
1524
+ //# sourceMappingURL=migration-graph-DKl_IYsF.mjs.map