@prisma-next/cli 0.3.0-dev.10 → 0.3.0-dev.113

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 (215) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +470 -134
  3. package/dist/cli-errors-ByGuoqNj.mjs +3 -0
  4. package/dist/cli-errors-D6HxRn3A.d.mts +2 -0
  5. package/dist/cli.d.mts +1 -0
  6. package/dist/cli.js +1 -2350
  7. package/dist/cli.mjs +235 -0
  8. package/dist/cli.mjs.map +1 -0
  9. package/dist/client-612RJJD_.mjs +1069 -0
  10. package/dist/client-612RJJD_.mjs.map +1 -0
  11. package/dist/commands/contract-emit.d.mts +7 -0
  12. package/dist/commands/contract-emit.d.mts.map +1 -0
  13. package/dist/commands/contract-emit.mjs +4 -0
  14. package/dist/commands/contract-infer.d.mts +7 -0
  15. package/dist/commands/contract-infer.d.mts.map +1 -0
  16. package/dist/commands/contract-infer.mjs +4 -0
  17. package/dist/commands/db-init.d.mts +7 -0
  18. package/dist/commands/db-init.d.mts.map +1 -0
  19. package/dist/commands/db-init.mjs +124 -0
  20. package/dist/commands/db-init.mjs.map +1 -0
  21. package/dist/commands/db-schema.d.mts +7 -0
  22. package/dist/commands/db-schema.d.mts.map +1 -0
  23. package/dist/commands/db-schema.mjs +52 -0
  24. package/dist/commands/db-schema.mjs.map +1 -0
  25. package/dist/commands/db-sign.d.mts +7 -0
  26. package/dist/commands/db-sign.d.mts.map +1 -0
  27. package/dist/commands/db-sign.mjs +135 -0
  28. package/dist/commands/db-sign.mjs.map +1 -0
  29. package/dist/commands/db-update.d.mts +7 -0
  30. package/dist/commands/db-update.d.mts.map +1 -0
  31. package/dist/commands/db-update.mjs +121 -0
  32. package/dist/commands/db-update.mjs.map +1 -0
  33. package/dist/commands/db-verify.d.mts +7 -0
  34. package/dist/commands/db-verify.d.mts.map +1 -0
  35. package/dist/commands/db-verify.mjs +310 -0
  36. package/dist/commands/db-verify.mjs.map +1 -0
  37. package/dist/commands/migration-apply.d.mts +36 -0
  38. package/dist/commands/migration-apply.d.mts.map +1 -0
  39. package/dist/commands/migration-apply.mjs +240 -0
  40. package/dist/commands/migration-apply.mjs.map +1 -0
  41. package/dist/commands/migration-plan.d.mts +47 -0
  42. package/dist/commands/migration-plan.d.mts.map +1 -0
  43. package/dist/commands/migration-plan.mjs +288 -0
  44. package/dist/commands/migration-plan.mjs.map +1 -0
  45. package/dist/commands/migration-ref.d.mts +43 -0
  46. package/dist/commands/migration-ref.d.mts.map +1 -0
  47. package/dist/commands/migration-ref.mjs +194 -0
  48. package/dist/commands/migration-ref.mjs.map +1 -0
  49. package/dist/commands/migration-show.d.mts +28 -0
  50. package/dist/commands/migration-show.d.mts.map +1 -0
  51. package/dist/commands/migration-show.mjs +139 -0
  52. package/dist/commands/migration-show.mjs.map +1 -0
  53. package/dist/commands/migration-status.d.mts +85 -0
  54. package/dist/commands/migration-status.d.mts.map +1 -0
  55. package/dist/commands/migration-status.mjs +4 -0
  56. package/dist/commands/migration-verify.d.mts +16 -0
  57. package/dist/commands/migration-verify.d.mts.map +1 -0
  58. package/dist/commands/migration-verify.mjs +87 -0
  59. package/dist/commands/migration-verify.mjs.map +1 -0
  60. package/dist/config-loader-d_KF19Tw.mjs +43 -0
  61. package/dist/config-loader-d_KF19Tw.mjs.map +1 -0
  62. package/dist/{config-loader.d.ts → config-loader.d.mts} +8 -3
  63. package/dist/config-loader.d.mts.map +1 -0
  64. package/dist/config-loader.mjs +3 -0
  65. package/dist/contract-emit-CVv7dbQ9.mjs +187 -0
  66. package/dist/contract-emit-CVv7dbQ9.mjs.map +1 -0
  67. package/dist/contract-infer-Bvw8u8Eu.mjs +83 -0
  68. package/dist/contract-infer-Bvw8u8Eu.mjs.map +1 -0
  69. package/dist/exports/config-types.d.mts +2 -0
  70. package/dist/exports/config-types.mjs +3 -0
  71. package/dist/exports/control-api.d.mts +626 -0
  72. package/dist/exports/control-api.d.mts.map +1 -0
  73. package/dist/exports/control-api.mjs +107 -0
  74. package/dist/exports/control-api.mjs.map +1 -0
  75. package/dist/{load-ts-contract.d.ts → exports/index.d.mts} +10 -5
  76. package/dist/exports/index.d.mts.map +1 -0
  77. package/dist/exports/index.mjs +130 -0
  78. package/dist/exports/index.mjs.map +1 -0
  79. package/dist/extract-sql-ddl-Jf5blEO0.mjs +26 -0
  80. package/dist/extract-sql-ddl-Jf5blEO0.mjs.map +1 -0
  81. package/dist/framework-components-M2j-qPfr.mjs +59 -0
  82. package/dist/framework-components-M2j-qPfr.mjs.map +1 -0
  83. package/dist/inspect-live-schema-BQe5i4YE.mjs +90 -0
  84. package/dist/inspect-live-schema-BQe5i4YE.mjs.map +1 -0
  85. package/dist/migration-command-scaffold-SLrjcKXS.mjs +104 -0
  86. package/dist/migration-command-scaffold-SLrjcKXS.mjs.map +1 -0
  87. package/dist/migration-status-DAQKsmWW.mjs +1576 -0
  88. package/dist/migration-status-DAQKsmWW.mjs.map +1 -0
  89. package/dist/migrations-Db_ea9eE.mjs +173 -0
  90. package/dist/migrations-Db_ea9eE.mjs.map +1 -0
  91. package/dist/progress-adapter-DRNe2idZ.mjs +43 -0
  92. package/dist/progress-adapter-DRNe2idZ.mjs.map +1 -0
  93. package/dist/terminal-ui-DAcMBRKf.mjs +980 -0
  94. package/dist/terminal-ui-DAcMBRKf.mjs.map +1 -0
  95. package/dist/verify-DXKxBFvU.mjs +385 -0
  96. package/dist/verify-DXKxBFvU.mjs.map +1 -0
  97. package/package.json +88 -43
  98. package/src/cli.ts +109 -58
  99. package/src/commands/contract-emit.ts +236 -143
  100. package/src/commands/contract-infer-paths.ts +32 -0
  101. package/src/commands/contract-infer.ts +131 -0
  102. package/src/commands/db-init.ts +211 -425
  103. package/src/commands/db-schema.ts +77 -0
  104. package/src/commands/db-sign.ts +207 -228
  105. package/src/commands/db-update.ts +236 -0
  106. package/src/commands/db-verify.ts +484 -186
  107. package/src/commands/inspect-live-schema.ts +171 -0
  108. package/src/commands/migration-apply.ts +416 -0
  109. package/src/commands/migration-plan.ts +451 -0
  110. package/src/commands/migration-ref.ts +305 -0
  111. package/src/commands/migration-show.ts +246 -0
  112. package/src/commands/migration-status.ts +838 -0
  113. package/src/commands/migration-verify.ts +134 -0
  114. package/src/config-loader.ts +13 -3
  115. package/src/control-api/client.ts +614 -0
  116. package/src/control-api/contract-enrichment.ts +135 -0
  117. package/src/control-api/errors.ts +9 -0
  118. package/src/control-api/operations/contract-emit.ts +173 -0
  119. package/src/control-api/operations/db-init.ts +286 -0
  120. package/src/control-api/operations/db-update.ts +221 -0
  121. package/src/control-api/operations/extract-sql-ddl.ts +47 -0
  122. package/src/control-api/operations/migration-apply.ts +194 -0
  123. package/src/control-api/operations/migration-helpers.ts +49 -0
  124. package/src/control-api/types.ts +683 -0
  125. package/src/exports/config-types.ts +4 -3
  126. package/src/exports/control-api.ts +56 -0
  127. package/src/load-ts-contract.ts +16 -11
  128. package/src/utils/cli-errors.ts +5 -2
  129. package/src/utils/command-helpers.ts +293 -3
  130. package/src/utils/formatters/emit.ts +67 -0
  131. package/src/utils/formatters/errors.ts +82 -0
  132. package/src/utils/formatters/graph-migration-mapper.ts +220 -0
  133. package/src/utils/formatters/graph-render.ts +1317 -0
  134. package/src/utils/formatters/graph-types.ts +114 -0
  135. package/src/utils/formatters/help.ts +380 -0
  136. package/src/utils/formatters/helpers.ts +28 -0
  137. package/src/utils/formatters/migrations.ts +346 -0
  138. package/src/utils/formatters/styled.ts +212 -0
  139. package/src/utils/formatters/verify.ts +620 -0
  140. package/src/utils/global-flags.ts +41 -23
  141. package/src/utils/migration-command-scaffold.ts +187 -0
  142. package/src/utils/migration-types.ts +12 -0
  143. package/src/utils/progress-adapter.ts +75 -0
  144. package/src/utils/result-handler.ts +12 -13
  145. package/src/utils/shutdown.ts +92 -0
  146. package/src/utils/suggest-command.ts +31 -0
  147. package/src/utils/terminal-ui.ts +276 -0
  148. package/dist/chunk-BZMBKEEQ.js +0 -997
  149. package/dist/chunk-BZMBKEEQ.js.map +0 -1
  150. package/dist/chunk-CVNWLFXO.js +0 -91
  151. package/dist/chunk-CVNWLFXO.js.map +0 -1
  152. package/dist/chunk-HWYQOCAJ.js +0 -47
  153. package/dist/chunk-HWYQOCAJ.js.map +0 -1
  154. package/dist/chunk-QUPBU4KV.js +0 -131
  155. package/dist/chunk-QUPBU4KV.js.map +0 -1
  156. package/dist/cli.d.ts +0 -2
  157. package/dist/cli.d.ts.map +0 -1
  158. package/dist/cli.js.map +0 -1
  159. package/dist/commands/contract-emit.d.ts +0 -3
  160. package/dist/commands/contract-emit.d.ts.map +0 -1
  161. package/dist/commands/contract-emit.js +0 -9
  162. package/dist/commands/contract-emit.js.map +0 -1
  163. package/dist/commands/db-init.d.ts +0 -3
  164. package/dist/commands/db-init.d.ts.map +0 -1
  165. package/dist/commands/db-init.js +0 -337
  166. package/dist/commands/db-init.js.map +0 -1
  167. package/dist/commands/db-introspect.d.ts +0 -3
  168. package/dist/commands/db-introspect.d.ts.map +0 -1
  169. package/dist/commands/db-introspect.js +0 -186
  170. package/dist/commands/db-introspect.js.map +0 -1
  171. package/dist/commands/db-schema-verify.d.ts +0 -3
  172. package/dist/commands/db-schema-verify.d.ts.map +0 -1
  173. package/dist/commands/db-schema-verify.js +0 -160
  174. package/dist/commands/db-schema-verify.js.map +0 -1
  175. package/dist/commands/db-sign.d.ts +0 -3
  176. package/dist/commands/db-sign.d.ts.map +0 -1
  177. package/dist/commands/db-sign.js +0 -195
  178. package/dist/commands/db-sign.js.map +0 -1
  179. package/dist/commands/db-verify.d.ts +0 -3
  180. package/dist/commands/db-verify.d.ts.map +0 -1
  181. package/dist/commands/db-verify.js +0 -169
  182. package/dist/commands/db-verify.js.map +0 -1
  183. package/dist/config-loader.d.ts.map +0 -1
  184. package/dist/config-loader.js +0 -7
  185. package/dist/config-loader.js.map +0 -1
  186. package/dist/exports/config-types.d.ts +0 -3
  187. package/dist/exports/config-types.d.ts.map +0 -1
  188. package/dist/exports/config-types.js +0 -6
  189. package/dist/exports/config-types.js.map +0 -1
  190. package/dist/exports/index.d.ts +0 -4
  191. package/dist/exports/index.d.ts.map +0 -1
  192. package/dist/exports/index.js +0 -175
  193. package/dist/exports/index.js.map +0 -1
  194. package/dist/load-ts-contract.d.ts.map +0 -1
  195. package/dist/utils/action.d.ts +0 -16
  196. package/dist/utils/action.d.ts.map +0 -1
  197. package/dist/utils/cli-errors.d.ts +0 -7
  198. package/dist/utils/cli-errors.d.ts.map +0 -1
  199. package/dist/utils/command-helpers.d.ts +0 -12
  200. package/dist/utils/command-helpers.d.ts.map +0 -1
  201. package/dist/utils/framework-components.d.ts +0 -70
  202. package/dist/utils/framework-components.d.ts.map +0 -1
  203. package/dist/utils/global-flags.d.ts +0 -25
  204. package/dist/utils/global-flags.d.ts.map +0 -1
  205. package/dist/utils/output.d.ts +0 -142
  206. package/dist/utils/output.d.ts.map +0 -1
  207. package/dist/utils/result-handler.d.ts +0 -15
  208. package/dist/utils/result-handler.d.ts.map +0 -1
  209. package/dist/utils/spinner.d.ts +0 -29
  210. package/dist/utils/spinner.d.ts.map +0 -1
  211. package/src/commands/db-introspect.ts +0 -256
  212. package/src/commands/db-schema-verify.ts +0 -232
  213. package/src/utils/action.ts +0 -43
  214. package/src/utils/output.ts +0 -1471
  215. package/src/utils/spinner.ts +0 -67
@@ -0,0 +1,620 @@
1
+ import type { CoreSchemaView, SchemaTreeNode } from '@prisma-next/core-control-plane/schema-view';
2
+ import type {
3
+ IntrospectSchemaResult,
4
+ SchemaVerificationNode,
5
+ SignDatabaseResult,
6
+ VerifyDatabaseResult,
7
+ VerifyDatabaseSchemaResult,
8
+ } from '@prisma-next/core-control-plane/types';
9
+ import { ifDefined } from '@prisma-next/utils/defined';
10
+ import { bold, cyan, dim, green, magenta, red, yellow } from 'colorette';
11
+ import type { GlobalFlags } from '../global-flags';
12
+ import { createColorFormatter, formatDim, isVerbose } from './helpers';
13
+
14
+ // ============================================================================
15
+ // Verify Output Formatters
16
+ // ============================================================================
17
+
18
+ export interface DbVerifyCommandSuccessResult {
19
+ readonly ok: true;
20
+ readonly mode: 'full' | 'marker-only';
21
+ readonly summary: string;
22
+ readonly contract: VerifyDatabaseResult['contract'];
23
+ readonly marker?: VerifyDatabaseResult['marker'];
24
+ readonly target: VerifyDatabaseResult['target'];
25
+ readonly missingCodecs?: VerifyDatabaseResult['missingCodecs'];
26
+ readonly codecCoverageSkipped?: VerifyDatabaseResult['codecCoverageSkipped'];
27
+ readonly schema?: {
28
+ readonly summary: string;
29
+ readonly counts: VerifyDatabaseSchemaResult['schema']['counts'];
30
+ readonly strict: boolean;
31
+ };
32
+ readonly warning?: string;
33
+ readonly meta?:
34
+ | (NonNullable<VerifyDatabaseResult['meta']> & {
35
+ readonly schemaVerification: 'performed' | 'skipped';
36
+ })
37
+ | {
38
+ readonly schemaVerification: 'performed' | 'skipped';
39
+ };
40
+ readonly timings: {
41
+ readonly total: number;
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Formats human-readable output for database verify.
47
+ */
48
+ export function formatVerifyOutput(
49
+ result: DbVerifyCommandSuccessResult,
50
+ flags: GlobalFlags,
51
+ ): string {
52
+ if (flags.quiet) {
53
+ return '';
54
+ }
55
+
56
+ const lines: string[] = [];
57
+
58
+ const useColor = flags.color !== false;
59
+ const formatGreen = createColorFormatter(useColor, green);
60
+ const formatYellow = createColorFormatter(useColor, yellow);
61
+ const formatDimText = (text: string) => formatDim(useColor, text);
62
+ const verificationMode =
63
+ result.mode === 'full'
64
+ ? `marker + schema${result.schema?.strict ? ' (strict)' : ' (tolerant)'}`
65
+ : 'marker only (--marker-only)';
66
+
67
+ lines.push(`${formatGreen('✔')} ${result.summary}`);
68
+ lines.push(`${formatDimText(` verification: ${verificationMode}`)}`);
69
+ lines.push(`${formatDimText(` storageHash: ${result.contract.storageHash}`)}`);
70
+ if (result.contract.profileHash) {
71
+ lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);
72
+ }
73
+ if (result.mode === 'full' && result.schema && isVerbose(flags, 1)) {
74
+ lines.push(
75
+ `${formatDimText(` schema: pass=${result.schema.counts.pass} warn=${result.schema.counts.warn} fail=${result.schema.counts.fail}`)}`,
76
+ );
77
+ }
78
+ if (result.warning) {
79
+ lines.push('');
80
+ lines.push(`${formatYellow('⚠')} ${result.warning}`);
81
+ }
82
+
83
+ if (isVerbose(flags, 1)) {
84
+ if (result.codecCoverageSkipped) {
85
+ lines.push(
86
+ `${formatDimText(' Codec coverage check skipped (helper returned no supported types)')}`,
87
+ );
88
+ }
89
+ lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
90
+ }
91
+
92
+ return lines.join('\n');
93
+ }
94
+
95
+ /**
96
+ * Formats JSON output for database verify.
97
+ */
98
+ export function formatVerifyJson(result: DbVerifyCommandSuccessResult): string {
99
+ const output = {
100
+ ok: result.ok,
101
+ summary: result.summary,
102
+ mode: result.mode,
103
+ contract: result.contract,
104
+ ...ifDefined('marker', result.marker),
105
+ target: result.target,
106
+ ...ifDefined('missingCodecs', result.missingCodecs),
107
+ ...ifDefined('codecCoverageSkipped', result.codecCoverageSkipped),
108
+ ...ifDefined('schema', result.schema),
109
+ ...ifDefined('warning', result.warning),
110
+ ...ifDefined('meta', result.meta),
111
+ timings: result.timings,
112
+ };
113
+
114
+ return JSON.stringify(output, null, 2);
115
+ }
116
+
117
+ /**
118
+ * Formats JSON output for database introspection.
119
+ */
120
+ export function formatIntrospectJson(result: IntrospectSchemaResult<unknown>): string {
121
+ return JSON.stringify(result, null, 2);
122
+ }
123
+
124
+ /**
125
+ * Renders a schema tree structure from CoreSchemaView.
126
+ * Matches the style of renderSchemaVerificationTree for consistency.
127
+ */
128
+ function renderSchemaTree(
129
+ node: SchemaTreeNode,
130
+ flags: GlobalFlags,
131
+ options: {
132
+ readonly isLast: boolean;
133
+ readonly prefix: string;
134
+ readonly useColor: boolean;
135
+ readonly formatDimText: (text: string) => string;
136
+ readonly isRoot?: boolean;
137
+ },
138
+ ): string[] {
139
+ const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;
140
+ const lines: string[] = [];
141
+
142
+ // Format node label with color based on kind (matching schema-verify style)
143
+ let formattedLabel: string = node.label;
144
+
145
+ if (useColor) {
146
+ switch (node.kind) {
147
+ case 'root':
148
+ formattedLabel = bold(node.label);
149
+ break;
150
+ case 'entity': {
151
+ // Parse "table tableName" format - color "table" dim, tableName cyan
152
+ const tableMatch = node.label.match(/^table\s+(.+)$/);
153
+ if (tableMatch?.[1]) {
154
+ const tableName = tableMatch[1];
155
+ formattedLabel = `${dim('table')} ${cyan(tableName)}`;
156
+ } else {
157
+ // Fallback: color entire label with cyan
158
+ formattedLabel = cyan(node.label);
159
+ }
160
+ break;
161
+ }
162
+ case 'collection': {
163
+ // "columns" grouping node - dim the label
164
+ formattedLabel = dim(node.label);
165
+ break;
166
+ }
167
+ case 'field': {
168
+ // Parse column name format: "columnName: typeDisplay (nullability)"
169
+ // Color code: column name (cyan), type (default), nullability (dim)
170
+ const columnMatch = node.label.match(/^([^:]+):\s*(.+)$/);
171
+ if (columnMatch?.[1] && columnMatch[2]) {
172
+ const columnName = columnMatch[1];
173
+ const rest = columnMatch[2];
174
+ // Parse rest: "typeDisplay (nullability)"
175
+ const typeMatch = rest.match(/^([^\s(]+)\s*(\([^)]+\))$/);
176
+ if (typeMatch?.[1] && typeMatch[2]) {
177
+ const typeDisplay = typeMatch[1];
178
+ const nullability = typeMatch[2];
179
+ formattedLabel = `${cyan(columnName)}: ${typeDisplay} ${dim(nullability)}`;
180
+ } else {
181
+ // Fallback if format doesn't match
182
+ formattedLabel = `${cyan(columnName)}: ${rest}`;
183
+ }
184
+ } else {
185
+ formattedLabel = node.label;
186
+ }
187
+ break;
188
+ }
189
+ case 'index': {
190
+ // Parse index/unique constraint/primary key formats
191
+ // "primary key: columnName" -> dim "primary key", cyan columnName
192
+ const pkMatch = node.label.match(/^primary key:\s*(.+)$/);
193
+ if (pkMatch?.[1]) {
194
+ const columnNames = pkMatch[1];
195
+ formattedLabel = `${dim('primary key')}: ${cyan(columnNames)}`;
196
+ } else {
197
+ // "unique name" -> dim "unique", cyan "name"
198
+ const uniqueMatch = node.label.match(/^unique\s+(.+)$/);
199
+ if (uniqueMatch?.[1]) {
200
+ const name = uniqueMatch[1];
201
+ formattedLabel = `${dim('unique')} ${cyan(name)}`;
202
+ } else {
203
+ // "index name" or "unique index name" -> dim label prefix, cyan name
204
+ const indexMatch = node.label.match(/^(unique\s+)?index\s+(.+)$/);
205
+ if (indexMatch?.[2]) {
206
+ const indexPrefix = indexMatch[1] ? `${dim('unique')} ` : '';
207
+ const name = indexMatch[2];
208
+ formattedLabel = `${indexPrefix}${dim('index')} ${cyan(name)}`;
209
+ } else {
210
+ formattedLabel = dim(node.label);
211
+ }
212
+ }
213
+ }
214
+ break;
215
+ }
216
+ case 'dependency': {
217
+ // Parse extension message formats similar to schema-verify
218
+ // "extensionName extension is enabled" -> cyan extensionName, dim rest
219
+ const extMatch = node.label.match(/^([^\s]+)\s+(extension is enabled)$/);
220
+ if (extMatch?.[1] && extMatch[2]) {
221
+ const extName = extMatch[1];
222
+ const rest = extMatch[2];
223
+ formattedLabel = `${cyan(extName)} ${dim(rest)}`;
224
+ } else {
225
+ // Fallback: color entire label with magenta
226
+ formattedLabel = magenta(node.label);
227
+ }
228
+ break;
229
+ }
230
+ default:
231
+ formattedLabel = node.label;
232
+ break;
233
+ }
234
+ }
235
+
236
+ // Root node renders without tree characters or prefix
237
+ if (isRoot) {
238
+ lines.push(formattedLabel);
239
+ } else {
240
+ const treeChar = isLast ? '└' : '├';
241
+ const treePrefix = `${formatDimText(treeChar)}─ `;
242
+ lines.push(`${prefix}${treePrefix}${formattedLabel}`);
243
+ }
244
+
245
+ // Render children if present
246
+ if (node.children && node.children.length > 0) {
247
+ const childPrefix = isRoot ? '' : `${prefix}${isLast ? ' ' : `${formatDimText('│')} `}`;
248
+ for (let i = 0; i < node.children.length; i++) {
249
+ const child = node.children[i];
250
+ if (!child) continue;
251
+ const isLastChild = i === node.children.length - 1;
252
+ const childLines = renderSchemaTree(child, flags, {
253
+ isLast: isLastChild,
254
+ prefix: childPrefix,
255
+ useColor,
256
+ formatDimText,
257
+ isRoot: false,
258
+ });
259
+ lines.push(...childLines);
260
+ }
261
+ }
262
+
263
+ return lines;
264
+ }
265
+
266
+ /**
267
+ * Formats human-readable output for database introspection.
268
+ */
269
+ export function formatIntrospectOutput(
270
+ result: IntrospectSchemaResult<unknown>,
271
+ schemaView: CoreSchemaView | undefined,
272
+ flags: GlobalFlags,
273
+ ): string {
274
+ if (flags.quiet) {
275
+ return '';
276
+ }
277
+
278
+ const lines: string[] = [];
279
+
280
+ const useColor = flags.color !== false;
281
+ const formatDimText = (text: string) => formatDim(useColor, text);
282
+
283
+ if (schemaView) {
284
+ // Render tree structure - root node is special (no tree characters)
285
+ const treeLines = renderSchemaTree(schemaView.root, flags, {
286
+ isLast: true,
287
+ prefix: '',
288
+ useColor,
289
+ formatDimText,
290
+ isRoot: true,
291
+ });
292
+ lines.push(...treeLines);
293
+ } else {
294
+ // Fallback: print summary when toSchemaView is not available
295
+ lines.push(`✔ ${result.summary}`);
296
+ if (isVerbose(flags, 1)) {
297
+ lines.push(` Target: ${result.target.familyId}/${result.target.id}`);
298
+ if (result.meta?.dbUrl) {
299
+ lines.push(` Database: ${result.meta.dbUrl}`);
300
+ }
301
+ }
302
+ }
303
+
304
+ // Add timings in verbose mode
305
+ if (isVerbose(flags, 1)) {
306
+ lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
307
+ }
308
+
309
+ return lines.join('\n');
310
+ }
311
+
312
+ /**
313
+ * Renders a schema verification tree structure from SchemaVerificationNode.
314
+ * Similar to renderSchemaTree but for verification nodes with status-based colors and glyphs.
315
+ */
316
+ function renderSchemaVerificationTree(
317
+ node: SchemaVerificationNode,
318
+ flags: GlobalFlags,
319
+ options: {
320
+ readonly isLast: boolean;
321
+ readonly prefix: string;
322
+ readonly useColor: boolean;
323
+ readonly formatDimText: (text: string) => string;
324
+ readonly isRoot?: boolean;
325
+ },
326
+ ): string[] {
327
+ const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;
328
+ const lines: string[] = [];
329
+
330
+ // Format status glyph and color based on status
331
+ let statusGlyph = '';
332
+ let statusColor: (text: string) => string = (text) => text;
333
+ if (useColor) {
334
+ switch (node.status) {
335
+ case 'pass':
336
+ statusGlyph = '✔';
337
+ statusColor = green;
338
+ break;
339
+ case 'warn':
340
+ statusGlyph = '⚠';
341
+ statusColor = (text) => (useColor ? yellow(text) : text);
342
+ break;
343
+ case 'fail':
344
+ statusGlyph = '✖';
345
+ statusColor = red;
346
+ break;
347
+ }
348
+ } else {
349
+ switch (node.status) {
350
+ case 'pass':
351
+ statusGlyph = '✔';
352
+ break;
353
+ case 'warn':
354
+ statusGlyph = '⚠';
355
+ break;
356
+ case 'fail':
357
+ statusGlyph = '✖';
358
+ break;
359
+ }
360
+ }
361
+
362
+ // Format node label with color based on kind
363
+ // For column nodes, we need to parse the name to color code different parts
364
+ let labelColor: (text: string) => string = (text) => text;
365
+ let formattedLabel: string = node.name;
366
+
367
+ if (useColor) {
368
+ switch (node.kind) {
369
+ case 'contract':
370
+ case 'schema':
371
+ labelColor = bold;
372
+ formattedLabel = labelColor(node.name);
373
+ break;
374
+ case 'table': {
375
+ // Parse "table tableName" format - color "table" dim, tableName cyan
376
+ const tableMatch = node.name.match(/^table\s+(.+)$/);
377
+ if (tableMatch?.[1]) {
378
+ const tableName = tableMatch[1];
379
+ formattedLabel = `${dim('table')} ${cyan(tableName)}`;
380
+ } else {
381
+ formattedLabel = dim(node.name);
382
+ }
383
+ break;
384
+ }
385
+ case 'columns':
386
+ labelColor = dim;
387
+ formattedLabel = labelColor(node.name);
388
+ break;
389
+ case 'column': {
390
+ // Parse column name format: "columnName: contractType -> nativeType (nullability)"
391
+ // Color code: column name (cyan), contract type (default), native type (dim), nullability (dim)
392
+ const columnMatch = node.name.match(/^([^:]+):\s*(.+)$/);
393
+ if (columnMatch?.[1] && columnMatch[2]) {
394
+ const columnName = columnMatch[1];
395
+ const rest = columnMatch[2];
396
+ // Parse rest: "contractType -> nativeType (nullability)"
397
+ // Match contract type (can contain /, @, etc.), arrow, native type, then nullability in parentheses
398
+ const typeMatch = rest.match(/^([^\s→]+)\s*→\s*([^\s(]+)\s*(\([^)]+\))$/);
399
+ if (typeMatch?.[1] && typeMatch[2] && typeMatch[3]) {
400
+ const contractType = typeMatch[1];
401
+ const nativeType = typeMatch[2];
402
+ const nullability = typeMatch[3];
403
+ formattedLabel = `${cyan(columnName)}: ${contractType} → ${dim(nativeType)} ${dim(nullability)}`;
404
+ } else {
405
+ // Fallback if format doesn't match (e.g., no native type or no nullability)
406
+ formattedLabel = `${cyan(columnName)}: ${rest}`;
407
+ }
408
+ } else {
409
+ formattedLabel = node.name;
410
+ }
411
+ break;
412
+ }
413
+ case 'type':
414
+ case 'nullability':
415
+ labelColor = (text) => text; // Default color
416
+ formattedLabel = labelColor(node.name);
417
+ break;
418
+ case 'primaryKey': {
419
+ // Parse "primary key: columnName" format - color "primary key" dim, columnName cyan
420
+ const pkMatch = node.name.match(/^primary key:\s*(.+)$/);
421
+ if (pkMatch?.[1]) {
422
+ const columnNames = pkMatch[1];
423
+ formattedLabel = `${dim('primary key')}: ${cyan(columnNames)}`;
424
+ } else {
425
+ formattedLabel = dim(node.name);
426
+ }
427
+ break;
428
+ }
429
+ case 'foreignKey':
430
+ case 'unique':
431
+ case 'index':
432
+ labelColor = dim;
433
+ formattedLabel = labelColor(node.name);
434
+ break;
435
+ case 'dependency': {
436
+ // Parse specific extension message formats
437
+ // "database is postgres" -> dim "database is", cyan "postgres"
438
+ const dbMatch = node.name.match(/^database is\s+(.+)$/);
439
+ if (dbMatch?.[1]) {
440
+ const dbName = dbMatch[1];
441
+ formattedLabel = `${dim('database is')} ${cyan(dbName)}`;
442
+ } else {
443
+ // "vector extension is enabled" -> dim everything except extension name
444
+ // Match pattern: "extensionName extension is enabled"
445
+ const extMatch = node.name.match(/^([^\s]+)\s+(extension is enabled)$/);
446
+ if (extMatch?.[1] && extMatch[2]) {
447
+ const extName = extMatch[1];
448
+ const rest = extMatch[2];
449
+ formattedLabel = `${cyan(extName)} ${dim(rest)}`;
450
+ } else {
451
+ // Fallback: color entire name with magenta
452
+ labelColor = magenta;
453
+ formattedLabel = labelColor(node.name);
454
+ }
455
+ }
456
+ break;
457
+ }
458
+ default:
459
+ formattedLabel = node.name;
460
+ break;
461
+ }
462
+ } else {
463
+ formattedLabel = node.name;
464
+ }
465
+
466
+ const statusGlyphColored = statusColor(statusGlyph);
467
+
468
+ // Build the label with optional message for failure/warn nodes
469
+ let nodeLabel = formattedLabel;
470
+ if (
471
+ (node.status === 'fail' || node.status === 'warn') &&
472
+ node.message &&
473
+ node.message.length > 0
474
+ ) {
475
+ // Always show message for failure/warn nodes - it provides crucial context
476
+ // For parent nodes, the message summarizes child failures
477
+ // For leaf nodes, the message explains the specific issue
478
+ const messageText = formatDimText(`(${node.message})`);
479
+ nodeLabel = `${formattedLabel} ${messageText}`;
480
+ }
481
+
482
+ // Root node renders without tree characters or | prefix
483
+ // Root node renders without tree characters or prefix
484
+ if (isRoot) {
485
+ lines.push(`${statusGlyphColored} ${nodeLabel}`);
486
+ } else {
487
+ const treeChar = isLast ? '└' : '├';
488
+ const treePrefix = `${formatDimText(treeChar)}─ `;
489
+ lines.push(`${prefix}${treePrefix}${statusGlyphColored} ${nodeLabel}`);
490
+ }
491
+
492
+ // Render children if present
493
+ if (node.children && node.children.length > 0) {
494
+ const childPrefix = isRoot ? '' : `${prefix}${isLast ? ' ' : `${formatDimText('│')} `}`;
495
+ for (let i = 0; i < node.children.length; i++) {
496
+ const child = node.children[i];
497
+ if (!child) continue;
498
+ const isLastChild = i === node.children.length - 1;
499
+ const childLines = renderSchemaVerificationTree(child, flags, {
500
+ isLast: isLastChild,
501
+ prefix: childPrefix,
502
+ useColor,
503
+ formatDimText,
504
+ isRoot: false,
505
+ });
506
+ lines.push(...childLines);
507
+ }
508
+ }
509
+
510
+ return lines;
511
+ }
512
+
513
+ /**
514
+ * Formats human-readable output for database schema verification.
515
+ */
516
+ export function formatSchemaVerifyOutput(
517
+ result: VerifyDatabaseSchemaResult,
518
+ flags: GlobalFlags,
519
+ ): string {
520
+ if (flags.quiet) {
521
+ return '';
522
+ }
523
+
524
+ const lines: string[] = [];
525
+
526
+ const useColor = flags.color !== false;
527
+ const formatGreen = createColorFormatter(useColor, green);
528
+ const formatRed = createColorFormatter(useColor, red);
529
+ const formatDimText = (text: string) => formatDim(useColor, text);
530
+
531
+ // Render verification tree first
532
+ const treeLines = renderSchemaVerificationTree(result.schema.root, flags, {
533
+ isLast: true,
534
+ prefix: '',
535
+ useColor,
536
+ formatDimText,
537
+ isRoot: true,
538
+ });
539
+ lines.push(...treeLines);
540
+
541
+ // Add counts and timings in verbose mode
542
+ if (isVerbose(flags, 1)) {
543
+ lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
544
+ lines.push(
545
+ `${formatDimText(` pass=${result.schema.counts.pass} warn=${result.schema.counts.warn} fail=${result.schema.counts.fail}`)}`,
546
+ );
547
+ }
548
+
549
+ // Blank line before summary
550
+ lines.push('');
551
+
552
+ // Summary line at the end: summary with status glyph
553
+ if (result.ok) {
554
+ lines.push(`${formatGreen('✔')} ${result.summary}`);
555
+ } else {
556
+ const codeText = result.code ? ` (${result.code})` : '';
557
+ lines.push(`${formatRed('✖')} ${result.summary}${codeText}`);
558
+ }
559
+
560
+ return lines.join('\n');
561
+ }
562
+
563
+ /**
564
+ * Formats JSON output for database schema verification.
565
+ */
566
+ export function formatSchemaVerifyJson(result: VerifyDatabaseSchemaResult): string {
567
+ return JSON.stringify(result, null, 2);
568
+ }
569
+
570
+ // ============================================================================
571
+ // Sign Output Formatters
572
+ // ============================================================================
573
+
574
+ /**
575
+ * Formats human-readable output for database sign.
576
+ */
577
+ export function formatSignOutput(result: SignDatabaseResult, flags: GlobalFlags): string {
578
+ if (flags.quiet) {
579
+ return '';
580
+ }
581
+
582
+ const lines: string[] = [];
583
+
584
+ const useColor = flags.color !== false;
585
+ const formatGreen = createColorFormatter(useColor, green);
586
+ const formatDimText = (text: string) => formatDim(useColor, text);
587
+
588
+ if (result.ok) {
589
+ // Main success message in white (not dimmed)
590
+ lines.push(`${formatGreen('✔')} Database signed`);
591
+
592
+ // Show from -> to hashes with clear labels
593
+ const previousHash = result.marker.previous?.storageHash ?? 'none';
594
+ const currentHash = result.contract.storageHash;
595
+
596
+ lines.push(`${formatDimText(` from: ${previousHash}`)}`);
597
+ lines.push(`${formatDimText(` to: ${currentHash}`)}`);
598
+
599
+ if (isVerbose(flags, 1)) {
600
+ if (result.contract.profileHash) {
601
+ lines.push(`${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);
602
+ }
603
+ if (result.marker.previous?.profileHash) {
604
+ lines.push(
605
+ `${formatDimText(` previous profileHash: ${result.marker.previous.profileHash}`)}`,
606
+ );
607
+ }
608
+ lines.push(`${formatDimText(` Total time: ${result.timings.total}ms`)}`);
609
+ }
610
+ }
611
+
612
+ return lines.join('\n');
613
+ }
614
+
615
+ /**
616
+ * Formats JSON output for database sign.
617
+ */
618
+ export function formatSignJson(result: SignDatabaseResult): string {
619
+ return JSON.stringify(result, null, 2);
620
+ }