@prisma-next/cli 0.3.0-dev.17 → 0.3.0-dev.19
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.
- package/dist/{chunk-ZG5T6OB5.js → chunk-AGOTG4L3.js} +43 -1
- package/dist/chunk-AGOTG4L3.js.map +1 -0
- package/dist/chunk-HLLI4YL7.js +180 -0
- package/dist/chunk-HLLI4YL7.js.map +1 -0
- package/dist/chunk-VG2R7DGF.js +735 -0
- package/dist/chunk-VG2R7DGF.js.map +1 -0
- package/dist/cli.js +1621 -1382
- package/dist/cli.js.map +1 -1
- package/dist/commands/contract-emit.d.ts.map +1 -1
- package/dist/commands/contract-emit.js +3 -4
- package/dist/commands/db-init.js +4 -49
- package/dist/commands/db-init.js.map +1 -1
- package/dist/commands/db-introspect.d.ts.map +1 -1
- package/dist/commands/db-introspect.js +106 -136
- package/dist/commands/db-introspect.js.map +1 -1
- package/dist/commands/db-schema-verify.d.ts.map +1 -1
- package/dist/commands/db-schema-verify.js +118 -110
- package/dist/commands/db-schema-verify.js.map +1 -1
- package/dist/commands/db-sign.d.ts.map +1 -1
- package/dist/commands/db-sign.js +150 -153
- package/dist/commands/db-sign.js.map +1 -1
- package/dist/commands/db-verify.d.ts.map +1 -1
- package/dist/commands/db-verify.js +140 -119
- package/dist/commands/db-verify.js.map +1 -1
- package/dist/control-api/client.d.ts.map +1 -1
- package/dist/control-api/types.d.ts +132 -1
- package/dist/control-api/types.d.ts.map +1 -1
- package/dist/exports/control-api.d.ts +1 -1
- package/dist/exports/control-api.d.ts.map +1 -1
- package/dist/exports/control-api.js +1 -3
- package/dist/exports/index.js +3 -4
- package/dist/exports/index.js.map +1 -1
- package/package.json +10 -10
- package/src/commands/contract-emit.ts +179 -102
- package/src/commands/db-introspect.ts +151 -178
- package/src/commands/db-schema-verify.ts +150 -143
- package/src/commands/db-sign.ts +202 -196
- package/src/commands/db-verify.ts +179 -149
- package/src/control-api/client.ts +352 -22
- package/src/control-api/types.ts +149 -1
- package/src/exports/control-api.ts +9 -0
- package/dist/chunk-5MPKZYVI.js +0 -47
- package/dist/chunk-5MPKZYVI.js.map +0 -1
- package/dist/chunk-6EPKRATC.js +0 -91
- package/dist/chunk-6EPKRATC.js.map +0 -1
- package/dist/chunk-74IELXRA.js +0 -371
- package/dist/chunk-74IELXRA.js.map +0 -1
- package/dist/chunk-U6QI3AZ3.js +0 -133
- package/dist/chunk-U6QI3AZ3.js.map +0 -1
- package/dist/chunk-VI2YETW7.js +0 -38
- package/dist/chunk-VI2YETW7.js.map +0 -1
- package/dist/chunk-ZG5T6OB5.js.map +0 -1
- package/dist/utils/action.d.ts +0 -16
- package/dist/utils/action.d.ts.map +0 -1
- package/dist/utils/spinner.d.ts +0 -29
- package/dist/utils/spinner.d.ts.map +0 -1
- package/src/utils/action.ts +0 -43
- package/src/utils/spinner.ts +0 -67
|
@@ -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 {
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
201
|
+
format: 'ndjson',
|
|
202
|
+
supportedFormats: ['object'],
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
205
|
+
const exitCode = handleResult(result, flags);
|
|
206
|
+
process.exit(exitCode);
|
|
207
|
+
}
|
|
120
208
|
|
|
121
|
-
|
|
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);
|