@prisma-next/cli 0.3.0-pr.87.2 → 0.3.0-pr.88.2

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 (43) hide show
  1. package/dist/{chunk-74IELXRA.js → chunk-4GX6MW6J.js} +265 -19
  2. package/dist/chunk-4GX6MW6J.js.map +1 -0
  3. package/dist/chunk-BO73VO4I.js +45 -0
  4. package/dist/chunk-BO73VO4I.js.map +1 -0
  5. package/dist/{chunk-U6QI3AZ3.js → chunk-HLEJGWAP.js} +44 -5
  6. package/dist/chunk-HLEJGWAP.js.map +1 -0
  7. package/dist/{chunk-VI2YETW7.js → chunk-MPSJAVF6.js} +4 -2
  8. package/dist/cli.js +694 -522
  9. package/dist/cli.js.map +1 -1
  10. package/dist/commands/contract-emit.js +2 -3
  11. package/dist/commands/db-init.js +5 -46
  12. package/dist/commands/db-init.js.map +1 -1
  13. package/dist/commands/db-introspect.d.ts.map +1 -1
  14. package/dist/commands/db-introspect.js +128 -133
  15. package/dist/commands/db-introspect.js.map +1 -1
  16. package/dist/commands/db-schema-verify.d.ts.map +1 -1
  17. package/dist/commands/db-schema-verify.js +119 -107
  18. package/dist/commands/db-schema-verify.js.map +1 -1
  19. package/dist/commands/db-sign.d.ts.map +1 -1
  20. package/dist/commands/db-sign.js +128 -125
  21. package/dist/commands/db-sign.js.map +1 -1
  22. package/dist/commands/db-verify.d.ts.map +1 -1
  23. package/dist/commands/db-verify.js +141 -116
  24. package/dist/commands/db-verify.js.map +1 -1
  25. package/dist/control-api/types.d.ts +24 -0
  26. package/dist/control-api/types.d.ts.map +1 -1
  27. package/dist/exports/control-api.js +2 -3
  28. package/dist/exports/index.js +2 -3
  29. package/dist/exports/index.js.map +1 -1
  30. package/package.json +10 -10
  31. package/src/commands/db-introspect.ts +181 -177
  32. package/src/commands/db-schema-verify.ts +150 -143
  33. package/src/commands/db-sign.ts +172 -164
  34. package/src/commands/db-verify.ts +179 -149
  35. package/src/control-api/client.ts +246 -22
  36. package/src/control-api/types.ts +24 -0
  37. package/dist/chunk-5MPKZYVI.js +0 -47
  38. package/dist/chunk-5MPKZYVI.js.map +0 -1
  39. package/dist/chunk-6EPKRATC.js +0 -91
  40. package/dist/chunk-6EPKRATC.js.map +0 -1
  41. package/dist/chunk-74IELXRA.js.map +0 -1
  42. package/dist/chunk-U6QI3AZ3.js.map +0 -1
  43. /package/dist/{chunk-VI2YETW7.js.map → chunk-MPSJAVF6.js.map} +0 -0
@@ -1,26 +1,24 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { relative, resolve } from 'node:path';
3
- import {
4
- errorDatabaseConnectionRequired,
5
- errorDriverRequired,
6
- errorFileNotFound,
7
- errorRuntime,
8
- errorUnexpected,
9
- } from '@prisma-next/core-control-plane/errors';
10
3
  import type {
11
4
  SignDatabaseResult,
12
5
  VerifyDatabaseSchemaResult,
13
6
  } from '@prisma-next/core-control-plane/types';
14
- import { createControlPlaneStack } from '@prisma-next/core-control-plane/types';
7
+ import { notOk, ok, type Result } from '@prisma-next/utils/result';
15
8
  import { Command } from 'commander';
16
9
  import { loadConfig } from '../config-loader';
17
- import { performAction } from '../utils/action';
18
- import { setCommandDescriptions } from '../utils/command-helpers';
10
+ import { createControlClient } from '../control-api/client';
19
11
  import {
20
- assertContractRequirementsSatisfied,
21
- assertFrameworkComponentsCompatible,
22
- } from '../utils/framework-components';
23
- import { parseGlobalFlags } from '../utils/global-flags';
12
+ CliStructuredError,
13
+ errorContractValidationFailed,
14
+ errorDatabaseConnectionRequired,
15
+ errorDriverRequired,
16
+ errorFileNotFound,
17
+ errorJsonFormatNotSupported,
18
+ errorUnexpected,
19
+ } from '../utils/cli-errors';
20
+ import { setCommandDescriptions } from '../utils/command-helpers';
21
+ import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
24
22
  import {
25
23
  formatCommandHelp,
26
24
  formatSchemaVerifyJson,
@@ -29,8 +27,8 @@ import {
29
27
  formatSignOutput,
30
28
  formatStyledHeader,
31
29
  } from '../utils/output';
30
+ import { createProgressAdapter } from '../utils/progress-adapter';
32
31
  import { handleResult } from '../utils/result-handler';
33
- import { withSpinner } from '../utils/spinner';
34
32
 
35
33
  interface DbSignOptions {
36
34
  readonly db?: string;
@@ -47,6 +45,153 @@ interface DbSignOptions {
47
45
  readonly 'no-color'?: boolean;
48
46
  }
49
47
 
48
+ interface DbSignResult {
49
+ readonly schemaVerifyResult: VerifyDatabaseSchemaResult | undefined;
50
+ readonly signResult: SignDatabaseResult | undefined;
51
+ }
52
+
53
+ /**
54
+ * Executes the db sign command and returns a structured Result.
55
+ */
56
+ async function executeDbSignCommand(
57
+ options: DbSignOptions,
58
+ flags: GlobalFlags,
59
+ ): Promise<Result<DbSignResult, CliStructuredError>> {
60
+ // Load config
61
+ const config = await loadConfig(options.config);
62
+ const configPath = options.config
63
+ ? relative(process.cwd(), resolve(options.config))
64
+ : 'prisma-next.config.ts';
65
+ const contractPathAbsolute = config.contract?.output
66
+ ? resolve(config.contract.output)
67
+ : resolve('src/prisma/contract.json');
68
+ const contractPath = relative(process.cwd(), contractPathAbsolute);
69
+
70
+ // Output header
71
+ if (flags.json !== 'object' && !flags.quiet) {
72
+ const details: Array<{ label: string; value: string }> = [
73
+ { label: 'config', value: configPath },
74
+ { label: 'contract', value: contractPath },
75
+ ];
76
+ if (options.db) {
77
+ details.push({ label: 'database', value: options.db });
78
+ }
79
+ const header = formatStyledHeader({
80
+ command: 'db sign',
81
+ description: 'Sign the database with your contract so you can safely run queries',
82
+ url: 'https://pris.ly/db-sign',
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
+ let contractJson: Record<string, unknown>;
110
+ try {
111
+ contractJson = JSON.parse(contractJsonContent) as Record<string, unknown>;
112
+ } catch (error) {
113
+ return notOk(
114
+ errorContractValidationFailed(
115
+ `Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
116
+ { where: { path: contractPathAbsolute } },
117
+ ),
118
+ );
119
+ }
120
+
121
+ // Resolve database connection (--db flag or config.db.connection)
122
+ const dbConnection = options.db ?? config.db?.connection;
123
+ if (!dbConnection) {
124
+ return notOk(
125
+ errorDatabaseConnectionRequired({
126
+ why: `Database connection is required for db sign (set db.connection in ${configPath}, or pass --db <url>)`,
127
+ }),
128
+ );
129
+ }
130
+
131
+ // Check for driver
132
+ if (!config.driver) {
133
+ return notOk(errorDriverRequired({ why: 'Config.driver is required for db sign' }));
134
+ }
135
+
136
+ // Create control client
137
+ const client = createControlClient({
138
+ family: config.family,
139
+ target: config.target,
140
+ adapter: config.adapter,
141
+ driver: config.driver,
142
+ extensionPacks: config.extensionPacks ?? [],
143
+ });
144
+
145
+ // Create progress adapter
146
+ const onProgress = createProgressAdapter({ flags });
147
+
148
+ try {
149
+ // Step 1: Schema verification - connect here
150
+ const schemaVerifyResult = await client.schemaVerify({
151
+ contractIR: contractJson,
152
+ strict: false,
153
+ connection: dbConnection,
154
+ onProgress,
155
+ });
156
+
157
+ // If schema verification failed, return result for handling
158
+ if (!schemaVerifyResult.ok) {
159
+ // Add blank line after all async operations if spinners were shown
160
+ if (!flags.quiet && flags.json !== 'object' && process.stdout.isTTY) {
161
+ console.log('');
162
+ }
163
+ return ok({ schemaVerifyResult, signResult: undefined });
164
+ }
165
+
166
+ // Step 2: Sign (already connected from schemaVerify)
167
+ const signResult = await client.sign({
168
+ contractIR: contractJson,
169
+ onProgress,
170
+ });
171
+
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('');
175
+ }
176
+
177
+ return ok({ schemaVerifyResult: undefined, signResult });
178
+ } catch (error) {
179
+ // Driver already throws CliStructuredError for connection failures
180
+ if (error instanceof CliStructuredError) {
181
+ return notOk(error);
182
+ }
183
+
184
+ // Wrap unexpected errors
185
+ return notOk(
186
+ errorUnexpected(error instanceof Error ? error.message : String(error), {
187
+ why: `Unexpected error during db sign: ${error instanceof Error ? error.message : String(error)}`,
188
+ }),
189
+ );
190
+ } finally {
191
+ await client.close();
192
+ }
193
+ }
194
+
50
195
  export function createDbSignCommand(): Command {
51
196
  const command = new Command('sign');
52
197
  setCommandDescriptions(
@@ -66,7 +211,7 @@ export function createDbSignCommand(): Command {
66
211
  })
67
212
  .option('--db <url>', 'Database connection string')
68
213
  .option('--config <path>', 'Path to prisma-next.config.ts')
69
- .option('--json [format]', 'Output as JSON (object or ndjson)', false)
214
+ .option('--json [format]', 'Output as JSON (object)', false)
70
215
  .option('-q, --quiet', 'Quiet mode: errors only')
71
216
  .option('-v, --verbose', 'Verbose output: debug info, timings')
72
217
  .option('-vv, --trace', 'Trace output: deep internals, stack traces')
@@ -76,156 +221,20 @@ export function createDbSignCommand(): Command {
76
221
  .action(async (options: DbSignOptions) => {
77
222
  const flags = parseGlobalFlags(options);
78
223
 
79
- const result = await performAction(async () => {
80
- // Load config (file I/O)
81
- const config = await loadConfig(options.config);
82
- // Normalize config path for display (match contract path format - no ./ prefix)
83
- const configPath = options.config
84
- ? relative(process.cwd(), resolve(options.config))
85
- : 'prisma-next.config.ts';
86
- const contractPathAbsolute = config.contract?.output
87
- ? resolve(config.contract.output)
88
- : resolve('src/prisma/contract.json');
89
- // Convert to relative path for display
90
- const contractPath = relative(process.cwd(), contractPathAbsolute);
91
-
92
- // Output header (only for human-readable output)
93
- if (flags.json !== 'object' && !flags.quiet) {
94
- const details: Array<{ label: string; value: string }> = [
95
- { label: 'config', value: configPath },
96
- { label: 'contract', value: contractPath },
97
- ];
98
- if (options.db) {
99
- details.push({ label: 'database', value: options.db });
100
- }
101
- const header = formatStyledHeader({
224
+ // Validate JSON format option
225
+ if (flags.json === 'ndjson') {
226
+ const result = notOk(
227
+ errorJsonFormatNotSupported({
102
228
  command: 'db sign',
103
- description: 'Sign the database with your contract so you can safely run queries',
104
- url: 'https://pris.ly/db-sign',
105
- details,
106
- flags,
107
- });
108
- console.log(header);
109
- }
110
-
111
- // Load contract file (file I/O)
112
- let contractJsonContent: string;
113
- try {
114
- contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');
115
- } catch (error) {
116
- if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {
117
- throw errorFileNotFound(contractPathAbsolute, {
118
- why: `Contract file not found at ${contractPathAbsolute}`,
119
- });
120
- }
121
- throw errorUnexpected(error instanceof Error ? error.message : String(error), {
122
- why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,
123
- });
124
- }
125
- const contractJson = JSON.parse(contractJsonContent) as Record<string, unknown>;
126
-
127
- // Resolve database connection (--db flag or config.db.connection)
128
- const dbConnection = options.db ?? config.db?.connection;
129
- if (!dbConnection) {
130
- throw errorDatabaseConnectionRequired();
131
- }
132
-
133
- // Check for driver
134
- if (!config.driver) {
135
- throw errorDriverRequired();
136
- }
137
-
138
- // Store driver descriptor after null check
139
- const driverDescriptor = config.driver;
140
-
141
- // Create family instance (needed for contract validation - no DB connection required)
142
- const stack = createControlPlaneStack({
143
- target: config.target,
144
- adapter: config.adapter,
145
- driver: driverDescriptor,
146
- extensionPacks: config.extensionPacks,
147
- });
148
- const familyInstance = config.family.create(stack);
149
-
150
- // Validate contract using instance validator (fail-fast before DB connection)
151
- const contractIR = familyInstance.validateContractIR(contractJson);
152
- assertContractRequirementsSatisfied({ contract: contractIR, stack });
153
-
154
- const rawComponents = [config.target, config.adapter, ...(config.extensionPacks ?? [])];
155
- const frameworkComponents = assertFrameworkComponentsCompatible(
156
- config.family.familyId,
157
- config.target.targetId,
158
- rawComponents,
229
+ format: 'ndjson',
230
+ supportedFormats: ['object'],
231
+ }),
159
232
  );
233
+ const exitCode = handleResult(result, flags);
234
+ process.exit(exitCode);
235
+ }
160
236
 
161
- // Create driver (expensive operation - done after validation)
162
- const driver = await driverDescriptor.create(dbConnection);
163
-
164
- try {
165
- // Schema verification precondition with spinner
166
- let schemaVerifyResult: VerifyDatabaseSchemaResult;
167
- try {
168
- schemaVerifyResult = await withSpinner(
169
- () =>
170
- familyInstance.schemaVerify({
171
- driver,
172
- contractIR,
173
- strict: false,
174
- contractPath: contractPathAbsolute,
175
- configPath,
176
- frameworkComponents,
177
- }),
178
- {
179
- message: 'Verifying database satisfies contract',
180
- flags,
181
- },
182
- );
183
- } catch (error) {
184
- // Wrap errors from schemaVerify() in structured error
185
- throw errorRuntime(error instanceof Error ? error.message : String(error), {
186
- why: `Failed to verify database schema: ${error instanceof Error ? error.message : String(error)}`,
187
- });
188
- }
189
-
190
- // If schema verification failed, return both results for handling outside performAction
191
- if (!schemaVerifyResult.ok) {
192
- return { schemaVerifyResult, signResult: undefined };
193
- }
194
-
195
- // Schema verification passed - proceed with signing
196
- let signResult: SignDatabaseResult;
197
- try {
198
- signResult = await withSpinner(
199
- () =>
200
- familyInstance.sign({
201
- driver,
202
- contractIR,
203
- contractPath: contractPathAbsolute,
204
- configPath,
205
- }),
206
- {
207
- message: 'Signing database...',
208
- flags,
209
- },
210
- );
211
- } catch (error) {
212
- // Wrap errors from sign() in structured error
213
- throw errorRuntime(error instanceof Error ? error.message : String(error), {
214
- why: `Failed to sign database: ${error instanceof Error ? error.message : String(error)}`,
215
- });
216
- }
217
-
218
- // Add blank line after all async operations if spinners were shown
219
- if (!flags.quiet && flags.json !== 'object' && process.stdout.isTTY) {
220
- console.log('');
221
- }
222
-
223
- return { schemaVerifyResult: undefined, signResult };
224
- } finally {
225
- // Ensure driver connection is closed
226
- await driver.close();
227
- }
228
- });
237
+ const result = await executeDbSignCommand(options, flags);
229
238
 
230
239
  // Handle result - formats output and returns exit code
231
240
  const exitCode = handleResult(result, flags, (value) => {
@@ -241,7 +250,6 @@ export function createDbSignCommand(): Command {
241
250
  console.log(output);
242
251
  }
243
252
  }
244
- // Don't proceed to sign output formatting
245
253
  return;
246
254
  }
247
255