@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
@@ -1,10 +1,20 @@
1
1
  import { readFile } from 'node:fs/promises';
2
- import { relative, resolve } from 'node:path';
3
- import type { VerifyDatabaseResult } from '@prisma-next/core-control-plane/types';
2
+ import type {
3
+ VerifyDatabaseResult,
4
+ VerifyDatabaseSchemaResult,
5
+ } from '@prisma-next/framework-components/control';
6
+ import {
7
+ VERIFY_CODE_HASH_MISMATCH,
8
+ VERIFY_CODE_MARKER_MISSING,
9
+ VERIFY_CODE_TARGET_MISMATCH,
10
+ } from '@prisma-next/framework-components/control';
11
+ import { ifDefined } from '@prisma-next/utils/defined';
4
12
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
5
13
  import { Command } from 'commander';
14
+ import { relative, resolve } from 'pathe';
6
15
  import { loadConfig } from '../config-loader';
7
16
  import { createControlClient } from '../control-api/client';
17
+ import { ContractValidationError } from '../control-api/errors';
8
18
  import {
9
19
  CliStructuredError,
10
20
  errorContractValidationFailed,
@@ -12,53 +22,73 @@ import {
12
22
  errorDriverRequired,
13
23
  errorFileNotFound,
14
24
  errorHashMismatch,
15
- errorJsonFormatNotSupported,
16
25
  errorMarkerMissing,
17
26
  errorRuntime,
18
27
  errorTargetMismatch,
19
28
  errorUnexpected,
20
29
  } from '../utils/cli-errors';
21
- import { setCommandDescriptions } from '../utils/command-helpers';
22
- import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
23
30
  import {
24
- formatCommandHelp,
25
- formatStyledHeader,
31
+ addGlobalOptions,
32
+ maskConnectionUrl,
33
+ resolveContractPath,
34
+ setCommandDescriptions,
35
+ setCommandExamples,
36
+ } from '../utils/command-helpers';
37
+ import { formatStyledHeader } from '../utils/formatters/styled';
38
+ import {
39
+ type DbVerifyCommandSuccessResult,
40
+ formatSchemaVerifyJson,
41
+ formatSchemaVerifyOutput,
26
42
  formatVerifyJson,
27
43
  formatVerifyOutput,
28
- } from '../utils/output';
44
+ } from '../utils/formatters/verify';
45
+ import type { CommonCommandOptions } from '../utils/global-flags';
46
+ import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
29
47
  import { createProgressAdapter } from '../utils/progress-adapter';
30
48
  import { handleResult } from '../utils/result-handler';
49
+ import { TerminalUI } from '../utils/terminal-ui';
31
50
 
32
- interface DbVerifyOptions {
51
+ interface DbVerifyOptions extends CommonCommandOptions {
33
52
  readonly db?: string;
34
53
  readonly config?: string;
35
- readonly json?: string | boolean;
36
- readonly quiet?: boolean;
37
- readonly q?: boolean;
38
- readonly verbose?: boolean;
39
- readonly v?: boolean;
40
- readonly vv?: boolean;
41
- readonly trace?: boolean;
42
- readonly timestamps?: boolean;
43
- readonly color?: boolean;
44
- readonly 'no-color'?: boolean;
54
+ readonly markerOnly?: boolean;
55
+ readonly schemaOnly?: boolean;
56
+ readonly strict?: boolean;
45
57
  }
46
58
 
59
+ type DbVerifyMode = 'full' | 'marker-only' | 'schema-only';
60
+
47
61
  /**
48
62
  * Maps a VerifyDatabaseResult failure to a CliStructuredError.
49
63
  */
50
64
  function mapVerifyFailure(verifyResult: VerifyDatabaseResult): CliStructuredError {
51
65
  if (!verifyResult.ok && verifyResult.code) {
52
- if (verifyResult.code === 'PN-RTM-3001') {
66
+ if (verifyResult.code === VERIFY_CODE_MARKER_MISSING) {
53
67
  return errorMarkerMissing();
54
68
  }
55
- if (verifyResult.code === 'PN-RTM-3002') {
69
+ if (verifyResult.code === VERIFY_CODE_HASH_MISMATCH) {
70
+ const storageMatch = verifyResult.marker?.storageHash === verifyResult.contract.storageHash;
71
+ const profileMatch =
72
+ !verifyResult.contract.profileHash ||
73
+ verifyResult.marker?.profileHash === verifyResult.contract.profileHash;
74
+
75
+ if (!storageMatch) {
76
+ return errorHashMismatch({
77
+ why: 'Contract storageHash does not match database marker',
78
+ expected: verifyResult.contract.storageHash,
79
+ ...ifDefined('actual', verifyResult.marker?.storageHash),
80
+ });
81
+ }
82
+
56
83
  return errorHashMismatch({
57
- expected: verifyResult.contract.coreHash,
58
- ...(verifyResult.marker?.coreHash ? { actual: verifyResult.marker.coreHash } : {}),
84
+ why: profileMatch
85
+ ? 'Contract hash does not match database marker'
86
+ : 'Contract profileHash does not match database marker',
87
+ ...ifDefined('expected', verifyResult.contract.profileHash),
88
+ ...ifDefined('actual', verifyResult.marker?.profileHash),
59
89
  });
60
90
  }
61
- if (verifyResult.code === 'PN-RTM-3003') {
91
+ if (verifyResult.code === VERIFY_CODE_TARGET_MISMATCH) {
62
92
  return errorTargetMismatch(
63
93
  verifyResult.target.expected,
64
94
  verifyResult.target.actual ?? 'unknown',
@@ -69,43 +99,152 @@ function mapVerifyFailure(verifyResult: VerifyDatabaseResult): CliStructuredErro
69
99
  return errorRuntime(verifyResult.summary);
70
100
  }
71
101
 
72
- /**
73
- * Executes the db verify command and returns a structured Result.
74
- */
75
- async function executeDbVerifyCommand(
102
+ type DbVerifyFailure = CliStructuredError | VerifyDatabaseSchemaResult;
103
+
104
+ function errorInvalidVerifyMode(options: {
105
+ readonly why: string;
106
+ readonly fix: string;
107
+ }): CliStructuredError {
108
+ return new CliStructuredError('4012', 'Invalid verify mode', {
109
+ domain: 'CLI',
110
+ why: options.why,
111
+ fix: options.fix,
112
+ docsUrl: 'https://pris.ly/db-verify',
113
+ });
114
+ }
115
+
116
+ function resolveDbVerifyMode(options: DbVerifyOptions): Result<DbVerifyMode, CliStructuredError> {
117
+ if (options.markerOnly && options.schemaOnly) {
118
+ return notOk(
119
+ errorInvalidVerifyMode({
120
+ why: '`--marker-only` and `--schema-only` cannot be used together',
121
+ fix: 'Choose one mode: omit both to check the marker and schema, use `--marker-only` to check only the marker, or use `--schema-only` to check only the live schema.',
122
+ }),
123
+ );
124
+ }
125
+
126
+ if (options.markerOnly && options.strict) {
127
+ return notOk(
128
+ errorInvalidVerifyMode({
129
+ why: '`--strict` requires schema verification, but `--marker-only` skips it',
130
+ fix: 'Remove `--strict`, or use `db verify` / `db verify --schema-only` when you want to check the live schema in strict mode.',
131
+ }),
132
+ );
133
+ }
134
+
135
+ if (options.schemaOnly) {
136
+ return ok('schema-only');
137
+ }
138
+
139
+ if (options.markerOnly) {
140
+ return ok('marker-only');
141
+ }
142
+
143
+ return ok('full');
144
+ }
145
+
146
+ function formatDbVerifyModeLabel(mode: DbVerifyMode, strict: boolean): string {
147
+ if (mode === 'marker-only') {
148
+ return 'marker only';
149
+ }
150
+
151
+ if (mode === 'schema-only') {
152
+ return `schema only (${strict ? 'strict' : 'tolerant'})`;
153
+ }
154
+
155
+ return `full (marker + schema, ${strict ? 'strict' : 'tolerant'})`;
156
+ }
157
+
158
+ function formatDbVerifyInvocation(mode: DbVerifyMode, strict: boolean): string {
159
+ const args = ['db verify'];
160
+
161
+ if (mode === 'marker-only') {
162
+ args.push('--marker-only');
163
+ }
164
+
165
+ if (mode === 'schema-only') {
166
+ args.push('--schema-only');
167
+ }
168
+
169
+ if (strict) {
170
+ args.push('--strict');
171
+ }
172
+
173
+ return args.join(' ');
174
+ }
175
+
176
+ function createDbVerifyConnectionRequiredError(options: {
177
+ readonly configPath: string;
178
+ readonly mode: DbVerifyMode;
179
+ readonly strict: boolean;
180
+ }): CliStructuredError {
181
+ const invocation = formatDbVerifyInvocation(options.mode, options.strict);
182
+ return errorDatabaseConnectionRequired({
183
+ why: `Database connection is required for ${invocation} (set db.connection in ${options.configPath}, or pass --db <url>)`,
184
+ retryCommand: `prisma-next ${invocation} --db <url>`,
185
+ });
186
+ }
187
+
188
+ function renderVerifyHeader(
189
+ paths: { configPath: string; contractPath: string },
76
190
  options: DbVerifyOptions,
191
+ mode: DbVerifyMode,
77
192
  flags: GlobalFlags,
78
- ): Promise<Result<VerifyDatabaseResult, CliStructuredError>> {
79
- // Load config
193
+ ui: TerminalUI,
194
+ ): void {
195
+ if (flags.json || flags.quiet) return;
196
+
197
+ const description =
198
+ mode === 'schema-only'
199
+ ? 'Check whether the live database schema matches your contract'
200
+ : mode === 'marker-only'
201
+ ? 'Check whether the database marker matches your contract'
202
+ : 'Check whether the database marker and live schema match your contract';
203
+
204
+ const details: Array<{ label: string; value: string }> = [
205
+ { label: 'config', value: paths.configPath },
206
+ { label: 'contract', value: paths.contractPath },
207
+ { label: 'mode', value: formatDbVerifyModeLabel(mode, options.strict ?? false) },
208
+ ];
209
+ if (options.db) {
210
+ details.push({ label: 'database', value: maskConnectionUrl(options.db) });
211
+ }
212
+
213
+ ui.stderr(
214
+ formatStyledHeader({
215
+ command: 'db verify',
216
+ description,
217
+ url: 'https://pris.ly/db-verify',
218
+ details,
219
+ flags,
220
+ }),
221
+ );
222
+ }
223
+
224
+ async function resolveVerifyPaths(options: DbVerifyOptions) {
80
225
  const config = await loadConfig(options.config);
81
226
  const configPath = options.config
82
227
  ? relative(process.cwd(), resolve(options.config))
83
228
  : 'prisma-next.config.ts';
84
- const contractPathAbsolute = config.contract?.output
85
- ? resolve(config.contract.output)
86
- : resolve('src/prisma/contract.json');
229
+ const contractPathAbsolute = resolveContractPath(config);
87
230
  const contractPath = relative(process.cwd(), contractPathAbsolute);
231
+ return { config, configPath, contractPathAbsolute, contractPath };
232
+ }
88
233
 
89
- // Output header
90
- if (flags.json !== 'object' && !flags.quiet) {
91
- const details: Array<{ label: string; value: string }> = [
92
- { label: 'config', value: configPath },
93
- { label: 'contract', value: contractPath },
94
- ];
95
- if (options.db) {
96
- details.push({ label: 'database', value: options.db });
97
- }
98
- const header = formatStyledHeader({
99
- command: 'db verify',
100
- description: 'Check whether the database has been signed with your contract',
101
- url: 'https://pris.ly/db-verify',
102
- details,
103
- flags,
104
- });
105
- console.log(header);
106
- }
234
+ type VerifyPaths = Awaited<ReturnType<typeof resolveVerifyPaths>>;
235
+
236
+ interface VerifySetup extends VerifyPaths {
237
+ readonly contractJson: Record<string, unknown>;
238
+ readonly dbConnection: string;
239
+ }
240
+
241
+ async function resolveVerifySetup(
242
+ paths: VerifyPaths,
243
+ options: DbVerifyOptions,
244
+ mode: DbVerifyMode,
245
+ ): Promise<Result<VerifySetup, CliStructuredError>> {
246
+ const { config, configPath, contractPathAbsolute, contractPath } = paths;
107
247
 
108
- // Load contract file
109
248
  let contractJsonContent: string;
110
249
  try {
111
250
  contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');
@@ -137,63 +276,174 @@ async function executeDbVerifyCommand(
137
276
  );
138
277
  }
139
278
 
140
- // Resolve database connection (--db flag or config.db.connection)
141
279
  const dbConnection = options.db ?? config.db?.connection;
142
- if (!dbConnection) {
280
+ if (typeof dbConnection !== 'string' || dbConnection.length === 0) {
143
281
  return notOk(
144
- errorDatabaseConnectionRequired({
145
- why: `Database connection is required for db verify (set db.connection in ${configPath}, or pass --db <url>)`,
282
+ createDbVerifyConnectionRequiredError({
283
+ configPath,
284
+ mode,
285
+ strict: options.strict ?? false,
146
286
  }),
147
287
  );
148
288
  }
149
289
 
150
- // Check for driver
151
290
  if (!config.driver) {
152
- return notOk(errorDriverRequired({ why: 'Config.driver is required for db verify' }));
291
+ return notOk(
292
+ errorDriverRequired({
293
+ why: `Config.driver is required for ${formatDbVerifyInvocation(mode, options.strict ?? false)}`,
294
+ }),
295
+ );
153
296
  }
154
297
 
155
- // Create control client
156
- const client = createControlClient({
157
- family: config.family,
158
- target: config.target,
159
- adapter: config.adapter,
160
- driver: config.driver,
161
- extensionPacks: config.extensionPacks ?? [],
298
+ return ok({ ...paths, contractJson, dbConnection });
299
+ }
300
+
301
+ function createVerifyClient(setup: VerifySetup) {
302
+ return createControlClient({
303
+ family: setup.config.family,
304
+ target: setup.config.target,
305
+ adapter: setup.config.adapter,
306
+ driver: setup.config.driver!,
307
+ extensionPacks: setup.config.extensionPacks ?? [],
162
308
  });
309
+ }
310
+
311
+ function wrapVerifyError(
312
+ error: unknown,
313
+ contractPathAbsolute: string,
314
+ modeLabel: string,
315
+ ): Result<never, CliStructuredError> {
316
+ if (error instanceof CliStructuredError) {
317
+ return notOk(error);
318
+ }
319
+ if (error instanceof ContractValidationError) {
320
+ return notOk(
321
+ errorContractValidationFailed(`Contract validation failed: ${error.message}`, {
322
+ where: { path: contractPathAbsolute },
323
+ }),
324
+ );
325
+ }
326
+ return notOk(
327
+ errorUnexpected(error instanceof Error ? error.message : String(error), {
328
+ why: `Unexpected error during ${modeLabel}: ${error instanceof Error ? error.message : String(error)}`,
329
+ }),
330
+ );
331
+ }
332
+
333
+ /**
334
+ * Executes the db verify command and returns a structured Result.
335
+ */
336
+ async function executeDbVerifyCommand(
337
+ options: DbVerifyOptions,
338
+ flags: GlobalFlags,
339
+ ui: TerminalUI,
340
+ mode: Extract<DbVerifyMode, 'full' | 'marker-only'>,
341
+ ): Promise<Result<DbVerifyCommandSuccessResult, DbVerifyFailure>> {
342
+ const startTime = Date.now();
343
+ const paths = await resolveVerifyPaths(options);
344
+ renderVerifyHeader(paths, options, mode, flags, ui);
163
345
 
164
- // Create progress adapter
165
- const onProgress = createProgressAdapter({ flags });
346
+ const setupResult = await resolveVerifySetup(paths, options, mode);
347
+ if (!setupResult.ok) return setupResult;
348
+ const { contractJson, dbConnection, contractPathAbsolute } = setupResult.value;
349
+
350
+ const client = createVerifyClient(setupResult.value);
351
+ const onProgress = createProgressAdapter({ ui, flags });
166
352
 
167
353
  try {
168
354
  const verifyResult = await client.verify({
169
- contractIR: contractJson,
355
+ contract: contractJson,
170
356
  connection: dbConnection,
171
357
  onProgress,
172
358
  });
173
359
 
174
- // Add blank line after all async operations if spinners were shown
175
- if (!flags.quiet && flags.json !== 'object' && process.stdout.isTTY) {
176
- console.log('');
177
- }
178
-
179
360
  // If verification failed, map to CLI structured error
180
361
  if (!verifyResult.ok) {
181
362
  return notOk(mapVerifyFailure(verifyResult));
182
363
  }
183
364
 
184
- return ok(verifyResult);
185
- } catch (error) {
186
- // Driver already throws CliStructuredError for connection failures
187
- if (error instanceof CliStructuredError) {
188
- return notOk(error);
365
+ if (mode === 'marker-only') {
366
+ return ok({
367
+ ok: true,
368
+ mode: 'marker-only',
369
+ summary: 'Database marker matches contract',
370
+ contract: verifyResult.contract,
371
+ marker: verifyResult.marker,
372
+ target: verifyResult.target,
373
+ ...ifDefined('missingCodecs', verifyResult.missingCodecs),
374
+ ...ifDefined('codecCoverageSkipped', verifyResult.codecCoverageSkipped),
375
+ warning: 'Schema verification skipped because --marker-only was provided',
376
+ meta: {
377
+ ...(verifyResult.meta ?? {}),
378
+ schemaVerification: 'skipped',
379
+ },
380
+ timings: { total: Date.now() - startTime },
381
+ });
189
382
  }
190
383
 
191
- // Wrap unexpected errors
192
- return notOk(
193
- errorUnexpected(error instanceof Error ? error.message : String(error), {
194
- why: `Unexpected error during db verify: ${error instanceof Error ? error.message : String(error)}`,
195
- }),
196
- );
384
+ const schemaVerifyResult = await client.schemaVerify({
385
+ contract: contractJson,
386
+ strict: options.strict ?? false,
387
+ onProgress,
388
+ });
389
+
390
+ if (!schemaVerifyResult.ok) {
391
+ return notOk(schemaVerifyResult);
392
+ }
393
+
394
+ return ok({
395
+ ok: true,
396
+ mode: 'full',
397
+ summary: 'Database marker and schema match contract',
398
+ contract: verifyResult.contract,
399
+ marker: verifyResult.marker,
400
+ target: verifyResult.target,
401
+ ...ifDefined('missingCodecs', verifyResult.missingCodecs),
402
+ ...ifDefined('codecCoverageSkipped', verifyResult.codecCoverageSkipped),
403
+ schema: {
404
+ summary: schemaVerifyResult.summary,
405
+ counts: schemaVerifyResult.schema.counts,
406
+ strict: schemaVerifyResult.meta?.strict ?? false,
407
+ },
408
+ meta: {
409
+ ...(verifyResult.meta ?? {}),
410
+ schemaVerification: 'performed',
411
+ },
412
+ timings: { total: Date.now() - startTime },
413
+ });
414
+ } catch (error) {
415
+ return wrapVerifyError(error, contractPathAbsolute, 'db verify');
416
+ } finally {
417
+ await client.close();
418
+ }
419
+ }
420
+
421
+ async function executeDbSchemaOnlyVerifyCommand(
422
+ options: DbVerifyOptions,
423
+ flags: GlobalFlags,
424
+ ui: TerminalUI,
425
+ ): Promise<Result<VerifyDatabaseSchemaResult, CliStructuredError>> {
426
+ const paths = await resolveVerifyPaths(options);
427
+ renderVerifyHeader(paths, options, 'schema-only', flags, ui);
428
+
429
+ const setupResult = await resolveVerifySetup(paths, options, 'schema-only');
430
+ if (!setupResult.ok) return setupResult;
431
+ const { contractJson, dbConnection, contractPathAbsolute } = setupResult.value;
432
+
433
+ const client = createVerifyClient(setupResult.value);
434
+ const onProgress = createProgressAdapter({ ui, flags });
435
+
436
+ try {
437
+ const schemaVerifyResult = await client.schemaVerify({
438
+ contract: contractJson,
439
+ strict: options.strict ?? false,
440
+ connection: dbConnection,
441
+ onProgress,
442
+ });
443
+
444
+ return ok(schemaVerifyResult);
445
+ } catch (error) {
446
+ return wrapVerifyError(error, contractPathAbsolute, 'db verify --schema-only');
197
447
  } finally {
198
448
  await client.close();
199
449
  }
@@ -203,55 +453,95 @@ export function createDbVerifyCommand(): Command {
203
453
  const command = new Command('verify');
204
454
  setCommandDescriptions(
205
455
  command,
206
- 'Check whether the database has been signed with your contract',
207
- 'Verifies that your database schema matches the emitted contract. Checks table structures,\n' +
208
- 'column types, constraints, and codec coverage. Reports any mismatches or missing codecs.',
456
+ 'Check whether the database marker and live schema match your contract',
457
+ 'Verifies the database marker first, then checks the database schema matches your contract.\n' +
458
+ 'Use `--marker-only` for marker-only verification, `--schema-only` to skip marker checks and\n' +
459
+ 'inspect only the live schema, and `--strict` to fail if the database includes elements\n' +
460
+ 'not present in the contract.',
209
461
  );
210
- command
211
- .configureHelp({
212
- formatHelp: (cmd) => {
213
- const flags = parseGlobalFlags({});
214
- return formatCommandHelp({ command: cmd, flags });
215
- },
216
- })
462
+ setCommandExamples(command, [
463
+ 'prisma-next db verify --db $DATABASE_URL',
464
+ 'prisma-next db verify --db $DATABASE_URL --strict',
465
+ 'prisma-next db verify --db $DATABASE_URL --schema-only',
466
+ 'prisma-next db verify --db $DATABASE_URL --schema-only --strict',
467
+ 'prisma-next db verify --db $DATABASE_URL --marker-only',
468
+ 'prisma-next db verify --db $DATABASE_URL --json',
469
+ ]);
470
+ addGlobalOptions(command)
217
471
  .option('--db <url>', 'Database connection string')
218
472
  .option('--config <path>', 'Path to prisma-next.config.ts')
219
- .option('--json [format]', 'Output as JSON (object)', false)
220
- .option('-q, --quiet', 'Quiet mode: errors only')
221
- .option('-v, --verbose', 'Verbose output: debug info, timings')
222
- .option('-vv, --trace', 'Trace output: deep internals, stack traces')
223
- .option('--timestamps', 'Add timestamps to output')
224
- .option('--color', 'Force color output')
225
- .option('--no-color', 'Disable color output')
473
+ .option('--marker-only', 'Skip schema verification and only check the database marker')
474
+ .option(
475
+ '--schema-only',
476
+ 'Skip marker verification and only check whether the live schema satisfies the contract',
477
+ )
478
+ .option(
479
+ '--strict',
480
+ 'Strict mode: schema elements not present in the contract are considered an error',
481
+ false,
482
+ )
226
483
  .action(async (options: DbVerifyOptions) => {
227
484
  const flags = parseGlobalFlags(options);
485
+ const ui = new TerminalUI({ color: flags.color, interactive: flags.interactive });
486
+
487
+ const modeResult = resolveDbVerifyMode(options);
488
+ if (!modeResult.ok) {
489
+ const exitCode = handleResult(modeResult as Result<never, CliStructuredError>, flags, ui);
490
+ process.exit(exitCode);
491
+ }
492
+
493
+ const mode = modeResult.value;
494
+
495
+ if (mode === 'schema-only') {
496
+ const result = await executeDbSchemaOnlyVerifyCommand(options, flags, ui);
497
+ const exitCode = handleResult(result, flags, ui, (schemaVerifyResult) => {
498
+ if (flags.json) {
499
+ ui.output(formatSchemaVerifyJson(schemaVerifyResult));
500
+ } else {
501
+ const output = formatSchemaVerifyOutput(schemaVerifyResult, flags);
502
+ if (output) {
503
+ ui.log(output);
504
+ }
505
+ }
506
+ });
507
+
508
+ if (result.ok && !result.value.ok) {
509
+ process.exit(1);
510
+ }
228
511
 
229
- // Validate JSON format option
230
- if (flags.json === 'ndjson') {
231
- const result = notOk(
232
- errorJsonFormatNotSupported({
233
- command: 'db verify',
234
- format: 'ndjson',
235
- supportedFormats: ['object'],
236
- }),
237
- );
238
- const exitCode = handleResult(result, flags);
239
512
  process.exit(exitCode);
240
513
  }
241
514
 
242
- const result = await executeDbVerifyCommand(options, flags);
515
+ const result = await executeDbVerifyCommand(options, flags, ui, mode);
243
516
 
244
- const exitCode = handleResult(result, flags, (verifyResult) => {
245
- if (flags.json === 'object') {
246
- console.log(formatVerifyJson(verifyResult));
517
+ if (result.ok) {
518
+ if (flags.json) {
519
+ ui.output(formatVerifyJson(result.value));
247
520
  } else {
248
- const output = formatVerifyOutput(verifyResult, flags);
521
+ const output = formatVerifyOutput(result.value, flags);
249
522
  if (output) {
250
- console.log(output);
523
+ ui.log(output);
251
524
  }
252
525
  }
253
- });
254
- process.exit(exitCode);
526
+ process.exit(0);
527
+ }
528
+
529
+ if (CliStructuredError.is(result.failure)) {
530
+ const exitCode = handleResult(result as Result<never, CliStructuredError>, flags, ui);
531
+ process.exit(exitCode);
532
+ }
533
+
534
+ if (flags.json) {
535
+ ui.output(formatSchemaVerifyJson(result.failure));
536
+ } else {
537
+ // Always show schema-drift failures, even in quiet mode — exiting 1 without
538
+ // diagnostics is unhelpful.
539
+ const output = formatSchemaVerifyOutput(result.failure, { ...flags, quiet: false });
540
+ if (output) {
541
+ ui.log(output);
542
+ }
543
+ }
544
+ process.exit(1);
255
545
  });
256
546
 
257
547
  return command;
@@ -0,0 +1,47 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { detect } from 'package-manager-detector/detect';
3
+ import { join } from 'pathe';
4
+
5
+ export type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun' | 'deno';
6
+
7
+ const KNOWN: ReadonlySet<string> = new Set<PackageManager>(['pnpm', 'npm', 'yarn', 'bun', 'deno']);
8
+
9
+ export async function detectPackageManager(cwd: string): Promise<PackageManager> {
10
+ const result = await detect({ cwd });
11
+ if (result && KNOWN.has(result.name)) {
12
+ return result.name as PackageManager;
13
+ }
14
+ return 'npm';
15
+ }
16
+
17
+ export function hasProjectManifest(cwd: string): boolean {
18
+ return (
19
+ existsSync(join(cwd, 'package.json')) ||
20
+ existsSync(join(cwd, 'deno.json')) ||
21
+ existsSync(join(cwd, 'deno.jsonc'))
22
+ );
23
+ }
24
+
25
+ export function formatRunCommand(pm: PackageManager, bin: string, args: string): string {
26
+ if (pm === 'npm') {
27
+ return `npx ${bin} ${args}`;
28
+ }
29
+ if (pm === 'deno') {
30
+ return `deno run npm:${bin} ${args}`;
31
+ }
32
+ return `${pm} ${bin} ${args}`;
33
+ }
34
+
35
+ export function formatAddArgs(pm: PackageManager, packages: string[]): string[] {
36
+ if (pm === 'deno') {
37
+ return ['add', ...packages.map((p) => `npm:${p}`)];
38
+ }
39
+ return ['add', ...packages];
40
+ }
41
+
42
+ export function formatAddDevArgs(pm: PackageManager, packages: string[]): string[] {
43
+ if (pm === 'deno') {
44
+ return ['add', '--dev', ...packages.map((p) => `npm:${p}`)];
45
+ }
46
+ return ['add', '-D', ...packages];
47
+ }