@prisma-next/cli 0.1.0-dev.2 → 0.1.0-dev.21
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/README.md +172 -20
- package/dist/{chunk-4Q3MO4TK.js → chunk-VWAVGWUP.js} +2 -2
- package/dist/chunk-VWAVGWUP.js.map +1 -0
- package/dist/chunk-YDE4ILKH.js +55 -0
- package/dist/chunk-YDE4ILKH.js.map +1 -0
- package/dist/{chunk-3EODSNGS.js → chunk-ZKEJZ3NU.js} +94 -32
- package/dist/chunk-ZKEJZ3NU.js.map +1 -0
- package/dist/cli.js +469 -99
- package/dist/cli.js.map +1 -1
- package/dist/commands/contract-emit.js +2 -2
- package/dist/commands/db-init.d.ts +5 -0
- package/dist/commands/db-init.js +292 -0
- package/dist/commands/db-init.js.map +1 -0
- package/dist/commands/db-introspect.js +6 -8
- package/dist/commands/db-introspect.js.map +1 -1
- package/dist/commands/db-schema-verify.js +14 -5
- package/dist/commands/db-schema-verify.js.map +1 -1
- package/dist/commands/db-sign.js +25 -20
- package/dist/commands/db-sign.js.map +1 -1
- package/dist/commands/db-verify.js +3 -4
- package/dist/commands/db-verify.js.map +1 -1
- package/dist/index.js +4 -4
- package/package.json +18 -11
- package/dist/chunk-3EODSNGS.js.map +0 -1
- package/dist/chunk-4Q3MO4TK.js.map +0 -1
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertFrameworkComponentsCompatible
|
|
3
|
+
} from "../chunk-YDE4ILKH.js";
|
|
4
|
+
import {
|
|
5
|
+
errorDatabaseUrlRequired,
|
|
6
|
+
errorDriverRequired,
|
|
7
|
+
errorFileNotFound,
|
|
8
|
+
errorMigrationPlanningFailed,
|
|
9
|
+
errorRuntime,
|
|
10
|
+
errorTargetMigrationNotSupported,
|
|
11
|
+
errorUnexpected,
|
|
12
|
+
formatCommandHelp,
|
|
13
|
+
formatDbInitApplyOutput,
|
|
14
|
+
formatDbInitJson,
|
|
15
|
+
formatDbInitPlanOutput,
|
|
16
|
+
formatStyledHeader,
|
|
17
|
+
handleResult,
|
|
18
|
+
parseGlobalFlags,
|
|
19
|
+
performAction,
|
|
20
|
+
setCommandDescriptions,
|
|
21
|
+
withSpinner
|
|
22
|
+
} from "../chunk-ZKEJZ3NU.js";
|
|
23
|
+
import {
|
|
24
|
+
loadConfig
|
|
25
|
+
} from "../chunk-HWYQOCAJ.js";
|
|
26
|
+
|
|
27
|
+
// src/commands/db-init.ts
|
|
28
|
+
import { readFile } from "fs/promises";
|
|
29
|
+
import { relative, resolve } from "path";
|
|
30
|
+
import { Command } from "commander";
|
|
31
|
+
function createDbInitCommand() {
|
|
32
|
+
const command = new Command("init");
|
|
33
|
+
setCommandDescriptions(
|
|
34
|
+
command,
|
|
35
|
+
"Bootstrap a database to match the current contract and write the contract marker",
|
|
36
|
+
"Initializes a database to match your emitted contract using additive-only operations.\nCreates any missing tables, columns, indexes, and constraints defined in your contract.\nLeaves existing compatible structures in place, surfaces conflicts when destructive changes\nwould be required, and writes a contract marker to track the database state. Use --plan to\npreview changes without applying."
|
|
37
|
+
);
|
|
38
|
+
command.configureHelp({
|
|
39
|
+
formatHelp: (cmd) => {
|
|
40
|
+
const flags = parseGlobalFlags({});
|
|
41
|
+
return formatCommandHelp({ command: cmd, flags });
|
|
42
|
+
}
|
|
43
|
+
}).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--plan", "Preview planned operations without applying", false).option("--json [format]", "Output as JSON (object or ndjson)", false).option("-q, --quiet", "Quiet mode: errors only").option("-v, --verbose", "Verbose output: debug info, timings").option("-vv, --trace", "Trace output: deep internals, stack traces").option("--timestamps", "Add timestamps to output").option("--color", "Force color output").option("--no-color", "Disable color output").action(async (options) => {
|
|
44
|
+
const flags = parseGlobalFlags(options);
|
|
45
|
+
const startTime = Date.now();
|
|
46
|
+
const result = await performAction(async () => {
|
|
47
|
+
const config = await loadConfig(options.config);
|
|
48
|
+
const configPath = options.config ? relative(process.cwd(), resolve(options.config)) : "prisma-next.config.ts";
|
|
49
|
+
const contractPathAbsolute = config.contract?.output ? resolve(config.contract.output) : resolve("src/prisma/contract.json");
|
|
50
|
+
const contractPath = relative(process.cwd(), contractPathAbsolute);
|
|
51
|
+
if (flags.json !== "object" && !flags.quiet) {
|
|
52
|
+
const details = [
|
|
53
|
+
{ label: "config", value: configPath },
|
|
54
|
+
{ label: "contract", value: contractPath }
|
|
55
|
+
];
|
|
56
|
+
if (options.db) {
|
|
57
|
+
details.push({ label: "database", value: options.db });
|
|
58
|
+
}
|
|
59
|
+
if (options.plan) {
|
|
60
|
+
details.push({ label: "mode", value: "plan (dry run)" });
|
|
61
|
+
}
|
|
62
|
+
const header = formatStyledHeader({
|
|
63
|
+
command: "db init",
|
|
64
|
+
description: "Bootstrap a database to match the current contract",
|
|
65
|
+
url: "https://pris.ly/db-init",
|
|
66
|
+
details,
|
|
67
|
+
flags
|
|
68
|
+
});
|
|
69
|
+
console.log(header);
|
|
70
|
+
}
|
|
71
|
+
let contractJsonContent;
|
|
72
|
+
try {
|
|
73
|
+
contractJsonContent = await readFile(contractPathAbsolute, "utf-8");
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (error instanceof Error && error.code === "ENOENT") {
|
|
76
|
+
throw errorFileNotFound(contractPathAbsolute, {
|
|
77
|
+
why: `Contract file not found at ${contractPathAbsolute}`
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
throw errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
81
|
+
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
let contractJson;
|
|
85
|
+
try {
|
|
86
|
+
contractJson = JSON.parse(contractJsonContent);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
throw errorUnexpected(error instanceof Error ? error.message : String(error), {
|
|
89
|
+
why: `Failed to parse contract JSON at ${contractPathAbsolute}: ${error instanceof Error ? error.message : String(error)}`
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const dbUrl = options.db ?? config.db?.url;
|
|
93
|
+
if (!dbUrl) {
|
|
94
|
+
throw errorDatabaseUrlRequired({ why: "Database URL is required for db init" });
|
|
95
|
+
}
|
|
96
|
+
if (!config.driver) {
|
|
97
|
+
throw errorDriverRequired({ why: "Config.driver is required for db init" });
|
|
98
|
+
}
|
|
99
|
+
const driverDescriptor = config.driver;
|
|
100
|
+
if (!config.target.migrations) {
|
|
101
|
+
throw errorTargetMigrationNotSupported({
|
|
102
|
+
why: `Target "${config.target.id}" does not support migrations`
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
const migrations = config.target.migrations;
|
|
106
|
+
const driver = await withSpinner(() => driverDescriptor.create(dbUrl), {
|
|
107
|
+
message: "Connecting to database...",
|
|
108
|
+
flags
|
|
109
|
+
});
|
|
110
|
+
try {
|
|
111
|
+
const familyInstance = config.family.create({
|
|
112
|
+
target: config.target,
|
|
113
|
+
adapter: config.adapter,
|
|
114
|
+
driver: driverDescriptor,
|
|
115
|
+
extensions: config.extensions ?? []
|
|
116
|
+
});
|
|
117
|
+
const rawComponents = [config.target, config.adapter, ...config.extensions ?? []];
|
|
118
|
+
const frameworkComponents = assertFrameworkComponentsCompatible(
|
|
119
|
+
config.family.familyId,
|
|
120
|
+
config.target.targetId,
|
|
121
|
+
rawComponents
|
|
122
|
+
);
|
|
123
|
+
const contractIR = familyInstance.validateContractIR(contractJson);
|
|
124
|
+
const planner = migrations.createPlanner(familyInstance);
|
|
125
|
+
const runner = migrations.createRunner(familyInstance);
|
|
126
|
+
const schemaIR = await withSpinner(() => familyInstance.introspect({ driver }), {
|
|
127
|
+
message: "Introspecting database schema...",
|
|
128
|
+
flags
|
|
129
|
+
});
|
|
130
|
+
const policy = { allowedOperationClasses: ["additive"] };
|
|
131
|
+
const plannerResult = await withSpinner(
|
|
132
|
+
async () => planner.plan({
|
|
133
|
+
contract: contractIR,
|
|
134
|
+
schema: schemaIR,
|
|
135
|
+
policy,
|
|
136
|
+
frameworkComponents
|
|
137
|
+
}),
|
|
138
|
+
{
|
|
139
|
+
message: "Planning migration...",
|
|
140
|
+
flags
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
if (plannerResult.kind === "failure") {
|
|
144
|
+
throw errorMigrationPlanningFailed({ conflicts: plannerResult.conflicts });
|
|
145
|
+
}
|
|
146
|
+
const migrationPlan = plannerResult.plan;
|
|
147
|
+
const existingMarker = await familyInstance.readMarker({ driver });
|
|
148
|
+
if (existingMarker) {
|
|
149
|
+
const markerMatchesDestination = existingMarker.coreHash === migrationPlan.destination.coreHash && (!migrationPlan.destination.profileHash || existingMarker.profileHash === migrationPlan.destination.profileHash);
|
|
150
|
+
if (markerMatchesDestination) {
|
|
151
|
+
const dbInitResult2 = {
|
|
152
|
+
ok: true,
|
|
153
|
+
mode: options.plan ? "plan" : "apply",
|
|
154
|
+
plan: {
|
|
155
|
+
targetId: migrationPlan.targetId,
|
|
156
|
+
destination: migrationPlan.destination,
|
|
157
|
+
operations: []
|
|
158
|
+
},
|
|
159
|
+
...options.plan ? {} : {
|
|
160
|
+
execution: { operationsPlanned: 0, operationsExecuted: 0 },
|
|
161
|
+
marker: {
|
|
162
|
+
coreHash: existingMarker.coreHash,
|
|
163
|
+
profileHash: existingMarker.profileHash
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
summary: "Database already at target contract state",
|
|
167
|
+
timings: { total: Date.now() - startTime }
|
|
168
|
+
};
|
|
169
|
+
return dbInitResult2;
|
|
170
|
+
}
|
|
171
|
+
const coreHashMismatch = existingMarker.coreHash !== migrationPlan.destination.coreHash;
|
|
172
|
+
const profileHashMismatch = migrationPlan.destination.profileHash && existingMarker.profileHash !== migrationPlan.destination.profileHash;
|
|
173
|
+
const mismatchParts = [];
|
|
174
|
+
if (coreHashMismatch) {
|
|
175
|
+
mismatchParts.push(
|
|
176
|
+
`coreHash (marker: ${existingMarker.coreHash}, destination: ${migrationPlan.destination.coreHash})`
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
if (profileHashMismatch) {
|
|
180
|
+
mismatchParts.push(
|
|
181
|
+
`profileHash (marker: ${existingMarker.profileHash}, destination: ${migrationPlan.destination.profileHash})`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
throw errorRuntime(
|
|
185
|
+
`Existing contract marker does not match plan destination. Mismatch in ${mismatchParts.join(" and ")}.`,
|
|
186
|
+
{
|
|
187
|
+
why: "Database has an existing contract marker that does not match the target contract",
|
|
188
|
+
fix: "Use `prisma-next db migrate` to migrate from the existing contract, or drop the database and re-run `db init`",
|
|
189
|
+
meta: {
|
|
190
|
+
code: "MARKER_ORIGIN_MISMATCH",
|
|
191
|
+
markerCoreHash: existingMarker.coreHash,
|
|
192
|
+
destinationCoreHash: migrationPlan.destination.coreHash,
|
|
193
|
+
...existingMarker.profileHash ? { markerProfileHash: existingMarker.profileHash } : {},
|
|
194
|
+
...migrationPlan.destination.profileHash ? { destinationProfileHash: migrationPlan.destination.profileHash } : {}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (options.plan) {
|
|
200
|
+
const dbInitResult2 = {
|
|
201
|
+
ok: true,
|
|
202
|
+
mode: "plan",
|
|
203
|
+
plan: {
|
|
204
|
+
targetId: migrationPlan.targetId,
|
|
205
|
+
destination: migrationPlan.destination,
|
|
206
|
+
operations: migrationPlan.operations.map((op) => ({
|
|
207
|
+
id: op.id,
|
|
208
|
+
label: op.label,
|
|
209
|
+
operationClass: op.operationClass
|
|
210
|
+
}))
|
|
211
|
+
},
|
|
212
|
+
summary: `Planned ${migrationPlan.operations.length} operation(s)`,
|
|
213
|
+
timings: { total: Date.now() - startTime }
|
|
214
|
+
};
|
|
215
|
+
return dbInitResult2;
|
|
216
|
+
}
|
|
217
|
+
const callbacks = {
|
|
218
|
+
onOperationStart: (op) => {
|
|
219
|
+
if (!flags.quiet && flags.json !== "object") {
|
|
220
|
+
console.log(` \u2192 ${op.label}...`);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
onOperationComplete: (_op) => {
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
const runnerResult = await withSpinner(
|
|
227
|
+
() => runner.execute({
|
|
228
|
+
plan: migrationPlan,
|
|
229
|
+
driver,
|
|
230
|
+
destinationContract: contractIR,
|
|
231
|
+
policy,
|
|
232
|
+
callbacks,
|
|
233
|
+
frameworkComponents
|
|
234
|
+
}),
|
|
235
|
+
{
|
|
236
|
+
message: "Applying migration plan...",
|
|
237
|
+
flags
|
|
238
|
+
}
|
|
239
|
+
);
|
|
240
|
+
if (!runnerResult.ok) {
|
|
241
|
+
throw errorRuntime(runnerResult.failure.summary, {
|
|
242
|
+
why: runnerResult.failure.why ?? `Migration runner failed: ${runnerResult.failure.code}`,
|
|
243
|
+
meta: { code: runnerResult.failure.code }
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
const execution = runnerResult.value;
|
|
247
|
+
const dbInitResult = {
|
|
248
|
+
ok: true,
|
|
249
|
+
mode: "apply",
|
|
250
|
+
plan: {
|
|
251
|
+
targetId: migrationPlan.targetId,
|
|
252
|
+
destination: migrationPlan.destination,
|
|
253
|
+
operations: migrationPlan.operations.map((op) => ({
|
|
254
|
+
id: op.id,
|
|
255
|
+
label: op.label,
|
|
256
|
+
operationClass: op.operationClass
|
|
257
|
+
}))
|
|
258
|
+
},
|
|
259
|
+
execution: {
|
|
260
|
+
operationsPlanned: execution.operationsPlanned,
|
|
261
|
+
operationsExecuted: execution.operationsExecuted
|
|
262
|
+
},
|
|
263
|
+
marker: migrationPlan.destination.profileHash ? {
|
|
264
|
+
coreHash: migrationPlan.destination.coreHash,
|
|
265
|
+
profileHash: migrationPlan.destination.profileHash
|
|
266
|
+
} : { coreHash: migrationPlan.destination.coreHash },
|
|
267
|
+
summary: `Applied ${execution.operationsExecuted} operation(s), marker written`,
|
|
268
|
+
timings: { total: Date.now() - startTime }
|
|
269
|
+
};
|
|
270
|
+
return dbInitResult;
|
|
271
|
+
} finally {
|
|
272
|
+
await driver.close();
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
const exitCode = handleResult(result, flags, (dbInitResult) => {
|
|
276
|
+
if (flags.json === "object") {
|
|
277
|
+
console.log(formatDbInitJson(dbInitResult));
|
|
278
|
+
} else {
|
|
279
|
+
const output = dbInitResult.mode === "plan" ? formatDbInitPlanOutput(dbInitResult, flags) : formatDbInitApplyOutput(dbInitResult, flags);
|
|
280
|
+
if (output) {
|
|
281
|
+
console.log(output);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
process.exit(exitCode);
|
|
286
|
+
});
|
|
287
|
+
return command;
|
|
288
|
+
}
|
|
289
|
+
export {
|
|
290
|
+
createDbInitCommand
|
|
291
|
+
};
|
|
292
|
+
//# sourceMappingURL=db-init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/commands/db-init.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport type {\n MigrationPlan,\n MigrationPlannerResult,\n MigrationPlanOperation,\n MigrationRunnerResult,\n} from '@prisma-next/core-control-plane/types';\nimport { Command } from 'commander';\nimport { loadConfig } from '../config-loader';\nimport {\n errorDatabaseUrlRequired,\n errorDriverRequired,\n errorFileNotFound,\n errorMigrationPlanningFailed,\n errorRuntime,\n errorTargetMigrationNotSupported,\n errorUnexpected,\n} from '../utils/cli-errors';\nimport { setCommandDescriptions } from '../utils/command-helpers';\nimport { assertFrameworkComponentsCompatible } from '../utils/framework-components';\nimport { parseGlobalFlags } from '../utils/global-flags';\nimport {\n type DbInitResult,\n formatCommandHelp,\n formatDbInitApplyOutput,\n formatDbInitJson,\n formatDbInitPlanOutput,\n formatStyledHeader,\n} from '../utils/output';\nimport { performAction } from '../utils/result';\nimport { handleResult } from '../utils/result-handler';\nimport { withSpinner } from '../utils/spinner';\n\ninterface DbInitOptions {\n readonly db?: string;\n readonly config?: string;\n readonly plan?: boolean;\n readonly json?: string | boolean;\n readonly quiet?: boolean;\n readonly q?: boolean;\n readonly verbose?: boolean;\n readonly v?: boolean;\n readonly vv?: boolean;\n readonly trace?: boolean;\n readonly timestamps?: boolean;\n readonly color?: boolean;\n readonly 'no-color'?: boolean;\n}\n\nexport function createDbInitCommand(): Command {\n const command = new Command('init');\n setCommandDescriptions(\n command,\n 'Bootstrap a database to match the current contract and write the contract marker',\n 'Initializes a database to match your emitted contract using additive-only operations.\\n' +\n 'Creates any missing tables, columns, indexes, and constraints defined in your contract.\\n' +\n 'Leaves existing compatible structures in place, surfaces conflicts when destructive changes\\n' +\n 'would be required, and writes a contract marker to track the database state. Use --plan to\\n' +\n 'preview changes without applying.',\n );\n command\n .configureHelp({\n formatHelp: (cmd) => {\n const flags = parseGlobalFlags({});\n return formatCommandHelp({ command: cmd, flags });\n },\n })\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--plan', 'Preview planned operations without applying', false)\n .option('--json [format]', 'Output as JSON (object or ndjson)', false)\n .option('-q, --quiet', 'Quiet mode: errors only')\n .option('-v, --verbose', 'Verbose output: debug info, timings')\n .option('-vv, --trace', 'Trace output: deep internals, stack traces')\n .option('--timestamps', 'Add timestamps to output')\n .option('--color', 'Force color output')\n .option('--no-color', 'Disable color output')\n .action(async (options: DbInitOptions) => {\n const flags = parseGlobalFlags(options);\n const startTime = Date.now();\n\n const result = await performAction(async () => {\n // Load config\n const config = await loadConfig(options.config);\n const configPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n const contractPathAbsolute = config.contract?.output\n ? resolve(config.contract.output)\n : resolve('src/prisma/contract.json');\n const contractPath = relative(process.cwd(), contractPathAbsolute);\n\n // Output header\n if (flags.json !== 'object' && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'contract', value: contractPath },\n ];\n if (options.db) {\n details.push({ label: 'database', value: options.db });\n }\n if (options.plan) {\n details.push({ label: 'mode', value: 'plan (dry run)' });\n }\n const header = formatStyledHeader({\n command: 'db init',\n description: 'Bootstrap a database to match the current contract',\n url: 'https://pris.ly/db-init',\n details,\n flags,\n });\n console.log(header);\n }\n\n // Load contract file\n let contractJsonContent: string;\n try {\n contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n throw errorFileNotFound(contractPathAbsolute, {\n why: `Contract file not found at ${contractPathAbsolute}`,\n });\n }\n throw errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n\n let contractJson: Record<string, unknown>;\n try {\n contractJson = JSON.parse(contractJsonContent) as Record<string, unknown>;\n } catch (error) {\n throw errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to parse contract JSON at ${contractPathAbsolute}: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n\n // Resolve database URL\n const dbUrl = options.db ?? config.db?.url;\n if (!dbUrl) {\n throw errorDatabaseUrlRequired({ why: 'Database URL is required for db init' });\n }\n\n // Check for driver\n if (!config.driver) {\n throw errorDriverRequired({ why: 'Config.driver is required for db init' });\n }\n const driverDescriptor = config.driver;\n\n // Check target supports migrations via the migrations capability\n if (!config.target.migrations) {\n throw errorTargetMigrationNotSupported({\n why: `Target \"${config.target.id}\" does not support migrations`,\n });\n }\n const migrations = config.target.migrations;\n\n // Create driver\n const driver = await withSpinner(() => driverDescriptor.create(dbUrl), {\n message: 'Connecting to database...',\n flags,\n });\n\n try {\n // Create family instance\n const familyInstance = config.family.create({\n target: config.target,\n adapter: config.adapter,\n driver: driverDescriptor,\n extensions: config.extensions ?? [],\n });\n const rawComponents = [config.target, config.adapter, ...(config.extensions ?? [])];\n const frameworkComponents = assertFrameworkComponentsCompatible(\n config.family.familyId,\n config.target.targetId,\n rawComponents,\n );\n\n // Validate contract\n const contractIR = familyInstance.validateContractIR(contractJson);\n\n // Create planner and runner from target migrations capability\n const planner = migrations.createPlanner(familyInstance);\n const runner = migrations.createRunner(familyInstance);\n\n // Introspect live schema\n const schemaIR = await withSpinner(() => familyInstance.introspect({ driver }), {\n message: 'Introspecting database schema...',\n flags,\n });\n\n // Policy for init mode (additive only)\n const policy = { allowedOperationClasses: ['additive'] as const };\n\n // Plan migration\n const plannerResult: MigrationPlannerResult = await withSpinner(\n async () =>\n planner.plan({\n contract: contractIR,\n schema: schemaIR,\n policy,\n frameworkComponents,\n }),\n {\n message: 'Planning migration...',\n flags,\n },\n );\n\n if (plannerResult.kind === 'failure') {\n throw errorMigrationPlanningFailed({ conflicts: plannerResult.conflicts });\n }\n\n const migrationPlan: MigrationPlan = plannerResult.plan;\n\n // Check for existing marker - handle idempotency and mismatch errors\n const existingMarker = await familyInstance.readMarker({ driver });\n if (existingMarker) {\n const markerMatchesDestination =\n existingMarker.coreHash === migrationPlan.destination.coreHash &&\n (!migrationPlan.destination.profileHash ||\n existingMarker.profileHash === migrationPlan.destination.profileHash);\n\n if (markerMatchesDestination) {\n // Already at destination - return success with no operations\n const dbInitResult: DbInitResult = {\n ok: true,\n mode: options.plan ? 'plan' : 'apply',\n plan: {\n targetId: migrationPlan.targetId,\n destination: migrationPlan.destination,\n operations: [],\n },\n ...(options.plan\n ? {}\n : {\n execution: { operationsPlanned: 0, operationsExecuted: 0 },\n marker: {\n coreHash: existingMarker.coreHash,\n profileHash: existingMarker.profileHash,\n },\n }),\n summary: 'Database already at target contract state',\n timings: { total: Date.now() - startTime },\n };\n return dbInitResult;\n }\n\n // Marker exists but doesn't match destination - fail\n const coreHashMismatch = existingMarker.coreHash !== migrationPlan.destination.coreHash;\n const profileHashMismatch =\n migrationPlan.destination.profileHash &&\n existingMarker.profileHash !== migrationPlan.destination.profileHash;\n\n const mismatchParts: string[] = [];\n if (coreHashMismatch) {\n mismatchParts.push(\n `coreHash (marker: ${existingMarker.coreHash}, destination: ${migrationPlan.destination.coreHash})`,\n );\n }\n if (profileHashMismatch) {\n mismatchParts.push(\n `profileHash (marker: ${existingMarker.profileHash}, destination: ${migrationPlan.destination.profileHash})`,\n );\n }\n\n throw errorRuntime(\n `Existing contract marker does not match plan destination. Mismatch in ${mismatchParts.join(' and ')}.`,\n {\n why: 'Database has an existing contract marker that does not match the target contract',\n fix: 'Use `prisma-next db migrate` to migrate from the existing contract, or drop the database and re-run `db init`',\n meta: {\n code: 'MARKER_ORIGIN_MISMATCH',\n markerCoreHash: existingMarker.coreHash,\n destinationCoreHash: migrationPlan.destination.coreHash,\n ...(existingMarker.profileHash\n ? { markerProfileHash: existingMarker.profileHash }\n : {}),\n ...(migrationPlan.destination.profileHash\n ? { destinationProfileHash: migrationPlan.destination.profileHash }\n : {}),\n },\n },\n );\n }\n\n // Plan mode - don't execute\n if (options.plan) {\n const dbInitResult: DbInitResult = {\n ok: true,\n mode: 'plan',\n plan: {\n targetId: migrationPlan.targetId,\n destination: migrationPlan.destination,\n operations: migrationPlan.operations.map((op) => ({\n id: op.id,\n label: op.label,\n operationClass: op.operationClass,\n })),\n },\n summary: `Planned ${migrationPlan.operations.length} operation(s)`,\n timings: { total: Date.now() - startTime },\n };\n return dbInitResult;\n }\n\n // Apply mode - execute runner\n const callbacks = {\n onOperationStart: (op: MigrationPlanOperation) => {\n if (!flags.quiet && flags.json !== 'object') {\n console.log(` → ${op.label}...`);\n }\n },\n onOperationComplete: (_op: MigrationPlanOperation) => {\n // Could log completion if needed\n },\n };\n\n const runnerResult: MigrationRunnerResult = await withSpinner(\n () =>\n runner.execute({\n plan: migrationPlan,\n driver,\n destinationContract: contractIR,\n policy,\n callbacks,\n frameworkComponents,\n }),\n {\n message: 'Applying migration plan...',\n flags,\n },\n );\n\n if (!runnerResult.ok) {\n throw errorRuntime(runnerResult.failure.summary, {\n why:\n runnerResult.failure.why ?? `Migration runner failed: ${runnerResult.failure.code}`,\n meta: { code: runnerResult.failure.code },\n });\n }\n\n const execution = runnerResult.value;\n\n const dbInitResult: DbInitResult = {\n ok: true,\n mode: 'apply',\n plan: {\n targetId: migrationPlan.targetId,\n destination: migrationPlan.destination,\n operations: migrationPlan.operations.map((op) => ({\n id: op.id,\n label: op.label,\n operationClass: op.operationClass,\n })),\n },\n execution: {\n operationsPlanned: execution.operationsPlanned,\n operationsExecuted: execution.operationsExecuted,\n },\n marker: migrationPlan.destination.profileHash\n ? {\n coreHash: migrationPlan.destination.coreHash,\n profileHash: migrationPlan.destination.profileHash,\n }\n : { coreHash: migrationPlan.destination.coreHash },\n summary: `Applied ${execution.operationsExecuted} operation(s), marker written`,\n timings: { total: Date.now() - startTime },\n };\n return dbInitResult;\n } finally {\n await driver.close();\n }\n });\n\n // Handle result\n const exitCode = handleResult(result, flags, (dbInitResult) => {\n if (flags.json === 'object') {\n console.log(formatDbInitJson(dbInitResult));\n } else {\n const output =\n dbInitResult.mode === 'plan'\n ? formatDbInitPlanOutput(dbInitResult, flags)\n : formatDbInitApplyOutput(dbInitResult, flags);\n if (output) {\n console.log(output);\n }\n }\n });\n\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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","names":["dbInitResult"]}
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
performAction,
|
|
9
9
|
setCommandDescriptions,
|
|
10
10
|
withSpinner
|
|
11
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-ZKEJZ3NU.js";
|
|
12
12
|
import {
|
|
13
13
|
loadConfig
|
|
14
14
|
} from "../chunk-HWYQOCAJ.js";
|
|
@@ -46,8 +46,7 @@ function createDbIntrospectCommand() {
|
|
|
46
46
|
const contractPath = resolve(config.contract.output);
|
|
47
47
|
try {
|
|
48
48
|
const contractJsonContent = await readFile(contractPath, "utf-8");
|
|
49
|
-
|
|
50
|
-
contractIR = contractJson;
|
|
49
|
+
contractIR = JSON.parse(contractJsonContent);
|
|
51
50
|
} catch (error) {
|
|
52
51
|
if (error instanceof Error && error.code !== "ENOENT") {
|
|
53
52
|
throw errorUnexpected(error.message, {
|
|
@@ -95,14 +94,13 @@ function createDbIntrospectCommand() {
|
|
|
95
94
|
driver: driverDescriptor,
|
|
96
95
|
extensions: config.extensions ?? []
|
|
97
96
|
});
|
|
98
|
-
const typedFamilyInstance = familyInstance;
|
|
99
97
|
if (contractIR) {
|
|
100
|
-
contractIR =
|
|
98
|
+
contractIR = familyInstance.validateContractIR(contractIR);
|
|
101
99
|
}
|
|
102
100
|
let schemaIR;
|
|
103
101
|
try {
|
|
104
102
|
schemaIR = await withSpinner(
|
|
105
|
-
() =>
|
|
103
|
+
() => familyInstance.introspect({
|
|
106
104
|
driver,
|
|
107
105
|
contractIR
|
|
108
106
|
}),
|
|
@@ -117,9 +115,9 @@ function createDbIntrospectCommand() {
|
|
|
117
115
|
});
|
|
118
116
|
}
|
|
119
117
|
let schemaView;
|
|
120
|
-
if (
|
|
118
|
+
if (familyInstance.toSchemaView) {
|
|
121
119
|
try {
|
|
122
|
-
schemaView =
|
|
120
|
+
schemaView = familyInstance.toSchemaView(schemaIR);
|
|
123
121
|
} catch (error) {
|
|
124
122
|
if (flags.verbose) {
|
|
125
123
|
console.error(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/db-introspect.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport {\n errorDatabaseUrlRequired,\n errorDriverRequired,\n errorRuntime,\n errorUnexpected,\n} from '@prisma-next/core-control-plane/errors';\nimport type { CoreSchemaView } from '@prisma-next/core-control-plane/schema-view';\nimport type { FamilyInstance, IntrospectSchemaResult } from '@prisma-next/core-control-plane/types';\nimport { Command } from 'commander';\nimport { loadConfig } from '../config-loader';\nimport { setCommandDescriptions } from '../utils/command-helpers';\nimport { parseGlobalFlags } from '../utils/global-flags';\nimport {\n formatCommandHelp,\n formatIntrospectJson,\n formatIntrospectOutput,\n formatStyledHeader,\n} from '../utils/output';\nimport { performAction } from '../utils/result';\nimport { handleResult } from '../utils/result-handler';\nimport { withSpinner } from '../utils/spinner';\n\ninterface DbIntrospectOptions {\n readonly db?: string;\n readonly config?: string;\n readonly json?: string | boolean;\n readonly quiet?: boolean;\n readonly q?: boolean;\n readonly verbose?: boolean;\n readonly v?: boolean;\n readonly vv?: boolean;\n readonly trace?: boolean;\n readonly timestamps?: boolean;\n readonly color?: boolean;\n readonly 'no-color'?: boolean;\n}\n\nexport function createDbIntrospectCommand(): Command {\n const command = new Command('introspect');\n setCommandDescriptions(\n command,\n 'Inspect the database schema',\n 'Reads the live database schema and displays it as a tree structure. This command\\n' +\n 'does not check the schema against your contract - it only shows what exists in\\n' +\n 'the database. Use `db verify` or `db schema-verify` to compare against your contract.',\n );\n command\n .configureHelp({\n formatHelp: (cmd) => {\n const flags = parseGlobalFlags({});\n return formatCommandHelp({ command: cmd, flags });\n },\n })\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--json [format]', 'Output as JSON (object or ndjson)', false)\n .option('-q, --quiet', 'Quiet mode: errors only')\n .option('-v, --verbose', 'Verbose output: debug info, timings')\n .option('-vv, --trace', 'Trace output: deep internals, stack traces')\n .option('--timestamps', 'Add timestamps to output')\n .option('--color', 'Force color output')\n .option('--no-color', 'Disable color output')\n .action(async (options: DbIntrospectOptions) => {\n const flags = parseGlobalFlags(options);\n\n const result = await performAction(async () => {\n const startTime = Date.now();\n\n // Load config (file I/O)\n const config = await loadConfig(options.config);\n // Normalize config path for display (match contract path format - no ./ prefix)\n const configPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n\n // Optionally load contract if contract config exists\n let contractIR: unknown | undefined;\n if (config.contract?.output) {\n const contractPath = resolve(config.contract.output);\n try {\n const contractJsonContent = await readFile(contractPath, 'utf-8');\n const contractJson = JSON.parse(contractJsonContent) as Record<string, unknown>;\n // Validate contract using family instance (will be created later)\n // For now, we'll pass the raw JSON and let the family instance validate it\n contractIR = contractJson;\n } catch (error) {\n // Contract file is optional for introspection - don't fail if it doesn't exist\n if (error instanceof Error && (error as { code?: string }).code !== 'ENOENT') {\n throw errorUnexpected(error.message, {\n why: `Failed to read contract file: ${error.message}`,\n });\n }\n }\n }\n\n // Output header (only for human-readable output)\n if (flags.json !== 'object' && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n ];\n if (options.db) {\n // Mask password in URL for security\n const maskedUrl = options.db.replace(/:([^:@]+)@/, ':****@');\n details.push({ label: 'database', value: maskedUrl });\n } else if (config.db?.url) {\n // Mask password in URL for security\n const maskedUrl = config.db.url.replace(/:([^:@]+)@/, ':****@');\n details.push({ label: 'database', value: maskedUrl });\n }\n const header = formatStyledHeader({\n command: 'db introspect',\n description: 'Inspect the database schema',\n url: 'https://pris.ly/db-introspect',\n details,\n flags,\n });\n console.log(header);\n }\n\n // Resolve database URL\n const dbUrl = options.db ?? config.db?.url;\n if (!dbUrl) {\n throw errorDatabaseUrlRequired();\n }\n\n // Check for driver\n if (!config.driver) {\n throw errorDriverRequired();\n }\n\n // Store driver descriptor after null check\n const driverDescriptor = config.driver;\n\n // Create driver\n const driver = await withSpinner(() => driverDescriptor.create(dbUrl), {\n message: 'Connecting to database...',\n flags,\n });\n\n try {\n // Create family instance\n const familyInstance = config.family.create({\n target: config.target,\n adapter: config.adapter,\n driver: driverDescriptor,\n extensions: config.extensions ?? [],\n });\n const typedFamilyInstance = familyInstance as FamilyInstance<string>;\n\n // Validate contract IR if we loaded it\n if (contractIR) {\n contractIR = typedFamilyInstance.validateContractIR(contractIR);\n }\n\n // Call family instance introspect method\n let schemaIR: unknown;\n try {\n schemaIR = await withSpinner(\n () =>\n typedFamilyInstance.introspect({\n driver,\n contractIR,\n }),\n {\n message: 'Introspecting database schema...',\n flags,\n },\n );\n } catch (error) {\n // Wrap errors from introspect() in structured error\n throw errorRuntime(error instanceof Error ? error.message : String(error), {\n why: `Failed to introspect database: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n\n // Optionally call toSchemaView if available\n let schemaView: CoreSchemaView | undefined;\n if (typedFamilyInstance.toSchemaView) {\n try {\n schemaView = typedFamilyInstance.toSchemaView(schemaIR);\n } catch (error) {\n // Schema view projection is optional - log but don't fail\n if (flags.verbose) {\n console.error(\n `Warning: Failed to project schema to view: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n }\n\n const totalTime = Date.now() - startTime;\n\n // Add blank line after all async operations if spinners were shown\n if (!flags.quiet && flags.json !== 'object' && process.stdout.isTTY) {\n console.log('');\n }\n\n // Build result envelope\n const introspectResult: IntrospectSchemaResult<unknown> = {\n ok: true,\n summary: 'Schema introspected successfully',\n target: {\n familyId: config.family.familyId,\n id: config.target.targetId,\n },\n schema: schemaIR,\n ...(configPath || options.db || config.db?.url\n ? {\n meta: {\n ...(configPath ? { configPath } : {}),\n ...(options.db || config.db?.url\n ? {\n dbUrl: (options.db ?? config.db?.url ?? '').replace(\n /:([^:@]+)@/,\n ':****@',\n ),\n }\n : {}),\n },\n }\n : {}),\n timings: {\n total: totalTime,\n },\n };\n\n return { introspectResult, schemaView };\n } finally {\n // Ensure driver connection is closed\n await driver.close();\n }\n });\n\n // Handle result - formats output and returns exit code\n const exitCode = handleResult(result, flags, (value) => {\n const { introspectResult, schemaView } = value;\n // Output based on flags\n if (flags.json === 'object') {\n // JSON output to stdout\n console.log(formatIntrospectJson(introspectResult));\n } else {\n // Human-readable output to stdout\n const output = formatIntrospectOutput(introspectResult, schemaView, flags);\n if (output) {\n console.log(output);\n }\n }\n });\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/commands/db-introspect.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport {\n errorDatabaseUrlRequired,\n errorDriverRequired,\n errorRuntime,\n errorUnexpected,\n} from '@prisma-next/core-control-plane/errors';\nimport type { CoreSchemaView } from '@prisma-next/core-control-plane/schema-view';\nimport type { IntrospectSchemaResult } from '@prisma-next/core-control-plane/types';\nimport { Command } from 'commander';\nimport { loadConfig } from '../config-loader';\nimport { setCommandDescriptions } from '../utils/command-helpers';\nimport { parseGlobalFlags } from '../utils/global-flags';\nimport {\n formatCommandHelp,\n formatIntrospectJson,\n formatIntrospectOutput,\n formatStyledHeader,\n} from '../utils/output';\nimport { performAction } from '../utils/result';\nimport { handleResult } from '../utils/result-handler';\nimport { withSpinner } from '../utils/spinner';\n\ninterface DbIntrospectOptions {\n readonly db?: string;\n readonly config?: string;\n readonly json?: string | boolean;\n readonly quiet?: boolean;\n readonly q?: boolean;\n readonly verbose?: boolean;\n readonly v?: boolean;\n readonly vv?: boolean;\n readonly trace?: boolean;\n readonly timestamps?: boolean;\n readonly color?: boolean;\n readonly 'no-color'?: boolean;\n}\n\nexport function createDbIntrospectCommand(): Command {\n const command = new Command('introspect');\n setCommandDescriptions(\n command,\n 'Inspect the database schema',\n 'Reads the live database schema and displays it as a tree structure. This command\\n' +\n 'does not check the schema against your contract - it only shows what exists in\\n' +\n 'the database. Use `db verify` or `db schema-verify` to compare against your contract.',\n );\n command\n .configureHelp({\n formatHelp: (cmd) => {\n const flags = parseGlobalFlags({});\n return formatCommandHelp({ command: cmd, flags });\n },\n })\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--json [format]', 'Output as JSON (object or ndjson)', false)\n .option('-q, --quiet', 'Quiet mode: errors only')\n .option('-v, --verbose', 'Verbose output: debug info, timings')\n .option('-vv, --trace', 'Trace output: deep internals, stack traces')\n .option('--timestamps', 'Add timestamps to output')\n .option('--color', 'Force color output')\n .option('--no-color', 'Disable color output')\n .action(async (options: DbIntrospectOptions) => {\n const flags = parseGlobalFlags(options);\n\n const result = await performAction(async () => {\n const startTime = Date.now();\n\n // Load config (file I/O)\n const config = await loadConfig(options.config);\n // Normalize config path for display (match contract path format - no ./ prefix)\n const configPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n\n // Optionally load contract if contract config exists\n let contractIR: unknown | undefined;\n if (config.contract?.output) {\n const contractPath = resolve(config.contract.output);\n try {\n const contractJsonContent = await readFile(contractPath, 'utf-8');\n contractIR = JSON.parse(contractJsonContent);\n } catch (error) {\n // Contract file is optional for introspection - don't fail if it doesn't exist\n if (error instanceof Error && (error as { code?: string }).code !== 'ENOENT') {\n throw errorUnexpected(error.message, {\n why: `Failed to read contract file: ${error.message}`,\n });\n }\n }\n }\n\n // Output header (only for human-readable output)\n if (flags.json !== 'object' && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n ];\n if (options.db) {\n // Mask password in URL for security\n const maskedUrl = options.db.replace(/:([^:@]+)@/, ':****@');\n details.push({ label: 'database', value: maskedUrl });\n } else if (config.db?.url) {\n // Mask password in URL for security\n const maskedUrl = config.db.url.replace(/:([^:@]+)@/, ':****@');\n details.push({ label: 'database', value: maskedUrl });\n }\n const header = formatStyledHeader({\n command: 'db introspect',\n description: 'Inspect the database schema',\n url: 'https://pris.ly/db-introspect',\n details,\n flags,\n });\n console.log(header);\n }\n\n // Resolve database URL\n const dbUrl = options.db ?? config.db?.url;\n if (!dbUrl) {\n throw errorDatabaseUrlRequired();\n }\n\n // Check for driver\n if (!config.driver) {\n throw errorDriverRequired();\n }\n\n // Store driver descriptor after null check\n const driverDescriptor = config.driver;\n\n // Create driver\n const driver = await withSpinner(() => driverDescriptor.create(dbUrl), {\n message: 'Connecting to database...',\n flags,\n });\n\n try {\n // Create family instance\n const familyInstance = config.family.create({\n target: config.target,\n adapter: config.adapter,\n driver: driverDescriptor,\n extensions: config.extensions ?? [],\n });\n\n // Validate contract IR if we loaded it\n if (contractIR) {\n contractIR = familyInstance.validateContractIR(contractIR);\n }\n\n // Call family instance introspect method\n let schemaIR: unknown;\n try {\n schemaIR = await withSpinner(\n () =>\n familyInstance.introspect({\n driver,\n contractIR,\n }),\n {\n message: 'Introspecting database schema...',\n flags,\n },\n );\n } catch (error) {\n // Wrap errors from introspect() in structured error\n throw errorRuntime(error instanceof Error ? error.message : String(error), {\n why: `Failed to introspect database: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n\n // Optionally call toSchemaView if available\n let schemaView: CoreSchemaView | undefined;\n if (familyInstance.toSchemaView) {\n try {\n schemaView = familyInstance.toSchemaView(schemaIR);\n } catch (error) {\n // Schema view projection is optional - log but don't fail\n if (flags.verbose) {\n console.error(\n `Warning: Failed to project schema to view: ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n }\n\n const totalTime = Date.now() - startTime;\n\n // Add blank line after all async operations if spinners were shown\n if (!flags.quiet && flags.json !== 'object' && process.stdout.isTTY) {\n console.log('');\n }\n\n // Build result envelope\n const introspectResult: IntrospectSchemaResult<unknown> = {\n ok: true,\n summary: 'Schema introspected successfully',\n target: {\n familyId: config.family.familyId,\n id: config.target.targetId,\n },\n schema: schemaIR,\n ...(configPath || options.db || config.db?.url\n ? {\n meta: {\n ...(configPath ? { configPath } : {}),\n ...(options.db || config.db?.url\n ? {\n dbUrl: (options.db ?? config.db?.url ?? '').replace(\n /:([^:@]+)@/,\n ':****@',\n ),\n }\n : {}),\n },\n }\n : {}),\n timings: {\n total: totalTime,\n },\n };\n\n return { introspectResult, schemaView };\n } finally {\n // Ensure driver connection is closed\n await driver.close();\n }\n });\n\n // Handle result - formats output and returns exit code\n const exitCode = handleResult(result, flags, (value) => {\n const { introspectResult, schemaView } = value;\n // Output based on flags\n if (flags.json === 'object') {\n // JSON output to stdout\n console.log(formatIntrospectJson(introspectResult));\n } else {\n // Human-readable output to stdout\n const output = formatIntrospectOutput(introspectResult, schemaView, flags);\n if (output) {\n console.log(output);\n }\n }\n });\n process.exit(exitCode);\n });\n\n return command;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
assertFrameworkComponentsCompatible
|
|
3
|
+
} from "../chunk-YDE4ILKH.js";
|
|
1
4
|
import {
|
|
2
5
|
formatCommandHelp,
|
|
3
6
|
formatSchemaVerifyJson,
|
|
@@ -8,7 +11,7 @@ import {
|
|
|
8
11
|
performAction,
|
|
9
12
|
setCommandDescriptions,
|
|
10
13
|
withSpinner
|
|
11
|
-
} from "../chunk-
|
|
14
|
+
} from "../chunk-ZKEJZ3NU.js";
|
|
12
15
|
import {
|
|
13
16
|
loadConfig
|
|
14
17
|
} from "../chunk-HWYQOCAJ.js";
|
|
@@ -93,17 +96,23 @@ function createDbSchemaVerifyCommand() {
|
|
|
93
96
|
driver: driverDescriptor,
|
|
94
97
|
extensions: config.extensions ?? []
|
|
95
98
|
});
|
|
96
|
-
const
|
|
97
|
-
const
|
|
99
|
+
const rawComponents = [config.target, config.adapter, ...config.extensions ?? []];
|
|
100
|
+
const frameworkComponents = assertFrameworkComponentsCompatible(
|
|
101
|
+
config.family.familyId,
|
|
102
|
+
config.target.targetId,
|
|
103
|
+
rawComponents
|
|
104
|
+
);
|
|
105
|
+
const contractIR = familyInstance.validateContractIR(contractJson);
|
|
98
106
|
let schemaVerifyResult;
|
|
99
107
|
try {
|
|
100
108
|
schemaVerifyResult = await withSpinner(
|
|
101
|
-
() =>
|
|
109
|
+
() => familyInstance.schemaVerify({
|
|
102
110
|
driver,
|
|
103
111
|
contractIR,
|
|
104
112
|
strict: options.strict ?? false,
|
|
105
113
|
contractPath: contractPathAbsolute,
|
|
106
|
-
configPath
|
|
114
|
+
configPath,
|
|
115
|
+
frameworkComponents
|
|
107
116
|
}),
|
|
108
117
|
{
|
|
109
118
|
message: "Verifying database schema...",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/commands/db-schema-verify.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport type { ContractIR } from '@prisma-next/contract/ir';\nimport {\n errorDatabaseUrlRequired,\n errorDriverRequired,\n errorFileNotFound,\n errorRuntime,\n errorUnexpected,\n} from '@prisma-next/core-control-plane/errors';\nimport type {\n FamilyInstance,\n VerifyDatabaseSchemaResult,\n} from '@prisma-next/core-control-plane/types';\nimport { Command } from 'commander';\nimport { loadConfig } from '../config-loader';\nimport { setCommandDescriptions } from '../utils/command-helpers';\nimport { parseGlobalFlags } from '../utils/global-flags';\nimport {\n formatCommandHelp,\n formatSchemaVerifyJson,\n formatSchemaVerifyOutput,\n formatStyledHeader,\n} from '../utils/output';\nimport { performAction } from '../utils/result';\nimport { handleResult } from '../utils/result-handler';\nimport { withSpinner } from '../utils/spinner';\n\ninterface DbSchemaVerifyOptions {\n readonly db?: string;\n readonly config?: string;\n readonly json?: string | boolean;\n readonly strict?: boolean;\n readonly quiet?: boolean;\n readonly q?: boolean;\n readonly verbose?: boolean;\n readonly v?: boolean;\n readonly vv?: boolean;\n readonly trace?: boolean;\n readonly timestamps?: boolean;\n readonly color?: boolean;\n readonly 'no-color'?: boolean;\n}\n\nexport function createDbSchemaVerifyCommand(): Command {\n const command = new Command('schema-verify');\n setCommandDescriptions(\n command,\n 'Check whether the database schema satisfies your contract',\n 'Verifies that your database schema satisfies the emitted contract. Compares table structures,\\n' +\n 'column types, constraints, and extensions. Reports any mismatches via a contract-shaped\\n' +\n 'verification tree. This is a read-only operation that does not modify the database.',\n );\n command\n .configureHelp({\n formatHelp: (cmd) => {\n const flags = parseGlobalFlags({});\n return formatCommandHelp({ command: cmd, flags });\n },\n })\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--json [format]', 'Output as JSON (object or ndjson)', false)\n .option('--strict', 'Strict mode: extra schema elements cause failures', false)\n .option('-q, --quiet', 'Quiet mode: errors only')\n .option('-v, --verbose', 'Verbose output: debug info, timings')\n .option('-vv, --trace', 'Trace output: deep internals, stack traces')\n .option('--timestamps', 'Add timestamps to output')\n .option('--color', 'Force color output')\n .option('--no-color', 'Disable color output')\n .action(async (options: DbSchemaVerifyOptions) => {\n const flags = parseGlobalFlags(options);\n\n const result = await performAction(async () => {\n // Load config (file I/O)\n const config = await loadConfig(options.config);\n // Normalize config path for display (match contract path format - no ./ prefix)\n const configPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n const contractPathAbsolute = config.contract?.output\n ? resolve(config.contract.output)\n : resolve('src/prisma/contract.json');\n // Convert to relative path for display\n const contractPath = relative(process.cwd(), contractPathAbsolute);\n\n // Output header (only for human-readable output)\n if (flags.json !== 'object' && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'contract', value: contractPath },\n ];\n if (options.db) {\n details.push({ label: 'database', value: options.db });\n }\n const header = formatStyledHeader({\n command: 'db schema-verify',\n description: 'Check whether the database schema satisfies your contract',\n url: 'https://pris.ly/db-schema-verify',\n details,\n flags,\n });\n console.log(header);\n }\n\n // Load contract file (file I/O)\n let contractJsonContent: string;\n try {\n contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n throw errorFileNotFound(contractPathAbsolute, {\n why: `Contract file not found at ${contractPathAbsolute}`,\n });\n }\n throw errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n const contractJson = JSON.parse(contractJsonContent) as Record<string, unknown>;\n\n // Resolve database URL\n const dbUrl = options.db ?? config.db?.url;\n if (!dbUrl) {\n throw errorDatabaseUrlRequired();\n }\n\n // Check for driver\n if (!config.driver) {\n throw errorDriverRequired();\n }\n\n // Store driver descriptor after null check\n const driverDescriptor = config.driver;\n\n // Create driver\n const driver = await withSpinner(() => driverDescriptor.create(dbUrl), {\n message: 'Connecting to database...',\n flags,\n });\n\n try {\n // Create family instance\n const familyInstance = config.family.create({\n target: config.target,\n adapter: config.adapter,\n driver: driverDescriptor,\n extensions: config.extensions ?? [],\n });\n const typedFamilyInstance = familyInstance as FamilyInstance<string>;\n\n // Validate contract using instance validator\n const contractIR = typedFamilyInstance.validateContractIR(contractJson) as ContractIR;\n\n // Call family instance schemaVerify method\n let schemaVerifyResult: VerifyDatabaseSchemaResult;\n try {\n schemaVerifyResult = (await withSpinner(\n () =>\n typedFamilyInstance.schemaVerify({\n driver,\n contractIR,\n strict: options.strict ?? false,\n contractPath: contractPathAbsolute,\n configPath,\n }),\n {\n message: 'Verifying database schema...',\n flags,\n },\n )) as VerifyDatabaseSchemaResult;\n } catch (error) {\n // Wrap errors from schemaVerify() in structured error\n throw errorRuntime(error instanceof Error ? error.message : String(error), {\n why: `Failed to verify database schema: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n\n // Add blank line after all async operations if spinners were shown\n if (!flags.quiet && flags.json !== 'object' && process.stdout.isTTY) {\n console.log('');\n }\n\n // Return result (don't throw for logical mismatches - handle exit code separately)\n return schemaVerifyResult;\n } finally {\n // Ensure driver connection is closed\n await driver.close();\n }\n });\n\n // Handle result - formats output and returns exit code\n const exitCode = handleResult(result, flags, (schemaVerifyResult) => {\n // Output based on flags\n if (flags.json === 'object') {\n // JSON output to stdout\n console.log(formatSchemaVerifyJson(schemaVerifyResult));\n } else {\n // Human-readable output to stdout\n const output = formatSchemaVerifyOutput(schemaVerifyResult, flags);\n if (output) {\n console.log(output);\n }\n }\n });\n\n // For logical schema mismatches, check if verification passed\n // Infra errors already handled by handleResult (returns non-zero exit code)\n if (result.ok && !result.value.ok) {\n // Schema verification failed - exit with code 1\n process.exit(1);\n } else {\n // Success or infra error - use exit code from handleResult\n process.exit(exitCode);\n }\n });\n\n return command;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/commands/db-schema-verify.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport type { ContractIR } from '@prisma-next/contract/ir';\nimport {\n errorDatabaseUrlRequired,\n errorDriverRequired,\n errorFileNotFound,\n errorRuntime,\n errorUnexpected,\n} from '@prisma-next/core-control-plane/errors';\nimport type { VerifyDatabaseSchemaResult } from '@prisma-next/core-control-plane/types';\nimport { Command } from 'commander';\nimport { loadConfig } from '../config-loader';\nimport { setCommandDescriptions } from '../utils/command-helpers';\nimport { assertFrameworkComponentsCompatible } from '../utils/framework-components';\nimport { parseGlobalFlags } from '../utils/global-flags';\nimport {\n formatCommandHelp,\n formatSchemaVerifyJson,\n formatSchemaVerifyOutput,\n formatStyledHeader,\n} from '../utils/output';\nimport { performAction } from '../utils/result';\nimport { handleResult } from '../utils/result-handler';\nimport { withSpinner } from '../utils/spinner';\n\ninterface DbSchemaVerifyOptions {\n readonly db?: string;\n readonly config?: string;\n readonly json?: string | boolean;\n readonly strict?: boolean;\n readonly quiet?: boolean;\n readonly q?: boolean;\n readonly verbose?: boolean;\n readonly v?: boolean;\n readonly vv?: boolean;\n readonly trace?: boolean;\n readonly timestamps?: boolean;\n readonly color?: boolean;\n readonly 'no-color'?: boolean;\n}\n\nexport function createDbSchemaVerifyCommand(): Command {\n const command = new Command('schema-verify');\n setCommandDescriptions(\n command,\n 'Check whether the database schema satisfies your contract',\n 'Verifies that your database schema satisfies the emitted contract. Compares table structures,\\n' +\n 'column types, constraints, and extensions. Reports any mismatches via a contract-shaped\\n' +\n 'verification tree. This is a read-only operation that does not modify the database.',\n );\n command\n .configureHelp({\n formatHelp: (cmd) => {\n const flags = parseGlobalFlags({});\n return formatCommandHelp({ command: cmd, flags });\n },\n })\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .option('--json [format]', 'Output as JSON (object or ndjson)', false)\n .option('--strict', 'Strict mode: extra schema elements cause failures', false)\n .option('-q, --quiet', 'Quiet mode: errors only')\n .option('-v, --verbose', 'Verbose output: debug info, timings')\n .option('-vv, --trace', 'Trace output: deep internals, stack traces')\n .option('--timestamps', 'Add timestamps to output')\n .option('--color', 'Force color output')\n .option('--no-color', 'Disable color output')\n .action(async (options: DbSchemaVerifyOptions) => {\n const flags = parseGlobalFlags(options);\n\n const result = await performAction(async () => {\n // Load config (file I/O)\n const config = await loadConfig(options.config);\n // Normalize config path for display (match contract path format - no ./ prefix)\n const configPath = options.config\n ? relative(process.cwd(), resolve(options.config))\n : 'prisma-next.config.ts';\n const contractPathAbsolute = config.contract?.output\n ? resolve(config.contract.output)\n : resolve('src/prisma/contract.json');\n // Convert to relative path for display\n const contractPath = relative(process.cwd(), contractPathAbsolute);\n\n // Output header (only for human-readable output)\n if (flags.json !== 'object' && !flags.quiet) {\n const details: Array<{ label: string; value: string }> = [\n { label: 'config', value: configPath },\n { label: 'contract', value: contractPath },\n ];\n if (options.db) {\n details.push({ label: 'database', value: options.db });\n }\n const header = formatStyledHeader({\n command: 'db schema-verify',\n description: 'Check whether the database schema satisfies your contract',\n url: 'https://pris.ly/db-schema-verify',\n details,\n flags,\n });\n console.log(header);\n }\n\n // Load contract file (file I/O)\n let contractJsonContent: string;\n try {\n contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');\n } catch (error) {\n if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {\n throw errorFileNotFound(contractPathAbsolute, {\n why: `Contract file not found at ${contractPathAbsolute}`,\n });\n }\n throw errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n const contractJson = JSON.parse(contractJsonContent) as Record<string, unknown>;\n\n // Resolve database URL\n const dbUrl = options.db ?? config.db?.url;\n if (!dbUrl) {\n throw errorDatabaseUrlRequired();\n }\n\n // Check for driver\n if (!config.driver) {\n throw errorDriverRequired();\n }\n\n // Store driver descriptor after null check\n const driverDescriptor = config.driver;\n\n // Create driver\n const driver = await withSpinner(() => driverDescriptor.create(dbUrl), {\n message: 'Connecting to database...',\n flags,\n });\n\n try {\n // Create family instance\n const familyInstance = config.family.create({\n target: config.target,\n adapter: config.adapter,\n driver: driverDescriptor,\n extensions: config.extensions ?? [],\n });\n const rawComponents = [config.target, config.adapter, ...(config.extensions ?? [])];\n const frameworkComponents = assertFrameworkComponentsCompatible(\n config.family.familyId,\n config.target.targetId,\n rawComponents,\n );\n\n // Validate contract using instance validator\n const contractIR = familyInstance.validateContractIR(contractJson) as ContractIR;\n\n // Call family instance schemaVerify method\n let schemaVerifyResult: VerifyDatabaseSchemaResult;\n try {\n schemaVerifyResult = await withSpinner(\n () =>\n familyInstance.schemaVerify({\n driver,\n contractIR,\n strict: options.strict ?? false,\n contractPath: contractPathAbsolute,\n configPath,\n frameworkComponents,\n }),\n {\n message: 'Verifying database schema...',\n flags,\n },\n );\n } catch (error) {\n // Wrap errors from schemaVerify() in structured error\n throw errorRuntime(error instanceof Error ? error.message : String(error), {\n why: `Failed to verify database schema: ${error instanceof Error ? error.message : String(error)}`,\n });\n }\n\n // Add blank line after all async operations if spinners were shown\n if (!flags.quiet && flags.json !== 'object' && process.stdout.isTTY) {\n console.log('');\n }\n\n // Return result (don't throw for logical mismatches - handle exit code separately)\n return schemaVerifyResult;\n } finally {\n // Ensure driver connection is closed\n await driver.close();\n }\n });\n\n // Handle result - formats output and returns exit code\n const exitCode = handleResult(result, flags, (schemaVerifyResult) => {\n // Output based on flags\n if (flags.json === 'object') {\n // JSON output to stdout\n console.log(formatSchemaVerifyJson(schemaVerifyResult));\n } else {\n // Human-readable output to stdout\n const output = formatSchemaVerifyOutput(schemaVerifyResult, flags);\n if (output) {\n console.log(output);\n }\n }\n });\n\n // For logical schema mismatches, check if verification passed\n // Infra errors already handled by handleResult (returns non-zero exit code)\n if (result.ok && !result.value.ok) {\n // Schema verification failed - exit with code 1\n process.exit(1);\n } else {\n // Success or infra error - use exit code from handleResult\n process.exit(exitCode);\n }\n });\n\n return command;\n}\n"],"mappings":"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","names":[]}
|