@prisma-next/cli 0.11.0 → 0.12.0
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/README.md +13 -9
- package/dist/cli.mjs +259 -12
- package/dist/cli.mjs.map +1 -1
- package/dist/{client-oXO2WCPD.mjs → client-KgJorIvG.mjs} +72 -60
- package/dist/client-KgJorIvG.mjs.map +1 -0
- package/dist/{command-helpers-BSb0tRC8.mjs → command-helpers-Bbw1GbwL.mjs} +646 -46
- package/dist/command-helpers-Bbw1GbwL.mjs.map +1 -0
- package/dist/commands/contract-emit.d.mts.map +1 -1
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.d.mts.map +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.d.mts.map +1 -1
- package/dist/commands/db-init.mjs +32 -7
- package/dist/commands/db-init.mjs.map +1 -1
- package/dist/commands/db-schema.d.mts.map +1 -1
- package/dist/commands/db-schema.mjs +3 -4
- package/dist/commands/db-schema.mjs.map +1 -1
- package/dist/commands/db-sign.d.mts.map +1 -1
- package/dist/commands/db-sign.mjs +12 -10
- package/dist/commands/db-sign.mjs.map +1 -1
- package/dist/commands/db-update.d.mts.map +1 -1
- package/dist/commands/db-update.mjs +41 -11
- package/dist/commands/db-update.mjs.map +1 -1
- package/dist/commands/db-verify.d.mts.map +1 -1
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.d.mts +6 -2
- package/dist/commands/migrate.d.mts.map +1 -1
- package/dist/commands/migrate.mjs +75 -40
- package/dist/commands/migrate.mjs.map +1 -1
- package/dist/commands/migration-check.d.mts +4 -3
- package/dist/commands/migration-check.d.mts.map +1 -1
- package/dist/commands/migration-check.mjs +1 -280
- package/dist/commands/migration-graph.d.mts +13 -2
- package/dist/commands/migration-graph.d.mts.map +1 -1
- package/dist/commands/migration-graph.mjs +2 -137
- package/dist/commands/migration-list.d.mts +64 -4
- package/dist/commands/migration-list.d.mts.map +1 -1
- package/dist/commands/migration-list.mjs +143 -56
- package/dist/commands/migration-list.mjs.map +1 -1
- package/dist/commands/migration-log.d.mts +10 -1
- package/dist/commands/migration-log.d.mts.map +1 -1
- package/dist/commands/migration-log.mjs +10 -15
- package/dist/commands/migration-log.mjs.map +1 -1
- package/dist/commands/migration-new.d.mts.map +1 -1
- package/dist/commands/migration-new.mjs +32 -38
- package/dist/commands/migration-new.mjs.map +1 -1
- package/dist/commands/migration-plan.d.mts +3 -2
- package/dist/commands/migration-plan.d.mts.map +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.d.mts +4 -55
- package/dist/commands/migration-show.d.mts.map +1 -1
- package/dist/commands/migration-show.mjs +61 -153
- package/dist/commands/migration-show.mjs.map +1 -1
- package/dist/commands/migration-status.d.mts +12 -49
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +85 -81
- package/dist/commands/migration-status.mjs.map +1 -1
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/ref.d.mts.map +1 -1
- package/dist/commands/ref.mjs +38 -10
- package/dist/commands/ref.mjs.map +1 -1
- package/dist/config-loader-B6sJjXTv.mjs.map +1 -1
- package/dist/config-loader.d.mts.map +1 -1
- package/dist/contract-at-errors-BxP-TOMl.mjs +42 -0
- package/dist/contract-at-errors-BxP-TOMl.mjs.map +1 -0
- package/dist/{contract-emit-bcrpT-wD.mjs → contract-emit-D-4jrNve.mjs} +25 -10
- package/dist/contract-emit-D-4jrNve.mjs.map +1 -0
- package/dist/{contract-emit-r4y8Zhf1.mjs → contract-emit-DxcGl4Uq.mjs} +19 -14
- package/dist/contract-emit-DxcGl4Uq.mjs.map +1 -0
- package/dist/{contract-enrichment-Dani0mMW.mjs → contract-enrichment-a0V5Y_mL.mjs} +4 -25
- package/dist/contract-enrichment-a0V5Y_mL.mjs.map +1 -0
- package/dist/{contract-infer-BmySmqVT.mjs → contract-infer-D8uEbJuu.mjs} +4 -5
- package/dist/{contract-infer-BmySmqVT.mjs.map → contract-infer-D8uEbJuu.mjs.map} +1 -1
- package/dist/contract-space-aggregate-loader-DvZwdkrr.mjs +247 -0
- package/dist/contract-space-aggregate-loader-DvZwdkrr.mjs.map +1 -0
- package/dist/{db-verify-BClPs3ph.mjs → db-verify-v_vUKXTU.mjs} +5 -7
- package/dist/{db-verify-BClPs3ph.mjs.map → db-verify-v_vUKXTU.mjs.map} +1 -1
- package/dist/exports/control-api.d.mts +3 -3
- package/dist/exports/control-api.d.mts.map +1 -1
- package/dist/exports/control-api.mjs +3 -3
- package/dist/exports/index.d.mts.map +1 -1
- package/dist/exports/index.mjs +1 -1
- package/dist/exports/index.mjs.map +1 -1
- package/dist/exports/init-output.d.mts.map +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/extension-pack-inputs-IDvjRCi3.mjs +62 -0
- package/dist/extension-pack-inputs-IDvjRCi3.mjs.map +1 -0
- package/dist/{framework-components-65gOHkHB.mjs → framework-components-fYXjz_in.mjs} +2 -2
- package/dist/{framework-components-65gOHkHB.mjs.map → framework-components-fYXjz_in.mjs.map} +1 -1
- package/dist/global-flags-DEHjV8_s.d.mts +34 -0
- package/dist/global-flags-DEHjV8_s.d.mts.map +1 -0
- package/dist/{graph-render-DJVv0_uf.mjs → graph-render-rFAqZujX.mjs} +2 -2
- package/dist/{graph-render-DJVv0_uf.mjs.map → graph-render-rFAqZujX.mjs.map} +1 -1
- package/dist/{init-BCJZPWE1.mjs → init-Cv9UzWL5.mjs} +20 -269
- package/dist/init-Cv9UzWL5.mjs.map +1 -0
- package/dist/{inspect-live-schema-DSRbFoOL.mjs → inspect-live-schema-C6ohV_oQ.mjs} +4 -5
- package/dist/{inspect-live-schema-DSRbFoOL.mjs.map → inspect-live-schema-C6ohV_oQ.mjs.map} +1 -1
- package/dist/migration-check-BiBJoYYW.mjs +341 -0
- package/dist/migration-check-BiBJoYYW.mjs.map +1 -0
- package/dist/migration-cli.d.mts.map +1 -1
- package/dist/migration-cli.mjs +4 -4
- package/dist/migration-cli.mjs.map +1 -1
- package/dist/{migration-command-scaffold-Bzd9La5c.mjs → migration-command-scaffold-CjvwO6at.mjs} +4 -5
- package/dist/{migration-command-scaffold-Bzd9La5c.mjs.map → migration-command-scaffold-CjvwO6at.mjs.map} +1 -1
- package/dist/migration-graph-D7DVUElV.mjs +1232 -0
- package/dist/migration-graph-D7DVUElV.mjs.map +1 -0
- package/dist/migration-list-styler-BRwF4-gy.mjs +399 -0
- package/dist/migration-list-styler-BRwF4-gy.mjs.map +1 -0
- package/dist/{migration-plan-CFwqw3Gk.mjs → migration-plan-9DJ7q7_z.mjs} +372 -133
- package/dist/migration-plan-9DJ7q7_z.mjs.map +1 -0
- package/dist/{migration-types-BXWvz12q.d.mts → migration-types-D2FW63pr.d.mts} +1 -1
- package/dist/{migration-types-BXWvz12q.d.mts.map → migration-types-D2FW63pr.d.mts.map} +1 -1
- package/dist/{migrations-CwZMa1Ck.mjs → migrations-Cv2jxNNK.mjs} +12 -13
- package/dist/migrations-Cv2jxNNK.mjs.map +1 -0
- package/dist/{output-BlsrGMEF.mjs → output-B60Gw5fu.mjs} +1 -1
- package/dist/{output-BlsrGMEF.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
- package/dist/{progress-adapter-DFfvZcYL.mjs → progress-adapter-C644QK8l.mjs} +1 -1
- package/dist/{progress-adapter-DFfvZcYL.mjs.map → progress-adapter-C644QK8l.mjs.map} +1 -1
- package/dist/ref-advancement-DUZqsue6.mjs +50 -0
- package/dist/ref-advancement-DUZqsue6.mjs.map +1 -0
- package/dist/terminal-ui-5Y6mrg93.d.mts +133 -0
- package/dist/terminal-ui-5Y6mrg93.d.mts.map +1 -0
- package/dist/{types--CqjMdk0.d.mts → types-Dt_SfqFm.d.mts} +28 -28
- package/dist/types-Dt_SfqFm.d.mts.map +1 -0
- package/dist/{verify-Bom75OYI.mjs → verify-DCA9Sldu.mjs} +2 -2
- package/dist/{verify-Bom75OYI.mjs.map → verify-DCA9Sldu.mjs.map} +1 -1
- package/package.json +35 -24
- package/src/commands/contract-emit.ts +19 -7
- package/src/commands/contract-infer.ts +1 -1
- package/src/commands/db-init.ts +48 -2
- package/src/commands/db-sign.ts +9 -5
- package/src/commands/db-update.ts +54 -8
- package/src/commands/init/hygiene-gitattributes.ts +2 -2
- package/src/commands/init/index.ts +2 -1
- package/src/commands/init/templates/code-templates.ts +4 -2
- package/src/commands/init/templates/env.ts +13 -14
- package/src/commands/migrate.ts +125 -44
- package/src/commands/migration-check.ts +43 -83
- package/src/commands/migration-graph.ts +75 -60
- package/src/commands/migration-list.ts +220 -74
- package/src/commands/migration-log.ts +8 -14
- package/src/commands/migration-new.ts +44 -48
- package/src/commands/migration-plan.ts +412 -197
- package/src/commands/migration-show.ts +65 -284
- package/src/commands/migration-status.ts +127 -124
- package/src/commands/ref.ts +53 -8
- package/src/control-api/client.ts +0 -1
- package/src/control-api/contract-enrichment.ts +6 -42
- package/src/control-api/operations/{apply-aggregate.ts → apply.ts} +44 -75
- package/src/control-api/operations/contract-emit.ts +14 -6
- package/src/control-api/operations/{db-apply-aggregate.ts → db-apply.ts} +19 -19
- package/src/control-api/operations/db-init.ts +4 -4
- package/src/control-api/operations/db-update.ts +4 -4
- package/src/control-api/operations/db-verify.ts +15 -11
- package/src/control-api/operations/migration-apply.ts +56 -47
- package/src/control-api/types.ts +26 -27
- package/src/migration-cli.ts +4 -4
- package/src/utils/cli-errors.ts +234 -0
- package/src/utils/command-helpers.ts +9 -24
- package/src/utils/contract-at-errors.ts +96 -0
- package/src/utils/contract-space-aggregate-loader.ts +336 -117
- package/src/utils/formatters/migration-graph-layout.ts +1119 -0
- package/src/utils/formatters/migration-graph-rows.ts +336 -0
- package/src/utils/formatters/migration-graph-tree-render.ts +459 -0
- package/src/utils/formatters/migration-list-data-column.ts +115 -0
- package/src/utils/formatters/migration-list-graph-topology.ts +368 -0
- package/src/utils/formatters/migration-list-render.ts +191 -0
- package/src/utils/formatters/migration-list-styler.ts +63 -0
- package/src/utils/formatters/migration-list-types.ts +21 -0
- package/src/utils/formatters/migrations.ts +37 -46
- package/src/utils/glyph-mode.ts +22 -0
- package/src/utils/integrity-violation-to-check-failure.ts +130 -0
- package/src/utils/plan-resolution.ts +258 -0
- package/src/utils/ref-advancement.ts +68 -0
- package/src/utils/terminal-ui.ts +42 -1
- package/dist/cli-errors-Czmx92Zy.d.mts +0 -3
- package/dist/cli-errors-Djtz98Vm.mjs +0 -71
- package/dist/cli-errors-Djtz98Vm.mjs.map +0 -1
- package/dist/client-oXO2WCPD.mjs.map +0 -1
- package/dist/command-helpers-BSb0tRC8.mjs.map +0 -1
- package/dist/commands/migration-check.mjs.map +0 -1
- package/dist/commands/migration-graph.mjs.map +0 -1
- package/dist/contract-emit-bcrpT-wD.mjs.map +0 -1
- package/dist/contract-emit-r4y8Zhf1.mjs.map +0 -1
- package/dist/contract-enrichment-Dani0mMW.mjs.map +0 -1
- package/dist/contract-space-aggregate-loader-BmNQwlws.mjs +0 -160
- package/dist/contract-space-aggregate-loader-BmNQwlws.mjs.map +0 -1
- package/dist/global-flags-CdE7M0d9.d.mts +0 -15
- package/dist/global-flags-CdE7M0d9.d.mts.map +0 -1
- package/dist/init-BCJZPWE1.mjs.map +0 -1
- package/dist/migration-plan-CFwqw3Gk.mjs.map +0 -1
- package/dist/migrations-CwZMa1Ck.mjs.map +0 -1
- package/dist/rolldown-runtime-twds-ZHy.mjs +0 -14
- package/dist/terminal-ui-BiB_8KNo.mjs +0 -379
- package/dist/terminal-ui-BiB_8KNo.mjs.map +0 -1
- package/dist/types--CqjMdk0.d.mts.map +0 -1
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
2
|
+
import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
|
|
3
|
+
import {
|
|
4
|
+
classifyMigrationGraphTopology,
|
|
5
|
+
type MigrationEdgeKind,
|
|
6
|
+
type MigrationListGraphTopology,
|
|
7
|
+
} from './migration-list-graph-topology';
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Public types
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A migration edge with its forward/rollback/self classification resolved.
|
|
15
|
+
* `from` and `to` are contract hashes (EMPTY_CONTRACT_HASH for the baseline).
|
|
16
|
+
*/
|
|
17
|
+
export interface ClassifiedEdge {
|
|
18
|
+
readonly migrationHash: string;
|
|
19
|
+
readonly from: string;
|
|
20
|
+
readonly to: string;
|
|
21
|
+
readonly dirName: string;
|
|
22
|
+
readonly kind: MigrationEdgeKind;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The pure-data output of the row-model stage.
|
|
27
|
+
*
|
|
28
|
+
* `nodes` is the vertical ordering of contract nodes: index 0 is the topmost
|
|
29
|
+
* row (the tip), the last non-null entry is the bottommost root. `null`
|
|
30
|
+
* sentinels separate disjoint components (the blank row in the rendered
|
|
31
|
+
* output). Ordering within each component is deterministic: longest forward-
|
|
32
|
+
* path rank from forward roots (tips at rank max, roots at 0), with lex-
|
|
33
|
+
* ascending tie-break among same-rank siblings.
|
|
34
|
+
*
|
|
35
|
+
* `edges` carries every classified migration. `edgesByFrom` and `edgesByTo`
|
|
36
|
+
* are pre-built lookup maps for the column allocator.
|
|
37
|
+
*/
|
|
38
|
+
export interface MigrationGraphRowModel {
|
|
39
|
+
readonly nodes: readonly (string | null)[];
|
|
40
|
+
readonly edges: readonly ClassifiedEdge[];
|
|
41
|
+
readonly edgesByFrom: ReadonlyMap<string, readonly ClassifiedEdge[]>;
|
|
42
|
+
readonly edgesByTo: ReadonlyMap<string, readonly ClassifiedEdge[]>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BuildMigrationGraphRowsOptions {
|
|
46
|
+
readonly contractHash?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Weak connectivity — identify disjoint components
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Return the weakly-connected components of `graph` as an array of node sets,
|
|
55
|
+
* ordered so the component containing EMPTY_CONTRACT_HASH comes first (if
|
|
56
|
+
* present), with remaining components sorted by their lex-smallest node hash.
|
|
57
|
+
*/
|
|
58
|
+
function weaklyConnectedComponents(graph: MigrationGraph): readonly ReadonlySet<string>[] {
|
|
59
|
+
const visited = new Set<string>();
|
|
60
|
+
const adjacency = new Map<string, string[]>();
|
|
61
|
+
|
|
62
|
+
function addAdjacent(a: string, b: string): void {
|
|
63
|
+
const aList = adjacency.get(a);
|
|
64
|
+
if (aList) aList.push(b);
|
|
65
|
+
else adjacency.set(a, [b]);
|
|
66
|
+
const bList = adjacency.get(b);
|
|
67
|
+
if (bList) bList.push(a);
|
|
68
|
+
else adjacency.set(b, [a]);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (const edges of graph.forwardChain.values()) {
|
|
72
|
+
for (const edge of edges) {
|
|
73
|
+
if (edge.from !== edge.to) {
|
|
74
|
+
addAdjacent(edge.from, edge.to);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Ensure all nodes (including isolated self-loops) are reachable
|
|
80
|
+
for (const node of graph.nodes) {
|
|
81
|
+
if (!adjacency.has(node)) {
|
|
82
|
+
adjacency.set(node, []);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const components: Set<string>[] = [];
|
|
87
|
+
|
|
88
|
+
function bfsComponent(start: string): Set<string> {
|
|
89
|
+
const component = new Set<string>();
|
|
90
|
+
const queue = [start];
|
|
91
|
+
while (queue.length > 0) {
|
|
92
|
+
const node = queue.shift();
|
|
93
|
+
if (node === undefined || visited.has(node)) continue;
|
|
94
|
+
visited.add(node);
|
|
95
|
+
component.add(node);
|
|
96
|
+
for (const neighbor of adjacency.get(node) ?? []) {
|
|
97
|
+
if (!visited.has(neighbor)) {
|
|
98
|
+
queue.push(neighbor);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return component;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Deterministic: visit nodes in a fixed order (EMPTY first, then lex)
|
|
106
|
+
const allNodes = [...graph.nodes].sort((a, b) => {
|
|
107
|
+
if (a === EMPTY_CONTRACT_HASH) return -1;
|
|
108
|
+
if (b === EMPTY_CONTRACT_HASH) return 1;
|
|
109
|
+
return a.localeCompare(b);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
for (const node of allNodes) {
|
|
113
|
+
if (!visited.has(node)) {
|
|
114
|
+
components.push(bfsComponent(node));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Order: EMPTY component first, others by lex-smallest node hash
|
|
119
|
+
components.sort((a, b) => {
|
|
120
|
+
const aHasEmpty = a.has(EMPTY_CONTRACT_HASH);
|
|
121
|
+
const bHasEmpty = b.has(EMPTY_CONTRACT_HASH);
|
|
122
|
+
if (aHasEmpty && !bHasEmpty) return -1;
|
|
123
|
+
if (!aHasEmpty && bHasEmpty) return 1;
|
|
124
|
+
const aMin = [...a].sort((x, y) => x.localeCompare(y))[0] ?? '';
|
|
125
|
+
const bMin = [...b].sort((x, y) => x.localeCompare(y))[0] ?? '';
|
|
126
|
+
return aMin.localeCompare(bMin);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
return components;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// Longest forward-path node ordering within a component
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
|
|
136
|
+
function forwardRootsInComponent(
|
|
137
|
+
componentNodes: ReadonlySet<string>,
|
|
138
|
+
topology: MigrationListGraphTopology,
|
|
139
|
+
): readonly string[] {
|
|
140
|
+
const roots: string[] = [];
|
|
141
|
+
for (const node of componentNodes) {
|
|
142
|
+
if ((topology.forwardInDegree.get(node) ?? 0) === 0) {
|
|
143
|
+
roots.push(node);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
roots.sort((a, b) => {
|
|
147
|
+
if (a === EMPTY_CONTRACT_HASH) return -1;
|
|
148
|
+
if (b === EMPTY_CONTRACT_HASH) return 1;
|
|
149
|
+
return a.localeCompare(b);
|
|
150
|
+
});
|
|
151
|
+
if (roots.length > 0) return roots;
|
|
152
|
+
|
|
153
|
+
return [...componentNodes].sort((a, b) => {
|
|
154
|
+
if (a === EMPTY_CONTRACT_HASH) return -1;
|
|
155
|
+
if (b === EMPTY_CONTRACT_HASH) return 1;
|
|
156
|
+
return a.localeCompare(b);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function compareNodesTipsFirst(a: string, b: string, rank: ReadonlyMap<string, number>): number {
|
|
161
|
+
const rankA = rank.get(a) ?? 0;
|
|
162
|
+
const rankB = rank.get(b) ?? 0;
|
|
163
|
+
if (rankA !== rankB) return rankB - rankA;
|
|
164
|
+
if (a === EMPTY_CONTRACT_HASH) return 1;
|
|
165
|
+
if (b === EMPTY_CONTRACT_HASH) return -1;
|
|
166
|
+
return a.localeCompare(b);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Layer nodes by longest forward-path rank from forward roots within the
|
|
171
|
+
* component. Rank 0 is the root (bottom row); the maximum rank is the tip
|
|
172
|
+
* (top row). Emits rank-descending with lex-ascending tie-break among siblings
|
|
173
|
+
* at the same rank — stable across edge-insertion order and correct under
|
|
174
|
+
* diamonds, cross-links, and rollbacks.
|
|
175
|
+
*/
|
|
176
|
+
function layerNodesByLongestForwardPath(
|
|
177
|
+
componentNodes: ReadonlySet<string>,
|
|
178
|
+
topology: MigrationListGraphTopology,
|
|
179
|
+
graph: MigrationGraph,
|
|
180
|
+
): readonly string[] {
|
|
181
|
+
const forwardOut = new Map<string, string[]>();
|
|
182
|
+
|
|
183
|
+
for (const node of componentNodes) {
|
|
184
|
+
forwardOut.set(node, []);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
for (const edges of graph.forwardChain.values()) {
|
|
188
|
+
for (const edge of edges) {
|
|
189
|
+
if (!componentNodes.has(edge.from) || !componentNodes.has(edge.to)) continue;
|
|
190
|
+
if (edge.from === edge.to) continue;
|
|
191
|
+
if (topology.kindByMigrationHash.get(edge.migrationHash) !== 'forward') continue;
|
|
192
|
+
const bucket = forwardOut.get(edge.from);
|
|
193
|
+
if (bucket) bucket.push(edge.to);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const roots = forwardRootsInComponent(componentNodes, topology);
|
|
198
|
+
const rank = new Map<string, number>();
|
|
199
|
+
for (const root of roots) {
|
|
200
|
+
rank.set(root, 0);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const maxPasses = componentNodes.size;
|
|
204
|
+
for (let pass = 0; pass < maxPasses; pass++) {
|
|
205
|
+
let changed = false;
|
|
206
|
+
for (const node of componentNodes) {
|
|
207
|
+
const base = rank.get(node);
|
|
208
|
+
if (base === undefined) continue;
|
|
209
|
+
for (const to of forwardOut.get(node) ?? []) {
|
|
210
|
+
const next = base + 1;
|
|
211
|
+
const prev = rank.get(to) ?? -1;
|
|
212
|
+
if (next > prev) {
|
|
213
|
+
rank.set(to, next);
|
|
214
|
+
changed = true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (!changed) break;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
for (const node of componentNodes) {
|
|
222
|
+
if (!rank.has(node)) {
|
|
223
|
+
rank.set(node, 0);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return [...componentNodes].sort((a, b) => compareNodesTipsFirst(a, b, rank));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
// Public builder
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Build the row model from a tolerant `MigrationGraph`.
|
|
236
|
+
*
|
|
237
|
+
* The row model is the first pure-data stage of the `migration graph` render
|
|
238
|
+
* pipeline. It:
|
|
239
|
+
* - classifies every edge as `forward`, `rollback`, or `self`;
|
|
240
|
+
* - produces a deterministic vertical node ordering (tips at index 0, roots
|
|
241
|
+
* at the end) within each weakly-connected component;
|
|
242
|
+
* - separates disjoint components with `null` sentinels;
|
|
243
|
+
* - optionally prepends a detached current contract as its own single-node
|
|
244
|
+
* component when `contractHash` is not already in the graph.
|
|
245
|
+
*
|
|
246
|
+
* No columns, no lane allocation, no glyphs, no rendering.
|
|
247
|
+
*/
|
|
248
|
+
/**
|
|
249
|
+
* Resolve the detached current contract, if any: a real contract (not the
|
|
250
|
+
* empty baseline) that no migration on disk produces, so it is absent from
|
|
251
|
+
* the graph. Such a contract renders as a floating node rather than
|
|
252
|
+
* decorating an existing one. Returns the hash when detached, else undefined.
|
|
253
|
+
*/
|
|
254
|
+
function detachedContractHash(
|
|
255
|
+
graph: MigrationGraph,
|
|
256
|
+
contractHash: string | undefined,
|
|
257
|
+
): string | undefined {
|
|
258
|
+
return contractHash !== undefined &&
|
|
259
|
+
contractHash !== EMPTY_CONTRACT_HASH &&
|
|
260
|
+
!graph.nodes.has(contractHash)
|
|
261
|
+
? contractHash
|
|
262
|
+
: undefined;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
export function buildMigrationGraphRows(
|
|
266
|
+
graph: MigrationGraph,
|
|
267
|
+
options: BuildMigrationGraphRowsOptions = {},
|
|
268
|
+
): MigrationGraphRowModel {
|
|
269
|
+
const emptyModel: MigrationGraphRowModel = {
|
|
270
|
+
nodes: [],
|
|
271
|
+
edges: [],
|
|
272
|
+
edgesByFrom: new Map(),
|
|
273
|
+
edgesByTo: new Map(),
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
if (graph.nodes.size === 0) {
|
|
277
|
+
const detached = detachedContractHash(graph, options.contractHash);
|
|
278
|
+
return detached !== undefined ? { ...emptyModel, nodes: [detached] } : emptyModel;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 1. Classify all edges (shared classifier: DFS plus a peel pass that demotes
|
|
282
|
+
// node-skipping rollbacks, so the forward subgraph is acyclic)
|
|
283
|
+
const topology = classifyMigrationGraphTopology(graph);
|
|
284
|
+
|
|
285
|
+
// 2. Build classified edge list
|
|
286
|
+
const edges: ClassifiedEdge[] = [];
|
|
287
|
+
const edgesByFrom = new Map<string, ClassifiedEdge[]>();
|
|
288
|
+
const edgesByTo = new Map<string, ClassifiedEdge[]>();
|
|
289
|
+
|
|
290
|
+
for (const edgeList of graph.forwardChain.values()) {
|
|
291
|
+
for (const edge of edgeList) {
|
|
292
|
+
const kind = topology.kindByMigrationHash.get(edge.migrationHash) ?? 'forward';
|
|
293
|
+
const classified: ClassifiedEdge = {
|
|
294
|
+
migrationHash: edge.migrationHash,
|
|
295
|
+
from: edge.from,
|
|
296
|
+
to: edge.to,
|
|
297
|
+
dirName: edge.dirName,
|
|
298
|
+
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]);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// 3. Find weakly-connected components (ordered: EMPTY first, then lex)
|
|
313
|
+
const components = weaklyConnectedComponents(graph);
|
|
314
|
+
|
|
315
|
+
// 4. Layer nodes by longest forward path per component, separate with null
|
|
316
|
+
const nodes: (string | null)[] = [];
|
|
317
|
+
for (let i = 0; i < components.length; i++) {
|
|
318
|
+
if (i > 0) nodes.push(null);
|
|
319
|
+
const component = components[i];
|
|
320
|
+
if (component === undefined) continue;
|
|
321
|
+
const ordered = layerNodesByLongestForwardPath(component, topology, graph);
|
|
322
|
+
for (const node of ordered) {
|
|
323
|
+
nodes.push(node);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const detached = detachedContractHash(graph, options.contractHash);
|
|
328
|
+
if (detached !== undefined) {
|
|
329
|
+
if (nodes.length > 0) {
|
|
330
|
+
nodes.unshift(null);
|
|
331
|
+
}
|
|
332
|
+
nodes.unshift(detached);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return { nodes, edges, edgesByFrom, edgesByTo };
|
|
336
|
+
}
|