@prisma-next/cli 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +56 -21
  2. package/dist/cli-errors-By1iVE3z.mjs +34 -0
  3. package/dist/cli-errors-By1iVE3z.mjs.map +1 -0
  4. package/dist/{cli-errors-BFYgBH3L.d.mts → cli-errors-DDeVsP2Y.d.mts} +1 -0
  5. package/dist/cli.mjs +19 -16
  6. package/dist/cli.mjs.map +1 -1
  7. package/dist/{client-CrsnY58k.mjs → client-1JqqkiC7.mjs} +45 -20
  8. package/dist/client-1JqqkiC7.mjs.map +1 -0
  9. package/dist/commands/contract-emit.d.mts.map +1 -1
  10. package/dist/commands/contract-emit.mjs +7 -7
  11. package/dist/commands/contract-infer.d.mts.map +1 -1
  12. package/dist/commands/contract-infer.mjs +8 -8
  13. package/dist/commands/db-init.d.mts.map +1 -1
  14. package/dist/commands/db-init.mjs +11 -10
  15. package/dist/commands/db-init.mjs.map +1 -1
  16. package/dist/commands/db-schema.mjs +8 -8
  17. package/dist/commands/db-sign.mjs +8 -8
  18. package/dist/commands/db-update.mjs +10 -10
  19. package/dist/commands/db-update.mjs.map +1 -1
  20. package/dist/commands/db-verify.mjs +10 -10
  21. package/dist/commands/migration-apply.d.mts +5 -2
  22. package/dist/commands/migration-apply.d.mts.map +1 -1
  23. package/dist/commands/migration-apply.mjs +56 -57
  24. package/dist/commands/migration-apply.mjs.map +1 -1
  25. package/dist/commands/migration-new.d.mts.map +1 -1
  26. package/dist/commands/migration-new.mjs +26 -32
  27. package/dist/commands/migration-new.mjs.map +1 -1
  28. package/dist/commands/migration-plan.d.mts +14 -5
  29. package/dist/commands/migration-plan.d.mts.map +1 -1
  30. package/dist/commands/migration-plan.mjs +45 -48
  31. package/dist/commands/migration-plan.mjs.map +1 -1
  32. package/dist/commands/migration-ref.d.mts +1 -1
  33. package/dist/commands/migration-ref.d.mts.map +1 -1
  34. package/dist/commands/migration-ref.mjs +6 -10
  35. package/dist/commands/migration-ref.mjs.map +1 -1
  36. package/dist/commands/migration-show.d.mts +13 -7
  37. package/dist/commands/migration-show.d.mts.map +1 -1
  38. package/dist/commands/migration-show.mjs +28 -29
  39. package/dist/commands/migration-show.mjs.map +1 -1
  40. package/dist/commands/migration-status.d.mts +23 -5
  41. package/dist/commands/migration-status.d.mts.map +1 -1
  42. package/dist/commands/migration-status.mjs +8 -8
  43. package/dist/{config-loader-C25b63rJ.mjs → config-loader-ih8ViDb_.mjs} +2 -2
  44. package/dist/config-loader-ih8ViDb_.mjs.map +1 -0
  45. package/dist/config-loader.mjs +1 -1
  46. package/dist/contract-emit-DS5NzZh2.mjs +6 -0
  47. package/dist/contract-emit-RZBWzkop.mjs +329 -0
  48. package/dist/contract-emit-RZBWzkop.mjs.map +1 -0
  49. package/dist/contract-emit-rt_Nmdwq.mjs +150 -0
  50. package/dist/contract-emit-rt_Nmdwq.mjs.map +1 -0
  51. package/dist/{contract-enrichment-CAOELa-H.mjs → contract-enrichment-4Ptgw3Pe.mjs} +1 -1
  52. package/dist/{contract-enrichment-CAOELa-H.mjs.map → contract-enrichment-4Ptgw3Pe.mjs.map} +1 -1
  53. package/dist/{contract-infer-D9cC3rJm.mjs → contract-infer-Cf5J2wVg.mjs} +11 -19
  54. package/dist/contract-infer-Cf5J2wVg.mjs.map +1 -0
  55. package/dist/exports/control-api.d.mts +86 -21
  56. package/dist/exports/control-api.d.mts.map +1 -1
  57. package/dist/exports/control-api.mjs +7 -7
  58. package/dist/exports/index.mjs +8 -8
  59. package/dist/{framework-components-Cr--XBKy.mjs → framework-components-Bgcre3Z6.mjs} +2 -2
  60. package/dist/{framework-components-Cr--XBKy.mjs.map → framework-components-Bgcre3Z6.mjs.map} +1 -1
  61. package/dist/{init-m8x0UoPY.mjs → init-DAbQMxIR.mjs} +5 -5
  62. package/dist/{init-m8x0UoPY.mjs.map → init-DAbQMxIR.mjs.map} +1 -1
  63. package/dist/{inspect-live-schema-yrHAvG71.mjs → inspect-live-schema-LWtXfxm_.mjs} +9 -9
  64. package/dist/inspect-live-schema-LWtXfxm_.mjs.map +1 -0
  65. package/dist/migration-cli.d.mts +41 -11
  66. package/dist/migration-cli.d.mts.map +1 -1
  67. package/dist/migration-cli.mjs +308 -84
  68. package/dist/migration-cli.mjs.map +1 -1
  69. package/dist/{migration-command-scaffold-B3B09et6.mjs → migration-command-scaffold-CU452v9h.mjs} +7 -7
  70. package/dist/{migration-command-scaffold-B3B09et6.mjs.map → migration-command-scaffold-CU452v9h.mjs.map} +1 -1
  71. package/dist/{migration-status-DUMiH8_G.mjs → migration-status-DoPrFIOQ.mjs} +114 -57
  72. package/dist/migration-status-DoPrFIOQ.mjs.map +1 -0
  73. package/dist/{migrations-Bo5WtTla.mjs → migrations-MEoKMiV5.mjs} +42 -21
  74. package/dist/migrations-MEoKMiV5.mjs.map +1 -0
  75. package/dist/{progress-adapter-DvQWB1nK.mjs → progress-adapter-DgRGldpT.mjs} +1 -1
  76. package/dist/{progress-adapter-DvQWB1nK.mjs.map → progress-adapter-DgRGldpT.mjs.map} +1 -1
  77. package/dist/{result-handler-Ba3zWQsI.mjs → result-handler-Ch6hVnOo.mjs} +34 -19
  78. package/dist/{result-handler-Ba3zWQsI.mjs.map → result-handler-Ch6hVnOo.mjs.map} +1 -1
  79. package/dist/{terminal-ui-C3ZLwQxK.mjs → terminal-ui-u2YgKghu.mjs} +1 -1
  80. package/dist/{terminal-ui-C3ZLwQxK.mjs.map → terminal-ui-u2YgKghu.mjs.map} +1 -1
  81. package/dist/{verify-Bkycc-Tf.mjs → verify-BT9tgCOH.mjs} +2 -2
  82. package/dist/{verify-Bkycc-Tf.mjs.map → verify-BT9tgCOH.mjs.map} +1 -1
  83. package/package.json +16 -15
  84. package/src/cli.ts +32 -6
  85. package/src/commands/contract-emit.ts +67 -163
  86. package/src/commands/contract-infer.ts +7 -20
  87. package/src/commands/db-init.ts +1 -0
  88. package/src/commands/db-update.ts +1 -1
  89. package/src/commands/inspect-live-schema.ts +10 -5
  90. package/src/commands/migration-apply.ts +84 -63
  91. package/src/commands/migration-new.ts +28 -34
  92. package/src/commands/migration-plan.ts +80 -56
  93. package/src/commands/migration-ref.ts +8 -7
  94. package/src/commands/migration-show.ts +53 -36
  95. package/src/commands/migration-status.ts +194 -58
  96. package/src/config-path-validation.ts +0 -1
  97. package/src/control-api/client.ts +21 -0
  98. package/src/control-api/operations/contract-emit.ts +198 -115
  99. package/src/control-api/operations/db-init.ts +10 -6
  100. package/src/control-api/operations/db-update.ts +10 -6
  101. package/src/control-api/operations/migration-apply.ts +30 -9
  102. package/src/control-api/types.ts +69 -7
  103. package/src/exports/control-api.ts +2 -1
  104. package/src/migration-cli.ts +445 -122
  105. package/src/utils/cli-errors.ts +49 -2
  106. package/src/utils/command-helpers.ts +45 -23
  107. package/src/utils/emit-queue.ts +26 -0
  108. package/src/utils/formatters/graph-migration-mapper.ts +7 -3
  109. package/src/utils/formatters/migrations.ts +62 -26
  110. package/src/utils/publish-contract-artifact-pair.ts +134 -0
  111. package/dist/cli-errors-Cd79vmTH.mjs +0 -5
  112. package/dist/client-CrsnY58k.mjs.map +0 -1
  113. package/dist/config-loader-C25b63rJ.mjs.map +0 -1
  114. package/dist/contract-emit-DxgyXrqV.mjs +0 -6
  115. package/dist/contract-emit-NJ01hiiv.mjs +0 -195
  116. package/dist/contract-emit-NJ01hiiv.mjs.map +0 -1
  117. package/dist/contract-emit-V5SSitUT.mjs +0 -122
  118. package/dist/contract-emit-V5SSitUT.mjs.map +0 -1
  119. package/dist/contract-infer-D9cC3rJm.mjs.map +0 -1
  120. package/dist/extract-operation-statements-DsFfxXVZ.mjs +0 -13
  121. package/dist/extract-operation-statements-DsFfxXVZ.mjs.map +0 -1
  122. package/dist/extract-sql-ddl-D9UbZDyz.mjs +0 -26
  123. package/dist/extract-sql-ddl-D9UbZDyz.mjs.map +0 -1
  124. package/dist/inspect-live-schema-yrHAvG71.mjs.map +0 -1
  125. package/dist/migration-status-DUMiH8_G.mjs.map +0 -1
  126. package/dist/migrations-Bo5WtTla.mjs.map +0 -1
  127. package/dist/validate-contract-deps-B_Cs29TL.mjs +0 -37
  128. package/dist/validate-contract-deps-B_Cs29TL.mjs.map +0 -1
  129. package/src/control-api/operations/extract-operation-statements.ts +0 -14
  130. package/src/control-api/operations/extract-sql-ddl.ts +0 -47
package/src/cli.ts CHANGED
@@ -50,8 +50,18 @@ program.configureOutput({
50
50
  writeErr: () => {
51
51
  // Suppress all default error output - we handle errors in exitOverride
52
52
  },
53
- writeOut: () => {
54
- // Suppress all default output - our custom formatters handle everything
53
+ writeOut: (str) => {
54
+ // Commander routes explicitly-requested `--help` (success-path help)
55
+ // through writeOut; per the Style Guide § Output Conventions rule 8,
56
+ // user-requested help is data and goes to stdout. Error-path help
57
+ // (e.g. usage shown after an unknown command) goes through writeErr,
58
+ // which stays suppressed because we render that ourselves with the
59
+ // matching error envelope.
60
+ //
61
+ // Explicit `--version` is short-circuited before `program.parse()`
62
+ // (see the argv pre-scan at the bottom of this file), so it does not
63
+ // reach this writer.
64
+ process.stdout.write(str);
55
65
  },
56
66
  });
57
67
 
@@ -261,14 +271,25 @@ const helpCommand = new Command('help')
261
271
  .action(() => {
262
272
  const flags = parseGlobalFlags({});
263
273
  const helpText = formatRootHelp({ program, flags });
264
- // Help is decoration stderr
265
- process.stderr.write(`${helpText}\n`);
274
+ // The `help` command was invoked explicitly: help is the data the
275
+ // caller asked for. Per Style Guide § Output Conventions rule 8,
276
+ // explicit help goes to stdout with exit code 0.
277
+ process.stdout.write(`${helpText}\n`);
266
278
  process.exit(0);
267
279
  });
268
280
 
269
281
  program.addCommand(helpCommand);
270
282
 
271
- // Set help as the default action when no command is provided
283
+ // Set help as the default action when no command is provided. The user
284
+ // did not invoke `--help`; we are voluntarily showing usage to help them
285
+ // recover from an underspecified invocation, so the help text is
286
+ // decoration around an implicit "what did you want me to do?" and goes
287
+ // to stderr (Style Guide § Output Conventions rule 8).
288
+ //
289
+ // FOLLOW-UP: the exit code here is 0 today, but a no-arg invocation is
290
+ // arguably a usage error (PRECONDITION → exit 2) for consistency with
291
+ // the unknown-command path. Out of scope for the explicit-help routing
292
+ // work; revisit when tightening exit-code semantics across the CLI.
272
293
  program.action(() => {
273
294
  const flags = parseGlobalFlags({});
274
295
  const helpText = formatRootHelp({ program, flags });
@@ -304,7 +325,12 @@ if (args.length > 0) {
304
325
  process.stderr.write(`${helpText}\n`);
305
326
  process.exit(2);
306
327
  } else if (command.commands.length > 0 && args.length === 1) {
307
- // Parent command called with no subcommand - show help and exit with 0
328
+ // Parent command called with no subcommand. Same shape as the
329
+ // no-args case above: the user did not request help, we are
330
+ // voluntarily rendering it as decoration around an underspecified
331
+ // invocation, so it goes to stderr per Style Guide § Output
332
+ // Conventions rule 8. Exit code 0 today; the FOLLOW-UP note on
333
+ // `program.action` applies here too (arguably should be 2).
308
334
  const flags = parseGlobalFlags({});
309
335
  const helpText = formatCommandHelp({ command, flags });
310
336
  process.stderr.write(`${helpText}\n`);
@@ -1,18 +1,13 @@
1
- import { mkdirSync, writeFileSync } from 'node:fs';
2
1
  import { getEmittedArtifactPaths } from '@prisma-next/emitter';
3
2
  import { errorContractConfigMissing } from '@prisma-next/errors/control';
3
+ import { ifDefined } from '@prisma-next/utils/defined';
4
4
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
5
5
  import { Command } from 'commander';
6
6
  import { dirname, relative, resolve } from 'pathe';
7
7
  import { loadConfig } from '../config-loader';
8
- import { createControlClient } from '../control-api/client';
9
- import type { EmitFailure } from '../control-api/types';
10
- import {
11
- CliStructuredError,
12
- errorContractValidationFailed,
13
- errorRuntime,
14
- errorUnexpected,
15
- } from '../utils/cli-errors';
8
+ import { executeContractEmit } from '../control-api/operations/contract-emit';
9
+ import type { ContractEmitResult } from '../control-api/types';
10
+ import { CliStructuredError, errorUnexpected } from '../utils/cli-errors';
16
11
  import {
17
12
  addGlobalOptions,
18
13
  setCommandDescriptions,
@@ -34,74 +29,29 @@ interface ContractEmitOptions extends CommonCommandOptions {
34
29
  readonly config?: string;
35
30
  }
36
31
 
37
- function mapDiagnosticsToIssues(
38
- failure: EmitFailure,
39
- ): ReadonlyArray<{ kind: string; message: string }> {
40
- const diagnostics = failure.diagnostics?.diagnostics ?? [];
41
- return diagnostics.map((diagnostic) => {
42
- const location =
43
- diagnostic.sourceId && diagnostic.span
44
- ? ` (${diagnostic.sourceId}:${diagnostic.span.start.line}:${diagnostic.span.start.column})`
45
- : diagnostic.sourceId
46
- ? ` (${diagnostic.sourceId})`
47
- : '';
48
- return {
49
- kind: diagnostic.code,
50
- message: `${diagnostic.message}${location}`,
51
- };
52
- });
32
+ interface HeaderPaths {
33
+ readonly displayConfigPath: string;
34
+ readonly outputJsonPath: string;
35
+ readonly outputDtsPath: string;
53
36
  }
54
37
 
55
38
  /**
56
- * Maps an EmitFailure to a CliStructuredError for consistent error handling.
39
+ * Pre-load the config just to compute display paths for the styled header. The
40
+ * actual emit work goes through `executeContractEmit`, which loads the config
41
+ * itself; the redundant load here is bounded and lets the header render before
42
+ * the emit spans start.
57
43
  */
58
- function mapEmitFailure(
59
- failure: EmitFailure,
60
- context?: { readonly configPath?: string },
61
- ): CliStructuredError {
62
- if (failure.code === 'CONTRACT_SOURCE_INVALID') {
63
- const issues = mapDiagnosticsToIssues(failure);
64
- return errorRuntime(failure.summary, {
65
- why: failure.why ?? 'Contract source provider failed',
66
- fix: 'Check your contract source provider in prisma-next.config.ts and ensure it returns Result<Contract, Diagnostics>',
67
- ...(issues.length > 0 ? { meta: { issues } } : {}),
68
- });
69
- }
70
-
71
- if (failure.code === 'CONTRACT_VALIDATION_FAILED') {
72
- return errorContractValidationFailed(
73
- failure.why ?? 'Contract validation failed while emitting',
74
- context?.configPath ? { where: { path: context.configPath } } : undefined,
75
- );
76
- }
77
-
78
- if (failure.code === 'EMIT_FAILED') {
79
- return errorRuntime(failure.summary, {
80
- why: failure.why ?? 'Failed to emit contract',
81
- fix: 'Check your contract configuration and ensure the source is valid',
82
- });
83
- }
84
-
85
- // Exhaustive check - TypeScript will error if a new code is added but not handled
86
- const exhaustive: never = failure.code;
87
- throw new Error(`Unhandled EmitFailure code: ${exhaustive}`);
88
- }
44
+ async function resolveHeaderPaths(
45
+ configOption: string | undefined,
46
+ ): Promise<Result<HeaderPaths, CliStructuredError>> {
47
+ const displayConfigPath = configOption
48
+ ? relative(process.cwd(), resolve(configOption))
49
+ : 'prisma-next.config.ts';
89
50
 
90
- /**
91
- * Executes the contract emit command and returns a structured Result.
92
- */
93
- async function executeContractEmitCommand(
94
- options: ContractEmitOptions,
95
- flags: GlobalFlags,
96
- ui: TerminalUI,
97
- startTime: number,
98
- ): Promise<Result<EmitContractResult, CliStructuredError>> {
99
- // Load config
100
51
  let config: Awaited<ReturnType<typeof loadConfig>>;
101
52
  try {
102
- config = await loadConfig(options.config);
53
+ config = await loadConfig(configOption);
103
54
  } catch (error) {
104
- // Convert thrown CliStructuredError to Result
105
55
  if (error instanceof CliStructuredError) {
106
56
  return notOk(error);
107
57
  }
@@ -112,33 +62,19 @@ async function executeContractEmitCommand(
112
62
  );
113
63
  }
114
64
 
115
- const configPath = options.config
116
- ? relative(process.cwd(), resolve(options.config))
117
- : 'prisma-next.config.ts';
118
-
119
- // Resolve contract from config
120
- if (!config.contract) {
65
+ if (!config.contract?.output) {
121
66
  return notOk(
122
67
  errorContractConfigMissing({
123
- why: 'Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ... }',
68
+ why: 'Config.contract.output is required for emit. Define it in your config: contract: { source: ..., output: ... }',
124
69
  }),
125
70
  );
126
71
  }
127
72
 
128
- // Contract config is already normalized by defineConfig() with defaults applied
129
- const contractConfig = config.contract;
130
-
131
- // Resolve artifact paths from config (already normalized by defineConfig() with defaults)
132
- if (!contractConfig.output) {
133
- return notOk(
134
- errorContractConfigMissing({
135
- why: 'Contract config must have output path. This should not happen if defineConfig() was used.',
136
- }),
137
- );
138
- }
139
- let outputPaths: ReturnType<typeof getEmittedArtifactPaths>;
140
73
  try {
141
- outputPaths = getEmittedArtifactPaths(contractConfig.output);
74
+ const { jsonPath: outputJsonPath, dtsPath: outputDtsPath } = getEmittedArtifactPaths(
75
+ config.contract.output,
76
+ );
77
+ return ok({ displayConfigPath, outputJsonPath, outputDtsPath });
142
78
  } catch (error) {
143
79
  return notOk(
144
80
  errorContractConfigMissing({
@@ -146,96 +82,65 @@ async function executeContractEmitCommand(
146
82
  }),
147
83
  );
148
84
  }
149
- const { jsonPath: outputJsonPath, dtsPath: outputDtsPath } = outputPaths;
85
+ }
150
86
 
151
- // Output header to stderr (decoration)
152
- if (!flags.json && !flags.quiet) {
153
- const contractPath = relative(process.cwd(), outputJsonPath);
154
- const typesPath = relative(process.cwd(), outputDtsPath);
155
- const header = formatStyledHeader({
156
- command: 'contract emit',
157
- description: 'Emit your contract artifacts',
158
- url: 'https://pris.ly/contract-emit',
159
- details: [
160
- { label: 'config', value: configPath },
161
- { label: 'contract', value: contractPath },
162
- { label: 'types', value: typesPath },
163
- ],
164
- flags,
165
- });
166
- ui.stderr(header);
87
+ async function executeContractEmitCommand(
88
+ options: ContractEmitOptions,
89
+ flags: GlobalFlags,
90
+ ui: TerminalUI,
91
+ startTime: number,
92
+ ): Promise<Result<EmitContractResult, CliStructuredError>> {
93
+ const headerPathsResult = await resolveHeaderPaths(options.config);
94
+ if (!headerPathsResult.ok) {
95
+ return headerPathsResult;
167
96
  }
97
+ const { displayConfigPath, outputJsonPath, outputDtsPath } = headerPathsResult.value;
168
98
 
169
- // Create control client (no driver needed for emit)
170
- const client = createControlClient({
171
- family: config.family,
172
- target: config.target,
173
- adapter: config.adapter,
174
- extensionPacks: config.extensionPacks ?? [],
175
- });
99
+ if (!flags.json && !flags.quiet) {
100
+ ui.stderr(
101
+ formatStyledHeader({
102
+ command: 'contract emit',
103
+ description: 'Emit your contract artifacts',
104
+ url: 'https://pris.ly/contract-emit',
105
+ details: [
106
+ { label: 'config', value: displayConfigPath },
107
+ { label: 'contract', value: relative(process.cwd(), outputJsonPath) },
108
+ { label: 'types', value: relative(process.cwd(), outputDtsPath) },
109
+ ],
110
+ flags,
111
+ }),
112
+ );
113
+ }
176
114
 
177
- // Create progress adapter
178
115
  const onProgress = createProgressAdapter({ ui, flags });
116
+ const configPath = options.config ? resolve(options.config) : 'prisma-next.config.ts';
179
117
 
118
+ let result: ContractEmitResult;
180
119
  try {
181
- // Call emit with progress callback
182
- const result = await client.emit({
183
- contractConfig: {
184
- source: contractConfig.source,
185
- output: outputJsonPath,
186
- },
187
- onProgress,
188
- });
189
-
190
- // Handle failures by mapping to CLI structured error
191
- if (!result.ok) {
192
- return notOk(mapEmitFailure(result.failure, { configPath }));
193
- }
194
-
195
- // Create directories if needed
196
- mkdirSync(dirname(outputJsonPath), { recursive: true });
197
- mkdirSync(dirname(outputDtsPath), { recursive: true });
198
-
199
- // Write the results to files
200
- writeFileSync(outputJsonPath, result.value.contractJson, 'utf-8');
201
- writeFileSync(outputDtsPath, result.value.contractDts, 'utf-8');
202
-
203
- // Validate that contract.d.ts type imports are resolvable
204
- const { validateContractDeps } = await import('../utils/validate-contract-deps');
205
- const depsValidation = validateContractDeps(result.value.contractDts, dirname(outputDtsPath));
206
- if (depsValidation.warning) {
207
- ui.warn(depsValidation.warning);
208
- }
209
-
210
- // Convert success result to CLI output format
211
- const emitResult: EmitContractResult = {
212
- storageHash: result.value.storageHash,
213
- ...(result.value.executionHash ? { executionHash: result.value.executionHash } : {}),
214
- profileHash: result.value.profileHash,
215
- outDir: dirname(outputJsonPath),
216
- files: {
217
- json: outputJsonPath,
218
- dts: outputDtsPath,
219
- },
220
- timings: { total: Date.now() - startTime },
221
- };
222
-
223
- return ok(emitResult);
120
+ result = await executeContractEmit({ configPath, onProgress });
224
121
  } catch (error) {
225
- // Use static type guard to work across module boundaries
226
122
  if (CliStructuredError.is(error)) {
227
123
  return notOk(error);
228
124
  }
229
-
230
- // Wrap unexpected errors
231
125
  return notOk(
232
126
  errorUnexpected('Unexpected error during contract emit', {
233
127
  why: error instanceof Error ? error.message : String(error),
234
128
  }),
235
129
  );
236
- } finally {
237
- await client.close();
238
130
  }
131
+
132
+ if (result.validationWarning) {
133
+ ui.warn(result.validationWarning);
134
+ }
135
+
136
+ return ok({
137
+ storageHash: result.storageHash,
138
+ ...ifDefined('executionHash', result.executionHash),
139
+ profileHash: result.profileHash,
140
+ outDir: dirname(result.files.json),
141
+ files: result.files,
142
+ timings: { total: Date.now() - startTime },
143
+ });
239
144
  }
240
145
 
241
146
  export function createContractEmitCommand(): Command {
@@ -260,7 +165,6 @@ export function createContractEmitCommand(): Command {
260
165
 
261
166
  const result = await executeContractEmitCommand(options, flags, ui, startTime);
262
167
 
263
- // Handle result - formats output and returns exit code
264
168
  const exitCode = handleResult(result, flags, ui, (emitResult) => {
265
169
  if (flags.json) {
266
170
  ui.output(formatEmitJson(emitResult));
@@ -1,12 +1,6 @@
1
1
  import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { errorRuntime } from '@prisma-next/errors/execution';
3
- import { printPsl, validatePrintableSqlSchemaIR } from '@prisma-next/psl-printer';
4
- import {
5
- createPostgresDefaultMapping,
6
- createPostgresTypeMap,
7
- extractEnumInfo,
8
- parseRawDefault,
9
- } from '@prisma-next/psl-printer/postgres';
3
+ import { printPsl } from '@prisma-next/psl-printer';
10
4
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
11
5
  import { Command } from 'commander';
12
6
  import { dirname, relative } from 'pathe';
@@ -59,26 +53,19 @@ async function executeContractInferCommand(
59
53
  return inspectResult;
60
54
  }
61
55
 
62
- const { config, target, meta } = inspectResult.value;
56
+ const { config, target, meta, pslContractAst } = inspectResult.value;
63
57
 
64
- if (target.familyId !== 'sql') {
58
+ if (!pslContractAst) {
65
59
  return notOk(
66
- errorRuntime(`contract infer is not supported for family "${target.familyId}"`, {
67
- why: 'contract infer currently supports SQL targets only',
68
- fix: 'Use an SQL target (e.g. Postgres) with this command',
60
+ errorRuntime('contract infer is not supported for this family', {
61
+ why: 'The configured family does not implement the PslContractInferCapable capability, so an inferred PSL contract cannot be produced from the live database schema.',
62
+ fix: 'Use a family that supports contract inference (e.g. SQL/Postgres).',
69
63
  }),
70
64
  );
71
65
  }
72
66
 
73
- const schema = validatePrintableSqlSchemaIR(inspectResult.value.schema);
74
67
  const outputPath = resolveContractInferOutputPath(options, config.contract?.output);
75
- const enumInfo = extractEnumInfo(schema.annotations);
76
- const pslContent = printPsl(schema, {
77
- defaultMapping: createPostgresDefaultMapping(),
78
- typeMap: createPostgresTypeMap(enumInfo.typeNames),
79
- enumInfo,
80
- parseRawDefault,
81
- });
68
+ const pslContent = printPsl(pslContractAst);
82
69
 
83
70
  if (existsSync(outputPath) && !flags.json && !flags.quiet) {
84
71
  ui.stderr(`\u26A0 Overwriting existing file: ${relative(process.cwd(), outputPath)}`);
@@ -142,6 +142,7 @@ async function executeDbInitCommand(
142
142
  label: op.label,
143
143
  operationClass: op.operationClass,
144
144
  })),
145
+ ...ifDefined('preview', result.value.plan.preview),
145
146
  },
146
147
  ...(result.value.execution
147
148
  ? {
@@ -112,7 +112,7 @@ async function executeDbUpdateCommand(
112
112
  label: op.label,
113
113
  operationClass: op.operationClass,
114
114
  })),
115
- ...ifDefined('sql', result.value.plan.sql),
115
+ ...ifDefined('preview', result.value.plan.preview),
116
116
  },
117
117
  ...ifDefined(
118
118
  'execution',
@@ -1,5 +1,5 @@
1
1
  import type { CoreSchemaView } from '@prisma-next/framework-components/control';
2
- import { validatePrintableSqlSchemaIR } from '@prisma-next/psl-printer';
2
+ import type { PslDocumentAst } from '@prisma-next/framework-components/psl-ast';
3
3
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
4
4
  import { relative, resolve } from 'pathe';
5
5
  import { loadConfig } from '../config-loader';
@@ -33,6 +33,12 @@ export interface InspectLiveSchemaResult {
33
33
  readonly config: LoadedCliConfig;
34
34
  readonly schema: unknown;
35
35
  readonly schemaView: CoreSchemaView | undefined;
36
+ /**
37
+ * PSL AST inferred from the introspected schema, when the configured family
38
+ * implements `PslContractInferCapable`. `undefined` for families that do not
39
+ * support inference (e.g. Mongo today).
40
+ */
41
+ readonly pslContractAst: PslDocumentAst | undefined;
36
42
  readonly target: {
37
43
  readonly familyId: string;
38
44
  readonly id: string;
@@ -122,14 +128,12 @@ export async function inspectLiveSchema(
122
128
  const onProgress = createProgressAdapter({ ui, flags });
123
129
 
124
130
  try {
125
- const schemaIR = await client.introspect({
131
+ const schema = await client.introspect({
126
132
  connection: dbConnection,
127
133
  onProgress,
128
134
  });
129
- // TODO(TML-2251): Remove SQL-specific branching — SQL should use the same family-agnostic path as Mongo.
130
- const schema =
131
- config.family.familyId === 'sql' ? validatePrintableSqlSchemaIR(schemaIR) : schemaIR;
132
135
  const schemaView = client.toSchemaView(schema);
136
+ const pslContractAst = client.inferPslContract(schema);
133
137
 
134
138
  const dbUrl = typeof dbConnection === 'string' ? maskConnectionUrl(dbConnection) : undefined;
135
139
 
@@ -137,6 +141,7 @@ export async function inspectLiveSchema(
137
141
  config,
138
142
  schema,
139
143
  schemaView,
144
+ pslContractAst,
140
145
  target: {
141
146
  familyId: config.family.familyId,
142
147
  id: config.target.targetId,