@prisma-next/cli 0.3.0-dev.6 → 0.3.0-dev.64

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 (180) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +314 -80
  3. package/dist/cli-errors-JlPTsazx.mjs +3 -0
  4. package/dist/cli.d.mts +1 -0
  5. package/dist/cli.js +1 -2376
  6. package/dist/cli.mjs +203 -0
  7. package/dist/cli.mjs.map +1 -0
  8. package/dist/client-PimzSD1f.mjs +981 -0
  9. package/dist/client-PimzSD1f.mjs.map +1 -0
  10. package/dist/commands/contract-emit.d.mts +7 -0
  11. package/dist/commands/contract-emit.d.mts.map +1 -0
  12. package/dist/commands/contract-emit.mjs +151 -0
  13. package/dist/commands/contract-emit.mjs.map +1 -0
  14. package/dist/commands/db-init.d.mts +7 -0
  15. package/dist/commands/db-init.d.mts.map +1 -0
  16. package/dist/commands/db-init.mjs +134 -0
  17. package/dist/commands/db-init.mjs.map +1 -0
  18. package/dist/commands/db-introspect.d.mts +7 -0
  19. package/dist/commands/db-introspect.d.mts.map +1 -0
  20. package/dist/commands/db-introspect.mjs +118 -0
  21. package/dist/commands/db-introspect.mjs.map +1 -0
  22. package/dist/commands/db-schema-verify.d.mts +7 -0
  23. package/dist/commands/db-schema-verify.d.mts.map +1 -0
  24. package/dist/commands/db-schema-verify.mjs +120 -0
  25. package/dist/commands/db-schema-verify.mjs.map +1 -0
  26. package/dist/commands/db-sign.d.mts +7 -0
  27. package/dist/commands/db-sign.d.mts.map +1 -0
  28. package/dist/commands/db-sign.mjs +142 -0
  29. package/dist/commands/db-sign.mjs.map +1 -0
  30. package/dist/commands/db-update.d.mts +7 -0
  31. package/dist/commands/db-update.d.mts.map +1 -0
  32. package/dist/commands/db-update.mjs +123 -0
  33. package/dist/commands/db-update.mjs.map +1 -0
  34. package/dist/commands/db-verify.d.mts +7 -0
  35. package/dist/commands/db-verify.d.mts.map +1 -0
  36. package/dist/commands/db-verify.mjs +133 -0
  37. package/dist/commands/db-verify.mjs.map +1 -0
  38. package/dist/commands/migration-apply.d.mts +23 -0
  39. package/dist/commands/migration-apply.d.mts.map +1 -0
  40. package/dist/commands/migration-apply.mjs +250 -0
  41. package/dist/commands/migration-apply.mjs.map +1 -0
  42. package/dist/commands/migration-plan.d.mts +25 -0
  43. package/dist/commands/migration-plan.d.mts.map +1 -0
  44. package/dist/commands/migration-plan.mjs +266 -0
  45. package/dist/commands/migration-plan.mjs.map +1 -0
  46. package/dist/commands/migration-show.d.mts +28 -0
  47. package/dist/commands/migration-show.d.mts.map +1 -0
  48. package/dist/commands/migration-show.mjs +138 -0
  49. package/dist/commands/migration-show.mjs.map +1 -0
  50. package/dist/commands/migration-status.d.mts +35 -0
  51. package/dist/commands/migration-status.d.mts.map +1 -0
  52. package/dist/commands/migration-status.mjs +260 -0
  53. package/dist/commands/migration-status.mjs.map +1 -0
  54. package/dist/commands/migration-verify.d.mts +16 -0
  55. package/dist/commands/migration-verify.d.mts.map +1 -0
  56. package/dist/commands/migration-verify.mjs +86 -0
  57. package/dist/commands/migration-verify.mjs.map +1 -0
  58. package/dist/config-loader-PPf4CtDj.mjs +43 -0
  59. package/dist/config-loader-PPf4CtDj.mjs.map +1 -0
  60. package/dist/{config-loader.d.ts → config-loader.d.mts} +8 -3
  61. package/dist/config-loader.d.mts.map +1 -0
  62. package/dist/config-loader.mjs +3 -0
  63. package/dist/exports/config-types.d.mts +2 -0
  64. package/dist/exports/config-types.mjs +3 -0
  65. package/dist/exports/control-api.d.mts +621 -0
  66. package/dist/exports/control-api.d.mts.map +1 -0
  67. package/dist/exports/control-api.mjs +98 -0
  68. package/dist/exports/control-api.mjs.map +1 -0
  69. package/dist/{load-ts-contract.d.ts → exports/index.d.mts} +10 -5
  70. package/dist/exports/index.d.mts.map +1 -0
  71. package/dist/exports/index.mjs +135 -0
  72. package/dist/exports/index.mjs.map +1 -0
  73. package/dist/extract-sql-ddl-BmlKvk4o.mjs +26 -0
  74. package/dist/extract-sql-ddl-BmlKvk4o.mjs.map +1 -0
  75. package/dist/framework-components-CjV_jD8f.mjs +59 -0
  76. package/dist/framework-components-CjV_jD8f.mjs.map +1 -0
  77. package/dist/migration-command-scaffold-DfY_F3ev.mjs +97 -0
  78. package/dist/migration-command-scaffold-DfY_F3ev.mjs.map +1 -0
  79. package/dist/progress-adapter-DENrzF6I.mjs +49 -0
  80. package/dist/progress-adapter-DENrzF6I.mjs.map +1 -0
  81. package/dist/result-handler-iA9JtUC7.mjs +1186 -0
  82. package/dist/result-handler-iA9JtUC7.mjs.map +1 -0
  83. package/package.json +75 -38
  84. package/src/cli.ts +43 -0
  85. package/src/commands/contract-emit.ts +221 -111
  86. package/src/commands/db-init.ts +217 -426
  87. package/src/commands/db-introspect.ts +148 -185
  88. package/src/commands/db-schema-verify.ts +162 -149
  89. package/src/commands/db-sign.ts +215 -202
  90. package/src/commands/db-update.ts +220 -0
  91. package/src/commands/db-verify.ts +193 -156
  92. package/src/commands/migration-apply.ts +431 -0
  93. package/src/commands/migration-plan.ts +446 -0
  94. package/src/commands/migration-show.ts +255 -0
  95. package/src/commands/migration-status.ts +436 -0
  96. package/src/commands/migration-verify.ts +151 -0
  97. package/src/config-loader.ts +13 -3
  98. package/src/control-api/client.ts +605 -0
  99. package/src/control-api/errors.ts +9 -0
  100. package/src/control-api/operations/contract-emit.ts +161 -0
  101. package/src/control-api/operations/db-init.ts +286 -0
  102. package/src/control-api/operations/db-update.ts +221 -0
  103. package/src/control-api/operations/extract-sql-ddl.ts +47 -0
  104. package/src/control-api/operations/migration-apply.ts +195 -0
  105. package/src/control-api/operations/migration-helpers.ts +49 -0
  106. package/src/control-api/types.ts +687 -0
  107. package/src/exports/config-types.ts +3 -3
  108. package/src/exports/control-api.ts +53 -0
  109. package/src/load-ts-contract.ts +16 -11
  110. package/src/utils/cli-errors.ts +3 -1
  111. package/src/utils/command-helpers.ts +92 -3
  112. package/src/utils/framework-components.ts +11 -30
  113. package/src/utils/migration-command-scaffold.ts +190 -0
  114. package/src/utils/output.ts +363 -25
  115. package/src/utils/progress-adapter.ts +86 -0
  116. package/dist/chunk-464LNZCE.js +0 -134
  117. package/dist/chunk-464LNZCE.js.map +0 -1
  118. package/dist/chunk-BZMBKEEQ.js +0 -997
  119. package/dist/chunk-BZMBKEEQ.js.map +0 -1
  120. package/dist/chunk-HWYQOCAJ.js +0 -47
  121. package/dist/chunk-HWYQOCAJ.js.map +0 -1
  122. package/dist/chunk-ZKYEJROM.js +0 -94
  123. package/dist/chunk-ZKYEJROM.js.map +0 -1
  124. package/dist/cli.d.ts +0 -2
  125. package/dist/cli.d.ts.map +0 -1
  126. package/dist/cli.js.map +0 -1
  127. package/dist/commands/contract-emit.d.ts +0 -3
  128. package/dist/commands/contract-emit.d.ts.map +0 -1
  129. package/dist/commands/contract-emit.js +0 -9
  130. package/dist/commands/contract-emit.js.map +0 -1
  131. package/dist/commands/db-init.d.ts +0 -3
  132. package/dist/commands/db-init.d.ts.map +0 -1
  133. package/dist/commands/db-init.js +0 -341
  134. package/dist/commands/db-init.js.map +0 -1
  135. package/dist/commands/db-introspect.d.ts +0 -3
  136. package/dist/commands/db-introspect.d.ts.map +0 -1
  137. package/dist/commands/db-introspect.js +0 -190
  138. package/dist/commands/db-introspect.js.map +0 -1
  139. package/dist/commands/db-schema-verify.d.ts +0 -3
  140. package/dist/commands/db-schema-verify.d.ts.map +0 -1
  141. package/dist/commands/db-schema-verify.js +0 -164
  142. package/dist/commands/db-schema-verify.js.map +0 -1
  143. package/dist/commands/db-sign.d.ts +0 -3
  144. package/dist/commands/db-sign.d.ts.map +0 -1
  145. package/dist/commands/db-sign.js +0 -199
  146. package/dist/commands/db-sign.js.map +0 -1
  147. package/dist/commands/db-verify.d.ts +0 -3
  148. package/dist/commands/db-verify.d.ts.map +0 -1
  149. package/dist/commands/db-verify.js +0 -173
  150. package/dist/commands/db-verify.js.map +0 -1
  151. package/dist/config-loader.d.ts.map +0 -1
  152. package/dist/config-loader.js +0 -7
  153. package/dist/config-loader.js.map +0 -1
  154. package/dist/exports/config-types.d.ts +0 -3
  155. package/dist/exports/config-types.d.ts.map +0 -1
  156. package/dist/exports/config-types.js +0 -6
  157. package/dist/exports/config-types.js.map +0 -1
  158. package/dist/exports/index.d.ts +0 -4
  159. package/dist/exports/index.d.ts.map +0 -1
  160. package/dist/exports/index.js +0 -175
  161. package/dist/exports/index.js.map +0 -1
  162. package/dist/load-ts-contract.d.ts.map +0 -1
  163. package/dist/utils/action.d.ts +0 -16
  164. package/dist/utils/action.d.ts.map +0 -1
  165. package/dist/utils/cli-errors.d.ts +0 -7
  166. package/dist/utils/cli-errors.d.ts.map +0 -1
  167. package/dist/utils/command-helpers.d.ts +0 -12
  168. package/dist/utils/command-helpers.d.ts.map +0 -1
  169. package/dist/utils/framework-components.d.ts +0 -81
  170. package/dist/utils/framework-components.d.ts.map +0 -1
  171. package/dist/utils/global-flags.d.ts +0 -25
  172. package/dist/utils/global-flags.d.ts.map +0 -1
  173. package/dist/utils/output.d.ts +0 -142
  174. package/dist/utils/output.d.ts.map +0 -1
  175. package/dist/utils/result-handler.d.ts +0 -15
  176. package/dist/utils/result-handler.d.ts.map +0 -1
  177. package/dist/utils/spinner.d.ts +0 -29
  178. package/dist/utils/spinner.d.ts.map +0 -1
  179. package/src/utils/action.ts +0 -43
  180. package/src/utils/spinner.ts +0 -67
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Programmatic Control API for Prisma Next.
3
+ *
4
+ * This module exports the control client factory and types for programmatic
5
+ * access to control-plane operations without using the CLI.
6
+ *
7
+ * @see README.md "Programmatic Control API" section for usage examples
8
+ * @module
9
+ */
10
+
11
+ // Re-export core control plane types for consumer convenience
12
+ export type {
13
+ ControlPlaneStack,
14
+ SignDatabaseResult,
15
+ VerifyDatabaseResult,
16
+ VerifyDatabaseSchemaResult,
17
+ } from '@prisma-next/core-control-plane/types';
18
+ // Client factory
19
+ export { createControlClient } from '../control-api/client';
20
+
21
+ // Standalone operations (for tooling that doesn't need full client)
22
+ export { executeContractEmit } from '../control-api/operations/contract-emit';
23
+
24
+ // CLI-specific types
25
+ export type {
26
+ ContractEmitOptions,
27
+ ContractEmitResult,
28
+ ControlActionName,
29
+ ControlClient,
30
+ ControlClientOptions,
31
+ ControlProgressEvent,
32
+ DbInitFailure,
33
+ DbInitFailureCode,
34
+ DbInitOptions,
35
+ DbInitResult,
36
+ DbInitSuccess,
37
+ DbUpdateFailure,
38
+ DbUpdateFailureCode,
39
+ DbUpdateOptions,
40
+ DbUpdateResult,
41
+ DbUpdateSuccess,
42
+ EmitContractConfig,
43
+ EmitFailure,
44
+ EmitFailureCode,
45
+ EmitOptions,
46
+ EmitResult,
47
+ EmitSuccess,
48
+ IntrospectOptions,
49
+ OnControlProgress,
50
+ SchemaVerifyOptions,
51
+ SignOptions,
52
+ VerifyOptions,
53
+ } from '../control-api/types';
@@ -30,26 +30,31 @@ function validatePurity(value: unknown): void {
30
30
  return;
31
31
  }
32
32
 
33
- const seen = new WeakSet();
33
+ const path = new WeakSet();
34
+
34
35
  function check(value: unknown): void {
35
36
  if (value === null || typeof value !== 'object') {
36
37
  return;
37
38
  }
38
39
 
39
- if (seen.has(value)) {
40
+ if (path.has(value)) {
40
41
  throw new Error('Contract export contains circular references');
41
42
  }
42
- seen.add(value);
43
+ path.add(value);
43
44
 
44
- for (const key in value) {
45
- const descriptor = Object.getOwnPropertyDescriptor(value, key);
46
- if (descriptor && (descriptor.get || descriptor.set)) {
47
- throw new Error(`Contract export contains getter/setter at key "${key}"`);
48
- }
49
- if (descriptor && typeof descriptor.value === 'function') {
50
- throw new Error(`Contract export contains function at key "${key}"`);
45
+ try {
46
+ for (const key in value) {
47
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
48
+ if (descriptor && (descriptor.get || descriptor.set)) {
49
+ throw new Error(`Contract export contains getter/setter at key "${key}"`);
50
+ }
51
+ if (descriptor && typeof descriptor.value === 'function') {
52
+ throw new Error(`Contract export contains function at key "${key}"`);
53
+ }
54
+ check((value as Record<string, unknown>)[key]);
51
55
  }
52
- check((value as Record<string, unknown>)[key]);
56
+ } finally {
57
+ path.delete(value);
53
58
  }
54
59
  }
55
60
 
@@ -10,7 +10,8 @@ export {
10
10
  errorContractConfigMissing,
11
11
  errorContractMissingExtensionPacks,
12
12
  errorContractValidationFailed,
13
- errorDatabaseUrlRequired,
13
+ errorDatabaseConnectionRequired,
14
+ errorDestructiveChanges,
14
15
  errorDriverRequired,
15
16
  errorFamilyReadMarkerSqlRequired,
16
17
  errorFileNotFound,
@@ -19,6 +20,7 @@ export {
19
20
  errorMarkerMissing,
20
21
  errorMigrationPlanningFailed,
21
22
  errorQueryRunnerFactoryRequired,
23
+ errorRunnerFailed,
22
24
  errorRuntime,
23
25
  errorTargetMigrationNotSupported,
24
26
  errorTargetMismatch,
@@ -1,4 +1,7 @@
1
1
  import type { Command } from 'commander';
2
+ import { resolve } from 'pathe';
3
+
4
+ const longDescriptions = new WeakMap<Command, string>();
2
5
 
3
6
  /**
4
7
  * Sets both short and long descriptions for a command.
@@ -12,8 +15,7 @@ export function setCommandDescriptions(
12
15
  ): Command {
13
16
  command.description(shortDescription);
14
17
  if (longDescription) {
15
- // Store long description in a custom property for our formatters to access
16
- (command as Command & { _longDescription?: string })._longDescription = longDescription;
18
+ longDescriptions.set(command, longDescription);
17
19
  }
18
20
  return command;
19
21
  }
@@ -22,5 +24,92 @@ export function setCommandDescriptions(
22
24
  * Gets the long description from a command if it was set via setCommandDescriptions.
23
25
  */
24
26
  export function getLongDescription(command: Command): string | undefined {
25
- return (command as Command & { _longDescription?: string })._longDescription;
27
+ return longDescriptions.get(command);
28
+ }
29
+
30
+ /**
31
+ * Shared CLI options interface for migration commands (db init, db update).
32
+ * These are the Commander.js parsed options common to both commands.
33
+ */
34
+ export interface MigrationCommandOptions {
35
+ readonly db?: string;
36
+ readonly config?: string;
37
+ readonly plan?: boolean;
38
+ readonly json?: string | boolean;
39
+ readonly quiet?: boolean;
40
+ readonly q?: boolean;
41
+ readonly verbose?: boolean;
42
+ readonly v?: boolean;
43
+ readonly vv?: boolean;
44
+ readonly trace?: boolean;
45
+ readonly timestamps?: boolean;
46
+ readonly color?: boolean;
47
+ readonly 'no-color'?: boolean;
48
+ }
49
+
50
+ /**
51
+ * Resolves the absolute path to contract.json from the config.
52
+ * Centralises the fallback logic shared by every command that reads the contract.
53
+ */
54
+ export function resolveContractPath(config: { contract?: { output?: string } }): string {
55
+ return config.contract?.output
56
+ ? resolve(config.contract.output)
57
+ : resolve('src/prisma/contract.json');
58
+ }
59
+
60
+ /**
61
+ * Masks credentials in a database connection URL.
62
+ * Handles standard URLs (username + password + query params) and libpq-style key=value strings.
63
+ */
64
+ export function maskConnectionUrl(url: string): string {
65
+ try {
66
+ const parsed = new URL(url);
67
+ if (parsed.username) {
68
+ parsed.username = '****';
69
+ }
70
+ if (parsed.password) {
71
+ parsed.password = '****';
72
+ }
73
+ // Also mask password in query parameters (e.g., ?password=secret, ?sslpassword=secret)
74
+ for (const key of [...parsed.searchParams.keys()]) {
75
+ if (/password/i.test(key)) {
76
+ parsed.searchParams.set(key, '****');
77
+ }
78
+ }
79
+ return parsed.toString();
80
+ } catch {
81
+ // Fallback for libpq-style key=value connection strings (e.g., "host=localhost password=secret user=admin")
82
+ return url
83
+ .replace(/password\s*=\s*\S+/gi, 'password=****')
84
+ .replace(/user\s*=\s*\S+/gi, 'user=****');
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Strips raw connection URL fragments from an error message to prevent credential leakage.
90
+ * Call this before surfacing driver errors to the user.
91
+ */
92
+ export function sanitizeErrorMessage(message: string, connectionUrl?: string): string {
93
+ if (!connectionUrl) {
94
+ return message;
95
+ }
96
+ try {
97
+ const parsed = new URL(connectionUrl);
98
+ // Replace the full URL (with and without trailing slash)
99
+ let sanitized = message;
100
+ sanitized = sanitized.replaceAll(connectionUrl, maskConnectionUrl(connectionUrl));
101
+ // Also replace the password and username individually if they appear
102
+ if (parsed.password) {
103
+ sanitized = sanitized.replaceAll(parsed.password, '****');
104
+ }
105
+ if (parsed.username) {
106
+ sanitized = sanitized.replaceAll(parsed.username, '****');
107
+ }
108
+ return sanitized;
109
+ } catch {
110
+ // For libpq-style strings, mask password and user values in the message
111
+ return message
112
+ .replace(/password\s*=\s*\S+/gi, 'password=****')
113
+ .replace(/user\s*=\s*\S+/gi, 'user=****');
114
+ }
26
115
  }
@@ -3,12 +3,7 @@ import {
3
3
  type TargetBoundComponentDescriptor,
4
4
  } from '@prisma-next/contract/framework-components';
5
5
  import type { ContractIR } from '@prisma-next/contract/ir';
6
- import type {
7
- ControlAdapterDescriptor,
8
- ControlExtensionDescriptor,
9
- ControlFamilyDescriptor,
10
- ControlTargetDescriptor,
11
- } from '@prisma-next/core-control-plane/types';
6
+ import type { ControlPlaneStack } from '@prisma-next/core-control-plane/types';
12
7
  import { errorConfigValidation, errorContractMissingExtensionPacks } from './cli-errors';
13
8
 
14
9
  /**
@@ -113,17 +108,14 @@ export function assertFrameworkComponentsCompatible<
113
108
  }
114
109
 
115
110
  /**
116
- * Validates that a contract is compatible with the configured family, target, adapter,
111
+ * Validates that a contract is compatible with the configured target, adapter,
117
112
  * and extension packs. Throws on family/target mismatches or missing extension packs.
118
113
  *
119
114
  * This check ensures the emitted contract matches the CLI config before running
120
115
  * commands that depend on the contract (e.g., db verify, db sign).
121
116
  *
122
117
  * @param contract - The contract IR to validate (must include targetFamily, target, extensionPacks).
123
- * @param family - The configured family descriptor.
124
- * @param target - The configured target descriptor.
125
- * @param adapter - The configured adapter descriptor.
126
- * @param extensionPacks - Optional array of extension descriptors provided by the config.
118
+ * @param stack - The control plane stack (target, adapter, driver, extensionPacks).
127
119
  *
128
120
  * @throws {CliStructuredError} errorConfigValidation when contract.targetFamily or contract.target
129
121
  * doesn't match the configured family/target.
@@ -136,15 +128,10 @@ export function assertFrameworkComponentsCompatible<
136
128
  *
137
129
  * const config = await loadConfig();
138
130
  * const contractIR = await loadContractJson(config.contract.output);
131
+ * const stack = createControlPlaneStack({ target: config.target, adapter: config.adapter, ... });
139
132
  *
140
133
  * // Throws if contract is incompatible with config
141
- * assertContractRequirementsSatisfied({
142
- * contract: contractIR,
143
- * family: config.family,
144
- * target: config.target,
145
- * adapter: config.adapter,
146
- * extensionPacks: config.extensionPacks,
147
- * });
134
+ * assertContractRequirementsSatisfied({ contract: contractIR, stack });
148
135
  * ```
149
136
  */
150
137
  export function assertContractRequirementsSatisfied<
@@ -152,26 +139,20 @@ export function assertContractRequirementsSatisfied<
152
139
  TTargetId extends string,
153
140
  >({
154
141
  contract,
155
- family,
156
- target,
157
- adapter,
158
- extensionPacks,
142
+ stack,
159
143
  }: {
160
144
  readonly contract: Pick<ContractIR, 'targetFamily' | 'target' | 'extensionPacks'>;
161
- readonly family: ControlFamilyDescriptor<TFamilyId>;
162
- readonly target: ControlTargetDescriptor<TFamilyId, TTargetId>;
163
- readonly adapter: ControlAdapterDescriptor<TFamilyId, TTargetId>;
164
- readonly extensionPacks?: readonly ControlExtensionDescriptor<TFamilyId, TTargetId>[] | undefined;
145
+ readonly stack: ControlPlaneStack<TFamilyId, TTargetId>;
165
146
  }): void {
166
- const providedComponentIds = new Set<string>([target.id, adapter.id]);
167
- for (const extension of extensionPacks ?? []) {
147
+ const providedComponentIds = new Set<string>([stack.target.id, stack.adapter.id]);
148
+ for (const extension of stack.extensionPacks) {
168
149
  providedComponentIds.add(extension.id);
169
150
  }
170
151
 
171
152
  const result = checkContractComponentRequirements({
172
153
  contract,
173
- expectedTargetFamily: family.familyId,
174
- expectedTargetId: target.targetId,
154
+ expectedTargetFamily: stack.target.familyId,
155
+ expectedTargetId: stack.target.targetId,
175
156
  providedComponentIds,
176
157
  });
177
158
 
@@ -0,0 +1,190 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { relative, resolve } from 'node:path';
3
+ import { notOk, ok, type Result } from '@prisma-next/utils/result';
4
+ import type { Command } from 'commander';
5
+ import { loadConfig } from '../config-loader';
6
+ import { createControlClient } from '../control-api/client';
7
+ import type { ControlClient } from '../control-api/types';
8
+ import {
9
+ type CliStructuredError,
10
+ errorContractValidationFailed,
11
+ errorDatabaseConnectionRequired,
12
+ errorDriverRequired,
13
+ errorFileNotFound,
14
+ errorTargetMigrationNotSupported,
15
+ errorUnexpected,
16
+ } from './cli-errors';
17
+ import { maskConnectionUrl, resolveContractPath } from './command-helpers';
18
+ import type { GlobalFlags } from './global-flags';
19
+ import { formatStyledHeader } from './output';
20
+ import { createProgressAdapter } from './progress-adapter';
21
+
22
+ /**
23
+ * Resolved context for a migration command.
24
+ * Contains everything needed to invoke a control-api operation.
25
+ */
26
+ export interface MigrationContext {
27
+ readonly client: ControlClient;
28
+ readonly contractJson: Record<string, unknown>;
29
+ readonly dbConnection: unknown;
30
+ readonly onProgress: ReturnType<typeof createProgressAdapter>;
31
+ readonly configPath: string;
32
+ readonly contractPath: string;
33
+ readonly contractPathAbsolute: string;
34
+ readonly config: Awaited<ReturnType<typeof loadConfig>>;
35
+ }
36
+
37
+ /**
38
+ * Command-specific configuration for the shared scaffold.
39
+ */
40
+ export interface MigrationCommandDescriptor {
41
+ readonly commandName: string;
42
+ readonly description: string;
43
+ readonly url: string;
44
+ }
45
+
46
+ /**
47
+ * Prepares the shared context for migration commands (db init, db update).
48
+ *
49
+ * Handles: config loading, contract file reading, JSON parsing, connection resolution,
50
+ * driver/migration-support validation, client creation, and header output.
51
+ *
52
+ * Returns a Result with either the resolved context or a structured error.
53
+ */
54
+ export async function prepareMigrationContext(
55
+ options: { readonly db?: string; readonly config?: string; readonly plan?: boolean },
56
+ flags: GlobalFlags,
57
+ descriptor: MigrationCommandDescriptor,
58
+ ): Promise<Result<MigrationContext, CliStructuredError>> {
59
+ // Load config
60
+ const config = await loadConfig(options.config);
61
+ const configPath = options.config
62
+ ? relative(process.cwd(), resolve(options.config))
63
+ : 'prisma-next.config.ts';
64
+ const contractPathAbsolute = resolveContractPath(config);
65
+ const contractPath = relative(process.cwd(), contractPathAbsolute);
66
+
67
+ // Output header
68
+ if (flags.json !== 'object' && !flags.quiet) {
69
+ const details: Array<{ label: string; value: string }> = [
70
+ { label: 'config', value: configPath },
71
+ { label: 'contract', value: contractPath },
72
+ ];
73
+ if (options.db) {
74
+ details.push({ label: 'database', value: maskConnectionUrl(options.db) });
75
+ }
76
+ if (options.plan) {
77
+ details.push({ label: 'mode', value: 'plan (dry run)' });
78
+ }
79
+ const header = formatStyledHeader({
80
+ command: descriptor.commandName,
81
+ description: descriptor.description,
82
+ url: descriptor.url,
83
+ details,
84
+ flags,
85
+ });
86
+ console.log(header);
87
+ }
88
+
89
+ // Load contract file
90
+ let contractJsonContent: string;
91
+ try {
92
+ contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');
93
+ } catch (error) {
94
+ if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {
95
+ return notOk(
96
+ errorFileNotFound(contractPathAbsolute, {
97
+ why: `Contract file not found at ${contractPathAbsolute}`,
98
+ fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`,
99
+ }),
100
+ );
101
+ }
102
+ return notOk(
103
+ errorUnexpected(error instanceof Error ? error.message : String(error), {
104
+ why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,
105
+ }),
106
+ );
107
+ }
108
+
109
+ // Parse contract JSON
110
+ let contractJson: Record<string, unknown>;
111
+ try {
112
+ contractJson = JSON.parse(contractJsonContent) as Record<string, unknown>;
113
+ } catch (error) {
114
+ return notOk(
115
+ errorContractValidationFailed(
116
+ `Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
117
+ { where: { path: contractPathAbsolute } },
118
+ ),
119
+ );
120
+ }
121
+
122
+ // Resolve database connection (--db flag or config.db.connection)
123
+ const dbConnection = options.db ?? config.db?.connection;
124
+ if (!dbConnection) {
125
+ return notOk(
126
+ errorDatabaseConnectionRequired({
127
+ why: `Database connection is required for ${descriptor.commandName} (set db.connection in ${configPath}, or pass --db <url>)`,
128
+ }),
129
+ );
130
+ }
131
+
132
+ // Check for driver
133
+ if (!config.driver) {
134
+ return notOk(
135
+ errorDriverRequired({ why: `Config.driver is required for ${descriptor.commandName}` }),
136
+ );
137
+ }
138
+
139
+ // Check target supports migrations via optional descriptor capability
140
+ const targetWithMigrations = config.target as typeof config.target & {
141
+ readonly migrations?: unknown;
142
+ };
143
+ if (!targetWithMigrations.migrations) {
144
+ return notOk(
145
+ errorTargetMigrationNotSupported({
146
+ why: `Target "${config.target.id}" does not support migrations`,
147
+ }),
148
+ );
149
+ }
150
+
151
+ // Create control client
152
+ const client = createControlClient({
153
+ family: config.family,
154
+ target: config.target,
155
+ adapter: config.adapter,
156
+ driver: config.driver,
157
+ extensionPacks: config.extensionPacks ?? [],
158
+ });
159
+
160
+ // Create progress adapter
161
+ const onProgress = createProgressAdapter({ flags });
162
+
163
+ return ok({
164
+ client,
165
+ contractJson,
166
+ dbConnection,
167
+ onProgress,
168
+ configPath,
169
+ contractPath,
170
+ contractPathAbsolute,
171
+ config,
172
+ });
173
+ }
174
+
175
+ /**
176
+ * Registers the shared CLI options for migration commands (db init, db update).
177
+ */
178
+ export function addMigrationCommandOptions(command: Command): Command {
179
+ return command
180
+ .option('--db <url>', 'Database connection string')
181
+ .option('--config <path>', 'Path to prisma-next.config.ts')
182
+ .option('--plan', 'Preview planned operations without applying', false)
183
+ .option('--json [format]', 'Output as JSON (object)', false)
184
+ .option('-q, --quiet', 'Quiet mode: errors only')
185
+ .option('-v, --verbose', 'Verbose output: debug info, timings')
186
+ .option('-vv, --trace', 'Trace output: deep internals, stack traces')
187
+ .option('--timestamps', 'Add timestamps to output')
188
+ .option('--color', 'Force color output')
189
+ .option('--no-color', 'Disable color output');
190
+ }