@prisma-next/cli 0.12.0-dev.27 → 0.12.0-dev.29
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 +12 -12
- package/dist/{client-V7BkIQrQ.mjs → client-xeWpMlq1.mjs} +22 -11
- package/dist/client-xeWpMlq1.mjs.map +1 -0
- package/dist/{command-helpers-DlrUCI7s.mjs → command-helpers-DK_5ItoJ.mjs} +253 -2
- package/dist/command-helpers-DK_5ItoJ.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +4 -5
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.mjs +3 -3
- package/dist/commands/db-sign.mjs +4 -4
- package/dist/commands/db-update.d.mts.map +1 -1
- package/dist/commands/db-update.mjs +10 -7
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.d.mts +1 -1
- package/dist/commands/migrate.mjs +5 -6
- package/dist/commands/migrate.mjs.map +1 -1
- package/dist/commands/migration-check.mjs +1 -1
- package/dist/commands/migration-graph.d.mts +12 -15
- package/dist/commands/migration-graph.d.mts.map +1 -1
- package/dist/commands/migration-graph.mjs +84 -51
- package/dist/commands/migration-graph.mjs.map +1 -1
- package/dist/commands/migration-list.d.mts +14 -5
- package/dist/commands/migration-list.d.mts.map +1 -1
- package/dist/commands/migration-list.mjs +1 -187
- package/dist/commands/migration-log.d.mts +2 -2
- package/dist/commands/migration-log.mjs +1 -1
- package/dist/commands/migration-new.mjs +3 -3
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.d.mts +1 -1
- package/dist/commands/migration-show.mjs +3 -4
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +21 -3
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +2 -3
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/ref.mjs +3 -3
- package/dist/commands/telemetry/index.mjs +1 -1
- package/dist/{contract-at-errors-DlZHXSkI.mjs → contract-at-errors-DG3kjgoz.mjs} +2 -2
- package/dist/{contract-at-errors-DlZHXSkI.mjs.map → contract-at-errors-DG3kjgoz.mjs.map} +1 -1
- package/dist/{contract-emit-S53EyBRV.mjs → contract-emit-BO0l6fnT.mjs} +3 -3
- package/dist/{contract-emit-S53EyBRV.mjs.map → contract-emit-BO0l6fnT.mjs.map} +1 -1
- package/dist/{contract-emit-CaKp92-Q.mjs → contract-emit-C0Bs0VRj.mjs} +3 -3
- package/dist/{contract-emit-CaKp92-Q.mjs.map → contract-emit-C0Bs0VRj.mjs.map} +1 -1
- package/dist/{contract-infer-Cebb-_Qx.mjs → contract-infer-2wtPflGH.mjs} +3 -3
- package/dist/{contract-infer-Cebb-_Qx.mjs.map → contract-infer-2wtPflGH.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-Dvl1SJ4C.mjs → contract-space-aggregate-loader-Dbr3-jHF.mjs} +3 -3
- package/dist/{contract-space-aggregate-loader-Dvl1SJ4C.mjs.map → contract-space-aggregate-loader-Dbr3-jHF.mjs.map} +1 -1
- package/dist/{db-verify-B1OoWEWn.mjs → db-verify-CxHiSiTG.mjs} +4 -4
- package/dist/{db-verify-B1OoWEWn.mjs.map → db-verify-CxHiSiTG.mjs.map} +1 -1
- package/dist/exports/control-api.d.mts +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.mjs +1 -1
- package/dist/{framework-components-DCAT1uUC.mjs → framework-components-CxOVKAAh.mjs} +2 -2
- package/dist/{framework-components-DCAT1uUC.mjs.map → framework-components-CxOVKAAh.mjs.map} +1 -1
- package/dist/{init-Kf3T4A4W.mjs → init-R272pxux.mjs} +3 -3
- package/dist/{init-Kf3T4A4W.mjs.map → init-R272pxux.mjs.map} +1 -1
- package/dist/{inspect-live-schema-DTqflZ8X.mjs → inspect-live-schema-RekOwfi5.mjs} +3 -3
- package/dist/{inspect-live-schema-DTqflZ8X.mjs.map → inspect-live-schema-RekOwfi5.mjs.map} +1 -1
- package/dist/{migration-check-Ccyd0QKb.mjs → migration-check-Dc0cOhKH.mjs} +2 -2
- package/dist/{migration-check-Ccyd0QKb.mjs.map → migration-check-Dc0cOhKH.mjs.map} +1 -1
- package/dist/{migration-command-scaffold-DI7_SFL0.mjs → migration-command-scaffold-ApB3NxWY.mjs} +3 -3
- package/dist/{migration-command-scaffold-DI7_SFL0.mjs.map → migration-command-scaffold-ApB3NxWY.mjs.map} +1 -1
- package/dist/{migration-graph-tree-render-DyDBuJEX.mjs → migration-graph-space-render-dmLLWift.mjs} +389 -210
- package/dist/migration-graph-space-render-dmLLWift.mjs.map +1 -0
- package/dist/migration-list-C5sXrl0U.mjs +228 -0
- package/dist/migration-list-C5sXrl0U.mjs.map +1 -0
- package/dist/{migration-list-types-DV9PBc7Z.d.mts → migration-list-types-DS63IdFd.d.mts} +1 -1
- package/dist/{migration-list-types-DV9PBc7Z.d.mts.map → migration-list-types-DS63IdFd.d.mts.map} +1 -1
- package/dist/{migration-log-Des4seHP.mjs → migration-log-DD_vCbYW.mjs} +4 -4
- package/dist/{migration-log-Des4seHP.mjs.map → migration-log-DD_vCbYW.mjs.map} +1 -1
- package/dist/{migration-plan-DxDTBzGS.mjs → migration-plan-CeTjQOIG.mjs} +5 -5
- package/dist/{migration-plan-DxDTBzGS.mjs.map → migration-plan-CeTjQOIG.mjs.map} +1 -1
- package/dist/{migration-status-CCwqA-vi.mjs → migration-status-qV8ctwPy.mjs} +61 -45
- package/dist/migration-status-qV8ctwPy.mjs.map +1 -0
- package/dist/{ref-advancement-DUZqsue6.mjs → ref-advancement-V1o-9LVK.mjs} +1 -1
- package/dist/{ref-advancement-DUZqsue6.mjs.map → ref-advancement-V1o-9LVK.mjs.map} +1 -1
- package/dist/{telemetry-LFFQmqHd.mjs → telemetry-S-NGi9U6.mjs} +2 -2
- package/dist/{telemetry-LFFQmqHd.mjs.map → telemetry-S-NGi9U6.mjs.map} +1 -1
- package/dist/{terminal-ui-C3xGyxW-.d.mts → terminal-ui-5Y6mrg93.d.mts} +1 -1
- package/dist/{terminal-ui-C3xGyxW-.d.mts.map → terminal-ui-5Y6mrg93.d.mts.map} +1 -1
- package/dist/{types-BdS8PoKM.d.mts → types-Mh7mdPHM.d.mts} +5 -1
- package/dist/types-Mh7mdPHM.d.mts.map +1 -0
- package/dist/{verify-C0TARc6h.mjs → verify-BdI-BgYi.mjs} +2 -2
- package/dist/{verify-C0TARc6h.mjs.map → verify-BdI-BgYi.mjs.map} +1 -1
- package/package.json +18 -18
- package/src/commands/db-update.ts +7 -1
- package/src/commands/migration-graph.ts +125 -58
- package/src/commands/migration-list.ts +43 -9
- package/src/commands/migration-status.ts +106 -74
- package/src/control-api/operations/db-apply.ts +24 -1
- package/src/control-api/types.ts +4 -0
- package/src/utils/cli-errors.ts +17 -0
- package/src/utils/formatters/errors.ts +11 -0
- package/src/utils/formatters/migration-graph-lane-colors.ts +164 -1
- package/src/utils/formatters/migration-graph-rows.ts +128 -15
- package/src/utils/formatters/migration-graph-space-render.ts +138 -0
- package/src/utils/formatters/migration-graph-tree-render.ts +149 -239
- package/src/utils/formatters/migration-list-data-column.ts +6 -0
- package/src/utils/formatters/migration-list-render.ts +43 -23
- package/src/utils/formatters/migration-list-styler.ts +48 -5
- package/src/utils/formatters/migrations.ts +25 -1
- package/src/utils/legend.ts +38 -0
- package/dist/client-V7BkIQrQ.mjs.map +0 -1
- package/dist/command-helpers-DlrUCI7s.mjs.map +0 -1
- package/dist/commands/migration-list.mjs.map +0 -1
- package/dist/migration-graph-tree-render-DyDBuJEX.mjs.map +0 -1
- package/dist/migration-status-CCwqA-vi.mjs.map +0 -1
- package/dist/migration-types-CAQ-0TEE.d.mts +0 -15
- package/dist/migration-types-CAQ-0TEE.d.mts.map +0 -1
- package/dist/migrations-B3H6RTXb.mjs +0 -228
- package/dist/migrations-B3H6RTXb.mjs.map +0 -1
- package/dist/types-BdS8PoKM.d.mts.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createColors } from 'colorette';
|
|
2
|
+
import type { StructuralCell } from './migration-graph-layout';
|
|
2
3
|
|
|
3
4
|
export type LaneColorizer = (text: string) => string;
|
|
4
5
|
|
|
@@ -13,6 +14,12 @@ export const LANE_COLOR_CYCLE: readonly LaneColorizer[] = [
|
|
|
13
14
|
red,
|
|
14
15
|
];
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* The leftmost lane (column 0) renders neutral — no palette hue. Columns ≥ 1
|
|
19
|
+
* rotate through {@link LANE_COLOR_CYCLE}.
|
|
20
|
+
*/
|
|
21
|
+
export const NEUTRAL_LANE_COLUMN = 0;
|
|
22
|
+
|
|
16
23
|
/**
|
|
17
24
|
* The hue for a gutter column. The leftmost lane (column 0) is **neutral** — it
|
|
18
25
|
* has nothing to be told apart from in the common single-lane linear case, so
|
|
@@ -23,9 +30,165 @@ export const LANE_COLOR_CYCLE: readonly LaneColorizer[] = [
|
|
|
23
30
|
* branch identity, exactly like `git log --graph`.
|
|
24
31
|
*/
|
|
25
32
|
export function laneColorForColumn(column: number): LaneColorizer {
|
|
26
|
-
if (column <=
|
|
33
|
+
if (column <= NEUTRAL_LANE_COLUMN) {
|
|
27
34
|
return (text) => text;
|
|
28
35
|
}
|
|
29
36
|
const colorizer = LANE_COLOR_CYCLE[(column - 1) % LANE_COLOR_CYCLE.length];
|
|
30
37
|
return colorizer ?? ((text) => text);
|
|
31
38
|
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Style a structural glyph by its resolved colour column. Column 0 and the
|
|
42
|
+
* neutral sentinel render dim (`dimLane`); columns ≥ 1 take a palette hue.
|
|
43
|
+
*/
|
|
44
|
+
export function stylerForLaneColumn(
|
|
45
|
+
colorColumn: number,
|
|
46
|
+
colorize: boolean,
|
|
47
|
+
dimLane: (text: string) => string,
|
|
48
|
+
): LaneColorizer {
|
|
49
|
+
if (!colorize || colorColumn <= NEUTRAL_LANE_COLUMN) {
|
|
50
|
+
return dimLane;
|
|
51
|
+
}
|
|
52
|
+
return laneColorForColumn(colorColumn);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The colour-source column for each cell of a row, resolved together because a
|
|
57
|
+
* routed back-arc spans columns and must read as **one hue** rather than a
|
|
58
|
+
* per-column "rainbow".
|
|
59
|
+
*/
|
|
60
|
+
export interface RowArcLaneColors {
|
|
61
|
+
/** Colour column for a cell's structural glyph (lane / spine / arc body). */
|
|
62
|
+
readonly lane: readonly number[];
|
|
63
|
+
/** Colour column for a node arc-pair's connector half (`◂` / `─`). */
|
|
64
|
+
readonly connector: readonly number[];
|
|
65
|
+
/**
|
|
66
|
+
* Colour column for the trailing `─` of a landing tee (`┴─`). The junction
|
|
67
|
+
* (`lane`) keeps its own column; the dash leads into the next converging arc.
|
|
68
|
+
*/
|
|
69
|
+
readonly dash: readonly number[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolve per-cell colour columns for a node/arc row. Scanning right-to-left
|
|
74
|
+
* lets each arc segment inherit the hue of the arc it leads into.
|
|
75
|
+
*/
|
|
76
|
+
export function resolveRowArcLaneColors(cells: readonly StructuralCell[]): RowArcLaneColors {
|
|
77
|
+
const lane = new Array<number>(cells.length);
|
|
78
|
+
const connector = new Array<number>(cells.length);
|
|
79
|
+
const dash = new Array<number>(cells.length);
|
|
80
|
+
let arcCorner = NEUTRAL_LANE_COLUMN;
|
|
81
|
+
let landingAnchor = NEUTRAL_LANE_COLUMN;
|
|
82
|
+
for (let column = cells.length - 1; column >= 0; column--) {
|
|
83
|
+
const cell = cells[column];
|
|
84
|
+
connector[column] = landingAnchor !== NEUTRAL_LANE_COLUMN ? landingAnchor : arcCorner;
|
|
85
|
+
switch (cell?.kind) {
|
|
86
|
+
case 'arc-branch-corner':
|
|
87
|
+
arcCorner = column;
|
|
88
|
+
lane[column] = column;
|
|
89
|
+
dash[column] = column;
|
|
90
|
+
break;
|
|
91
|
+
case 'arc-land-corner':
|
|
92
|
+
arcCorner = column;
|
|
93
|
+
landingAnchor = column;
|
|
94
|
+
lane[column] = column;
|
|
95
|
+
dash[column] = column;
|
|
96
|
+
break;
|
|
97
|
+
case 'arc-branch-tee':
|
|
98
|
+
lane[column] = column;
|
|
99
|
+
dash[column] = column;
|
|
100
|
+
break;
|
|
101
|
+
case 'arc-land-tee':
|
|
102
|
+
lane[column] = column;
|
|
103
|
+
dash[column] = landingAnchor === NEUTRAL_LANE_COLUMN ? column : landingAnchor;
|
|
104
|
+
landingAnchor = column;
|
|
105
|
+
break;
|
|
106
|
+
case 'arc-crossing':
|
|
107
|
+
case 'arc-land-bridge': {
|
|
108
|
+
const served = landingAnchor !== NEUTRAL_LANE_COLUMN ? landingAnchor : arcCorner;
|
|
109
|
+
lane[column] = served;
|
|
110
|
+
dash[column] = served;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case 'horizontal-pass':
|
|
114
|
+
lane[column] = arcCorner === NEUTRAL_LANE_COLUMN ? column : arcCorner;
|
|
115
|
+
dash[column] = lane[column] ?? column;
|
|
116
|
+
break;
|
|
117
|
+
case 'node':
|
|
118
|
+
lane[column] = column;
|
|
119
|
+
dash[column] = column;
|
|
120
|
+
arcCorner = NEUTRAL_LANE_COLUMN;
|
|
121
|
+
landingAnchor = NEUTRAL_LANE_COLUMN;
|
|
122
|
+
break;
|
|
123
|
+
default:
|
|
124
|
+
lane[column] = column;
|
|
125
|
+
dash[column] = column;
|
|
126
|
+
arcCorner = NEUTRAL_LANE_COLUMN;
|
|
127
|
+
landingAnchor = NEUTRAL_LANE_COLUMN;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { lane, connector, dash };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Per-cell colour for a forward branch/merge connector row, split into the
|
|
135
|
+
* cell's junction `glyph` and its trailing `dash`.
|
|
136
|
+
*/
|
|
137
|
+
export interface ConnectorLaneColors {
|
|
138
|
+
/** Colour column for a cell's junction glyph (`├` / `┬` / `┴` / `╮` / `╯`). */
|
|
139
|
+
readonly glyph: readonly number[];
|
|
140
|
+
/** Colour column for a tee's trailing `─` — the branch it leads into. */
|
|
141
|
+
readonly dash: readonly number[];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Resolve per-cell connector colours. Scanning right-to-left, a corner or an
|
|
146
|
+
* intermediate tee anchors its own lane, but a tee's trailing dash leads into
|
|
147
|
+
* the branch on its right.
|
|
148
|
+
*/
|
|
149
|
+
export function resolveConnectorLaneColors(
|
|
150
|
+
cells: readonly StructuralCell[],
|
|
151
|
+
startLane: number,
|
|
152
|
+
): ConnectorLaneColors {
|
|
153
|
+
const glyph = new Array<number>(cells.length);
|
|
154
|
+
const dash = new Array<number>(cells.length);
|
|
155
|
+
let owner = NEUTRAL_LANE_COLUMN;
|
|
156
|
+
for (let column = cells.length - 1; column >= 0; column--) {
|
|
157
|
+
const cell = cells[column];
|
|
158
|
+
switch (cell?.kind) {
|
|
159
|
+
case 'branch-corner':
|
|
160
|
+
case 'merge-corner':
|
|
161
|
+
owner = column;
|
|
162
|
+
glyph[column] = column;
|
|
163
|
+
dash[column] = column;
|
|
164
|
+
break;
|
|
165
|
+
case 'branch-tee':
|
|
166
|
+
case 'merge-tee':
|
|
167
|
+
if (column === startLane) {
|
|
168
|
+
const served = owner === NEUTRAL_LANE_COLUMN ? column : owner;
|
|
169
|
+
glyph[column] = column;
|
|
170
|
+
dash[column] = served;
|
|
171
|
+
} else {
|
|
172
|
+
dash[column] = owner === NEUTRAL_LANE_COLUMN ? column : owner;
|
|
173
|
+
glyph[column] = column;
|
|
174
|
+
owner = column;
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
case 'arc-crossing':
|
|
178
|
+
glyph[column] = column;
|
|
179
|
+
dash[column] = owner === NEUTRAL_LANE_COLUMN ? column : owner;
|
|
180
|
+
owner = column;
|
|
181
|
+
break;
|
|
182
|
+
case 'horizontal-pass': {
|
|
183
|
+
const served = owner === NEUTRAL_LANE_COLUMN ? column : owner;
|
|
184
|
+
glyph[column] = served;
|
|
185
|
+
dash[column] = served;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
default:
|
|
189
|
+
glyph[column] = column;
|
|
190
|
+
dash[column] = column;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return { glyph, dash };
|
|
194
|
+
}
|
|
@@ -173,10 +173,19 @@ function compareNodesTipsFirst(a: string, b: string, rank: ReadonlyMap<string, n
|
|
|
173
173
|
* at the same rank — stable across edge-insertion order and correct under
|
|
174
174
|
* diamonds, cross-links, and rollbacks.
|
|
175
175
|
*/
|
|
176
|
+
function maxRank(rank: ReadonlyMap<string, number>): number {
|
|
177
|
+
let max = 0;
|
|
178
|
+
for (const value of rank.values()) {
|
|
179
|
+
if (value > max) max = value;
|
|
180
|
+
}
|
|
181
|
+
return max;
|
|
182
|
+
}
|
|
183
|
+
|
|
176
184
|
function layerNodesByLongestForwardPath(
|
|
177
185
|
componentNodes: ReadonlySet<string>,
|
|
178
186
|
topology: MigrationListGraphTopology,
|
|
179
187
|
graph: MigrationGraph,
|
|
188
|
+
contractHash: string | undefined,
|
|
180
189
|
): readonly string[] {
|
|
181
190
|
const forwardOut = new Map<string, string[]>();
|
|
182
191
|
|
|
@@ -224,6 +233,15 @@ function layerNodesByLongestForwardPath(
|
|
|
224
233
|
}
|
|
225
234
|
}
|
|
226
235
|
|
|
236
|
+
if (
|
|
237
|
+
contractHash !== undefined &&
|
|
238
|
+
contractHash !== EMPTY_CONTRACT_HASH &&
|
|
239
|
+
componentNodes.has(contractHash) &&
|
|
240
|
+
(forwardOut.get(contractHash) ?? []).length === 0
|
|
241
|
+
) {
|
|
242
|
+
rank.set(contractHash, maxRank(rank) + 1);
|
|
243
|
+
}
|
|
244
|
+
|
|
227
245
|
return [...componentNodes].sort((a, b) => compareNodesTipsFirst(a, b, rank));
|
|
228
246
|
}
|
|
229
247
|
|
|
@@ -262,6 +280,99 @@ function detachedContractHash(
|
|
|
262
280
|
: undefined;
|
|
263
281
|
}
|
|
264
282
|
|
|
283
|
+
function isForwardLeaf(node: string, edges: readonly ClassifiedEdge[]): boolean {
|
|
284
|
+
return !edges.some((e) => e.kind === 'forward' && e.from === node && e.from !== e.to);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function forwardReachableFrom(
|
|
288
|
+
start: string,
|
|
289
|
+
forwardTo: ReadonlyMap<string, readonly string[]>,
|
|
290
|
+
): ReadonlySet<string> {
|
|
291
|
+
const reachable = new Set<string>([start]);
|
|
292
|
+
const queue = [start];
|
|
293
|
+
while (queue.length > 0) {
|
|
294
|
+
const node = queue.shift();
|
|
295
|
+
if (node === undefined) continue;
|
|
296
|
+
for (const next of forwardTo.get(node) ?? []) {
|
|
297
|
+
if (!reachable.has(next)) {
|
|
298
|
+
reachable.add(next);
|
|
299
|
+
queue.push(next);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return reachable;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function buildForwardToMap(edges: readonly ClassifiedEdge[]): Map<string, string[]> {
|
|
307
|
+
const forwardTo = new Map<string, string[]>();
|
|
308
|
+
for (const edge of edges) {
|
|
309
|
+
if (edge.kind !== 'forward' || edge.from === edge.to) continue;
|
|
310
|
+
const bucket = forwardTo.get(edge.from);
|
|
311
|
+
if (bucket) bucket.push(edge.to);
|
|
312
|
+
else forwardTo.set(edge.from, [edge.to]);
|
|
313
|
+
}
|
|
314
|
+
return forwardTo;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function sortEdgesForContractHashTrunk(
|
|
318
|
+
edges: ClassifiedEdge[],
|
|
319
|
+
contractHash: string | undefined,
|
|
320
|
+
): ClassifiedEdge[] {
|
|
321
|
+
if (
|
|
322
|
+
contractHash === undefined ||
|
|
323
|
+
contractHash === EMPTY_CONTRACT_HASH ||
|
|
324
|
+
!isForwardLeaf(contractHash, edges)
|
|
325
|
+
) {
|
|
326
|
+
return edges;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const preferredLeaf = contractHash;
|
|
330
|
+
const forwardTo = buildForwardToMap(edges);
|
|
331
|
+
const reachability = new Map<string, ReadonlySet<string>>();
|
|
332
|
+
function canReachContractHash(from: string): boolean {
|
|
333
|
+
let cached = reachability.get(from);
|
|
334
|
+
if (cached === undefined) {
|
|
335
|
+
cached = forwardReachableFrom(from, forwardTo);
|
|
336
|
+
reachability.set(from, cached);
|
|
337
|
+
}
|
|
338
|
+
return cached.has(preferredLeaf);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function trunkBias(edge: ClassifiedEdge): number {
|
|
342
|
+
if (edge.kind !== 'forward' || edge.from === edge.to) return 0;
|
|
343
|
+
if (edge.to === preferredLeaf) return 2;
|
|
344
|
+
if (canReachContractHash(edge.to)) return 1;
|
|
345
|
+
return 0;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return edges
|
|
349
|
+
.map((edge, index) => ({ edge, index, bias: trunkBias(edge) }))
|
|
350
|
+
.sort((a, b) => {
|
|
351
|
+
if (a.edge.from !== b.edge.from) return a.index - b.index;
|
|
352
|
+
if (a.bias !== b.bias) return b.bias - a.bias;
|
|
353
|
+
return a.index - b.index;
|
|
354
|
+
})
|
|
355
|
+
.map(({ edge }) => edge);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function rebuildEdgeLookupMaps(edges: readonly ClassifiedEdge[]): {
|
|
359
|
+
edgesByFrom: Map<string, ClassifiedEdge[]>;
|
|
360
|
+
edgesByTo: Map<string, ClassifiedEdge[]>;
|
|
361
|
+
} {
|
|
362
|
+
const edgesByFrom = new Map<string, ClassifiedEdge[]>();
|
|
363
|
+
const edgesByTo = new Map<string, ClassifiedEdge[]>();
|
|
364
|
+
for (const classified of edges) {
|
|
365
|
+
const fromBucket = edgesByFrom.get(classified.from);
|
|
366
|
+
if (fromBucket) fromBucket.push(classified);
|
|
367
|
+
else edgesByFrom.set(classified.from, [classified]);
|
|
368
|
+
|
|
369
|
+
const toBucket = edgesByTo.get(classified.to);
|
|
370
|
+
if (toBucket) toBucket.push(classified);
|
|
371
|
+
else edgesByTo.set(classified.to, [classified]);
|
|
372
|
+
}
|
|
373
|
+
return { edgesByFrom, edgesByTo };
|
|
374
|
+
}
|
|
375
|
+
|
|
265
376
|
export function buildMigrationGraphRows(
|
|
266
377
|
graph: MigrationGraph,
|
|
267
378
|
options: BuildMigrationGraphRowsOptions = {},
|
|
@@ -284,31 +395,23 @@ export function buildMigrationGraphRows(
|
|
|
284
395
|
|
|
285
396
|
// 2. Build classified edge list
|
|
286
397
|
const edges: ClassifiedEdge[] = [];
|
|
287
|
-
const edgesByFrom = new Map<string, ClassifiedEdge[]>();
|
|
288
|
-
const edgesByTo = new Map<string, ClassifiedEdge[]>();
|
|
289
398
|
|
|
290
399
|
for (const edgeList of graph.forwardChain.values()) {
|
|
291
400
|
for (const edge of edgeList) {
|
|
292
401
|
const kind = topology.kindByMigrationHash.get(edge.migrationHash) ?? 'forward';
|
|
293
|
-
|
|
402
|
+
edges.push({
|
|
294
403
|
migrationHash: edge.migrationHash,
|
|
295
404
|
from: edge.from,
|
|
296
405
|
to: edge.to,
|
|
297
406
|
dirName: edge.dirName,
|
|
298
407
|
kind,
|
|
299
|
-
};
|
|
300
|
-
edges.push(classified);
|
|
301
|
-
|
|
302
|
-
const fromBucket = edgesByFrom.get(edge.from);
|
|
303
|
-
if (fromBucket) fromBucket.push(classified);
|
|
304
|
-
else edgesByFrom.set(edge.from, [classified]);
|
|
305
|
-
|
|
306
|
-
const toBucket = edgesByTo.get(edge.to);
|
|
307
|
-
if (toBucket) toBucket.push(classified);
|
|
308
|
-
else edgesByTo.set(edge.to, [classified]);
|
|
408
|
+
});
|
|
309
409
|
}
|
|
310
410
|
}
|
|
311
411
|
|
|
412
|
+
const sortedEdges = sortEdgesForContractHashTrunk(edges, options.contractHash);
|
|
413
|
+
const { edgesByFrom, edgesByTo } = rebuildEdgeLookupMaps(sortedEdges);
|
|
414
|
+
|
|
312
415
|
// 3. Find weakly-connected components (ordered: EMPTY first, then lex)
|
|
313
416
|
const components = weaklyConnectedComponents(graph);
|
|
314
417
|
|
|
@@ -318,7 +421,12 @@ export function buildMigrationGraphRows(
|
|
|
318
421
|
if (i > 0) nodes.push(null);
|
|
319
422
|
const component = components[i];
|
|
320
423
|
if (component === undefined) continue;
|
|
321
|
-
const ordered = layerNodesByLongestForwardPath(
|
|
424
|
+
const ordered = layerNodesByLongestForwardPath(
|
|
425
|
+
component,
|
|
426
|
+
topology,
|
|
427
|
+
graph,
|
|
428
|
+
options.contractHash,
|
|
429
|
+
);
|
|
322
430
|
for (const node of ordered) {
|
|
323
431
|
nodes.push(node);
|
|
324
432
|
}
|
|
@@ -332,5 +440,10 @@ export function buildMigrationGraphRows(
|
|
|
332
440
|
nodes.unshift(detached);
|
|
333
441
|
}
|
|
334
442
|
|
|
335
|
-
return {
|
|
443
|
+
return {
|
|
444
|
+
nodes,
|
|
445
|
+
edges: sortedEdges,
|
|
446
|
+
edgesByFrom,
|
|
447
|
+
edgesByTo,
|
|
448
|
+
};
|
|
336
449
|
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
2
|
+
import type { GlyphMode } from '../glyph-mode';
|
|
3
|
+
import { buildMigrationGraphLayout } from './migration-graph-layout';
|
|
4
|
+
import { buildMigrationGraphRows } from './migration-graph-rows';
|
|
5
|
+
import {
|
|
6
|
+
computeMaxDirNameLengthForLayout,
|
|
7
|
+
computeMaxEdgeTreePrefixWidthForLayout,
|
|
8
|
+
type MigrationEdgeAnnotation,
|
|
9
|
+
renderMigrationGraphTree,
|
|
10
|
+
} from './migration-graph-tree-render';
|
|
11
|
+
import {
|
|
12
|
+
buildEdgeAnnotationsByHashFromListEntries,
|
|
13
|
+
buildRefsByHashFromListEntries,
|
|
14
|
+
type MigrationListStyler,
|
|
15
|
+
} from './migration-list-render';
|
|
16
|
+
import type { MigrationListEntry } from './migration-list-types';
|
|
17
|
+
|
|
18
|
+
export { buildEdgeAnnotationsByHashFromListEntries } from './migration-list-render';
|
|
19
|
+
|
|
20
|
+
export function mergeMigrationEdgeAnnotations(
|
|
21
|
+
listOverlay: ReadonlyMap<string, MigrationEdgeAnnotation>,
|
|
22
|
+
statusOverlay: ReadonlyMap<string, MigrationEdgeAnnotation>,
|
|
23
|
+
): ReadonlyMap<string, MigrationEdgeAnnotation> {
|
|
24
|
+
const merged = new Map<string, MigrationEdgeAnnotation>();
|
|
25
|
+
for (const [migrationHash, listAnnotation] of listOverlay) {
|
|
26
|
+
const statusAnnotation = statusOverlay.get(migrationHash);
|
|
27
|
+
merged.set(migrationHash, {
|
|
28
|
+
...listAnnotation,
|
|
29
|
+
...(statusAnnotation?.status !== undefined ? { status: statusAnnotation.status } : {}),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return merged;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RenderMigrationGraphSpaceTreeInput {
|
|
36
|
+
readonly graph: MigrationGraph;
|
|
37
|
+
readonly migrations: readonly MigrationListEntry[];
|
|
38
|
+
readonly liveContractHash: string;
|
|
39
|
+
readonly glyphMode: GlyphMode;
|
|
40
|
+
readonly colorize: boolean;
|
|
41
|
+
readonly refsByHash?: ReadonlyMap<string, readonly string[]>;
|
|
42
|
+
readonly statusOverlayByHash?: ReadonlyMap<string, MigrationEdgeAnnotation>;
|
|
43
|
+
readonly dbHash?: string;
|
|
44
|
+
readonly styler?: MigrationListStyler;
|
|
45
|
+
readonly globalMaxEdgeTreePrefixWidth?: number;
|
|
46
|
+
readonly globalMaxDirNameWidth?: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ComputeGlobalMaxEdgeTreePrefixWidthInput {
|
|
50
|
+
readonly graph: MigrationGraph;
|
|
51
|
+
readonly liveContractHash: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function computeGlobalMaxEdgeTreePrefixWidth(
|
|
55
|
+
inputs: readonly ComputeGlobalMaxEdgeTreePrefixWidthInput[],
|
|
56
|
+
): number {
|
|
57
|
+
let globalMax = 0;
|
|
58
|
+
for (const input of inputs) {
|
|
59
|
+
const rowModel = buildMigrationGraphRows(input.graph, {
|
|
60
|
+
contractHash: input.liveContractHash,
|
|
61
|
+
});
|
|
62
|
+
const layout = buildMigrationGraphLayout(rowModel);
|
|
63
|
+
globalMax = Math.max(globalMax, computeMaxEdgeTreePrefixWidthForLayout(layout));
|
|
64
|
+
}
|
|
65
|
+
return globalMax;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function computeGlobalMaxDirNameWidth(
|
|
69
|
+
inputs: readonly ComputeGlobalMaxEdgeTreePrefixWidthInput[],
|
|
70
|
+
): number {
|
|
71
|
+
let globalMax = 0;
|
|
72
|
+
for (const input of inputs) {
|
|
73
|
+
const rowModel = buildMigrationGraphRows(input.graph, {
|
|
74
|
+
contractHash: input.liveContractHash,
|
|
75
|
+
});
|
|
76
|
+
const layout = buildMigrationGraphLayout(rowModel);
|
|
77
|
+
globalMax = Math.max(globalMax, computeMaxDirNameLengthForLayout(layout));
|
|
78
|
+
}
|
|
79
|
+
return globalMax;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function renderMigrationGraphSpaceTreeInternal(input: RenderMigrationGraphSpaceTreeInput): string {
|
|
83
|
+
const rowModel = buildMigrationGraphRows(input.graph, {
|
|
84
|
+
contractHash: input.liveContractHash,
|
|
85
|
+
});
|
|
86
|
+
const layout = buildMigrationGraphLayout(rowModel);
|
|
87
|
+
const listOverlay = buildEdgeAnnotationsByHashFromListEntries(input.migrations);
|
|
88
|
+
const edgeAnnotationsByHash =
|
|
89
|
+
input.statusOverlayByHash === undefined
|
|
90
|
+
? listOverlay
|
|
91
|
+
: mergeMigrationEdgeAnnotations(listOverlay, input.statusOverlayByHash);
|
|
92
|
+
return renderMigrationGraphTree(layout, {
|
|
93
|
+
refsByHash: input.refsByHash ?? buildRefsByHashFromListEntries(input.migrations),
|
|
94
|
+
contractHash: input.liveContractHash,
|
|
95
|
+
edgeAnnotationsByHash,
|
|
96
|
+
colorize: input.colorize,
|
|
97
|
+
glyphMode: input.glyphMode,
|
|
98
|
+
...(input.dbHash !== undefined ? { dbHash: input.dbHash } : {}),
|
|
99
|
+
...(input.styler !== undefined ? { styler: input.styler } : {}),
|
|
100
|
+
...(input.globalMaxEdgeTreePrefixWidth !== undefined
|
|
101
|
+
? { globalMaxEdgeTreePrefixWidth: input.globalMaxEdgeTreePrefixWidth }
|
|
102
|
+
: {}),
|
|
103
|
+
...(input.globalMaxDirNameWidth !== undefined
|
|
104
|
+
? { globalMaxDirNameWidth: input.globalMaxDirNameWidth }
|
|
105
|
+
: {}),
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function renderMigrationGraphSpaceTree(input: RenderMigrationGraphSpaceTreeInput): string {
|
|
110
|
+
return renderMigrationGraphSpaceTreeInternal(input);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function renderMigrationGraphSpaceTrees(
|
|
114
|
+
inputs: readonly RenderMigrationGraphSpaceTreeInput[],
|
|
115
|
+
): readonly string[] {
|
|
116
|
+
const globalMaxTreePrefix =
|
|
117
|
+
inputs.length > 1 ? computeGlobalMaxEdgeTreePrefixWidth(inputs) : undefined;
|
|
118
|
+
const globalMaxDirName = inputs.length > 1 ? computeGlobalMaxDirNameWidth(inputs) : undefined;
|
|
119
|
+
return inputs.map((input) =>
|
|
120
|
+
renderMigrationGraphSpaceTreeInternal({
|
|
121
|
+
...input,
|
|
122
|
+
...(globalMaxTreePrefix !== undefined
|
|
123
|
+
? { globalMaxEdgeTreePrefixWidth: globalMaxTreePrefix }
|
|
124
|
+
: {}),
|
|
125
|
+
...(globalMaxDirName !== undefined ? { globalMaxDirNameWidth: globalMaxDirName } : {}),
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function indentMigrationGraphTreeBlock(treeOutput: string, indent: string): string {
|
|
131
|
+
if (treeOutput.length === 0) {
|
|
132
|
+
return treeOutput;
|
|
133
|
+
}
|
|
134
|
+
return treeOutput
|
|
135
|
+
.split('\n')
|
|
136
|
+
.map((line) => (line.length === 0 ? line : `${indent}${line}`))
|
|
137
|
+
.join('\n');
|
|
138
|
+
}
|