@prisma-next/cli 0.3.0-pr.99.6 → 0.4.0-dev.1

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 (257) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +381 -128
  3. package/dist/agent-skill-mongo.md +106 -0
  4. package/dist/agent-skill-postgres.md +106 -0
  5. package/dist/cli-errors-BDCYR5ap.mjs +4 -0
  6. package/dist/cli-errors-DStABy9d.d.mts +3 -0
  7. package/dist/cli.d.mts +1 -0
  8. package/dist/cli.js +1 -2910
  9. package/dist/cli.mjs +254 -0
  10. package/dist/cli.mjs.map +1 -0
  11. package/dist/client-DiUkJAeN.mjs +987 -0
  12. package/dist/client-DiUkJAeN.mjs.map +1 -0
  13. package/dist/commands/contract-emit.d.mts +7 -0
  14. package/dist/commands/contract-emit.d.mts.map +1 -0
  15. package/dist/commands/contract-emit.mjs +4 -0
  16. package/dist/commands/contract-infer.d.mts +7 -0
  17. package/dist/commands/contract-infer.d.mts.map +1 -0
  18. package/dist/commands/contract-infer.mjs +4 -0
  19. package/dist/commands/db-init.d.mts +7 -0
  20. package/dist/commands/db-init.d.mts.map +1 -0
  21. package/dist/commands/db-init.mjs +125 -0
  22. package/dist/commands/db-init.mjs.map +1 -0
  23. package/dist/commands/db-schema.d.mts +7 -0
  24. package/dist/commands/db-schema.d.mts.map +1 -0
  25. package/dist/commands/db-schema.mjs +53 -0
  26. package/dist/commands/db-schema.mjs.map +1 -0
  27. package/dist/commands/db-sign.d.mts +7 -0
  28. package/dist/commands/db-sign.d.mts.map +1 -0
  29. package/dist/commands/db-sign.mjs +136 -0
  30. package/dist/commands/db-sign.mjs.map +1 -0
  31. package/dist/commands/db-update.d.mts +7 -0
  32. package/dist/commands/db-update.d.mts.map +1 -0
  33. package/dist/commands/db-update.mjs +122 -0
  34. package/dist/commands/db-update.mjs.map +1 -0
  35. package/dist/commands/db-verify.d.mts +7 -0
  36. package/dist/commands/db-verify.d.mts.map +1 -0
  37. package/dist/commands/db-verify.mjs +322 -0
  38. package/dist/commands/db-verify.mjs.map +1 -0
  39. package/dist/commands/migration-apply.d.mts +36 -0
  40. package/dist/commands/migration-apply.d.mts.map +1 -0
  41. package/dist/commands/migration-apply.mjs +244 -0
  42. package/dist/commands/migration-apply.mjs.map +1 -0
  43. package/dist/commands/migration-new.d.mts +8 -0
  44. package/dist/commands/migration-new.d.mts.map +1 -0
  45. package/dist/commands/migration-new.mjs +152 -0
  46. package/dist/commands/migration-new.mjs.map +1 -0
  47. package/dist/commands/migration-plan.d.mts +47 -0
  48. package/dist/commands/migration-plan.d.mts.map +1 -0
  49. package/dist/commands/migration-plan.mjs +313 -0
  50. package/dist/commands/migration-plan.mjs.map +1 -0
  51. package/dist/commands/migration-ref.d.mts +43 -0
  52. package/dist/commands/migration-ref.d.mts.map +1 -0
  53. package/dist/commands/migration-ref.mjs +195 -0
  54. package/dist/commands/migration-ref.mjs.map +1 -0
  55. package/dist/commands/migration-show.d.mts +28 -0
  56. package/dist/commands/migration-show.d.mts.map +1 -0
  57. package/dist/commands/migration-show.mjs +140 -0
  58. package/dist/commands/migration-show.mjs.map +1 -0
  59. package/dist/commands/migration-status.d.mts +86 -0
  60. package/dist/commands/migration-status.d.mts.map +1 -0
  61. package/dist/commands/migration-status.mjs +4 -0
  62. package/dist/commands/migration-verify.d.mts +16 -0
  63. package/dist/commands/migration-verify.d.mts.map +1 -0
  64. package/dist/commands/migration-verify.mjs +110 -0
  65. package/dist/commands/migration-verify.mjs.map +1 -0
  66. package/dist/config-loader-C4VXKl8f.mjs +43 -0
  67. package/dist/config-loader-C4VXKl8f.mjs.map +1 -0
  68. package/dist/{config-loader.d.ts → config-loader.d.mts} +8 -3
  69. package/dist/config-loader.d.mts.map +1 -0
  70. package/dist/config-loader.mjs +3 -0
  71. package/dist/contract-emit-D2wDXfyo.mjs +191 -0
  72. package/dist/contract-emit-D2wDXfyo.mjs.map +1 -0
  73. package/dist/contract-emit-D9WOShFz.mjs +4 -0
  74. package/dist/contract-emit-Zm_sd1wQ.mjs +112 -0
  75. package/dist/contract-emit-Zm_sd1wQ.mjs.map +1 -0
  76. package/dist/contract-enrichment-CGW6mm-E.mjs +79 -0
  77. package/dist/contract-enrichment-CGW6mm-E.mjs.map +1 -0
  78. package/dist/contract-infer-DozZT511.mjs +90 -0
  79. package/dist/contract-infer-DozZT511.mjs.map +1 -0
  80. package/dist/exports/config-types.d.mts +2 -0
  81. package/dist/exports/config-types.mjs +3 -0
  82. package/dist/exports/control-api.d.mts +624 -0
  83. package/dist/exports/control-api.d.mts.map +1 -0
  84. package/dist/exports/control-api.mjs +6 -0
  85. package/dist/{load-ts-contract.d.ts → exports/index.d.mts} +12 -7
  86. package/dist/exports/index.d.mts.map +1 -0
  87. package/dist/exports/index.mjs +137 -0
  88. package/dist/exports/index.mjs.map +1 -0
  89. package/dist/extract-operation-statements-DZUJNmL3.mjs +13 -0
  90. package/dist/extract-operation-statements-DZUJNmL3.mjs.map +1 -0
  91. package/dist/extract-sql-ddl-DDMX-9mz.mjs +26 -0
  92. package/dist/extract-sql-ddl-DDMX-9mz.mjs.map +1 -0
  93. package/dist/framework-components-BAsliT4V.mjs +59 -0
  94. package/dist/framework-components-BAsliT4V.mjs.map +1 -0
  95. package/dist/init-DQ8auNB4.mjs +430 -0
  96. package/dist/init-DQ8auNB4.mjs.map +1 -0
  97. package/dist/inspect-live-schema-BYnhztxZ.mjs +91 -0
  98. package/dist/inspect-live-schema-BYnhztxZ.mjs.map +1 -0
  99. package/dist/migration-command-scaffold-CntCcntR.mjs +105 -0
  100. package/dist/migration-command-scaffold-CntCcntR.mjs.map +1 -0
  101. package/dist/migration-status-CJANY4yr.mjs +1583 -0
  102. package/dist/migration-status-CJANY4yr.mjs.map +1 -0
  103. package/dist/migrations-DTZBYXm1.mjs +173 -0
  104. package/dist/migrations-DTZBYXm1.mjs.map +1 -0
  105. package/dist/progress-adapter-B-YvmcDu.mjs +43 -0
  106. package/dist/progress-adapter-B-YvmcDu.mjs.map +1 -0
  107. package/dist/quick-reference-mongo.md +93 -0
  108. package/dist/quick-reference-postgres.md +91 -0
  109. package/dist/result-handler-oK_vA-Fn.mjs +697 -0
  110. package/dist/result-handler-oK_vA-Fn.mjs.map +1 -0
  111. package/dist/terminal-ui-C5k88MmW.mjs +274 -0
  112. package/dist/terminal-ui-C5k88MmW.mjs.map +1 -0
  113. package/dist/validate-contract-deps-esa-VQ0h.mjs +37 -0
  114. package/dist/validate-contract-deps-esa-VQ0h.mjs.map +1 -0
  115. package/dist/verify-DlFQ2FOw.mjs +385 -0
  116. package/dist/verify-DlFQ2FOw.mjs.map +1 -0
  117. package/package.json +87 -40
  118. package/src/cli.ts +118 -58
  119. package/src/commands/contract-emit.ts +101 -78
  120. package/src/commands/contract-infer-paths.ts +32 -0
  121. package/src/commands/contract-infer.ts +143 -0
  122. package/src/commands/db-init.ts +97 -219
  123. package/src/commands/db-schema.ts +77 -0
  124. package/src/commands/db-sign.ts +46 -73
  125. package/src/commands/db-update.ts +236 -0
  126. package/src/commands/db-verify.ts +409 -119
  127. package/src/commands/init/detect-package-manager.ts +47 -0
  128. package/src/commands/init/index.ts +21 -0
  129. package/src/commands/init/init.ts +203 -0
  130. package/src/commands/init/templates/agent-skill-mongo.md +106 -0
  131. package/src/commands/init/templates/agent-skill-postgres.md +106 -0
  132. package/src/commands/init/templates/agent-skill.ts +19 -0
  133. package/src/commands/init/templates/code-templates.ts +168 -0
  134. package/src/commands/init/templates/quick-reference-mongo.md +93 -0
  135. package/src/commands/init/templates/quick-reference-postgres.md +91 -0
  136. package/src/commands/init/templates/quick-reference.ts +19 -0
  137. package/src/commands/init/templates/render.ts +20 -0
  138. package/src/commands/init/templates/tsconfig.ts +35 -0
  139. package/src/commands/inspect-live-schema.ts +170 -0
  140. package/src/commands/migration-apply.ts +427 -0
  141. package/src/commands/migration-new.ts +260 -0
  142. package/src/commands/migration-plan.ts +519 -0
  143. package/src/commands/migration-ref.ts +305 -0
  144. package/src/commands/migration-show.ts +246 -0
  145. package/src/commands/migration-status.ts +864 -0
  146. package/src/commands/migration-verify.ts +180 -0
  147. package/src/config-loader.ts +13 -3
  148. package/src/control-api/client.ts +205 -183
  149. package/src/control-api/contract-enrichment.ts +119 -0
  150. package/src/control-api/errors.ts +9 -0
  151. package/src/control-api/operations/contract-emit.ts +181 -0
  152. package/src/control-api/operations/db-init.ts +53 -49
  153. package/src/control-api/operations/db-update.ts +220 -0
  154. package/src/control-api/operations/extract-operation-statements.ts +14 -0
  155. package/src/control-api/operations/extract-sql-ddl.ts +47 -0
  156. package/src/control-api/operations/migration-apply.ts +191 -0
  157. package/src/control-api/operations/migration-helpers.ts +49 -0
  158. package/src/control-api/types.ts +274 -52
  159. package/src/exports/config-types.ts +4 -3
  160. package/src/exports/control-api.ts +15 -5
  161. package/src/load-ts-contract.ts +30 -19
  162. package/src/utils/cli-errors.ts +14 -8
  163. package/src/utils/command-helpers.ts +302 -3
  164. package/src/utils/formatters/emit.ts +67 -0
  165. package/src/utils/formatters/errors.ts +82 -0
  166. package/src/utils/formatters/graph-migration-mapper.ts +240 -0
  167. package/src/utils/formatters/graph-render.ts +1323 -0
  168. package/src/utils/formatters/graph-types.ts +120 -0
  169. package/src/utils/formatters/help.ts +380 -0
  170. package/src/utils/formatters/helpers.ts +28 -0
  171. package/src/utils/formatters/migrations.ts +346 -0
  172. package/src/utils/formatters/styled.ts +212 -0
  173. package/src/utils/formatters/verify.ts +621 -0
  174. package/src/utils/framework-components.ts +13 -10
  175. package/src/utils/global-flags.ts +41 -23
  176. package/src/utils/migration-command-scaffold.ts +184 -0
  177. package/src/utils/migration-types.ts +12 -0
  178. package/src/utils/progress-adapter.ts +18 -29
  179. package/src/utils/result-handler.ts +12 -13
  180. package/src/utils/shutdown.ts +92 -0
  181. package/src/utils/suggest-command.ts +31 -0
  182. package/src/utils/terminal-ui.ts +276 -0
  183. package/src/utils/validate-contract-deps.ts +49 -0
  184. package/dist/chunk-AGOTG4L3.js +0 -965
  185. package/dist/chunk-AGOTG4L3.js.map +0 -1
  186. package/dist/chunk-HLLI4YL7.js +0 -180
  187. package/dist/chunk-HLLI4YL7.js.map +0 -1
  188. package/dist/chunk-HWYQOCAJ.js +0 -47
  189. package/dist/chunk-HWYQOCAJ.js.map +0 -1
  190. package/dist/chunk-VG2R7DGF.js +0 -735
  191. package/dist/chunk-VG2R7DGF.js.map +0 -1
  192. package/dist/cli.d.ts +0 -2
  193. package/dist/cli.d.ts.map +0 -1
  194. package/dist/cli.js.map +0 -1
  195. package/dist/commands/contract-emit.d.ts +0 -3
  196. package/dist/commands/contract-emit.d.ts.map +0 -1
  197. package/dist/commands/contract-emit.js +0 -10
  198. package/dist/commands/contract-emit.js.map +0 -1
  199. package/dist/commands/db-init.d.ts +0 -3
  200. package/dist/commands/db-init.d.ts.map +0 -1
  201. package/dist/commands/db-init.js +0 -257
  202. package/dist/commands/db-init.js.map +0 -1
  203. package/dist/commands/db-introspect.d.ts +0 -3
  204. package/dist/commands/db-introspect.d.ts.map +0 -1
  205. package/dist/commands/db-introspect.js +0 -155
  206. package/dist/commands/db-introspect.js.map +0 -1
  207. package/dist/commands/db-schema-verify.d.ts +0 -3
  208. package/dist/commands/db-schema-verify.d.ts.map +0 -1
  209. package/dist/commands/db-schema-verify.js +0 -171
  210. package/dist/commands/db-schema-verify.js.map +0 -1
  211. package/dist/commands/db-sign.d.ts +0 -3
  212. package/dist/commands/db-sign.d.ts.map +0 -1
  213. package/dist/commands/db-sign.js +0 -195
  214. package/dist/commands/db-sign.js.map +0 -1
  215. package/dist/commands/db-verify.d.ts +0 -3
  216. package/dist/commands/db-verify.d.ts.map +0 -1
  217. package/dist/commands/db-verify.js +0 -193
  218. package/dist/commands/db-verify.js.map +0 -1
  219. package/dist/config-loader.d.ts.map +0 -1
  220. package/dist/config-loader.js +0 -7
  221. package/dist/config-loader.js.map +0 -1
  222. package/dist/control-api/client.d.ts +0 -13
  223. package/dist/control-api/client.d.ts.map +0 -1
  224. package/dist/control-api/operations/db-init.d.ts +0 -29
  225. package/dist/control-api/operations/db-init.d.ts.map +0 -1
  226. package/dist/control-api/types.d.ts +0 -387
  227. package/dist/control-api/types.d.ts.map +0 -1
  228. package/dist/exports/config-types.d.ts +0 -3
  229. package/dist/exports/config-types.d.ts.map +0 -1
  230. package/dist/exports/config-types.js +0 -6
  231. package/dist/exports/config-types.js.map +0 -1
  232. package/dist/exports/control-api.d.ts +0 -13
  233. package/dist/exports/control-api.d.ts.map +0 -1
  234. package/dist/exports/control-api.js +0 -7
  235. package/dist/exports/control-api.js.map +0 -1
  236. package/dist/exports/index.d.ts +0 -4
  237. package/dist/exports/index.d.ts.map +0 -1
  238. package/dist/exports/index.js +0 -176
  239. package/dist/exports/index.js.map +0 -1
  240. package/dist/load-ts-contract.d.ts.map +0 -1
  241. package/dist/utils/cli-errors.d.ts +0 -7
  242. package/dist/utils/cli-errors.d.ts.map +0 -1
  243. package/dist/utils/command-helpers.d.ts +0 -12
  244. package/dist/utils/command-helpers.d.ts.map +0 -1
  245. package/dist/utils/framework-components.d.ts +0 -70
  246. package/dist/utils/framework-components.d.ts.map +0 -1
  247. package/dist/utils/global-flags.d.ts +0 -25
  248. package/dist/utils/global-flags.d.ts.map +0 -1
  249. package/dist/utils/output.d.ts +0 -142
  250. package/dist/utils/output.d.ts.map +0 -1
  251. package/dist/utils/progress-adapter.d.ts +0 -26
  252. package/dist/utils/progress-adapter.d.ts.map +0 -1
  253. package/dist/utils/result-handler.d.ts +0 -15
  254. package/dist/utils/result-handler.d.ts.map +0 -1
  255. package/src/commands/db-introspect.ts +0 -227
  256. package/src/commands/db-schema-verify.ts +0 -238
  257. package/src/utils/output.ts +0 -1471
@@ -0,0 +1,120 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Generic graph renderer types — domain-agnostic
3
+ // ---------------------------------------------------------------------------
4
+
5
+ /** A typed marker attached to a node. Different marker kinds get different visual treatment. */
6
+ export type NodeMarker =
7
+ | { readonly kind: 'db' }
8
+ | { readonly kind: 'ref'; readonly name: string; readonly active?: boolean }
9
+ | { readonly kind: 'contract'; readonly planned: boolean }
10
+ | { readonly kind: 'custom'; readonly label: string };
11
+
12
+ /** A node in the graph. Rendered as ○ with optional typed markers. */
13
+ export interface GraphNode {
14
+ readonly id: string;
15
+ /** Typed markers rendered inline on the node row */
16
+ readonly markers?: readonly NodeMarker[] | undefined;
17
+ /** Detached nodes use a dashed connector (e.g. unplanned contract) */
18
+ readonly style?: 'normal' | 'detached' | undefined;
19
+ }
20
+
21
+ /** A directed edge between two nodes. Carries an optional label rendered on the edge line. */
22
+ export interface GraphEdge {
23
+ readonly from: string;
24
+ readonly to: string;
25
+ /** Edge line text, e.g. "20260101_init ✓ abc12.." */
26
+ readonly label?: string;
27
+ /**
28
+ * Visual color hint for the edge. Overrides the default role-based
29
+ * coloring (spine/branch/backward) when set.
30
+ *
31
+ * - `'applied'` — cyan (CVD-safe: completed/done)
32
+ * - `'pending'` — yellow (CVD-safe: waiting/upcoming)
33
+ * - `'unreachable'` — magenta (CVD-safe: DB on a different branch)
34
+ */
35
+ readonly colorHint?: 'applied' | 'pending' | 'unreachable';
36
+ /**
37
+ * Edge rendering style.
38
+ * - `'solid'` (default) — normal edge with `│` connector
39
+ * - `'dashed'` — draft/phantom edge with `┊` connector, excluded from path resolution
40
+ */
41
+ readonly style?: 'solid' | 'dashed';
42
+ }
43
+
44
+ /**
45
+ * Immutable directed graph with adjacency-list indexing.
46
+ *
47
+ * Built once from flat arrays of nodes and edges, then passed around as
48
+ * the primary graph representation for rendering, traversal, truncation,
49
+ * and subgraph extraction.
50
+ */
51
+ export class RenderGraph {
52
+ readonly nodes: readonly GraphNode[];
53
+ readonly edges: readonly GraphEdge[];
54
+
55
+ /** Forward adjacency: node id → outgoing edges. */
56
+ readonly forward: ReadonlyMap<string, readonly GraphEdge[]>;
57
+ /** Set of node ids that have at least one incoming edge. */
58
+ readonly incomingNodes: ReadonlySet<string>;
59
+ /** Node lookup by id. */
60
+ readonly nodeById: ReadonlyMap<string, GraphNode>;
61
+
62
+ constructor(nodes: readonly GraphNode[], edges: readonly GraphEdge[]) {
63
+ this.nodes = nodes;
64
+ this.edges = edges;
65
+
66
+ const fwd = new Map<string, GraphEdge[]>();
67
+ const inc = new Set<string>();
68
+ const byId = new Map<string, GraphNode>();
69
+
70
+ for (const n of nodes) {
71
+ byId.set(n.id, n);
72
+ }
73
+ for (const e of edges) {
74
+ const list = fwd.get(e.from);
75
+ if (list) list.push(e);
76
+ else fwd.set(e.from, [e]);
77
+ inc.add(e.to);
78
+ }
79
+
80
+ this.forward = fwd;
81
+ this.incomingNodes = inc;
82
+ this.nodeById = byId;
83
+ }
84
+
85
+ /** Outgoing edges from a node (empty array if none). */
86
+ outgoing(nodeId: string): readonly GraphEdge[] {
87
+ return this.forward.get(nodeId) ?? [];
88
+ }
89
+ }
90
+
91
+ /** Options controlling graph rendering. */
92
+ export interface GraphRenderOptions {
93
+ /** Node id for the spine endpoint */
94
+ readonly spineTarget: string;
95
+ /** Root node id. Defaults to the node with no incoming edges. */
96
+ readonly rootId?: string;
97
+ /** Enable ANSI colour output */
98
+ readonly colorize?: boolean;
99
+ /** Terminal width. Default 80. */
100
+ readonly maxWidth?: number;
101
+ /**
102
+ * Truncate to show at most this many spine edges. `undefined` = no truncation.
103
+ * The effective limit expands to include any DB or contract markers on the spine.
104
+ */
105
+ readonly limit?: number | undefined;
106
+ /**
107
+ * Override dagre layout parameters. Merged over defaults:
108
+ * `{ ranksep: 4, nodesep: 6, marginx: 2, marginy: 1 }`.
109
+ *
110
+ * When `ranksep` is ≤ 1, labels are placed inline on the arrowhead row
111
+ * instead of in a separate label-placement pass (there isn't enough
112
+ * vertical space for a dedicated label row).
113
+ */
114
+ readonly dagreOptions?: {
115
+ readonly ranksep?: number;
116
+ readonly nodesep?: number;
117
+ readonly marginx?: number;
118
+ readonly marginy?: number;
119
+ };
120
+ }
@@ -0,0 +1,380 @@
1
+ import { blue, bold, cyan, dim, green, magenta } from 'colorette';
2
+ import type { Command } from 'commander';
3
+ import wrapAnsi from 'wrap-ansi';
4
+
5
+ import { getCommandExamples, getLongDescription } from '../command-helpers';
6
+ import type { GlobalFlags } from '../global-flags';
7
+ import { formatDim } from './helpers';
8
+ import { padToFixedWidth, renderCommandTree } from './styled';
9
+
10
+ // ============================================================================
11
+ // Help Output Formatters
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Fixed width for left column in help output.
16
+ * Must match the value in styled.ts.
17
+ */
18
+ const LEFT_COLUMN_WIDTH = 20;
19
+
20
+ /**
21
+ * Minimum width for right column wrapping in help output.
22
+ */
23
+ const RIGHT_COLUMN_MIN_WIDTH = 40;
24
+
25
+ /**
26
+ * Maximum width for right column wrapping in help output (when terminal is wide enough).
27
+ */
28
+ const RIGHT_COLUMN_MAX_WIDTH = 90;
29
+
30
+ /**
31
+ * Gets the terminal width, or returns a default if not available.
32
+ */
33
+ function getTerminalWidth(): number {
34
+ // Help text goes to stderr, so prefer stderr columns. Fall back to stdout, then CLI_WIDTH env.
35
+ const terminalWidth = process.stderr.columns || process.stdout.columns;
36
+ const envWidth = Number.parseInt(process.env['CLI_WIDTH'] || '', 10);
37
+ return terminalWidth || (Number.isFinite(envWidth) ? envWidth : 80);
38
+ }
39
+
40
+ /**
41
+ * Calculates the available width for the right column based on terminal width.
42
+ */
43
+ function calculateRightColumnWidth(): number {
44
+ const terminalWidth = getTerminalWidth();
45
+ const availableWidth = terminalWidth - 2 - LEFT_COLUMN_WIDTH - 2;
46
+ return Math.max(RIGHT_COLUMN_MIN_WIDTH, Math.min(availableWidth, RIGHT_COLUMN_MAX_WIDTH));
47
+ }
48
+
49
+ /**
50
+ * Creates the CLI brand badge.
51
+ */
52
+ function createPrismaNextBadge(useColor: boolean): string {
53
+ return useColor ? bold('prisma-next') : 'prisma-next';
54
+ }
55
+
56
+ /**
57
+ * Formats a header line: brand + operation + intent
58
+ */
59
+ function formatHeaderLine(options: {
60
+ readonly brand: string;
61
+ readonly operation: string;
62
+ readonly intent: string;
63
+ }): string {
64
+ if (options.operation) {
65
+ return `${options.brand} ${options.operation} → ${options.intent}`;
66
+ }
67
+ return `${options.brand} ${options.intent}`;
68
+ }
69
+
70
+ /**
71
+ * Wraps text to fit within a specified width using wrap-ansi.
72
+ */
73
+ function wrapTextAnsi(text: string, width: number): string[] {
74
+ const wrapped = wrapAnsi(text, width, { hard: false, trim: true });
75
+ return wrapped.split('\n');
76
+ }
77
+
78
+ /**
79
+ * Formats a default value as "default: <value>" with dimming.
80
+ */
81
+ function formatDefaultValue(value: unknown, useColor: boolean): string {
82
+ const valueStr = String(value);
83
+ const defaultText = `default: ${valueStr}`;
84
+ return useColor ? dim(defaultText) : defaultText;
85
+ }
86
+
87
+ /**
88
+ * Formats a "Read more" URL line.
89
+ */
90
+ function formatReadMoreLine(options: {
91
+ readonly url: string;
92
+ readonly maxLabelWidth: number;
93
+ readonly useColor: boolean;
94
+ readonly formatDimText: (text: string) => string;
95
+ }): string {
96
+ const labelPadded = `Read more${' '.repeat(Math.max(0, options.maxLabelWidth - 'Read more'.length))}`;
97
+ const valueColored = options.useColor ? blue(options.url) : options.url;
98
+ return `${options.formatDimText('│')} ${labelPadded} ${valueColored}`;
99
+ }
100
+
101
+ /**
102
+ * Formats multiline description with "Prisma Next" in green.
103
+ */
104
+ function formatMultilineDescription(options: {
105
+ readonly descriptionLines: readonly string[];
106
+ readonly useColor: boolean;
107
+ readonly formatDimText: (text: string) => string;
108
+ }): string[] {
109
+ const lines: string[] = [];
110
+ const formatGreen = (text: string) => (options.useColor ? green(text) : text);
111
+
112
+ const rightColumnWidth = calculateRightColumnWidth();
113
+ const totalWidth = 2 + LEFT_COLUMN_WIDTH + 2 + rightColumnWidth;
114
+ const wrapWidth = totalWidth - 2;
115
+
116
+ for (const descLine of options.descriptionLines) {
117
+ const formattedLine = descLine.replace(/Prisma Next/g, (match) => formatGreen(match));
118
+ const wrappedLines = wrapTextAnsi(formattedLine, wrapWidth);
119
+ for (const wrappedLine of wrappedLines) {
120
+ lines.push(`${options.formatDimText('│')} ${wrappedLine}`);
121
+ }
122
+ }
123
+ return lines;
124
+ }
125
+
126
+ /**
127
+ * Maps command paths to their documentation URLs.
128
+ */
129
+ function getCommandDocsUrl(commandPath: string): string | undefined {
130
+ const docsMap: Record<string, string> = {
131
+ 'contract emit': 'https://pris.ly/contract-emit',
132
+ 'contract infer': 'https://pris.ly/contract-infer',
133
+ 'db schema': 'https://pris.ly/db-schema',
134
+ 'db verify': 'https://pris.ly/db-verify',
135
+ 'db update': 'https://pris.ly/db-update',
136
+ 'migration plan': 'https://pris.ly/migration-plan',
137
+ 'migration apply': 'https://pris.ly/migration-apply',
138
+ 'migration show': 'https://pris.ly/migration-show',
139
+ 'migration status': 'https://pris.ly/migration-status',
140
+ 'migration verify': 'https://pris.ly/migration-verify',
141
+ };
142
+ return docsMap[commandPath];
143
+ }
144
+
145
+ /**
146
+ * Builds the full command path from a command and its parents.
147
+ */
148
+ function buildCommandPath(command: Command): string {
149
+ const parts: string[] = [];
150
+ let current: Command | undefined = command;
151
+ while (current && current.name() !== 'prisma-next') {
152
+ parts.unshift(current.name());
153
+ current = current.parent ?? undefined;
154
+ }
155
+ return parts.join(' ');
156
+ }
157
+
158
+ /**
159
+ * Formats help output for a command using the styled format.
160
+ */
161
+ export function formatCommandHelp(options: {
162
+ readonly command: Command;
163
+ readonly flags: GlobalFlags;
164
+ }): string {
165
+ const { command, flags } = options;
166
+ const lines: string[] = [];
167
+ const useColor = flags.color !== false;
168
+ const formatDimText = (text: string) => formatDim(useColor, text);
169
+
170
+ // Build full command path (e.g., "db verify")
171
+ const commandPath = buildCommandPath(command);
172
+ const shortDescription = command.description() || '';
173
+ const longDescription = getLongDescription(command);
174
+
175
+ // Include positional arguments in the header line
176
+ const argsSuffix = command.registeredArguments
177
+ .map((arg) => (arg.required ? `<${arg.name()}>` : `[${arg.name()}]`))
178
+ .join(' ');
179
+ const brand = createPrismaNextBadge(useColor);
180
+ const commandWithArgs = argsSuffix ? `${commandPath} ${argsSuffix}` : commandPath;
181
+ const operation = useColor ? bold(commandWithArgs) : commandWithArgs;
182
+ const intent = formatDimText(shortDescription);
183
+ lines.push(formatHeaderLine({ brand, operation, intent }));
184
+ lines.push(formatDimText('│'));
185
+
186
+ // Extract options and format them
187
+ const optionsList = command.options.map((opt) => {
188
+ const description = opt.description || '';
189
+ // Commander.js stores default value in defaultValue property
190
+ const defaultValue = (opt as { defaultValue?: unknown }).defaultValue;
191
+ return { flags: opt.flags, description, defaultValue };
192
+ });
193
+
194
+ // Extract subcommands if any
195
+ const subcommands = command.commands.filter((cmd) => !cmd.name().startsWith('_'));
196
+
197
+ // Format subcommands as a tree if present
198
+ if (subcommands.length > 0) {
199
+ const hasItemsAfter = optionsList.length > 0;
200
+ const treeLines = renderCommandTree({
201
+ commands: subcommands,
202
+ useColor,
203
+ formatDimText,
204
+ hasItemsAfter,
205
+ });
206
+ lines.push(...treeLines);
207
+ }
208
+
209
+ // Add separator between subcommands and options if both exist
210
+ if (subcommands.length > 0 && optionsList.length > 0) {
211
+ lines.push(formatDimText('│'));
212
+ }
213
+
214
+ // Format options with fixed width, wrapping, and default values
215
+ if (optionsList.length > 0) {
216
+ for (const opt of optionsList) {
217
+ // Format flag with fixed 30-char width
218
+ const flagsPadded = padToFixedWidth(opt.flags, LEFT_COLUMN_WIDTH);
219
+ let flagsColored = flagsPadded;
220
+ if (useColor) {
221
+ // Color placeholders in magenta, then wrap in cyan
222
+ flagsColored = flagsPadded.replace(/(<[^>]+>)/g, (match: string) => magenta(match));
223
+ flagsColored = cyan(flagsColored);
224
+ }
225
+
226
+ // Wrap description based on terminal width
227
+ const rightColumnWidth = calculateRightColumnWidth();
228
+ const wrappedDescription = wrapTextAnsi(opt.description, rightColumnWidth);
229
+
230
+ // First line: flag + first line of description
231
+ lines.push(`${formatDimText('│')} ${flagsColored} ${wrappedDescription[0] || ''}`);
232
+
233
+ // Continuation lines: empty label (30 spaces) + wrapped lines
234
+ for (let i = 1; i < wrappedDescription.length; i++) {
235
+ const emptyLabel = ' '.repeat(LEFT_COLUMN_WIDTH);
236
+ lines.push(`${formatDimText('│')} ${emptyLabel} ${wrappedDescription[i] || ''}`);
237
+ }
238
+
239
+ // Default value line (if present)
240
+ if (opt.defaultValue !== undefined) {
241
+ const emptyLabel = ' '.repeat(LEFT_COLUMN_WIDTH);
242
+ const defaultText = formatDefaultValue(opt.defaultValue, useColor);
243
+ lines.push(`${formatDimText('│')} ${emptyLabel} ${defaultText}`);
244
+ }
245
+ }
246
+ }
247
+
248
+ // Add docs URL if available (with separator line before it)
249
+ const docsUrl = getCommandDocsUrl(commandPath);
250
+ if (docsUrl) {
251
+ lines.push(formatDimText('│')); // Separator line between params and docs
252
+ lines.push(
253
+ formatReadMoreLine({
254
+ url: docsUrl,
255
+ maxLabelWidth: LEFT_COLUMN_WIDTH,
256
+ useColor,
257
+ formatDimText,
258
+ }),
259
+ );
260
+ }
261
+
262
+ // Examples (copy-pastable)
263
+ const examples = getCommandExamples(command);
264
+ if (examples && examples.length > 0) {
265
+ lines.push(formatDimText('│'));
266
+ lines.push(`${formatDimText('│')} ${formatDimText('Examples:')}`);
267
+ for (const example of examples) {
268
+ lines.push(`${formatDimText('│')} ${useColor ? dim('$') : '$'} ${example}`);
269
+ }
270
+ }
271
+
272
+ // Multi-line description (if present) - shown after all other content
273
+ if (longDescription) {
274
+ lines.push(formatDimText('│'));
275
+ const descriptionLines = longDescription.split('\n').filter((line) => line.trim().length > 0);
276
+ lines.push(...formatMultilineDescription({ descriptionLines, useColor, formatDimText }));
277
+ }
278
+
279
+ lines.push(formatDimText('└'));
280
+
281
+ return `${lines.join('\n')}\n`;
282
+ }
283
+
284
+ /**
285
+ * Formats help output for the root program using the styled format.
286
+ */
287
+ export function formatRootHelp(options: {
288
+ readonly program: Command;
289
+ readonly flags: GlobalFlags;
290
+ }): string {
291
+ const { program, flags } = options;
292
+ const lines: string[] = [];
293
+ const useColor = flags.color !== false;
294
+ const formatDimText = (text: string) => formatDim(useColor, text);
295
+
296
+ // Header: "prisma-next -> Manage your data layer"
297
+ const brand = createPrismaNextBadge(useColor);
298
+ const shortDescription = 'Manage your data layer';
299
+ const intent = formatDimText(shortDescription);
300
+ lines.push(formatHeaderLine({ brand, operation: '', intent }));
301
+ lines.push(formatDimText('│')); // Vertical line separator after header
302
+
303
+ // Extract top-level commands (exclude hidden commands starting with '_' and the 'help' command)
304
+ const topLevelCommands = program.commands.filter(
305
+ (cmd) => !cmd.name().startsWith('_') && cmd.name() !== 'help',
306
+ );
307
+
308
+ // Extract global options (needed to determine if last command)
309
+ const globalOptions = program.options.map((opt) => {
310
+ const description = opt.description || '';
311
+ // Commander.js stores default value in defaultValue property
312
+ const defaultValue = (opt as { defaultValue?: unknown }).defaultValue;
313
+ return { flags: opt.flags, description, defaultValue };
314
+ });
315
+
316
+ // Build command tree
317
+ if (topLevelCommands.length > 0) {
318
+ const hasItemsAfter = globalOptions.length > 0;
319
+ const treeLines = renderCommandTree({
320
+ commands: topLevelCommands,
321
+ useColor,
322
+ formatDimText,
323
+ hasItemsAfter,
324
+ });
325
+ lines.push(...treeLines);
326
+ }
327
+
328
+ // Add separator between commands and options if both exist
329
+ if (topLevelCommands.length > 0 && globalOptions.length > 0) {
330
+ lines.push(formatDimText('│'));
331
+ }
332
+
333
+ // Format global options with fixed width, wrapping, and default values
334
+ if (globalOptions.length > 0) {
335
+ for (const opt of globalOptions) {
336
+ // Format flag with fixed 30-char width
337
+ const flagsPadded = padToFixedWidth(opt.flags, LEFT_COLUMN_WIDTH);
338
+ let flagsColored = flagsPadded;
339
+ if (useColor) {
340
+ // Color placeholders in magenta, then wrap in cyan
341
+ flagsColored = flagsPadded.replace(/(<[^>]+>)/g, (match: string) => magenta(match));
342
+ flagsColored = cyan(flagsColored);
343
+ }
344
+
345
+ // Wrap description based on terminal width
346
+ const rightColumnWidth = calculateRightColumnWidth();
347
+ const wrappedDescription = wrapTextAnsi(opt.description, rightColumnWidth);
348
+
349
+ // First line: flag + first line of description
350
+ lines.push(`${formatDimText('│')} ${flagsColored} ${wrappedDescription[0] || ''}`);
351
+
352
+ // Continuation lines: empty label (30 spaces) + wrapped lines
353
+ for (let i = 1; i < wrappedDescription.length; i++) {
354
+ const emptyLabel = ' '.repeat(LEFT_COLUMN_WIDTH);
355
+ lines.push(`${formatDimText('│')} ${emptyLabel} ${wrappedDescription[i] || ''}`);
356
+ }
357
+
358
+ // Default value line (if present)
359
+ if (opt.defaultValue !== undefined) {
360
+ const emptyLabel = ' '.repeat(LEFT_COLUMN_WIDTH);
361
+ const defaultText = formatDefaultValue(opt.defaultValue, useColor);
362
+ lines.push(`${formatDimText('│')} ${emptyLabel} ${defaultText}`);
363
+ }
364
+ }
365
+ }
366
+
367
+ // Multi-line description (white, not dimmed, with "Prisma Next" in green) - shown at bottom
368
+ const formatGreen = (text: string) => (useColor ? green(text) : text);
369
+ const descriptionLines = [
370
+ `Use ${formatGreen('Prisma Next')} to define your data layer as a contract. Sign your database and application with the same contract to guarantee compatibility. Plan and apply migrations to safely evolve your schema.`,
371
+ ];
372
+ if (descriptionLines.length > 0) {
373
+ lines.push(formatDimText('│')); // Separator line before description
374
+ lines.push(...formatMultilineDescription({ descriptionLines, useColor, formatDimText }));
375
+ }
376
+
377
+ lines.push(formatDimText('└'));
378
+
379
+ return `${lines.join('\n')}\n`;
380
+ }
@@ -0,0 +1,28 @@
1
+ import { dim } from 'colorette';
2
+
3
+ import type { GlobalFlags } from '../global-flags';
4
+
5
+ /**
6
+ * Checks if verbose output is enabled at the specified level.
7
+ */
8
+ export function isVerbose(flags: GlobalFlags, level: 1 | 2): boolean {
9
+ return (flags.verbose ?? 0) >= level;
10
+ }
11
+
12
+ /**
13
+ * Creates a color-aware formatter function.
14
+ * Returns a function that applies the color only if colors are enabled.
15
+ */
16
+ export function createColorFormatter<T extends (text: string) => string>(
17
+ useColor: boolean,
18
+ colorFn: T,
19
+ ): (text: string) => string {
20
+ return useColor ? colorFn : (text: string) => text;
21
+ }
22
+
23
+ /**
24
+ * Formats text with dim styling if colors are enabled.
25
+ */
26
+ export function formatDim(useColor: boolean, text: string): string {
27
+ return useColor ? dim(text) : text;
28
+ }