@prisma-next/cli 0.12.0-dev.17 → 0.12.0-dev.3

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 (104) hide show
  1. package/dist/cli.mjs +160 -177
  2. package/dist/cli.mjs.map +1 -1
  3. package/dist/{client-Cdxcme1x.mjs → client-KgJorIvG.mjs} +8 -21
  4. package/dist/client-KgJorIvG.mjs.map +1 -0
  5. package/dist/{command-helpers-Cmdqyhz9.mjs → command-helpers-Bbw1GbwL.mjs} +2 -32
  6. package/dist/{command-helpers-Cmdqyhz9.mjs.map → command-helpers-Bbw1GbwL.mjs.map} +1 -1
  7. package/dist/commands/contract-emit.mjs +1 -1
  8. package/dist/commands/contract-infer.mjs +1 -1
  9. package/dist/commands/db-init.mjs +4 -4
  10. package/dist/commands/db-schema.mjs +3 -3
  11. package/dist/commands/db-sign.mjs +4 -4
  12. package/dist/commands/db-update.mjs +5 -5
  13. package/dist/commands/db-verify.mjs +1 -1
  14. package/dist/commands/migrate.d.mts +1 -1
  15. package/dist/commands/migrate.mjs +5 -5
  16. package/dist/commands/migration-check.mjs +1 -1
  17. package/dist/commands/migration-graph.d.mts +5 -23
  18. package/dist/commands/migration-graph.d.mts.map +1 -1
  19. package/dist/commands/migration-graph.mjs +2 -2
  20. package/dist/commands/migration-list.d.mts +3 -3
  21. package/dist/commands/migration-list.mjs +2 -2
  22. package/dist/commands/migration-log.d.mts +3 -3
  23. package/dist/commands/migration-log.mjs +3 -3
  24. package/dist/commands/migration-new.mjs +3 -3
  25. package/dist/commands/migration-plan.d.mts +1 -1
  26. package/dist/commands/migration-plan.mjs +1 -1
  27. package/dist/commands/migration-show.d.mts +1 -1
  28. package/dist/commands/migration-show.mjs +3 -3
  29. package/dist/commands/migration-status.d.mts +1 -1
  30. package/dist/commands/migration-status.mjs +4 -4
  31. package/dist/commands/migration-status.mjs.map +1 -1
  32. package/dist/commands/ref.d.mts +1 -1
  33. package/dist/commands/ref.mjs +2 -2
  34. package/dist/{contract-at-errors-Cz0z5PJi.mjs → contract-at-errors-BxP-TOMl.mjs} +2 -2
  35. package/dist/{contract-at-errors-Cz0z5PJi.mjs.map → contract-at-errors-BxP-TOMl.mjs.map} +1 -1
  36. package/dist/{contract-emit-CC9jDOmu.mjs → contract-emit-D-4jrNve.mjs} +3 -3
  37. package/dist/{contract-emit-CC9jDOmu.mjs.map → contract-emit-D-4jrNve.mjs.map} +1 -1
  38. package/dist/{contract-emit-DPMij44i.mjs → contract-emit-DxcGl4Uq.mjs} +3 -3
  39. package/dist/{contract-emit-DPMij44i.mjs.map → contract-emit-DxcGl4Uq.mjs.map} +1 -1
  40. package/dist/{contract-infer-DaFPNrZH.mjs → contract-infer-D8uEbJuu.mjs} +3 -3
  41. package/dist/{contract-infer-DaFPNrZH.mjs.map → contract-infer-D8uEbJuu.mjs.map} +1 -1
  42. package/dist/{contract-space-aggregate-loader-CirAEsM8.mjs → contract-space-aggregate-loader-DvZwdkrr.mjs} +2 -2
  43. package/dist/{contract-space-aggregate-loader-CirAEsM8.mjs.map → contract-space-aggregate-loader-DvZwdkrr.mjs.map} +1 -1
  44. package/dist/{db-verify-BSA1a_W_.mjs → db-verify-v_vUKXTU.mjs} +4 -4
  45. package/dist/{db-verify-BSA1a_W_.mjs.map → db-verify-v_vUKXTU.mjs.map} +1 -1
  46. package/dist/exports/control-api.d.mts +1 -1
  47. package/dist/exports/control-api.d.mts.map +1 -1
  48. package/dist/exports/control-api.mjs +2 -2
  49. package/dist/exports/index.mjs +1 -1
  50. package/dist/exports/init-output.mjs +1 -1
  51. package/dist/{framework-components-DynSvww4.mjs → framework-components-fYXjz_in.mjs} +2 -2
  52. package/dist/{framework-components-DynSvww4.mjs.map → framework-components-fYXjz_in.mjs.map} +1 -1
  53. package/dist/{global-flags-DG4uY5tV.d.mts → global-flags-DEHjV8_s.d.mts} +1 -1
  54. package/dist/{global-flags-DG4uY5tV.d.mts.map → global-flags-DEHjV8_s.d.mts.map} +1 -1
  55. package/dist/{init-B6kKrmf7.mjs → init-Cv9UzWL5.mjs} +58 -5
  56. package/dist/init-Cv9UzWL5.mjs.map +1 -0
  57. package/dist/{inspect-live-schema-Dn56wDhG.mjs → inspect-live-schema-C6ohV_oQ.mjs} +3 -3
  58. package/dist/{inspect-live-schema-Dn56wDhG.mjs.map → inspect-live-schema-C6ohV_oQ.mjs.map} +1 -1
  59. package/dist/{migration-check-DzH1u-O1.mjs → migration-check-BiBJoYYW.mjs} +2 -2
  60. package/dist/{migration-check-DzH1u-O1.mjs.map → migration-check-BiBJoYYW.mjs.map} +1 -1
  61. package/dist/{migration-command-scaffold-V52dV2Tv.mjs → migration-command-scaffold-CjvwO6at.mjs} +3 -3
  62. package/dist/{migration-command-scaffold-V52dV2Tv.mjs.map → migration-command-scaffold-CjvwO6at.mjs.map} +1 -1
  63. package/dist/{migration-graph-Cm3oee8C.mjs → migration-graph-D7DVUElV.mjs} +80 -326
  64. package/dist/migration-graph-D7DVUElV.mjs.map +1 -0
  65. package/dist/{migration-plan-CaeKCKp4.mjs → migration-plan-9DJ7q7_z.mjs} +5 -5
  66. package/dist/{migration-plan-CaeKCKp4.mjs.map → migration-plan-9DJ7q7_z.mjs.map} +1 -1
  67. package/dist/{migration-types-CAQ-0TEE.d.mts → migration-types-D2FW63pr.d.mts} +1 -1
  68. package/dist/{migration-types-CAQ-0TEE.d.mts.map → migration-types-D2FW63pr.d.mts.map} +1 -1
  69. package/dist/{migrations-DQ1t3XFL.mjs → migrations-Cv2jxNNK.mjs} +2 -2
  70. package/dist/{migrations-DQ1t3XFL.mjs.map → migrations-Cv2jxNNK.mjs.map} +1 -1
  71. package/dist/{output-CF_hqzI-.mjs → output-B60Gw5fu.mjs} +1 -1
  72. package/dist/{output-CF_hqzI-.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
  73. package/dist/{terminal-ui-C3xGyxW-.d.mts → terminal-ui-5Y6mrg93.d.mts} +1 -1
  74. package/dist/{terminal-ui-C3xGyxW-.d.mts.map → terminal-ui-5Y6mrg93.d.mts.map} +1 -1
  75. package/dist/{types-DiC683UW.d.mts → types-Dt_SfqFm.d.mts} +2 -8
  76. package/dist/{types-DiC683UW.d.mts.map → types-Dt_SfqFm.d.mts.map} +1 -1
  77. package/dist/{verify-CreSJ1Mz.mjs → verify-DCA9Sldu.mjs} +2 -2
  78. package/dist/{verify-CreSJ1Mz.mjs.map → verify-DCA9Sldu.mjs.map} +1 -1
  79. package/package.json +18 -22
  80. package/src/cli.ts +0 -5
  81. package/src/commands/init/index.ts +35 -6
  82. package/src/commands/init/init.ts +14 -1
  83. package/src/commands/init/inputs.ts +75 -0
  84. package/src/commands/migration-graph.ts +2 -43
  85. package/src/commands/migration-status.ts +1 -1
  86. package/src/control-api/client.ts +1 -11
  87. package/src/control-api/operations/apply.ts +0 -1
  88. package/src/control-api/operations/migration-apply.ts +3 -10
  89. package/src/control-api/types.ts +1 -12
  90. package/src/utils/formatters/migration-graph-layout.ts +5 -27
  91. package/src/utils/formatters/migration-graph-tree-render.ts +51 -360
  92. package/src/utils/global-flags.ts +0 -35
  93. package/src/utils/telemetry.ts +32 -68
  94. package/dist/client-Cdxcme1x.mjs.map +0 -1
  95. package/dist/commands/telemetry/index.d.mts +0 -7
  96. package/dist/commands/telemetry/index.d.mts.map +0 -1
  97. package/dist/commands/telemetry/index.mjs +0 -2
  98. package/dist/init-B6kKrmf7.mjs.map +0 -1
  99. package/dist/migration-graph-Cm3oee8C.mjs.map +0 -1
  100. package/dist/telemetry-Q88WHwlv.mjs +0 -122
  101. package/dist/telemetry-Q88WHwlv.mjs.map +0 -1
  102. package/src/commands/telemetry/index.ts +0 -107
  103. package/src/commands/telemetry/status.ts +0 -67
  104. package/src/utils/formatters/migration-graph-lane-colors.ts +0 -31
@@ -1,8 +1,7 @@
1
1
  import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
2
- import { bold, createColors } from 'colorette';
2
+ import { bold } from 'colorette';
3
3
  import stringWidth from 'string-width';
4
4
  import type { GlyphMode } from '../glyph-mode';
5
- import { laneColorForColumn } from './migration-graph-lane-colors';
6
5
  import type {
7
6
  MigrationGraphGridModel,
8
7
  MigrationGraphGridRow,
@@ -117,276 +116,56 @@ function arrowForEdgeKind(
117
116
  return palette.edgeArrow[kind];
118
117
  }
119
118
 
120
- /**
121
- * The leftmost lane (column 0) renders with the neutral dim lane style rather
122
- * than a palette hue — in the common single-lane case it has nothing to be told
123
- * apart from. Used as the "no owning arc" sentinel during colour resolution.
124
- */
125
- const NEUTRAL_LANE = 0;
126
-
127
- /**
128
- * Forced bold for branch-coloured names. A branched name pairs its lane hue
129
- * (also forced, via {@link laneColorForColumn}) with bold; both must emit even
130
- * when colorette's ambient TTY detection is off, so the colorized branch name
131
- * is deterministically bold + hue rather than hue-only.
132
- */
133
- const { bold: forcedBold } = createColors({ useColor: true });
134
-
135
- /**
136
- * The colour-source column for each cell of a row, resolved together because a
137
- * routed back-arc spans columns and must read as **one hue** rather than a
138
- * per-column "rainbow". An arc's horizontal bridges, corners, and node-pair
139
- * connector all take the arc's owning back-lane column (the corner that closes
140
- * the arc), not the column they pass through.
141
- */
142
- interface RowLaneColors {
143
- /** Colour column for a cell's structural glyph (lane / spine / arc body). */
144
- readonly lane: readonly number[];
145
- /** Colour column for a node arc-pair's connector half (`◂` / `─`). */
146
- readonly connector: readonly number[];
147
- }
148
-
149
- /**
150
- * Resolve per-cell colour columns for a row. Scanning right-to-left lets each
151
- * arc bridge inherit the corner column that closes it (the arc's back-lane), so
152
- * the whole arc — vertical run (already its own column), horizontal bridges,
153
- * corners, crossings, and the `◂`/`─` connector — reads as a single continuous
154
- * hue. A crossing can only be one colour, so rather than leave it dim (wrong for
155
- * both crossing lines) it takes the arc owning the horizontal run at this row
156
- * (the nearest corner to its right); the crossed vertical lane is simply
157
- * occluded at that one cell and reappears on the next row.
158
- */
159
- function resolveRowLaneColors(cells: readonly StructuralCell[]): RowLaneColors {
160
- const lane = new Array<number>(cells.length);
161
- const connector = new Array<number>(cells.length);
162
- let arcCorner = NEUTRAL_LANE;
163
- for (let column = cells.length - 1; column >= 0; column--) {
164
- const cell = cells[column];
165
- connector[column] = arcCorner;
166
- switch (cell?.kind) {
167
- case 'arc-branch-corner':
168
- case 'arc-land-corner':
169
- arcCorner = column;
170
- lane[column] = column;
171
- break;
172
- case 'arc-branch-tee':
173
- // An inner co-sourced arc's own back-lane junction: its vertical run
174
- // continues below in this column, so it keeps its own column hue.
175
- lane[column] = column;
176
- break;
177
- case 'arc-crossing':
178
- case 'arc-land-bridge':
179
- lane[column] = arcCorner;
180
- break;
181
- case 'horizontal-pass':
182
- lane[column] = arcCorner === NEUTRAL_LANE ? column : arcCorner;
183
- break;
184
- case 'node':
185
- lane[column] = column;
186
- arcCorner = NEUTRAL_LANE;
187
- break;
188
- default:
189
- lane[column] = column;
190
- arcCorner = NEUTRAL_LANE;
191
- }
192
- }
193
- return { lane, connector };
194
- }
195
-
196
- /**
197
- * Per-cell colour for a forward branch/merge connector row, split into the
198
- * cell's junction `glyph` and its trailing `dash`. A connector's horizontal run
199
- * is one logical line (a fork into new lanes, or a merge into a surviving lane)
200
- * and reads best as the colour of the lane each segment serves — not dim-gray
201
- * or a per-pass-through-column "rainbow".
202
- */
203
- interface ConnectorLaneColors {
204
- /** Colour column for a cell's junction glyph (`├` / `┬` / `┴` / `╮` / `╯`). */
205
- readonly glyph: readonly number[];
206
- /** Colour column for a tee's trailing `─` — the branch it leads into. */
207
- readonly dash: readonly number[];
208
- }
209
-
210
- /**
211
- * Resolve per-cell connector colours. Scanning right-to-left, a corner or an
212
- * intermediate tee anchors its own lane (its junction glyph takes that column),
213
- * but a tee's **trailing dash leads into the branch on its right** (the next
214
- * branch point), so `┬─` reads as "this lane, then on toward the next" rather
215
- * than tinting the dash with the left lane. The leading tee at `startLane` (the
216
- * fork/merge origin) and pure horizontal segments inherit the nearest branch
217
- * point to their right whole-cell, so the run into a branch — or collapsing
218
- * into a merge corner — stays continuous. Pass-through verticals outside the
219
- * run keep their own column (column 0 stays neutral).
220
- */
221
- function resolveConnectorLaneColors(
222
- cells: readonly StructuralCell[],
223
- startLane: number,
224
- ): ConnectorLaneColors {
225
- const glyph = new Array<number>(cells.length);
226
- const dash = new Array<number>(cells.length);
227
- let owner = NEUTRAL_LANE;
228
- for (let column = cells.length - 1; column >= 0; column--) {
229
- const cell = cells[column];
230
- switch (cell?.kind) {
231
- case 'branch-corner':
232
- case 'merge-corner':
233
- owner = column;
234
- glyph[column] = column;
235
- dash[column] = column;
236
- break;
237
- case 'branch-tee':
238
- case 'merge-tee':
239
- if (column === startLane) {
240
- const served = owner === NEUTRAL_LANE ? column : owner;
241
- glyph[column] = column;
242
- dash[column] = served;
243
- } else {
244
- dash[column] = owner === NEUTRAL_LANE ? column : owner;
245
- glyph[column] = column;
246
- owner = column;
247
- }
248
- break;
249
- case 'arc-crossing':
250
- glyph[column] = column;
251
- dash[column] = column;
252
- break;
253
- case 'horizontal-pass': {
254
- const served = owner === NEUTRAL_LANE ? column : owner;
255
- glyph[column] = served;
256
- dash[column] = served;
257
- break;
258
- }
259
- default:
260
- glyph[column] = column;
261
- dash[column] = column;
262
- }
263
- }
264
- return { glyph, dash };
265
- }
266
-
267
- /**
268
- * Style a structural glyph by its resolved colour column. Column 0 and the
269
- * neutral sentinel render dim (`style.lane`); columns ≥ 1 take a palette hue.
270
- */
271
- function laneStylerForColumn(
272
- colorColumn: number,
273
- colorize: boolean,
274
- style: MigrationListStyler,
275
- ): (text: string) => string {
276
- if (!colorize || colorColumn <= NEUTRAL_LANE) {
277
- return (text) => style.lane(text);
278
- }
279
- return laneColorForColumn(colorColumn);
280
- }
281
-
282
- /**
283
- * Tint a branch-owned token (direction arrow, migration name) by its edge's
284
- * lane so the whole branch row reads in one colour. Column 0 has nothing to be
285
- * told apart from in the common linear chain, so it keeps the token's existing
286
- * default styling (`fallback`) rather than a palette hue; only lanes ≥ 1 take a
287
- * colour. With colour off, the fallback (also colourless) is used unchanged.
288
- */
289
- function branchStylerOrDefault(
290
- column: number,
291
- colorize: boolean,
292
- fallback: (text: string) => string,
293
- ): (text: string) => string {
294
- if (!colorize || column <= NEUTRAL_LANE) {
295
- return fallback;
296
- }
297
- return laneColorForColumn(column);
298
- }
299
-
300
- /**
301
- * Render a connector tee (`├─` / `┬─` / `┴─`) with its junction glyph and its
302
- * trailing dash coloured independently: the junction anchors its own lane while
303
- * the dash leads into the branch on its right.
304
- */
305
- function renderConnectorTee(
306
- pair: string,
307
- glyphColumn: number,
308
- dashColumn: number,
309
- colorize: boolean,
310
- style: MigrationListStyler,
311
- ): string {
312
- const glyph = laneStylerForColumn(glyphColumn, colorize, style);
313
- if (glyphColumn === dashColumn) {
314
- return glyph(pair);
315
- }
316
- return glyph(pair.slice(0, 1)) + laneStylerForColumn(dashColumn, colorize, style)(pair.slice(1));
317
- }
318
-
319
119
  /**
320
120
  * A node-marker glyph pair (`○◂`, `○─`, `*<`, `*-`) is the contract node
321
- * marker (`○` / `*`) followed by an arc connector (`◂` / `─` / `<` / `-`). The
322
- * marker takes its own lane's hue (so each node visibly belongs to its branch);
323
- * the connector follows the arc it belongs to (its owning back-lane hue).
324
- * Direction arrows are handled elsewhere — they take their edge's lane hue too.
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.
325
125
  */
326
- function renderNodeMarkerPair(
327
- pair: string,
328
- nodeColumn: number,
329
- arcColumn: number,
330
- colorize: boolean,
331
- style: MigrationListStyler,
332
- ): string {
333
- const marker = laneStylerForColumn(nodeColumn, colorize, style);
334
- const connector = laneStylerForColumn(arcColumn, colorize, style);
335
- return marker(pair.slice(0, 1)) + connector(pair.slice(1));
126
+ function renderNodeMarkerPair(pair: string, style: MigrationListStyler): string {
127
+ return style.kind(pair.slice(0, 1)) + style.lane(pair.slice(1));
336
128
  }
337
129
 
338
130
  function renderCellPair(
339
131
  cell: StructuralCell,
340
- column: number,
341
- colors: RowLaneColors,
342
- colorize: boolean,
343
132
  style: MigrationListStyler,
344
133
  palette: MigrationGraphTreeGlyphPalette,
345
134
  ): string {
346
- const laneColumn = colors.lane[column] ?? column;
347
- const lane = laneStylerForColumn(laneColumn, colorize, style);
348
135
  switch (cell.kind) {
349
- case 'node': {
350
- const arcColumn = colors.connector[column] ?? NEUTRAL_LANE;
351
- if (cell.arcLand === true) {
352
- return renderNodeMarkerPair(palette.arcLand, column, arcColumn, colorize, style);
353
- }
354
- if (cell.arcTee === true) {
355
- return renderNodeMarkerPair(palette.arcTee, column, arcColumn, colorize, style);
356
- }
357
- return lane(palette.node);
358
- }
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);
359
140
  case 'vertical-pass':
360
- return lane(palette.verticalPass);
141
+ return style.lane(palette.verticalPass);
361
142
  case 'edge-lane':
143
+ // The lane stays dim; the direction arrow (↑ / ↓ / ⟲) is the signal and
144
+ // stays bright, like the contract-node marker.
362
145
  return cell.ownsLabel
363
- ? lane(palette.verticalPass.trimEnd()) +
364
- branchStylerOrDefault(
365
- column,
366
- colorize,
367
- style.kind,
368
- )(arrowForEdgeKind(cell.edgeKind, palette))
369
- : lane(palette.verticalPass);
146
+ ? style.lane(palette.verticalPass.trimEnd()) +
147
+ style.kind(arrowForEdgeKind(cell.edgeKind, palette))
148
+ : style.lane(palette.verticalPass);
370
149
  case 'branch-tee':
371
- return lane(palette.branchTee);
150
+ return style.lane(palette.branchTee);
372
151
  case 'merge-tee':
373
- return lane(palette.mergeTee);
152
+ return style.lane(palette.mergeTee);
374
153
  case 'branch-corner':
375
- return lane(palette.branchCorner);
154
+ return style.lane(palette.branchCorner);
376
155
  case 'merge-corner':
377
- return lane(palette.mergeCorner);
156
+ return style.lane(palette.mergeCorner);
378
157
  case 'arc-branch-corner':
379
- return lane(palette.arcBranchCorner);
158
+ return style.lane(palette.arcBranchCorner);
380
159
  case 'arc-branch-tee':
381
- return lane(palette.arcBranchTee);
160
+ return style.lane(palette.arcBranchTee);
382
161
  case 'arc-land-corner':
383
- return lane(palette.arcLandCorner);
162
+ return style.lane(palette.arcLandCorner);
384
163
  case 'arc-crossing':
385
- return lane(palette.arcLandBridge);
164
+ return style.lane(palette.arcCrossing);
386
165
  case 'arc-land-bridge':
387
- return lane(palette.arcLandBridge);
166
+ return style.lane(palette.arcLandBridge);
388
167
  case 'horizontal-pass':
389
- return lane(palette.horizontalPass);
168
+ return style.lane(palette.horizontalPass);
390
169
  case 'empty':
391
170
  return ' ';
392
171
  }
@@ -395,56 +174,34 @@ function renderCellPair(
395
174
  function renderConnectorRow(
396
175
  row: MigrationGraphGridRow,
397
176
  gridWidth: number,
398
- colorize: boolean,
399
177
  style: MigrationListStyler,
400
178
  palette: MigrationGraphTreeGlyphPalette,
401
179
  ): string {
402
180
  const isMerge = row.kind === 'merge-connector';
403
181
  if (row.cells.length > 0) {
404
- const colors = resolveConnectorLaneColors(row.cells, row.startLane ?? 0);
405
182
  let seenTee = false;
406
183
  let out = '';
407
- for (let column = 0; column < row.cells.length; column++) {
408
- const cell = row.cells[column];
409
- if (cell === undefined) continue;
410
- const glyphColumn = colors.glyph[column] ?? column;
411
- const dashColumn = colors.dash[column] ?? glyphColumn;
412
- const lane = laneStylerForColumn(glyphColumn, colorize, style);
184
+ for (const cell of row.cells) {
413
185
  switch (cell.kind) {
414
186
  case 'branch-tee':
415
- out += renderConnectorTee(
416
- seenTee ? palette.connectorBranchTeeCo : palette.connectorBranchTee,
417
- glyphColumn,
418
- dashColumn,
419
- colorize,
420
- style,
421
- );
187
+ out += style.lane(seenTee ? palette.connectorBranchTeeCo : palette.connectorBranchTee);
422
188
  seenTee = true;
423
189
  break;
424
190
  case 'merge-tee':
425
- out += renderConnectorTee(
426
- seenTee ? palette.connectorMergeTeeCo : palette.connectorBranchTee,
427
- glyphColumn,
428
- dashColumn,
429
- colorize,
430
- style,
431
- );
191
+ out += style.lane(seenTee ? palette.connectorMergeTeeCo : palette.connectorBranchTee);
432
192
  seenTee = true;
433
193
  break;
434
194
  case 'branch-corner':
435
- out += lane(palette.branchCorner);
195
+ out += style.lane(palette.branchCorner);
436
196
  break;
437
197
  case 'merge-corner':
438
- out += lane(palette.mergeCorner);
198
+ out += style.lane(palette.mergeCorner);
439
199
  break;
440
200
  case 'vertical-pass':
441
- out += lane(palette.verticalPass);
201
+ out += style.lane(palette.verticalPass);
442
202
  break;
443
203
  case 'horizontal-pass':
444
- out += lane(palette.horizontalPass);
445
- break;
446
- case 'arc-crossing':
447
- out += renderConnectorTee(palette.arcCrossing, glyphColumn, dashColumn, colorize, style);
204
+ out += style.lane(palette.horizontalPass);
448
205
  break;
449
206
  default:
450
207
  out += ' ';
@@ -461,15 +218,13 @@ function renderConnectorRow(
461
218
 
462
219
  const start = row.startLane ?? 0;
463
220
  const end = row.endLane ?? start;
464
- // The whole fork/merge run reads as one line in the served lane's hue (the
465
- // corner it reaches); pass-through columns outside the run keep their own.
466
- const runLane = laneStylerForColumn(end, colorize, style);
467
221
  let out = '';
468
222
  for (let column = 0; column < gridWidth; column++) {
469
223
  if (column < start || column > end) out += ' ';
470
- else if (column === start) out += runLane(palette.connectorBranchTee);
471
- else if (column === end) out += runLane(isMerge ? palette.mergeCorner : palette.branchCorner);
472
- else out += runLane(isMerge ? palette.connectorMergeTeeCo : palette.connectorBranchTeeCo);
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);
473
228
  }
474
229
  return out;
475
230
  }
@@ -542,13 +297,6 @@ function padVisible(text: string, targetWidth: number): string {
542
297
  return text + ' '.repeat(padding);
543
298
  }
544
299
 
545
- const ANSI_ESCAPE = '\x1b';
546
-
547
- function trimTrailingWhitespace(line: string): string {
548
- const trailingSpaceBeforeReset = new RegExp(`[\\t ]+((?:${ANSI_ESCAPE}\\[[0-9;]*m)+)$`);
549
- return line.replace(trailingSpaceBeforeReset, '$1').replace(/\s+$/, '');
550
- }
551
-
552
300
  function gridWidthForModel(rows: readonly MigrationGraphGridRow[]): number {
553
301
  return rows.reduce(
554
302
  (max, row) =>
@@ -625,32 +373,19 @@ export function renderMigrationGraphTree(
625
373
  }
626
374
 
627
375
  if (row.kind === 'branch-connector' || row.kind === 'merge-connector') {
628
- lines.push(
629
- trimTrailingWhitespace(renderConnectorRow(row, gridWidth, opts.colorize, style, palette)),
630
- );
376
+ lines.push(renderConnectorRow(row, gridWidth, style, palette).replace(/\s+$/, ''));
631
377
  continue;
632
378
  }
633
379
 
634
- const cellColors = resolveRowLaneColors(row.cells);
635
- let gutter = row.cells
636
- .map((cell, column) =>
637
- renderCellPair(cell, column, cellColors, opts.colorize, style, palette),
638
- )
639
- .join('');
380
+ let gutter = row.cells.map((cell) => renderCellPair(cell, style, palette)).join('');
381
+ const prevRow = model.rows[rowIndex - 1];
640
382
  let laneSpan = row.cells.length;
641
383
  if (row.kind === 'node') {
642
384
  const contractHash = row.contractHash ?? EMPTY_CONTRACT_HASH;
643
- if (contractHash === EMPTY_CONTRACT_HASH) {
385
+ if (prevRow?.kind === 'merge-connector' || contractHash === EMPTY_CONTRACT_HASH) {
644
386
  laneSpan = 1;
645
387
  } else {
646
- let lastActiveColumn = -1;
647
- for (let column = row.cells.length - 1; column >= 0; column--) {
648
- if (row.cells[column]?.kind !== 'empty') {
649
- lastActiveColumn = column;
650
- break;
651
- }
652
- }
653
- laneSpan = lastActiveColumn >= 0 ? lastActiveColumn + 1 : 1;
388
+ laneSpan = row.cells.length;
654
389
  }
655
390
  }
656
391
  const labelColumn =
@@ -667,16 +402,12 @@ export function renderMigrationGraphTree(
667
402
  ) {
668
403
  gutter = row.cells
669
404
  .slice(0, 1)
670
- .map((cell, column) =>
671
- renderCellPair(cell, column, cellColors, opts.colorize, style, palette),
672
- )
405
+ .map((cell) => renderCellPair(cell, style, palette))
673
406
  .join('');
674
407
  } else if (row.kind === 'node' && laneSpan < row.cells.length && !nodeHasArcDecoration(row)) {
675
408
  gutter = row.cells
676
409
  .slice(0, laneSpan)
677
- .map((cell, column) =>
678
- renderCellPair(cell, column, cellColors, opts.colorize, style, palette),
679
- )
410
+ .map((cell) => renderCellPair(cell, style, palette))
680
411
  .join('');
681
412
  } else if (gutter.length < laneSpan * 2) {
682
413
  gutter = gutter.padEnd(laneSpan * 2, ' ');
@@ -690,18 +421,16 @@ export function renderMigrationGraphTree(
690
421
  if (contractHash === EMPTY_CONTRACT_HASH) {
691
422
  const trailingLanes = row.cells
692
423
  .slice(1)
693
- .map((cell, offset) =>
694
- renderCellPair(cell, offset + 1, cellColors, opts.colorize, style, palette),
695
- )
424
+ .map((cell) => renderCellPair(cell, style, palette))
696
425
  .join('');
697
426
  const emptyGutter = palette.emptySource.padEnd(2, ' ') + trailingLanes;
698
427
  const overlayNames = overlayNamesForContract(contractHash, opts);
699
428
  if (overlayNames.length === 0) {
700
- lines.push(trimTrailingWhitespace(emptyGutter));
429
+ lines.push(emptyGutter.replace(/\s+$/, ''));
701
430
  continue;
702
431
  }
703
432
  const overlay = style.refs(overlayNames);
704
- lines.push(trimTrailingWhitespace(`${padVisible(emptyGutter, dataColumn)}${overlay}`));
433
+ lines.push(`${padVisible(emptyGutter, dataColumn)}${overlay}`.replace(/\s+$/, ''));
705
434
  continue;
706
435
  }
707
436
  const hashText = style.sourceHash(
@@ -713,7 +442,7 @@ export function renderMigrationGraphTree(
713
442
  ? ' '.repeat(Math.max(0, dataColumn - labelColumn - stringWidth(hashText)))
714
443
  : '';
715
444
  const overlay = overlayNames.length > 0 ? style.refs(overlayNames) : '';
716
- lines.push(trimTrailingWhitespace(`${gutterPad}${hashText}${overlayPad}${overlay}`));
445
+ lines.push(`${gutterPad}${hashText}${overlayPad}${overlay}`.replace(/\s+$/, ''));
717
446
  continue;
718
447
  }
719
448
 
@@ -721,48 +450,10 @@ export function renderMigrationGraphTree(
721
450
  if (edge === undefined) continue;
722
451
 
723
452
  const dirNamePadding = ' '.repeat(Math.max(0, dirNameWidth - edge.dirName.length));
724
- const laneIndex = row.laneIndex ?? 0;
725
- // A branched name keeps its bold (via `style.dirName`) and adds the lane
726
- // hue, so it reads as one with its lane/arrow; column-0 names stay bold-only.
727
- const dirNameStyler =
728
- opts.colorize && laneIndex > NEUTRAL_LANE
729
- ? (text: string) => forcedBold(laneColorForColumn(laneIndex)(text))
730
- : style.dirName;
731
- const dirName = `${dirNameStyler(edge.dirName)}${dirNamePadding}`;
453
+ const dirName = `${style.dirName(edge.dirName)}${dirNamePadding}`;
732
454
  const hashColumn = formatEdgeHashColumn(edge, style, hashLength, palette);
733
- lines.push(trimTrailingWhitespace(`${gutterPad}${dirName}${hashColumn}`));
455
+ lines.push(`${gutterPad}${dirName}${hashColumn}`.replace(/\s+$/, ''));
734
456
  }
735
457
 
736
458
  return lines.join('\n');
737
459
  }
738
-
739
- export interface RenderMigrationGraphLegendOptions {
740
- readonly colorize: boolean;
741
- readonly glyphMode?: GlyphMode;
742
- }
743
-
744
- /**
745
- * A compact key for the `--tree` visual language: the contract marker, the
746
- * in-lane direction arrows, the empty baseline, the `(refs)` overlay (including
747
- * the reserved `db` live-database and `contract` working-schema markers), and a
748
- * worked sample of the data-column `from → to` migration hash arrow.
749
- *
750
- * Honors the same glyph palette (unicode vs ASCII) and `colorize` gate as the
751
- * tree renderer, so the key matches whatever the graph itself drew and stays
752
- * pipe-safe (zero ANSI when color is off). The caller adds the trailing blank
753
- * line that separates this stderr key from the graph on stdout.
754
- */
755
- export function renderMigrationGraphLegend(opts: RenderMigrationGraphLegendOptions): string {
756
- const palette = paletteFor(opts.glyphMode ?? 'unicode');
757
- const style = createAnsiMigrationListStyler({ useColor: opts.colorize });
758
- const node = palette.node.trimEnd();
759
- const sampleArrow = `${style.sourceHash('aaaaaa')} ${style.glyph(palette.forwardArrow)} ${style.destHash('bbbbbb')}`;
760
- return [
761
- 'Legend:',
762
- ` ${style.kind(node)} contract ${style.kind(palette.edgeArrow.forward)} forward ${style.kind(palette.edgeArrow.rollback)} rollback`,
763
- ` ${style.kind(palette.edgeArrow.self)} migration without schema change`,
764
- ` ${style.glyph(palette.emptySource)} empty database (baseline)`,
765
- ` ${style.refs(['refs'])} ${DB_MARKER_NAME} / ${CONTRACT_MARKER_NAME} markers`,
766
- ` ${sampleArrow} migration from contract aaaaaa to bbbbbb`,
767
- ].join('\n');
768
- }
@@ -180,38 +180,3 @@ export function parseGlobalFlags(options: CommonCommandOptions): GlobalFlags {
180
180
 
181
181
  return flags as GlobalFlags;
182
182
  }
183
-
184
- /**
185
- * Bridges the two TTY checks (stdout via `flags`, stdin via
186
- * `process.stdin.isTTY`) into the `canPrompt` boolean the interactive
187
- * `init` flow consumes.
188
- *
189
- * Per the [Style Guide § Interactivity](../../../../../../../docs/CLI%20Style%20Guide.md#interactivity):
190
- *
191
- * - `flags.interactive` governs *decoration* (TerminalUI, intro/outro,
192
- * spinners) and is derived from stdout-TTY by `parseGlobalFlags`,
193
- * honouring `--interactive` / `--no-interactive`.
194
- * - Prompting additionally requires a stdin TTY — closing stdin is a
195
- * common signal in CI / agent environments even when stdout stays
196
- * attached.
197
- * - `--interactive` is the explicit override: when the user passes it,
198
- * we honour it (e.g. testing flows where stdin is stubbed).
199
- *
200
- * Single source of truth for the interactive-prompt decision: both the
201
- * `init` action handler and the preAction telemetry bridge derive
202
- * prompt-eligibility from this helper so they cannot drift. Lives in
203
- * `global-flags` (alongside `parseGlobalFlags`) to keep
204
- * `utils/telemetry` and `commands/init/index` free of an import cycle.
205
- *
206
- * Exported so callers and tests can derive the same value without
207
- * touching `process` globals.
208
- */
209
- export function deriveCanPrompt(opts: {
210
- readonly flagsInteractive: boolean | undefined;
211
- readonly optionInteractive: boolean | undefined;
212
- readonly stdinIsTTY: boolean;
213
- }): boolean {
214
- if (opts.optionInteractive === true) return true;
215
- if (opts.flagsInteractive === false) return false;
216
- return opts.stdinIsTTY;
217
- }