@prisma-next/cli 0.3.0-dev.11 → 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
@@ -1,177 +1,270 @@
1
1
  import { mkdirSync, writeFileSync } from 'node:fs';
2
- import { dirname, relative, resolve } from 'node:path';
3
2
  import { errorContractConfigMissing } from '@prisma-next/core-control-plane/errors';
4
- import { createControlPlaneStack } from '@prisma-next/core-control-plane/types';
3
+ import { notOk, ok, type Result } from '@prisma-next/utils/result';
5
4
  import { Command } from 'commander';
5
+ import { dirname, isAbsolute, join, relative, resolve } from 'pathe';
6
6
  import { loadConfig } from '../config-loader';
7
- import { performAction } from '../utils/action';
8
- import { setCommandDescriptions } from '../utils/command-helpers';
9
- import { parseGlobalFlags } from '../utils/global-flags';
7
+ import { createControlClient } from '../control-api/client';
8
+ import type { EmitFailure } from '../control-api/types';
10
9
  import {
11
- formatCommandHelp,
10
+ CliStructuredError,
11
+ errorContractValidationFailed,
12
+ errorRuntime,
13
+ errorUnexpected,
14
+ } from '../utils/cli-errors';
15
+ import {
16
+ addGlobalOptions,
17
+ setCommandDescriptions,
18
+ setCommandExamples,
19
+ } from '../utils/command-helpers';
20
+ import {
21
+ type EmitContractResult,
12
22
  formatEmitJson,
13
23
  formatEmitOutput,
14
- formatStyledHeader,
15
- formatSuccessMessage,
16
- } from '../utils/output';
24
+ } from '../utils/formatters/emit';
25
+ import { formatStyledHeader, formatSuccessMessage } from '../utils/formatters/styled';
26
+ import type { CommonCommandOptions } from '../utils/global-flags';
27
+ import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
28
+ import { createProgressAdapter } from '../utils/progress-adapter';
17
29
  import { handleResult } from '../utils/result-handler';
18
- import { withSpinner } from '../utils/spinner';
30
+ import { TerminalUI } from '../utils/terminal-ui';
19
31
 
20
- interface ContractEmitOptions {
32
+ interface ContractEmitOptions extends CommonCommandOptions {
21
33
  readonly config?: string;
22
- readonly json?: string | boolean;
23
- readonly quiet?: boolean;
24
- readonly q?: boolean;
25
- readonly verbose?: boolean;
26
- readonly v?: boolean;
27
- readonly vv?: boolean;
28
- readonly trace?: boolean;
29
- readonly timestamps?: boolean;
30
- readonly color?: boolean;
31
- readonly 'no-color'?: boolean;
34
+ }
35
+
36
+ function mapDiagnosticsToIssues(
37
+ failure: EmitFailure,
38
+ ): ReadonlyArray<{ kind: string; message: string }> {
39
+ const diagnostics = failure.diagnostics?.diagnostics ?? [];
40
+ return diagnostics.map((diagnostic) => {
41
+ const location =
42
+ diagnostic.sourceId && diagnostic.span
43
+ ? ` (${diagnostic.sourceId}:${diagnostic.span.start.line}:${diagnostic.span.start.column})`
44
+ : diagnostic.sourceId
45
+ ? ` (${diagnostic.sourceId})`
46
+ : '';
47
+ return {
48
+ kind: diagnostic.code,
49
+ message: `${diagnostic.message}${location}`,
50
+ };
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Maps an EmitFailure to a CliStructuredError for consistent error handling.
56
+ */
57
+ function mapEmitFailure(
58
+ failure: EmitFailure,
59
+ context?: { readonly configPath?: string },
60
+ ): CliStructuredError {
61
+ if (failure.code === 'CONTRACT_SOURCE_INVALID') {
62
+ const issues = mapDiagnosticsToIssues(failure);
63
+ return errorRuntime(failure.summary, {
64
+ why: failure.why ?? 'Contract source provider failed',
65
+ fix: 'Check your contract source provider in prisma-next.config.ts and ensure it returns Result<ContractIR, Diagnostics>',
66
+ ...(issues.length > 0 ? { meta: { issues } } : {}),
67
+ });
68
+ }
69
+
70
+ if (failure.code === 'CONTRACT_VALIDATION_FAILED') {
71
+ return errorContractValidationFailed(
72
+ failure.why ?? 'Contract validation failed while emitting',
73
+ context?.configPath ? { where: { path: context.configPath } } : undefined,
74
+ );
75
+ }
76
+
77
+ if (failure.code === 'EMIT_FAILED') {
78
+ return errorRuntime(failure.summary, {
79
+ why: failure.why ?? 'Failed to emit contract',
80
+ fix: 'Check your contract configuration and ensure the source is valid',
81
+ });
82
+ }
83
+
84
+ // Exhaustive check - TypeScript will error if a new code is added but not handled
85
+ const exhaustive: never = failure.code;
86
+ throw new Error(`Unhandled EmitFailure code: ${exhaustive}`);
87
+ }
88
+
89
+ /**
90
+ * Executes the contract emit command and returns a structured Result.
91
+ */
92
+ async function executeContractEmitCommand(
93
+ options: ContractEmitOptions,
94
+ flags: GlobalFlags,
95
+ ui: TerminalUI,
96
+ startTime: number,
97
+ ): Promise<Result<EmitContractResult, CliStructuredError>> {
98
+ // Load config
99
+ let config: Awaited<ReturnType<typeof loadConfig>>;
100
+ try {
101
+ config = await loadConfig(options.config);
102
+ } catch (error) {
103
+ // Convert thrown CliStructuredError to Result
104
+ if (error instanceof CliStructuredError) {
105
+ return notOk(error);
106
+ }
107
+ return notOk(
108
+ errorUnexpected(error instanceof Error ? error.message : String(error), {
109
+ why: 'Failed to load config',
110
+ }),
111
+ );
112
+ }
113
+
114
+ const configPath = options.config
115
+ ? relative(process.cwd(), resolve(options.config))
116
+ : 'prisma-next.config.ts';
117
+
118
+ // Resolve contract from config
119
+ if (!config.contract) {
120
+ return notOk(
121
+ errorContractConfigMissing({
122
+ why: 'Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ... }',
123
+ }),
124
+ );
125
+ }
126
+
127
+ // Contract config is already normalized by defineConfig() with defaults applied
128
+ const contractConfig = config.contract;
129
+
130
+ // Resolve artifact paths from config (already normalized by defineConfig() with defaults)
131
+ if (!contractConfig.output) {
132
+ return notOk(
133
+ errorContractConfigMissing({
134
+ why: 'Contract config must have output path. This should not happen if defineConfig() was used.',
135
+ }),
136
+ );
137
+ }
138
+ if (!contractConfig.output.endsWith('.json')) {
139
+ return notOk(
140
+ errorContractConfigMissing({
141
+ why: 'Contract config output path must end with .json (e.g., "src/prisma/contract.json")',
142
+ }),
143
+ );
144
+ }
145
+ const configDir = options.config ? dirname(resolve(options.config)) : process.cwd();
146
+ const outputJsonPath = isAbsolute(contractConfig.output)
147
+ ? contractConfig.output
148
+ : join(configDir, contractConfig.output);
149
+ // Colocate .d.ts with .json (contract.json → contract.d.ts)
150
+ const outputDtsPath = `${outputJsonPath.slice(0, -5)}.d.ts`;
151
+
152
+ // Output header to stderr (decoration)
153
+ if (!flags.json && !flags.quiet) {
154
+ const contractPath = relative(process.cwd(), outputJsonPath);
155
+ const typesPath = relative(process.cwd(), outputDtsPath);
156
+ const header = formatStyledHeader({
157
+ command: 'contract emit',
158
+ description: 'Emit your contract artifacts',
159
+ url: 'https://pris.ly/contract-emit',
160
+ details: [
161
+ { label: 'config', value: configPath },
162
+ { label: 'contract', value: contractPath },
163
+ { label: 'types', value: typesPath },
164
+ ],
165
+ flags,
166
+ });
167
+ ui.stderr(header);
168
+ }
169
+
170
+ // Create control client (no driver needed for emit)
171
+ const client = createControlClient({
172
+ family: config.family,
173
+ target: config.target,
174
+ adapter: config.adapter,
175
+ extensionPacks: config.extensionPacks ?? [],
176
+ });
177
+
178
+ // Create progress adapter
179
+ const onProgress = createProgressAdapter({ ui, flags });
180
+
181
+ try {
182
+ // Call emit with progress callback
183
+ const result = await client.emit({
184
+ contractConfig: {
185
+ sourceProvider: contractConfig.source,
186
+ output: outputJsonPath,
187
+ },
188
+ onProgress,
189
+ });
190
+
191
+ // Handle failures by mapping to CLI structured error
192
+ if (!result.ok) {
193
+ return notOk(mapEmitFailure(result.failure, { configPath }));
194
+ }
195
+
196
+ // Create directories if needed
197
+ mkdirSync(dirname(outputJsonPath), { recursive: true });
198
+ mkdirSync(dirname(outputDtsPath), { recursive: true });
199
+
200
+ // Write the results to files
201
+ writeFileSync(outputJsonPath, result.value.contractJson, 'utf-8');
202
+ writeFileSync(outputDtsPath, result.value.contractDts, 'utf-8');
203
+
204
+ // Convert success result to CLI output format
205
+ const emitResult: EmitContractResult = {
206
+ storageHash: result.value.storageHash,
207
+ ...(result.value.executionHash ? { executionHash: result.value.executionHash } : {}),
208
+ profileHash: result.value.profileHash,
209
+ outDir: dirname(outputJsonPath),
210
+ files: {
211
+ json: outputJsonPath,
212
+ dts: outputDtsPath,
213
+ },
214
+ timings: { total: Date.now() - startTime },
215
+ };
216
+
217
+ return ok(emitResult);
218
+ } catch (error) {
219
+ // Use static type guard to work across module boundaries
220
+ if (CliStructuredError.is(error)) {
221
+ return notOk(error);
222
+ }
223
+
224
+ // Wrap unexpected errors
225
+ return notOk(
226
+ errorUnexpected('Unexpected error during contract emit', {
227
+ why: error instanceof Error ? error.message : String(error),
228
+ }),
229
+ );
230
+ } finally {
231
+ await client.close();
232
+ }
32
233
  }
33
234
 
34
235
  export function createContractEmitCommand(): Command {
35
236
  const command = new Command('emit');
36
237
  setCommandDescriptions(
37
238
  command,
38
- 'Write your contract to JSON and sign it',
239
+ 'Emit your contract artifacts',
39
240
  'Reads your contract source (TypeScript or Prisma schema) and emits contract.json and\n' +
40
241
  'contract.d.ts. The contract.json contains the canonical contract structure, and\n' +
41
242
  'contract.d.ts provides TypeScript types for type-safe query building.',
42
243
  );
43
- command
44
- .configureHelp({
45
- formatHelp: (cmd) => {
46
- const flags = parseGlobalFlags({});
47
- return formatCommandHelp({ command: cmd, flags });
48
- },
49
- })
244
+ setCommandExamples(command, [
245
+ 'prisma-next contract emit',
246
+ 'prisma-next contract emit --config ./custom-config.ts',
247
+ ]);
248
+ addGlobalOptions(command)
50
249
  .option('--config <path>', 'Path to prisma-next.config.ts')
51
- .option('--json [format]', 'Output as JSON (object or ndjson)', false)
52
- .option('-q, --quiet', 'Quiet mode: errors only')
53
- .option('-v, --verbose', 'Verbose output: debug info, timings')
54
- .option('-vv, --trace', 'Trace output: deep internals, stack traces')
55
- .option('--timestamps', 'Add timestamps to output')
56
- .option('--color', 'Force color output')
57
- .option('--no-color', 'Disable color output')
58
250
  .action(async (options: ContractEmitOptions) => {
59
251
  const flags = parseGlobalFlags(options);
252
+ const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
253
+ const startTime = Date.now();
60
254
 
61
- const result = await performAction(async () => {
62
- // Load config
63
- const config = await loadConfig(options.config);
64
-
65
- // Resolve contract from config
66
- if (!config.contract) {
67
- throw errorContractConfigMissing({
68
- why: 'Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ..., types: ... }',
69
- });
70
- }
71
-
72
- // Contract config is already normalized by defineConfig() with defaults applied
73
- const contractConfig = config.contract;
74
-
75
- // Resolve artifact paths from config (already normalized by defineConfig() with defaults)
76
- if (!contractConfig.output || !contractConfig.types) {
77
- throw errorContractConfigMissing({
78
- why: 'Contract config must have output and types paths. This should not happen if defineConfig() was used.',
79
- });
80
- }
81
- const outputJsonPath = resolve(contractConfig.output);
82
- const outputDtsPath = resolve(contractConfig.types);
83
-
84
- // Output header (only for human-readable output)
85
- if (flags.json !== 'object' && !flags.quiet) {
86
- // Normalize config path for display (match contract path format - no ./ prefix)
87
- const configPath = options.config
88
- ? relative(process.cwd(), resolve(options.config))
89
- : 'prisma-next.config.ts';
90
- // Convert absolute paths to relative paths for display
91
- const contractPath = relative(process.cwd(), outputJsonPath);
92
- const typesPath = relative(process.cwd(), outputDtsPath);
93
- const header = formatStyledHeader({
94
- command: 'contract emit',
95
- description: 'Write your contract to JSON and sign it',
96
- url: 'https://pris.ly/contract-emit',
97
- details: [
98
- { label: 'config', value: configPath },
99
- { label: 'contract', value: contractPath },
100
- { label: 'types', value: typesPath },
101
- ],
102
- flags,
103
- });
104
- console.log(header);
105
- }
106
-
107
- const stack = createControlPlaneStack({
108
- target: config.target,
109
- adapter: config.adapter,
110
- driver: config.driver,
111
- extensionPacks: config.extensionPacks,
112
- });
113
- const familyInstance = config.family.create(stack);
114
-
115
- // Resolve contract source from config (user's config handles loading)
116
- let contractRaw: unknown;
117
- if (typeof contractConfig.source === 'function') {
118
- contractRaw = await contractConfig.source();
119
- } else {
120
- contractRaw = contractConfig.source;
121
- }
122
-
123
- // Call emitContract on family instance (handles stripping mappings and validation internally)
124
- const emitResult = await withSpinner(
125
- () => familyInstance.emitContract({ contractIR: contractRaw }),
126
- {
127
- message: 'Emitting contract...',
128
- flags,
129
- },
130
- );
131
-
132
- // Create directories if needed
133
- mkdirSync(dirname(outputJsonPath), { recursive: true });
134
- mkdirSync(dirname(outputDtsPath), { recursive: true });
135
-
136
- // Write the results to files
137
- writeFileSync(outputJsonPath, emitResult.contractJson, 'utf-8');
138
- writeFileSync(outputDtsPath, emitResult.contractDts, 'utf-8');
139
-
140
- // Add blank line after all async operations if spinners were shown
141
- if (!flags.quiet && flags.json !== 'object' && process.stdout.isTTY) {
142
- console.log('');
143
- }
144
-
145
- // Return result with file paths for output formatting
146
- return {
147
- coreHash: emitResult.coreHash,
148
- profileHash: emitResult.profileHash,
149
- outDir: dirname(outputJsonPath),
150
- files: {
151
- json: outputJsonPath,
152
- dts: outputDtsPath,
153
- },
154
- timings: {
155
- total: 0, // Timing is handled by emitContract internally if needed
156
- },
157
- };
158
- });
255
+ const result = await executeContractEmitCommand(options, flags, ui, startTime);
159
256
 
160
257
  // Handle result - formats output and returns exit code
161
- const exitCode = handleResult(result, flags, (emitResult) => {
162
- // Output based on flags
163
- if (flags.json === 'object') {
164
- // JSON output to stdout
165
- console.log(formatEmitJson(emitResult));
258
+ const exitCode = handleResult(result, flags, ui, (emitResult) => {
259
+ if (flags.json) {
260
+ ui.output(formatEmitJson(emitResult));
166
261
  } else {
167
- // Human-readable output to stdout
168
262
  const output = formatEmitOutput(emitResult, flags);
169
263
  if (output) {
170
- console.log(output);
264
+ ui.log(output);
171
265
  }
172
- // Output success message
173
266
  if (!flags.quiet) {
174
- console.log(formatSuccessMessage(flags));
267
+ ui.success(formatSuccessMessage(flags));
175
268
  }
176
269
  }
177
270
  });
@@ -0,0 +1,32 @@
1
+ import { dirname, resolve } from 'pathe';
2
+
3
+ interface ContractInferPathOptions {
4
+ readonly output?: string;
5
+ readonly config?: string;
6
+ }
7
+
8
+ /**
9
+ * Resolves the output path for the inferred PSL contract.
10
+ *
11
+ * Priority:
12
+ * 1. --output <path> flag (resolved relative to cwd)
13
+ * 2. contract.prisma next to config.contract.output
14
+ * 3. Canonical default: contract.prisma in the config directory
15
+ */
16
+ export function resolveContractInferOutputPath(
17
+ options: ContractInferPathOptions,
18
+ contractOutput: string | undefined,
19
+ ): string {
20
+ const configDir = options.config
21
+ ? dirname(resolve(process.cwd(), options.config))
22
+ : process.cwd();
23
+
24
+ if (options.output) {
25
+ return resolve(process.cwd(), options.output);
26
+ }
27
+ if (contractOutput) {
28
+ const contractPath = resolve(configDir, contractOutput);
29
+ return resolve(dirname(contractPath), 'contract.prisma');
30
+ }
31
+ return resolve(configDir, 'contract.prisma');
32
+ }
@@ -0,0 +1,131 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
+ import { printPsl } from '@prisma-next/psl-printer';
3
+ import {
4
+ createPostgresDefaultMapping,
5
+ createPostgresTypeMap,
6
+ extractEnumInfo,
7
+ parseRawDefault,
8
+ } from '@prisma-next/psl-printer/postgres';
9
+ import { ok, type Result } from '@prisma-next/utils/result';
10
+ import { Command } from 'commander';
11
+ import { dirname, relative } from 'pathe';
12
+ import type { CliStructuredError } from '../utils/cli-errors';
13
+ import {
14
+ addGlobalOptions,
15
+ setCommandDescriptions,
16
+ setCommandExamples,
17
+ } from '../utils/command-helpers';
18
+ import { parseGlobalFlags } from '../utils/global-flags';
19
+ import { handleResult } from '../utils/result-handler';
20
+ import { TerminalUI } from '../utils/terminal-ui';
21
+ import { resolveContractInferOutputPath } from './contract-infer-paths';
22
+ import {
23
+ type InspectLiveSchemaOptions,
24
+ type InspectLiveSchemaResult,
25
+ inspectLiveSchema,
26
+ } from './inspect-live-schema';
27
+
28
+ interface ContractInferOptions extends InspectLiveSchemaOptions {
29
+ readonly output?: string;
30
+ }
31
+
32
+ interface ContractInferSuccessResult {
33
+ readonly ok: true;
34
+ readonly summary: string;
35
+ readonly target: InspectLiveSchemaResult['target'];
36
+ readonly psl: {
37
+ readonly path: string;
38
+ };
39
+ readonly meta: InspectLiveSchemaResult['meta'];
40
+ readonly timings: {
41
+ readonly total: number;
42
+ };
43
+ }
44
+
45
+ async function executeContractInferCommand(
46
+ options: ContractInferOptions,
47
+ ui: TerminalUI,
48
+ startTime: number,
49
+ ): Promise<Result<ContractInferSuccessResult, CliStructuredError>> {
50
+ const flags = parseGlobalFlags(options);
51
+ const inspectResult = await inspectLiveSchema(options, flags, ui, startTime, {
52
+ commandName: 'contract infer',
53
+ description: 'Infer a PSL contract from the live database schema',
54
+ url: 'https://pris.ly/contract-infer',
55
+ });
56
+
57
+ if (!inspectResult.ok) {
58
+ return inspectResult;
59
+ }
60
+
61
+ const { config, schema, target, meta } = inspectResult.value;
62
+ const outputPath = resolveContractInferOutputPath(options, config.contract?.output);
63
+ const enumInfo = extractEnumInfo(schema.annotations);
64
+ const pslContent = printPsl(schema, {
65
+ defaultMapping: createPostgresDefaultMapping(),
66
+ typeMap: createPostgresTypeMap(enumInfo.typeNames),
67
+ enumInfo,
68
+ parseRawDefault,
69
+ });
70
+
71
+ if (existsSync(outputPath) && !flags.json && !flags.quiet) {
72
+ ui.stderr(`\u26A0 Overwriting existing file: ${relative(process.cwd(), outputPath)}`);
73
+ }
74
+
75
+ mkdirSync(dirname(outputPath), { recursive: true });
76
+ writeFileSync(outputPath, pslContent, 'utf-8');
77
+
78
+ const pslPath = relative(process.cwd(), outputPath);
79
+ if (!flags.json && !flags.quiet) {
80
+ ui.stderr(`\u2714 Contract written to ${pslPath}`);
81
+ }
82
+
83
+ return ok({
84
+ ok: true,
85
+ summary: 'Contract inferred successfully',
86
+ target,
87
+ psl: {
88
+ path: pslPath,
89
+ },
90
+ meta,
91
+ timings: {
92
+ total: Date.now() - startTime,
93
+ },
94
+ });
95
+ }
96
+
97
+ export function createContractInferCommand(): Command {
98
+ const command = new Command('infer');
99
+ setCommandDescriptions(
100
+ command,
101
+ 'Infer a PSL contract from the live database schema',
102
+ 'Reads the live database schema and writes an inferred PSL contract to disk.\n' +
103
+ 'This command stops at `contract.prisma`; follow it with `contract emit` and\n' +
104
+ '`db sign` as separate steps.',
105
+ );
106
+ setCommandExamples(command, [
107
+ 'prisma-next contract infer --db $DATABASE_URL',
108
+ 'prisma-next contract infer --db $DATABASE_URL --output ./prisma/contract.prisma',
109
+ 'prisma-next contract infer --db $DATABASE_URL --json',
110
+ ]);
111
+ addGlobalOptions(command)
112
+ .option('--db <url>', 'Database connection string')
113
+ .option('--config <path>', 'Path to prisma-next.config.ts')
114
+ .option('--output <path>', 'Write the inferred PSL contract to the specified path')
115
+ .action(async (options: ContractInferOptions) => {
116
+ const flags = parseGlobalFlags(options);
117
+ const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
118
+ const startTime = Date.now();
119
+
120
+ const result = await executeContractInferCommand(options, ui, startTime);
121
+ const exitCode = handleResult(result, flags, ui, (value) => {
122
+ if (flags.json) {
123
+ ui.output(JSON.stringify(value, null, 2));
124
+ }
125
+ });
126
+
127
+ process.exit(exitCode);
128
+ });
129
+
130
+ return command;
131
+ }