prisma-next 0.11.0 → 0.12.0-dev.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/dist/cli.mjs +259 -12
  2. package/dist/cli.mjs.map +1 -1
  3. package/dist/{client-oXO2WCPD.mjs → client-CDr4o07S.mjs} +88 -63
  4. package/dist/client-CDr4o07S.mjs.map +1 -0
  5. package/dist/{command-helpers-BSb0tRC8.mjs → command-helpers-Bbw1GbwL.mjs} +646 -46
  6. package/dist/command-helpers-Bbw1GbwL.mjs.map +1 -0
  7. package/dist/commands/contract-emit.d.mts.map +1 -1
  8. package/dist/commands/contract-emit.mjs +1 -1
  9. package/dist/commands/contract-infer.d.mts.map +1 -1
  10. package/dist/commands/contract-infer.mjs +1 -1
  11. package/dist/commands/db-init.d.mts.map +1 -1
  12. package/dist/commands/db-init.mjs +32 -7
  13. package/dist/commands/db-init.mjs.map +1 -1
  14. package/dist/commands/db-schema.d.mts.map +1 -1
  15. package/dist/commands/db-schema.mjs +3 -4
  16. package/dist/commands/db-schema.mjs.map +1 -1
  17. package/dist/commands/db-sign.d.mts.map +1 -1
  18. package/dist/commands/db-sign.mjs +12 -10
  19. package/dist/commands/db-sign.mjs.map +1 -1
  20. package/dist/commands/db-update.d.mts.map +1 -1
  21. package/dist/commands/db-update.mjs +41 -11
  22. package/dist/commands/db-update.mjs.map +1 -1
  23. package/dist/commands/db-verify.d.mts.map +1 -1
  24. package/dist/commands/db-verify.mjs +1 -1
  25. package/dist/commands/migrate.d.mts +6 -2
  26. package/dist/commands/migrate.d.mts.map +1 -1
  27. package/dist/commands/migrate.mjs +75 -40
  28. package/dist/commands/migrate.mjs.map +1 -1
  29. package/dist/commands/migration-check.d.mts +4 -3
  30. package/dist/commands/migration-check.d.mts.map +1 -1
  31. package/dist/commands/migration-check.mjs +1 -280
  32. package/dist/commands/migration-graph.d.mts +31 -2
  33. package/dist/commands/migration-graph.d.mts.map +1 -1
  34. package/dist/commands/migration-graph.mjs +2 -137
  35. package/dist/commands/migration-list.d.mts +64 -4
  36. package/dist/commands/migration-list.d.mts.map +1 -1
  37. package/dist/commands/migration-list.mjs +143 -56
  38. package/dist/commands/migration-list.mjs.map +1 -1
  39. package/dist/commands/migration-log.d.mts +10 -1
  40. package/dist/commands/migration-log.d.mts.map +1 -1
  41. package/dist/commands/migration-log.mjs +10 -15
  42. package/dist/commands/migration-log.mjs.map +1 -1
  43. package/dist/commands/migration-new.d.mts.map +1 -1
  44. package/dist/commands/migration-new.mjs +32 -38
  45. package/dist/commands/migration-new.mjs.map +1 -1
  46. package/dist/commands/migration-plan.d.mts +3 -2
  47. package/dist/commands/migration-plan.d.mts.map +1 -1
  48. package/dist/commands/migration-plan.mjs +1 -1
  49. package/dist/commands/migration-show.d.mts +4 -55
  50. package/dist/commands/migration-show.d.mts.map +1 -1
  51. package/dist/commands/migration-show.mjs +61 -153
  52. package/dist/commands/migration-show.mjs.map +1 -1
  53. package/dist/commands/migration-status.d.mts +12 -49
  54. package/dist/commands/migration-status.d.mts.map +1 -1
  55. package/dist/commands/migration-status.mjs +86 -82
  56. package/dist/commands/migration-status.mjs.map +1 -1
  57. package/dist/commands/ref.d.mts +1 -1
  58. package/dist/commands/ref.d.mts.map +1 -1
  59. package/dist/commands/ref.mjs +38 -10
  60. package/dist/commands/ref.mjs.map +1 -1
  61. package/dist/config-loader-B6sJjXTv.mjs.map +1 -1
  62. package/dist/config-loader.d.mts.map +1 -1
  63. package/dist/contract-at-errors-BxP-TOMl.mjs +42 -0
  64. package/dist/contract-at-errors-BxP-TOMl.mjs.map +1 -0
  65. package/dist/{contract-emit-bcrpT-wD.mjs → contract-emit-D-4jrNve.mjs} +25 -10
  66. package/dist/contract-emit-D-4jrNve.mjs.map +1 -0
  67. package/dist/{contract-emit-r4y8Zhf1.mjs → contract-emit-DxcGl4Uq.mjs} +19 -14
  68. package/dist/contract-emit-DxcGl4Uq.mjs.map +1 -0
  69. package/dist/{contract-enrichment-Dani0mMW.mjs → contract-enrichment-a0V5Y_mL.mjs} +4 -25
  70. package/dist/contract-enrichment-a0V5Y_mL.mjs.map +1 -0
  71. package/dist/{contract-infer-BmySmqVT.mjs → contract-infer-C8J1WMvO.mjs} +4 -5
  72. package/dist/{contract-infer-BmySmqVT.mjs.map → contract-infer-C8J1WMvO.mjs.map} +1 -1
  73. package/dist/contract-space-aggregate-loader-DvZwdkrr.mjs +247 -0
  74. package/dist/contract-space-aggregate-loader-DvZwdkrr.mjs.map +1 -0
  75. package/dist/{db-verify-BClPs3ph.mjs → db-verify-BeRHwN8M.mjs} +5 -7
  76. package/dist/{db-verify-BClPs3ph.mjs.map → db-verify-BeRHwN8M.mjs.map} +1 -1
  77. package/dist/exports/control-api.d.mts +3 -3
  78. package/dist/exports/control-api.d.mts.map +1 -1
  79. package/dist/exports/control-api.mjs +3 -3
  80. package/dist/exports/index.d.mts.map +1 -1
  81. package/dist/exports/index.mjs +1 -1
  82. package/dist/exports/index.mjs.map +1 -1
  83. package/dist/exports/init-output.d.mts.map +1 -1
  84. package/dist/exports/init-output.mjs +1 -1
  85. package/dist/extension-pack-inputs-IDvjRCi3.mjs +62 -0
  86. package/dist/extension-pack-inputs-IDvjRCi3.mjs.map +1 -0
  87. package/dist/{framework-components-65gOHkHB.mjs → framework-components-fYXjz_in.mjs} +2 -2
  88. package/dist/{framework-components-65gOHkHB.mjs.map → framework-components-fYXjz_in.mjs.map} +1 -1
  89. package/dist/global-flags-DEHjV8_s.d.mts +34 -0
  90. package/dist/global-flags-DEHjV8_s.d.mts.map +1 -0
  91. package/dist/{graph-render-DJVv0_uf.mjs → graph-render-rFAqZujX.mjs} +2 -2
  92. package/dist/{graph-render-DJVv0_uf.mjs.map → graph-render-rFAqZujX.mjs.map} +1 -1
  93. package/dist/{init-BCJZPWE1.mjs → init-Cv9UzWL5.mjs} +20 -269
  94. package/dist/init-Cv9UzWL5.mjs.map +1 -0
  95. package/dist/{inspect-live-schema-DSRbFoOL.mjs → inspect-live-schema-BlKR2Zln.mjs} +4 -5
  96. package/dist/{inspect-live-schema-DSRbFoOL.mjs.map → inspect-live-schema-BlKR2Zln.mjs.map} +1 -1
  97. package/dist/migration-check-BiBJoYYW.mjs +341 -0
  98. package/dist/migration-check-BiBJoYYW.mjs.map +1 -0
  99. package/dist/migration-cli.d.mts.map +1 -1
  100. package/dist/migration-cli.mjs +4 -4
  101. package/dist/migration-cli.mjs.map +1 -1
  102. package/dist/{migration-command-scaffold-Bzd9La5c.mjs → migration-command-scaffold-BAGUiGOK.mjs} +4 -5
  103. package/dist/{migration-command-scaffold-Bzd9La5c.mjs.map → migration-command-scaffold-BAGUiGOK.mjs.map} +1 -1
  104. package/dist/migration-graph-C9WC-7eO.mjs +1478 -0
  105. package/dist/migration-graph-C9WC-7eO.mjs.map +1 -0
  106. package/dist/migration-list-styler-BRwF4-gy.mjs +399 -0
  107. package/dist/migration-list-styler-BRwF4-gy.mjs.map +1 -0
  108. package/dist/{migration-plan-CFwqw3Gk.mjs → migration-plan-9DJ7q7_z.mjs} +372 -133
  109. package/dist/migration-plan-9DJ7q7_z.mjs.map +1 -0
  110. package/dist/{migration-types-BXWvz12q.d.mts → migration-types-D2FW63pr.d.mts} +1 -1
  111. package/dist/{migration-types-BXWvz12q.d.mts.map → migration-types-D2FW63pr.d.mts.map} +1 -1
  112. package/dist/{migrations-CwZMa1Ck.mjs → migrations-Cv2jxNNK.mjs} +12 -13
  113. package/dist/migrations-Cv2jxNNK.mjs.map +1 -0
  114. package/dist/{output-BlsrGMEF.mjs → output-B60Gw5fu.mjs} +1 -1
  115. package/dist/{output-BlsrGMEF.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
  116. package/dist/{progress-adapter-DFfvZcYL.mjs → progress-adapter-C644QK8l.mjs} +1 -1
  117. package/dist/{progress-adapter-DFfvZcYL.mjs.map → progress-adapter-C644QK8l.mjs.map} +1 -1
  118. package/dist/ref-advancement-DUZqsue6.mjs +50 -0
  119. package/dist/ref-advancement-DUZqsue6.mjs.map +1 -0
  120. package/dist/terminal-ui-5Y6mrg93.d.mts +133 -0
  121. package/dist/terminal-ui-5Y6mrg93.d.mts.map +1 -0
  122. package/dist/{types--CqjMdk0.d.mts → types-CeC5ec2Y.d.mts} +35 -29
  123. package/dist/types-CeC5ec2Y.d.mts.map +1 -0
  124. package/dist/{verify-Bom75OYI.mjs → verify-DCA9Sldu.mjs} +2 -2
  125. package/dist/{verify-Bom75OYI.mjs.map → verify-DCA9Sldu.mjs.map} +1 -1
  126. package/package.json +28 -17
  127. package/dist/cli-errors-Czmx92Zy.d.mts +0 -3
  128. package/dist/cli-errors-Djtz98Vm.mjs +0 -71
  129. package/dist/cli-errors-Djtz98Vm.mjs.map +0 -1
  130. package/dist/client-oXO2WCPD.mjs.map +0 -1
  131. package/dist/command-helpers-BSb0tRC8.mjs.map +0 -1
  132. package/dist/commands/migration-check.mjs.map +0 -1
  133. package/dist/commands/migration-graph.mjs.map +0 -1
  134. package/dist/contract-emit-bcrpT-wD.mjs.map +0 -1
  135. package/dist/contract-emit-r4y8Zhf1.mjs.map +0 -1
  136. package/dist/contract-enrichment-Dani0mMW.mjs.map +0 -1
  137. package/dist/contract-space-aggregate-loader-BmNQwlws.mjs +0 -160
  138. package/dist/contract-space-aggregate-loader-BmNQwlws.mjs.map +0 -1
  139. package/dist/global-flags-CdE7M0d9.d.mts +0 -15
  140. package/dist/global-flags-CdE7M0d9.d.mts.map +0 -1
  141. package/dist/init-BCJZPWE1.mjs.map +0 -1
  142. package/dist/migration-plan-CFwqw3Gk.mjs.map +0 -1
  143. package/dist/migrations-CwZMa1Ck.mjs.map +0 -1
  144. package/dist/rolldown-runtime-twds-ZHy.mjs +0 -14
  145. package/dist/terminal-ui-BiB_8KNo.mjs +0 -379
  146. package/dist/terminal-ui-BiB_8KNo.mjs.map +0 -1
  147. package/dist/types--CqjMdk0.d.mts.map +0 -1
@@ -0,0 +1,1478 @@
1
+ import { t as loadConfig } from "./config-loader-B6sJjXTv.mjs";
2
+ import { T as formatStyledHeader, _ as createTerminalUI, d as setCommandSeeAlso, g as parseGlobalFlagsOrExit, l as setCommandDescriptions, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, y as handleResult } from "./command-helpers-Bbw1GbwL.mjs";
3
+ import { r as buildReadAggregate } from "./contract-space-aggregate-loader-DvZwdkrr.mjs";
4
+ import { i as migrationGraphToRenderInput, n as graphRenderer } from "./graph-render-rFAqZujX.mjs";
5
+ import { a as migrationListEmptySource, n as createAnsiMigrationListStyler, o as migrationListForwardArrow, s as classifyMigrationGraphTopology, t as CONTRACT_MARKER_NAME } from "./migration-list-styler-BRwF4-gy.mjs";
6
+ import { Command } from "commander";
7
+ import { ok } from "@prisma-next/utils/result";
8
+ import { bold, createColors } from "colorette";
9
+ import stringWidth from "string-width";
10
+ import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
11
+ //#region src/utils/formatters/migration-graph-layout.ts
12
+ function forwardEdges(edges) {
13
+ return edges.filter((e) => e.kind === "forward");
14
+ }
15
+ function buildForwardProducersByTo(edges) {
16
+ const byTo = /* @__PURE__ */ new Map();
17
+ for (const edge of edges) {
18
+ if (edge.kind !== "forward") continue;
19
+ const bucket = byTo.get(edge.to);
20
+ if (bucket) bucket.push(edge);
21
+ else byTo.set(edge.to, [edge]);
22
+ }
23
+ return byTo;
24
+ }
25
+ function buildForwardOutDegree(edges) {
26
+ const out = /* @__PURE__ */ new Map();
27
+ for (const edge of edges) {
28
+ if (edge.kind !== "forward" || edge.from === edge.to) continue;
29
+ out.set(edge.from, (out.get(edge.from) ?? 0) + 1);
30
+ }
31
+ return out;
32
+ }
33
+ function buildForwardInDegree(edges) {
34
+ const indeg = /* @__PURE__ */ new Map();
35
+ for (const edge of forwardEdges(edges)) {
36
+ if (edge.from === edge.to) continue;
37
+ indeg.set(edge.to, (indeg.get(edge.to) ?? 0) + 1);
38
+ }
39
+ return indeg;
40
+ }
41
+ /**
42
+ * Distinct source contracts among a contract's forward producers. A contract is
43
+ * a *convergence* when this count is >= 2. Multiple migrations sharing one
44
+ * source (a multi-edge) count once — they stack in a single lane rather than
45
+ * fanning into a convergence.
46
+ */
47
+ function buildDistinctSourceCountByTo(edges) {
48
+ const sources = /* @__PURE__ */ new Map();
49
+ for (const edge of edges) {
50
+ if (edge.kind !== "forward" || edge.from === edge.to) continue;
51
+ const set = sources.get(edge.to);
52
+ if (set) set.add(edge.from);
53
+ else sources.set(edge.to, new Set([edge.from]));
54
+ }
55
+ const counts = /* @__PURE__ */ new Map();
56
+ for (const [to, set] of sources) counts.set(to, set.size);
57
+ return counts;
58
+ }
59
+ function splitComponents(nodes) {
60
+ const components = [];
61
+ let current = [];
62
+ for (const node of nodes) {
63
+ if (node === null) {
64
+ if (current.length > 0) {
65
+ components.push(current);
66
+ current = [];
67
+ }
68
+ continue;
69
+ }
70
+ current.push(node);
71
+ }
72
+ if (current.length > 0) components.push(current);
73
+ return components;
74
+ }
75
+ function classifyForwardShortConvergenceAdjacency(rows, edgeRowIndex, edge, laneIndex) {
76
+ for (let index = edgeRowIndex + 1; index < rows.length; index++) {
77
+ const row = rows[index];
78
+ if (row === void 0) break;
79
+ if (row.kind === "component-separator" || row.kind === "branch-connector") continue;
80
+ if (row.kind === "merge-connector") continue;
81
+ if (row.kind === "edge") {
82
+ if (row.laneIndex === laneIndex) return "node-skipping-forward";
83
+ continue;
84
+ }
85
+ if (row.kind === "node" && row.contractHash === edge.from) return "adjacent";
86
+ }
87
+ return "node-skipping-forward";
88
+ }
89
+ function convergenceProducerUsesShortAdjacency(edge, laneIndex, forwardProducersByTo, producerLaneByHash) {
90
+ const producers = (forwardProducersByTo.get(edge.to) ?? []).filter((candidate) => candidate.kind === "forward");
91
+ if (producers.length < 2) return false;
92
+ const fanStart = [...new Set(producers.map((producer) => producerLaneByHash.get(producer.migrationHash)).filter((candidate) => candidate !== void 0))].sort((a, b) => a - b)[0];
93
+ if (fanStart === void 0) return false;
94
+ return laneIndex === fanStart;
95
+ }
96
+ function classifyForwardLayoutAdjacency(rows, edgeRowIndex, edge, laneIndex, passThroughLanes, nodeColumn, convergenceProducer, divergenceBranchEdge) {
97
+ let sawObstruction = false;
98
+ const passThroughLaneSet = new Set(passThroughLanes);
99
+ for (let index = edgeRowIndex + 1; index < rows.length; index++) {
100
+ const row = rows[index];
101
+ if (row === void 0) break;
102
+ if (row.kind === "component-separator") continue;
103
+ if (row.kind === "merge-connector") {
104
+ if (convergenceProducer) {
105
+ if (row.contractHash === edge.from) sawObstruction = true;
106
+ } else if (!divergenceBranchEdge && row.contractHash !== edge.from) sawObstruction = true;
107
+ continue;
108
+ }
109
+ if (row.kind === "branch-connector") continue;
110
+ if (row.kind === "edge") {
111
+ if (row.laneIndex === laneIndex) return "node-skipping-forward";
112
+ if (!divergenceBranchEdge && row.edge !== void 0 && row.edge.to !== edge.to) sawObstruction = true;
113
+ continue;
114
+ }
115
+ if (row.kind === "node" && row.contractHash !== void 0) {
116
+ if (row.contractHash === edge.from) return sawObstruction ? "node-skipping-forward" : "adjacent";
117
+ const nodeCol = nodeColumn.get(row.contractHash) ?? 0;
118
+ if (!divergenceBranchEdge && !passThroughLaneSet.has(nodeCol)) sawObstruction = true;
119
+ }
120
+ }
121
+ return "node-skipping-forward";
122
+ }
123
+ function classifyLayoutAdjacency(rows, edgeRowIndex, edge, laneIndex, passThroughLanes, nodeColumn, position, forwardInDegree, convergenceProducer, divergenceBranchEdge) {
124
+ if (edge.kind === "self") return "adjacent";
125
+ const fromPos = position.get(edge.from);
126
+ const toPos = position.get(edge.to);
127
+ if (edge.kind === "forward") {
128
+ if ((forwardInDegree.get(edge.to) ?? 0) <= 1 && fromPos !== void 0 && toPos !== void 0 && fromPos === toPos + 1) return "adjacent";
129
+ return classifyForwardLayoutAdjacency(rows, edgeRowIndex, edge, laneIndex, passThroughLanes, nodeColumn, convergenceProducer, divergenceBranchEdge);
130
+ }
131
+ if (fromPos !== void 0 && toPos !== void 0 && toPos === fromPos + 1) return "adjacent";
132
+ for (let index = edgeRowIndex + 1; index < rows.length; index++) {
133
+ const row = rows[index];
134
+ if (row === void 0) break;
135
+ if (row.kind === "component-separator" || row.kind === "branch-connector" || row.kind === "merge-connector") continue;
136
+ if (row.kind === "edge") continue;
137
+ if (row.kind === "node") return row.contractHash === edge.to ? "adjacent" : "node-skipping-rollback";
138
+ }
139
+ return "node-skipping-rollback";
140
+ }
141
+ function refineAdjacency(rows, nodeColumn, position, forwardInDegree, forwardOutDegree, edges, producerLaneByHash) {
142
+ const forwardProducersByTo = buildForwardProducersByTo(edges);
143
+ function branchLaneForEdge(producer) {
144
+ const children = edges.filter((edge) => edge.from === producer.from && edge.kind === "forward" && edge.from !== edge.to);
145
+ if (children.length < 2) return void 0;
146
+ const index = children.findIndex((child) => child.migrationHash === producer.migrationHash);
147
+ return index >= 0 ? index : void 0;
148
+ }
149
+ return rows.map((row, rowIndex) => {
150
+ if (row.kind !== "edge" || row.edge === void 0 || row.laneIndex === void 0) return row;
151
+ const divergenceBranchEdge = row.edge.kind === "forward" && !(row.convergenceProducer ?? false) && (forwardOutDegree.get(row.edge.from) ?? 0) >= 2 && branchLaneForEdge(row.edge) !== void 0;
152
+ const adjacency = row.convergenceProducer === true && convergenceProducerUsesShortAdjacency(row.edge, row.laneIndex, forwardProducersByTo, producerLaneByHash) ? classifyForwardShortConvergenceAdjacency(rows, rowIndex, row.edge, row.laneIndex) : classifyLayoutAdjacency(rows, rowIndex, row.edge, row.laneIndex, row.passThroughLanes ?? [], nodeColumn, position, forwardInDegree, row.convergenceProducer ?? false, divergenceBranchEdge);
153
+ return {
154
+ ...row,
155
+ cells: buildEdgeCells(row.edge, row.laneIndex, row.passThroughLanes ?? [], adjacency, row.cells.length)
156
+ };
157
+ });
158
+ }
159
+ function classifyEdgeAdjacency(edge, position) {
160
+ if (edge.kind === "self") return "adjacent";
161
+ const fromPos = position.get(edge.from);
162
+ const toPos = position.get(edge.to);
163
+ if (fromPos === void 0 || toPos === void 0) return "adjacent";
164
+ if (edge.kind === "forward") {
165
+ if (toPos >= fromPos) return "adjacent";
166
+ return fromPos === toPos + 1 ? "adjacent" : "node-skipping-forward";
167
+ }
168
+ if (toPos <= fromPos) return "adjacent";
169
+ return toPos === fromPos + 1 ? "adjacent" : "node-skipping-rollback";
170
+ }
171
+ function emptyCells(width) {
172
+ return Array.from({ length: width }, () => ({ kind: "empty" }));
173
+ }
174
+ function buildBranchConnectorCells(startLane, endLane, fanTargetLanes, activeLanes, gridWidth) {
175
+ const cells = emptyCells(gridWidth);
176
+ for (let lane = 0; lane < gridWidth; lane++) {
177
+ if (activeLanes.has(lane) && (lane < startLane || lane > endLane)) {
178
+ cells[lane] = { kind: "vertical-pass" };
179
+ continue;
180
+ }
181
+ if (lane === startLane) cells[lane] = { kind: "branch-tee" };
182
+ else if (lane === endLane) cells[lane] = { kind: "branch-corner" };
183
+ else if (lane > startLane && lane < endLane) if (fanTargetLanes.has(lane)) cells[lane] = { kind: "branch-tee" };
184
+ else if (activeLanes.has(lane)) cells[lane] = { kind: "arc-crossing" };
185
+ else cells[lane] = { kind: "branch-tee" };
186
+ }
187
+ return cells;
188
+ }
189
+ function buildMergeConnectorCells(startLane, endLane, fanTargetLanes, activeLanes, gridWidth) {
190
+ const cells = emptyCells(gridWidth);
191
+ for (let lane = 0; lane < gridWidth; lane++) {
192
+ if (activeLanes.has(lane) && (lane < startLane || lane > endLane)) {
193
+ cells[lane] = { kind: "vertical-pass" };
194
+ continue;
195
+ }
196
+ if (lane === startLane) cells[lane] = { kind: "merge-tee" };
197
+ else if (lane === endLane) cells[lane] = { kind: "merge-corner" };
198
+ else if (lane > startLane && lane < endLane) if (fanTargetLanes.has(lane)) cells[lane] = { kind: "merge-tee" };
199
+ else if (activeLanes.has(lane)) cells[lane] = { kind: "arc-crossing" };
200
+ else cells[lane] = { kind: "horizontal-pass" };
201
+ }
202
+ return cells;
203
+ }
204
+ function buildNodeCells(contractHash, nodeColumn, activeLanes, gridWidth) {
205
+ const cells = emptyCells(gridWidth);
206
+ for (const lane of activeLanes) if (lane !== nodeColumn && lane < gridWidth) cells[lane] = { kind: "vertical-pass" };
207
+ if (nodeColumn < gridWidth) cells[nodeColumn] = {
208
+ kind: "node",
209
+ contractHash
210
+ };
211
+ return cells;
212
+ }
213
+ function buildEdgeCells(edge, laneIndex, passThroughLanes, adjacency, gridWidth) {
214
+ const cells = emptyCells(gridWidth);
215
+ for (const lane of passThroughLanes) if (lane < gridWidth) cells[lane] = { kind: "vertical-pass" };
216
+ if (laneIndex < gridWidth) cells[laneIndex] = {
217
+ kind: "edge-lane",
218
+ migrationHash: edge.migrationHash,
219
+ edgeKind: edge.kind,
220
+ ownsLabel: true,
221
+ adjacency
222
+ };
223
+ return cells;
224
+ }
225
+ /**
226
+ * Compute the vertical node order for a component: tips at the top (index 0),
227
+ * roots at the bottom. This is a DFS post-order over forward edges starting
228
+ * from forward roots, visiting children in their input (insertion) order. A
229
+ * node is emitted only after all of its forward children, so convergence nodes
230
+ * sit below every branch that feeds them and the longest contiguous chain reads
231
+ * top-to-bottom without braiding.
232
+ */
233
+ function computeVerticalOrder(componentNodes, forwardChildren, forwardInDegree) {
234
+ const WHITE = 0;
235
+ const GRAY = 1;
236
+ const BLACK = 2;
237
+ const color = /* @__PURE__ */ new Map();
238
+ for (const node of componentNodes) color.set(node, WHITE);
239
+ const sortRoots = (roots) => [...roots].sort((a, b) => {
240
+ if (a === EMPTY_CONTRACT_HASH) return -1;
241
+ if (b === EMPTY_CONTRACT_HASH) return 1;
242
+ return a.localeCompare(b);
243
+ });
244
+ let roots = sortRoots(componentNodes.filter((n) => (forwardInDegree.get(n) ?? 0) === 0));
245
+ if (roots.length === 0) roots = sortRoots(componentNodes);
246
+ const result = [];
247
+ function runDfs(root) {
248
+ if (color.get(root) !== WHITE) return;
249
+ const stack = [{
250
+ node: root,
251
+ children: forwardChildren.get(root) ?? [],
252
+ index: 0
253
+ }];
254
+ color.set(root, GRAY);
255
+ while (stack.length > 0) {
256
+ const frame = stack[stack.length - 1];
257
+ if (frame === void 0) break;
258
+ if (frame.index >= frame.children.length) {
259
+ color.set(frame.node, BLACK);
260
+ result.push(frame.node);
261
+ stack.pop();
262
+ continue;
263
+ }
264
+ const child = frame.children[frame.index];
265
+ frame.index += 1;
266
+ if (child === void 0) continue;
267
+ if (color.get(child.to) === WHITE) {
268
+ color.set(child.to, GRAY);
269
+ stack.push({
270
+ node: child.to,
271
+ children: forwardChildren.get(child.to) ?? [],
272
+ index: 0
273
+ });
274
+ }
275
+ }
276
+ }
277
+ for (const root of roots) runDfs(root);
278
+ for (const node of componentNodes) if (color.get(node) === WHITE) runDfs(node);
279
+ return result;
280
+ }
281
+ function rollbackSpan(edge, position) {
282
+ const top = position.get(edge.from) ?? 0;
283
+ return {
284
+ top,
285
+ bottom: position.get(edge.to) ?? top
286
+ };
287
+ }
288
+ function spansOverlap(a, b) {
289
+ return a.top <= b.bottom && b.top <= a.bottom;
290
+ }
291
+ function forwardMaxLane(rows, skipMigrationHashes) {
292
+ let max = 0;
293
+ for (const row of rows) {
294
+ if (row.kind === "edge" && row.edge !== void 0 && skipMigrationHashes.has(row.edge.migrationHash)) continue;
295
+ max = Math.max(max, row.laneIndex ?? 0);
296
+ for (const lane of row.passThroughLanes ?? []) max = Math.max(max, lane);
297
+ if (row.startLane !== void 0) max = Math.max(max, row.startLane, row.endLane ?? row.startLane);
298
+ }
299
+ return max;
300
+ }
301
+ function allocateSkipRollbackBackLanes(skipRollbacks, position, forwardMax) {
302
+ const sorted = [...skipRollbacks].sort((a, b) => {
303
+ const aTop = position.get(a.from) ?? 0;
304
+ const bTop = position.get(b.from) ?? 0;
305
+ if (aTop !== bTop) return aTop - bTop;
306
+ return b.dirName.localeCompare(a.dirName);
307
+ });
308
+ const occupied = [];
309
+ const lanes = /* @__PURE__ */ new Map();
310
+ let nextLane = forwardMax + 1;
311
+ for (const edge of sorted) {
312
+ const span = rollbackSpan(edge, position);
313
+ let lane = nextLane;
314
+ while (occupied.some((entry) => entry.lane === lane && spansOverlap(entry, span))) lane += 1;
315
+ occupied.push({
316
+ ...span,
317
+ lane
318
+ });
319
+ lanes.set(edge.migrationHash, lane);
320
+ nextLane = Math.max(nextLane, lane + 1);
321
+ }
322
+ return lanes;
323
+ }
324
+ function findNodeRowIndex(rows, contractHash) {
325
+ return rows.findIndex((row) => row.kind === "node" && row.contractHash === contractHash);
326
+ }
327
+ function findEdgeRowIndex(rows, migrationHash) {
328
+ return rows.findIndex((row) => row.kind === "edge" && row.edge?.migrationHash === migrationHash);
329
+ }
330
+ function ensureCellWidth(cells, width) {
331
+ while (cells.length < width) cells.push({ kind: "empty" });
332
+ }
333
+ function cloneRow(row) {
334
+ return {
335
+ ...row,
336
+ cells: [...row.cells]
337
+ };
338
+ }
339
+ function routeCrossesRow(route, rowIndex, rows) {
340
+ const sourceRow = findNodeRowIndex(rows, route.edge.from);
341
+ const targetRow = findNodeRowIndex(rows, route.edge.to);
342
+ if (sourceRow < 0 || targetRow < 0) return false;
343
+ return rowIndex > sourceRow && rowIndex <= targetRow;
344
+ }
345
+ function applySkipRollbackRouting(rows, skipRollbacks, position, nodeColumn, edgeColumn) {
346
+ if (skipRollbacks.length === 0) return [...rows];
347
+ const forwardMax = forwardMaxLane(rows, new Set(skipRollbacks.map((edge) => edge.migrationHash)));
348
+ const backLaneByHash = allocateSkipRollbackBackLanes(skipRollbacks, position, forwardMax);
349
+ const routes = skipRollbacks.map((edge) => ({
350
+ edge,
351
+ backLane: backLaneByHash.get(edge.migrationHash) ?? forwardMax + 1
352
+ }));
353
+ const result = rows.map(cloneRow);
354
+ for (const route of routes) {
355
+ const { edge, backLane } = route;
356
+ const nodeCol = nodeColumn.get(edge.from) ?? 0;
357
+ const targetCol = nodeColumn.get(edge.to) ?? 0;
358
+ const sourceRowIndex = findNodeRowIndex(result, edge.from);
359
+ const targetRowIndex = findNodeRowIndex(result, edge.to);
360
+ const edgeRowIndex = findEdgeRowIndex(result, edge.migrationHash);
361
+ if (sourceRowIndex < 0 || targetRowIndex < 0 || edgeRowIndex < 0) continue;
362
+ edgeColumn.set(edge.migrationHash, backLane);
363
+ const coSourcedLanes = routes.filter((other) => other.edge.from === edge.from).map((other) => other.backLane);
364
+ const maxCoSourcedLane = Math.max(...coSourcedLanes);
365
+ const sourceRow = result[sourceRowIndex];
366
+ if (sourceRow !== void 0) {
367
+ const cells = sourceRow.cells;
368
+ ensureCellWidth(cells, backLane + 1);
369
+ cells[nodeCol] = {
370
+ kind: "node",
371
+ contractHash: sourceRow.contractHash ?? EMPTY_CONTRACT_HASH,
372
+ arcTee: true
373
+ };
374
+ for (let lane = nodeCol + 1; lane < backLane; lane += 1) {
375
+ if (coSourcedLanes.includes(lane)) {
376
+ cells[lane] = { kind: "arc-branch-tee" };
377
+ continue;
378
+ }
379
+ const existing = cells[lane];
380
+ cells[lane] = existing !== void 0 && existing.kind !== "empty" && existing.kind !== "horizontal-pass" && existing.kind !== "arc-land-bridge" || routes.some((other) => other.edge.migrationHash !== edge.migrationHash && other.backLane === lane && routeCrossesRow(other, sourceRowIndex, result)) ? { kind: "arc-crossing" } : { kind: "horizontal-pass" };
381
+ }
382
+ cells[backLane] = backLane < maxCoSourcedLane ? { kind: "arc-branch-tee" } : { kind: "arc-branch-corner" };
383
+ }
384
+ const edgeRow = result[edgeRowIndex];
385
+ if (edgeRow !== void 0) {
386
+ const cells = edgeRow.cells;
387
+ ensureCellWidth(cells, backLane + 1);
388
+ cells[nodeCol] = { kind: "vertical-pass" };
389
+ cells[backLane] = {
390
+ kind: "edge-lane",
391
+ migrationHash: edge.migrationHash,
392
+ edgeKind: edge.kind,
393
+ ownsLabel: true,
394
+ adjacency: "node-skipping-rollback"
395
+ };
396
+ result[edgeRowIndex] = {
397
+ ...edgeRow,
398
+ laneIndex: backLane,
399
+ passThroughLanes: [nodeCol]
400
+ };
401
+ }
402
+ for (let index = sourceRowIndex + 1; index < targetRowIndex; index += 1) {
403
+ if (index === edgeRowIndex) continue;
404
+ const row = result[index];
405
+ if (row === void 0) continue;
406
+ const cells = row.cells;
407
+ ensureCellWidth(cells, backLane + 1);
408
+ const existing = cells[backLane];
409
+ if (existing?.kind !== "arc-land-corner" && existing?.kind !== "arc-land-bridge" && existing?.kind !== "arc-branch-corner" && existing?.kind !== "arc-branch-tee" && existing?.kind !== "arc-crossing") cells[backLane] = { kind: "vertical-pass" };
410
+ }
411
+ const targetRow = result[targetRowIndex];
412
+ if (targetRow !== void 0) {
413
+ const cells = targetRow.cells;
414
+ ensureCellWidth(cells, backLane + 1);
415
+ cells[targetCol] = {
416
+ kind: "node",
417
+ contractHash: targetRow.contractHash ?? EMPTY_CONTRACT_HASH,
418
+ arcLand: true
419
+ };
420
+ for (let lane = targetCol + 1; lane < backLane; lane += 1) {
421
+ const existing = cells[lane];
422
+ cells[lane] = existing !== void 0 && existing.kind !== "empty" && existing.kind !== "horizontal-pass" && existing.kind !== "arc-land-bridge" || routes.some((other) => other.edge.migrationHash !== edge.migrationHash && other.backLane === lane && routeCrossesRow(other, targetRowIndex, result)) ? { kind: "arc-crossing" } : { kind: "arc-land-bridge" };
423
+ }
424
+ cells[backLane] = { kind: "arc-land-corner" };
425
+ for (const other of routes) {
426
+ if (other.backLane <= backLane) continue;
427
+ if (!routeCrossesRow(other, targetRowIndex, result)) continue;
428
+ ensureCellWidth(cells, other.backLane + 1);
429
+ const existing = cells[other.backLane];
430
+ if (existing?.kind !== "arc-land-corner" && existing?.kind !== "arc-land-bridge" && existing?.kind !== "node") cells[other.backLane] = { kind: "vertical-pass" };
431
+ }
432
+ }
433
+ }
434
+ return result;
435
+ }
436
+ function collectNodeSkippingRollbacks(edges, position) {
437
+ return edges.filter((edge) => edge.kind === "rollback" && classifyEdgeAdjacency(edge, position) === "node-skipping-rollback");
438
+ }
439
+ function layoutComponent(componentNodes, allEdges) {
440
+ const componentSet = new Set(componentNodes);
441
+ const edges = allEdges.filter((e) => componentSet.has(e.from) && componentSet.has(e.to));
442
+ const forwardChildren = /* @__PURE__ */ new Map();
443
+ const producersByTo = /* @__PURE__ */ new Map();
444
+ const rollbacksByFrom = /* @__PURE__ */ new Map();
445
+ const selfByFrom = /* @__PURE__ */ new Map();
446
+ for (const edge of edges) {
447
+ if (edge.kind === "self" || edge.from === edge.to) {
448
+ const bucket = selfByFrom.get(edge.from);
449
+ if (bucket) bucket.push(edge);
450
+ else selfByFrom.set(edge.from, [edge]);
451
+ continue;
452
+ }
453
+ if (edge.kind === "forward") {
454
+ const children = forwardChildren.get(edge.from);
455
+ if (children) children.push(edge);
456
+ else forwardChildren.set(edge.from, [edge]);
457
+ const producers = producersByTo.get(edge.to);
458
+ if (producers) producers.push(edge);
459
+ else producersByTo.set(edge.to, [edge]);
460
+ continue;
461
+ }
462
+ const bucket = rollbacksByFrom.get(edge.from);
463
+ if (bucket) bucket.push(edge);
464
+ else rollbacksByFrom.set(edge.from, [edge]);
465
+ }
466
+ const forwardInDegree = buildForwardInDegree(edges);
467
+ const forwardOutDegree = buildForwardOutDegree(edges);
468
+ const distinctSourceCountByTo = buildDistinctSourceCountByTo(edges);
469
+ const order = computeVerticalOrder(componentNodes, forwardChildren, forwardInDegree);
470
+ const position = /* @__PURE__ */ new Map();
471
+ for (let index = 0; index < order.length; index++) {
472
+ const node = order[index];
473
+ if (node !== void 0) position.set(node, index);
474
+ }
475
+ const lanes = [];
476
+ const rows = [];
477
+ const nodeColumn = /* @__PURE__ */ new Map();
478
+ const edgeColumn = /* @__PURE__ */ new Map();
479
+ const producerLaneByHash = /* @__PURE__ */ new Map();
480
+ let gridWidth = 1;
481
+ function ensureGridWidth(minWidth) {
482
+ if (minWidth > gridWidth) gridWidth = minWidth;
483
+ }
484
+ function setLane(index, want) {
485
+ while (lanes.length <= index) lanes.push(null);
486
+ lanes[index] = want;
487
+ if (want !== null) ensureGridWidth(index + 1);
488
+ }
489
+ function activeLaneIndices() {
490
+ const indices = [];
491
+ for (let index = 0; index < lanes.length; index++) if (lanes[index] !== null) indices.push(index);
492
+ return indices;
493
+ }
494
+ function passThroughExcept(lane) {
495
+ return activeLaneIndices().filter((index) => index !== lane);
496
+ }
497
+ function leftmostFreeLane() {
498
+ for (let index = 0; index < lanes.length; index++) if (lanes[index] === null) return index;
499
+ return lanes.length;
500
+ }
501
+ function lanesWanting(contract) {
502
+ const indices = [];
503
+ for (let index = 0; index < lanes.length; index++) if (lanes[index] === contract) indices.push(index);
504
+ return indices;
505
+ }
506
+ function emitMergeConnector(contractHash, laneIndices) {
507
+ const startLane = Math.min(...laneIndices);
508
+ const endLane = Math.max(...laneIndices);
509
+ ensureGridWidth(endLane + 1);
510
+ const activeLanes = new Set(activeLaneIndices());
511
+ const fanTargetLanes = new Set(laneIndices);
512
+ rows.push({
513
+ kind: "merge-connector",
514
+ contractHash,
515
+ startLane,
516
+ endLane,
517
+ branchCount: laneIndices.length,
518
+ cells: buildMergeConnectorCells(startLane, endLane, fanTargetLanes, activeLanes, gridWidth)
519
+ });
520
+ for (const index of laneIndices) if (index !== startLane) setLane(index, null);
521
+ return startLane;
522
+ }
523
+ function emitBranchConnector(contractHash, startLane, endLane, branchCount, fanTargetLanes) {
524
+ ensureGridWidth(endLane + 1);
525
+ const activeLanes = new Set(activeLaneIndices());
526
+ rows.push({
527
+ kind: "branch-connector",
528
+ contractHash,
529
+ startLane,
530
+ endLane,
531
+ branchCount,
532
+ cells: buildBranchConnectorCells(startLane, endLane, new Set(fanTargetLanes), activeLanes, gridWidth)
533
+ });
534
+ }
535
+ function emitEdgeRow(edge, lane, convergenceProducer) {
536
+ const passThrough = passThroughExcept(lane);
537
+ const adjacency = classifyEdgeAdjacency(edge, position);
538
+ ensureGridWidth(Math.max(lane, ...passThrough, 0) + 1);
539
+ const row = {
540
+ kind: "edge",
541
+ edge,
542
+ laneIndex: lane,
543
+ passThroughLanes: passThrough,
544
+ cells: buildEdgeCells(edge, lane, passThrough, adjacency, gridWidth)
545
+ };
546
+ rows.push(convergenceProducer ? {
547
+ ...row,
548
+ convergenceProducer: true
549
+ } : row);
550
+ edgeColumn.set(edge.migrationHash, lane);
551
+ if (convergenceProducer) producerLaneByHash.set(edge.migrationHash, lane);
552
+ }
553
+ function emitNodeRow(contractHash, column) {
554
+ ensureGridWidth(column + 1);
555
+ const passThrough = activeLaneIndices().filter((index) => index !== column);
556
+ rows.push({
557
+ kind: "node",
558
+ contractHash,
559
+ cells: buildNodeCells(contractHash, column, passThrough, gridWidth)
560
+ });
561
+ nodeColumn.set(contractHash, column);
562
+ }
563
+ function producerGroups(node) {
564
+ const byTarget = /* @__PURE__ */ new Map();
565
+ for (const producer of producersByTo.get(node) ?? []) {
566
+ const group = byTarget.get(producer.from);
567
+ if (group) group.edges.push(producer);
568
+ else byTarget.set(producer.from, {
569
+ target: producer.from,
570
+ edges: [producer]
571
+ });
572
+ }
573
+ const groups = [...byTarget.values()];
574
+ groups.sort((a, b) => (position.get(a.target) ?? 0) - (position.get(b.target) ?? 0));
575
+ for (const group of groups) group.edges.sort((a, b) => b.dirName.localeCompare(a.dirName));
576
+ return groups;
577
+ }
578
+ function processNode(node) {
579
+ const wanting = lanesWanting(node);
580
+ let column;
581
+ if (wanting.length >= 2) column = emitMergeConnector(node, wanting);
582
+ else if (wanting.length === 1) column = wanting[0] ?? 0;
583
+ else column = leftmostFreeLane();
584
+ const selfEdges = [...selfByFrom.get(node) ?? []].sort((a, b) => b.dirName.localeCompare(a.dirName));
585
+ for (const selfEdge of selfEdges) emitEdgeRow(selfEdge, column, false);
586
+ emitNodeRow(node, column);
587
+ const rollbacks = [...rollbacksByFrom.get(node) ?? []].sort((a, b) => b.dirName.localeCompare(a.dirName));
588
+ const skipRollbacks = [];
589
+ const adjacentRollbacks = [];
590
+ for (const rollback of rollbacks) if (classifyEdgeAdjacency(rollback, position) === "node-skipping-rollback") skipRollbacks.push(rollback);
591
+ else adjacentRollbacks.push(rollback);
592
+ for (const rollback of skipRollbacks) emitEdgeRow(rollback, column, false);
593
+ const groups = producerGroups(node);
594
+ const isConvergence = (distinctSourceCountByTo.get(node) ?? 0) >= 2;
595
+ const laneForGroup = [];
596
+ for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) {
597
+ const group = groups[groupIndex];
598
+ if (group === void 0) continue;
599
+ const lane = groupIndex === 0 ? column : leftmostFreeLane();
600
+ laneForGroup[groupIndex] = lane;
601
+ setLane(lane, group.target);
602
+ }
603
+ if (groups.length >= 2) {
604
+ const endLane = Math.max(...laneForGroup);
605
+ emitBranchConnector(node, column, endLane, groups.length, laneForGroup);
606
+ }
607
+ for (let groupIndex = 0; groupIndex < groups.length; groupIndex++) {
608
+ const group = groups[groupIndex];
609
+ const lane = laneForGroup[groupIndex];
610
+ if (group === void 0 || lane === void 0) continue;
611
+ for (const edge of group.edges) emitEdgeRow(edge, lane, isConvergence);
612
+ }
613
+ for (const rollback of adjacentRollbacks) emitEdgeRow(rollback, column, false);
614
+ if (groups.length === 0) setLane(column, null);
615
+ }
616
+ for (const node of order) processNode(node);
617
+ return {
618
+ rows: applySkipRollbackRouting(refineAdjacency(rows, nodeColumn, position, forwardInDegree, forwardOutDegree, edges, producerLaneByHash), collectNodeSkippingRollbacks(edges, position), position, nodeColumn, edgeColumn),
619
+ nodeColumn,
620
+ edgeColumn
621
+ };
622
+ }
623
+ function buildMigrationGraphLayout(rowModel) {
624
+ if (rowModel.nodes.length === 0) return {
625
+ rows: [],
626
+ nodeColumn: /* @__PURE__ */ new Map(),
627
+ edgeColumn: /* @__PURE__ */ new Map()
628
+ };
629
+ const components = splitComponents(rowModel.nodes);
630
+ const allRows = [];
631
+ const nodeColumn = /* @__PURE__ */ new Map();
632
+ const edgeColumn = /* @__PURE__ */ new Map();
633
+ for (let componentIndex = 0; componentIndex < components.length; componentIndex++) {
634
+ if (componentIndex > 0) allRows.push({
635
+ kind: "component-separator",
636
+ cells: []
637
+ });
638
+ const component = components[componentIndex];
639
+ if (component === void 0 || component.length === 0) continue;
640
+ const result = layoutComponent(component, rowModel.edges);
641
+ allRows.push(...result.rows);
642
+ for (const [hash, column] of result.nodeColumn) nodeColumn.set(hash, column);
643
+ for (const [hash, column] of result.edgeColumn) edgeColumn.set(hash, column);
644
+ }
645
+ return {
646
+ rows: allRows,
647
+ nodeColumn,
648
+ edgeColumn
649
+ };
650
+ }
651
+ //#endregion
652
+ //#region src/utils/formatters/migration-graph-rows.ts
653
+ /**
654
+ * Return the weakly-connected components of `graph` as an array of node sets,
655
+ * ordered so the component containing EMPTY_CONTRACT_HASH comes first (if
656
+ * present), with remaining components sorted by their lex-smallest node hash.
657
+ */
658
+ function weaklyConnectedComponents(graph) {
659
+ const visited = /* @__PURE__ */ new Set();
660
+ const adjacency = /* @__PURE__ */ new Map();
661
+ function addAdjacent(a, b) {
662
+ const aList = adjacency.get(a);
663
+ if (aList) aList.push(b);
664
+ else adjacency.set(a, [b]);
665
+ const bList = adjacency.get(b);
666
+ if (bList) bList.push(a);
667
+ else adjacency.set(b, [a]);
668
+ }
669
+ for (const edges of graph.forwardChain.values()) for (const edge of edges) if (edge.from !== edge.to) addAdjacent(edge.from, edge.to);
670
+ for (const node of graph.nodes) if (!adjacency.has(node)) adjacency.set(node, []);
671
+ const components = [];
672
+ function bfsComponent(start) {
673
+ const component = /* @__PURE__ */ new Set();
674
+ const queue = [start];
675
+ while (queue.length > 0) {
676
+ const node = queue.shift();
677
+ if (node === void 0 || visited.has(node)) continue;
678
+ visited.add(node);
679
+ component.add(node);
680
+ for (const neighbor of adjacency.get(node) ?? []) if (!visited.has(neighbor)) queue.push(neighbor);
681
+ }
682
+ return component;
683
+ }
684
+ const allNodes = [...graph.nodes].sort((a, b) => {
685
+ if (a === EMPTY_CONTRACT_HASH) return -1;
686
+ if (b === EMPTY_CONTRACT_HASH) return 1;
687
+ return a.localeCompare(b);
688
+ });
689
+ for (const node of allNodes) if (!visited.has(node)) components.push(bfsComponent(node));
690
+ components.sort((a, b) => {
691
+ const aHasEmpty = a.has(EMPTY_CONTRACT_HASH);
692
+ const bHasEmpty = b.has(EMPTY_CONTRACT_HASH);
693
+ if (aHasEmpty && !bHasEmpty) return -1;
694
+ if (!aHasEmpty && bHasEmpty) return 1;
695
+ const aMin = [...a].sort((x, y) => x.localeCompare(y))[0] ?? "";
696
+ const bMin = [...b].sort((x, y) => x.localeCompare(y))[0] ?? "";
697
+ return aMin.localeCompare(bMin);
698
+ });
699
+ return components;
700
+ }
701
+ function forwardRootsInComponent(componentNodes, topology) {
702
+ const roots = [];
703
+ for (const node of componentNodes) if ((topology.forwardInDegree.get(node) ?? 0) === 0) roots.push(node);
704
+ roots.sort((a, b) => {
705
+ if (a === EMPTY_CONTRACT_HASH) return -1;
706
+ if (b === EMPTY_CONTRACT_HASH) return 1;
707
+ return a.localeCompare(b);
708
+ });
709
+ if (roots.length > 0) return roots;
710
+ return [...componentNodes].sort((a, b) => {
711
+ if (a === EMPTY_CONTRACT_HASH) return -1;
712
+ if (b === EMPTY_CONTRACT_HASH) return 1;
713
+ return a.localeCompare(b);
714
+ });
715
+ }
716
+ function compareNodesTipsFirst(a, b, rank) {
717
+ const rankA = rank.get(a) ?? 0;
718
+ const rankB = rank.get(b) ?? 0;
719
+ if (rankA !== rankB) return rankB - rankA;
720
+ if (a === EMPTY_CONTRACT_HASH) return 1;
721
+ if (b === EMPTY_CONTRACT_HASH) return -1;
722
+ return a.localeCompare(b);
723
+ }
724
+ /**
725
+ * Layer nodes by longest forward-path rank from forward roots within the
726
+ * component. Rank 0 is the root (bottom row); the maximum rank is the tip
727
+ * (top row). Emits rank-descending with lex-ascending tie-break among siblings
728
+ * at the same rank — stable across edge-insertion order and correct under
729
+ * diamonds, cross-links, and rollbacks.
730
+ */
731
+ function layerNodesByLongestForwardPath(componentNodes, topology, graph) {
732
+ const forwardOut = /* @__PURE__ */ new Map();
733
+ for (const node of componentNodes) forwardOut.set(node, []);
734
+ for (const edges of graph.forwardChain.values()) for (const edge of edges) {
735
+ if (!componentNodes.has(edge.from) || !componentNodes.has(edge.to)) continue;
736
+ if (edge.from === edge.to) continue;
737
+ if (topology.kindByMigrationHash.get(edge.migrationHash) !== "forward") continue;
738
+ const bucket = forwardOut.get(edge.from);
739
+ if (bucket) bucket.push(edge.to);
740
+ }
741
+ const roots = forwardRootsInComponent(componentNodes, topology);
742
+ const rank = /* @__PURE__ */ new Map();
743
+ for (const root of roots) rank.set(root, 0);
744
+ const maxPasses = componentNodes.size;
745
+ for (let pass = 0; pass < maxPasses; pass++) {
746
+ let changed = false;
747
+ for (const node of componentNodes) {
748
+ const base = rank.get(node);
749
+ if (base === void 0) continue;
750
+ for (const to of forwardOut.get(node) ?? []) {
751
+ const next = base + 1;
752
+ if (next > (rank.get(to) ?? -1)) {
753
+ rank.set(to, next);
754
+ changed = true;
755
+ }
756
+ }
757
+ }
758
+ if (!changed) break;
759
+ }
760
+ for (const node of componentNodes) if (!rank.has(node)) rank.set(node, 0);
761
+ return [...componentNodes].sort((a, b) => compareNodesTipsFirst(a, b, rank));
762
+ }
763
+ /**
764
+ * Build the row model from a tolerant `MigrationGraph`.
765
+ *
766
+ * The row model is the first pure-data stage of the `migration graph` render
767
+ * pipeline. It:
768
+ * - classifies every edge as `forward`, `rollback`, or `self`;
769
+ * - produces a deterministic vertical node ordering (tips at index 0, roots
770
+ * at the end) within each weakly-connected component;
771
+ * - separates disjoint components with `null` sentinels;
772
+ * - optionally prepends a detached current contract as its own single-node
773
+ * component when `contractHash` is not already in the graph.
774
+ *
775
+ * No columns, no lane allocation, no glyphs, no rendering.
776
+ */
777
+ /**
778
+ * Resolve the detached current contract, if any: a real contract (not the
779
+ * empty baseline) that no migration on disk produces, so it is absent from
780
+ * the graph. Such a contract renders as a floating node rather than
781
+ * decorating an existing one. Returns the hash when detached, else undefined.
782
+ */
783
+ function detachedContractHash(graph, contractHash) {
784
+ return contractHash !== void 0 && contractHash !== EMPTY_CONTRACT_HASH && !graph.nodes.has(contractHash) ? contractHash : void 0;
785
+ }
786
+ function buildMigrationGraphRows(graph, options = {}) {
787
+ const emptyModel = {
788
+ nodes: [],
789
+ edges: [],
790
+ edgesByFrom: /* @__PURE__ */ new Map(),
791
+ edgesByTo: /* @__PURE__ */ new Map()
792
+ };
793
+ if (graph.nodes.size === 0) {
794
+ const detached = detachedContractHash(graph, options.contractHash);
795
+ return detached !== void 0 ? {
796
+ ...emptyModel,
797
+ nodes: [detached]
798
+ } : emptyModel;
799
+ }
800
+ const topology = classifyMigrationGraphTopology(graph);
801
+ const edges = [];
802
+ const edgesByFrom = /* @__PURE__ */ new Map();
803
+ const edgesByTo = /* @__PURE__ */ new Map();
804
+ for (const edgeList of graph.forwardChain.values()) for (const edge of edgeList) {
805
+ const kind = topology.kindByMigrationHash.get(edge.migrationHash) ?? "forward";
806
+ const classified = {
807
+ migrationHash: edge.migrationHash,
808
+ from: edge.from,
809
+ to: edge.to,
810
+ dirName: edge.dirName,
811
+ kind
812
+ };
813
+ edges.push(classified);
814
+ const fromBucket = edgesByFrom.get(edge.from);
815
+ if (fromBucket) fromBucket.push(classified);
816
+ else edgesByFrom.set(edge.from, [classified]);
817
+ const toBucket = edgesByTo.get(edge.to);
818
+ if (toBucket) toBucket.push(classified);
819
+ else edgesByTo.set(edge.to, [classified]);
820
+ }
821
+ const components = weaklyConnectedComponents(graph);
822
+ const nodes = [];
823
+ for (let i = 0; i < components.length; i++) {
824
+ if (i > 0) nodes.push(null);
825
+ const component = components[i];
826
+ if (component === void 0) continue;
827
+ const ordered = layerNodesByLongestForwardPath(component, topology, graph);
828
+ for (const node of ordered) nodes.push(node);
829
+ }
830
+ const detached = detachedContractHash(graph, options.contractHash);
831
+ if (detached !== void 0) {
832
+ if (nodes.length > 0) nodes.unshift(null);
833
+ nodes.unshift(detached);
834
+ }
835
+ return {
836
+ nodes,
837
+ edges,
838
+ edgesByFrom,
839
+ edgesByTo
840
+ };
841
+ }
842
+ //#endregion
843
+ //#region src/utils/formatters/migration-graph-lane-colors.ts
844
+ const { magenta: magenta$1, cyan: cyan$1, green: green$1, yellow: yellow$1, blueBright, red: red$1 } = createColors({ useColor: true });
845
+ const LANE_COLOR_CYCLE = [
846
+ magenta$1,
847
+ cyan$1,
848
+ green$1,
849
+ yellow$1,
850
+ blueBright,
851
+ red$1
852
+ ];
853
+ /**
854
+ * The hue for a gutter column. The leftmost lane (column 0) is **neutral** — it
855
+ * has nothing to be told apart from in the common single-lane linear case, so
856
+ * the renderer dims it rather than tinting it; the rotating palette is reserved
857
+ * for columns ≥ 1 (where a second lane exists to distinguish). Callers must dim
858
+ * column 0 themselves; this returns identity for it as a guard. A lane freed and
859
+ * reused by a later branch keeps its column's hue — coloring is by position, not
860
+ * branch identity, exactly like `git log --graph`.
861
+ */
862
+ function laneColorForColumn(column) {
863
+ if (column <= 0) return (text) => text;
864
+ return LANE_COLOR_CYCLE[(column - 1) % LANE_COLOR_CYCLE.length] ?? ((text) => text);
865
+ }
866
+ //#endregion
867
+ //#region src/utils/formatters/migration-graph-tree-render.ts
868
+ const LABEL_GAP = 2;
869
+ /**
870
+ * The live-database overlay marker. Just another ref as far as styling goes —
871
+ * the only emphasized markers are the active ref and the `contract`
872
+ * desired-state marker (see {@link CONTRACT_MARKER_NAME}).
873
+ */
874
+ const DB_MARKER_NAME = "db";
875
+ const UNICODE_PALETTE = {
876
+ node: "○ ",
877
+ arcLand: "○◂",
878
+ arcTee: "○─",
879
+ verticalPass: "│ ",
880
+ branchTee: "├─",
881
+ mergeTee: "├─",
882
+ branchCorner: "╮ ",
883
+ mergeCorner: "╯ ",
884
+ arcBranchCorner: "╮ ",
885
+ arcBranchTee: "┬─",
886
+ arcLandCorner: "╯ ",
887
+ arcCrossing: "┼─",
888
+ arcLandBridge: "──",
889
+ horizontalPass: "──",
890
+ connectorBranchTee: "├─",
891
+ connectorBranchTeeCo: "┬─",
892
+ connectorMergeTeeCo: "┴─",
893
+ edgeArrow: {
894
+ forward: "↑",
895
+ rollback: "↓",
896
+ self: "⟲"
897
+ },
898
+ forwardArrow: migrationListForwardArrow("unicode"),
899
+ emptySource: migrationListEmptySource("unicode")
900
+ };
901
+ const ASCII_PALETTE = {
902
+ node: "* ",
903
+ arcLand: "*<",
904
+ arcTee: "*-",
905
+ verticalPass: "| ",
906
+ branchTee: "+-",
907
+ mergeTee: "+-",
908
+ branchCorner: "\\ ",
909
+ mergeCorner: "/ ",
910
+ arcBranchCorner: "\\ ",
911
+ arcBranchTee: "+-",
912
+ arcLandCorner: "/ ",
913
+ arcCrossing: "+-",
914
+ arcLandBridge: "--",
915
+ horizontalPass: "--",
916
+ connectorBranchTee: "+-",
917
+ connectorBranchTeeCo: "+-",
918
+ connectorMergeTeeCo: "+-",
919
+ edgeArrow: {
920
+ forward: "^",
921
+ rollback: "v",
922
+ self: "@"
923
+ },
924
+ forwardArrow: migrationListForwardArrow("ascii"),
925
+ emptySource: migrationListEmptySource("ascii")
926
+ };
927
+ function paletteFor(mode) {
928
+ return mode === "ascii" ? ASCII_PALETTE : UNICODE_PALETTE;
929
+ }
930
+ function arrowForEdgeKind(kind, palette) {
931
+ return palette.edgeArrow[kind];
932
+ }
933
+ /**
934
+ * The leftmost lane (column 0) renders with the neutral dim lane style rather
935
+ * than a palette hue — in the common single-lane case it has nothing to be told
936
+ * apart from. Used as the "no owning arc" sentinel during colour resolution.
937
+ */
938
+ const NEUTRAL_LANE = 0;
939
+ /**
940
+ * Forced bold for branch-coloured names. A branched name pairs its lane hue
941
+ * (also forced, via {@link laneColorForColumn}) with bold; both must emit even
942
+ * when colorette's ambient TTY detection is off, so the colorized branch name
943
+ * is deterministically bold + hue rather than hue-only.
944
+ */
945
+ const { bold: forcedBold } = createColors({ useColor: true });
946
+ /**
947
+ * Resolve per-cell colour columns for a row. Scanning right-to-left lets each
948
+ * arc bridge inherit the corner column that closes it (the arc's back-lane), so
949
+ * the whole arc — vertical run (already its own column), horizontal bridges,
950
+ * corners, crossings, and the `◂`/`─` connector — reads as a single continuous
951
+ * hue. A crossing can only be one colour, so rather than leave it dim (wrong for
952
+ * both crossing lines) it takes the arc owning the horizontal run at this row
953
+ * (the nearest corner to its right); the crossed vertical lane is simply
954
+ * occluded at that one cell and reappears on the next row.
955
+ */
956
+ function resolveRowLaneColors(cells) {
957
+ const lane = new Array(cells.length);
958
+ const connector = new Array(cells.length);
959
+ let arcCorner = NEUTRAL_LANE;
960
+ for (let column = cells.length - 1; column >= 0; column--) {
961
+ const cell = cells[column];
962
+ connector[column] = arcCorner;
963
+ switch (cell?.kind) {
964
+ case "arc-branch-corner":
965
+ case "arc-land-corner":
966
+ arcCorner = column;
967
+ lane[column] = column;
968
+ break;
969
+ case "arc-branch-tee":
970
+ lane[column] = column;
971
+ break;
972
+ case "arc-crossing":
973
+ case "arc-land-bridge":
974
+ lane[column] = arcCorner;
975
+ break;
976
+ case "horizontal-pass":
977
+ lane[column] = arcCorner === NEUTRAL_LANE ? column : arcCorner;
978
+ break;
979
+ case "node":
980
+ lane[column] = column;
981
+ arcCorner = NEUTRAL_LANE;
982
+ break;
983
+ default:
984
+ lane[column] = column;
985
+ arcCorner = NEUTRAL_LANE;
986
+ }
987
+ }
988
+ return {
989
+ lane,
990
+ connector
991
+ };
992
+ }
993
+ /**
994
+ * Resolve per-cell connector colours. Scanning right-to-left, a corner or an
995
+ * intermediate tee anchors its own lane (its junction glyph takes that column),
996
+ * but a tee's **trailing dash leads into the branch on its right** (the next
997
+ * branch point), so `┬─` reads as "this lane, then on toward the next" rather
998
+ * than tinting the dash with the left lane. The leading tee at `startLane` (the
999
+ * fork/merge origin) and pure horizontal segments inherit the nearest branch
1000
+ * point to their right whole-cell, so the run into a branch — or collapsing
1001
+ * into a merge corner — stays continuous. Pass-through verticals outside the
1002
+ * run keep their own column (column 0 stays neutral).
1003
+ */
1004
+ function resolveConnectorLaneColors(cells, startLane) {
1005
+ const glyph = new Array(cells.length);
1006
+ const dash = new Array(cells.length);
1007
+ let owner = NEUTRAL_LANE;
1008
+ for (let column = cells.length - 1; column >= 0; column--) switch (cells[column]?.kind) {
1009
+ case "branch-corner":
1010
+ case "merge-corner":
1011
+ owner = column;
1012
+ glyph[column] = column;
1013
+ dash[column] = column;
1014
+ break;
1015
+ case "branch-tee":
1016
+ case "merge-tee":
1017
+ if (column === startLane) {
1018
+ const served = owner === NEUTRAL_LANE ? column : owner;
1019
+ glyph[column] = column;
1020
+ dash[column] = served;
1021
+ } else {
1022
+ dash[column] = owner === NEUTRAL_LANE ? column : owner;
1023
+ glyph[column] = column;
1024
+ owner = column;
1025
+ }
1026
+ break;
1027
+ case "arc-crossing":
1028
+ glyph[column] = column;
1029
+ dash[column] = column;
1030
+ break;
1031
+ case "horizontal-pass": {
1032
+ const served = owner === NEUTRAL_LANE ? column : owner;
1033
+ glyph[column] = served;
1034
+ dash[column] = served;
1035
+ break;
1036
+ }
1037
+ default:
1038
+ glyph[column] = column;
1039
+ dash[column] = column;
1040
+ }
1041
+ return {
1042
+ glyph,
1043
+ dash
1044
+ };
1045
+ }
1046
+ /**
1047
+ * Style a structural glyph by its resolved colour column. Column 0 and the
1048
+ * neutral sentinel render dim (`style.lane`); columns ≥ 1 take a palette hue.
1049
+ */
1050
+ function laneStylerForColumn(colorColumn, colorize, style) {
1051
+ if (!colorize || colorColumn <= NEUTRAL_LANE) return (text) => style.lane(text);
1052
+ return laneColorForColumn(colorColumn);
1053
+ }
1054
+ /**
1055
+ * Tint a branch-owned token (direction arrow, migration name) by its edge's
1056
+ * lane so the whole branch row reads in one colour. Column 0 has nothing to be
1057
+ * told apart from in the common linear chain, so it keeps the token's existing
1058
+ * default styling (`fallback`) rather than a palette hue; only lanes ≥ 1 take a
1059
+ * colour. With colour off, the fallback (also colourless) is used unchanged.
1060
+ */
1061
+ function branchStylerOrDefault(column, colorize, fallback) {
1062
+ if (!colorize || column <= NEUTRAL_LANE) return fallback;
1063
+ return laneColorForColumn(column);
1064
+ }
1065
+ /**
1066
+ * Render a connector tee (`├─` / `┬─` / `┴─`) with its junction glyph and its
1067
+ * trailing dash coloured independently: the junction anchors its own lane while
1068
+ * the dash leads into the branch on its right.
1069
+ */
1070
+ function renderConnectorTee(pair, glyphColumn, dashColumn, colorize, style) {
1071
+ const glyph = laneStylerForColumn(glyphColumn, colorize, style);
1072
+ if (glyphColumn === dashColumn) return glyph(pair);
1073
+ return glyph(pair.slice(0, 1)) + laneStylerForColumn(dashColumn, colorize, style)(pair.slice(1));
1074
+ }
1075
+ /**
1076
+ * A node-marker glyph pair (`○◂`, `○─`, `*<`, `*-`) is the contract node
1077
+ * marker (`○` / `*`) followed by an arc connector (`◂` / `─` / `<` / `-`). The
1078
+ * marker takes its own lane's hue (so each node visibly belongs to its branch);
1079
+ * the connector follows the arc it belongs to (its owning back-lane hue).
1080
+ * Direction arrows are handled elsewhere — they take their edge's lane hue too.
1081
+ */
1082
+ function renderNodeMarkerPair(pair, nodeColumn, arcColumn, colorize, style) {
1083
+ const marker = laneStylerForColumn(nodeColumn, colorize, style);
1084
+ const connector = laneStylerForColumn(arcColumn, colorize, style);
1085
+ return marker(pair.slice(0, 1)) + connector(pair.slice(1));
1086
+ }
1087
+ function renderCellPair(cell, column, colors, colorize, style, palette) {
1088
+ const lane = laneStylerForColumn(colors.lane[column] ?? column, colorize, style);
1089
+ switch (cell.kind) {
1090
+ case "node": {
1091
+ const arcColumn = colors.connector[column] ?? NEUTRAL_LANE;
1092
+ if (cell.arcLand === true) return renderNodeMarkerPair(palette.arcLand, column, arcColumn, colorize, style);
1093
+ if (cell.arcTee === true) return renderNodeMarkerPair(palette.arcTee, column, arcColumn, colorize, style);
1094
+ return lane(palette.node);
1095
+ }
1096
+ case "vertical-pass": return lane(palette.verticalPass);
1097
+ case "edge-lane": return cell.ownsLabel ? lane(palette.verticalPass.trimEnd()) + branchStylerOrDefault(column, colorize, style.kind)(arrowForEdgeKind(cell.edgeKind, palette)) : lane(palette.verticalPass);
1098
+ case "branch-tee": return lane(palette.branchTee);
1099
+ case "merge-tee": return lane(palette.mergeTee);
1100
+ case "branch-corner": return lane(palette.branchCorner);
1101
+ case "merge-corner": return lane(palette.mergeCorner);
1102
+ case "arc-branch-corner": return lane(palette.arcBranchCorner);
1103
+ case "arc-branch-tee": return lane(palette.arcBranchTee);
1104
+ case "arc-land-corner": return lane(palette.arcLandCorner);
1105
+ case "arc-crossing": return lane(palette.arcLandBridge);
1106
+ case "arc-land-bridge": return lane(palette.arcLandBridge);
1107
+ case "horizontal-pass": return lane(palette.horizontalPass);
1108
+ case "empty": return " ";
1109
+ }
1110
+ }
1111
+ function renderConnectorRow(row, gridWidth, colorize, style, palette) {
1112
+ const isMerge = row.kind === "merge-connector";
1113
+ if (row.cells.length > 0) {
1114
+ const colors = resolveConnectorLaneColors(row.cells, row.startLane ?? 0);
1115
+ let seenTee = false;
1116
+ let out = "";
1117
+ for (let column = 0; column < row.cells.length; column++) {
1118
+ const cell = row.cells[column];
1119
+ if (cell === void 0) continue;
1120
+ const glyphColumn = colors.glyph[column] ?? column;
1121
+ const dashColumn = colors.dash[column] ?? glyphColumn;
1122
+ const lane = laneStylerForColumn(glyphColumn, colorize, style);
1123
+ switch (cell.kind) {
1124
+ case "branch-tee":
1125
+ out += renderConnectorTee(seenTee ? palette.connectorBranchTeeCo : palette.connectorBranchTee, glyphColumn, dashColumn, colorize, style);
1126
+ seenTee = true;
1127
+ break;
1128
+ case "merge-tee":
1129
+ out += renderConnectorTee(seenTee ? palette.connectorMergeTeeCo : palette.connectorBranchTee, glyphColumn, dashColumn, colorize, style);
1130
+ seenTee = true;
1131
+ break;
1132
+ case "branch-corner":
1133
+ out += lane(palette.branchCorner);
1134
+ break;
1135
+ case "merge-corner":
1136
+ out += lane(palette.mergeCorner);
1137
+ break;
1138
+ case "vertical-pass":
1139
+ out += lane(palette.verticalPass);
1140
+ break;
1141
+ case "horizontal-pass":
1142
+ out += lane(palette.horizontalPass);
1143
+ break;
1144
+ case "arc-crossing":
1145
+ out += renderConnectorTee(palette.arcCrossing, glyphColumn, dashColumn, colorize, style);
1146
+ break;
1147
+ default: out += " ";
1148
+ }
1149
+ }
1150
+ for (let column = row.cells.length; column < gridWidth; column++) out += " ";
1151
+ return out;
1152
+ }
1153
+ const start = row.startLane ?? 0;
1154
+ const end = row.endLane ?? start;
1155
+ const runLane = laneStylerForColumn(end, colorize, style);
1156
+ let out = "";
1157
+ for (let column = 0; column < gridWidth; column++) if (column < start || column > end) out += " ";
1158
+ else if (column === start) out += runLane(palette.connectorBranchTee);
1159
+ else if (column === end) out += runLane(isMerge ? palette.mergeCorner : palette.branchCorner);
1160
+ else out += runLane(isMerge ? palette.connectorMergeTeeCo : palette.connectorBranchTeeCo);
1161
+ return out;
1162
+ }
1163
+ function abbreviateHash(hash, hashLength, emptySource) {
1164
+ if (hash === EMPTY_CONTRACT_HASH) return emptySource;
1165
+ return (hash.startsWith("sha256:") ? hash.slice(7) : hash).slice(0, hashLength);
1166
+ }
1167
+ const MIN_HASH_DATA_COLUMN = 25;
1168
+ function overlayNamesForContract(contractHash, opts) {
1169
+ const names = [];
1170
+ const userRefs = opts.refsByHash?.get(contractHash);
1171
+ if (userRefs) names.push(...[...userRefs].sort((a, b) => a.localeCompare(b)));
1172
+ if (opts.dbHash === contractHash) names.push(DB_MARKER_NAME);
1173
+ if (opts.contractHash === contractHash && contractHash !== EMPTY_CONTRACT_HASH) names.push(CONTRACT_MARKER_NAME);
1174
+ return names;
1175
+ }
1176
+ function createTreeStyler(opts) {
1177
+ const base = createAnsiMigrationListStyler({ useColor: opts.colorize });
1178
+ const activeRefName = opts.activeRefName;
1179
+ if (!opts.colorize || activeRefName === void 0) return base;
1180
+ return {
1181
+ ...base,
1182
+ refs: (names) => {
1183
+ const styledNames = names.map((name) => name === activeRefName ? bold(name) : name);
1184
+ return base.refs(styledNames);
1185
+ }
1186
+ };
1187
+ }
1188
+ function formatEdgeHashColumn(edge, style, hashLength, palette) {
1189
+ if (edge.kind === "self") {
1190
+ const hash = abbreviateHash(edge.from, hashLength, palette.emptySource);
1191
+ return `${style.sourceHash(hash)} ${style.glyph(palette.forwardArrow)} ${style.destHash(hash)}`;
1192
+ }
1193
+ return `${edge.from === EMPTY_CONTRACT_HASH ? style.glyph(palette.emptySource) + " ".repeat(Math.max(0, hashLength - palette.emptySource.length)) : style.sourceHash(abbreviateHash(edge.from, hashLength, palette.emptySource))} ${style.glyph(palette.forwardArrow)} ${style.destHash(abbreviateHash(edge.to, hashLength, palette.emptySource))}`;
1194
+ }
1195
+ function padVisible(text, targetWidth) {
1196
+ const padding = Math.max(0, targetWidth - stringWidth(text));
1197
+ return text + " ".repeat(padding);
1198
+ }
1199
+ const ANSI_ESCAPE = "\x1B";
1200
+ function trimTrailingWhitespace(line) {
1201
+ const trailingSpaceBeforeReset = new RegExp(`[\\t ]+((?:${ANSI_ESCAPE}\\[[0-9;]*m)+)$`);
1202
+ return line.replace(trailingSpaceBeforeReset, "$1").replace(/\s+$/, "");
1203
+ }
1204
+ function gridWidthForModel(rows) {
1205
+ return rows.reduce((max, row) => row.kind === "node" || row.kind === "edge" ? Math.max(max, row.cells.length) : max, 1);
1206
+ }
1207
+ function maxDirNameLength(edges) {
1208
+ if (edges.length === 0) return 0;
1209
+ return Math.max(...edges.map((edge) => edge.dirName.length));
1210
+ }
1211
+ function rowDirNameWidth(labelColumn, maxDirNameLen, dirNameGap) {
1212
+ return Math.max(maxDirNameLen + dirNameGap, MIN_HASH_DATA_COLUMN - labelColumn);
1213
+ }
1214
+ function gridUsesSkipRollbackArcs(rows) {
1215
+ return rows.some((row) => row.cells.some((cell) => cell.kind === "edge-lane" && cell.adjacency === "node-skipping-rollback"));
1216
+ }
1217
+ function edgeLabelColumn(row, wideLabelColumn) {
1218
+ if (wideLabelColumn !== void 0) return wideLabelColumn;
1219
+ const laneIndex = row.laneIndex ?? 0;
1220
+ if (row.edge?.from === EMPTY_CONTRACT_HASH && laneIndex === 0) return (laneIndex + 1) * 2 + LABEL_GAP;
1221
+ return row.cells.some((cell, index) => index > laneIndex && cell.kind === "vertical-pass") ? row.cells.length * 2 + LABEL_GAP : (laneIndex + 1) * 2 + LABEL_GAP;
1222
+ }
1223
+ function nodeHasArcDecoration(row) {
1224
+ return row.cells.some((cell) => cell.kind === "node" && (cell.arcTee === true || cell.arcLand === true));
1225
+ }
1226
+ function renderMigrationGraphTree(model, opts) {
1227
+ const palette = paletteFor(opts.glyphMode ?? "unicode");
1228
+ const style = createTreeStyler(opts);
1229
+ const hashLength = opts.hashLength ?? 7;
1230
+ const gridWidth = gridWidthForModel(model.rows);
1231
+ const wideLabelColumn = gridUsesSkipRollbackArcs(model.rows) ? gridWidth * 2 + 4 : void 0;
1232
+ const dirNameGap = wideLabelColumn !== void 0 ? 3 : LABEL_GAP;
1233
+ const maxDirNameLen = maxDirNameLength(model.rows.filter((row) => row.kind === "edge" && row.edge !== void 0).map((row) => row.edge));
1234
+ const lines = [];
1235
+ for (let rowIndex = 0; rowIndex < model.rows.length; rowIndex++) {
1236
+ const row = model.rows[rowIndex];
1237
+ if (row === void 0) continue;
1238
+ if (row.kind === "component-separator") {
1239
+ lines.push("");
1240
+ continue;
1241
+ }
1242
+ if (row.kind === "branch-connector" || row.kind === "merge-connector") {
1243
+ lines.push(trimTrailingWhitespace(renderConnectorRow(row, gridWidth, opts.colorize, style, palette)));
1244
+ continue;
1245
+ }
1246
+ const cellColors = resolveRowLaneColors(row.cells);
1247
+ let gutter = row.cells.map((cell, column) => renderCellPair(cell, column, cellColors, opts.colorize, style, palette)).join("");
1248
+ let laneSpan = row.cells.length;
1249
+ if (row.kind === "node") if ((row.contractHash ?? EMPTY_CONTRACT_HASH) === EMPTY_CONTRACT_HASH) laneSpan = 1;
1250
+ else {
1251
+ let lastActiveColumn = -1;
1252
+ for (let column = row.cells.length - 1; column >= 0; column--) if (row.cells[column]?.kind !== "empty") {
1253
+ lastActiveColumn = column;
1254
+ break;
1255
+ }
1256
+ laneSpan = lastActiveColumn >= 0 ? lastActiveColumn + 1 : 1;
1257
+ }
1258
+ const labelColumn = row.kind === "edge" ? edgeLabelColumn(row, wideLabelColumn) : wideLabelColumn !== void 0 && (nodeHasArcDecoration(row) || row.contractHash !== void 0) ? wideLabelColumn : laneSpan * 2 + LABEL_GAP;
1259
+ 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("");
1260
+ 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("");
1261
+ else if (gutter.length < laneSpan * 2) gutter = gutter.padEnd(laneSpan * 2, " ");
1262
+ const dirNameWidth = rowDirNameWidth(labelColumn, maxDirNameLen, dirNameGap);
1263
+ const dataColumn = labelColumn + dirNameWidth;
1264
+ const gutterPad = padVisible(gutter, labelColumn);
1265
+ if (row.kind === "node") {
1266
+ const contractHash = row.contractHash ?? EMPTY_CONTRACT_HASH;
1267
+ if (contractHash === EMPTY_CONTRACT_HASH) {
1268
+ const trailingLanes = row.cells.slice(1).map((cell, offset) => renderCellPair(cell, offset + 1, cellColors, opts.colorize, style, palette)).join("");
1269
+ const emptyGutter = palette.emptySource.padEnd(2, " ") + trailingLanes;
1270
+ const overlayNames = overlayNamesForContract(contractHash, opts);
1271
+ if (overlayNames.length === 0) {
1272
+ lines.push(trimTrailingWhitespace(emptyGutter));
1273
+ continue;
1274
+ }
1275
+ const overlay = style.refs(overlayNames);
1276
+ lines.push(trimTrailingWhitespace(`${padVisible(emptyGutter, dataColumn)}${overlay}`));
1277
+ continue;
1278
+ }
1279
+ const hashText = style.sourceHash(abbreviateHash(contractHash, hashLength, palette.emptySource));
1280
+ const overlayNames = overlayNamesForContract(contractHash, opts);
1281
+ const overlayPad = overlayNames.length > 0 ? " ".repeat(Math.max(0, dataColumn - labelColumn - stringWidth(hashText))) : "";
1282
+ const overlay = overlayNames.length > 0 ? style.refs(overlayNames) : "";
1283
+ lines.push(trimTrailingWhitespace(`${gutterPad}${hashText}${overlayPad}${overlay}`));
1284
+ continue;
1285
+ }
1286
+ const edge = row.edge;
1287
+ if (edge === void 0) continue;
1288
+ const dirNamePadding = " ".repeat(Math.max(0, dirNameWidth - edge.dirName.length));
1289
+ const laneIndex = row.laneIndex ?? 0;
1290
+ const dirName = `${(opts.colorize && laneIndex > NEUTRAL_LANE ? (text) => forcedBold(laneColorForColumn(laneIndex)(text)) : style.dirName)(edge.dirName)}${dirNamePadding}`;
1291
+ const hashColumn = formatEdgeHashColumn(edge, style, hashLength, palette);
1292
+ lines.push(trimTrailingWhitespace(`${gutterPad}${dirName}${hashColumn}`));
1293
+ }
1294
+ return lines.join("\n");
1295
+ }
1296
+ /**
1297
+ * A compact key for the `--tree` visual language: the contract marker, the
1298
+ * in-lane direction arrows, the empty baseline, the `(refs)` overlay (including
1299
+ * the reserved `db` live-database and `contract` working-schema markers), and a
1300
+ * worked sample of the data-column `from → to` migration hash arrow.
1301
+ *
1302
+ * Honors the same glyph palette (unicode vs ASCII) and `colorize` gate as the
1303
+ * tree renderer, so the key matches whatever the graph itself drew and stays
1304
+ * pipe-safe (zero ANSI when color is off). The caller adds the trailing blank
1305
+ * line that separates this stderr key from the graph on stdout.
1306
+ */
1307
+ function renderMigrationGraphLegend(opts) {
1308
+ const palette = paletteFor(opts.glyphMode ?? "unicode");
1309
+ const style = createAnsiMigrationListStyler({ useColor: opts.colorize });
1310
+ const node = palette.node.trimEnd();
1311
+ const sampleArrow = `${style.sourceHash("aaaaaa")} ${style.glyph(palette.forwardArrow)} ${style.destHash("bbbbbb")}`;
1312
+ return [
1313
+ "Legend:",
1314
+ ` ${style.kind(node)} contract ${style.kind(palette.edgeArrow.forward)} forward ${style.kind(palette.edgeArrow.rollback)} rollback`,
1315
+ ` ${style.kind(palette.edgeArrow.self)} migration without schema change`,
1316
+ ` ${style.glyph(palette.emptySource)} empty database (baseline)`,
1317
+ ` ${style.refs(["refs"])} ${DB_MARKER_NAME} / ${CONTRACT_MARKER_NAME} markers`,
1318
+ ` ${sampleArrow} migration from contract aaaaaa to bbbbbb`
1319
+ ].join("\n");
1320
+ }
1321
+ //#endregion
1322
+ //#region src/commands/migration-graph.ts
1323
+ /**
1324
+ * `--legend` describes the `--tree` visual language, so passing it auto-enables
1325
+ * the tree path (it has nothing to say about the legacy dagre default).
1326
+ */
1327
+ function migrationGraphUsesTree(options) {
1328
+ return options.tree === true || options.legend === true;
1329
+ }
1330
+ /**
1331
+ * The legend is decoration printed alongside the command header on stderr, so
1332
+ * it is suppressed for the machine-readable / silent paths (`--json`, `--dot`,
1333
+ * `--quiet`) exactly as the header is.
1334
+ */
1335
+ function migrationGraphShowsLegend(options, flags) {
1336
+ return options.legend === true && options.dot !== true && flags.json !== true && flags.quiet !== true;
1337
+ }
1338
+ async function executeMigrationGraphCommand(options, flags, ui) {
1339
+ const config = await loadConfig(options.config);
1340
+ const { configPath, appMigrationsRelative, migrationsDir } = resolveMigrationPaths(options.config, config);
1341
+ if (!flags.json && !flags.quiet) {
1342
+ const header = formatStyledHeader({
1343
+ command: "migration graph",
1344
+ description: "Show the migration graph topology",
1345
+ details: [{
1346
+ label: "config",
1347
+ value: configPath
1348
+ }, {
1349
+ label: "migrations",
1350
+ value: appMigrationsRelative
1351
+ }],
1352
+ flags
1353
+ });
1354
+ ui.stderr(header);
1355
+ if (migrationGraphShowsLegend(options, flags)) {
1356
+ ui.stderr(renderMigrationGraphLegend({
1357
+ colorize: flags.color !== false,
1358
+ glyphMode: ui.resolveGlyphMode(options.ascii === true)
1359
+ }));
1360
+ ui.stderr("");
1361
+ }
1362
+ }
1363
+ const loaded = await buildReadAggregate(config, { migrationsDir });
1364
+ if (!loaded.ok) return loaded;
1365
+ const { aggregate, contractHash } = loaded.value;
1366
+ const graph = aggregate.app.graph();
1367
+ return ok({
1368
+ ok: true,
1369
+ graph,
1370
+ contractHash,
1371
+ refs: Object.entries(aggregate.app.refs).map(([name, entry]) => ({
1372
+ name,
1373
+ hash: entry.hash,
1374
+ active: false
1375
+ })),
1376
+ summary: `${graph.nodes.size} node(s), ${graph.migrationByHash.size} edge(s)`
1377
+ });
1378
+ }
1379
+ function createMigrationGraphCommand() {
1380
+ const command = new Command("graph");
1381
+ setCommandDescriptions(command, "Show the migration graph topology", "Renders the migration graph topology. Offline — does not consult\nthe database. Use --tree for the condensed annotated tree\n(--ascii swaps box-drawing for pipe-friendly ASCII glyphs),\n--json for machine-readable output, or --dot for Graphviz DOT\nformat.");
1382
+ setCommandExamples(command, [
1383
+ "prisma-next migration graph",
1384
+ "prisma-next migration graph --json",
1385
+ "prisma-next migration graph --dot",
1386
+ "prisma-next migration graph --tree",
1387
+ "prisma-next migration graph --tree --ascii",
1388
+ "prisma-next migration graph --legend"
1389
+ ]);
1390
+ setCommandSeeAlso(command, [
1391
+ {
1392
+ verb: "migration status",
1393
+ oneLiner: "Show migration path and pending status"
1394
+ },
1395
+ {
1396
+ verb: "migration log",
1397
+ oneLiner: "Show executed migration history"
1398
+ },
1399
+ {
1400
+ verb: "migration list",
1401
+ oneLiner: "List on-disk migrations"
1402
+ },
1403
+ {
1404
+ verb: "migration show",
1405
+ oneLiner: "Display migration package contents"
1406
+ }
1407
+ ]);
1408
+ 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) => {
1409
+ const flags = parseGlobalFlagsOrExit(options);
1410
+ const ui = createTerminalUI(flags);
1411
+ const exitCode = handleResult(await executeMigrationGraphCommand(options, flags, ui), flags, ui, (graphResult) => {
1412
+ if (options.dot) {
1413
+ const lines = ["digraph migrations {"];
1414
+ for (const edge of graphResult.graph.migrationByHash.values()) {
1415
+ const from = edge.from.slice(0, 12);
1416
+ const to = edge.to.slice(0, 12);
1417
+ lines.push(` "${from}" -> "${to}" [label="${edge.dirName}"];`);
1418
+ }
1419
+ lines.push("}");
1420
+ ui.output(lines.join("\n"));
1421
+ } else if (flags.json) {
1422
+ const nodes = [...graphResult.graph.nodes];
1423
+ const edges = [...graphResult.graph.migrationByHash.values()].map((e) => ({
1424
+ dirName: e.dirName,
1425
+ from: e.from,
1426
+ to: e.to,
1427
+ migrationHash: e.migrationHash
1428
+ }));
1429
+ ui.output(JSON.stringify({
1430
+ ok: true,
1431
+ nodes,
1432
+ edges,
1433
+ summary: graphResult.summary
1434
+ }, null, 2));
1435
+ } else if (!flags.quiet) if (migrationGraphUsesTree(options)) {
1436
+ const refsByHash = /* @__PURE__ */ new Map();
1437
+ for (const ref of graphResult.refs) {
1438
+ const existing = refsByHash.get(ref.hash);
1439
+ refsByHash.set(ref.hash, existing ? [...existing, ref.name] : [ref.name]);
1440
+ }
1441
+ const layout = buildMigrationGraphLayout(buildMigrationGraphRows(graphResult.graph, { ...graphResult.contractHash !== null ? { contractHash: graphResult.contractHash } : {} }));
1442
+ const activeRef = graphResult.refs.find((ref) => ref.active);
1443
+ const treeOutput = renderMigrationGraphTree(layout, {
1444
+ refsByHash,
1445
+ ...graphResult.contractHash !== null ? { contractHash: graphResult.contractHash } : {},
1446
+ ...activeRef !== void 0 ? { activeRefName: activeRef.name } : {},
1447
+ colorize: flags.color !== false,
1448
+ glyphMode: ui.resolveGlyphMode(options.ascii === true)
1449
+ });
1450
+ ui.output(treeOutput);
1451
+ ui.output(`\n${graphResult.summary}`);
1452
+ } else {
1453
+ const renderInput = migrationGraphToRenderInput({
1454
+ graph: graphResult.graph,
1455
+ mode: "offline",
1456
+ markerHash: void 0,
1457
+ contractHash: graphResult.contractHash ?? EMPTY_CONTRACT_HASH,
1458
+ refs: graphResult.refs,
1459
+ activeRefHash: void 0,
1460
+ activeRefName: void 0,
1461
+ edgeStatuses: []
1462
+ });
1463
+ const graphOutput = graphRenderer.render(renderInput.graph, {
1464
+ ...renderInput.options,
1465
+ colorize: flags.color !== false
1466
+ });
1467
+ ui.log(graphOutput);
1468
+ ui.log(`\n${graphResult.summary}`);
1469
+ }
1470
+ });
1471
+ process.exit(exitCode);
1472
+ });
1473
+ return command;
1474
+ }
1475
+ //#endregion
1476
+ export { migrationGraphUsesTree as i, executeMigrationGraphCommand as n, migrationGraphShowsLegend as r, createMigrationGraphCommand as t };
1477
+
1478
+ //# sourceMappingURL=migration-graph-C9WC-7eO.mjs.map