@prisma-next/cli 0.12.0 → 0.13.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 (213) 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-CJzuo5wX.mjs} +222 -107
  5. package/dist/client-CJzuo5wX.mjs.map +1 -0
  6. package/dist/{command-helpers-Bbw1GbwL.mjs → command-helpers-DGMvGBeX.mjs} +318 -25
  7. package/dist/command-helpers-DGMvGBeX.mjs.map +1 -0
  8. package/dist/commands/contract-emit.d.mts.map +1 -1
  9. package/dist/commands/contract-emit.mjs +1 -1
  10. package/dist/commands/contract-infer.d.mts.map +1 -1
  11. package/dist/commands/contract-infer.mjs +1 -1
  12. package/dist/commands/db-init.d.mts.map +1 -1
  13. package/dist/commands/db-init.mjs +4 -5
  14. package/dist/commands/db-init.mjs.map +1 -1
  15. package/dist/commands/db-schema.d.mts.map +1 -1
  16. package/dist/commands/db-schema.mjs +3 -3
  17. package/dist/commands/db-schema.mjs.map +1 -1
  18. package/dist/commands/db-sign.d.mts.map +1 -1
  19. package/dist/commands/db-sign.mjs +6 -6
  20. package/dist/commands/db-sign.mjs.map +1 -1
  21. package/dist/commands/db-update.d.mts.map +1 -1
  22. package/dist/commands/db-update.mjs +10 -7
  23. package/dist/commands/db-update.mjs.map +1 -1
  24. package/dist/commands/db-verify.d.mts.map +1 -1
  25. package/dist/commands/db-verify.mjs +1 -1
  26. package/dist/commands/migrate.d.mts +37 -3
  27. package/dist/commands/migrate.d.mts.map +1 -1
  28. package/dist/commands/migrate.mjs +298 -12
  29. package/dist/commands/migrate.mjs.map +1 -1
  30. package/dist/commands/migration-check.d.mts +55 -13
  31. package/dist/commands/migration-check.d.mts.map +1 -1
  32. package/dist/commands/migration-check.mjs +3 -2
  33. package/dist/commands/migration-graph.d.mts +17 -8
  34. package/dist/commands/migration-graph.d.mts.map +1 -1
  35. package/dist/commands/migration-graph.mjs +185 -2
  36. package/dist/commands/migration-graph.mjs.map +1 -0
  37. package/dist/commands/migration-list.d.mts +26 -27
  38. package/dist/commands/migration-list.d.mts.map +1 -1
  39. package/dist/commands/migration-list.mjs +2 -190
  40. package/dist/commands/migration-log.d.mts +9 -19
  41. package/dist/commands/migration-log.d.mts.map +1 -1
  42. package/dist/commands/migration-log.mjs +1 -137
  43. package/dist/commands/migration-new.d.mts.map +1 -1
  44. package/dist/commands/migration-new.mjs +6 -5
  45. package/dist/commands/migration-new.mjs.map +1 -1
  46. package/dist/commands/migration-plan.d.mts +1 -1
  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 +17 -21
  50. package/dist/commands/migration-show.d.mts.map +1 -1
  51. package/dist/commands/migration-show.mjs +24 -36
  52. package/dist/commands/migration-show.mjs.map +1 -1
  53. package/dist/commands/migration-status.d.mts +42 -144
  54. package/dist/commands/migration-status.d.mts.map +1 -1
  55. package/dist/commands/migration-status.mjs +3 -759
  56. package/dist/commands/ref.d.mts +1 -1
  57. package/dist/commands/ref.d.mts.map +1 -1
  58. package/dist/commands/ref.mjs +4 -4
  59. package/dist/commands/ref.mjs.map +1 -1
  60. package/dist/commands/telemetry/index.d.mts +7 -0
  61. package/dist/commands/telemetry/index.d.mts.map +1 -0
  62. package/dist/commands/telemetry/index.mjs +2 -0
  63. package/dist/{config-loader-B6sJjXTv.mjs → config-loader-p9JMrekQ.mjs} +1 -1
  64. package/dist/{config-loader-B6sJjXTv.mjs.map → config-loader-p9JMrekQ.mjs.map} +1 -1
  65. package/dist/config-loader.mjs +1 -1
  66. package/dist/{contract-at-errors-BxP-TOMl.mjs → contract-at-errors-CFXsstzm.mjs} +2 -2
  67. package/dist/{contract-at-errors-BxP-TOMl.mjs.map → contract-at-errors-CFXsstzm.mjs.map} +1 -1
  68. package/dist/{contract-emit-DxcGl4Uq.mjs → contract-emit-B_qriF8B.mjs} +5 -5
  69. package/dist/{contract-emit-DxcGl4Uq.mjs.map → contract-emit-B_qriF8B.mjs.map} +1 -1
  70. package/dist/{contract-emit-D-4jrNve.mjs → contract-emit-C8HmtboH.mjs} +12 -7
  71. package/dist/contract-emit-C8HmtboH.mjs.map +1 -0
  72. package/dist/{contract-enrichment-a0V5Y_mL.mjs → contract-enrichment-gn9sWbPw.mjs} +1 -1
  73. package/dist/{contract-enrichment-a0V5Y_mL.mjs.map → contract-enrichment-gn9sWbPw.mjs.map} +1 -1
  74. package/dist/{contract-infer-D8uEbJuu.mjs → contract-infer-BYT_ra_U.mjs} +5 -5
  75. package/dist/contract-infer-BYT_ra_U.mjs.map +1 -0
  76. package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs → contract-space-aggregate-loader-ClI1KN6d.mjs} +5 -5
  77. package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs.map → contract-space-aggregate-loader-ClI1KN6d.mjs.map} +1 -1
  78. package/dist/{db-verify-v_vUKXTU.mjs → db-verify-C24FKhb7.mjs} +6 -6
  79. package/dist/{db-verify-v_vUKXTU.mjs.map → db-verify-C24FKhb7.mjs.map} +1 -1
  80. package/dist/exports/control-api.d.mts +5 -3
  81. package/dist/exports/control-api.d.mts.map +1 -1
  82. package/dist/exports/control-api.mjs +3 -3
  83. package/dist/exports/index.mjs +1 -1
  84. package/dist/exports/index.mjs.map +1 -1
  85. package/dist/exports/init-output.d.mts +1 -3
  86. package/dist/exports/init-output.d.mts.map +1 -1
  87. package/dist/exports/init-output.mjs +1 -1
  88. package/dist/{extension-pack-inputs-IDvjRCi3.mjs → extension-pack-inputs-1ySHqxKG.mjs} +1 -1
  89. package/dist/{extension-pack-inputs-IDvjRCi3.mjs.map → extension-pack-inputs-1ySHqxKG.mjs.map} +1 -1
  90. package/dist/{framework-components-fYXjz_in.mjs → framework-components-YVQHhPH7.mjs} +2 -2
  91. package/dist/{framework-components-fYXjz_in.mjs.map → framework-components-YVQHhPH7.mjs.map} +1 -1
  92. package/dist/{global-flags-DEHjV8_s.d.mts → global-flags-BpoOYtNZ.d.mts} +1 -1
  93. package/dist/{global-flags-DEHjV8_s.d.mts.map → global-flags-BpoOYtNZ.d.mts.map} +1 -1
  94. package/dist/{init-Cv9UzWL5.mjs → init-0HwB-Vh8.mjs} +5 -58
  95. package/dist/init-0HwB-Vh8.mjs.map +1 -0
  96. package/dist/{inspect-live-schema-C6ohV_oQ.mjs → inspect-live-schema-DF6IwcDl.mjs} +7 -5
  97. package/dist/inspect-live-schema-DF6IwcDl.mjs.map +1 -0
  98. package/dist/migration-check-soB5uZEQ.mjs +573 -0
  99. package/dist/migration-check-soB5uZEQ.mjs.map +1 -0
  100. package/dist/migration-cli.mjs +1 -1
  101. package/dist/migration-cli.mjs.map +1 -1
  102. package/dist/{migration-command-scaffold-CjvwO6at.mjs → migration-command-scaffold-DA-Lhx6o.mjs} +5 -5
  103. package/dist/{migration-command-scaffold-CjvwO6at.mjs.map → migration-command-scaffold-DA-Lhx6o.mjs.map} +1 -1
  104. package/dist/migration-graph-command-render-CEez7YUK.mjs +1960 -0
  105. package/dist/migration-graph-command-render-CEez7YUK.mjs.map +1 -0
  106. package/dist/migration-list-DlJJ_38Z.mjs +230 -0
  107. package/dist/migration-list-DlJJ_38Z.mjs.map +1 -0
  108. package/dist/migration-log-CG0qQAFm.mjs +222 -0
  109. package/dist/migration-log-CG0qQAFm.mjs.map +1 -0
  110. package/dist/migration-path-target-Ce6OZImp.mjs +38 -0
  111. package/dist/migration-path-target-Ce6OZImp.mjs.map +1 -0
  112. package/dist/{migration-plan-9DJ7q7_z.mjs → migration-plan-z5Ing-TD.mjs} +9 -8
  113. package/dist/migration-plan-z5Ing-TD.mjs.map +1 -0
  114. package/dist/migration-status-CgWSoI_g.mjs +446 -0
  115. package/dist/migration-status-CgWSoI_g.mjs.map +1 -0
  116. package/dist/{output-B60Gw5fu.mjs → output-mEQ74_nd.mjs} +1 -1
  117. package/dist/{output-B60Gw5fu.mjs.map → output-mEQ74_nd.mjs.map} +1 -1
  118. package/dist/{progress-adapter-C644QK8l.mjs → progress-adapter-CjAeTxY_.mjs} +1 -1
  119. package/dist/{progress-adapter-C644QK8l.mjs.map → progress-adapter-CjAeTxY_.mjs.map} +1 -1
  120. package/dist/{ref-advancement-DUZqsue6.mjs → ref-advancement-BkXlikCA.mjs} +1 -1
  121. package/dist/{ref-advancement-DUZqsue6.mjs.map → ref-advancement-BkXlikCA.mjs.map} +1 -1
  122. package/dist/schemas-CeGMYFYX.d.mts +191 -0
  123. package/dist/schemas-CeGMYFYX.d.mts.map +1 -0
  124. package/dist/schemas-KhXMzNA_.mjs +112 -0
  125. package/dist/schemas-KhXMzNA_.mjs.map +1 -0
  126. package/dist/telemetry-BIM4beEO.mjs +122 -0
  127. package/dist/telemetry-BIM4beEO.mjs.map +1 -0
  128. package/dist/{terminal-ui-5Y6mrg93.d.mts → terminal-ui-DGRNFWna.d.mts} +1 -1
  129. package/dist/terminal-ui-DGRNFWna.d.mts.map +1 -0
  130. package/dist/{types-Dt_SfqFm.d.mts → types-C_tYiJYx.d.mts} +53 -31
  131. package/dist/types-C_tYiJYx.d.mts.map +1 -0
  132. package/dist/{verify-DCA9Sldu.mjs → verify-DcOYZ1tH.mjs} +2 -2
  133. package/dist/{verify-DCA9Sldu.mjs.map → verify-DcOYZ1tH.mjs.map} +1 -1
  134. package/package.json +26 -22
  135. package/src/cli.ts +5 -0
  136. package/src/commands/contract-infer.ts +2 -2
  137. package/src/commands/db-update.ts +7 -1
  138. package/src/commands/init/index.ts +6 -35
  139. package/src/commands/init/init.ts +1 -14
  140. package/src/commands/init/inputs.ts +0 -75
  141. package/src/commands/inspect-live-schema.ts +10 -0
  142. package/src/commands/json/schemas.ts +195 -0
  143. package/src/commands/migrate.ts +527 -8
  144. package/src/commands/migration-check.ts +469 -134
  145. package/src/commands/migration-graph.ts +164 -91
  146. package/src/commands/migration-list.ts +72 -39
  147. package/src/commands/migration-log.ts +52 -102
  148. package/src/commands/migration-new.ts +2 -1
  149. package/src/commands/migration-plan.ts +2 -1
  150. package/src/commands/migration-show.ts +31 -66
  151. package/src/commands/migration-status-overlay.ts +61 -0
  152. package/src/commands/migration-status.ts +458 -1066
  153. package/src/commands/telemetry/index.ts +107 -0
  154. package/src/commands/telemetry/status.ts +67 -0
  155. package/src/control-api/client.ts +70 -9
  156. package/src/control-api/operations/contract-emit.ts +22 -2
  157. package/src/control-api/operations/db-init.ts +6 -3
  158. package/src/control-api/operations/{db-apply.ts → db-run.ts} +55 -14
  159. package/src/control-api/operations/db-update.ts +7 -4
  160. package/src/control-api/operations/db-verify.ts +15 -5
  161. package/src/control-api/operations/{migration-apply.ts → migrate.ts} +181 -80
  162. package/src/control-api/operations/{apply.ts → run-migration.ts} +33 -27
  163. package/src/control-api/types.ts +56 -29
  164. package/src/utils/cli-errors.ts +70 -2
  165. package/src/utils/formatters/errors.ts +11 -0
  166. package/src/utils/formatters/migration-graph-command-render.ts +239 -0
  167. package/src/utils/formatters/migration-graph-grid-layout.ts +1134 -0
  168. package/src/utils/formatters/migration-graph-labels.ts +408 -0
  169. package/src/utils/formatters/migration-graph-model.ts +103 -0
  170. package/src/utils/formatters/migration-graph-occlusion-render.ts +258 -0
  171. package/src/utils/formatters/migration-graph-rows.ts +128 -15
  172. package/src/utils/formatters/migration-graph-space-render.ts +188 -0
  173. package/src/utils/formatters/migration-list-data-column.ts +4 -91
  174. package/src/utils/formatters/migration-list-graph-topology.ts +72 -94
  175. package/src/utils/formatters/migration-list-render.ts +135 -71
  176. package/src/utils/formatters/migration-list-styler.ts +46 -5
  177. package/src/utils/formatters/migration-list-types.ts +5 -21
  178. package/src/utils/formatters/migration-log-table.ts +205 -0
  179. package/src/utils/formatters/migrations.ts +33 -11
  180. package/src/utils/global-flags.ts +35 -0
  181. package/src/utils/integrity-violation-to-check-failure.ts +28 -19
  182. package/src/utils/legend.ts +38 -0
  183. package/src/utils/migration-path-target.ts +60 -0
  184. package/src/utils/telemetry.ts +68 -32
  185. package/dist/client-KgJorIvG.mjs.map +0 -1
  186. package/dist/command-helpers-Bbw1GbwL.mjs.map +0 -1
  187. package/dist/commands/migration-list.mjs.map +0 -1
  188. package/dist/commands/migration-log.mjs.map +0 -1
  189. package/dist/commands/migration-status.mjs.map +0 -1
  190. package/dist/contract-emit-D-4jrNve.mjs.map +0 -1
  191. package/dist/contract-infer-D8uEbJuu.mjs.map +0 -1
  192. package/dist/graph-render-rFAqZujX.mjs +0 -1081
  193. package/dist/graph-render-rFAqZujX.mjs.map +0 -1
  194. package/dist/init-Cv9UzWL5.mjs.map +0 -1
  195. package/dist/inspect-live-schema-C6ohV_oQ.mjs.map +0 -1
  196. package/dist/migration-check-BiBJoYYW.mjs +0 -341
  197. package/dist/migration-check-BiBJoYYW.mjs.map +0 -1
  198. package/dist/migration-graph-D7DVUElV.mjs +0 -1232
  199. package/dist/migration-graph-D7DVUElV.mjs.map +0 -1
  200. package/dist/migration-list-styler-BRwF4-gy.mjs +0 -399
  201. package/dist/migration-list-styler-BRwF4-gy.mjs.map +0 -1
  202. package/dist/migration-plan-9DJ7q7_z.mjs.map +0 -1
  203. package/dist/migration-types-D2FW63pr.d.mts +0 -15
  204. package/dist/migration-types-D2FW63pr.d.mts.map +0 -1
  205. package/dist/migrations-Cv2jxNNK.mjs +0 -228
  206. package/dist/migrations-Cv2jxNNK.mjs.map +0 -1
  207. package/dist/terminal-ui-5Y6mrg93.d.mts.map +0 -1
  208. package/dist/types-Dt_SfqFm.d.mts.map +0 -1
  209. package/src/utils/formatters/graph-migration-mapper.ts +0 -235
  210. package/src/utils/formatters/graph-render.ts +0 -1323
  211. package/src/utils/formatters/graph-types.ts +0 -120
  212. package/src/utils/formatters/migration-graph-layout.ts +0 -1119
  213. package/src/utils/formatters/migration-graph-tree-render.ts +0 -459
@@ -0,0 +1,408 @@
1
+ /**
2
+ * Per-row label formatting for the command graph renderer.
3
+ *
4
+ * The command graph renderer ({@link renderMigrationGraphCommand}) derives the
5
+ * graph structure — rows, gutter, lane colours — from the grid pipeline. The
6
+ * per-row LABEL (contract hash + markers + refs for node rows;
7
+ * migration name + `from → to` + ops/status/will-run for migration rows) is
8
+ * formatted here. This module owns ONLY label text + styling; it knows nothing
9
+ * about lanes, gutters, or grid geometry.
10
+ *
11
+ * The label format (hash abbreviation, `from → to` arrow column, `@contract`/
12
+ * `@db` markers, `(refs)`, ops/status/will-run suffix, the legend) is the same
13
+ * as the previous renderer — that part was never the bug.
14
+ */
15
+
16
+ import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
17
+ import { bold, createColors, green, yellow } from 'colorette';
18
+ import type { GlyphMode } from '../glyph-mode';
19
+ import { laneColorizer } from './migration-graph-occlusion-render';
20
+ import type { ClassifiedEdge } from './migration-graph-rows';
21
+ import {
22
+ MIGRATION_LIST_HASH_WIDTH,
23
+ migrationListEmptySource,
24
+ migrationListForwardArrow,
25
+ padFromHashColumn,
26
+ } from './migration-list-data-column';
27
+ import type { MigrationEdgeKind } from './migration-list-graph-topology';
28
+ import type { MigrationListStyler } from './migration-list-render';
29
+ import {
30
+ CONTRACT_MARKER_NAME,
31
+ createAnsiMigrationListStyler,
32
+ formatContractNodeOverlays,
33
+ } from './migration-list-styler';
34
+
35
+ /**
36
+ * The live-database overlay marker. Just another ref as far as styling goes —
37
+ * the only emphasized markers are the active ref and the `contract`
38
+ * desired-state marker (see {@link CONTRACT_MARKER_NAME}).
39
+ */
40
+ const DB_MARKER_NAME = 'db';
41
+
42
+ export interface MigrationEdgeAnnotation {
43
+ readonly status?: 'applied' | 'pending';
44
+ readonly operationCount?: number;
45
+ readonly invariants?: readonly string[];
46
+ /**
47
+ * Path-highlight annotation for `migrate --show` preview.
48
+ * - `'on-path'`: migration is on the chosen path; rendered in bright green.
49
+ * - `'off-path'`: migration is off the chosen path; fully drawn but dim grey.
50
+ */
51
+ readonly pathHighlight?: 'on-path' | 'off-path';
52
+ }
53
+
54
+ /**
55
+ * Inputs that drive label formatting. A subset of the command renderer's
56
+ * options — everything the label functions read.
57
+ */
58
+ export interface MigrationGraphLabelOptions {
59
+ readonly refsByHash?: ReadonlyMap<string, readonly string[]>;
60
+ readonly edgeAnnotationsByHash?: ReadonlyMap<string, MigrationEdgeAnnotation>;
61
+ readonly dbHash?: string;
62
+ readonly contractHash?: string;
63
+ readonly isAppSpace?: boolean;
64
+ readonly activeRefName?: string;
65
+ readonly hashLength?: number;
66
+ readonly colorize: boolean;
67
+ readonly glyphMode?: GlyphMode;
68
+ readonly styler?: MigrationListStyler;
69
+ }
70
+
71
+ /**
72
+ * Forced-color functions that always emit ANSI regardless of the ambient TTY
73
+ * environment (NO_COLOR, piped output). Used so on-path green / off-path dim are
74
+ * deterministically emitted in tests that request colour while NO_COLOR is set.
75
+ */
76
+ const { dim: forcedDim } = createColors({ useColor: true });
77
+ const { greenBright: forcedGreen } = createColors({ useColor: true });
78
+
79
+ /**
80
+ * The two label styles used in `migrate --show` path-highlight mode.
81
+ *
82
+ * - `onPath`: bold name, neutral hashes (the on-path lane glyphs are coloured
83
+ * green by the grid renderer, not here).
84
+ * - `offPath`: uniform dim grey on the name and the whole hash column.
85
+ *
86
+ * To change the on-path / off-path label colour in future, edit this object.
87
+ */
88
+ export const PATH_HIGHLIGHT_STYLES = {
89
+ onPath: (_style: MigrationListStyler, colorize: boolean) => ({
90
+ lane: colorize ? forcedGreen : (text: string) => text,
91
+ arrow: (text: string) => text,
92
+ dirName: (text: string) => bold(text),
93
+ hashOverride: undefined,
94
+ }),
95
+ offPath: (colorize: boolean) => ({
96
+ lane: colorize ? forcedDim : (text: string) => text,
97
+ arrow: colorize ? forcedDim : (text: string) => text,
98
+ dirName: colorize ? forcedDim : (text: string) => text,
99
+ hashOverride: colorize ? forcedDim : undefined,
100
+ }),
101
+ } as const;
102
+
103
+ function abbreviateHash(hash: string, hashLength: number, emptySource: string): string {
104
+ if (hash === EMPTY_CONTRACT_HASH) {
105
+ return emptySource;
106
+ }
107
+ const stripped = hash.startsWith('sha256:') ? hash.slice(7) : hash;
108
+ return stripped.slice(0, hashLength);
109
+ }
110
+
111
+ interface ContractOverlayNames {
112
+ readonly markers: readonly string[];
113
+ readonly refs: readonly string[];
114
+ }
115
+
116
+ function overlayNamesForContract(
117
+ contractHash: string,
118
+ opts: MigrationGraphLabelOptions,
119
+ ): ContractOverlayNames {
120
+ const markers: string[] = [];
121
+ const refs: string[] = [];
122
+ const userRefs = opts.refsByHash?.get(contractHash);
123
+ if (userRefs) {
124
+ refs.push(...[...userRefs].sort((a, b) => a.localeCompare(b)));
125
+ }
126
+ if (
127
+ opts.isAppSpace !== false &&
128
+ opts.contractHash === contractHash &&
129
+ contractHash !== EMPTY_CONTRACT_HASH
130
+ ) {
131
+ markers.push(CONTRACT_MARKER_NAME);
132
+ }
133
+ if (opts.dbHash === contractHash) {
134
+ markers.push(DB_MARKER_NAME);
135
+ }
136
+ markers.sort((a, b) => {
137
+ if (a === CONTRACT_MARKER_NAME) return -1;
138
+ if (b === CONTRACT_MARKER_NAME) return 1;
139
+ return a.localeCompare(b);
140
+ });
141
+ return { markers, refs };
142
+ }
143
+
144
+ export function createLabelStyler(opts: MigrationGraphLabelOptions): MigrationListStyler {
145
+ const base = opts.styler ?? createAnsiMigrationListStyler({ useColor: opts.colorize });
146
+ const activeRefName = opts.activeRefName;
147
+ if (!opts.colorize || activeRefName === undefined) {
148
+ return base;
149
+ }
150
+ return {
151
+ ...base,
152
+ refs: (names) => {
153
+ const styledNames = names.map((name) => (name === activeRefName ? bold(name) : name));
154
+ return base.refs(styledNames);
155
+ },
156
+ };
157
+ }
158
+
159
+ function overlayStatusGlyphs(mode: GlyphMode): {
160
+ readonly applied: string;
161
+ readonly pending: string;
162
+ } {
163
+ return mode === 'ascii' ? { applied: '+', pending: '>' } : { applied: '✓', pending: '⧗' };
164
+ }
165
+
166
+ function formatEdgeAnnotationSuffix(
167
+ migrationHash: string,
168
+ opts: MigrationGraphLabelOptions,
169
+ style: MigrationListStyler,
170
+ ): string {
171
+ const annotation = opts.edgeAnnotationsByHash?.get(migrationHash);
172
+ if (annotation === undefined) {
173
+ return '';
174
+ }
175
+ const isOffPath = annotation.pathHighlight === 'off-path';
176
+ const segments: string[] = [];
177
+ if (annotation.operationCount !== undefined) {
178
+ segments.push(`${annotation.operationCount} ops`);
179
+ }
180
+ if (annotation.invariants !== undefined && annotation.invariants.length > 0) {
181
+ segments.push(style.invariants(annotation.invariants));
182
+ }
183
+ const status = annotation.status;
184
+ if (status !== undefined) {
185
+ const glyphs = overlayStatusGlyphs(opts.glyphMode ?? 'unicode');
186
+ const glyph = status === 'applied' ? glyphs.applied : glyphs.pending;
187
+ const label = status === 'applied' ? 'applied' : 'pending';
188
+ if (!opts.colorize) {
189
+ segments.push(`${glyph} ${label}`);
190
+ } else {
191
+ const styler = status === 'applied' ? green : yellow;
192
+ segments.push(styler(`${glyph} ${label}`));
193
+ }
194
+ }
195
+ if (annotation.pathHighlight === 'on-path') {
196
+ const glyph = opts.glyphMode === 'ascii' ? '>' : '↑';
197
+ segments.push(`${glyph} will run`);
198
+ }
199
+ if (segments.length === 0) {
200
+ return '';
201
+ }
202
+ const suffix = ` ${segments.join(' ')}`;
203
+ return opts.colorize && isOffPath ? forcedDim(suffix) : suffix;
204
+ }
205
+
206
+ /**
207
+ * Format the `from → to` hash data column for an edge row.
208
+ *
209
+ * When `hashOverride` is provided (off-path → `dim`), it replaces ALL sub-stylers
210
+ * so dim reaches every character without inner ANSI codes overriding it.
211
+ */
212
+ function formatEdgeHashColumn(
213
+ edge: ClassifiedEdge,
214
+ style: MigrationListStyler,
215
+ hashLength: number,
216
+ glyphMode: GlyphMode,
217
+ hashOverride?: (text: string) => string,
218
+ ): string {
219
+ const emptySource = migrationListEmptySource(glyphMode);
220
+ const forwardArrow = migrationListForwardArrow(glyphMode);
221
+ const src = hashOverride ?? style.sourceHash;
222
+ const dst = hashOverride ?? style.destHash;
223
+ const glyph = hashOverride ?? style.glyph;
224
+ if (edge.kind === 'self') {
225
+ const hash = abbreviateHash(edge.from, hashLength, emptySource);
226
+ const source = padFromHashColumn(src(hash), hashLength);
227
+ return `${source} ${glyph(forwardArrow)} ${dst(hash)}`;
228
+ }
229
+ const source =
230
+ edge.from === EMPTY_CONTRACT_HASH
231
+ ? padFromHashColumn(glyph(emptySource), hashLength)
232
+ : padFromHashColumn(src(abbreviateHash(edge.from, hashLength, emptySource)), hashLength);
233
+ const arrow = glyph(forwardArrow);
234
+ const dest = dst(abbreviateHash(edge.to, hashLength, emptySource));
235
+ return `${source} ${arrow} ${dest}`;
236
+ }
237
+
238
+ // ---------------------------------------------------------------------------
239
+ // Public label builders used by the command renderer.
240
+ // ---------------------------------------------------------------------------
241
+
242
+ /**
243
+ * The label text for a contract node row: the abbreviated hash (or the `∅`
244
+ * empty-source token for the baseline) followed by its `@contract`/`@db` markers
245
+ * and `(refs)`, with two spaces between the hash and the overlay block.
246
+ */
247
+ export function formatNodeLabel(
248
+ contractHash: string,
249
+ opts: MigrationGraphLabelOptions,
250
+ nodeHighlight?: 'on-path' | 'off-path' | undefined,
251
+ ): string {
252
+ const style = createLabelStyler(opts);
253
+ const hashLength = opts.hashLength ?? MIGRATION_LIST_HASH_WIDTH;
254
+ const emptySource = migrationListEmptySource(opts.glyphMode ?? 'unicode');
255
+ const overlays = overlayNamesForContract(contractHash, opts);
256
+ const hasOverlays = overlays.markers.length > 0 || overlays.refs.length > 0;
257
+ const offPath = nodeHighlight === 'off-path' && opts.colorize;
258
+ // The baseline's label is the ∅ empty-source token (the gutter draws ○ for
259
+ // every node, including the baseline); a real contract's label is its hash.
260
+ const hashText =
261
+ contractHash === EMPTY_CONTRACT_HASH
262
+ ? (offPath ? forcedDim : style.glyph)(emptySource)
263
+ : (offPath ? forcedDim : style.sourceHash)(
264
+ abbreviateHash(contractHash, hashLength, emptySource),
265
+ );
266
+ if (!hasOverlays) return hashText;
267
+ const overlay = formatContractNodeOverlays(style, overlays.markers, overlays.refs);
268
+ return `${hashText} ${overlay}`;
269
+ }
270
+
271
+ /**
272
+ * The label text for a migration row: the migration name (padded to
273
+ * `dirNameWidth`) followed by the `from → to` hash column and the annotation
274
+ * suffix (ops / status / will-run).
275
+ *
276
+ * In flat mode the name is tinted with its lane's hue (`lane` ≥ 0), so the node
277
+ * `○`, the edges/arrows in the gutter, and the name all read in one colour. In
278
+ * focus mode the on-path/off-path role overrides the lane hue (bold / dim).
279
+ */
280
+ export function formatMigrationLabel(
281
+ edge: ClassifiedEdge,
282
+ dirNameWidth: number,
283
+ opts: MigrationGraphLabelOptions,
284
+ lane?: number,
285
+ ): string {
286
+ const style = createLabelStyler(opts);
287
+ const hashLength = opts.hashLength ?? MIGRATION_LIST_HASH_WIDTH;
288
+ const glyphMode = opts.glyphMode ?? 'unicode';
289
+ const highlight = opts.edgeAnnotationsByHash?.get(edge.migrationHash)?.pathHighlight;
290
+
291
+ let dirNameStyler: (text: string) => string;
292
+ let hashOverride: ((text: string) => string) | undefined;
293
+ if (highlight === 'on-path') {
294
+ // On-path: tint the name with the on-path green (matching the route's green
295
+ // glyphs in the gutter), not bolded.
296
+ dirNameStyler = opts.colorize ? forcedGreen : (text) => text;
297
+ hashOverride = undefined;
298
+ } else if (highlight === 'off-path') {
299
+ dirNameStyler = opts.colorize ? forcedDim : style.dirName;
300
+ hashOverride = opts.colorize ? forcedDim : undefined;
301
+ } else if (opts.colorize && lane !== undefined) {
302
+ // Flat mode: tint the name with the lane hue (matching the lane's
303
+ // node/edge/arrow colour in the gutter), not bolded.
304
+ dirNameStyler = (text) => laneColorizer(lane)(text);
305
+ hashOverride = undefined;
306
+ } else {
307
+ dirNameStyler = style.dirName;
308
+ hashOverride = undefined;
309
+ }
310
+
311
+ const dirNamePadding = ' '.repeat(Math.max(0, dirNameWidth - edge.dirName.length));
312
+ const dirName = `${dirNameStyler(edge.dirName)}${dirNamePadding}`;
313
+ const hashColumn = formatEdgeHashColumn(edge, style, hashLength, glyphMode, hashOverride);
314
+ const annotationSuffix = formatEdgeAnnotationSuffix(edge.migrationHash, opts, style);
315
+ return `${dirName}${hashColumn}${annotationSuffix}`;
316
+ }
317
+
318
+ /**
319
+ * Format a single on-path migration row for the `migrate --show` run-list.
320
+ * Shares PATH_HIGHLIGHT_STYLES.onPath with the graph tree so the run-list and
321
+ * the graph are byte-for-byte identical in their name/hash columns.
322
+ */
323
+ export function formatOnPathMigrationRow(
324
+ dirName: string,
325
+ from: string,
326
+ to: string,
327
+ dirNameWidth: number,
328
+ colorize: boolean,
329
+ glyphMode: GlyphMode,
330
+ ): string {
331
+ const style = createAnsiMigrationListStyler({ useColor: colorize });
332
+ const s = PATH_HIGHLIGHT_STYLES.onPath(style, colorize);
333
+ const styledDirName = `${s.dirName(dirName)}${' '.repeat(Math.max(0, dirNameWidth - dirName.length))}`;
334
+ const hashLength = MIGRATION_LIST_HASH_WIDTH;
335
+ const emptySource = migrationListEmptySource(glyphMode);
336
+ const forwardArrow = migrationListForwardArrow(glyphMode);
337
+ const fromAbbr =
338
+ from === EMPTY_CONTRACT_HASH
339
+ ? padFromHashColumn(style.glyph(emptySource), hashLength)
340
+ : padFromHashColumn(
341
+ style.sourceHash(abbreviateHash(from, hashLength, emptySource)),
342
+ hashLength,
343
+ );
344
+ const toAbbr =
345
+ to === EMPTY_CONTRACT_HASH
346
+ ? style.glyph(emptySource)
347
+ : style.destHash(abbreviateHash(to, hashLength, emptySource));
348
+ const arrow = style.glyph(forwardArrow);
349
+ return `${styledDirName} ${fromAbbr} ${arrow} ${toAbbr}`;
350
+ }
351
+
352
+ export interface RenderMigrationGraphLegendOptions {
353
+ readonly colorize: boolean;
354
+ readonly glyphMode?: GlyphMode;
355
+ }
356
+
357
+ function legendGlyphs(mode: GlyphMode): {
358
+ readonly node: string;
359
+ readonly forward: string;
360
+ readonly rollback: string;
361
+ readonly self: string;
362
+ } {
363
+ return mode === 'ascii'
364
+ ? { node: '*', forward: '^', rollback: 'v', self: '@' }
365
+ : { node: '○', forward: '↑', rollback: '↓', self: '⟲' };
366
+ }
367
+
368
+ function formatLegendExampleMarkers(colorize: boolean): string {
369
+ if (!colorize) {
370
+ return '@contract @db';
371
+ }
372
+ const sigil = green('@');
373
+ return `${sigil + bold(green('contract'))} ${sigil}${green('db')}`;
374
+ }
375
+
376
+ /**
377
+ * A compact key for the tree visual language: the contract node glyph, the
378
+ * in-lane direction arrows, the empty baseline, the system-marker `@…` and
379
+ * user-ref `(…)` conventions, and a worked sample of the data-column hash arrow.
380
+ */
381
+ export function renderMigrationGraphLegend(opts: RenderMigrationGraphLegendOptions): string {
382
+ const glyphMode = opts.glyphMode ?? 'unicode';
383
+ const style = createAnsiMigrationListStyler({ useColor: opts.colorize });
384
+ const glyphs = legendGlyphs(glyphMode);
385
+ const emptySource = migrationListEmptySource(glyphMode);
386
+ const forwardArrow = migrationListForwardArrow(glyphMode);
387
+ const sampleArrow = `${style.sourceHash('aaaaaa')} ${style.glyph(forwardArrow)} ${style.destHash('bbbbbb')}`;
388
+ const statusGlyphs = overlayStatusGlyphs(glyphMode);
389
+ const appliedPending = opts.colorize
390
+ ? ` ${green(statusGlyphs.applied)} ${style.summary('applied')} ${yellow(statusGlyphs.pending)} ${style.summary('pending')}`
391
+ : ` ${statusGlyphs.applied} ${style.summary('applied')} ${statusGlyphs.pending} ${style.summary('pending')}`;
392
+ const exampleMarkers = formatLegendExampleMarkers(opts.colorize);
393
+ const exampleRefs = opts.colorize ? style.refs(['prod', 'staging']) : '(prod, staging)';
394
+ const lines = [
395
+ 'Legend:',
396
+ ` ${style.kind(glyphs.node)} ${style.summary('contract')} ${style.kind(glyphs.forward)} ${style.summary('forward')} ${style.kind(glyphs.rollback)} ${style.summary('rollback')}`,
397
+ ` ${style.kind(glyphs.self)} ${style.summary('migration without schema change')}`,
398
+ appliedPending,
399
+ ` ${style.kind(emptySource)} ${style.summary('empty database (baseline)')}`,
400
+ ` ${exampleMarkers} ${style.summary('reserved markers — also typeable as --from/--to tokens')}`,
401
+ ` ${exampleRefs} ${style.summary('user-defined refs')}`,
402
+ ` ${sampleArrow} ${style.summary('migration from contract aaaaaa to bbbbbb')}`,
403
+ ];
404
+ return lines.join('\n');
405
+ }
406
+
407
+ // Re-export the edge kind type alias for downstream label callers.
408
+ export type { MigrationEdgeKind };
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Data structures for the line/plane/occlusion migration-graph renderer.
3
+ *
4
+ * A _line_ is the primitive. Each migration edge becomes a routed line that
5
+ * carries its identity. Cells hold an ordered (z) set of lines; the topmost
6
+ * plane wins and is drawn; lower planes are occluded.
7
+ */
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Directions — which arms a line occupies in a cell.
11
+ // ---------------------------------------------------------------------------
12
+ export type Direction = 'up' | 'down' | 'left' | 'right';
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // PathRole — whether this line is on-path or off-path in focus mode.
16
+ // In flat mode all lines have role = undefined.
17
+ // ---------------------------------------------------------------------------
18
+ export type PathRole = 'on-path' | 'off-path';
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // LineRef — identity carried into every cell the line touches.
22
+ // ---------------------------------------------------------------------------
23
+ export interface LineRef {
24
+ readonly migrationHash: string;
25
+ readonly dirName: string;
26
+ readonly lane: number;
27
+ readonly role: PathRole | undefined;
28
+ }
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // NodeRef — identity of a contract node in the grid.
32
+ // ---------------------------------------------------------------------------
33
+ export interface NodeRef {
34
+ readonly contractHash: string;
35
+ readonly isEmpty: boolean;
36
+ /** Lane index; used to pick the node's colour in flat mode. */
37
+ readonly lane: number;
38
+ /** In focus mode: 'on-path' (green) or 'off-path' (dim). undefined in flat mode. */
39
+ readonly role: PathRole | undefined;
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // CellLine — one line's presence in one cell.
44
+ //
45
+ // `selfLoop` marks a self-edge (a migration whose from === to). It renders as
46
+ // the ⟲ glyph and is modelled separately from `directions` because a self-loop
47
+ // is not one of the four cardinal arms — `Direction` stays honestly up|down|
48
+ // left|right.
49
+ // ---------------------------------------------------------------------------
50
+ export interface CellLine {
51
+ readonly line: LineRef;
52
+ readonly directions: ReadonlySet<Direction>;
53
+ readonly plane: number;
54
+ readonly selfLoop?: boolean;
55
+ /**
56
+ * Marks the arrowhead where a routed back-arc lands into its target node
57
+ * (the connector cell immediately right of the node). Renders as `◂` instead
58
+ * of the box-drawing glyph for its directions.
59
+ */
60
+ readonly landingArrow?: boolean;
61
+ }
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Cell — one position in the grid.
65
+ //
66
+ // `separator` marks an inter-component blank-line row. When the first cell in
67
+ // a row has `separator: true`, the row renders as an empty line (the renderGrid
68
+ // blank-separator pass-through).
69
+ // ---------------------------------------------------------------------------
70
+ export interface Cell {
71
+ readonly node?: NodeRef;
72
+ readonly lines: readonly CellLine[];
73
+ readonly separator?: boolean;
74
+ }
75
+
76
+ // ---------------------------------------------------------------------------
77
+ // Grid — the full rendered layout (rows × columns, row 0 = top of display).
78
+ // ---------------------------------------------------------------------------
79
+ export type Grid = readonly (readonly Cell[])[];
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // GridOptions — configurable geometry.
83
+ // ---------------------------------------------------------------------------
84
+
85
+ /** Default number of grid columns allocated per lane (one rail + one connector). */
86
+ export const DEFAULT_COLS_PER_LANE = 2;
87
+
88
+ export interface GridOptions {
89
+ readonly colsPerLane?: number;
90
+ }
91
+
92
+ // ---------------------------------------------------------------------------
93
+ // Highlight — focus-mode input.
94
+ //
95
+ // `flat` (the default) → trunk-on-top z-order, lane-rotation colour.
96
+ // `focus` → on-path-on-top z-order; the migration names in `onPath` are the
97
+ // chosen route. Lines whose migration is in `onPath` get role 'on-path' (green,
98
+ // continuous, topmost plane); every other line is 'off-path' (dim, yields).
99
+ // ---------------------------------------------------------------------------
100
+ export interface Highlight {
101
+ readonly mode: 'flat' | 'focus';
102
+ readonly onPath: ReadonlySet<string>;
103
+ }