@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
package/src/cli.ts CHANGED
@@ -1,13 +1,41 @@
1
1
  import { Command } from 'commander';
2
2
  import { createContractEmitCommand } from './commands/contract-emit';
3
+ import { createContractInferCommand } from './commands/contract-infer';
4
+ import { createInitCommand } from './commands/init';
5
+ import { installShutdownHandlers } from './utils/shutdown';
6
+
7
+ // Install SIGINT/SIGTERM handlers before anything else
8
+ installShutdownHandlers();
9
+
3
10
  import { createDbInitCommand } from './commands/db-init';
4
- import { createDbIntrospectCommand } from './commands/db-introspect';
5
- import { createDbSchemaVerifyCommand } from './commands/db-schema-verify';
11
+ import { createDbSchemaCommand } from './commands/db-schema';
6
12
  import { createDbSignCommand } from './commands/db-sign';
13
+ import { createDbUpdateCommand } from './commands/db-update';
7
14
  import { createDbVerifyCommand } from './commands/db-verify';
15
+ import { createMigrationApplyCommand } from './commands/migration-apply';
16
+ import { createMigrationNewCommand } from './commands/migration-new';
17
+ import { createMigrationPlanCommand } from './commands/migration-plan';
18
+ import { createMigrationRefCommand } from './commands/migration-ref';
19
+ import { createMigrationShowCommand } from './commands/migration-show';
20
+ import { createMigrationStatusCommand } from './commands/migration-status';
21
+ import { createMigrationVerifyCommand } from './commands/migration-verify';
8
22
  import { setCommandDescriptions } from './utils/command-helpers';
23
+ import { formatCommandHelp, formatRootHelp } from './utils/formatters/help';
9
24
  import { parseGlobalFlags } from './utils/global-flags';
10
- import { formatCommandHelp, formatRootHelp } from './utils/output';
25
+ import { suggestCommands } from './utils/suggest-command';
26
+
27
+ /**
28
+ * Formats the "Did you mean ...?" hint for an unknown command.
29
+ */
30
+ function formatSuggestion(input: string, candidates: readonly string[]): string {
31
+ const suggestions = suggestCommands(
32
+ input,
33
+ candidates.map((c) => c),
34
+ );
35
+ if (suggestions.length === 0) return '';
36
+ if (suggestions.length === 1) return `\nDid you mean ${suggestions[0]}?\n`;
37
+ return `\nDid you mean one of these?\n${suggestions.map((s) => ` ${s}`).join('\n')}\n`;
38
+ }
11
39
 
12
40
  const program = new Command();
13
41
 
@@ -43,14 +71,11 @@ program.configureHelp({
43
71
  // Commands handle structured errors themselves via process.exit()
44
72
  program.exitOverride((err) => {
45
73
  if (err) {
46
- // Help requests are not errors - allow Commander to output help and exit normally
47
- // Commander throws errors with codes like 'commander.help', 'commander.helpDisplayed', or 'outputHelp'
48
74
  const errorCode = (err as { code?: string }).code;
49
75
  const errorMessage = String(err.message ?? '');
50
76
  const errorName = err.name ?? '';
51
77
 
52
- // Check for unknown command errors first (before other checks)
53
- // Commander.js uses code 'commander.unknownCommand' or error message contains 'unknown command'
78
+ // Unknown command/argument exit 2 (CLI usage error)
54
79
  const isUnknownCommandError =
55
80
  errorCode === 'commander.unknownCommand' ||
56
81
  errorCode === 'commander.unknownArgument' ||
@@ -58,40 +83,34 @@ program.exitOverride((err) => {
58
83
  (errorMessage.includes('unknown command') || errorMessage.includes('unknown argument')));
59
84
  if (isUnknownCommandError) {
60
85
  const flags = parseGlobalFlags({});
61
- // Extract the command/subcommand name from the error message
62
- // Error message format: "unknown command 'command-name'"
63
86
  const match = errorMessage.match(/unknown command ['"]([^'"]+)['"]/);
64
87
  const commandName = match ? match[1] : process.argv[3] || process.argv[2] || 'unknown';
65
88
 
66
- // Determine which command context we're in
67
- // Check if the first argument is a recognized parent command
68
89
  const firstArg = process.argv[2];
69
90
  const parentCommand = firstArg
70
91
  ? program.commands.find((cmd) => cmd.name() === firstArg)
71
92
  : undefined;
72
93
 
73
94
  if (parentCommand && commandName !== firstArg) {
74
- // Unrecognized subcommand - show parent command help
75
- // eslint-disable-next-line no-console
76
- console.error(`Unknown command: ${commandName}`);
77
- // eslint-disable-next-line no-console
78
- console.error('');
95
+ const subNames = parentCommand.commands.map((c) => c.name());
96
+ process.stderr.write(
97
+ `Unknown command: ${commandName}${formatSuggestion(commandName!, subNames)}\n`,
98
+ );
79
99
  const helpText = formatCommandHelp({ command: parentCommand, flags });
80
- // eslint-disable-next-line no-console
81
- console.log(helpText);
100
+ process.stderr.write(`${helpText}\n`);
82
101
  } else {
83
- // Unrecognized top-level command - show root help
84
- // eslint-disable-next-line no-console
85
- console.error(`Unknown command: ${commandName}`);
86
- // eslint-disable-next-line no-console
87
- console.error('');
102
+ const topNames = program.commands.map((c) => c.name());
103
+ process.stderr.write(
104
+ `Unknown command: ${commandName}${formatSuggestion(commandName!, topNames)}\n`,
105
+ );
88
106
  const helpText = formatRootHelp({ program, flags });
89
- // eslint-disable-next-line no-console
90
- console.log(helpText);
107
+ process.stderr.write(`${helpText}\n`);
91
108
  }
92
- process.exit(1);
109
+ process.exit(2);
93
110
  return;
94
111
  }
112
+
113
+ // Help requests → exit 0
95
114
  const isHelpError =
96
115
  errorCode === 'commander.help' ||
97
116
  errorCode === 'commander.helpDisplayed' ||
@@ -103,25 +122,22 @@ program.exitOverride((err) => {
103
122
  process.exit(0);
104
123
  return;
105
124
  }
106
- // Missing required arguments/subcommands - show help and exit with 0
107
- // Commander throws errors with code 'commander.missingArgument' or 'commander.missingMandatoryOptionValue'
108
- // or when a command with subcommands is called without a subcommand
125
+
126
+ // Missing required arguments exit 2 (CLI usage error)
109
127
  const isMissingArgumentError =
110
128
  errorCode === 'commander.missingArgument' ||
111
129
  errorCode === 'commander.missingMandatoryOptionValue' ||
112
130
  (errorName === 'CommanderError' &&
113
131
  (errorMessage.includes('missing') || errorMessage.includes('required')));
114
132
  if (isMissingArgumentError) {
115
- // Help was already displayed by Commander.js, just exit with 0
116
- process.exit(0);
133
+ process.exit(2);
117
134
  return;
118
135
  }
119
- // Unhandled error - fail fast with exit code 1
120
- // eslint-disable-next-line no-console
121
- console.error(`Unhandled error: ${err.message}`);
136
+
137
+ // Unhandled error → exit 1
138
+ process.stderr.write(`Unhandled error: ${err.message}\n`);
122
139
  if (err.stack) {
123
- // eslint-disable-next-line no-console
124
- console.error(err.stack);
140
+ process.stderr.write(`${err.stack}\n`);
125
141
  }
126
142
  process.exit(1);
127
143
  }
@@ -148,6 +164,10 @@ contractCommand.configureHelp({
148
164
  const contractEmitCommand = createContractEmitCommand();
149
165
  contractCommand.addCommand(contractEmitCommand);
150
166
 
167
+ // Add infer subcommand to contract
168
+ const contractInferCommand = createContractInferCommand();
169
+ contractCommand.addCommand(contractInferCommand);
170
+
151
171
  // Register contract command
152
172
  program.addCommand(contractCommand);
153
173
 
@@ -175,13 +195,13 @@ dbCommand.addCommand(dbVerifyCommand);
175
195
  const dbInitCommand = createDbInitCommand();
176
196
  dbCommand.addCommand(dbInitCommand);
177
197
 
178
- // Add introspect subcommand to db
179
- const dbIntrospectCommand = createDbIntrospectCommand();
180
- dbCommand.addCommand(dbIntrospectCommand);
198
+ // Add update subcommand to db
199
+ const dbUpdateCommand = createDbUpdateCommand();
200
+ dbCommand.addCommand(dbUpdateCommand);
181
201
 
182
- // Add schema-verify subcommand to db
183
- const dbSchemaVerifyCommand = createDbSchemaVerifyCommand();
184
- dbCommand.addCommand(dbSchemaVerifyCommand);
202
+ // Add schema subcommand to db
203
+ const dbSchemaCommand = createDbSchemaCommand();
204
+ dbCommand.addCommand(dbSchemaCommand);
185
205
 
186
206
  // Add sign subcommand to db
187
207
  const dbSignCommand = createDbSignCommand();
@@ -190,6 +210,49 @@ dbCommand.addCommand(dbSignCommand);
190
210
  // Register db command
191
211
  program.addCommand(dbCommand);
192
212
 
213
+ // Register migration subcommand
214
+ const migrationCommand = new Command('migration');
215
+ setCommandDescriptions(
216
+ migrationCommand,
217
+ 'On-disk migration management commands',
218
+ 'Plan, apply, verify, and scaffold on-disk migration packages. Migrations are\n' +
219
+ 'contract-to-contract edges stored as versioned directories under migrations/.',
220
+ );
221
+ migrationCommand.configureHelp({
222
+ formatHelp: (cmd) => {
223
+ const flags = parseGlobalFlags({});
224
+ return formatCommandHelp({ command: cmd, flags });
225
+ },
226
+ subcommandDescription: () => '',
227
+ });
228
+
229
+ const migrationPlanCommand = createMigrationPlanCommand();
230
+ migrationCommand.addCommand(migrationPlanCommand);
231
+
232
+ const migrationNewCommand = createMigrationNewCommand();
233
+ migrationCommand.addCommand(migrationNewCommand);
234
+
235
+ const migrationShowCommand = createMigrationShowCommand();
236
+ migrationCommand.addCommand(migrationShowCommand);
237
+
238
+ const migrationStatusCommand = createMigrationStatusCommand();
239
+ migrationCommand.addCommand(migrationStatusCommand);
240
+
241
+ const migrationVerifyCommand = createMigrationVerifyCommand();
242
+ migrationCommand.addCommand(migrationVerifyCommand);
243
+
244
+ const migrationApplyCommand = createMigrationApplyCommand();
245
+ migrationCommand.addCommand(migrationApplyCommand);
246
+
247
+ const migrationRefCommand = createMigrationRefCommand();
248
+ migrationCommand.addCommand(migrationRefCommand);
249
+
250
+ program.addCommand(migrationCommand);
251
+
252
+ // Register init command (top-level, not nested)
253
+ const initCommand = createInitCommand();
254
+ program.addCommand(initCommand);
255
+
193
256
  // Create help command
194
257
  const helpCommand = new Command('help')
195
258
  .description('Show usage instructions')
@@ -202,8 +265,8 @@ const helpCommand = new Command('help')
202
265
  .action(() => {
203
266
  const flags = parseGlobalFlags({});
204
267
  const helpText = formatRootHelp({ program, flags });
205
- // eslint-disable-next-line no-console
206
- console.log(helpText);
268
+ // Help is decoration → stderr
269
+ process.stderr.write(`${helpText}\n`);
207
270
  process.exit(0);
208
271
  });
209
272
 
@@ -213,8 +276,7 @@ program.addCommand(helpCommand);
213
276
  program.action(() => {
214
277
  const flags = parseGlobalFlags({});
215
278
  const helpText = formatRootHelp({ program, flags });
216
- // eslint-disable-next-line no-console
217
- console.log(helpText);
279
+ process.stderr.write(`${helpText}\n`);
218
280
  process.exit(0);
219
281
  });
220
282
 
@@ -225,8 +287,8 @@ if (args.length > 0) {
225
287
  const commandName = args[0];
226
288
  // Handle version option explicitly since we suppress default output
227
289
  if (commandName === '--version' || commandName === '-V') {
228
- // eslint-disable-next-line no-console
229
- console.log(program.version());
290
+ // Version is data → stdout
291
+ process.stdout.write(`${program.version()}\n`);
230
292
  process.exit(0);
231
293
  }
232
294
  // Skip command check for global options like --help, -h
@@ -236,22 +298,20 @@ if (args.length > 0) {
236
298
  const command = program.commands.find((cmd) => cmd.name() === commandName);
237
299
 
238
300
  if (!command) {
239
- // Unrecognized command - show error message and usage
301
+ // Unrecognized command exit 2 (CLI usage error)
240
302
  const flags = parseGlobalFlags({});
241
- // eslint-disable-next-line no-console
242
- console.error(`Unknown command: ${commandName}`);
243
- // eslint-disable-next-line no-console
244
- console.error('');
303
+ const topNames = program.commands.map((c) => c.name());
304
+ process.stderr.write(
305
+ `Unknown command: ${commandName}${formatSuggestion(commandName!, topNames)}\n`,
306
+ );
245
307
  const helpText = formatRootHelp({ program, flags });
246
- // eslint-disable-next-line no-console
247
- console.log(helpText);
248
- process.exit(1);
308
+ process.stderr.write(`${helpText}\n`);
309
+ process.exit(2);
249
310
  } else if (command.commands.length > 0 && args.length === 1) {
250
311
  // Parent command called with no subcommand - show help and exit with 0
251
312
  const flags = parseGlobalFlags({});
252
313
  const helpText = formatCommandHelp({ command, flags });
253
- // eslint-disable-next-line no-console
254
- console.log(helpText);
314
+ process.stderr.write(`${helpText}\n`);
255
315
  process.exit(0);
256
316
  }
257
317
  }
@@ -1,50 +1,79 @@
1
1
  import { mkdirSync, writeFileSync } from 'node:fs';
2
- import { dirname, relative, resolve } from 'node:path';
3
- import { errorContractConfigMissing } from '@prisma-next/core-control-plane/errors';
2
+ import { errorContractConfigMissing } from '@prisma-next/errors/control';
4
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
7
  import { createControlClient } from '../control-api/client';
8
- import type { EmitContractSource, EmitFailure } from '../control-api/types';
9
- import { CliStructuredError, errorRuntime, errorUnexpected } from '../utils/cli-errors';
10
- import { setCommandDescriptions } from '../utils/command-helpers';
11
- import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
8
+ import type { EmitFailure } from '../control-api/types';
9
+ import {
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';
12
20
  import {
13
21
  type EmitContractResult,
14
- formatCommandHelp,
15
22
  formatEmitJson,
16
23
  formatEmitOutput,
17
- formatStyledHeader,
18
- formatSuccessMessage,
19
- } 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';
20
28
  import { createProgressAdapter } from '../utils/progress-adapter';
21
29
  import { handleResult } from '../utils/result-handler';
30
+ import { TerminalUI } from '../utils/terminal-ui';
22
31
 
23
- interface ContractEmitOptions {
32
+ interface ContractEmitOptions extends CommonCommandOptions {
24
33
  readonly config?: string;
25
- readonly json?: string | boolean;
26
- readonly quiet?: boolean;
27
- readonly q?: boolean;
28
- readonly verbose?: boolean;
29
- readonly v?: boolean;
30
- readonly vv?: boolean;
31
- readonly trace?: boolean;
32
- readonly timestamps?: boolean;
33
- readonly color?: boolean;
34
- 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
+ });
35
52
  }
36
53
 
37
54
  /**
38
55
  * Maps an EmitFailure to a CliStructuredError for consistent error handling.
39
56
  */
40
- function mapEmitFailure(failure: EmitFailure): CliStructuredError {
57
+ function mapEmitFailure(
58
+ failure: EmitFailure,
59
+ context?: { readonly configPath?: string },
60
+ ): CliStructuredError {
41
61
  if (failure.code === 'CONTRACT_SOURCE_INVALID') {
62
+ const issues = mapDiagnosticsToIssues(failure);
42
63
  return errorRuntime(failure.summary, {
43
- why: failure.why ?? 'Contract source is invalid',
44
- fix: 'Check your contract source configuration in prisma-next.config.ts',
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<Contract, Diagnostics>',
66
+ ...(issues.length > 0 ? { meta: { issues } } : {}),
45
67
  });
46
68
  }
47
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
+
48
77
  if (failure.code === 'EMIT_FAILED') {
49
78
  return errorRuntime(failure.summary, {
50
79
  why: failure.why ?? 'Failed to emit contract',
@@ -63,6 +92,7 @@ function mapEmitFailure(failure: EmitFailure): CliStructuredError {
63
92
  async function executeContractEmitCommand(
64
93
  options: ContractEmitOptions,
65
94
  flags: GlobalFlags,
95
+ ui: TerminalUI,
66
96
  startTime: number,
67
97
  ): Promise<Result<EmitContractResult, CliStructuredError>> {
68
98
  // Load config
@@ -81,11 +111,15 @@ async function executeContractEmitCommand(
81
111
  );
82
112
  }
83
113
 
114
+ const configPath = options.config
115
+ ? relative(process.cwd(), resolve(options.config))
116
+ : 'prisma-next.config.ts';
117
+
84
118
  // Resolve contract from config
85
119
  if (!config.contract) {
86
120
  return notOk(
87
121
  errorContractConfigMissing({
88
- why: 'Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ..., types: ... }',
122
+ why: 'Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ... }',
89
123
  }),
90
124
  );
91
125
  }
@@ -94,28 +128,34 @@ async function executeContractEmitCommand(
94
128
  const contractConfig = config.contract;
95
129
 
96
130
  // Resolve artifact paths from config (already normalized by defineConfig() with defaults)
97
- if (!contractConfig.output || !contractConfig.types) {
131
+ if (!contractConfig.output) {
98
132
  return notOk(
99
133
  errorContractConfigMissing({
100
- why: 'Contract config must have output and types paths. This should not happen if defineConfig() was used.',
134
+ why: 'Contract config must have output path. This should not happen if defineConfig() was used.',
101
135
  }),
102
136
  );
103
137
  }
104
- const outputJsonPath = resolve(contractConfig.output);
105
- const outputDtsPath = resolve(contractConfig.types);
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`;
106
151
 
107
- // Output header (only for human-readable output)
108
- if (flags.json !== 'object' && !flags.quiet) {
109
- // Normalize config path for display (match contract path format - no ./ prefix)
110
- const configPath = options.config
111
- ? relative(process.cwd(), resolve(options.config))
112
- : 'prisma-next.config.ts';
113
- // Convert absolute paths to relative paths for display
152
+ // Output header to stderr (decoration)
153
+ if (!flags.json && !flags.quiet) {
114
154
  const contractPath = relative(process.cwd(), outputJsonPath);
115
155
  const typesPath = relative(process.cwd(), outputDtsPath);
116
156
  const header = formatStyledHeader({
117
157
  command: 'contract emit',
118
- description: 'Write your contract to JSON and sign it',
158
+ description: 'Emit your contract artifacts',
119
159
  url: 'https://pris.ly/contract-emit',
120
160
  details: [
121
161
  { label: 'config', value: configPath },
@@ -124,7 +164,7 @@ async function executeContractEmitCommand(
124
164
  ],
125
165
  flags,
126
166
  });
127
- console.log(header);
167
+ ui.stderr(header);
128
168
  }
129
169
 
130
170
  // Create control client (no driver needed for emit)
@@ -136,29 +176,21 @@ async function executeContractEmitCommand(
136
176
  });
137
177
 
138
178
  // Create progress adapter
139
- const onProgress = createProgressAdapter({ flags });
179
+ const onProgress = createProgressAdapter({ ui, flags });
140
180
 
141
181
  try {
142
- // Convert user config source to discriminated union
143
- // Type assertion is safe: we check typeof to determine if it's a function
144
- const source: EmitContractSource =
145
- typeof contractConfig.source === 'function'
146
- ? { kind: 'loader', load: contractConfig.source as () => unknown | Promise<unknown> }
147
- : { kind: 'value', value: contractConfig.source };
148
-
149
182
  // Call emit with progress callback
150
183
  const result = await client.emit({
151
184
  contractConfig: {
152
- source,
185
+ sourceProvider: contractConfig.source,
153
186
  output: outputJsonPath,
154
- types: outputDtsPath,
155
187
  },
156
188
  onProgress,
157
189
  });
158
190
 
159
191
  // Handle failures by mapping to CLI structured error
160
192
  if (!result.ok) {
161
- return notOk(mapEmitFailure(result.failure));
193
+ return notOk(mapEmitFailure(result.failure, { configPath }));
162
194
  }
163
195
 
164
196
  // Create directories if needed
@@ -169,14 +201,17 @@ async function executeContractEmitCommand(
169
201
  writeFileSync(outputJsonPath, result.value.contractJson, 'utf-8');
170
202
  writeFileSync(outputDtsPath, result.value.contractDts, 'utf-8');
171
203
 
172
- // Add blank line after all async operations if spinners were shown
173
- if (!flags.quiet && flags.json !== 'object' && process.stdout.isTTY) {
174
- console.log('');
204
+ // Validate that contract.d.ts type imports are resolvable
205
+ const { validateContractDeps } = await import('../utils/validate-contract-deps');
206
+ const depsValidation = validateContractDeps(result.value.contractDts, dirname(outputDtsPath));
207
+ if (depsValidation.warning) {
208
+ ui.warn(depsValidation.warning);
175
209
  }
176
210
 
177
211
  // Convert success result to CLI output format
178
212
  const emitResult: EmitContractResult = {
179
- coreHash: result.value.coreHash,
213
+ storageHash: result.value.storageHash,
214
+ ...(result.value.executionHash ? { executionHash: result.value.executionHash } : {}),
180
215
  profileHash: result.value.profileHash,
181
216
  outDir: dirname(outputJsonPath),
182
217
  files: {
@@ -208,47 +243,35 @@ export function createContractEmitCommand(): Command {
208
243
  const command = new Command('emit');
209
244
  setCommandDescriptions(
210
245
  command,
211
- 'Write your contract to JSON and sign it',
246
+ 'Emit your contract artifacts',
212
247
  'Reads your contract source (TypeScript or Prisma schema) and emits contract.json and\n' +
213
248
  'contract.d.ts. The contract.json contains the canonical contract structure, and\n' +
214
249
  'contract.d.ts provides TypeScript types for type-safe query building.',
215
250
  );
216
- command
217
- .configureHelp({
218
- formatHelp: (cmd) => {
219
- const flags = parseGlobalFlags({});
220
- return formatCommandHelp({ command: cmd, flags });
221
- },
222
- })
251
+ setCommandExamples(command, [
252
+ 'prisma-next contract emit',
253
+ 'prisma-next contract emit --config ./custom-config.ts',
254
+ ]);
255
+ addGlobalOptions(command)
223
256
  .option('--config <path>', 'Path to prisma-next.config.ts')
224
- .option('--json [format]', 'Output as JSON (object or ndjson)', false)
225
- .option('-q, --quiet', 'Quiet mode: errors only')
226
- .option('-v, --verbose', 'Verbose output: debug info, timings')
227
- .option('-vv, --trace', 'Trace output: deep internals, stack traces')
228
- .option('--timestamps', 'Add timestamps to output')
229
- .option('--color', 'Force color output')
230
- .option('--no-color', 'Disable color output')
231
257
  .action(async (options: ContractEmitOptions) => {
232
258
  const flags = parseGlobalFlags(options);
259
+ const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
233
260
  const startTime = Date.now();
234
261
 
235
- const result = await executeContractEmitCommand(options, flags, startTime);
262
+ const result = await executeContractEmitCommand(options, flags, ui, startTime);
236
263
 
237
264
  // Handle result - formats output and returns exit code
238
- const exitCode = handleResult(result, flags, (emitResult) => {
239
- // Output based on flags
240
- if (flags.json === 'object') {
241
- // JSON output to stdout
242
- console.log(formatEmitJson(emitResult));
265
+ const exitCode = handleResult(result, flags, ui, (emitResult) => {
266
+ if (flags.json) {
267
+ ui.output(formatEmitJson(emitResult));
243
268
  } else {
244
- // Human-readable output to stdout
245
269
  const output = formatEmitOutput(emitResult, flags);
246
270
  if (output) {
247
- console.log(output);
271
+ ui.log(output);
248
272
  }
249
- // Output success message
250
273
  if (!flags.quiet) {
251
- console.log(formatSuccessMessage(flags));
274
+ ui.success(formatSuccessMessage(flags));
252
275
  }
253
276
  }
254
277
  });
@@ -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
+ }