@prisma-next/cli 0.5.0-dev.3 → 0.5.0-dev.31

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 (169) hide show
  1. package/README.md +56 -21
  2. package/dist/agent-skill-mongo.md +63 -31
  3. package/dist/agent-skill-postgres.md +1 -1
  4. package/dist/cli-errors-By1iVE3z.mjs +34 -0
  5. package/dist/cli-errors-By1iVE3z.mjs.map +1 -0
  6. package/dist/{cli-errors-C0JhVj0c.d.mts → cli-errors-DDeVsP2Y.d.mts} +1 -0
  7. package/dist/cli.mjs +131 -15
  8. package/dist/cli.mjs.map +1 -1
  9. package/dist/{client-TG7rbCWT.mjs → client-keSCAgjW.mjs} +43 -19
  10. package/dist/client-keSCAgjW.mjs.map +1 -0
  11. package/dist/commands/contract-emit.d.mts.map +1 -1
  12. package/dist/commands/contract-emit.mjs +7 -2
  13. package/dist/commands/contract-infer.d.mts.map +1 -1
  14. package/dist/commands/contract-infer.mjs +8 -2
  15. package/dist/commands/db-init.d.mts.map +1 -1
  16. package/dist/commands/db-init.mjs +11 -9
  17. package/dist/commands/db-init.mjs.map +1 -1
  18. package/dist/commands/db-schema.mjs +8 -5
  19. package/dist/commands/db-schema.mjs.map +1 -1
  20. package/dist/commands/db-sign.mjs +8 -7
  21. package/dist/commands/db-sign.mjs.map +1 -1
  22. package/dist/commands/db-update.mjs +10 -9
  23. package/dist/commands/db-update.mjs.map +1 -1
  24. package/dist/commands/db-verify.mjs +10 -9
  25. package/dist/commands/db-verify.mjs.map +1 -1
  26. package/dist/commands/migration-apply.d.mts +2 -2
  27. package/dist/commands/migration-apply.d.mts.map +1 -1
  28. package/dist/commands/migration-apply.mjs +15 -38
  29. package/dist/commands/migration-apply.mjs.map +1 -1
  30. package/dist/commands/migration-new.d.mts.map +1 -1
  31. package/dist/commands/migration-new.mjs +24 -30
  32. package/dist/commands/migration-new.mjs.map +1 -1
  33. package/dist/commands/migration-plan.d.mts +14 -5
  34. package/dist/commands/migration-plan.d.mts.map +1 -1
  35. package/dist/commands/migration-plan.mjs +45 -47
  36. package/dist/commands/migration-plan.mjs.map +1 -1
  37. package/dist/commands/migration-ref.d.mts +6 -4
  38. package/dist/commands/migration-ref.d.mts.map +1 -1
  39. package/dist/commands/migration-ref.mjs +31 -40
  40. package/dist/commands/migration-ref.mjs.map +1 -1
  41. package/dist/commands/migration-show.d.mts +13 -7
  42. package/dist/commands/migration-show.d.mts.map +1 -1
  43. package/dist/commands/migration-show.mjs +28 -29
  44. package/dist/commands/migration-show.mjs.map +1 -1
  45. package/dist/commands/migration-status.d.mts +5 -4
  46. package/dist/commands/migration-status.d.mts.map +1 -1
  47. package/dist/commands/migration-status.mjs +7 -2
  48. package/dist/{config-loader-_W4T21X1.mjs → config-loader-ih8ViDb_.mjs} +2 -2
  49. package/dist/config-loader-ih8ViDb_.mjs.map +1 -0
  50. package/dist/config-loader.mjs +1 -1
  51. package/dist/contract-emit-DS5NzZh2.mjs +6 -0
  52. package/dist/contract-emit-DWtGQYCD.mjs +150 -0
  53. package/dist/contract-emit-DWtGQYCD.mjs.map +1 -0
  54. package/dist/contract-emit-RZBWzkop.mjs +329 -0
  55. package/dist/contract-emit-RZBWzkop.mjs.map +1 -0
  56. package/dist/{contract-enrichment-CGW6mm-E.mjs → contract-enrichment-4Ptgw3Pe.mjs} +1 -1
  57. package/dist/{contract-enrichment-CGW6mm-E.mjs.map → contract-enrichment-4Ptgw3Pe.mjs.map} +1 -1
  58. package/dist/{contract-infer-BP3DrGgz.mjs → contract-infer-GztVCOCJ.mjs} +11 -19
  59. package/dist/contract-infer-GztVCOCJ.mjs.map +1 -0
  60. package/dist/exports/control-api.d.mts +78 -21
  61. package/dist/exports/control-api.d.mts.map +1 -1
  62. package/dist/exports/control-api.mjs +7 -5
  63. package/dist/exports/index.mjs +8 -3
  64. package/dist/exports/index.mjs.map +1 -1
  65. package/dist/exports/init-output.d.mts +39 -0
  66. package/dist/exports/init-output.d.mts.map +1 -0
  67. package/dist/exports/init-output.mjs +3 -0
  68. package/dist/{framework-components-DfZKQBQ2.mjs → framework-components-Bgcre3Z6.mjs} +2 -2
  69. package/dist/{framework-components-DfZKQBQ2.mjs.map → framework-components-Bgcre3Z6.mjs.map} +1 -1
  70. package/dist/init-DAbQMxIR.mjs +2062 -0
  71. package/dist/init-DAbQMxIR.mjs.map +1 -0
  72. package/dist/{inspect-live-schema-DWzf4Q_m.mjs → inspect-live-schema-BaR9ISwa.mjs} +9 -9
  73. package/dist/inspect-live-schema-BaR9ISwa.mjs.map +1 -0
  74. package/dist/migration-cli.d.mts +41 -11
  75. package/dist/migration-cli.d.mts.map +1 -1
  76. package/dist/migration-cli.mjs +308 -84
  77. package/dist/migration-cli.mjs.map +1 -1
  78. package/dist/{migration-command-scaffold-CLMD302g.mjs → migration-command-scaffold-D1dWuEWQ.mjs} +7 -7
  79. package/dist/{migration-command-scaffold-CLMD302g.mjs.map → migration-command-scaffold-D1dWuEWQ.mjs.map} +1 -1
  80. package/dist/{migration-status-B0HLF7So.mjs → migration-status-CP5k8O5i.mjs} +21 -35
  81. package/dist/migration-status-CP5k8O5i.mjs.map +1 -0
  82. package/dist/{migrations-B0dOQlk0.mjs → migrations-MEoKMiV5.mjs} +42 -21
  83. package/dist/migrations-MEoKMiV5.mjs.map +1 -0
  84. package/dist/output-BpcQrnnq.mjs +103 -0
  85. package/dist/output-BpcQrnnq.mjs.map +1 -0
  86. package/dist/{progress-adapter-B-YvmcDu.mjs → progress-adapter-DgRGldpT.mjs} +1 -1
  87. package/dist/{progress-adapter-B-YvmcDu.mjs.map → progress-adapter-DgRGldpT.mjs.map} +1 -1
  88. package/dist/quick-reference-mongo.md +34 -13
  89. package/dist/quick-reference-postgres.md +11 -9
  90. package/dist/{result-handler-CIyu0Pdt.mjs → result-handler-BmVh8AeV.mjs} +12 -93
  91. package/dist/result-handler-BmVh8AeV.mjs.map +1 -0
  92. package/dist/{terminal-ui-C5k88MmW.mjs → terminal-ui-u2YgKghu.mjs} +76 -2
  93. package/dist/terminal-ui-u2YgKghu.mjs.map +1 -0
  94. package/dist/{verify-BxiVp50b.mjs → verify-BT9tgCOH.mjs} +2 -2
  95. package/dist/{verify-BxiVp50b.mjs.map → verify-BT9tgCOH.mjs.map} +1 -1
  96. package/package.json +21 -15
  97. package/src/cli.ts +32 -6
  98. package/src/commands/contract-emit.ts +67 -163
  99. package/src/commands/contract-infer.ts +7 -20
  100. package/src/commands/db-init.ts +1 -0
  101. package/src/commands/db-update.ts +1 -1
  102. package/src/commands/init/detect-pnpm-catalog.ts +141 -0
  103. package/src/commands/init/errors.ts +254 -0
  104. package/src/commands/init/exit-codes.ts +62 -0
  105. package/src/commands/init/hygiene-gitattributes.ts +97 -0
  106. package/src/commands/init/hygiene-gitignore.ts +48 -0
  107. package/src/commands/init/hygiene-package-scripts.ts +91 -0
  108. package/src/commands/init/index.ts +112 -7
  109. package/src/commands/init/init.ts +766 -144
  110. package/src/commands/init/inputs.ts +421 -0
  111. package/src/commands/init/output.ts +147 -0
  112. package/src/commands/init/probe-db.ts +308 -0
  113. package/src/commands/init/reinit-cleanup.ts +83 -0
  114. package/src/commands/init/templates/agent-skill-mongo.md +63 -31
  115. package/src/commands/init/templates/agent-skill-postgres.md +1 -1
  116. package/src/commands/init/templates/agent-skill.ts +25 -3
  117. package/src/commands/init/templates/code-templates.ts +125 -32
  118. package/src/commands/init/templates/env.ts +80 -0
  119. package/src/commands/init/templates/quick-reference-mongo.md +34 -13
  120. package/src/commands/init/templates/quick-reference-postgres.md +11 -9
  121. package/src/commands/init/templates/quick-reference.ts +42 -3
  122. package/src/commands/init/templates/tsconfig.ts +167 -5
  123. package/src/commands/inspect-live-schema.ts +10 -5
  124. package/src/commands/migration-apply.ts +16 -51
  125. package/src/commands/migration-new.ts +26 -32
  126. package/src/commands/migration-plan.ts +80 -55
  127. package/src/commands/migration-ref.ts +40 -54
  128. package/src/commands/migration-show.ts +53 -36
  129. package/src/commands/migration-status.ts +33 -50
  130. package/src/config-path-validation.ts +0 -1
  131. package/src/control-api/client.ts +21 -0
  132. package/src/control-api/operations/contract-emit.ts +198 -115
  133. package/src/control-api/operations/db-init.ts +8 -5
  134. package/src/control-api/operations/db-update.ts +8 -5
  135. package/src/control-api/operations/migration-apply.ts +29 -9
  136. package/src/control-api/types.ts +61 -7
  137. package/src/exports/control-api.ts +2 -1
  138. package/src/exports/init-output.ts +10 -0
  139. package/src/migration-cli.ts +445 -122
  140. package/src/utils/cli-errors.ts +49 -2
  141. package/src/utils/command-helpers.ts +13 -26
  142. package/src/utils/emit-queue.ts +26 -0
  143. package/src/utils/formatters/graph-migration-mapper.ts +2 -2
  144. package/src/utils/formatters/migrations.ts +62 -26
  145. package/src/utils/publish-contract-artifact-pair.ts +134 -0
  146. package/dist/cli-errors-DHq6GQGu.mjs +0 -5
  147. package/dist/client-TG7rbCWT.mjs.map +0 -1
  148. package/dist/config-loader-_W4T21X1.mjs.map +0 -1
  149. package/dist/contract-emit-CNYyzJwF.mjs +0 -195
  150. package/dist/contract-emit-CNYyzJwF.mjs.map +0 -1
  151. package/dist/contract-emit-CQfj7xJn.mjs +0 -122
  152. package/dist/contract-emit-CQfj7xJn.mjs.map +0 -1
  153. package/dist/contract-emit-fhNwwhkQ.mjs +0 -4
  154. package/dist/contract-infer-BP3DrGgz.mjs.map +0 -1
  155. package/dist/extract-operation-statements-DZUJNmL3.mjs +0 -13
  156. package/dist/extract-operation-statements-DZUJNmL3.mjs.map +0 -1
  157. package/dist/extract-sql-ddl-DDMX-9mz.mjs +0 -26
  158. package/dist/extract-sql-ddl-DDMX-9mz.mjs.map +0 -1
  159. package/dist/init-CQfo_4Ro.mjs +0 -430
  160. package/dist/init-CQfo_4Ro.mjs.map +0 -1
  161. package/dist/inspect-live-schema-DWzf4Q_m.mjs.map +0 -1
  162. package/dist/migration-status-B0HLF7So.mjs.map +0 -1
  163. package/dist/migrations-B0dOQlk0.mjs.map +0 -1
  164. package/dist/result-handler-CIyu0Pdt.mjs.map +0 -1
  165. package/dist/terminal-ui-C5k88MmW.mjs.map +0 -1
  166. package/dist/validate-contract-deps-esa-VQ0h.mjs +0 -37
  167. package/dist/validate-contract-deps-esa-VQ0h.mjs.map +0 -1
  168. package/src/control-api/operations/extract-operation-statements.ts +0 -14
  169. package/src/control-api/operations/extract-sql-ddl.ts +0 -47
@@ -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',
@@ -0,0 +1,141 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'pathe';
3
+
4
+ /**
5
+ * Catalog entry detected in a `pnpm-workspace.yaml` that overrides one of
6
+ * the packages `init` installs. The `version` is the raw value as written
7
+ * in the workspace file (no normalisation), so the warning surfaces the
8
+ * exact text the user can search for if they want to find the override.
9
+ */
10
+ export interface PnpmCatalogOverride {
11
+ readonly name: string;
12
+ readonly version: string;
13
+ }
14
+
15
+ /**
16
+ * Result of scanning for a pnpm workspace catalog that overrides any of
17
+ * the packages `init` is about to install. Returns `null` when no
18
+ * `pnpm-workspace.yaml` is found in `baseDir` or any ancestor; an empty
19
+ * `entries` array means a workspace exists but contains none of our
20
+ * packages.
21
+ */
22
+ export interface PnpmCatalogScanResult {
23
+ /** Absolute path of the `pnpm-workspace.yaml` that was consulted. */
24
+ readonly workspaceFile: string;
25
+ readonly entries: readonly PnpmCatalogOverride[];
26
+ }
27
+
28
+ /**
29
+ * Walks up from `baseDir` looking for `pnpm-workspace.yaml`, then scans
30
+ * its top-level `catalog:` block for entries that match any of `packages`.
31
+ *
32
+ * Implements FR7.3 / Spec Decision 8 (honour-and-warn): when `init` runs
33
+ * inside a pnpm workspace whose catalog overrides one of the packages it
34
+ * installs, surface a structured warning so the user knows the catalog
35
+ * version (not the published `latest`) is what ended up in their
36
+ * `node_modules`. pnpm itself does this silently; the warning closes the
37
+ * "looks fine, must be wrong version six months later" gap.
38
+ *
39
+ * Notes / scope:
40
+ *
41
+ * - We only inspect the unnamed top-level `catalog:` block. pnpm also
42
+ * supports `catalogs:` (plural — *named* catalogs referenced via
43
+ * `catalog:foo` specifiers); those don't apply to a vanilla
44
+ * `pnpm add prisma-next` invocation, so we skip them.
45
+ * - We don't validate YAML syntax exhaustively. The file format pnpm
46
+ * ships is line-oriented and well-known; a minimal regex is more
47
+ * robust than depending on a YAML parser for one warning.
48
+ * - We don't compare against the registry's `latest` — pnpm uses the
49
+ * catalog version regardless, so the warning fires whenever a match
50
+ * exists. The user-facing copy explains how to opt out.
51
+ */
52
+ export function detectPnpmCatalogOverrides(
53
+ baseDir: string,
54
+ packages: readonly string[],
55
+ ): PnpmCatalogScanResult | null {
56
+ const workspaceFile = findNearestPnpmWorkspaceFile(baseDir);
57
+ if (workspaceFile === null) {
58
+ return null;
59
+ }
60
+
61
+ const contents = readFileSync(workspaceFile, 'utf-8');
62
+ const catalog = extractCatalogBlock(contents);
63
+ if (catalog === null) {
64
+ return { workspaceFile, entries: [] };
65
+ }
66
+
67
+ const wanted = new Set(packages);
68
+ const entries: PnpmCatalogOverride[] = [];
69
+ for (const [name, version] of catalog) {
70
+ if (wanted.has(name)) {
71
+ entries.push({ name, version });
72
+ }
73
+ }
74
+ return { workspaceFile, entries };
75
+ }
76
+
77
+ function findNearestPnpmWorkspaceFile(baseDir: string): string | null {
78
+ let dir = baseDir;
79
+ let prev = '';
80
+ while (dir !== prev) {
81
+ const candidate = join(dir, 'pnpm-workspace.yaml');
82
+ if (existsSync(candidate)) {
83
+ return candidate;
84
+ }
85
+ prev = dir;
86
+ dir = dirname(dir);
87
+ }
88
+ return null;
89
+ }
90
+
91
+ /**
92
+ * Returns the entries inside the top-level `catalog:` block as `[name, version]`
93
+ * pairs in document order, or `null` when no `catalog:` block exists.
94
+ *
95
+ * The parser is intentionally minimal: it reads line-by-line, locates the
96
+ * top-level `catalog:` line (no leading whitespace), then collects every
97
+ * subsequent indented line of the form `<key>: <value>` until the next
98
+ * top-level key (or end of file). Quotes around `<key>` and `<value>`
99
+ * are stripped; comments (`#…`) are ignored.
100
+ */
101
+ function extractCatalogBlock(contents: string): Array<[string, string]> | null {
102
+ const lines = contents.split(/\r?\n/);
103
+ const startIdx = lines.findIndex((line) => /^catalog\s*:\s*$/.test(line));
104
+ if (startIdx === -1) {
105
+ return null;
106
+ }
107
+
108
+ const entries: Array<[string, string]> = [];
109
+ for (let i = startIdx + 1; i < lines.length; i++) {
110
+ const raw = lines[i] ?? '';
111
+ if (raw.trim() === '' || /^\s*#/.test(raw)) {
112
+ continue;
113
+ }
114
+ if (!/^\s/.test(raw)) {
115
+ // Hit the next top-level key — catalog block ended.
116
+ break;
117
+ }
118
+ const match = raw.match(/^\s+(?:'([^']+)'|"([^"]+)"|([^:\s'"]+))\s*:\s*(.*?)\s*(?:#.*)?$/);
119
+ if (!match) {
120
+ continue;
121
+ }
122
+ const name = match[1] ?? match[2] ?? match[3];
123
+ if (name === undefined) continue;
124
+ const rawValue = match[4] ?? '';
125
+ const version = stripQuotes(rawValue.trim());
126
+ if (version === '') continue;
127
+ entries.push([name, version]);
128
+ }
129
+ return entries;
130
+ }
131
+
132
+ function stripQuotes(value: string): string {
133
+ if (value.length >= 2) {
134
+ const first = value[0];
135
+ const last = value[value.length - 1];
136
+ if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
137
+ return value.slice(1, -1);
138
+ }
139
+ }
140
+ return value;
141
+ }