@prisma-next/cli 0.12.0-dev.4 → 0.12.0-dev.41

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 (166) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.mjs +180 -163
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/{client-KgJorIvG.mjs → client-Dk-zRFuT.mjs} +83 -58
  5. package/dist/client-Dk-zRFuT.mjs.map +1 -0
  6. package/dist/{command-helpers-Bbw1GbwL.mjs → command-helpers-xvg9oq4T.mjs} +301 -23
  7. package/dist/command-helpers-xvg9oq4T.mjs.map +1 -0
  8. package/dist/commands/contract-emit.mjs +1 -1
  9. package/dist/commands/contract-infer.mjs +1 -1
  10. package/dist/commands/db-init.mjs +4 -5
  11. package/dist/commands/db-init.mjs.map +1 -1
  12. package/dist/commands/db-schema.mjs +3 -3
  13. package/dist/commands/db-sign.mjs +4 -4
  14. package/dist/commands/db-update.d.mts.map +1 -1
  15. package/dist/commands/db-update.mjs +10 -7
  16. package/dist/commands/db-update.mjs.map +1 -1
  17. package/dist/commands/db-verify.mjs +1 -1
  18. package/dist/commands/migrate.d.mts +2 -2
  19. package/dist/commands/migrate.d.mts.map +1 -1
  20. package/dist/commands/migrate.mjs +6 -8
  21. package/dist/commands/migrate.mjs.map +1 -1
  22. package/dist/commands/migration-check.d.mts +55 -1
  23. package/dist/commands/migration-check.d.mts.map +1 -1
  24. package/dist/commands/migration-check.mjs +2 -2
  25. package/dist/commands/migration-graph.d.mts +25 -7
  26. package/dist/commands/migration-graph.d.mts.map +1 -1
  27. package/dist/commands/migration-graph.mjs +170 -2
  28. package/dist/commands/migration-graph.mjs.map +1 -0
  29. package/dist/commands/migration-list.d.mts +24 -26
  30. package/dist/commands/migration-list.d.mts.map +1 -1
  31. package/dist/commands/migration-list.mjs +2 -190
  32. package/dist/commands/migration-log.d.mts +20 -15
  33. package/dist/commands/migration-log.d.mts.map +1 -1
  34. package/dist/commands/migration-log.mjs +1 -137
  35. package/dist/commands/migration-new.mjs +3 -3
  36. package/dist/commands/migration-plan.d.mts +1 -1
  37. package/dist/commands/migration-plan.mjs +1 -1
  38. package/dist/commands/migration-show.d.mts +1 -4
  39. package/dist/commands/migration-show.d.mts.map +1 -1
  40. package/dist/commands/migration-show.mjs +13 -25
  41. package/dist/commands/migration-show.mjs.map +1 -1
  42. package/dist/commands/migration-status.d.mts +41 -141
  43. package/dist/commands/migration-status.d.mts.map +1 -1
  44. package/dist/commands/migration-status.mjs +2 -759
  45. package/dist/commands/ref.d.mts +1 -1
  46. package/dist/commands/ref.mjs +3 -3
  47. package/dist/commands/telemetry/index.d.mts +7 -0
  48. package/dist/commands/telemetry/index.d.mts.map +1 -0
  49. package/dist/commands/telemetry/index.mjs +2 -0
  50. package/dist/{contract-at-errors-BxP-TOMl.mjs → contract-at-errors-Wj3u4Xco.mjs} +2 -2
  51. package/dist/{contract-at-errors-BxP-TOMl.mjs.map → contract-at-errors-Wj3u4Xco.mjs.map} +1 -1
  52. package/dist/{contract-emit-DxcGl4Uq.mjs → contract-emit-CZU0UO6M.mjs} +3 -3
  53. package/dist/{contract-emit-DxcGl4Uq.mjs.map → contract-emit-CZU0UO6M.mjs.map} +1 -1
  54. package/dist/{contract-emit-D-4jrNve.mjs → contract-emit-FetLZ3jn.mjs} +5 -5
  55. package/dist/{contract-emit-D-4jrNve.mjs.map → contract-emit-FetLZ3jn.mjs.map} +1 -1
  56. package/dist/{contract-infer-D8uEbJuu.mjs → contract-infer-3wtOsS_H.mjs} +3 -3
  57. package/dist/{contract-infer-D8uEbJuu.mjs.map → contract-infer-3wtOsS_H.mjs.map} +1 -1
  58. package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs → contract-space-aggregate-loader-BdRPfM3Q.mjs} +63 -5
  59. package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs.map → contract-space-aggregate-loader-BdRPfM3Q.mjs.map} +1 -1
  60. package/dist/{db-verify-v_vUKXTU.mjs → db-verify-BisylXFZ.mjs} +4 -4
  61. package/dist/{db-verify-v_vUKXTU.mjs.map → db-verify-BisylXFZ.mjs.map} +1 -1
  62. package/dist/exports/control-api.d.mts +2 -2
  63. package/dist/exports/control-api.d.mts.map +1 -1
  64. package/dist/exports/control-api.mjs +2 -2
  65. package/dist/exports/index.mjs +1 -1
  66. package/dist/exports/init-output.mjs +1 -1
  67. package/dist/{framework-components-fYXjz_in.mjs → framework-components-Be4inY3I.mjs} +2 -2
  68. package/dist/{framework-components-fYXjz_in.mjs.map → framework-components-Be4inY3I.mjs.map} +1 -1
  69. package/dist/{global-flags-DEHjV8_s.d.mts → global-flags-DG4uY5tV.d.mts} +1 -1
  70. package/dist/{global-flags-DEHjV8_s.d.mts.map → global-flags-DG4uY5tV.d.mts.map} +1 -1
  71. package/dist/{init-Cv9UzWL5.mjs → init-BTE2U7lG.mjs} +5 -58
  72. package/dist/init-BTE2U7lG.mjs.map +1 -0
  73. package/dist/{inspect-live-schema-C6ohV_oQ.mjs → inspect-live-schema-Cy9Y4wsL.mjs} +3 -3
  74. package/dist/{inspect-live-schema-C6ohV_oQ.mjs.map → inspect-live-schema-Cy9Y4wsL.mjs.map} +1 -1
  75. package/dist/{migration-check-BiBJoYYW.mjs → migration-check-CUavU7U9.mjs} +236 -88
  76. package/dist/migration-check-CUavU7U9.mjs.map +1 -0
  77. package/dist/{migration-command-scaffold-CjvwO6at.mjs → migration-command-scaffold-BxOxtyJ6.mjs} +3 -3
  78. package/dist/{migration-command-scaffold-CjvwO6at.mjs.map → migration-command-scaffold-BxOxtyJ6.mjs.map} +1 -1
  79. package/dist/migration-graph-space-render-ByJ83gxp.mjs +1966 -0
  80. package/dist/migration-graph-space-render-ByJ83gxp.mjs.map +1 -0
  81. package/dist/migration-list-jK6QeczE.mjs +228 -0
  82. package/dist/migration-list-jK6QeczE.mjs.map +1 -0
  83. package/dist/migration-list-types-DS63IdFd.d.mts +23 -0
  84. package/dist/migration-list-types-DS63IdFd.d.mts.map +1 -0
  85. package/dist/migration-log-DWI6dZyi.mjs +215 -0
  86. package/dist/migration-log-DWI6dZyi.mjs.map +1 -0
  87. package/dist/migration-path-target-DqcrbOis.mjs +24 -0
  88. package/dist/migration-path-target-DqcrbOis.mjs.map +1 -0
  89. package/dist/{migration-plan-9DJ7q7_z.mjs → migration-plan-NHdlUwPG.mjs} +5 -6
  90. package/dist/{migration-plan-9DJ7q7_z.mjs.map → migration-plan-NHdlUwPG.mjs.map} +1 -1
  91. package/dist/migration-status-DI6Ldjbo.mjs +439 -0
  92. package/dist/migration-status-DI6Ldjbo.mjs.map +1 -0
  93. package/dist/{output-B60Gw5fu.mjs → output-CF_hqzI-.mjs} +1 -1
  94. package/dist/{output-B60Gw5fu.mjs.map → output-CF_hqzI-.mjs.map} +1 -1
  95. package/dist/{ref-advancement-DUZqsue6.mjs → ref-advancement-CJY9zOv7.mjs} +1 -1
  96. package/dist/{ref-advancement-DUZqsue6.mjs.map → ref-advancement-CJY9zOv7.mjs.map} +1 -1
  97. package/dist/telemetry-DQP0BvKv.mjs +122 -0
  98. package/dist/telemetry-DQP0BvKv.mjs.map +1 -0
  99. package/dist/{types-Dt_SfqFm.d.mts → types-Cculk5KV.d.mts} +44 -31
  100. package/dist/types-Cculk5KV.d.mts.map +1 -0
  101. package/dist/{verify-DCA9Sldu.mjs → verify-tvHRBBVP.mjs} +2 -2
  102. package/dist/{verify-DCA9Sldu.mjs.map → verify-tvHRBBVP.mjs.map} +1 -1
  103. package/package.json +22 -19
  104. package/src/cli.ts +5 -0
  105. package/src/commands/db-update.ts +7 -1
  106. package/src/commands/init/index.ts +6 -35
  107. package/src/commands/init/init.ts +1 -14
  108. package/src/commands/init/inputs.ts +0 -75
  109. package/src/commands/migrate.ts +6 -6
  110. package/src/commands/migration-check.ts +340 -117
  111. package/src/commands/migration-graph.ts +163 -90
  112. package/src/commands/migration-list.ts +55 -25
  113. package/src/commands/migration-log.ts +49 -98
  114. package/src/commands/migration-show.ts +10 -38
  115. package/src/commands/migration-status-overlay.ts +61 -0
  116. package/src/commands/migration-status.ts +440 -1056
  117. package/src/commands/telemetry/index.ts +107 -0
  118. package/src/commands/telemetry/status.ts +67 -0
  119. package/src/control-api/client.ts +20 -9
  120. package/src/control-api/operations/contract-emit.ts +2 -2
  121. package/src/control-api/operations/db-init.ts +3 -3
  122. package/src/control-api/operations/{db-apply.ts → db-run.ts} +37 -10
  123. package/src/control-api/operations/db-update.ts +4 -4
  124. package/src/control-api/operations/{migration-apply.ts → migrate.ts} +32 -24
  125. package/src/control-api/operations/{apply.ts → run-migration.ts} +33 -27
  126. package/src/control-api/types.ts +46 -29
  127. package/src/utils/cli-errors.ts +47 -2
  128. package/src/utils/formatters/errors.ts +11 -0
  129. package/src/utils/formatters/migration-graph-lane-colors.ts +194 -0
  130. package/src/utils/formatters/migration-graph-layout.ts +51 -7
  131. package/src/utils/formatters/migration-graph-rows.ts +128 -15
  132. package/src/utils/formatters/migration-graph-space-render.ts +138 -0
  133. package/src/utils/formatters/migration-graph-tree-render.ts +405 -77
  134. package/src/utils/formatters/migration-list-data-column.ts +4 -91
  135. package/src/utils/formatters/migration-list-graph-topology.ts +68 -90
  136. package/src/utils/formatters/migration-list-render.ts +122 -70
  137. package/src/utils/formatters/migration-list-styler.ts +48 -5
  138. package/src/utils/formatters/migration-log-table.ts +200 -0
  139. package/src/utils/formatters/migrations.ts +25 -1
  140. package/src/utils/global-flags.ts +35 -0
  141. package/src/utils/legend.ts +38 -0
  142. package/src/utils/migration-path-target.ts +39 -0
  143. package/src/utils/telemetry.ts +68 -32
  144. package/dist/client-KgJorIvG.mjs.map +0 -1
  145. package/dist/command-helpers-Bbw1GbwL.mjs.map +0 -1
  146. package/dist/commands/migration-list.mjs.map +0 -1
  147. package/dist/commands/migration-log.mjs.map +0 -1
  148. package/dist/commands/migration-status.mjs.map +0 -1
  149. package/dist/extension-pack-inputs-IDvjRCi3.mjs +0 -62
  150. package/dist/extension-pack-inputs-IDvjRCi3.mjs.map +0 -1
  151. package/dist/graph-render-rFAqZujX.mjs +0 -1081
  152. package/dist/graph-render-rFAqZujX.mjs.map +0 -1
  153. package/dist/init-Cv9UzWL5.mjs.map +0 -1
  154. package/dist/migration-check-BiBJoYYW.mjs.map +0 -1
  155. package/dist/migration-graph-D7DVUElV.mjs +0 -1232
  156. package/dist/migration-graph-D7DVUElV.mjs.map +0 -1
  157. package/dist/migration-list-styler-BRwF4-gy.mjs +0 -399
  158. package/dist/migration-list-styler-BRwF4-gy.mjs.map +0 -1
  159. package/dist/migration-types-D2FW63pr.d.mts +0 -15
  160. package/dist/migration-types-D2FW63pr.d.mts.map +0 -1
  161. package/dist/migrations-Cv2jxNNK.mjs +0 -228
  162. package/dist/migrations-Cv2jxNNK.mjs.map +0 -1
  163. package/dist/types-Dt_SfqFm.d.mts.map +0 -1
  164. package/src/utils/formatters/graph-migration-mapper.ts +0 -235
  165. package/src/utils/formatters/graph-render.ts +0 -1323
  166. package/src/utils/formatters/graph-types.ts +0 -120
@@ -1,7 +1,18 @@
1
1
  import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
2
- import { bold } from 'colorette';
2
+ import { bold, createColors, green, yellow } from 'colorette';
3
3
  import stringWidth from 'string-width';
4
4
  import type { GlyphMode } from '../glyph-mode';
5
+ import {
6
+ laneColorForColumn,
7
+ NEUTRAL_LANE_COLUMN,
8
+ type RowArcLaneColors,
9
+ resolveConnectorLaneColors,
10
+ resolveRowArcLaneColors,
11
+ stylerForLaneColumn,
12
+ } from './migration-graph-lane-colors';
13
+
14
+ export { resolveConnectorLaneColors } from './migration-graph-lane-colors';
15
+
5
16
  import type {
6
17
  MigrationGraphGridModel,
7
18
  MigrationGraphGridRow,
@@ -12,10 +23,15 @@ import {
12
23
  MIGRATION_LIST_HASH_WIDTH,
13
24
  migrationListEmptySource,
14
25
  migrationListForwardArrow,
26
+ padFromHashColumn,
15
27
  } from './migration-list-data-column';
16
28
  import type { MigrationEdgeKind } from './migration-list-graph-topology';
17
29
  import type { MigrationListStyler } from './migration-list-render';
18
- import { CONTRACT_MARKER_NAME, createAnsiMigrationListStyler } from './migration-list-styler';
30
+ import {
31
+ CONTRACT_MARKER_NAME,
32
+ createAnsiMigrationListStyler,
33
+ formatContractNodeOverlays,
34
+ } from './migration-list-styler';
19
35
 
20
36
  const LABEL_GAP = 2;
21
37
 
@@ -26,14 +42,24 @@ const LABEL_GAP = 2;
26
42
  */
27
43
  const DB_MARKER_NAME = 'db';
28
44
 
45
+ export interface MigrationEdgeAnnotation {
46
+ readonly status?: 'applied' | 'pending';
47
+ readonly operationCount?: number;
48
+ readonly invariants?: readonly string[];
49
+ }
50
+
29
51
  export interface RenderMigrationGraphTreeOptions {
30
52
  readonly refsByHash?: ReadonlyMap<string, readonly string[]>;
53
+ readonly edgeAnnotationsByHash?: ReadonlyMap<string, MigrationEdgeAnnotation>;
31
54
  readonly dbHash?: string;
32
55
  readonly contractHash?: string;
33
56
  readonly activeRefName?: string;
34
57
  readonly hashLength?: number;
58
+ readonly globalMaxEdgeTreePrefixWidth?: number;
59
+ readonly globalMaxDirNameWidth?: number;
35
60
  readonly colorize: boolean;
36
61
  readonly glyphMode?: GlyphMode;
62
+ readonly styler?: MigrationListStyler;
37
63
  }
38
64
 
39
65
  interface MigrationGraphTreeGlyphPalette {
@@ -48,6 +74,7 @@ interface MigrationGraphTreeGlyphPalette {
48
74
  readonly arcBranchCorner: string;
49
75
  readonly arcBranchTee: string;
50
76
  readonly arcLandCorner: string;
77
+ readonly arcLandTee: string;
51
78
  readonly arcCrossing: string;
52
79
  readonly arcLandBridge: string;
53
80
  readonly horizontalPass: string;
@@ -71,6 +98,7 @@ const UNICODE_PALETTE: MigrationGraphTreeGlyphPalette = {
71
98
  arcBranchCorner: '╮ ',
72
99
  arcBranchTee: '┬─',
73
100
  arcLandCorner: '╯ ',
101
+ arcLandTee: '┴─',
74
102
  arcCrossing: '┼─',
75
103
  arcLandBridge: '──',
76
104
  horizontalPass: '──',
@@ -94,6 +122,7 @@ const ASCII_PALETTE: MigrationGraphTreeGlyphPalette = {
94
122
  arcBranchCorner: '\\ ',
95
123
  arcBranchTee: '+-',
96
124
  arcLandCorner: '/ ',
125
+ arcLandTee: '+-',
97
126
  arcCrossing: '+-',
98
127
  arcLandBridge: '--',
99
128
  horizontalPass: '--',
@@ -109,6 +138,13 @@ function paletteFor(mode: GlyphMode): MigrationGraphTreeGlyphPalette {
109
138
  return mode === 'ascii' ? ASCII_PALETTE : UNICODE_PALETTE;
110
139
  }
111
140
 
141
+ function overlayStatusGlyphs(mode: GlyphMode): {
142
+ readonly applied: string;
143
+ readonly pending: string;
144
+ } {
145
+ return mode === 'ascii' ? { applied: '+', pending: '>' } : { applied: '✓', pending: '⧗' };
146
+ }
147
+
112
148
  function arrowForEdgeKind(
113
149
  kind: MigrationEdgeKind,
114
150
  palette: MigrationGraphTreeGlyphPalette,
@@ -116,56 +152,153 @@ function arrowForEdgeKind(
116
152
  return palette.edgeArrow[kind];
117
153
  }
118
154
 
155
+ /**
156
+ * Forced bold for branch-coloured names. A branched name pairs its lane hue
157
+ * (also forced, via {@link laneColorForColumn}) with bold; both must emit even
158
+ * when colorette's ambient TTY detection is off, so the colorized branch name
159
+ * is deterministically bold + hue rather than hue-only.
160
+ */
161
+ const { bold: forcedBold } = createColors({ useColor: true });
162
+
163
+ function laneStylerForColumn(
164
+ colorColumn: number,
165
+ colorize: boolean,
166
+ style: MigrationListStyler,
167
+ ): (text: string) => string {
168
+ return stylerForLaneColumn(colorColumn, colorize, style.lane);
169
+ }
170
+
171
+ /**
172
+ * Tint a branch-owned token (direction arrow, migration name) by its edge's
173
+ * lane so the whole branch row reads in one colour. Column 0 has nothing to be
174
+ * told apart from in the common linear chain, so it keeps the token's existing
175
+ * default styling (`fallback`) rather than a palette hue; only lanes ≥ 1 take a
176
+ * colour. With colour off, the fallback (also colourless) is used unchanged.
177
+ */
178
+ function branchStylerOrDefault(
179
+ column: number,
180
+ colorize: boolean,
181
+ fallback: (text: string) => string,
182
+ ): (text: string) => string {
183
+ if (!colorize || column <= NEUTRAL_LANE_COLUMN) {
184
+ return fallback;
185
+ }
186
+ return laneColorForColumn(column);
187
+ }
188
+
189
+ /**
190
+ * Render a crossing tee (`┼─`): the junction stays dim/neutral so neither arc
191
+ * steals the cell; the trailing dash takes the served lane hue.
192
+ */
193
+ function renderArcCrossing(
194
+ pair: string,
195
+ dashColumn: number,
196
+ colorize: boolean,
197
+ style: MigrationListStyler,
198
+ ): string {
199
+ const junction = colorize ? style.lane : (text: string) => text;
200
+ const dash = laneStylerForColumn(dashColumn, colorize, style);
201
+ return junction(pair.slice(0, 1)) + dash(pair.slice(1));
202
+ }
203
+
204
+ /**
205
+ * Render a connector tee (`├─` / `┬─` / `┴─`) with its junction glyph and its
206
+ * trailing dash coloured independently: the junction anchors its own lane while
207
+ * the dash leads into the branch on its right.
208
+ */
209
+ function renderConnectorTee(
210
+ pair: string,
211
+ glyphColumn: number,
212
+ dashColumn: number,
213
+ colorize: boolean,
214
+ style: MigrationListStyler,
215
+ ): string {
216
+ const glyph = laneStylerForColumn(glyphColumn, colorize, style);
217
+ if (glyphColumn === dashColumn) {
218
+ return glyph(pair);
219
+ }
220
+ return glyph(pair.slice(0, 1)) + laneStylerForColumn(dashColumn, colorize, style)(pair.slice(1));
221
+ }
222
+
119
223
  /**
120
224
  * A node-marker glyph pair (`○◂`, `○─`, `*<`, `*-`) is the contract node
121
- * marker (`○` / `*`) followed by an arc connector (`◂` / `─` / `<` / `-`).
122
- * The marker is the signal and stays bright (`style.kind`); the connector is
123
- * gutter and stays dim (`style.lane`) consistent with the plain node marker,
124
- * which is never dimmed.
225
+ * marker (`○` / `*`) followed by an arc connector (`◂` / `─` / `<` / `-`). The
226
+ * marker takes its own lane's hue (so each node visibly belongs to its branch);
227
+ * the connector follows the arc it belongs to (its owning back-lane hue).
228
+ * Direction arrows are handled elsewhere — they take their edge's lane hue too.
125
229
  */
126
- function renderNodeMarkerPair(pair: string, style: MigrationListStyler): string {
127
- return style.kind(pair.slice(0, 1)) + style.lane(pair.slice(1));
230
+ function renderNodeMarkerPair(
231
+ pair: string,
232
+ nodeColumn: number,
233
+ arcColumn: number,
234
+ colorize: boolean,
235
+ style: MigrationListStyler,
236
+ ): string {
237
+ const marker = laneStylerForColumn(nodeColumn, colorize, style);
238
+ const connector = laneStylerForColumn(arcColumn, colorize, style);
239
+ return marker(pair.slice(0, 1)) + connector(pair.slice(1));
128
240
  }
129
241
 
130
242
  function renderCellPair(
131
243
  cell: StructuralCell,
244
+ column: number,
245
+ colors: RowArcLaneColors,
246
+ colorize: boolean,
132
247
  style: MigrationListStyler,
133
248
  palette: MigrationGraphTreeGlyphPalette,
134
249
  ): string {
250
+ const laneColumn = colors.lane[column] ?? column;
251
+ const lane = laneStylerForColumn(laneColumn, colorize, style);
135
252
  switch (cell.kind) {
136
- case 'node':
137
- if (cell.arcLand === true) return renderNodeMarkerPair(palette.arcLand, style);
138
- if (cell.arcTee === true) return renderNodeMarkerPair(palette.arcTee, style);
139
- return style.kind(palette.node);
253
+ case 'node': {
254
+ const arcColumn = colors.connector[column] ?? NEUTRAL_LANE_COLUMN;
255
+ if (cell.arcLand === true) {
256
+ return renderNodeMarkerPair(palette.arcLand, column, arcColumn, colorize, style);
257
+ }
258
+ if (cell.arcTee === true) {
259
+ return renderNodeMarkerPair(palette.arcTee, column, arcColumn, colorize, style);
260
+ }
261
+ return lane(palette.node);
262
+ }
140
263
  case 'vertical-pass':
141
- return style.lane(palette.verticalPass);
264
+ return lane(palette.verticalPass);
142
265
  case 'edge-lane':
143
- // The lane stays dim; the direction arrow (↑ / ↓ / ⟲) is the signal and
144
- // stays bright, like the contract-node marker.
145
266
  return cell.ownsLabel
146
- ? style.lane(palette.verticalPass.trimEnd()) +
147
- style.kind(arrowForEdgeKind(cell.edgeKind, palette))
148
- : style.lane(palette.verticalPass);
267
+ ? lane(palette.verticalPass.trimEnd()) +
268
+ branchStylerOrDefault(
269
+ column,
270
+ colorize,
271
+ style.kind,
272
+ )(arrowForEdgeKind(cell.edgeKind, palette))
273
+ : lane(palette.verticalPass);
149
274
  case 'branch-tee':
150
- return style.lane(palette.branchTee);
275
+ return lane(palette.branchTee);
151
276
  case 'merge-tee':
152
- return style.lane(palette.mergeTee);
277
+ return lane(palette.mergeTee);
153
278
  case 'branch-corner':
154
- return style.lane(palette.branchCorner);
279
+ return lane(palette.branchCorner);
155
280
  case 'merge-corner':
156
- return style.lane(palette.mergeCorner);
281
+ return lane(palette.mergeCorner);
157
282
  case 'arc-branch-corner':
158
- return style.lane(palette.arcBranchCorner);
283
+ return lane(palette.arcBranchCorner);
159
284
  case 'arc-branch-tee':
160
- return style.lane(palette.arcBranchTee);
285
+ return lane(palette.arcBranchTee);
161
286
  case 'arc-land-corner':
162
- return style.lane(palette.arcLandCorner);
287
+ return lane(palette.arcLandCorner);
288
+ case 'arc-land-tee':
289
+ return renderConnectorTee(
290
+ palette.arcLandTee,
291
+ laneColumn,
292
+ colors.dash[column] ?? laneColumn,
293
+ colorize,
294
+ style,
295
+ );
163
296
  case 'arc-crossing':
164
- return style.lane(palette.arcCrossing);
297
+ return lane(palette.arcLandBridge);
165
298
  case 'arc-land-bridge':
166
- return style.lane(palette.arcLandBridge);
299
+ return lane(palette.arcLandBridge);
167
300
  case 'horizontal-pass':
168
- return style.lane(palette.horizontalPass);
301
+ return lane(palette.horizontalPass);
169
302
  case 'empty':
170
303
  return ' ';
171
304
  }
@@ -174,34 +307,56 @@ function renderCellPair(
174
307
  function renderConnectorRow(
175
308
  row: MigrationGraphGridRow,
176
309
  gridWidth: number,
310
+ colorize: boolean,
177
311
  style: MigrationListStyler,
178
312
  palette: MigrationGraphTreeGlyphPalette,
179
313
  ): string {
180
314
  const isMerge = row.kind === 'merge-connector';
181
315
  if (row.cells.length > 0) {
316
+ const colors = resolveConnectorLaneColors(row.cells, row.startLane ?? 0);
182
317
  let seenTee = false;
183
318
  let out = '';
184
- for (const cell of row.cells) {
319
+ for (let column = 0; column < row.cells.length; column++) {
320
+ const cell = row.cells[column];
321
+ if (cell === undefined) continue;
322
+ const glyphColumn = colors.glyph[column] ?? column;
323
+ const dashColumn = colors.dash[column] ?? glyphColumn;
324
+ const lane = laneStylerForColumn(glyphColumn, colorize, style);
185
325
  switch (cell.kind) {
186
326
  case 'branch-tee':
187
- out += style.lane(seenTee ? palette.connectorBranchTeeCo : palette.connectorBranchTee);
327
+ out += renderConnectorTee(
328
+ seenTee ? palette.connectorBranchTeeCo : palette.connectorBranchTee,
329
+ glyphColumn,
330
+ dashColumn,
331
+ colorize,
332
+ style,
333
+ );
188
334
  seenTee = true;
189
335
  break;
190
336
  case 'merge-tee':
191
- out += style.lane(seenTee ? palette.connectorMergeTeeCo : palette.connectorBranchTee);
337
+ out += renderConnectorTee(
338
+ seenTee ? palette.connectorMergeTeeCo : palette.connectorBranchTee,
339
+ glyphColumn,
340
+ dashColumn,
341
+ colorize,
342
+ style,
343
+ );
192
344
  seenTee = true;
193
345
  break;
194
346
  case 'branch-corner':
195
- out += style.lane(palette.branchCorner);
347
+ out += lane(palette.branchCorner);
196
348
  break;
197
349
  case 'merge-corner':
198
- out += style.lane(palette.mergeCorner);
350
+ out += lane(palette.mergeCorner);
199
351
  break;
200
352
  case 'vertical-pass':
201
- out += style.lane(palette.verticalPass);
353
+ out += lane(palette.verticalPass);
202
354
  break;
203
355
  case 'horizontal-pass':
204
- out += style.lane(palette.horizontalPass);
356
+ out += lane(palette.horizontalPass);
357
+ break;
358
+ case 'arc-crossing':
359
+ out += renderArcCrossing(palette.arcCrossing, dashColumn, colorize, style);
205
360
  break;
206
361
  default:
207
362
  out += ' ';
@@ -218,13 +373,15 @@ function renderConnectorRow(
218
373
 
219
374
  const start = row.startLane ?? 0;
220
375
  const end = row.endLane ?? start;
376
+ // The whole fork/merge run reads as one line in the served lane's hue (the
377
+ // corner it reaches); pass-through columns outside the run keep their own.
378
+ const runLane = laneStylerForColumn(end, colorize, style);
221
379
  let out = '';
222
380
  for (let column = 0; column < gridWidth; column++) {
223
381
  if (column < start || column > end) out += ' ';
224
- else if (column === start) out += style.lane(palette.connectorBranchTee);
225
- else if (column === end)
226
- out += style.lane(isMerge ? palette.mergeCorner : palette.branchCorner);
227
- else out += style.lane(isMerge ? palette.connectorMergeTeeCo : palette.connectorBranchTeeCo);
382
+ else if (column === start) out += runLane(palette.connectorBranchTee);
383
+ else if (column === end) out += runLane(isMerge ? palette.mergeCorner : palette.branchCorner);
384
+ else out += runLane(isMerge ? palette.connectorMergeTeeCo : palette.connectorBranchTeeCo);
228
385
  }
229
386
  return out;
230
387
  }
@@ -239,26 +396,41 @@ function abbreviateHash(hash: string, hashLength: number, emptySource: string):
239
396
 
240
397
  const MIN_HASH_DATA_COLUMN = 25;
241
398
 
399
+ interface ContractOverlayNames {
400
+ readonly markers: readonly string[];
401
+ readonly refs: readonly string[];
402
+ }
403
+
242
404
  function overlayNamesForContract(
243
405
  contractHash: string,
244
406
  opts: RenderMigrationGraphTreeOptions,
245
- ): readonly string[] {
246
- const names: string[] = [];
407
+ ): ContractOverlayNames {
408
+ const markers: string[] = [];
409
+ const refs: string[] = [];
247
410
  const userRefs = opts.refsByHash?.get(contractHash);
248
411
  if (userRefs) {
249
- names.push(...[...userRefs].sort((a, b) => a.localeCompare(b)));
250
- }
251
- if (opts.dbHash === contractHash) {
252
- names.push(DB_MARKER_NAME);
412
+ refs.push(...[...userRefs].sort((a, b) => a.localeCompare(b)));
253
413
  }
254
414
  if (opts.contractHash === contractHash && contractHash !== EMPTY_CONTRACT_HASH) {
255
- names.push(CONTRACT_MARKER_NAME);
415
+ markers.push(CONTRACT_MARKER_NAME);
416
+ }
417
+ if (opts.dbHash === contractHash) {
418
+ markers.push(DB_MARKER_NAME);
256
419
  }
257
- return names;
420
+ markers.sort((a, b) => {
421
+ if (a === CONTRACT_MARKER_NAME) {
422
+ return -1;
423
+ }
424
+ if (b === CONTRACT_MARKER_NAME) {
425
+ return 1;
426
+ }
427
+ return a.localeCompare(b);
428
+ });
429
+ return { markers, refs };
258
430
  }
259
431
 
260
432
  function createTreeStyler(opts: RenderMigrationGraphTreeOptions): MigrationListStyler {
261
- const base = createAnsiMigrationListStyler({ useColor: opts.colorize });
433
+ const base = opts.styler ?? createAnsiMigrationListStyler({ useColor: opts.colorize });
262
434
  const activeRefName = opts.activeRefName;
263
435
  if (!opts.colorize || activeRefName === undefined) {
264
436
  return base;
@@ -272,6 +444,40 @@ function createTreeStyler(opts: RenderMigrationGraphTreeOptions): MigrationListS
272
444
  };
273
445
  }
274
446
 
447
+ function formatEdgeAnnotationSuffix(
448
+ migrationHash: string,
449
+ opts: RenderMigrationGraphTreeOptions,
450
+ style: MigrationListStyler,
451
+ ): string {
452
+ const annotation = opts.edgeAnnotationsByHash?.get(migrationHash);
453
+ if (annotation === undefined) {
454
+ return '';
455
+ }
456
+ const segments: string[] = [];
457
+ if (annotation.operationCount !== undefined) {
458
+ segments.push(`${annotation.operationCount} ops`);
459
+ }
460
+ if (annotation.invariants !== undefined && annotation.invariants.length > 0) {
461
+ segments.push(style.invariants(annotation.invariants));
462
+ }
463
+ const status = annotation.status;
464
+ if (status !== undefined) {
465
+ const glyphs = overlayStatusGlyphs(opts.glyphMode ?? 'unicode');
466
+ const glyph = status === 'applied' ? glyphs.applied : glyphs.pending;
467
+ const label = status === 'applied' ? 'applied' : 'pending';
468
+ if (!opts.colorize) {
469
+ segments.push(`${glyph} ${label}`);
470
+ } else {
471
+ const styler = status === 'applied' ? green : yellow;
472
+ segments.push(styler(`${glyph} ${label}`));
473
+ }
474
+ }
475
+ if (segments.length === 0) {
476
+ return '';
477
+ }
478
+ return ` ${segments.join(' ')}`;
479
+ }
480
+
275
481
  function formatEdgeHashColumn(
276
482
  edge: ClassifiedEdge,
277
483
  style: MigrationListStyler,
@@ -280,13 +486,16 @@ function formatEdgeHashColumn(
280
486
  ): string {
281
487
  if (edge.kind === 'self') {
282
488
  const hash = abbreviateHash(edge.from, hashLength, palette.emptySource);
283
- return `${style.sourceHash(hash)} ${style.glyph(palette.forwardArrow)} ${style.destHash(hash)}`;
489
+ const source = padFromHashColumn(style.sourceHash(hash), hashLength);
490
+ return `${source} ${style.glyph(palette.forwardArrow)} ${style.destHash(hash)}`;
284
491
  }
285
492
  const source =
286
493
  edge.from === EMPTY_CONTRACT_HASH
287
- ? style.glyph(palette.emptySource) +
288
- ' '.repeat(Math.max(0, hashLength - palette.emptySource.length))
289
- : style.sourceHash(abbreviateHash(edge.from, hashLength, palette.emptySource));
494
+ ? padFromHashColumn(style.glyph(palette.emptySource), hashLength)
495
+ : padFromHashColumn(
496
+ style.sourceHash(abbreviateHash(edge.from, hashLength, palette.emptySource)),
497
+ hashLength,
498
+ );
290
499
  const arrow = style.glyph(palette.forwardArrow);
291
500
  const dest = style.destHash(abbreviateHash(edge.to, hashLength, palette.emptySource));
292
501
  return `${source} ${arrow} ${dest}`;
@@ -297,6 +506,13 @@ function padVisible(text: string, targetWidth: number): string {
297
506
  return text + ' '.repeat(padding);
298
507
  }
299
508
 
509
+ const ANSI_ESCAPE = '\x1b';
510
+
511
+ function trimTrailingWhitespace(line: string): string {
512
+ const trailingSpaceBeforeReset = new RegExp(`[\\t ]+((?:${ANSI_ESCAPE}\\[[0-9;]*m)+)$`);
513
+ return line.replace(trailingSpaceBeforeReset, '$1').replace(/\s+$/, '');
514
+ }
515
+
300
516
  function gridWidthForModel(rows: readonly MigrationGraphGridRow[]): number {
301
517
  return rows.reduce(
302
518
  (max, row) =>
@@ -336,6 +552,35 @@ function edgeLabelColumn(row: MigrationGraphGridRow, wideLabelColumn: number | u
336
552
  return usesFullRowGutter ? row.cells.length * 2 + LABEL_GAP : (laneIndex + 1) * 2 + LABEL_GAP;
337
553
  }
338
554
 
555
+ function maxEdgeTreePrefixWidth(
556
+ rows: readonly MigrationGraphGridRow[],
557
+ wideLabelColumn: number | undefined,
558
+ ): number {
559
+ let max = 0;
560
+ for (const row of rows) {
561
+ if (row.kind !== 'edge' || row.edge === undefined) continue;
562
+ max = Math.max(max, edgeLabelColumn(row, wideLabelColumn));
563
+ }
564
+ return max;
565
+ }
566
+
567
+ export function computeMaxEdgeTreePrefixWidthForLayout(model: MigrationGraphGridModel): number {
568
+ const wideLabelColumn = gridUsesSkipRollbackArcs(model.rows)
569
+ ? gridWidthForModel(model.rows) * 2 + 4
570
+ : undefined;
571
+ return maxEdgeTreePrefixWidth(model.rows, wideLabelColumn);
572
+ }
573
+
574
+ export function computeMaxDirNameLengthForLayout(model: MigrationGraphGridModel): number {
575
+ const allEdges = model.rows
576
+ .filter(
577
+ (row): row is MigrationGraphGridRow & { edge: ClassifiedEdge } =>
578
+ row.kind === 'edge' && row.edge !== undefined,
579
+ )
580
+ .map((row) => row.edge);
581
+ return maxDirNameLength(allEdges);
582
+ }
583
+
339
584
  function nodeHasArcDecoration(row: MigrationGraphGridRow): boolean {
340
585
  return row.cells.some(
341
586
  (cell) => cell.kind === 'node' && (cell.arcTee === true || cell.arcLand === true),
@@ -360,6 +605,10 @@ export function renderMigrationGraphTree(
360
605
  )
361
606
  .map((row) => row.edge);
362
607
  const maxDirNameLen = maxDirNameLength(allEdges);
608
+ const effectiveMaxDirNameLen = opts.globalMaxDirNameWidth ?? maxDirNameLen;
609
+ const maxEdgePrefixWidth =
610
+ opts.globalMaxEdgeTreePrefixWidth ?? maxEdgeTreePrefixWidth(model.rows, wideLabelColumn);
611
+ const edgeDirNameWidth = rowDirNameWidth(maxEdgePrefixWidth, effectiveMaxDirNameLen, dirNameGap);
363
612
 
364
613
  const lines: string[] = [];
365
614
 
@@ -373,24 +622,37 @@ export function renderMigrationGraphTree(
373
622
  }
374
623
 
375
624
  if (row.kind === 'branch-connector' || row.kind === 'merge-connector') {
376
- lines.push(renderConnectorRow(row, gridWidth, style, palette).replace(/\s+$/, ''));
625
+ lines.push(
626
+ trimTrailingWhitespace(renderConnectorRow(row, gridWidth, opts.colorize, style, palette)),
627
+ );
377
628
  continue;
378
629
  }
379
630
 
380
- let gutter = row.cells.map((cell) => renderCellPair(cell, style, palette)).join('');
381
- const prevRow = model.rows[rowIndex - 1];
631
+ const cellColors = resolveRowArcLaneColors(row.cells);
632
+ let gutter = row.cells
633
+ .map((cell, column) =>
634
+ renderCellPair(cell, column, cellColors, opts.colorize, style, palette),
635
+ )
636
+ .join('');
382
637
  let laneSpan = row.cells.length;
383
638
  if (row.kind === 'node') {
384
639
  const contractHash = row.contractHash ?? EMPTY_CONTRACT_HASH;
385
- if (prevRow?.kind === 'merge-connector' || contractHash === EMPTY_CONTRACT_HASH) {
640
+ if (contractHash === EMPTY_CONTRACT_HASH) {
386
641
  laneSpan = 1;
387
642
  } else {
388
- laneSpan = row.cells.length;
643
+ let lastActiveColumn = -1;
644
+ for (let column = row.cells.length - 1; column >= 0; column--) {
645
+ if (row.cells[column]?.kind !== 'empty') {
646
+ lastActiveColumn = column;
647
+ break;
648
+ }
649
+ }
650
+ laneSpan = lastActiveColumn >= 0 ? lastActiveColumn + 1 : 1;
389
651
  }
390
652
  }
391
653
  const labelColumn =
392
654
  row.kind === 'edge'
393
- ? edgeLabelColumn(row, wideLabelColumn)
655
+ ? maxEdgePrefixWidth
394
656
  : wideLabelColumn !== undefined &&
395
657
  (nodeHasArcDecoration(row) || row.contractHash !== undefined)
396
658
  ? wideLabelColumn
@@ -402,18 +664,24 @@ export function renderMigrationGraphTree(
402
664
  ) {
403
665
  gutter = row.cells
404
666
  .slice(0, 1)
405
- .map((cell) => renderCellPair(cell, style, palette))
667
+ .map((cell, column) =>
668
+ renderCellPair(cell, column, cellColors, opts.colorize, style, palette),
669
+ )
406
670
  .join('');
407
671
  } else if (row.kind === 'node' && laneSpan < row.cells.length && !nodeHasArcDecoration(row)) {
408
672
  gutter = row.cells
409
673
  .slice(0, laneSpan)
410
- .map((cell) => renderCellPair(cell, style, palette))
674
+ .map((cell, column) =>
675
+ renderCellPair(cell, column, cellColors, opts.colorize, style, palette),
676
+ )
411
677
  .join('');
412
678
  } else if (gutter.length < laneSpan * 2) {
413
679
  gutter = gutter.padEnd(laneSpan * 2, ' ');
414
680
  }
415
- const dirNameWidth = rowDirNameWidth(labelColumn, maxDirNameLen, dirNameGap);
416
- const dataColumn = labelColumn + dirNameWidth;
681
+ const dirNameWidth =
682
+ row.kind === 'edge'
683
+ ? edgeDirNameWidth
684
+ : rowDirNameWidth(labelColumn, maxDirNameLen, dirNameGap);
417
685
  const gutterPad = padVisible(gutter, labelColumn);
418
686
 
419
687
  if (row.kind === 'node') {
@@ -421,28 +689,30 @@ export function renderMigrationGraphTree(
421
689
  if (contractHash === EMPTY_CONTRACT_HASH) {
422
690
  const trailingLanes = row.cells
423
691
  .slice(1)
424
- .map((cell) => renderCellPair(cell, style, palette))
692
+ .map((cell, offset) =>
693
+ renderCellPair(cell, offset + 1, cellColors, opts.colorize, style, palette),
694
+ )
425
695
  .join('');
426
696
  const emptyGutter = palette.emptySource.padEnd(2, ' ') + trailingLanes;
427
- const overlayNames = overlayNamesForContract(contractHash, opts);
428
- if (overlayNames.length === 0) {
429
- lines.push(emptyGutter.replace(/\s+$/, ''));
697
+ const overlays = overlayNamesForContract(contractHash, opts);
698
+ if (overlays.markers.length === 0 && overlays.refs.length === 0) {
699
+ lines.push(trimTrailingWhitespace(emptyGutter));
430
700
  continue;
431
701
  }
432
- const overlay = style.refs(overlayNames);
433
- lines.push(`${padVisible(emptyGutter, dataColumn)}${overlay}`.replace(/\s+$/, ''));
702
+ const overlay = formatContractNodeOverlays(style, overlays.markers, overlays.refs);
703
+ lines.push(trimTrailingWhitespace(`${emptyGutter}${' '.repeat(LABEL_GAP)}${overlay}`));
434
704
  continue;
435
705
  }
436
706
  const hashText = style.sourceHash(
437
707
  abbreviateHash(contractHash, hashLength, palette.emptySource),
438
708
  );
439
- const overlayNames = overlayNamesForContract(contractHash, opts);
440
- const overlayPad =
441
- overlayNames.length > 0
442
- ? ' '.repeat(Math.max(0, dataColumn - labelColumn - stringWidth(hashText)))
443
- : '';
444
- const overlay = overlayNames.length > 0 ? style.refs(overlayNames) : '';
445
- lines.push(`${gutterPad}${hashText}${overlayPad}${overlay}`.replace(/\s+$/, ''));
709
+ const overlays = overlayNamesForContract(contractHash, opts);
710
+ const hasOverlays = overlays.markers.length > 0 || overlays.refs.length > 0;
711
+ const overlayPad = hasOverlays ? ' '.repeat(LABEL_GAP) : '';
712
+ const overlay = hasOverlays
713
+ ? formatContractNodeOverlays(style, overlays.markers, overlays.refs)
714
+ : '';
715
+ lines.push(trimTrailingWhitespace(`${gutterPad}${hashText}${overlayPad}${overlay}`));
446
716
  continue;
447
717
  }
448
718
 
@@ -450,10 +720,68 @@ export function renderMigrationGraphTree(
450
720
  if (edge === undefined) continue;
451
721
 
452
722
  const dirNamePadding = ' '.repeat(Math.max(0, dirNameWidth - edge.dirName.length));
453
- const dirName = `${style.dirName(edge.dirName)}${dirNamePadding}`;
723
+ const laneIndex = row.laneIndex ?? 0;
724
+ // A branched name keeps its bold (via `style.dirName`) and adds the lane
725
+ // hue, so it reads as one with its lane/arrow; column-0 names stay bold-only.
726
+ const dirNameStyler =
727
+ opts.colorize && laneIndex > NEUTRAL_LANE_COLUMN
728
+ ? (text: string) => forcedBold(laneColorForColumn(laneIndex)(text))
729
+ : style.dirName;
730
+ const dirName = `${dirNameStyler(edge.dirName)}${dirNamePadding}`;
454
731
  const hashColumn = formatEdgeHashColumn(edge, style, hashLength, palette);
455
- lines.push(`${gutterPad}${dirName}${hashColumn}`.replace(/\s+$/, ''));
732
+ const annotationSuffix = formatEdgeAnnotationSuffix(edge.migrationHash, opts, style);
733
+ lines.push(trimTrailingWhitespace(`${gutterPad}${dirName}${hashColumn}${annotationSuffix}`));
734
+ }
735
+
736
+ return lines.join('\n');
737
+ }
738
+
739
+ export interface RenderMigrationGraphLegendOptions {
740
+ readonly colorize: boolean;
741
+ readonly glyphMode?: GlyphMode;
742
+ }
743
+
744
+ function formatLegendExampleMarkers(colorize: boolean): string {
745
+ if (!colorize) {
746
+ return '<contract, db>';
456
747
  }
748
+ const open = green('<');
749
+ const close = green('>');
750
+ const separator = green(', ');
751
+ return open + green('contract') + separator + green('db') + close;
752
+ }
457
753
 
754
+ /**
755
+ * A compact key for the tree visual language: the contract node glyph, the
756
+ * in-lane direction arrows, the empty baseline, the system-marker `<…>` and
757
+ * user-ref `(…)` bracket conventions (two illustrative example lines), and a
758
+ * worked sample of the data-column `from → to` migration hash arrow.
759
+ *
760
+ * Honors the same glyph palette (unicode vs ASCII) and `colorize` gate as the
761
+ * tree renderer, so the key matches whatever the graph itself drew and stays
762
+ * pipe-safe (zero ANSI when color is off). The caller adds the trailing blank
763
+ * line that separates this stderr key from the tree on stdout.
764
+ */
765
+ export function renderMigrationGraphLegend(opts: RenderMigrationGraphLegendOptions): string {
766
+ const palette = paletteFor(opts.glyphMode ?? 'unicode');
767
+ const style = createAnsiMigrationListStyler({ useColor: opts.colorize });
768
+ const node = palette.node.trimEnd();
769
+ const sampleArrow = `${style.sourceHash('aaaaaa')} ${style.glyph(palette.forwardArrow)} ${style.destHash('bbbbbb')}`;
770
+ const statusGlyphs = overlayStatusGlyphs(opts.glyphMode ?? 'unicode');
771
+ const appliedPending = opts.colorize
772
+ ? ` ${green(statusGlyphs.applied)} ${style.summary('applied')} ${yellow(statusGlyphs.pending)} ${style.summary('pending')}`
773
+ : ` ${statusGlyphs.applied} ${style.summary('applied')} ${statusGlyphs.pending} ${style.summary('pending')}`;
774
+ const exampleMarkers = formatLegendExampleMarkers(opts.colorize);
775
+ const exampleRefs = opts.colorize ? style.refs(['prod', 'staging']) : '(prod, staging)';
776
+ const lines = [
777
+ 'Legend:',
778
+ ` ${style.kind(node)} ${style.summary('contract')} ${style.kind(palette.edgeArrow.forward)} ${style.summary('forward')} ${style.kind(palette.edgeArrow.rollback)} ${style.summary('rollback')}`,
779
+ ` ${style.kind(palette.edgeArrow.self)} ${style.summary('migration without schema change')}`,
780
+ appliedPending,
781
+ ` ${style.kind(palette.emptySource)} ${style.summary('empty database (baseline)')}`,
782
+ ` ${exampleMarkers} ${style.summary('live markers (contract on disk, database state)')}`,
783
+ ` ${exampleRefs} ${style.summary('user-defined refs')}`,
784
+ ` ${sampleArrow} ${style.summary('migration from contract aaaaaa to bbbbbb')}`,
785
+ ];
458
786
  return lines.join('\n');
459
787
  }