@prisma-next/cli 0.3.0-dev.17 → 0.3.0-dev.18

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 (45) hide show
  1. package/dist/chunk-BO73VO4I.js +45 -0
  2. package/dist/chunk-BO73VO4I.js.map +1 -0
  3. package/dist/{chunk-VI2YETW7.js → chunk-MPSJAVF6.js} +4 -2
  4. package/dist/{chunk-U6QI3AZ3.js → chunk-RIONCN4I.js} +45 -6
  5. package/dist/chunk-RIONCN4I.js.map +1 -0
  6. package/dist/{chunk-74IELXRA.js → chunk-RPYY5SM7.js} +273 -19
  7. package/dist/chunk-RPYY5SM7.js.map +1 -0
  8. package/dist/cli.js +708 -551
  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 +107 -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 +151 -150
  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/client.d.ts.map +1 -1
  26. package/dist/control-api/types.d.ts +41 -0
  27. package/dist/control-api/types.d.ts.map +1 -1
  28. package/dist/exports/control-api.js +2 -3
  29. package/dist/exports/index.js +2 -3
  30. package/dist/exports/index.js.map +1 -1
  31. package/package.json +10 -10
  32. package/src/commands/contract-emit.ts +1 -1
  33. package/src/commands/db-introspect.ts +151 -178
  34. package/src/commands/db-schema-verify.ts +150 -143
  35. package/src/commands/db-sign.ts +202 -196
  36. package/src/commands/db-verify.ts +179 -149
  37. package/src/control-api/client.ts +256 -22
  38. package/src/control-api/types.ts +42 -0
  39. package/dist/chunk-5MPKZYVI.js +0 -47
  40. package/dist/chunk-5MPKZYVI.js.map +0 -1
  41. package/dist/chunk-6EPKRATC.js +0 -91
  42. package/dist/chunk-6EPKRATC.js.map +0 -1
  43. package/dist/chunk-74IELXRA.js.map +0 -1
  44. package/dist/chunk-U6QI3AZ3.js.map +0 -1
  45. /package/dist/{chunk-VI2YETW7.js.map → chunk-MPSJAVF6.js.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma-next/cli",
3
- "version": "0.3.0-dev.17",
3
+ "version": "0.3.0-dev.18",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "files": [
@@ -20,10 +20,10 @@
20
20
  "string-width": "^7.2.0",
21
21
  "strip-ansi": "^7.1.2",
22
22
  "wrap-ansi": "^9.0.2",
23
- "@prisma-next/contract": "0.3.0-dev.17",
24
- "@prisma-next/core-control-plane": "0.3.0-dev.17",
25
- "@prisma-next/emitter": "0.3.0-dev.17",
26
- "@prisma-next/utils": "0.3.0-dev.17"
23
+ "@prisma-next/contract": "0.3.0-dev.18",
24
+ "@prisma-next/core-control-plane": "0.3.0-dev.18",
25
+ "@prisma-next/emitter": "0.3.0-dev.18",
26
+ "@prisma-next/utils": "0.3.0-dev.18"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/node": "24.10.4",
@@ -31,11 +31,11 @@
31
31
  "tsup": "8.5.1",
32
32
  "typescript": "5.9.3",
33
33
  "vitest": "4.0.16",
34
- "@prisma-next/sql-contract": "0.3.0-dev.17",
35
- "@prisma-next/sql-contract-emitter": "0.3.0-dev.17",
36
- "@prisma-next/sql-contract-ts": "0.3.0-dev.17",
37
- "@prisma-next/sql-operations": "0.3.0-dev.17",
38
- "@prisma-next/sql-runtime": "0.3.0-dev.17",
34
+ "@prisma-next/sql-contract": "0.3.0-dev.18",
35
+ "@prisma-next/sql-contract-emitter": "0.3.0-dev.18",
36
+ "@prisma-next/sql-contract-ts": "0.3.0-dev.18",
37
+ "@prisma-next/sql-operations": "0.3.0-dev.18",
38
+ "@prisma-next/sql-runtime": "0.3.0-dev.18",
39
39
  "@prisma-next/test-utils": "0.0.1"
40
40
  },
41
41
  "exports": {
@@ -1,7 +1,7 @@
1
1
  import { mkdirSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, relative, resolve } from 'node:path';
3
3
  import { errorContractConfigMissing } from '@prisma-next/core-control-plane/errors';
4
- import { createControlPlaneStack } from '@prisma-next/core-control-plane/types';
4
+ import { createControlPlaneStack } from '@prisma-next/core-control-plane/stack';
5
5
  import { Command } from 'commander';
6
6
  import { loadConfig } from '../config-loader';
7
7
  import { performAction } from '../utils/action';
@@ -1,28 +1,27 @@
1
- import { readFile } from 'node:fs/promises';
2
1
  import { relative, resolve } from 'node:path';
3
- import {
4
- errorDatabaseConnectionRequired,
5
- errorDriverRequired,
6
- errorRuntime,
7
- errorUnexpected,
8
- } from '@prisma-next/core-control-plane/errors';
9
2
  import type { CoreSchemaView } from '@prisma-next/core-control-plane/schema-view';
10
3
  import type { IntrospectSchemaResult } from '@prisma-next/core-control-plane/types';
11
- import { createControlPlaneStack } from '@prisma-next/core-control-plane/types';
4
+ import { notOk, ok, type Result } from '@prisma-next/utils/result';
12
5
  import { Command } from 'commander';
13
6
  import { loadConfig } from '../config-loader';
14
- import { performAction } from '../utils/action';
7
+ import { createControlClient } from '../control-api/client';
8
+ import {
9
+ CliStructuredError,
10
+ errorDatabaseConnectionRequired,
11
+ errorDriverRequired,
12
+ errorJsonFormatNotSupported,
13
+ errorUnexpected,
14
+ } from '../utils/cli-errors';
15
15
  import { setCommandDescriptions } from '../utils/command-helpers';
16
- import { assertContractRequirementsSatisfied } from '../utils/framework-components';
17
- import { parseGlobalFlags } from '../utils/global-flags';
16
+ import { type GlobalFlags, parseGlobalFlags } from '../utils/global-flags';
18
17
  import {
19
18
  formatCommandHelp,
20
19
  formatIntrospectJson,
21
20
  formatIntrospectOutput,
22
21
  formatStyledHeader,
23
22
  } from '../utils/output';
23
+ import { createProgressAdapter } from '../utils/progress-adapter';
24
24
  import { handleResult } from '../utils/result-handler';
25
- import { withSpinner } from '../utils/spinner';
26
25
 
27
26
  interface DbIntrospectOptions {
28
27
  readonly db?: string;
@@ -39,6 +38,132 @@ interface DbIntrospectOptions {
39
38
  readonly 'no-color'?: boolean;
40
39
  }
41
40
 
41
+ interface DbIntrospectCommandResult {
42
+ readonly introspectResult: IntrospectSchemaResult<unknown>;
43
+ readonly schemaView: CoreSchemaView | undefined;
44
+ }
45
+
46
+ /**
47
+ * Executes the db introspect command and returns a structured Result.
48
+ */
49
+ async function executeDbIntrospectCommand(
50
+ options: DbIntrospectOptions,
51
+ flags: GlobalFlags,
52
+ startTime: number,
53
+ ): Promise<Result<DbIntrospectCommandResult, CliStructuredError>> {
54
+ // Load config
55
+ const config = await loadConfig(options.config);
56
+ const configPath = options.config
57
+ ? relative(process.cwd(), resolve(options.config))
58
+ : 'prisma-next.config.ts';
59
+
60
+ // Output header
61
+ if (flags.json !== 'object' && !flags.quiet) {
62
+ const details: Array<{ label: string; value: string }> = [
63
+ { label: 'config', value: configPath },
64
+ ];
65
+ if (options.db) {
66
+ // Mask password in URL for security
67
+ const maskedUrl = options.db.replace(/:([^:@]+)@/, ':****@');
68
+ details.push({ label: 'database', value: maskedUrl });
69
+ } else if (config.db?.connection && typeof config.db.connection === 'string') {
70
+ // Mask password in URL for security
71
+ const maskedUrl = config.db.connection.replace(/:([^:@]+)@/, ':****@');
72
+ details.push({ label: 'database', value: maskedUrl });
73
+ }
74
+ const header = formatStyledHeader({
75
+ command: 'db introspect',
76
+ description: 'Inspect the database schema',
77
+ url: 'https://pris.ly/db-introspect',
78
+ details,
79
+ flags,
80
+ });
81
+ console.log(header);
82
+ }
83
+
84
+ // Resolve database connection (--db flag or config.db.connection)
85
+ const dbConnection = options.db ?? config.db?.connection;
86
+ if (!dbConnection) {
87
+ return notOk(
88
+ errorDatabaseConnectionRequired({
89
+ why: `Database connection is required for db introspect (set db.connection in ${configPath}, or pass --db <url>)`,
90
+ }),
91
+ );
92
+ }
93
+
94
+ // Check for driver
95
+ if (!config.driver) {
96
+ return notOk(errorDriverRequired({ why: 'Config.driver is required for db introspect' }));
97
+ }
98
+
99
+ // Create control client
100
+ const client = createControlClient({
101
+ family: config.family,
102
+ target: config.target,
103
+ adapter: config.adapter,
104
+ driver: config.driver,
105
+ extensionPacks: config.extensionPacks ?? [],
106
+ });
107
+
108
+ // Create progress adapter
109
+ const onProgress = createProgressAdapter({ flags });
110
+
111
+ try {
112
+ // Introspect with connection and progress
113
+ const schemaIR = await client.introspect({
114
+ connection: dbConnection,
115
+ onProgress,
116
+ });
117
+
118
+ // Add blank line after all async operations if spinners were shown
119
+ if (!flags.quiet && flags.json !== 'object' && process.stdout.isTTY) {
120
+ console.log('');
121
+ }
122
+
123
+ // Call toSchemaView to convert schema IR to CoreSchemaView for tree rendering
124
+ const schemaView = client.toSchemaView(schemaIR);
125
+
126
+ const totalTime = Date.now() - startTime;
127
+
128
+ // Get masked connection URL for meta (only for string connections)
129
+ const connectionForMeta =
130
+ typeof dbConnection === 'string' ? dbConnection.replace(/:([^:@]+)@/, ':****@') : undefined;
131
+
132
+ const introspectResult: IntrospectSchemaResult<unknown> = {
133
+ ok: true,
134
+ summary: 'Schema introspected successfully',
135
+ target: {
136
+ familyId: config.family.familyId,
137
+ id: config.target.targetId,
138
+ },
139
+ schema: schemaIR,
140
+ meta: {
141
+ ...(configPath ? { configPath } : {}),
142
+ ...(connectionForMeta ? { dbUrl: connectionForMeta } : {}),
143
+ },
144
+ timings: {
145
+ total: totalTime,
146
+ },
147
+ };
148
+
149
+ return ok({ introspectResult, schemaView });
150
+ } catch (error) {
151
+ // Driver already throws CliStructuredError for connection failures
152
+ if (error instanceof CliStructuredError) {
153
+ return notOk(error);
154
+ }
155
+
156
+ // Wrap unexpected errors
157
+ return notOk(
158
+ errorUnexpected(error instanceof Error ? error.message : String(error), {
159
+ why: `Unexpected error during db introspect: ${error instanceof Error ? error.message : String(error)}`,
160
+ }),
161
+ );
162
+ } finally {
163
+ await client.close();
164
+ }
165
+ }
166
+
42
167
  export function createDbIntrospectCommand(): Command {
43
168
  const command = new Command('introspect');
44
169
  setCommandDescriptions(
@@ -57,7 +182,7 @@ export function createDbIntrospectCommand(): Command {
57
182
  })
58
183
  .option('--db <url>', 'Database connection string')
59
184
  .option('--config <path>', 'Path to prisma-next.config.ts')
60
- .option('--json [format]', 'Output as JSON (object or ndjson)', false)
185
+ .option('--json [format]', 'Output as JSON (object)', false)
61
186
  .option('-q, --quiet', 'Quiet mode: errors only')
62
187
  .option('-v, --verbose', 'Verbose output: debug info, timings')
63
188
  .option('-vv, --trace', 'Trace output: deep internals, stack traces')
@@ -66,181 +191,29 @@ export function createDbIntrospectCommand(): Command {
66
191
  .option('--no-color', 'Disable color output')
67
192
  .action(async (options: DbIntrospectOptions) => {
68
193
  const flags = parseGlobalFlags(options);
194
+ const startTime = Date.now();
69
195
 
70
- const result = await performAction(async () => {
71
- const startTime = Date.now();
72
-
73
- // Load config (file I/O)
74
- const config = await loadConfig(options.config);
75
- // Normalize config path for display (match contract path format - no ./ prefix)
76
- const configPath = options.config
77
- ? relative(process.cwd(), resolve(options.config))
78
- : 'prisma-next.config.ts';
79
-
80
- // Optionally load contract if contract config exists
81
- let contractIR: unknown | undefined;
82
- if (config.contract?.output) {
83
- const contractPath = resolve(config.contract.output);
84
- try {
85
- const contractJsonContent = await readFile(contractPath, 'utf-8');
86
- contractIR = JSON.parse(contractJsonContent);
87
- } catch (error) {
88
- // Contract file is optional for introspection - don't fail if it doesn't exist
89
- if (error instanceof Error && (error as { code?: string }).code !== 'ENOENT') {
90
- throw errorUnexpected(error.message, {
91
- why: `Failed to read contract file: ${error.message}`,
92
- });
93
- }
94
- }
95
- }
96
-
97
- // Output header (only for human-readable output)
98
- if (flags.json !== 'object' && !flags.quiet) {
99
- const details: Array<{ label: string; value: string }> = [
100
- { label: 'config', value: configPath },
101
- ];
102
- if (options.db) {
103
- // Mask password in URL for security
104
- const maskedUrl = options.db.replace(/:([^:@]+)@/, ':****@');
105
- details.push({ label: 'database', value: maskedUrl });
106
- } else if (config.db?.connection && typeof config.db.connection === 'string') {
107
- // Mask password in URL for security
108
- const maskedUrl = config.db.connection.replace(/:([^:@]+)@/, ':****@');
109
- details.push({ label: 'database', value: maskedUrl });
110
- }
111
- const header = formatStyledHeader({
196
+ // Validate JSON format option
197
+ if (flags.json === 'ndjson') {
198
+ const result = notOk(
199
+ errorJsonFormatNotSupported({
112
200
  command: 'db introspect',
113
- description: 'Inspect the database schema',
114
- url: 'https://pris.ly/db-introspect',
115
- details,
116
- flags,
117
- });
118
- console.log(header);
119
- }
201
+ format: 'ndjson',
202
+ supportedFormats: ['object'],
203
+ }),
204
+ );
205
+ const exitCode = handleResult(result, flags);
206
+ process.exit(exitCode);
207
+ }
120
208
 
121
- // Resolve database connection (--db flag or config.db.connection)
122
- const dbConnection = options.db ?? config.db?.connection;
123
- if (!dbConnection) {
124
- throw errorDatabaseConnectionRequired();
125
- }
126
-
127
- // Check for driver
128
- if (!config.driver) {
129
- throw errorDriverRequired();
130
- }
131
-
132
- // Store driver descriptor after null check
133
- const driverDescriptor = config.driver;
134
-
135
- const driver = await withSpinner(() => driverDescriptor.create(dbConnection), {
136
- message: 'Connecting to database...',
137
- flags,
138
- });
139
-
140
- try {
141
- // Create family instance
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 IR if we loaded it
151
- if (contractIR) {
152
- const validatedContract = familyInstance.validateContractIR(contractIR);
153
- assertContractRequirementsSatisfied({ contract: validatedContract, stack });
154
- contractIR = validatedContract;
155
- }
156
-
157
- // Call family instance introspect method
158
- let schemaIR: unknown;
159
- try {
160
- schemaIR = await withSpinner(
161
- () =>
162
- familyInstance.introspect({
163
- driver,
164
- contractIR,
165
- }),
166
- {
167
- message: 'Introspecting database schema...',
168
- flags,
169
- },
170
- );
171
- } catch (error) {
172
- // Wrap errors from introspect() in structured error
173
- throw errorRuntime(error instanceof Error ? error.message : String(error), {
174
- why: `Failed to introspect database: ${error instanceof Error ? error.message : String(error)}`,
175
- });
176
- }
177
-
178
- // Optionally call toSchemaView if available
179
- let schemaView: CoreSchemaView | undefined;
180
- if (familyInstance.toSchemaView) {
181
- try {
182
- schemaView = familyInstance.toSchemaView(schemaIR);
183
- } catch (error) {
184
- // Schema view projection is optional - log but don't fail
185
- if (flags.verbose) {
186
- console.error(
187
- `Warning: Failed to project schema to view: ${error instanceof Error ? error.message : String(error)}`,
188
- );
189
- }
190
- }
191
- }
192
-
193
- const totalTime = Date.now() - startTime;
194
-
195
- // Add blank line after all async operations if spinners were shown
196
- if (!flags.quiet && flags.json !== 'object' && process.stdout.isTTY) {
197
- console.log('');
198
- }
199
-
200
- // Build result envelope
201
- // Get masked connection URL for meta (only for string connections)
202
- const connectionForMeta =
203
- typeof dbConnection === 'string'
204
- ? dbConnection.replace(/:([^:@]+)@/, ':****@')
205
- : undefined;
206
-
207
- const introspectResult: IntrospectSchemaResult<unknown> = {
208
- ok: true,
209
- summary: 'Schema introspected successfully',
210
- target: {
211
- familyId: config.family.familyId,
212
- id: config.target.targetId,
213
- },
214
- schema: schemaIR,
215
- ...(configPath || connectionForMeta
216
- ? {
217
- meta: {
218
- ...(configPath ? { configPath } : {}),
219
- ...(connectionForMeta ? { dbUrl: connectionForMeta } : {}),
220
- },
221
- }
222
- : {}),
223
- timings: {
224
- total: totalTime,
225
- },
226
- };
227
-
228
- return { introspectResult, schemaView };
229
- } finally {
230
- // Ensure driver connection is closed
231
- await driver.close();
232
- }
233
- });
209
+ const result = await executeDbIntrospectCommand(options, flags, startTime);
234
210
 
235
211
  // Handle result - formats output and returns exit code
236
212
  const exitCode = handleResult(result, flags, (value) => {
237
213
  const { introspectResult, schemaView } = value;
238
- // Output based on flags
239
214
  if (flags.json === 'object') {
240
- // JSON output to stdout
241
215
  console.log(formatIntrospectJson(introspectResult));
242
216
  } else {
243
- // Human-readable output to stdout
244
217
  const output = formatIntrospectOutput(introspectResult, schemaView, flags);
245
218
  if (output) {
246
219
  console.log(output);