@prisma-next/cli 0.12.0-dev.6 → 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 +2 -2
- package/dist/commands/migration-graph.d.mts +19 -1
- package/dist/commands/migration-graph.d.mts.map +1 -1
- package/dist/commands/migration-graph.mjs +2 -2
- package/dist/{migration-graph-D7DVUElV.mjs → migration-graph-BzxEsMZg.mjs} +296 -65
- package/dist/migration-graph-BzxEsMZg.mjs.map +1 -0
- package/package.json +18 -18
- package/src/commands/migration-graph.ts +43 -2
- package/src/utils/formatters/migration-graph-lane-colors.ts +31 -0
- package/src/utils/formatters/migration-graph-tree-render.ts +344 -48
- package/dist/migration-graph-D7DVUElV.mjs.map +0 -1
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-
|
|
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.
|
|
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":";;;;;;;;;
|
|
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-
|
|
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
|
-
*
|
|
908
|
-
*
|
|
909
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
918
|
-
if (cell.
|
|
919
|
-
return
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
case "
|
|
923
|
-
case "
|
|
924
|
-
case "branch-
|
|
925
|
-
case "merge-
|
|
926
|
-
case "
|
|
927
|
-
case "
|
|
928
|
-
case "arc-
|
|
929
|
-
case "arc-
|
|
930
|
-
case "arc-land-
|
|
931
|
-
case "
|
|
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 (
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
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 +=
|
|
971
|
-
else if (column === end) out +=
|
|
972
|
-
else out +=
|
|
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)
|
|
1231
|
+
lines.push(trimTrailingWhitespace(renderConnectorRow(row, gridWidth, opts.colorize, style, palette)));
|
|
1051
1232
|
continue;
|
|
1052
1233
|
}
|
|
1053
|
-
|
|
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
|
|
1257
|
+
lines.push(trimTrailingWhitespace(emptyGutter));
|
|
1076
1258
|
continue;
|
|
1077
1259
|
}
|
|
1078
1260
|
const overlay = style.refs(overlayNames);
|
|
1079
|
-
lines.push(`${padVisible(emptyGutter, dataColumn)}${overlay}
|
|
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}
|
|
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
|
|
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}
|
|
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
|
|
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-
|
|
1463
|
+
//# sourceMappingURL=migration-graph-BzxEsMZg.mjs.map
|