@prisma-next/cli 0.12.0-dev.5 → 0.12.0-dev.7

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.
package/dist/cli.mjs CHANGED
@@ -9,7 +9,7 @@ import { createDbUpdateCommand } from "./commands/db-update.mjs";
9
9
  import { t as createDbVerifyCommand } from "./db-verify-v_vUKXTU.mjs";
10
10
  import { createMigrateCommand } from "./commands/migrate.mjs";
11
11
  import { t as createMigrationCheckCommand } from "./migration-check-BiBJoYYW.mjs";
12
- import { t as createMigrationGraphCommand } from "./migration-graph-D7DVUElV.mjs";
12
+ import { t as createMigrationGraphCommand } from "./migration-graph-BzxEsMZg.mjs";
13
13
  import { createMigrationListCommand } from "./commands/migration-list.mjs";
14
14
  import { createMigrationLogCommand } from "./commands/migration-log.mjs";
15
15
  import { createMigrationNewCommand } from "./commands/migration-new.mjs";
@@ -24,7 +24,7 @@ import { fileURLToPath } from "node:url";
24
24
  import { readUserConfig, resolveGating, runTelemetry } from "@prisma-next/cli-telemetry";
25
25
  import { distance } from "closest-match";
26
26
  //#region package.json
27
- var version = "0.12.0-dev.5";
27
+ var version = "0.12.0-dev.7";
28
28
  //#endregion
29
29
  //#region src/utils/telemetry.ts
30
30
  /**
@@ -12,7 +12,25 @@ interface MigrationGraphOptions extends CommonCommandOptions {
12
12
  readonly dot?: boolean;
13
13
  readonly tree?: boolean;
14
14
  readonly ascii?: boolean;
15
+ readonly legend?: boolean;
15
16
  }
17
+ /**
18
+ * `--legend` describes the `--tree` visual language, so passing it auto-enables
19
+ * the tree path (it has nothing to say about the legacy dagre default).
20
+ */
21
+ declare function migrationGraphUsesTree(options: {
22
+ readonly tree?: boolean;
23
+ readonly legend?: boolean;
24
+ }): boolean;
25
+ /**
26
+ * The legend is decoration printed alongside the command header on stderr, so
27
+ * it is suppressed for the machine-readable / silent paths (`--json`, `--dot`,
28
+ * `--quiet`) exactly as the header is.
29
+ */
30
+ declare function migrationGraphShowsLegend(options: {
31
+ readonly legend?: boolean;
32
+ readonly dot?: boolean;
33
+ }, flags: GlobalFlags): boolean;
16
34
  interface MigrationGraphResult {
17
35
  readonly ok: true;
18
36
  readonly graph: MigrationGraph;
@@ -23,5 +41,5 @@ interface MigrationGraphResult {
23
41
  declare function executeMigrationGraphCommand(options: MigrationGraphOptions, flags: GlobalFlags, ui: TerminalUI): Promise<Result<MigrationGraphResult, CliStructuredError>>;
24
42
  declare function createMigrationGraphCommand(): Command;
25
43
  //#endregion
26
- export { MigrationGraphResult, createMigrationGraphCommand, executeMigrationGraphCommand };
44
+ export { MigrationGraphResult, createMigrationGraphCommand, executeMigrationGraphCommand, migrationGraphShowsLegend, migrationGraphUsesTree };
27
45
  //# sourceMappingURL=migration-graph.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"migration-graph.d.mts","names":[],"sources":["../../src/commands/migration-graph.ts"],"mappings":";;;;;;;;;UA0BU,qBAAA,SAA8B,oBAAoB;EAAA,SACjD,MAAA;EAAA,SACA,GAAA;EAAA,SACA,IAAA;EAAA,SACA,KAAA;AAAA;AAAA,UAGM,oBAAA;EAAA,SACN,EAAA;EAAA,SACA,KAAA,EAAO,cAAA;EAAA,SACP,YAAA;EAAA,SACA,IAAA,WAAe,SAAS;EAAA,SACxB,OAAA;AAAA;AAAA,iBAGW,4BAAA,CACpB,OAAA,EAAS,qBAAA,EACT,KAAA,EAAO,WAAA,EACP,EAAA,EAAI,UAAA,GACH,OAAA,CAAQ,MAAA,CAAO,oBAAA,EAAsB,kBAAA;AAAA,iBA0CxB,2BAAA,CAAA,GAA+B,OAAO"}
1
+ {"version":3,"file":"migration-graph.d.mts","names":[],"sources":["../../src/commands/migration-graph.ts"],"mappings":";;;;;;;;;UA6BU,qBAAA,SAA8B,oBAAoB;EAAA,SACjD,MAAA;EAAA,SACA,GAAA;EAAA,SACA,IAAA;EAAA,SACA,KAAA;EAAA,SACA,MAAA;AAAA;;;;;iBAOK,sBAAA,CAAuB,OAAA;EAAA,SAC5B,IAAA;EAAA,SACA,MAAA;AAAA;;;;;;iBAUK,yBAAA,CACd,OAAA;EAAA,SAAoB,MAAA;EAAA,SAA2B,GAAA;AAAA,GAC/C,KAAA,EAAO,WAAW;AAAA,UAOH,oBAAA;EAAA,SACN,EAAA;EAAA,SACA,KAAA,EAAO,cAAA;EAAA,SACP,YAAA;EAAA,SACA,IAAA,WAAe,SAAS;EAAA,SACxB,OAAA;AAAA;AAAA,iBAGW,4BAAA,CACpB,OAAA,EAAS,qBAAA,EACT,KAAA,EAAO,WAAA,EACP,EAAA,EAAI,UAAA,GACH,OAAA,CAAQ,MAAA,CAAO,oBAAA,EAAsB,kBAAA;AAAA,iBAoDxB,2BAAA,CAAA,GAA+B,OAAO"}
@@ -1,2 +1,2 @@
1
- import { n as executeMigrationGraphCommand, t as createMigrationGraphCommand } from "../migration-graph-D7DVUElV.mjs";
2
- export { createMigrationGraphCommand, executeMigrationGraphCommand };
1
+ import { i as migrationGraphUsesTree, n as executeMigrationGraphCommand, r as migrationGraphShowsLegend, t as createMigrationGraphCommand } from "../migration-graph-BzxEsMZg.mjs";
2
+ export { createMigrationGraphCommand, executeMigrationGraphCommand, migrationGraphShowsLegend, migrationGraphUsesTree };
@@ -5,7 +5,7 @@ import { i as migrationGraphToRenderInput, n as graphRenderer } from "./graph-re
5
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";
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
@@ -835,6 +835,30 @@ function buildMigrationGraphRows(graph, options = {}) {
835
835
  };
836
836
  }
837
837
  //#endregion
838
+ //#region src/utils/formatters/migration-graph-lane-colors.ts
839
+ const { magenta: magenta$1, cyan: cyan$1, green: green$1, yellow: yellow$1, blueBright, red: red$1 } = createColors({ useColor: true });
840
+ const LANE_COLOR_CYCLE = [
841
+ magenta$1,
842
+ cyan$1,
843
+ green$1,
844
+ yellow$1,
845
+ blueBright,
846
+ red$1
847
+ ];
848
+ /**
849
+ * The hue for a gutter column. The leftmost lane (column 0) is **neutral** — it
850
+ * has nothing to be told apart from in the common single-lane linear case, so
851
+ * the renderer dims it rather than tinting it; the rotating palette is reserved
852
+ * for columns ≥ 1 (where a second lane exists to distinguish). Callers must dim
853
+ * column 0 themselves; this returns identity for it as a guard. A lane freed and
854
+ * reused by a later branch keeps its column's hue — coloring is by position, not
855
+ * branch identity, exactly like `git log --graph`.
856
+ */
857
+ function laneColorForColumn(column) {
858
+ if (column <= 0) return (text) => text;
859
+ return LANE_COLOR_CYCLE[(column - 1) % LANE_COLOR_CYCLE.length] ?? ((text) => text);
860
+ }
861
+ //#endregion
838
862
  //#region src/utils/formatters/migration-graph-tree-render.ts
839
863
  const LABEL_GAP = 2;
840
864
  /**
@@ -902,74 +926,226 @@ function arrowForEdgeKind(kind, palette) {
902
926
  return palette.edgeArrow[kind];
903
927
  }
904
928
  /**
929
+ * The leftmost lane (column 0) renders with the neutral dim lane style rather
930
+ * than a palette hue — in the common single-lane case it has nothing to be told
931
+ * apart from. Used as the "no owning arc" sentinel during colour resolution.
932
+ */
933
+ const NEUTRAL_LANE = 0;
934
+ /**
935
+ * Forced bold for branch-coloured names. A branched name pairs its lane hue
936
+ * (also forced, via {@link laneColorForColumn}) with bold; both must emit even
937
+ * when colorette's ambient TTY detection is off, so the colorized branch name
938
+ * is deterministically bold + hue rather than hue-only.
939
+ */
940
+ const { bold: forcedBold } = createColors({ useColor: true });
941
+ /**
942
+ * Resolve per-cell colour columns for a row. Scanning right-to-left lets each
943
+ * arc bridge inherit the corner column that closes it (the arc's back-lane), so
944
+ * the whole arc — vertical run (already its own column), horizontal bridges,
945
+ * corners, crossings, and the `◂`/`─` connector — reads as a single continuous
946
+ * hue. A crossing can only be one colour, so rather than leave it dim (wrong for
947
+ * both crossing lines) it takes the arc owning the horizontal run at this row
948
+ * (the nearest corner to its right); the crossed vertical lane is simply
949
+ * occluded at that one cell and reappears on the next row.
950
+ */
951
+ function resolveRowLaneColors(cells) {
952
+ const lane = new Array(cells.length);
953
+ const connector = new Array(cells.length);
954
+ let arcCorner = NEUTRAL_LANE;
955
+ for (let column = cells.length - 1; column >= 0; column--) {
956
+ const cell = cells[column];
957
+ connector[column] = arcCorner;
958
+ switch (cell?.kind) {
959
+ case "arc-branch-corner":
960
+ case "arc-land-corner":
961
+ arcCorner = column;
962
+ lane[column] = column;
963
+ break;
964
+ case "arc-branch-tee":
965
+ lane[column] = column;
966
+ break;
967
+ case "arc-crossing":
968
+ case "arc-land-bridge":
969
+ lane[column] = arcCorner;
970
+ break;
971
+ case "horizontal-pass":
972
+ lane[column] = arcCorner === NEUTRAL_LANE ? column : arcCorner;
973
+ break;
974
+ case "node":
975
+ lane[column] = column;
976
+ arcCorner = NEUTRAL_LANE;
977
+ break;
978
+ default:
979
+ lane[column] = column;
980
+ arcCorner = NEUTRAL_LANE;
981
+ }
982
+ }
983
+ return {
984
+ lane,
985
+ connector
986
+ };
987
+ }
988
+ /**
989
+ * Resolve per-cell connector colours. Scanning right-to-left, a corner or an
990
+ * intermediate tee anchors its own lane (its junction glyph takes that column),
991
+ * but a tee's **trailing dash leads into the branch on its right** (the next
992
+ * branch point), so `┬─` reads as "this lane, then on toward the next" rather
993
+ * than tinting the dash with the left lane. The leading tee at `startLane` (the
994
+ * fork/merge origin) and pure horizontal segments inherit the nearest branch
995
+ * point to their right whole-cell, so the run into a branch — or collapsing
996
+ * into a merge corner — stays continuous. Pass-through verticals outside the
997
+ * run keep their own column (column 0 stays neutral).
998
+ */
999
+ function resolveConnectorLaneColors(cells, startLane) {
1000
+ const glyph = new Array(cells.length);
1001
+ const dash = new Array(cells.length);
1002
+ let owner = NEUTRAL_LANE;
1003
+ for (let column = cells.length - 1; column >= 0; column--) switch (cells[column]?.kind) {
1004
+ case "branch-corner":
1005
+ case "merge-corner":
1006
+ owner = column;
1007
+ glyph[column] = column;
1008
+ dash[column] = column;
1009
+ break;
1010
+ case "branch-tee":
1011
+ case "merge-tee":
1012
+ if (column === startLane) {
1013
+ const served = owner === NEUTRAL_LANE ? column : owner;
1014
+ glyph[column] = served;
1015
+ dash[column] = served;
1016
+ } else {
1017
+ dash[column] = owner === NEUTRAL_LANE ? column : owner;
1018
+ glyph[column] = column;
1019
+ owner = column;
1020
+ }
1021
+ break;
1022
+ case "horizontal-pass": {
1023
+ const served = owner === NEUTRAL_LANE ? column : owner;
1024
+ glyph[column] = served;
1025
+ dash[column] = served;
1026
+ break;
1027
+ }
1028
+ default:
1029
+ glyph[column] = column;
1030
+ dash[column] = column;
1031
+ }
1032
+ return {
1033
+ glyph,
1034
+ dash
1035
+ };
1036
+ }
1037
+ /**
1038
+ * Style a structural glyph by its resolved colour column. Column 0 and the
1039
+ * neutral sentinel render dim (`style.lane`); columns ≥ 1 take a palette hue.
1040
+ */
1041
+ function laneStylerForColumn(colorColumn, colorize, style) {
1042
+ if (!colorize || colorColumn <= NEUTRAL_LANE) return (text) => style.lane(text);
1043
+ return laneColorForColumn(colorColumn);
1044
+ }
1045
+ /**
1046
+ * Tint a branch-owned token (direction arrow, migration name) by its edge's
1047
+ * lane so the whole branch row reads in one colour. Column 0 has nothing to be
1048
+ * told apart from in the common linear chain, so it keeps the token's existing
1049
+ * default styling (`fallback`) rather than a palette hue; only lanes ≥ 1 take a
1050
+ * colour. With colour off, the fallback (also colourless) is used unchanged.
1051
+ */
1052
+ function branchStylerOrDefault(column, colorize, fallback) {
1053
+ if (!colorize || column <= NEUTRAL_LANE) return fallback;
1054
+ return laneColorForColumn(column);
1055
+ }
1056
+ /**
1057
+ * Render a connector tee (`├─` / `┬─` / `┴─`) with its junction glyph and its
1058
+ * trailing dash coloured independently: the junction anchors its own lane while
1059
+ * the dash leads into the branch on its right.
1060
+ */
1061
+ function renderConnectorTee(pair, glyphColumn, dashColumn, colorize, style) {
1062
+ const glyph = laneStylerForColumn(glyphColumn, colorize, style);
1063
+ if (glyphColumn === dashColumn) return glyph(pair);
1064
+ return glyph(pair.slice(0, 1)) + laneStylerForColumn(dashColumn, colorize, style)(pair.slice(1));
1065
+ }
1066
+ /**
905
1067
  * 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.
1068
+ * marker (`○` / `*`) followed by an arc connector (`◂` / `─` / `<` / `-`). The
1069
+ * marker takes its own lane's hue (so each node visibly belongs to its branch);
1070
+ * the connector follows the arc it belongs to (its owning back-lane hue).
1071
+ * Direction arrows are handled elsewhere — they take their edge's lane hue too.
910
1072
  */
911
- function renderNodeMarkerPair(pair, style) {
912
- return style.kind(pair.slice(0, 1)) + style.lane(pair.slice(1));
1073
+ function renderNodeMarkerPair(pair, nodeColumn, arcColumn, colorize, style) {
1074
+ const marker = laneStylerForColumn(nodeColumn, colorize, style);
1075
+ const connector = laneStylerForColumn(arcColumn, colorize, style);
1076
+ return marker(pair.slice(0, 1)) + connector(pair.slice(1));
913
1077
  }
914
- function renderCellPair(cell, style, palette) {
1078
+ function renderCellPair(cell, column, colors, colorize, style, palette) {
1079
+ const lane = laneStylerForColumn(colors.lane[column] ?? column, colorize, style);
915
1080
  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);
1081
+ case "node": {
1082
+ const arcColumn = colors.connector[column] ?? NEUTRAL_LANE;
1083
+ if (cell.arcLand === true) return renderNodeMarkerPair(palette.arcLand, column, arcColumn, colorize, style);
1084
+ if (cell.arcTee === true) return renderNodeMarkerPair(palette.arcTee, column, arcColumn, colorize, style);
1085
+ return lane(palette.node);
1086
+ }
1087
+ case "vertical-pass": return lane(palette.verticalPass);
1088
+ case "edge-lane": return cell.ownsLabel ? lane(palette.verticalPass.trimEnd()) + branchStylerOrDefault(column, colorize, style.kind)(arrowForEdgeKind(cell.edgeKind, palette)) : lane(palette.verticalPass);
1089
+ case "branch-tee": return lane(palette.branchTee);
1090
+ case "merge-tee": return lane(palette.mergeTee);
1091
+ case "branch-corner": return lane(palette.branchCorner);
1092
+ case "merge-corner": return lane(palette.mergeCorner);
1093
+ case "arc-branch-corner": return lane(palette.arcBranchCorner);
1094
+ case "arc-branch-tee": return lane(palette.arcBranchTee);
1095
+ case "arc-land-corner": return lane(palette.arcLandCorner);
1096
+ case "arc-crossing": return lane(palette.arcCrossing);
1097
+ case "arc-land-bridge": return lane(palette.arcLandBridge);
1098
+ case "horizontal-pass": return lane(palette.horizontalPass);
932
1099
  case "empty": return " ";
933
1100
  }
934
1101
  }
935
- function renderConnectorRow(row, gridWidth, style, palette) {
1102
+ function renderConnectorRow(row, gridWidth, colorize, style, palette) {
936
1103
  const isMerge = row.kind === "merge-connector";
937
1104
  if (row.cells.length > 0) {
1105
+ const colors = resolveConnectorLaneColors(row.cells, row.startLane ?? 0);
938
1106
  let seenTee = false;
939
1107
  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 += " ";
1108
+ for (let column = 0; column < row.cells.length; column++) {
1109
+ const cell = row.cells[column];
1110
+ if (cell === void 0) continue;
1111
+ const glyphColumn = colors.glyph[column] ?? column;
1112
+ const dashColumn = colors.dash[column] ?? glyphColumn;
1113
+ const lane = laneStylerForColumn(glyphColumn, colorize, style);
1114
+ switch (cell.kind) {
1115
+ case "branch-tee":
1116
+ out += renderConnectorTee(seenTee ? palette.connectorBranchTeeCo : palette.connectorBranchTee, glyphColumn, dashColumn, colorize, style);
1117
+ seenTee = true;
1118
+ break;
1119
+ case "merge-tee":
1120
+ out += renderConnectorTee(seenTee ? palette.connectorMergeTeeCo : palette.connectorBranchTee, glyphColumn, dashColumn, colorize, style);
1121
+ seenTee = true;
1122
+ break;
1123
+ case "branch-corner":
1124
+ out += lane(palette.branchCorner);
1125
+ break;
1126
+ case "merge-corner":
1127
+ out += lane(palette.mergeCorner);
1128
+ break;
1129
+ case "vertical-pass":
1130
+ out += lane(palette.verticalPass);
1131
+ break;
1132
+ case "horizontal-pass":
1133
+ out += lane(palette.horizontalPass);
1134
+ break;
1135
+ default: out += " ";
1136
+ }
962
1137
  }
963
1138
  for (let column = row.cells.length; column < gridWidth; column++) out += " ";
964
1139
  return out;
965
1140
  }
966
1141
  const start = row.startLane ?? 0;
967
1142
  const end = row.endLane ?? start;
1143
+ const runLane = laneStylerForColumn(end, colorize, style);
968
1144
  let out = "";
969
1145
  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);
1146
+ else if (column === start) out += runLane(palette.connectorBranchTee);
1147
+ else if (column === end) out += runLane(isMerge ? palette.mergeCorner : palette.branchCorner);
1148
+ else out += runLane(isMerge ? palette.connectorMergeTeeCo : palette.connectorBranchTeeCo);
973
1149
  return out;
974
1150
  }
975
1151
  function abbreviateHash(hash, hashLength, emptySource) {
@@ -1008,6 +1184,11 @@ function padVisible(text, targetWidth) {
1008
1184
  const padding = Math.max(0, targetWidth - stringWidth(text));
1009
1185
  return text + " ".repeat(padding);
1010
1186
  }
1187
+ const ANSI_ESCAPE = "\x1B";
1188
+ function trimTrailingWhitespace(line) {
1189
+ const trailingSpaceBeforeReset = new RegExp(`[\\t ]+((?:${ANSI_ESCAPE}\\[[0-9;]*m)+)$`);
1190
+ return line.replace(trailingSpaceBeforeReset, "$1").replace(/\s+$/, "");
1191
+ }
1011
1192
  function gridWidthForModel(rows) {
1012
1193
  return rows.reduce((max, row) => row.kind === "node" || row.kind === "edge" ? Math.max(max, row.cells.length) : max, 1);
1013
1194
  }
@@ -1047,10 +1228,11 @@ function renderMigrationGraphTree(model, opts) {
1047
1228
  continue;
1048
1229
  }
1049
1230
  if (row.kind === "branch-connector" || row.kind === "merge-connector") {
1050
- lines.push(renderConnectorRow(row, gridWidth, style, palette).replace(/\s+$/, ""));
1231
+ lines.push(trimTrailingWhitespace(renderConnectorRow(row, gridWidth, opts.colorize, style, palette)));
1051
1232
  continue;
1052
1233
  }
1053
- let gutter = row.cells.map((cell) => renderCellPair(cell, style, palette)).join("");
1234
+ const cellColors = resolveRowLaneColors(row.cells);
1235
+ let gutter = row.cells.map((cell, column) => renderCellPair(cell, column, cellColors, opts.colorize, style, palette)).join("");
1054
1236
  const prevRow = model.rows[rowIndex - 1];
1055
1237
  let laneSpan = row.cells.length;
1056
1238
  if (row.kind === "node") {
@@ -1059,8 +1241,8 @@ function renderMigrationGraphTree(model, opts) {
1059
1241
  else laneSpan = row.cells.length;
1060
1242
  }
1061
1243
  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("");
1244
+ 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("");
1245
+ 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
1246
  else if (gutter.length < laneSpan * 2) gutter = gutter.padEnd(laneSpan * 2, " ");
1065
1247
  const dirNameWidth = rowDirNameWidth(labelColumn, maxDirNameLen, dirNameGap);
1066
1248
  const dataColumn = labelColumn + dirNameWidth;
@@ -1068,35 +1250,76 @@ function renderMigrationGraphTree(model, opts) {
1068
1250
  if (row.kind === "node") {
1069
1251
  const contractHash = row.contractHash ?? EMPTY_CONTRACT_HASH;
1070
1252
  if (contractHash === EMPTY_CONTRACT_HASH) {
1071
- const trailingLanes = row.cells.slice(1).map((cell) => renderCellPair(cell, style, palette)).join("");
1253
+ const trailingLanes = row.cells.slice(1).map((cell, offset) => renderCellPair(cell, offset + 1, cellColors, opts.colorize, style, palette)).join("");
1072
1254
  const emptyGutter = palette.emptySource.padEnd(2, " ") + trailingLanes;
1073
1255
  const overlayNames = overlayNamesForContract(contractHash, opts);
1074
1256
  if (overlayNames.length === 0) {
1075
- lines.push(emptyGutter.replace(/\s+$/, ""));
1257
+ lines.push(trimTrailingWhitespace(emptyGutter));
1076
1258
  continue;
1077
1259
  }
1078
1260
  const overlay = style.refs(overlayNames);
1079
- lines.push(`${padVisible(emptyGutter, dataColumn)}${overlay}`.replace(/\s+$/, ""));
1261
+ lines.push(trimTrailingWhitespace(`${padVisible(emptyGutter, dataColumn)}${overlay}`));
1080
1262
  continue;
1081
1263
  }
1082
1264
  const hashText = style.sourceHash(abbreviateHash(contractHash, hashLength, palette.emptySource));
1083
1265
  const overlayNames = overlayNamesForContract(contractHash, opts);
1084
1266
  const overlayPad = overlayNames.length > 0 ? " ".repeat(Math.max(0, dataColumn - labelColumn - stringWidth(hashText))) : "";
1085
1267
  const overlay = overlayNames.length > 0 ? style.refs(overlayNames) : "";
1086
- lines.push(`${gutterPad}${hashText}${overlayPad}${overlay}`.replace(/\s+$/, ""));
1268
+ lines.push(trimTrailingWhitespace(`${gutterPad}${hashText}${overlayPad}${overlay}`));
1087
1269
  continue;
1088
1270
  }
1089
1271
  const edge = row.edge;
1090
1272
  if (edge === void 0) continue;
1091
1273
  const dirNamePadding = " ".repeat(Math.max(0, dirNameWidth - edge.dirName.length));
1092
- const dirName = `${style.dirName(edge.dirName)}${dirNamePadding}`;
1274
+ const laneIndex = row.laneIndex ?? 0;
1275
+ const dirName = `${(opts.colorize && laneIndex > NEUTRAL_LANE ? (text) => forcedBold(laneColorForColumn(laneIndex)(text)) : style.dirName)(edge.dirName)}${dirNamePadding}`;
1093
1276
  const hashColumn = formatEdgeHashColumn(edge, style, hashLength, palette);
1094
- lines.push(`${gutterPad}${dirName}${hashColumn}`.replace(/\s+$/, ""));
1277
+ lines.push(trimTrailingWhitespace(`${gutterPad}${dirName}${hashColumn}`));
1095
1278
  }
1096
1279
  return lines.join("\n");
1097
1280
  }
1281
+ /**
1282
+ * A compact key for the `--tree` visual language: the contract marker, the
1283
+ * in-lane direction arrows, the empty baseline, the `(refs)` overlay (including
1284
+ * the reserved `db` live-database and `contract` working-schema markers), and a
1285
+ * worked sample of the data-column `from → to` migration hash arrow.
1286
+ *
1287
+ * Honors the same glyph palette (unicode vs ASCII) and `colorize` gate as the
1288
+ * tree renderer, so the key matches whatever the graph itself drew and stays
1289
+ * pipe-safe (zero ANSI when color is off). The caller adds the trailing blank
1290
+ * line that separates this stderr key from the graph on stdout.
1291
+ */
1292
+ function renderMigrationGraphLegend(opts) {
1293
+ const palette = paletteFor(opts.glyphMode ?? "unicode");
1294
+ const style = createAnsiMigrationListStyler({ useColor: opts.colorize });
1295
+ const node = palette.node.trimEnd();
1296
+ const sampleArrow = `${style.sourceHash("aaaaaa")} ${style.glyph(palette.forwardArrow)} ${style.destHash("bbbbbb")}`;
1297
+ return [
1298
+ "Legend:",
1299
+ ` ${style.kind(node)} contract ${style.kind(palette.edgeArrow.forward)} forward ${style.kind(palette.edgeArrow.rollback)} rollback`,
1300
+ ` ${style.kind(palette.edgeArrow.self)} migration without schema change`,
1301
+ ` ${style.glyph(palette.emptySource)} empty database (baseline)`,
1302
+ ` ${style.refs(["refs"])} ${DB_MARKER_NAME} / ${CONTRACT_MARKER_NAME} markers`,
1303
+ ` ${sampleArrow} migration from contract aaaaaa to bbbbbb`
1304
+ ].join("\n");
1305
+ }
1098
1306
  //#endregion
1099
1307
  //#region src/commands/migration-graph.ts
1308
+ /**
1309
+ * `--legend` describes the `--tree` visual language, so passing it auto-enables
1310
+ * the tree path (it has nothing to say about the legacy dagre default).
1311
+ */
1312
+ function migrationGraphUsesTree(options) {
1313
+ return options.tree === true || options.legend === true;
1314
+ }
1315
+ /**
1316
+ * The legend is decoration printed alongside the command header on stderr, so
1317
+ * it is suppressed for the machine-readable / silent paths (`--json`, `--dot`,
1318
+ * `--quiet`) exactly as the header is.
1319
+ */
1320
+ function migrationGraphShowsLegend(options, flags) {
1321
+ return options.legend === true && options.dot !== true && flags.json !== true && flags.quiet !== true;
1322
+ }
1100
1323
  async function executeMigrationGraphCommand(options, flags, ui) {
1101
1324
  const config = await loadConfig(options.config);
1102
1325
  const { configPath, appMigrationsRelative, migrationsDir } = resolveMigrationPaths(options.config, config);
@@ -1114,6 +1337,13 @@ async function executeMigrationGraphCommand(options, flags, ui) {
1114
1337
  flags
1115
1338
  });
1116
1339
  ui.stderr(header);
1340
+ if (migrationGraphShowsLegend(options, flags)) {
1341
+ ui.stderr(renderMigrationGraphLegend({
1342
+ colorize: flags.color !== false,
1343
+ glyphMode: ui.resolveGlyphMode(options.ascii === true)
1344
+ }));
1345
+ ui.stderr("");
1346
+ }
1117
1347
  }
1118
1348
  const loaded = await buildReadAggregate(config, { migrationsDir });
1119
1349
  if (!loaded.ok) return loaded;
@@ -1139,7 +1369,8 @@ function createMigrationGraphCommand() {
1139
1369
  "prisma-next migration graph --json",
1140
1370
  "prisma-next migration graph --dot",
1141
1371
  "prisma-next migration graph --tree",
1142
- "prisma-next migration graph --tree --ascii"
1372
+ "prisma-next migration graph --tree --ascii",
1373
+ "prisma-next migration graph --legend"
1143
1374
  ]);
1144
1375
  setCommandSeeAlso(command, [
1145
1376
  {
@@ -1159,7 +1390,7 @@ function createMigrationGraphCommand() {
1159
1390
  oneLiner: "Display migration package contents"
1160
1391
  }
1161
1392
  ]);
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) => {
1393
+ 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
1394
  const flags = parseGlobalFlagsOrExit(options);
1164
1395
  const ui = createTerminalUI(flags);
1165
1396
  const exitCode = handleResult(await executeMigrationGraphCommand(options, flags, ui), flags, ui, (graphResult) => {
@@ -1186,7 +1417,7 @@ function createMigrationGraphCommand() {
1186
1417
  edges,
1187
1418
  summary: graphResult.summary
1188
1419
  }, null, 2));
1189
- } else if (!flags.quiet) if (options.tree) {
1420
+ } else if (!flags.quiet) if (migrationGraphUsesTree(options)) {
1190
1421
  const refsByHash = /* @__PURE__ */ new Map();
1191
1422
  for (const ref of graphResult.refs) {
1192
1423
  const existing = refsByHash.get(ref.hash);
@@ -1227,6 +1458,6 @@ function createMigrationGraphCommand() {
1227
1458
  return command;
1228
1459
  }
1229
1460
  //#endregion
1230
- export { executeMigrationGraphCommand as n, createMigrationGraphCommand as t };
1461
+ export { migrationGraphUsesTree as i, executeMigrationGraphCommand as n, migrationGraphShowsLegend as r, createMigrationGraphCommand as t };
1231
1462
 
1232
- //# sourceMappingURL=migration-graph-D7DVUElV.mjs.map
1463
+ //# sourceMappingURL=migration-graph-BzxEsMZg.mjs.map