@prisma-next/cli 0.3.0-dev.3 → 0.3.0-dev.30
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 +111 -27
- package/dist/{chunk-BZMBKEEQ.js → chunk-AGOTG4L3.js} +44 -76
- 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.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1502 -968
- package/dist/cli.js.map +1 -1
- package/dist/commands/contract-emit.d.ts +2 -4
- package/dist/commands/contract-emit.d.ts.map +1 -0
- package/dist/commands/contract-emit.js +3 -2
- package/dist/commands/db-init.d.ts +2 -4
- package/dist/commands/db-init.d.ts.map +1 -0
- package/dist/commands/db-init.js +205 -289
- package/dist/commands/db-init.js.map +1 -1
- package/dist/commands/db-introspect.d.ts +2 -4
- package/dist/commands/db-introspect.d.ts.map +1 -0
- package/dist/commands/db-introspect.js +108 -143
- package/dist/commands/db-introspect.js.map +1 -1
- package/dist/commands/db-schema-verify.d.ts +2 -4
- package/dist/commands/db-schema-verify.d.ts.map +1 -0
- package/dist/commands/db-schema-verify.js +120 -113
- package/dist/commands/db-schema-verify.js.map +1 -1
- package/dist/commands/db-sign.d.ts +2 -4
- package/dist/commands/db-sign.d.ts.map +1 -0
- package/dist/commands/db-sign.js +152 -156
- package/dist/commands/db-sign.js.map +1 -1
- package/dist/commands/db-verify.d.ts +2 -4
- package/dist/commands/db-verify.d.ts.map +1 -0
- package/dist/commands/db-verify.js +142 -122
- package/dist/commands/db-verify.js.map +1 -1
- package/dist/config-loader.d.ts +3 -5
- package/dist/config-loader.d.ts.map +1 -0
- package/dist/control-api/client.d.ts +13 -0
- package/dist/control-api/client.d.ts.map +1 -0
- package/dist/control-api/operations/db-init.d.ts +29 -0
- package/dist/control-api/operations/db-init.d.ts.map +1 -0
- package/dist/control-api/types.d.ts +387 -0
- package/dist/control-api/types.d.ts.map +1 -0
- package/dist/exports/config-types.d.ts +3 -0
- package/dist/exports/config-types.d.ts.map +1 -0
- package/dist/exports/config-types.js.map +1 -0
- package/dist/exports/control-api.d.ts +13 -0
- package/dist/exports/control-api.d.ts.map +1 -0
- package/dist/exports/control-api.js +7 -0
- package/dist/exports/control-api.js.map +1 -0
- package/dist/exports/index.d.ts +4 -0
- package/dist/exports/index.d.ts.map +1 -0
- package/dist/{index.js → exports/index.js} +4 -3
- package/dist/exports/index.js.map +1 -0
- package/dist/{index.d.ts → load-ts-contract.d.ts} +4 -8
- package/dist/load-ts-contract.d.ts.map +1 -0
- package/dist/utils/cli-errors.d.ts +7 -0
- package/dist/utils/cli-errors.d.ts.map +1 -0
- package/dist/utils/command-helpers.d.ts +12 -0
- package/dist/utils/command-helpers.d.ts.map +1 -0
- package/dist/utils/framework-components.d.ts +70 -0
- package/dist/utils/framework-components.d.ts.map +1 -0
- package/dist/utils/global-flags.d.ts +25 -0
- package/dist/utils/global-flags.d.ts.map +1 -0
- package/dist/utils/output.d.ts +142 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/progress-adapter.d.ts +26 -0
- package/dist/utils/progress-adapter.d.ts.map +1 -0
- package/dist/utils/result-handler.d.ts +15 -0
- package/dist/utils/result-handler.d.ts.map +1 -0
- package/package.json +30 -26
- package/src/cli.ts +260 -0
- package/src/commands/contract-emit.ts +259 -0
- package/src/commands/db-init.ts +360 -0
- package/src/commands/db-introspect.ts +227 -0
- package/src/commands/db-schema-verify.ts +238 -0
- package/src/commands/db-sign.ts +279 -0
- package/src/commands/db-verify.ts +258 -0
- package/src/config-loader.ts +76 -0
- package/src/control-api/client.ts +589 -0
- package/src/control-api/operations/db-init.ts +281 -0
- package/src/control-api/types.ts +461 -0
- package/src/exports/config-types.ts +6 -0
- package/src/exports/control-api.ts +46 -0
- package/src/exports/index.ts +4 -0
- package/src/load-ts-contract.ts +217 -0
- package/src/utils/cli-errors.ts +26 -0
- package/src/utils/command-helpers.ts +26 -0
- package/src/utils/framework-components.ts +177 -0
- package/src/utils/global-flags.ts +75 -0
- package/src/utils/output.ts +1471 -0
- package/src/utils/progress-adapter.ts +86 -0
- package/src/utils/result-handler.ts +44 -0
- package/dist/chunk-464LNZCE.js +0 -134
- package/dist/chunk-464LNZCE.js.map +0 -1
- package/dist/chunk-BZMBKEEQ.js.map +0 -1
- package/dist/chunk-ZKYEJROM.js +0 -94
- package/dist/chunk-ZKYEJROM.js.map +0 -1
- package/dist/config-types.d.ts +0 -1
- package/dist/config-types.js.map +0 -1
- package/dist/index.js.map +0 -1
- /package/dist/{config-types.js → exports/config-types.js} +0 -0
package/dist/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Command as Command7 } from "commander";
|
|
|
7
7
|
import { mkdirSync, writeFileSync } from "fs";
|
|
8
8
|
import { dirname as dirname2, relative as relative2, resolve as resolve2 } from "path";
|
|
9
9
|
import { errorContractConfigMissing as errorContractConfigMissing2 } from "@prisma-next/core-control-plane/errors";
|
|
10
|
+
import { notOk as notOk3, ok as ok3 } from "@prisma-next/utils/result";
|
|
10
11
|
import { Command } from "commander";
|
|
11
12
|
|
|
12
13
|
// src/config-loader.ts
|
|
@@ -52,8 +53,14 @@ async function loadConfig(configPath) {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
// src/
|
|
56
|
-
import {
|
|
56
|
+
// src/control-api/client.ts
|
|
57
|
+
import { createControlPlaneStack } from "@prisma-next/core-control-plane/stack";
|
|
58
|
+
import { notOk as notOk2, ok as ok2 } from "@prisma-next/utils/result";
|
|
59
|
+
|
|
60
|
+
// src/utils/framework-components.ts
|
|
61
|
+
import {
|
|
62
|
+
checkContractComponentRequirements
|
|
63
|
+
} from "@prisma-next/contract/framework-components";
|
|
57
64
|
|
|
58
65
|
// src/utils/cli-errors.ts
|
|
59
66
|
import {
|
|
@@ -63,7 +70,7 @@ import {
|
|
|
63
70
|
errorContractConfigMissing,
|
|
64
71
|
errorContractMissingExtensionPacks,
|
|
65
72
|
errorContractValidationFailed,
|
|
66
|
-
|
|
73
|
+
errorDatabaseConnectionRequired,
|
|
67
74
|
errorDriverRequired,
|
|
68
75
|
errorFamilyReadMarkerSqlRequired,
|
|
69
76
|
errorFileNotFound,
|
|
@@ -78,18 +85,693 @@ import {
|
|
|
78
85
|
errorUnexpected as errorUnexpected2
|
|
79
86
|
} from "@prisma-next/core-control-plane/errors";
|
|
80
87
|
|
|
81
|
-
// src/utils/
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
// src/utils/framework-components.ts
|
|
89
|
+
function assertFrameworkComponentsCompatible(expectedFamilyId, expectedTargetId, frameworkComponents) {
|
|
90
|
+
for (let i = 0; i < frameworkComponents.length; i++) {
|
|
91
|
+
const component = frameworkComponents[i];
|
|
92
|
+
if (typeof component !== "object" || component === null) {
|
|
93
|
+
throw errorConfigValidation("frameworkComponents[]", {
|
|
94
|
+
why: `Framework component at index ${i} must be an object`
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
const record = component;
|
|
98
|
+
if (!Object.hasOwn(record, "kind")) {
|
|
99
|
+
throw errorConfigValidation("frameworkComponents[].kind", {
|
|
100
|
+
why: `Framework component at index ${i} must have 'kind' property`
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
const kind = record["kind"];
|
|
104
|
+
if (kind !== "target" && kind !== "adapter" && kind !== "extension" && kind !== "driver") {
|
|
105
|
+
throw errorConfigValidation("frameworkComponents[].kind", {
|
|
106
|
+
why: `Framework component at index ${i} has invalid kind '${String(kind)}' (must be 'target', 'adapter', 'extension', or 'driver')`
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
if (!Object.hasOwn(record, "familyId")) {
|
|
110
|
+
throw errorConfigValidation("frameworkComponents[].familyId", {
|
|
111
|
+
why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'familyId' property`
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const familyId = record["familyId"];
|
|
115
|
+
if (familyId !== expectedFamilyId) {
|
|
116
|
+
throw errorConfigValidation("frameworkComponents[].familyId", {
|
|
117
|
+
why: `Framework component at index ${i} (kind: ${String(kind)}) has familyId '${String(familyId)}' but expected '${expectedFamilyId}'`
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (!Object.hasOwn(record, "targetId")) {
|
|
121
|
+
throw errorConfigValidation("frameworkComponents[].targetId", {
|
|
122
|
+
why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'targetId' property`
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const targetId = record["targetId"];
|
|
126
|
+
if (targetId !== expectedTargetId) {
|
|
127
|
+
throw errorConfigValidation("frameworkComponents[].targetId", {
|
|
128
|
+
why: `Framework component at index ${i} (kind: ${String(kind)}) has targetId '${String(targetId)}' but expected '${expectedTargetId}'`
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return frameworkComponents;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/control-api/operations/db-init.ts
|
|
136
|
+
import { notOk, ok } from "@prisma-next/utils/result";
|
|
137
|
+
async function executeDbInit(options) {
|
|
138
|
+
const { driver, familyInstance, contractIR, mode, migrations, frameworkComponents, onProgress } = options;
|
|
139
|
+
const planner = migrations.createPlanner(familyInstance);
|
|
140
|
+
const runner = migrations.createRunner(familyInstance);
|
|
141
|
+
const introspectSpanId = "introspect";
|
|
142
|
+
onProgress?.({
|
|
143
|
+
action: "dbInit",
|
|
144
|
+
kind: "spanStart",
|
|
145
|
+
spanId: introspectSpanId,
|
|
146
|
+
label: "Introspecting database schema"
|
|
147
|
+
});
|
|
148
|
+
const schemaIR = await familyInstance.introspect({ driver });
|
|
149
|
+
onProgress?.({
|
|
150
|
+
action: "dbInit",
|
|
151
|
+
kind: "spanEnd",
|
|
152
|
+
spanId: introspectSpanId,
|
|
153
|
+
outcome: "ok"
|
|
154
|
+
});
|
|
155
|
+
const policy = { allowedOperationClasses: ["additive"] };
|
|
156
|
+
const planSpanId = "plan";
|
|
157
|
+
onProgress?.({
|
|
158
|
+
action: "dbInit",
|
|
159
|
+
kind: "spanStart",
|
|
160
|
+
spanId: planSpanId,
|
|
161
|
+
label: "Planning migration"
|
|
162
|
+
});
|
|
163
|
+
const plannerResult = await planner.plan({
|
|
164
|
+
contract: contractIR,
|
|
165
|
+
schema: schemaIR,
|
|
166
|
+
policy,
|
|
167
|
+
frameworkComponents
|
|
168
|
+
});
|
|
169
|
+
if (plannerResult.kind === "failure") {
|
|
170
|
+
onProgress?.({
|
|
171
|
+
action: "dbInit",
|
|
172
|
+
kind: "spanEnd",
|
|
173
|
+
spanId: planSpanId,
|
|
174
|
+
outcome: "error"
|
|
175
|
+
});
|
|
176
|
+
return notOk({
|
|
177
|
+
code: "PLANNING_FAILED",
|
|
178
|
+
summary: "Migration planning failed due to conflicts",
|
|
179
|
+
conflicts: plannerResult.conflicts,
|
|
180
|
+
why: void 0,
|
|
181
|
+
meta: void 0
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
const migrationPlan = plannerResult.plan;
|
|
185
|
+
onProgress?.({
|
|
186
|
+
action: "dbInit",
|
|
187
|
+
kind: "spanEnd",
|
|
188
|
+
spanId: planSpanId,
|
|
189
|
+
outcome: "ok"
|
|
190
|
+
});
|
|
191
|
+
const checkMarkerSpanId = "checkMarker";
|
|
192
|
+
onProgress?.({
|
|
193
|
+
action: "dbInit",
|
|
194
|
+
kind: "spanStart",
|
|
195
|
+
spanId: checkMarkerSpanId,
|
|
196
|
+
label: "Checking contract marker"
|
|
197
|
+
});
|
|
198
|
+
const existingMarker = await familyInstance.readMarker({ driver });
|
|
199
|
+
if (existingMarker) {
|
|
200
|
+
const markerMatchesDestination = existingMarker.coreHash === migrationPlan.destination.coreHash && (!migrationPlan.destination.profileHash || existingMarker.profileHash === migrationPlan.destination.profileHash);
|
|
201
|
+
if (markerMatchesDestination) {
|
|
202
|
+
onProgress?.({
|
|
203
|
+
action: "dbInit",
|
|
204
|
+
kind: "spanEnd",
|
|
205
|
+
spanId: checkMarkerSpanId,
|
|
206
|
+
outcome: "skipped"
|
|
207
|
+
});
|
|
208
|
+
const result2 = {
|
|
209
|
+
mode,
|
|
210
|
+
plan: { operations: [] },
|
|
211
|
+
...mode === "apply" ? {
|
|
212
|
+
execution: { operationsPlanned: 0, operationsExecuted: 0 },
|
|
213
|
+
marker: {
|
|
214
|
+
coreHash: existingMarker.coreHash,
|
|
215
|
+
profileHash: existingMarker.profileHash
|
|
216
|
+
}
|
|
217
|
+
} : {},
|
|
218
|
+
summary: "Database already at target contract state"
|
|
219
|
+
};
|
|
220
|
+
return ok(result2);
|
|
221
|
+
}
|
|
222
|
+
onProgress?.({
|
|
223
|
+
action: "dbInit",
|
|
224
|
+
kind: "spanEnd",
|
|
225
|
+
spanId: checkMarkerSpanId,
|
|
226
|
+
outcome: "error"
|
|
227
|
+
});
|
|
228
|
+
return notOk({
|
|
229
|
+
code: "MARKER_ORIGIN_MISMATCH",
|
|
230
|
+
summary: "Existing contract marker does not match plan destination",
|
|
231
|
+
marker: {
|
|
232
|
+
coreHash: existingMarker.coreHash,
|
|
233
|
+
profileHash: existingMarker.profileHash
|
|
234
|
+
},
|
|
235
|
+
destination: {
|
|
236
|
+
coreHash: migrationPlan.destination.coreHash,
|
|
237
|
+
profileHash: migrationPlan.destination.profileHash
|
|
238
|
+
},
|
|
239
|
+
why: void 0,
|
|
240
|
+
conflicts: void 0,
|
|
241
|
+
meta: void 0
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
onProgress?.({
|
|
245
|
+
action: "dbInit",
|
|
246
|
+
kind: "spanEnd",
|
|
247
|
+
spanId: checkMarkerSpanId,
|
|
248
|
+
outcome: "ok"
|
|
249
|
+
});
|
|
250
|
+
if (mode === "plan") {
|
|
251
|
+
const result2 = {
|
|
252
|
+
mode: "plan",
|
|
253
|
+
plan: { operations: migrationPlan.operations },
|
|
254
|
+
summary: `Planned ${migrationPlan.operations.length} operation(s)`
|
|
255
|
+
};
|
|
256
|
+
return ok(result2);
|
|
257
|
+
}
|
|
258
|
+
const applySpanId = "apply";
|
|
259
|
+
onProgress?.({
|
|
260
|
+
action: "dbInit",
|
|
261
|
+
kind: "spanStart",
|
|
262
|
+
spanId: applySpanId,
|
|
263
|
+
label: "Applying migration plan"
|
|
264
|
+
});
|
|
265
|
+
const callbacks = onProgress ? {
|
|
266
|
+
onOperationStart: (op) => {
|
|
267
|
+
onProgress({
|
|
268
|
+
action: "dbInit",
|
|
269
|
+
kind: "spanStart",
|
|
270
|
+
spanId: `operation:${op.id}`,
|
|
271
|
+
parentSpanId: applySpanId,
|
|
272
|
+
label: op.label
|
|
273
|
+
});
|
|
274
|
+
},
|
|
275
|
+
onOperationComplete: (op) => {
|
|
276
|
+
onProgress({
|
|
277
|
+
action: "dbInit",
|
|
278
|
+
kind: "spanEnd",
|
|
279
|
+
spanId: `operation:${op.id}`,
|
|
280
|
+
outcome: "ok"
|
|
281
|
+
});
|
|
89
282
|
}
|
|
90
|
-
|
|
283
|
+
} : void 0;
|
|
284
|
+
const runnerResult = await runner.execute({
|
|
285
|
+
plan: migrationPlan,
|
|
286
|
+
driver,
|
|
287
|
+
destinationContract: contractIR,
|
|
288
|
+
policy,
|
|
289
|
+
...callbacks ? { callbacks } : {},
|
|
290
|
+
// db init plans and applies back-to-back from a fresh introspection, so per-operation
|
|
291
|
+
// pre/postchecks and the idempotency probe are usually redundant overhead. We still
|
|
292
|
+
// enforce marker/origin compatibility and a full schema verification after apply.
|
|
293
|
+
executionChecks: {
|
|
294
|
+
prechecks: false,
|
|
295
|
+
postchecks: false,
|
|
296
|
+
idempotencyChecks: false
|
|
297
|
+
},
|
|
298
|
+
frameworkComponents
|
|
299
|
+
});
|
|
300
|
+
if (!runnerResult.ok) {
|
|
301
|
+
onProgress?.({
|
|
302
|
+
action: "dbInit",
|
|
303
|
+
kind: "spanEnd",
|
|
304
|
+
spanId: applySpanId,
|
|
305
|
+
outcome: "error"
|
|
306
|
+
});
|
|
307
|
+
return notOk({
|
|
308
|
+
code: "RUNNER_FAILED",
|
|
309
|
+
summary: runnerResult.failure.summary,
|
|
310
|
+
why: runnerResult.failure.why,
|
|
311
|
+
meta: runnerResult.failure.meta,
|
|
312
|
+
conflicts: void 0
|
|
313
|
+
});
|
|
91
314
|
}
|
|
315
|
+
const execution = runnerResult.value;
|
|
316
|
+
onProgress?.({
|
|
317
|
+
action: "dbInit",
|
|
318
|
+
kind: "spanEnd",
|
|
319
|
+
spanId: applySpanId,
|
|
320
|
+
outcome: "ok"
|
|
321
|
+
});
|
|
322
|
+
const result = {
|
|
323
|
+
mode: "apply",
|
|
324
|
+
plan: { operations: migrationPlan.operations },
|
|
325
|
+
execution: {
|
|
326
|
+
operationsPlanned: execution.operationsPlanned,
|
|
327
|
+
operationsExecuted: execution.operationsExecuted
|
|
328
|
+
},
|
|
329
|
+
marker: migrationPlan.destination.profileHash ? {
|
|
330
|
+
coreHash: migrationPlan.destination.coreHash,
|
|
331
|
+
profileHash: migrationPlan.destination.profileHash
|
|
332
|
+
} : { coreHash: migrationPlan.destination.coreHash },
|
|
333
|
+
summary: `Applied ${execution.operationsExecuted} operation(s), marker written`
|
|
334
|
+
};
|
|
335
|
+
return ok(result);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// src/control-api/client.ts
|
|
339
|
+
function createControlClient(options) {
|
|
340
|
+
return new ControlClientImpl(options);
|
|
92
341
|
}
|
|
342
|
+
var ControlClientImpl = class {
|
|
343
|
+
options;
|
|
344
|
+
stack = null;
|
|
345
|
+
driver = null;
|
|
346
|
+
familyInstance = null;
|
|
347
|
+
frameworkComponents = null;
|
|
348
|
+
initialized = false;
|
|
349
|
+
defaultConnection;
|
|
350
|
+
constructor(options) {
|
|
351
|
+
this.options = options;
|
|
352
|
+
this.defaultConnection = options.connection;
|
|
353
|
+
}
|
|
354
|
+
init() {
|
|
355
|
+
if (this.initialized) {
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
this.stack = createControlPlaneStack({
|
|
359
|
+
target: this.options.target,
|
|
360
|
+
adapter: this.options.adapter,
|
|
361
|
+
driver: this.options.driver,
|
|
362
|
+
extensionPacks: this.options.extensionPacks
|
|
363
|
+
});
|
|
364
|
+
this.familyInstance = this.options.family.create(this.stack);
|
|
365
|
+
const rawComponents = [
|
|
366
|
+
this.options.target,
|
|
367
|
+
this.options.adapter,
|
|
368
|
+
...this.options.extensionPacks ?? []
|
|
369
|
+
];
|
|
370
|
+
this.frameworkComponents = assertFrameworkComponentsCompatible(
|
|
371
|
+
this.options.family.familyId,
|
|
372
|
+
this.options.target.targetId,
|
|
373
|
+
rawComponents
|
|
374
|
+
);
|
|
375
|
+
this.initialized = true;
|
|
376
|
+
}
|
|
377
|
+
async connect(connection) {
|
|
378
|
+
this.init();
|
|
379
|
+
if (this.driver) {
|
|
380
|
+
throw new Error("Already connected. Call close() before reconnecting.");
|
|
381
|
+
}
|
|
382
|
+
const resolvedConnection = connection ?? this.defaultConnection;
|
|
383
|
+
if (resolvedConnection === void 0) {
|
|
384
|
+
throw new Error(
|
|
385
|
+
"No connection provided. Pass a connection to connect() or provide a default connection when creating the client."
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
if (!this.stack?.driver) {
|
|
389
|
+
throw new Error(
|
|
390
|
+
"Driver is not configured. Pass a driver descriptor when creating the control client to enable database operations."
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
this.driver = await this.stack?.driver.create(resolvedConnection);
|
|
394
|
+
}
|
|
395
|
+
async close() {
|
|
396
|
+
if (this.driver) {
|
|
397
|
+
await this.driver.close();
|
|
398
|
+
this.driver = null;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
async ensureConnected() {
|
|
402
|
+
this.init();
|
|
403
|
+
if (!this.driver && this.defaultConnection !== void 0) {
|
|
404
|
+
await this.connect(this.defaultConnection);
|
|
405
|
+
}
|
|
406
|
+
if (!this.driver || !this.familyInstance || !this.frameworkComponents) {
|
|
407
|
+
throw new Error("Not connected. Call connect(connection) first.");
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
driver: this.driver,
|
|
411
|
+
familyInstance: this.familyInstance,
|
|
412
|
+
frameworkComponents: this.frameworkComponents
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
async verify(options) {
|
|
416
|
+
const { onProgress } = options;
|
|
417
|
+
if (options.connection !== void 0) {
|
|
418
|
+
onProgress?.({
|
|
419
|
+
action: "verify",
|
|
420
|
+
kind: "spanStart",
|
|
421
|
+
spanId: "connect",
|
|
422
|
+
label: "Connecting to database..."
|
|
423
|
+
});
|
|
424
|
+
try {
|
|
425
|
+
await this.connect(options.connection);
|
|
426
|
+
onProgress?.({
|
|
427
|
+
action: "verify",
|
|
428
|
+
kind: "spanEnd",
|
|
429
|
+
spanId: "connect",
|
|
430
|
+
outcome: "ok"
|
|
431
|
+
});
|
|
432
|
+
} catch (error) {
|
|
433
|
+
onProgress?.({
|
|
434
|
+
action: "verify",
|
|
435
|
+
kind: "spanEnd",
|
|
436
|
+
spanId: "connect",
|
|
437
|
+
outcome: "error"
|
|
438
|
+
});
|
|
439
|
+
throw error;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
const { driver, familyInstance } = await this.ensureConnected();
|
|
443
|
+
const contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
444
|
+
onProgress?.({
|
|
445
|
+
action: "verify",
|
|
446
|
+
kind: "spanStart",
|
|
447
|
+
spanId: "verify",
|
|
448
|
+
label: "Verifying contract marker..."
|
|
449
|
+
});
|
|
450
|
+
try {
|
|
451
|
+
const result = await familyInstance.verify({
|
|
452
|
+
driver,
|
|
453
|
+
contractIR,
|
|
454
|
+
expectedTargetId: this.options.target.targetId,
|
|
455
|
+
contractPath: ""
|
|
456
|
+
});
|
|
457
|
+
onProgress?.({
|
|
458
|
+
action: "verify",
|
|
459
|
+
kind: "spanEnd",
|
|
460
|
+
spanId: "verify",
|
|
461
|
+
outcome: result.ok ? "ok" : "error"
|
|
462
|
+
});
|
|
463
|
+
return result;
|
|
464
|
+
} catch (error) {
|
|
465
|
+
onProgress?.({
|
|
466
|
+
action: "verify",
|
|
467
|
+
kind: "spanEnd",
|
|
468
|
+
spanId: "verify",
|
|
469
|
+
outcome: "error"
|
|
470
|
+
});
|
|
471
|
+
throw error;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
async schemaVerify(options) {
|
|
475
|
+
const { onProgress } = options;
|
|
476
|
+
if (options.connection !== void 0) {
|
|
477
|
+
onProgress?.({
|
|
478
|
+
action: "schemaVerify",
|
|
479
|
+
kind: "spanStart",
|
|
480
|
+
spanId: "connect",
|
|
481
|
+
label: "Connecting to database..."
|
|
482
|
+
});
|
|
483
|
+
try {
|
|
484
|
+
await this.connect(options.connection);
|
|
485
|
+
onProgress?.({
|
|
486
|
+
action: "schemaVerify",
|
|
487
|
+
kind: "spanEnd",
|
|
488
|
+
spanId: "connect",
|
|
489
|
+
outcome: "ok"
|
|
490
|
+
});
|
|
491
|
+
} catch (error) {
|
|
492
|
+
onProgress?.({
|
|
493
|
+
action: "schemaVerify",
|
|
494
|
+
kind: "spanEnd",
|
|
495
|
+
spanId: "connect",
|
|
496
|
+
outcome: "error"
|
|
497
|
+
});
|
|
498
|
+
throw error;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
502
|
+
const contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
503
|
+
onProgress?.({
|
|
504
|
+
action: "schemaVerify",
|
|
505
|
+
kind: "spanStart",
|
|
506
|
+
spanId: "schemaVerify",
|
|
507
|
+
label: "Verifying database schema..."
|
|
508
|
+
});
|
|
509
|
+
try {
|
|
510
|
+
const result = await familyInstance.schemaVerify({
|
|
511
|
+
driver,
|
|
512
|
+
contractIR,
|
|
513
|
+
strict: options.strict ?? false,
|
|
514
|
+
contractPath: "",
|
|
515
|
+
frameworkComponents
|
|
516
|
+
});
|
|
517
|
+
onProgress?.({
|
|
518
|
+
action: "schemaVerify",
|
|
519
|
+
kind: "spanEnd",
|
|
520
|
+
spanId: "schemaVerify",
|
|
521
|
+
outcome: result.ok ? "ok" : "error"
|
|
522
|
+
});
|
|
523
|
+
return result;
|
|
524
|
+
} catch (error) {
|
|
525
|
+
onProgress?.({
|
|
526
|
+
action: "schemaVerify",
|
|
527
|
+
kind: "spanEnd",
|
|
528
|
+
spanId: "schemaVerify",
|
|
529
|
+
outcome: "error"
|
|
530
|
+
});
|
|
531
|
+
throw error;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
async sign(options) {
|
|
535
|
+
const { onProgress } = options;
|
|
536
|
+
if (options.connection !== void 0) {
|
|
537
|
+
onProgress?.({
|
|
538
|
+
action: "sign",
|
|
539
|
+
kind: "spanStart",
|
|
540
|
+
spanId: "connect",
|
|
541
|
+
label: "Connecting to database..."
|
|
542
|
+
});
|
|
543
|
+
try {
|
|
544
|
+
await this.connect(options.connection);
|
|
545
|
+
onProgress?.({
|
|
546
|
+
action: "sign",
|
|
547
|
+
kind: "spanEnd",
|
|
548
|
+
spanId: "connect",
|
|
549
|
+
outcome: "ok"
|
|
550
|
+
});
|
|
551
|
+
} catch (error) {
|
|
552
|
+
onProgress?.({
|
|
553
|
+
action: "sign",
|
|
554
|
+
kind: "spanEnd",
|
|
555
|
+
spanId: "connect",
|
|
556
|
+
outcome: "error"
|
|
557
|
+
});
|
|
558
|
+
throw error;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
const { driver, familyInstance } = await this.ensureConnected();
|
|
562
|
+
const contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
563
|
+
onProgress?.({
|
|
564
|
+
action: "sign",
|
|
565
|
+
kind: "spanStart",
|
|
566
|
+
spanId: "sign",
|
|
567
|
+
label: "Signing database..."
|
|
568
|
+
});
|
|
569
|
+
try {
|
|
570
|
+
const result = await familyInstance.sign({
|
|
571
|
+
driver,
|
|
572
|
+
contractIR,
|
|
573
|
+
contractPath: options.contractPath ?? "",
|
|
574
|
+
...options.configPath ? { configPath: options.configPath } : {}
|
|
575
|
+
});
|
|
576
|
+
onProgress?.({
|
|
577
|
+
action: "sign",
|
|
578
|
+
kind: "spanEnd",
|
|
579
|
+
spanId: "sign",
|
|
580
|
+
outcome: "ok"
|
|
581
|
+
});
|
|
582
|
+
return result;
|
|
583
|
+
} catch (error) {
|
|
584
|
+
onProgress?.({
|
|
585
|
+
action: "sign",
|
|
586
|
+
kind: "spanEnd",
|
|
587
|
+
spanId: "sign",
|
|
588
|
+
outcome: "error"
|
|
589
|
+
});
|
|
590
|
+
throw error;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
async dbInit(options) {
|
|
594
|
+
const { onProgress } = options;
|
|
595
|
+
if (options.connection !== void 0) {
|
|
596
|
+
onProgress?.({
|
|
597
|
+
action: "dbInit",
|
|
598
|
+
kind: "spanStart",
|
|
599
|
+
spanId: "connect",
|
|
600
|
+
label: "Connecting to database..."
|
|
601
|
+
});
|
|
602
|
+
try {
|
|
603
|
+
await this.connect(options.connection);
|
|
604
|
+
onProgress?.({
|
|
605
|
+
action: "dbInit",
|
|
606
|
+
kind: "spanEnd",
|
|
607
|
+
spanId: "connect",
|
|
608
|
+
outcome: "ok"
|
|
609
|
+
});
|
|
610
|
+
} catch (error) {
|
|
611
|
+
onProgress?.({
|
|
612
|
+
action: "dbInit",
|
|
613
|
+
kind: "spanEnd",
|
|
614
|
+
spanId: "connect",
|
|
615
|
+
outcome: "error"
|
|
616
|
+
});
|
|
617
|
+
throw error;
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
621
|
+
if (!this.options.target.migrations) {
|
|
622
|
+
throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
|
|
623
|
+
}
|
|
624
|
+
const contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
625
|
+
return executeDbInit({
|
|
626
|
+
driver,
|
|
627
|
+
familyInstance,
|
|
628
|
+
contractIR,
|
|
629
|
+
mode: options.mode,
|
|
630
|
+
migrations: this.options.target.migrations,
|
|
631
|
+
frameworkComponents,
|
|
632
|
+
...onProgress ? { onProgress } : {}
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
async introspect(options) {
|
|
636
|
+
const onProgress = options?.onProgress;
|
|
637
|
+
if (options?.connection !== void 0) {
|
|
638
|
+
onProgress?.({
|
|
639
|
+
action: "introspect",
|
|
640
|
+
kind: "spanStart",
|
|
641
|
+
spanId: "connect",
|
|
642
|
+
label: "Connecting to database..."
|
|
643
|
+
});
|
|
644
|
+
try {
|
|
645
|
+
await this.connect(options.connection);
|
|
646
|
+
onProgress?.({
|
|
647
|
+
action: "introspect",
|
|
648
|
+
kind: "spanEnd",
|
|
649
|
+
spanId: "connect",
|
|
650
|
+
outcome: "ok"
|
|
651
|
+
});
|
|
652
|
+
} catch (error) {
|
|
653
|
+
onProgress?.({
|
|
654
|
+
action: "introspect",
|
|
655
|
+
kind: "spanEnd",
|
|
656
|
+
spanId: "connect",
|
|
657
|
+
outcome: "error"
|
|
658
|
+
});
|
|
659
|
+
throw error;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
const { driver, familyInstance } = await this.ensureConnected();
|
|
663
|
+
const _schema = options?.schema;
|
|
664
|
+
void _schema;
|
|
665
|
+
onProgress?.({
|
|
666
|
+
action: "introspect",
|
|
667
|
+
kind: "spanStart",
|
|
668
|
+
spanId: "introspect",
|
|
669
|
+
label: "Introspecting database schema..."
|
|
670
|
+
});
|
|
671
|
+
try {
|
|
672
|
+
const result = await familyInstance.introspect({ driver });
|
|
673
|
+
onProgress?.({
|
|
674
|
+
action: "introspect",
|
|
675
|
+
kind: "spanEnd",
|
|
676
|
+
spanId: "introspect",
|
|
677
|
+
outcome: "ok"
|
|
678
|
+
});
|
|
679
|
+
return result;
|
|
680
|
+
} catch (error) {
|
|
681
|
+
onProgress?.({
|
|
682
|
+
action: "introspect",
|
|
683
|
+
kind: "spanEnd",
|
|
684
|
+
spanId: "introspect",
|
|
685
|
+
outcome: "error"
|
|
686
|
+
});
|
|
687
|
+
throw error;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
toSchemaView(schemaIR) {
|
|
691
|
+
this.init();
|
|
692
|
+
if (this.familyInstance?.toSchemaView) {
|
|
693
|
+
return this.familyInstance.toSchemaView(schemaIR);
|
|
694
|
+
}
|
|
695
|
+
return void 0;
|
|
696
|
+
}
|
|
697
|
+
async emit(options) {
|
|
698
|
+
const { onProgress, contractConfig } = options;
|
|
699
|
+
this.init();
|
|
700
|
+
if (!this.familyInstance) {
|
|
701
|
+
throw new Error("Family instance was not initialized. This is a bug.");
|
|
702
|
+
}
|
|
703
|
+
let contractRaw;
|
|
704
|
+
onProgress?.({
|
|
705
|
+
action: "emit",
|
|
706
|
+
kind: "spanStart",
|
|
707
|
+
spanId: "resolveSource",
|
|
708
|
+
label: "Resolving contract source..."
|
|
709
|
+
});
|
|
710
|
+
try {
|
|
711
|
+
switch (contractConfig.source.kind) {
|
|
712
|
+
case "loader":
|
|
713
|
+
contractRaw = await contractConfig.source.load();
|
|
714
|
+
break;
|
|
715
|
+
case "value":
|
|
716
|
+
contractRaw = contractConfig.source.value;
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
onProgress?.({
|
|
720
|
+
action: "emit",
|
|
721
|
+
kind: "spanEnd",
|
|
722
|
+
spanId: "resolveSource",
|
|
723
|
+
outcome: "ok"
|
|
724
|
+
});
|
|
725
|
+
} catch (error) {
|
|
726
|
+
onProgress?.({
|
|
727
|
+
action: "emit",
|
|
728
|
+
kind: "spanEnd",
|
|
729
|
+
spanId: "resolveSource",
|
|
730
|
+
outcome: "error"
|
|
731
|
+
});
|
|
732
|
+
return notOk2({
|
|
733
|
+
code: "CONTRACT_SOURCE_INVALID",
|
|
734
|
+
summary: "Failed to resolve contract source",
|
|
735
|
+
why: error instanceof Error ? error.message : String(error),
|
|
736
|
+
meta: void 0
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
onProgress?.({
|
|
740
|
+
action: "emit",
|
|
741
|
+
kind: "spanStart",
|
|
742
|
+
spanId: "emit",
|
|
743
|
+
label: "Emitting contract..."
|
|
744
|
+
});
|
|
745
|
+
try {
|
|
746
|
+
const emitResult = await this.familyInstance.emitContract({ contractIR: contractRaw });
|
|
747
|
+
onProgress?.({
|
|
748
|
+
action: "emit",
|
|
749
|
+
kind: "spanEnd",
|
|
750
|
+
spanId: "emit",
|
|
751
|
+
outcome: "ok"
|
|
752
|
+
});
|
|
753
|
+
return ok2({
|
|
754
|
+
coreHash: emitResult.coreHash,
|
|
755
|
+
profileHash: emitResult.profileHash,
|
|
756
|
+
contractJson: emitResult.contractJson,
|
|
757
|
+
contractDts: emitResult.contractDts
|
|
758
|
+
});
|
|
759
|
+
} catch (error) {
|
|
760
|
+
onProgress?.({
|
|
761
|
+
action: "emit",
|
|
762
|
+
kind: "spanEnd",
|
|
763
|
+
spanId: "emit",
|
|
764
|
+
outcome: "error"
|
|
765
|
+
});
|
|
766
|
+
return notOk2({
|
|
767
|
+
code: "EMIT_FAILED",
|
|
768
|
+
summary: "Failed to emit contract",
|
|
769
|
+
why: error instanceof Error ? error.message : String(error),
|
|
770
|
+
meta: void 0
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
};
|
|
93
775
|
|
|
94
776
|
// src/utils/command-helpers.ts
|
|
95
777
|
function setCommandDescriptions(command, shortDescription, longDescription) {
|
|
@@ -1040,6 +1722,47 @@ function formatRootHelp(options) {
|
|
|
1040
1722
|
`;
|
|
1041
1723
|
}
|
|
1042
1724
|
|
|
1725
|
+
// src/utils/progress-adapter.ts
|
|
1726
|
+
import ora from "ora";
|
|
1727
|
+
function createProgressAdapter(options) {
|
|
1728
|
+
const { flags } = options;
|
|
1729
|
+
const shouldShowProgress = !flags.quiet && flags.json !== "object" && process.stdout.isTTY;
|
|
1730
|
+
if (!shouldShowProgress) {
|
|
1731
|
+
return () => {
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
const activeSpans = /* @__PURE__ */ new Map();
|
|
1735
|
+
return (event) => {
|
|
1736
|
+
if (event.kind === "spanStart") {
|
|
1737
|
+
if (event.parentSpanId) {
|
|
1738
|
+
console.log(` \u2192 ${event.label}...`);
|
|
1739
|
+
return;
|
|
1740
|
+
}
|
|
1741
|
+
const spinner = ora({
|
|
1742
|
+
text: event.label,
|
|
1743
|
+
color: flags.color !== false ? "cyan" : false
|
|
1744
|
+
}).start();
|
|
1745
|
+
activeSpans.set(event.spanId, {
|
|
1746
|
+
spinner,
|
|
1747
|
+
startTime: Date.now()
|
|
1748
|
+
});
|
|
1749
|
+
} else if (event.kind === "spanEnd") {
|
|
1750
|
+
const spanState = activeSpans.get(event.spanId);
|
|
1751
|
+
if (spanState) {
|
|
1752
|
+
const elapsed = Date.now() - spanState.startTime;
|
|
1753
|
+
if (event.outcome === "error") {
|
|
1754
|
+
spanState.spinner.fail(`${spanState.spinner.text} (failed)`);
|
|
1755
|
+
} else if (event.outcome === "skipped") {
|
|
1756
|
+
spanState.spinner.info(`${spanState.spinner.text} (skipped)`);
|
|
1757
|
+
} else {
|
|
1758
|
+
spanState.spinner.succeed(`${spanState.spinner.text} (${elapsed}ms)`);
|
|
1759
|
+
}
|
|
1760
|
+
activeSpans.delete(event.spanId);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1043
1766
|
// src/utils/result-handler.ts
|
|
1044
1767
|
function handleResult(result, flags, onSuccess) {
|
|
1045
1768
|
if (result.ok) {
|
|
@@ -1058,31 +1781,122 @@ function handleResult(result, flags, onSuccess) {
|
|
|
1058
1781
|
return exitCode;
|
|
1059
1782
|
}
|
|
1060
1783
|
|
|
1061
|
-
// src/
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1784
|
+
// src/commands/contract-emit.ts
|
|
1785
|
+
function mapEmitFailure(failure) {
|
|
1786
|
+
if (failure.code === "CONTRACT_SOURCE_INVALID") {
|
|
1787
|
+
return errorRuntime(failure.summary, {
|
|
1788
|
+
why: failure.why ?? "Contract source is invalid",
|
|
1789
|
+
fix: "Check your contract source configuration in prisma-next.config.ts"
|
|
1790
|
+
});
|
|
1791
|
+
}
|
|
1792
|
+
if (failure.code === "EMIT_FAILED") {
|
|
1793
|
+
return errorRuntime(failure.summary, {
|
|
1794
|
+
why: failure.why ?? "Failed to emit contract",
|
|
1795
|
+
fix: "Check your contract configuration and ensure the source is valid"
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
const exhaustive = failure.code;
|
|
1799
|
+
throw new Error(`Unhandled EmitFailure code: ${exhaustive}`);
|
|
1800
|
+
}
|
|
1801
|
+
async function executeContractEmitCommand(options, flags, startTime) {
|
|
1802
|
+
let config;
|
|
1803
|
+
try {
|
|
1804
|
+
config = await loadConfig(options.config);
|
|
1805
|
+
} catch (error) {
|
|
1806
|
+
if (error instanceof CliStructuredError) {
|
|
1807
|
+
return notOk3(error);
|
|
1808
|
+
}
|
|
1809
|
+
return notOk3(
|
|
1810
|
+
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
1811
|
+
why: "Failed to load config"
|
|
1812
|
+
})
|
|
1813
|
+
);
|
|
1814
|
+
}
|
|
1815
|
+
if (!config.contract) {
|
|
1816
|
+
return notOk3(
|
|
1817
|
+
errorContractConfigMissing2({
|
|
1818
|
+
why: "Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ..., types: ... }"
|
|
1819
|
+
})
|
|
1820
|
+
);
|
|
1821
|
+
}
|
|
1822
|
+
const contractConfig = config.contract;
|
|
1823
|
+
if (!contractConfig.output || !contractConfig.types) {
|
|
1824
|
+
return notOk3(
|
|
1825
|
+
errorContractConfigMissing2({
|
|
1826
|
+
why: "Contract config must have output and types paths. This should not happen if defineConfig() was used."
|
|
1827
|
+
})
|
|
1828
|
+
);
|
|
1829
|
+
}
|
|
1830
|
+
const outputJsonPath = resolve2(contractConfig.output);
|
|
1831
|
+
const outputDtsPath = resolve2(contractConfig.types);
|
|
1832
|
+
if (flags.json !== "object" && !flags.quiet) {
|
|
1833
|
+
const configPath = options.config ? relative2(process.cwd(), resolve2(options.config)) : "prisma-next.config.ts";
|
|
1834
|
+
const contractPath = relative2(process.cwd(), outputJsonPath);
|
|
1835
|
+
const typesPath = relative2(process.cwd(), outputDtsPath);
|
|
1836
|
+
const header = formatStyledHeader({
|
|
1837
|
+
command: "contract emit",
|
|
1838
|
+
description: "Write your contract to JSON and sign it",
|
|
1839
|
+
url: "https://pris.ly/contract-emit",
|
|
1840
|
+
details: [
|
|
1841
|
+
{ label: "config", value: configPath },
|
|
1842
|
+
{ label: "contract", value: contractPath },
|
|
1843
|
+
{ label: "types", value: typesPath }
|
|
1844
|
+
],
|
|
1845
|
+
flags
|
|
1846
|
+
});
|
|
1847
|
+
console.log(header);
|
|
1848
|
+
}
|
|
1849
|
+
const client = createControlClient({
|
|
1850
|
+
family: config.family,
|
|
1851
|
+
target: config.target,
|
|
1852
|
+
adapter: config.adapter,
|
|
1853
|
+
extensionPacks: config.extensionPacks ?? []
|
|
1854
|
+
});
|
|
1855
|
+
const onProgress = createProgressAdapter({ flags });
|
|
1074
1856
|
try {
|
|
1075
|
-
const
|
|
1076
|
-
const
|
|
1077
|
-
|
|
1078
|
-
|
|
1857
|
+
const source = typeof contractConfig.source === "function" ? { kind: "loader", load: contractConfig.source } : { kind: "value", value: contractConfig.source };
|
|
1858
|
+
const result = await client.emit({
|
|
1859
|
+
contractConfig: {
|
|
1860
|
+
source,
|
|
1861
|
+
output: outputJsonPath,
|
|
1862
|
+
types: outputDtsPath
|
|
1863
|
+
},
|
|
1864
|
+
onProgress
|
|
1865
|
+
});
|
|
1866
|
+
if (!result.ok) {
|
|
1867
|
+
return notOk3(mapEmitFailure(result.failure));
|
|
1868
|
+
}
|
|
1869
|
+
mkdirSync(dirname2(outputJsonPath), { recursive: true });
|
|
1870
|
+
mkdirSync(dirname2(outputDtsPath), { recursive: true });
|
|
1871
|
+
writeFileSync(outputJsonPath, result.value.contractJson, "utf-8");
|
|
1872
|
+
writeFileSync(outputDtsPath, result.value.contractDts, "utf-8");
|
|
1873
|
+
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
1874
|
+
console.log("");
|
|
1875
|
+
}
|
|
1876
|
+
const emitResult = {
|
|
1877
|
+
coreHash: result.value.coreHash,
|
|
1878
|
+
profileHash: result.value.profileHash,
|
|
1879
|
+
outDir: dirname2(outputJsonPath),
|
|
1880
|
+
files: {
|
|
1881
|
+
json: outputJsonPath,
|
|
1882
|
+
dts: outputDtsPath
|
|
1883
|
+
},
|
|
1884
|
+
timings: { total: Date.now() - startTime }
|
|
1885
|
+
};
|
|
1886
|
+
return ok3(emitResult);
|
|
1079
1887
|
} catch (error) {
|
|
1080
|
-
|
|
1081
|
-
|
|
1888
|
+
if (CliStructuredError.is(error)) {
|
|
1889
|
+
return notOk3(error);
|
|
1890
|
+
}
|
|
1891
|
+
return notOk3(
|
|
1892
|
+
errorUnexpected2("Unexpected error during contract emit", {
|
|
1893
|
+
why: error instanceof Error ? error.message : String(error)
|
|
1894
|
+
})
|
|
1895
|
+
);
|
|
1896
|
+
} finally {
|
|
1897
|
+
await client.close();
|
|
1082
1898
|
}
|
|
1083
1899
|
}
|
|
1084
|
-
|
|
1085
|
-
// src/commands/contract-emit.ts
|
|
1086
1900
|
function createContractEmitCommand() {
|
|
1087
1901
|
const command = new Command("emit");
|
|
1088
1902
|
setCommandDescriptions(
|
|
@@ -1097,83 +1911,8 @@ function createContractEmitCommand() {
|
|
|
1097
1911
|
}
|
|
1098
1912
|
}).option("--config <path>", "Path to prisma-next.config.ts").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) => {
|
|
1099
1913
|
const flags = parseGlobalFlags(options);
|
|
1100
|
-
const
|
|
1101
|
-
|
|
1102
|
-
if (!config.contract) {
|
|
1103
|
-
throw errorContractConfigMissing2({
|
|
1104
|
-
why: "Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ..., types: ... }"
|
|
1105
|
-
});
|
|
1106
|
-
}
|
|
1107
|
-
const contractConfig = config.contract;
|
|
1108
|
-
if (!contractConfig.output || !contractConfig.types) {
|
|
1109
|
-
throw errorContractConfigMissing2({
|
|
1110
|
-
why: "Contract config must have output and types paths. This should not happen if defineConfig() was used."
|
|
1111
|
-
});
|
|
1112
|
-
}
|
|
1113
|
-
const outputJsonPath = resolve2(contractConfig.output);
|
|
1114
|
-
const outputDtsPath = resolve2(contractConfig.types);
|
|
1115
|
-
if (flags.json !== "object" && !flags.quiet) {
|
|
1116
|
-
const configPath = options.config ? relative2(process.cwd(), resolve2(options.config)) : "prisma-next.config.ts";
|
|
1117
|
-
const contractPath = relative2(process.cwd(), outputJsonPath);
|
|
1118
|
-
const typesPath = relative2(process.cwd(), outputDtsPath);
|
|
1119
|
-
const header = formatStyledHeader({
|
|
1120
|
-
command: "contract emit",
|
|
1121
|
-
description: "Write your contract to JSON and sign it",
|
|
1122
|
-
url: "https://pris.ly/contract-emit",
|
|
1123
|
-
details: [
|
|
1124
|
-
{ label: "config", value: configPath },
|
|
1125
|
-
{ label: "contract", value: contractPath },
|
|
1126
|
-
{ label: "types", value: typesPath }
|
|
1127
|
-
],
|
|
1128
|
-
flags
|
|
1129
|
-
});
|
|
1130
|
-
console.log(header);
|
|
1131
|
-
}
|
|
1132
|
-
if (!config.driver) {
|
|
1133
|
-
throw errorContractConfigMissing2({
|
|
1134
|
-
why: "Config.driver is required. Even though emit does not use the driver, it is required by ControlFamilyDescriptor.create()"
|
|
1135
|
-
});
|
|
1136
|
-
}
|
|
1137
|
-
const familyInstance = config.family.create({
|
|
1138
|
-
target: config.target,
|
|
1139
|
-
adapter: config.adapter,
|
|
1140
|
-
driver: config.driver,
|
|
1141
|
-
extensionPacks: config.extensionPacks ?? []
|
|
1142
|
-
});
|
|
1143
|
-
let contractRaw;
|
|
1144
|
-
if (typeof contractConfig.source === "function") {
|
|
1145
|
-
contractRaw = await contractConfig.source();
|
|
1146
|
-
} else {
|
|
1147
|
-
contractRaw = contractConfig.source;
|
|
1148
|
-
}
|
|
1149
|
-
const emitResult = await withSpinner(
|
|
1150
|
-
() => familyInstance.emitContract({ contractIR: contractRaw }),
|
|
1151
|
-
{
|
|
1152
|
-
message: "Emitting contract...",
|
|
1153
|
-
flags
|
|
1154
|
-
}
|
|
1155
|
-
);
|
|
1156
|
-
mkdirSync(dirname2(outputJsonPath), { recursive: true });
|
|
1157
|
-
mkdirSync(dirname2(outputDtsPath), { recursive: true });
|
|
1158
|
-
writeFileSync(outputJsonPath, emitResult.contractJson, "utf-8");
|
|
1159
|
-
writeFileSync(outputDtsPath, emitResult.contractDts, "utf-8");
|
|
1160
|
-
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
1161
|
-
console.log("");
|
|
1162
|
-
}
|
|
1163
|
-
return {
|
|
1164
|
-
coreHash: emitResult.coreHash,
|
|
1165
|
-
profileHash: emitResult.profileHash,
|
|
1166
|
-
outDir: dirname2(outputJsonPath),
|
|
1167
|
-
files: {
|
|
1168
|
-
json: outputJsonPath,
|
|
1169
|
-
dts: outputDtsPath
|
|
1170
|
-
},
|
|
1171
|
-
timings: {
|
|
1172
|
-
total: 0
|
|
1173
|
-
// Timing is handled by emitContract internally if needed
|
|
1174
|
-
}
|
|
1175
|
-
};
|
|
1176
|
-
});
|
|
1914
|
+
const startTime = Date.now();
|
|
1915
|
+
const result = await executeContractEmitCommand(options, flags, startTime);
|
|
1177
1916
|
const exitCode = handleResult(result, flags, (emitResult) => {
|
|
1178
1917
|
if (flags.json === "object") {
|
|
1179
1918
|
console.log(formatEmitJson(emitResult));
|
|
@@ -1195,94 +1934,187 @@ function createContractEmitCommand() {
|
|
|
1195
1934
|
// src/commands/db-init.ts
|
|
1196
1935
|
import { readFile } from "fs/promises";
|
|
1197
1936
|
import { relative as relative3, resolve as resolve3 } from "path";
|
|
1198
|
-
import {
|
|
1937
|
+
import { notOk as notOk4, ok as ok4 } from "@prisma-next/utils/result";
|
|
1199
1938
|
import { Command as Command2 } from "commander";
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
why: `Framework component at index ${i} must be an object`
|
|
1211
|
-
});
|
|
1212
|
-
}
|
|
1213
|
-
const record = component;
|
|
1214
|
-
if (!Object.hasOwn(record, "kind")) {
|
|
1215
|
-
throw errorConfigValidation("frameworkComponents[].kind", {
|
|
1216
|
-
why: `Framework component at index ${i} must have 'kind' property`
|
|
1217
|
-
});
|
|
1218
|
-
}
|
|
1219
|
-
const kind = record["kind"];
|
|
1220
|
-
if (kind !== "target" && kind !== "adapter" && kind !== "extension" && kind !== "driver") {
|
|
1221
|
-
throw errorConfigValidation("frameworkComponents[].kind", {
|
|
1222
|
-
why: `Framework component at index ${i} has invalid kind '${String(kind)}' (must be 'target', 'adapter', 'extension', or 'driver')`
|
|
1223
|
-
});
|
|
1939
|
+
function mapDbInitFailure(failure) {
|
|
1940
|
+
if (failure.code === "PLANNING_FAILED") {
|
|
1941
|
+
return errorMigrationPlanningFailed({ conflicts: failure.conflicts ?? [] });
|
|
1942
|
+
}
|
|
1943
|
+
if (failure.code === "MARKER_ORIGIN_MISMATCH") {
|
|
1944
|
+
const mismatchParts = [];
|
|
1945
|
+
if (failure.marker?.coreHash !== failure.destination?.coreHash && failure.marker?.coreHash && failure.destination?.coreHash) {
|
|
1946
|
+
mismatchParts.push(
|
|
1947
|
+
`coreHash (marker: ${failure.marker.coreHash}, destination: ${failure.destination.coreHash})`
|
|
1948
|
+
);
|
|
1224
1949
|
}
|
|
1225
|
-
if (
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1950
|
+
if (failure.marker?.profileHash !== failure.destination?.profileHash && failure.marker?.profileHash && failure.destination?.profileHash) {
|
|
1951
|
+
mismatchParts.push(
|
|
1952
|
+
`profileHash (marker: ${failure.marker.profileHash}, destination: ${failure.destination.profileHash})`
|
|
1953
|
+
);
|
|
1229
1954
|
}
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
why:
|
|
1234
|
-
|
|
1955
|
+
return errorRuntime(
|
|
1956
|
+
`Existing contract marker does not match plan destination.${mismatchParts.length > 0 ? ` Mismatch in ${mismatchParts.join(" and ")}.` : ""}`,
|
|
1957
|
+
{
|
|
1958
|
+
why: "Database has an existing contract marker that does not match the target contract",
|
|
1959
|
+
fix: "If bootstrapping, drop/reset the database then re-run `prisma-next db init`; otherwise reconcile schema/marker using your migration workflow",
|
|
1960
|
+
meta: {
|
|
1961
|
+
code: "MARKER_ORIGIN_MISMATCH",
|
|
1962
|
+
...failure.marker?.coreHash ? { markerCoreHash: failure.marker.coreHash } : {},
|
|
1963
|
+
...failure.destination?.coreHash ? { destinationCoreHash: failure.destination.coreHash } : {},
|
|
1964
|
+
...failure.marker?.profileHash ? { markerProfileHash: failure.marker.profileHash } : {},
|
|
1965
|
+
...failure.destination?.profileHash ? { destinationProfileHash: failure.destination.profileHash } : {}
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
);
|
|
1969
|
+
}
|
|
1970
|
+
if (failure.code === "RUNNER_FAILED") {
|
|
1971
|
+
return errorRuntime(failure.summary, {
|
|
1972
|
+
why: failure.why ?? "Migration runner failed",
|
|
1973
|
+
fix: "Fix the schema mismatch (db init is additive-only), or drop/reset the database and re-run `prisma-next db init`",
|
|
1974
|
+
meta: {
|
|
1975
|
+
code: "RUNNER_FAILED",
|
|
1976
|
+
...failure.meta ?? {}
|
|
1977
|
+
}
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
const exhaustive = failure.code;
|
|
1981
|
+
throw new Error(`Unhandled DbInitFailure code: ${exhaustive}`);
|
|
1982
|
+
}
|
|
1983
|
+
async function executeDbInitCommand(options, flags, startTime) {
|
|
1984
|
+
const config = await loadConfig(options.config);
|
|
1985
|
+
const configPath = options.config ? relative3(process.cwd(), resolve3(options.config)) : "prisma-next.config.ts";
|
|
1986
|
+
const contractPathAbsolute = config.contract?.output ? resolve3(config.contract.output) : resolve3("src/prisma/contract.json");
|
|
1987
|
+
const contractPath = relative3(process.cwd(), contractPathAbsolute);
|
|
1988
|
+
if (flags.json !== "object" && !flags.quiet) {
|
|
1989
|
+
const details = [
|
|
1990
|
+
{ label: "config", value: configPath },
|
|
1991
|
+
{ label: "contract", value: contractPath }
|
|
1992
|
+
];
|
|
1993
|
+
if (options.db) {
|
|
1994
|
+
details.push({ label: "database", value: options.db });
|
|
1235
1995
|
}
|
|
1236
|
-
if (
|
|
1237
|
-
|
|
1238
|
-
why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'targetId' property`
|
|
1239
|
-
});
|
|
1996
|
+
if (options.plan) {
|
|
1997
|
+
details.push({ label: "mode", value: "plan (dry run)" });
|
|
1240
1998
|
}
|
|
1241
|
-
const
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1999
|
+
const header = formatStyledHeader({
|
|
2000
|
+
command: "db init",
|
|
2001
|
+
description: "Bootstrap a database to match the current contract",
|
|
2002
|
+
url: "https://pris.ly/db-init",
|
|
2003
|
+
details,
|
|
2004
|
+
flags
|
|
2005
|
+
});
|
|
2006
|
+
console.log(header);
|
|
2007
|
+
}
|
|
2008
|
+
let contractJsonContent;
|
|
2009
|
+
try {
|
|
2010
|
+
contractJsonContent = await readFile(contractPathAbsolute, "utf-8");
|
|
2011
|
+
} catch (error) {
|
|
2012
|
+
if (error instanceof Error && error.code === "ENOENT") {
|
|
2013
|
+
return notOk4(
|
|
2014
|
+
errorFileNotFound(contractPathAbsolute, {
|
|
2015
|
+
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
2016
|
+
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
|
|
2017
|
+
})
|
|
2018
|
+
);
|
|
1246
2019
|
}
|
|
2020
|
+
return notOk4(
|
|
2021
|
+
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2022
|
+
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
2023
|
+
})
|
|
2024
|
+
);
|
|
1247
2025
|
}
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
for (const extension of extensionPacks ?? []) {
|
|
1259
|
-
providedComponentIds.add(extension.id);
|
|
1260
|
-
}
|
|
1261
|
-
const result = checkContractComponentRequirements({
|
|
1262
|
-
contract,
|
|
1263
|
-
expectedTargetFamily: family.familyId,
|
|
1264
|
-
expectedTargetId: target.targetId,
|
|
1265
|
-
providedComponentIds
|
|
1266
|
-
});
|
|
1267
|
-
if (result.familyMismatch) {
|
|
1268
|
-
throw errorConfigValidation("contract.targetFamily", {
|
|
1269
|
-
why: `Contract was emitted for family '${result.familyMismatch.actual}' but CLI config is wired to '${result.familyMismatch.expected}'.`
|
|
1270
|
-
});
|
|
2026
|
+
let contractJson;
|
|
2027
|
+
try {
|
|
2028
|
+
contractJson = JSON.parse(contractJsonContent);
|
|
2029
|
+
} catch (error) {
|
|
2030
|
+
return notOk4(
|
|
2031
|
+
errorContractValidationFailed(
|
|
2032
|
+
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
2033
|
+
{ where: { path: contractPathAbsolute } }
|
|
2034
|
+
)
|
|
2035
|
+
);
|
|
1271
2036
|
}
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
2037
|
+
const dbConnection = options.db ?? config.db?.connection;
|
|
2038
|
+
if (!dbConnection) {
|
|
2039
|
+
return notOk4(
|
|
2040
|
+
errorDatabaseConnectionRequired({
|
|
2041
|
+
why: `Database connection is required for db init (set db.connection in ${configPath}, or pass --db <url>)`
|
|
2042
|
+
})
|
|
2043
|
+
);
|
|
1276
2044
|
}
|
|
1277
|
-
if (
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
2045
|
+
if (!config.driver) {
|
|
2046
|
+
return notOk4(errorDriverRequired({ why: "Config.driver is required for db init" }));
|
|
2047
|
+
}
|
|
2048
|
+
if (!config.target.migrations) {
|
|
2049
|
+
return notOk4(
|
|
2050
|
+
errorTargetMigrationNotSupported({
|
|
2051
|
+
why: `Target "${config.target.id}" does not support migrations`
|
|
2052
|
+
})
|
|
2053
|
+
);
|
|
2054
|
+
}
|
|
2055
|
+
const client = createControlClient({
|
|
2056
|
+
family: config.family,
|
|
2057
|
+
target: config.target,
|
|
2058
|
+
adapter: config.adapter,
|
|
2059
|
+
driver: config.driver,
|
|
2060
|
+
extensionPacks: config.extensionPacks ?? []
|
|
2061
|
+
});
|
|
2062
|
+
const onProgress = createProgressAdapter({ flags });
|
|
2063
|
+
try {
|
|
2064
|
+
const result = await client.dbInit({
|
|
2065
|
+
contractIR: contractJson,
|
|
2066
|
+
mode: options.plan ? "plan" : "apply",
|
|
2067
|
+
connection: dbConnection,
|
|
2068
|
+
onProgress
|
|
1281
2069
|
});
|
|
2070
|
+
if (!result.ok) {
|
|
2071
|
+
return notOk4(mapDbInitFailure(result.failure));
|
|
2072
|
+
}
|
|
2073
|
+
const profileHash = result.value.marker?.profileHash;
|
|
2074
|
+
const dbInitResult = {
|
|
2075
|
+
ok: true,
|
|
2076
|
+
mode: result.value.mode,
|
|
2077
|
+
plan: {
|
|
2078
|
+
targetId: config.target.targetId,
|
|
2079
|
+
destination: {
|
|
2080
|
+
coreHash: result.value.marker?.coreHash ?? "",
|
|
2081
|
+
...profileHash ? { profileHash } : {}
|
|
2082
|
+
},
|
|
2083
|
+
operations: result.value.plan.operations.map((op) => ({
|
|
2084
|
+
id: op.id,
|
|
2085
|
+
label: op.label,
|
|
2086
|
+
operationClass: op.operationClass
|
|
2087
|
+
}))
|
|
2088
|
+
},
|
|
2089
|
+
...result.value.execution ? {
|
|
2090
|
+
execution: {
|
|
2091
|
+
operationsPlanned: result.value.execution.operationsPlanned,
|
|
2092
|
+
operationsExecuted: result.value.execution.operationsExecuted
|
|
2093
|
+
}
|
|
2094
|
+
} : {},
|
|
2095
|
+
...result.value.marker ? {
|
|
2096
|
+
marker: {
|
|
2097
|
+
coreHash: result.value.marker.coreHash,
|
|
2098
|
+
...result.value.marker.profileHash ? { profileHash: result.value.marker.profileHash } : {}
|
|
2099
|
+
}
|
|
2100
|
+
} : {},
|
|
2101
|
+
summary: result.value.summary,
|
|
2102
|
+
timings: { total: Date.now() - startTime }
|
|
2103
|
+
};
|
|
2104
|
+
return ok4(dbInitResult);
|
|
2105
|
+
} catch (error) {
|
|
2106
|
+
if (CliStructuredError.is(error)) {
|
|
2107
|
+
return notOk4(error);
|
|
2108
|
+
}
|
|
2109
|
+
return notOk4(
|
|
2110
|
+
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2111
|
+
why: `Unexpected error during db init: ${error instanceof Error ? error.message : String(error)}`
|
|
2112
|
+
})
|
|
2113
|
+
);
|
|
2114
|
+
} finally {
|
|
2115
|
+
await client.close();
|
|
1282
2116
|
}
|
|
1283
2117
|
}
|
|
1284
|
-
|
|
1285
|
-
// src/commands/db-init.ts
|
|
1286
2118
|
function createDbInitCommand() {
|
|
1287
2119
|
const command = new Command2("init");
|
|
1288
2120
|
setCommandDescriptions(
|
|
@@ -1298,280 +2130,18 @@ function createDbInitCommand() {
|
|
|
1298
2130
|
}).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)", 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) => {
|
|
1299
2131
|
const flags = parseGlobalFlags(options);
|
|
1300
2132
|
const startTime = Date.now();
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
2133
|
+
if (flags.json === "ndjson") {
|
|
2134
|
+
const result2 = notOk4(
|
|
2135
|
+
errorJsonFormatNotSupported({
|
|
1304
2136
|
command: "db init",
|
|
1305
2137
|
format: "ndjson",
|
|
1306
2138
|
supportedFormats: ["object"]
|
|
1307
|
-
})
|
|
1308
|
-
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
if (flags.json !== "object" && !flags.quiet) {
|
|
1314
|
-
const details = [
|
|
1315
|
-
{ label: "config", value: configPath },
|
|
1316
|
-
{ label: "contract", value: contractPath }
|
|
1317
|
-
];
|
|
1318
|
-
if (options.db) {
|
|
1319
|
-
details.push({ label: "database", value: options.db });
|
|
1320
|
-
}
|
|
1321
|
-
if (options.plan) {
|
|
1322
|
-
details.push({ label: "mode", value: "plan (dry run)" });
|
|
1323
|
-
}
|
|
1324
|
-
const header = formatStyledHeader({
|
|
1325
|
-
command: "db init",
|
|
1326
|
-
description: "Bootstrap a database to match the current contract",
|
|
1327
|
-
url: "https://pris.ly/db-init",
|
|
1328
|
-
details,
|
|
1329
|
-
flags
|
|
1330
|
-
});
|
|
1331
|
-
console.log(header);
|
|
1332
|
-
}
|
|
1333
|
-
let contractJsonContent;
|
|
1334
|
-
try {
|
|
1335
|
-
contractJsonContent = await readFile(contractPathAbsolute, "utf-8");
|
|
1336
|
-
} catch (error) {
|
|
1337
|
-
if (error instanceof Error && error.code === "ENOENT") {
|
|
1338
|
-
throw errorFileNotFound(contractPathAbsolute, {
|
|
1339
|
-
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
1340
|
-
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
|
|
1341
|
-
});
|
|
1342
|
-
}
|
|
1343
|
-
throw errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
1344
|
-
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
1345
|
-
});
|
|
1346
|
-
}
|
|
1347
|
-
let contractJson;
|
|
1348
|
-
try {
|
|
1349
|
-
contractJson = JSON.parse(contractJsonContent);
|
|
1350
|
-
} catch (error) {
|
|
1351
|
-
throw errorContractValidationFailed(
|
|
1352
|
-
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
1353
|
-
{ where: { path: contractPathAbsolute } }
|
|
1354
|
-
);
|
|
1355
|
-
}
|
|
1356
|
-
const dbUrl = options.db ?? config.db?.url;
|
|
1357
|
-
if (!dbUrl) {
|
|
1358
|
-
throw errorDatabaseUrlRequired({
|
|
1359
|
-
why: `Database URL is required for db init (set db.url in ${configPath}, or pass --db <url>)`
|
|
1360
|
-
});
|
|
1361
|
-
}
|
|
1362
|
-
if (!config.driver) {
|
|
1363
|
-
throw errorDriverRequired({ why: "Config.driver is required for db init" });
|
|
1364
|
-
}
|
|
1365
|
-
const driverDescriptor = config.driver;
|
|
1366
|
-
if (!config.target.migrations) {
|
|
1367
|
-
throw errorTargetMigrationNotSupported({
|
|
1368
|
-
why: `Target "${config.target.id}" does not support migrations`
|
|
1369
|
-
});
|
|
1370
|
-
}
|
|
1371
|
-
const migrations = config.target.migrations;
|
|
1372
|
-
let driver;
|
|
1373
|
-
try {
|
|
1374
|
-
driver = await withSpinner(() => driverDescriptor.create(dbUrl), {
|
|
1375
|
-
message: "Connecting to database...",
|
|
1376
|
-
flags
|
|
1377
|
-
});
|
|
1378
|
-
} catch (error) {
|
|
1379
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1380
|
-
const code = error.code;
|
|
1381
|
-
const redacted = redactDatabaseUrl(dbUrl);
|
|
1382
|
-
throw errorRuntime("Database connection failed", {
|
|
1383
|
-
why: message,
|
|
1384
|
-
fix: "Verify the database URL, ensure the database is reachable, and confirm credentials/permissions",
|
|
1385
|
-
meta: {
|
|
1386
|
-
...typeof code !== "undefined" ? { code } : {},
|
|
1387
|
-
...redacted
|
|
1388
|
-
}
|
|
1389
|
-
});
|
|
1390
|
-
}
|
|
1391
|
-
try {
|
|
1392
|
-
const familyInstance = config.family.create({
|
|
1393
|
-
target: config.target,
|
|
1394
|
-
adapter: config.adapter,
|
|
1395
|
-
driver: driverDescriptor,
|
|
1396
|
-
extensionPacks: config.extensionPacks ?? []
|
|
1397
|
-
});
|
|
1398
|
-
const rawComponents = [config.target, config.adapter, ...config.extensionPacks ?? []];
|
|
1399
|
-
const frameworkComponents = assertFrameworkComponentsCompatible(
|
|
1400
|
-
config.family.familyId,
|
|
1401
|
-
config.target.targetId,
|
|
1402
|
-
rawComponents
|
|
1403
|
-
);
|
|
1404
|
-
const contractIR = familyInstance.validateContractIR(contractJson);
|
|
1405
|
-
assertContractRequirementsSatisfied({
|
|
1406
|
-
contract: contractIR,
|
|
1407
|
-
family: config.family,
|
|
1408
|
-
target: config.target,
|
|
1409
|
-
adapter: config.adapter,
|
|
1410
|
-
extensionPacks: config.extensionPacks
|
|
1411
|
-
});
|
|
1412
|
-
const planner = migrations.createPlanner(familyInstance);
|
|
1413
|
-
const runner = migrations.createRunner(familyInstance);
|
|
1414
|
-
const schemaIR = await withSpinner(() => familyInstance.introspect({ driver }), {
|
|
1415
|
-
message: "Introspecting database schema...",
|
|
1416
|
-
flags
|
|
1417
|
-
});
|
|
1418
|
-
const policy = { allowedOperationClasses: ["additive"] };
|
|
1419
|
-
const plannerResult = await withSpinner(
|
|
1420
|
-
async () => planner.plan({
|
|
1421
|
-
contract: contractIR,
|
|
1422
|
-
schema: schemaIR,
|
|
1423
|
-
policy,
|
|
1424
|
-
frameworkComponents
|
|
1425
|
-
}),
|
|
1426
|
-
{
|
|
1427
|
-
message: "Planning migration...",
|
|
1428
|
-
flags
|
|
1429
|
-
}
|
|
1430
|
-
);
|
|
1431
|
-
if (plannerResult.kind === "failure") {
|
|
1432
|
-
throw errorMigrationPlanningFailed({ conflicts: plannerResult.conflicts });
|
|
1433
|
-
}
|
|
1434
|
-
const migrationPlan = plannerResult.plan;
|
|
1435
|
-
const existingMarker = await familyInstance.readMarker({ driver });
|
|
1436
|
-
if (existingMarker) {
|
|
1437
|
-
const markerMatchesDestination = existingMarker.coreHash === migrationPlan.destination.coreHash && (!migrationPlan.destination.profileHash || existingMarker.profileHash === migrationPlan.destination.profileHash);
|
|
1438
|
-
if (markerMatchesDestination) {
|
|
1439
|
-
const dbInitResult2 = {
|
|
1440
|
-
ok: true,
|
|
1441
|
-
mode: options.plan ? "plan" : "apply",
|
|
1442
|
-
plan: {
|
|
1443
|
-
targetId: migrationPlan.targetId,
|
|
1444
|
-
destination: migrationPlan.destination,
|
|
1445
|
-
operations: []
|
|
1446
|
-
},
|
|
1447
|
-
...options.plan ? {} : {
|
|
1448
|
-
execution: { operationsPlanned: 0, operationsExecuted: 0 },
|
|
1449
|
-
marker: {
|
|
1450
|
-
coreHash: existingMarker.coreHash,
|
|
1451
|
-
profileHash: existingMarker.profileHash
|
|
1452
|
-
}
|
|
1453
|
-
},
|
|
1454
|
-
summary: "Database already at target contract state",
|
|
1455
|
-
timings: { total: Date.now() - startTime }
|
|
1456
|
-
};
|
|
1457
|
-
return dbInitResult2;
|
|
1458
|
-
}
|
|
1459
|
-
const coreHashMismatch = existingMarker.coreHash !== migrationPlan.destination.coreHash;
|
|
1460
|
-
const profileHashMismatch = migrationPlan.destination.profileHash && existingMarker.profileHash !== migrationPlan.destination.profileHash;
|
|
1461
|
-
const mismatchParts = [];
|
|
1462
|
-
if (coreHashMismatch) {
|
|
1463
|
-
mismatchParts.push(
|
|
1464
|
-
`coreHash (marker: ${existingMarker.coreHash}, destination: ${migrationPlan.destination.coreHash})`
|
|
1465
|
-
);
|
|
1466
|
-
}
|
|
1467
|
-
if (profileHashMismatch) {
|
|
1468
|
-
mismatchParts.push(
|
|
1469
|
-
`profileHash (marker: ${existingMarker.profileHash}, destination: ${migrationPlan.destination.profileHash})`
|
|
1470
|
-
);
|
|
1471
|
-
}
|
|
1472
|
-
throw errorRuntime(
|
|
1473
|
-
`Existing contract marker does not match plan destination. Mismatch in ${mismatchParts.join(" and ")}.`,
|
|
1474
|
-
{
|
|
1475
|
-
why: "Database has an existing contract marker that does not match the target contract",
|
|
1476
|
-
fix: "If bootstrapping, drop/reset the database then re-run `prisma-next db init`; otherwise reconcile schema/marker using your migration workflow",
|
|
1477
|
-
meta: {
|
|
1478
|
-
code: "MARKER_ORIGIN_MISMATCH",
|
|
1479
|
-
markerCoreHash: existingMarker.coreHash,
|
|
1480
|
-
destinationCoreHash: migrationPlan.destination.coreHash,
|
|
1481
|
-
...existingMarker.profileHash ? { markerProfileHash: existingMarker.profileHash } : {},
|
|
1482
|
-
...migrationPlan.destination.profileHash ? { destinationProfileHash: migrationPlan.destination.profileHash } : {}
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
);
|
|
1486
|
-
}
|
|
1487
|
-
if (options.plan) {
|
|
1488
|
-
const dbInitResult2 = {
|
|
1489
|
-
ok: true,
|
|
1490
|
-
mode: "plan",
|
|
1491
|
-
plan: {
|
|
1492
|
-
targetId: migrationPlan.targetId,
|
|
1493
|
-
destination: migrationPlan.destination,
|
|
1494
|
-
operations: migrationPlan.operations.map((op) => ({
|
|
1495
|
-
id: op.id,
|
|
1496
|
-
label: op.label,
|
|
1497
|
-
operationClass: op.operationClass
|
|
1498
|
-
}))
|
|
1499
|
-
},
|
|
1500
|
-
summary: `Planned ${migrationPlan.operations.length} operation(s)`,
|
|
1501
|
-
timings: { total: Date.now() - startTime }
|
|
1502
|
-
};
|
|
1503
|
-
return dbInitResult2;
|
|
1504
|
-
}
|
|
1505
|
-
if (!flags.quiet && flags.json !== "object") {
|
|
1506
|
-
console.log("Applying migration plan and verifying schema...");
|
|
1507
|
-
}
|
|
1508
|
-
const callbacks = {
|
|
1509
|
-
onOperationStart: (op) => {
|
|
1510
|
-
if (!flags.quiet && flags.json !== "object") {
|
|
1511
|
-
console.log(` \u2192 ${op.label}...`);
|
|
1512
|
-
}
|
|
1513
|
-
},
|
|
1514
|
-
onOperationComplete: (_op) => {
|
|
1515
|
-
}
|
|
1516
|
-
};
|
|
1517
|
-
const runnerResult = await runner.execute({
|
|
1518
|
-
plan: migrationPlan,
|
|
1519
|
-
driver,
|
|
1520
|
-
destinationContract: contractIR,
|
|
1521
|
-
policy,
|
|
1522
|
-
callbacks,
|
|
1523
|
-
// db init plans and applies back-to-back from a fresh introspection, so per-operation
|
|
1524
|
-
// pre/postchecks and the idempotency probe are usually redundant overhead. We still
|
|
1525
|
-
// enforce marker/origin compatibility and a full schema verification after apply.
|
|
1526
|
-
executionChecks: {
|
|
1527
|
-
prechecks: false,
|
|
1528
|
-
postchecks: false,
|
|
1529
|
-
idempotencyChecks: false
|
|
1530
|
-
},
|
|
1531
|
-
frameworkComponents
|
|
1532
|
-
});
|
|
1533
|
-
if (!runnerResult.ok) {
|
|
1534
|
-
const meta = {
|
|
1535
|
-
code: runnerResult.failure.code,
|
|
1536
|
-
...runnerResult.failure.meta ?? {}
|
|
1537
|
-
};
|
|
1538
|
-
const sqlState = typeof meta["sqlState"] === "string" ? meta["sqlState"] : void 0;
|
|
1539
|
-
const fix = sqlState === "42501" ? "Grant the database user sufficient privileges (insufficient_privilege), or run db init as a more privileged role" : runnerResult.failure.code === "SCHEMA_VERIFY_FAILED" ? "Fix the schema mismatch (db init is additive-only), or drop/reset the database and re-run `prisma-next db init`" : void 0;
|
|
1540
|
-
throw errorRuntime(runnerResult.failure.summary, {
|
|
1541
|
-
why: runnerResult.failure.why ?? `Migration runner failed: ${runnerResult.failure.code}`,
|
|
1542
|
-
...fix ? { fix } : {},
|
|
1543
|
-
meta
|
|
1544
|
-
});
|
|
1545
|
-
}
|
|
1546
|
-
const execution = runnerResult.value;
|
|
1547
|
-
const dbInitResult = {
|
|
1548
|
-
ok: true,
|
|
1549
|
-
mode: "apply",
|
|
1550
|
-
plan: {
|
|
1551
|
-
targetId: migrationPlan.targetId,
|
|
1552
|
-
destination: migrationPlan.destination,
|
|
1553
|
-
operations: migrationPlan.operations.map((op) => ({
|
|
1554
|
-
id: op.id,
|
|
1555
|
-
label: op.label,
|
|
1556
|
-
operationClass: op.operationClass
|
|
1557
|
-
}))
|
|
1558
|
-
},
|
|
1559
|
-
execution: {
|
|
1560
|
-
operationsPlanned: execution.operationsPlanned,
|
|
1561
|
-
operationsExecuted: execution.operationsExecuted
|
|
1562
|
-
},
|
|
1563
|
-
marker: migrationPlan.destination.profileHash ? {
|
|
1564
|
-
coreHash: migrationPlan.destination.coreHash,
|
|
1565
|
-
profileHash: migrationPlan.destination.profileHash
|
|
1566
|
-
} : { coreHash: migrationPlan.destination.coreHash },
|
|
1567
|
-
summary: `Applied ${execution.operationsExecuted} operation(s), marker written`,
|
|
1568
|
-
timings: { total: Date.now() - startTime }
|
|
1569
|
-
};
|
|
1570
|
-
return dbInitResult;
|
|
1571
|
-
} finally {
|
|
1572
|
-
await driver.close();
|
|
1573
|
-
}
|
|
1574
|
-
});
|
|
2139
|
+
})
|
|
2140
|
+
);
|
|
2141
|
+
const exitCode2 = handleResult(result2, flags);
|
|
2142
|
+
process.exit(exitCode2);
|
|
2143
|
+
}
|
|
2144
|
+
const result = await executeDbInitCommand(options, flags, startTime);
|
|
1575
2145
|
const exitCode = handleResult(result, flags, (dbInitResult) => {
|
|
1576
2146
|
if (flags.json === "object") {
|
|
1577
2147
|
console.log(formatDbInitJson(dbInitResult));
|
|
@@ -1588,15 +2158,92 @@ function createDbInitCommand() {
|
|
|
1588
2158
|
}
|
|
1589
2159
|
|
|
1590
2160
|
// src/commands/db-introspect.ts
|
|
1591
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
1592
2161
|
import { relative as relative4, resolve as resolve4 } from "path";
|
|
1593
|
-
import {
|
|
1594
|
-
errorDatabaseUrlRequired as errorDatabaseUrlRequired2,
|
|
1595
|
-
errorDriverRequired as errorDriverRequired2,
|
|
1596
|
-
errorRuntime as errorRuntime2,
|
|
1597
|
-
errorUnexpected as errorUnexpected3
|
|
1598
|
-
} from "@prisma-next/core-control-plane/errors";
|
|
2162
|
+
import { notOk as notOk5, ok as ok5 } from "@prisma-next/utils/result";
|
|
1599
2163
|
import { Command as Command3 } from "commander";
|
|
2164
|
+
async function executeDbIntrospectCommand(options, flags, startTime) {
|
|
2165
|
+
const config = await loadConfig(options.config);
|
|
2166
|
+
const configPath = options.config ? relative4(process.cwd(), resolve4(options.config)) : "prisma-next.config.ts";
|
|
2167
|
+
if (flags.json !== "object" && !flags.quiet) {
|
|
2168
|
+
const details = [
|
|
2169
|
+
{ label: "config", value: configPath }
|
|
2170
|
+
];
|
|
2171
|
+
if (options.db) {
|
|
2172
|
+
const maskedUrl = options.db.replace(/:([^:@]+)@/, ":****@");
|
|
2173
|
+
details.push({ label: "database", value: maskedUrl });
|
|
2174
|
+
} else if (config.db?.connection && typeof config.db.connection === "string") {
|
|
2175
|
+
const maskedUrl = config.db.connection.replace(/:([^:@]+)@/, ":****@");
|
|
2176
|
+
details.push({ label: "database", value: maskedUrl });
|
|
2177
|
+
}
|
|
2178
|
+
const header = formatStyledHeader({
|
|
2179
|
+
command: "db introspect",
|
|
2180
|
+
description: "Inspect the database schema",
|
|
2181
|
+
url: "https://pris.ly/db-introspect",
|
|
2182
|
+
details,
|
|
2183
|
+
flags
|
|
2184
|
+
});
|
|
2185
|
+
console.log(header);
|
|
2186
|
+
}
|
|
2187
|
+
const dbConnection = options.db ?? config.db?.connection;
|
|
2188
|
+
if (!dbConnection) {
|
|
2189
|
+
return notOk5(
|
|
2190
|
+
errorDatabaseConnectionRequired({
|
|
2191
|
+
why: `Database connection is required for db introspect (set db.connection in ${configPath}, or pass --db <url>)`
|
|
2192
|
+
})
|
|
2193
|
+
);
|
|
2194
|
+
}
|
|
2195
|
+
if (!config.driver) {
|
|
2196
|
+
return notOk5(errorDriverRequired({ why: "Config.driver is required for db introspect" }));
|
|
2197
|
+
}
|
|
2198
|
+
const client = createControlClient({
|
|
2199
|
+
family: config.family,
|
|
2200
|
+
target: config.target,
|
|
2201
|
+
adapter: config.adapter,
|
|
2202
|
+
driver: config.driver,
|
|
2203
|
+
extensionPacks: config.extensionPacks ?? []
|
|
2204
|
+
});
|
|
2205
|
+
const onProgress = createProgressAdapter({ flags });
|
|
2206
|
+
try {
|
|
2207
|
+
const schemaIR = await client.introspect({
|
|
2208
|
+
connection: dbConnection,
|
|
2209
|
+
onProgress
|
|
2210
|
+
});
|
|
2211
|
+
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
2212
|
+
console.log("");
|
|
2213
|
+
}
|
|
2214
|
+
const schemaView = client.toSchemaView(schemaIR);
|
|
2215
|
+
const totalTime = Date.now() - startTime;
|
|
2216
|
+
const connectionForMeta = typeof dbConnection === "string" ? dbConnection.replace(/:([^:@]+)@/, ":****@") : void 0;
|
|
2217
|
+
const introspectResult = {
|
|
2218
|
+
ok: true,
|
|
2219
|
+
summary: "Schema introspected successfully",
|
|
2220
|
+
target: {
|
|
2221
|
+
familyId: config.family.familyId,
|
|
2222
|
+
id: config.target.targetId
|
|
2223
|
+
},
|
|
2224
|
+
schema: schemaIR,
|
|
2225
|
+
meta: {
|
|
2226
|
+
...configPath ? { configPath } : {},
|
|
2227
|
+
...connectionForMeta ? { dbUrl: connectionForMeta } : {}
|
|
2228
|
+
},
|
|
2229
|
+
timings: {
|
|
2230
|
+
total: totalTime
|
|
2231
|
+
}
|
|
2232
|
+
};
|
|
2233
|
+
return ok5({ introspectResult, schemaView });
|
|
2234
|
+
} catch (error) {
|
|
2235
|
+
if (error instanceof CliStructuredError) {
|
|
2236
|
+
return notOk5(error);
|
|
2237
|
+
}
|
|
2238
|
+
return notOk5(
|
|
2239
|
+
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2240
|
+
why: `Unexpected error during db introspect: ${error instanceof Error ? error.message : String(error)}`
|
|
2241
|
+
})
|
|
2242
|
+
);
|
|
2243
|
+
} finally {
|
|
2244
|
+
await client.close();
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
1600
2247
|
function createDbIntrospectCommand() {
|
|
1601
2248
|
const command = new Command3("introspect");
|
|
1602
2249
|
setCommandDescriptions(
|
|
@@ -1609,137 +2256,21 @@ function createDbIntrospectCommand() {
|
|
|
1609
2256
|
const flags = parseGlobalFlags({});
|
|
1610
2257
|
return formatCommandHelp({ command: cmd, flags });
|
|
1611
2258
|
}
|
|
1612
|
-
}).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--json [format]", "Output as JSON (object
|
|
2259
|
+
}).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--json [format]", "Output as JSON (object)", 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) => {
|
|
1613
2260
|
const flags = parseGlobalFlags(options);
|
|
1614
|
-
const
|
|
1615
|
-
|
|
1616
|
-
const
|
|
1617
|
-
|
|
1618
|
-
let contractIR;
|
|
1619
|
-
if (config.contract?.output) {
|
|
1620
|
-
const contractPath = resolve4(config.contract.output);
|
|
1621
|
-
try {
|
|
1622
|
-
const contractJsonContent = await readFile2(contractPath, "utf-8");
|
|
1623
|
-
contractIR = JSON.parse(contractJsonContent);
|
|
1624
|
-
} catch (error) {
|
|
1625
|
-
if (error instanceof Error && error.code !== "ENOENT") {
|
|
1626
|
-
throw errorUnexpected3(error.message, {
|
|
1627
|
-
why: `Failed to read contract file: ${error.message}`
|
|
1628
|
-
});
|
|
1629
|
-
}
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
if (flags.json !== "object" && !flags.quiet) {
|
|
1633
|
-
const details = [
|
|
1634
|
-
{ label: "config", value: configPath }
|
|
1635
|
-
];
|
|
1636
|
-
if (options.db) {
|
|
1637
|
-
const maskedUrl = options.db.replace(/:([^:@]+)@/, ":****@");
|
|
1638
|
-
details.push({ label: "database", value: maskedUrl });
|
|
1639
|
-
} else if (config.db?.url) {
|
|
1640
|
-
const maskedUrl = config.db.url.replace(/:([^:@]+)@/, ":****@");
|
|
1641
|
-
details.push({ label: "database", value: maskedUrl });
|
|
1642
|
-
}
|
|
1643
|
-
const header = formatStyledHeader({
|
|
2261
|
+
const startTime = Date.now();
|
|
2262
|
+
if (flags.json === "ndjson") {
|
|
2263
|
+
const result2 = notOk5(
|
|
2264
|
+
errorJsonFormatNotSupported({
|
|
1644
2265
|
command: "db introspect",
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
if (!dbUrl) {
|
|
1654
|
-
throw errorDatabaseUrlRequired2();
|
|
1655
|
-
}
|
|
1656
|
-
if (!config.driver) {
|
|
1657
|
-
throw errorDriverRequired2();
|
|
1658
|
-
}
|
|
1659
|
-
const driverDescriptor = config.driver;
|
|
1660
|
-
const driver = await withSpinner(() => driverDescriptor.create(dbUrl), {
|
|
1661
|
-
message: "Connecting to database...",
|
|
1662
|
-
flags
|
|
1663
|
-
});
|
|
1664
|
-
try {
|
|
1665
|
-
const familyInstance = config.family.create({
|
|
1666
|
-
target: config.target,
|
|
1667
|
-
adapter: config.adapter,
|
|
1668
|
-
driver: driverDescriptor,
|
|
1669
|
-
extensionPacks: config.extensionPacks ?? []
|
|
1670
|
-
});
|
|
1671
|
-
if (contractIR) {
|
|
1672
|
-
const validatedContract = familyInstance.validateContractIR(contractIR);
|
|
1673
|
-
assertContractRequirementsSatisfied({
|
|
1674
|
-
contract: validatedContract,
|
|
1675
|
-
family: config.family,
|
|
1676
|
-
target: config.target,
|
|
1677
|
-
adapter: config.adapter,
|
|
1678
|
-
extensionPacks: config.extensionPacks
|
|
1679
|
-
});
|
|
1680
|
-
contractIR = validatedContract;
|
|
1681
|
-
}
|
|
1682
|
-
let schemaIR;
|
|
1683
|
-
try {
|
|
1684
|
-
schemaIR = await withSpinner(
|
|
1685
|
-
() => familyInstance.introspect({
|
|
1686
|
-
driver,
|
|
1687
|
-
contractIR
|
|
1688
|
-
}),
|
|
1689
|
-
{
|
|
1690
|
-
message: "Introspecting database schema...",
|
|
1691
|
-
flags
|
|
1692
|
-
}
|
|
1693
|
-
);
|
|
1694
|
-
} catch (error) {
|
|
1695
|
-
throw errorRuntime2(error instanceof Error ? error.message : String(error), {
|
|
1696
|
-
why: `Failed to introspect database: ${error instanceof Error ? error.message : String(error)}`
|
|
1697
|
-
});
|
|
1698
|
-
}
|
|
1699
|
-
let schemaView;
|
|
1700
|
-
if (familyInstance.toSchemaView) {
|
|
1701
|
-
try {
|
|
1702
|
-
schemaView = familyInstance.toSchemaView(schemaIR);
|
|
1703
|
-
} catch (error) {
|
|
1704
|
-
if (flags.verbose) {
|
|
1705
|
-
console.error(
|
|
1706
|
-
`Warning: Failed to project schema to view: ${error instanceof Error ? error.message : String(error)}`
|
|
1707
|
-
);
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
|
-
}
|
|
1711
|
-
const totalTime = Date.now() - startTime;
|
|
1712
|
-
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
1713
|
-
console.log("");
|
|
1714
|
-
}
|
|
1715
|
-
const introspectResult = {
|
|
1716
|
-
ok: true,
|
|
1717
|
-
summary: "Schema introspected successfully",
|
|
1718
|
-
target: {
|
|
1719
|
-
familyId: config.family.familyId,
|
|
1720
|
-
id: config.target.targetId
|
|
1721
|
-
},
|
|
1722
|
-
schema: schemaIR,
|
|
1723
|
-
...configPath || options.db || config.db?.url ? {
|
|
1724
|
-
meta: {
|
|
1725
|
-
...configPath ? { configPath } : {},
|
|
1726
|
-
...options.db || config.db?.url ? {
|
|
1727
|
-
dbUrl: (options.db ?? config.db?.url ?? "").replace(
|
|
1728
|
-
/:([^:@]+)@/,
|
|
1729
|
-
":****@"
|
|
1730
|
-
)
|
|
1731
|
-
} : {}
|
|
1732
|
-
}
|
|
1733
|
-
} : {},
|
|
1734
|
-
timings: {
|
|
1735
|
-
total: totalTime
|
|
1736
|
-
}
|
|
1737
|
-
};
|
|
1738
|
-
return { introspectResult, schemaView };
|
|
1739
|
-
} finally {
|
|
1740
|
-
await driver.close();
|
|
1741
|
-
}
|
|
1742
|
-
});
|
|
2266
|
+
format: "ndjson",
|
|
2267
|
+
supportedFormats: ["object"]
|
|
2268
|
+
})
|
|
2269
|
+
);
|
|
2270
|
+
const exitCode2 = handleResult(result2, flags);
|
|
2271
|
+
process.exit(exitCode2);
|
|
2272
|
+
}
|
|
2273
|
+
const result = await executeDbIntrospectCommand(options, flags, startTime);
|
|
1743
2274
|
const exitCode = handleResult(result, flags, (value) => {
|
|
1744
2275
|
const { introspectResult, schemaView } = value;
|
|
1745
2276
|
if (flags.json === "object") {
|
|
@@ -1757,16 +2288,104 @@ function createDbIntrospectCommand() {
|
|
|
1757
2288
|
}
|
|
1758
2289
|
|
|
1759
2290
|
// src/commands/db-schema-verify.ts
|
|
1760
|
-
import { readFile as
|
|
2291
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
1761
2292
|
import { relative as relative5, resolve as resolve5 } from "path";
|
|
1762
|
-
import {
|
|
1763
|
-
errorDatabaseUrlRequired as errorDatabaseUrlRequired3,
|
|
1764
|
-
errorDriverRequired as errorDriverRequired3,
|
|
1765
|
-
errorFileNotFound as errorFileNotFound2,
|
|
1766
|
-
errorRuntime as errorRuntime3,
|
|
1767
|
-
errorUnexpected as errorUnexpected4
|
|
1768
|
-
} from "@prisma-next/core-control-plane/errors";
|
|
2293
|
+
import { notOk as notOk6, ok as ok6 } from "@prisma-next/utils/result";
|
|
1769
2294
|
import { Command as Command4 } from "commander";
|
|
2295
|
+
async function executeDbSchemaVerifyCommand(options, flags) {
|
|
2296
|
+
const config = await loadConfig(options.config);
|
|
2297
|
+
const configPath = options.config ? relative5(process.cwd(), resolve5(options.config)) : "prisma-next.config.ts";
|
|
2298
|
+
const contractPathAbsolute = config.contract?.output ? resolve5(config.contract.output) : resolve5("src/prisma/contract.json");
|
|
2299
|
+
const contractPath = relative5(process.cwd(), contractPathAbsolute);
|
|
2300
|
+
if (flags.json !== "object" && !flags.quiet) {
|
|
2301
|
+
const details = [
|
|
2302
|
+
{ label: "config", value: configPath },
|
|
2303
|
+
{ label: "contract", value: contractPath }
|
|
2304
|
+
];
|
|
2305
|
+
if (options.db) {
|
|
2306
|
+
details.push({ label: "database", value: options.db });
|
|
2307
|
+
}
|
|
2308
|
+
const header = formatStyledHeader({
|
|
2309
|
+
command: "db schema-verify",
|
|
2310
|
+
description: "Check whether the database schema satisfies your contract",
|
|
2311
|
+
url: "https://pris.ly/db-schema-verify",
|
|
2312
|
+
details,
|
|
2313
|
+
flags
|
|
2314
|
+
});
|
|
2315
|
+
console.log(header);
|
|
2316
|
+
}
|
|
2317
|
+
let contractJsonContent;
|
|
2318
|
+
try {
|
|
2319
|
+
contractJsonContent = await readFile2(contractPathAbsolute, "utf-8");
|
|
2320
|
+
} catch (error) {
|
|
2321
|
+
if (error instanceof Error && error.code === "ENOENT") {
|
|
2322
|
+
return notOk6(
|
|
2323
|
+
errorFileNotFound(contractPathAbsolute, {
|
|
2324
|
+
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
2325
|
+
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
|
|
2326
|
+
})
|
|
2327
|
+
);
|
|
2328
|
+
}
|
|
2329
|
+
return notOk6(
|
|
2330
|
+
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2331
|
+
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
2332
|
+
})
|
|
2333
|
+
);
|
|
2334
|
+
}
|
|
2335
|
+
let contractJson;
|
|
2336
|
+
try {
|
|
2337
|
+
contractJson = JSON.parse(contractJsonContent);
|
|
2338
|
+
} catch (error) {
|
|
2339
|
+
return notOk6(
|
|
2340
|
+
errorContractValidationFailed(
|
|
2341
|
+
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
2342
|
+
{ where: { path: contractPathAbsolute } }
|
|
2343
|
+
)
|
|
2344
|
+
);
|
|
2345
|
+
}
|
|
2346
|
+
const dbConnection = options.db ?? config.db?.connection;
|
|
2347
|
+
if (!dbConnection) {
|
|
2348
|
+
return notOk6(
|
|
2349
|
+
errorDatabaseConnectionRequired({
|
|
2350
|
+
why: `Database connection is required for db schema-verify (set db.connection in ${configPath}, or pass --db <url>)`
|
|
2351
|
+
})
|
|
2352
|
+
);
|
|
2353
|
+
}
|
|
2354
|
+
if (!config.driver) {
|
|
2355
|
+
return notOk6(errorDriverRequired({ why: "Config.driver is required for db schema-verify" }));
|
|
2356
|
+
}
|
|
2357
|
+
const client = createControlClient({
|
|
2358
|
+
family: config.family,
|
|
2359
|
+
target: config.target,
|
|
2360
|
+
adapter: config.adapter,
|
|
2361
|
+
driver: config.driver,
|
|
2362
|
+
extensionPacks: config.extensionPacks ?? []
|
|
2363
|
+
});
|
|
2364
|
+
const onProgress = createProgressAdapter({ flags });
|
|
2365
|
+
try {
|
|
2366
|
+
const schemaVerifyResult = await client.schemaVerify({
|
|
2367
|
+
contractIR: contractJson,
|
|
2368
|
+
strict: options.strict ?? false,
|
|
2369
|
+
connection: dbConnection,
|
|
2370
|
+
onProgress
|
|
2371
|
+
});
|
|
2372
|
+
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
2373
|
+
console.log("");
|
|
2374
|
+
}
|
|
2375
|
+
return ok6(schemaVerifyResult);
|
|
2376
|
+
} catch (error) {
|
|
2377
|
+
if (error instanceof CliStructuredError) {
|
|
2378
|
+
return notOk6(error);
|
|
2379
|
+
}
|
|
2380
|
+
return notOk6(
|
|
2381
|
+
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2382
|
+
why: `Unexpected error during db schema-verify: ${error instanceof Error ? error.message : String(error)}`
|
|
2383
|
+
})
|
|
2384
|
+
);
|
|
2385
|
+
} finally {
|
|
2386
|
+
await client.close();
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
1770
2389
|
function createDbSchemaVerifyCommand() {
|
|
1771
2390
|
const command = new Command4("schema-verify");
|
|
1772
2391
|
setCommandDescriptions(
|
|
@@ -1779,106 +2398,20 @@ function createDbSchemaVerifyCommand() {
|
|
|
1779
2398
|
const flags = parseGlobalFlags({});
|
|
1780
2399
|
return formatCommandHelp({ command: cmd, flags });
|
|
1781
2400
|
}
|
|
1782
|
-
}).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--json [format]", "Output as JSON (object
|
|
2401
|
+
}).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--json [format]", "Output as JSON (object)", false).option("--strict", "Strict mode: extra schema elements cause failures", 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) => {
|
|
1783
2402
|
const flags = parseGlobalFlags(options);
|
|
1784
|
-
|
|
1785
|
-
const
|
|
1786
|
-
|
|
1787
|
-
const contractPathAbsolute = config.contract?.output ? resolve5(config.contract.output) : resolve5("src/prisma/contract.json");
|
|
1788
|
-
const contractPath = relative5(process.cwd(), contractPathAbsolute);
|
|
1789
|
-
if (flags.json !== "object" && !flags.quiet) {
|
|
1790
|
-
const details = [
|
|
1791
|
-
{ label: "config", value: configPath },
|
|
1792
|
-
{ label: "contract", value: contractPath }
|
|
1793
|
-
];
|
|
1794
|
-
if (options.db) {
|
|
1795
|
-
details.push({ label: "database", value: options.db });
|
|
1796
|
-
}
|
|
1797
|
-
const header = formatStyledHeader({
|
|
2403
|
+
if (flags.json === "ndjson") {
|
|
2404
|
+
const result2 = notOk6(
|
|
2405
|
+
errorJsonFormatNotSupported({
|
|
1798
2406
|
command: "db schema-verify",
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
try {
|
|
1808
|
-
contractJsonContent = await readFile3(contractPathAbsolute, "utf-8");
|
|
1809
|
-
} catch (error) {
|
|
1810
|
-
if (error instanceof Error && error.code === "ENOENT") {
|
|
1811
|
-
throw errorFileNotFound2(contractPathAbsolute, {
|
|
1812
|
-
why: `Contract file not found at ${contractPathAbsolute}`
|
|
1813
|
-
});
|
|
1814
|
-
}
|
|
1815
|
-
throw errorUnexpected4(error instanceof Error ? error.message : String(error), {
|
|
1816
|
-
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
1817
|
-
});
|
|
1818
|
-
}
|
|
1819
|
-
const contractJson = JSON.parse(contractJsonContent);
|
|
1820
|
-
if (!config.driver) {
|
|
1821
|
-
throw errorDriverRequired3();
|
|
1822
|
-
}
|
|
1823
|
-
const driverDescriptor = config.driver;
|
|
1824
|
-
const familyInstance = config.family.create({
|
|
1825
|
-
target: config.target,
|
|
1826
|
-
adapter: config.adapter,
|
|
1827
|
-
driver: driverDescriptor,
|
|
1828
|
-
extensionPacks: config.extensionPacks ?? []
|
|
1829
|
-
});
|
|
1830
|
-
const contractIR = familyInstance.validateContractIR(contractJson);
|
|
1831
|
-
assertContractRequirementsSatisfied({
|
|
1832
|
-
contract: contractIR,
|
|
1833
|
-
family: config.family,
|
|
1834
|
-
target: config.target,
|
|
1835
|
-
adapter: config.adapter,
|
|
1836
|
-
extensionPacks: config.extensionPacks
|
|
1837
|
-
});
|
|
1838
|
-
const dbUrl = options.db ?? config.db?.url;
|
|
1839
|
-
if (!dbUrl) {
|
|
1840
|
-
throw errorDatabaseUrlRequired3();
|
|
1841
|
-
}
|
|
1842
|
-
const driver = await withSpinner(() => driverDescriptor.create(dbUrl), {
|
|
1843
|
-
message: "Connecting to database...",
|
|
1844
|
-
flags
|
|
1845
|
-
});
|
|
1846
|
-
try {
|
|
1847
|
-
const rawComponents = [config.target, config.adapter, ...config.extensionPacks ?? []];
|
|
1848
|
-
const frameworkComponents = assertFrameworkComponentsCompatible(
|
|
1849
|
-
config.family.familyId,
|
|
1850
|
-
config.target.targetId,
|
|
1851
|
-
rawComponents
|
|
1852
|
-
);
|
|
1853
|
-
let schemaVerifyResult;
|
|
1854
|
-
try {
|
|
1855
|
-
schemaVerifyResult = await withSpinner(
|
|
1856
|
-
() => familyInstance.schemaVerify({
|
|
1857
|
-
driver,
|
|
1858
|
-
contractIR,
|
|
1859
|
-
strict: options.strict ?? false,
|
|
1860
|
-
contractPath: contractPathAbsolute,
|
|
1861
|
-
configPath,
|
|
1862
|
-
frameworkComponents
|
|
1863
|
-
}),
|
|
1864
|
-
{
|
|
1865
|
-
message: "Verifying database schema...",
|
|
1866
|
-
flags
|
|
1867
|
-
}
|
|
1868
|
-
);
|
|
1869
|
-
} catch (error) {
|
|
1870
|
-
throw errorRuntime3(error instanceof Error ? error.message : String(error), {
|
|
1871
|
-
why: `Failed to verify database schema: ${error instanceof Error ? error.message : String(error)}`
|
|
1872
|
-
});
|
|
1873
|
-
}
|
|
1874
|
-
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
1875
|
-
console.log("");
|
|
1876
|
-
}
|
|
1877
|
-
return schemaVerifyResult;
|
|
1878
|
-
} finally {
|
|
1879
|
-
await driver.close();
|
|
1880
|
-
}
|
|
1881
|
-
});
|
|
2407
|
+
format: "ndjson",
|
|
2408
|
+
supportedFormats: ["object"]
|
|
2409
|
+
})
|
|
2410
|
+
);
|
|
2411
|
+
const exitCode2 = handleResult(result2, flags);
|
|
2412
|
+
process.exit(exitCode2);
|
|
2413
|
+
}
|
|
2414
|
+
const result = await executeDbSchemaVerifyCommand(options, flags);
|
|
1882
2415
|
const exitCode = handleResult(result, flags, (schemaVerifyResult) => {
|
|
1883
2416
|
if (flags.json === "object") {
|
|
1884
2417
|
console.log(formatSchemaVerifyJson(schemaVerifyResult));
|
|
@@ -1899,16 +2432,116 @@ function createDbSchemaVerifyCommand() {
|
|
|
1899
2432
|
}
|
|
1900
2433
|
|
|
1901
2434
|
// src/commands/db-sign.ts
|
|
1902
|
-
import { readFile as
|
|
2435
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
1903
2436
|
import { relative as relative6, resolve as resolve6 } from "path";
|
|
1904
|
-
import {
|
|
1905
|
-
errorDatabaseUrlRequired as errorDatabaseUrlRequired4,
|
|
1906
|
-
errorDriverRequired as errorDriverRequired4,
|
|
1907
|
-
errorFileNotFound as errorFileNotFound3,
|
|
1908
|
-
errorRuntime as errorRuntime4,
|
|
1909
|
-
errorUnexpected as errorUnexpected5
|
|
1910
|
-
} from "@prisma-next/core-control-plane/errors";
|
|
2437
|
+
import { notOk as notOk7, ok as ok7 } from "@prisma-next/utils/result";
|
|
1911
2438
|
import { Command as Command5 } from "commander";
|
|
2439
|
+
async function executeDbSignCommand(options, flags) {
|
|
2440
|
+
const config = await loadConfig(options.config);
|
|
2441
|
+
const configPath = options.config ? relative6(process.cwd(), resolve6(options.config)) : "prisma-next.config.ts";
|
|
2442
|
+
const contractPathAbsolute = config.contract?.output ? resolve6(config.contract.output) : resolve6("src/prisma/contract.json");
|
|
2443
|
+
const contractPath = relative6(process.cwd(), contractPathAbsolute);
|
|
2444
|
+
if (flags.json !== "object" && !flags.quiet) {
|
|
2445
|
+
const details = [
|
|
2446
|
+
{ label: "config", value: configPath },
|
|
2447
|
+
{ label: "contract", value: contractPath }
|
|
2448
|
+
];
|
|
2449
|
+
if (options.db) {
|
|
2450
|
+
details.push({ label: "database", value: options.db });
|
|
2451
|
+
}
|
|
2452
|
+
const header = formatStyledHeader({
|
|
2453
|
+
command: "db sign",
|
|
2454
|
+
description: "Sign the database with your contract so you can safely run queries",
|
|
2455
|
+
url: "https://pris.ly/db-sign",
|
|
2456
|
+
details,
|
|
2457
|
+
flags
|
|
2458
|
+
});
|
|
2459
|
+
console.log(header);
|
|
2460
|
+
}
|
|
2461
|
+
let contractJsonContent;
|
|
2462
|
+
try {
|
|
2463
|
+
contractJsonContent = await readFile3(contractPathAbsolute, "utf-8");
|
|
2464
|
+
} catch (error) {
|
|
2465
|
+
if (error instanceof Error && error.code === "ENOENT") {
|
|
2466
|
+
return notOk7(
|
|
2467
|
+
errorFileNotFound(contractPathAbsolute, {
|
|
2468
|
+
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
2469
|
+
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
|
|
2470
|
+
})
|
|
2471
|
+
);
|
|
2472
|
+
}
|
|
2473
|
+
return notOk7(
|
|
2474
|
+
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2475
|
+
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
2476
|
+
})
|
|
2477
|
+
);
|
|
2478
|
+
}
|
|
2479
|
+
let contractJson;
|
|
2480
|
+
try {
|
|
2481
|
+
contractJson = JSON.parse(contractJsonContent);
|
|
2482
|
+
} catch (error) {
|
|
2483
|
+
return notOk7(
|
|
2484
|
+
errorContractValidationFailed(
|
|
2485
|
+
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
2486
|
+
{ where: { path: contractPathAbsolute } }
|
|
2487
|
+
)
|
|
2488
|
+
);
|
|
2489
|
+
}
|
|
2490
|
+
const dbConnection = options.db ?? config.db?.connection;
|
|
2491
|
+
if (!dbConnection) {
|
|
2492
|
+
return notOk7(
|
|
2493
|
+
errorDatabaseConnectionRequired({
|
|
2494
|
+
why: `Database connection is required for db sign (set db.connection in ${configPath}, or pass --db <url>)`
|
|
2495
|
+
})
|
|
2496
|
+
);
|
|
2497
|
+
}
|
|
2498
|
+
if (!config.driver) {
|
|
2499
|
+
return notOk7(errorDriverRequired({ why: "Config.driver is required for db sign" }));
|
|
2500
|
+
}
|
|
2501
|
+
const client = createControlClient({
|
|
2502
|
+
family: config.family,
|
|
2503
|
+
target: config.target,
|
|
2504
|
+
adapter: config.adapter,
|
|
2505
|
+
driver: config.driver,
|
|
2506
|
+
extensionPacks: config.extensionPacks ?? []
|
|
2507
|
+
});
|
|
2508
|
+
const onProgress = createProgressAdapter({ flags });
|
|
2509
|
+
try {
|
|
2510
|
+
const schemaVerifyResult = await client.schemaVerify({
|
|
2511
|
+
contractIR: contractJson,
|
|
2512
|
+
strict: false,
|
|
2513
|
+
connection: dbConnection,
|
|
2514
|
+
onProgress
|
|
2515
|
+
});
|
|
2516
|
+
if (!schemaVerifyResult.ok) {
|
|
2517
|
+
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
2518
|
+
console.log("");
|
|
2519
|
+
}
|
|
2520
|
+
return notOk7(schemaVerifyResult);
|
|
2521
|
+
}
|
|
2522
|
+
const signResult = await client.sign({
|
|
2523
|
+
contractIR: contractJson,
|
|
2524
|
+
contractPath,
|
|
2525
|
+
configPath,
|
|
2526
|
+
onProgress
|
|
2527
|
+
});
|
|
2528
|
+
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
2529
|
+
console.log("");
|
|
2530
|
+
}
|
|
2531
|
+
return ok7(signResult);
|
|
2532
|
+
} catch (error) {
|
|
2533
|
+
if (error instanceof CliStructuredError) {
|
|
2534
|
+
return notOk7(error);
|
|
2535
|
+
}
|
|
2536
|
+
return notOk7(
|
|
2537
|
+
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2538
|
+
why: `Unexpected error during db sign: ${error instanceof Error ? error.message : String(error)}`
|
|
2539
|
+
})
|
|
2540
|
+
);
|
|
2541
|
+
} finally {
|
|
2542
|
+
await client.close();
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
1912
2545
|
function createDbSignCommand() {
|
|
1913
2546
|
const command = new Command5("sign");
|
|
1914
2547
|
setCommandDescriptions(
|
|
@@ -1921,172 +2554,170 @@ function createDbSignCommand() {
|
|
|
1921
2554
|
const flags = parseGlobalFlags({});
|
|
1922
2555
|
return formatCommandHelp({ command: cmd, flags });
|
|
1923
2556
|
}
|
|
1924
|
-
}).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--json [format]", "Output as JSON (object
|
|
2557
|
+
}).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--json [format]", "Output as JSON (object)", 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) => {
|
|
1925
2558
|
const flags = parseGlobalFlags(options);
|
|
1926
|
-
|
|
1927
|
-
const
|
|
1928
|
-
|
|
1929
|
-
const contractPathAbsolute = config.contract?.output ? resolve6(config.contract.output) : resolve6("src/prisma/contract.json");
|
|
1930
|
-
const contractPath = relative6(process.cwd(), contractPathAbsolute);
|
|
1931
|
-
if (flags.json !== "object" && !flags.quiet) {
|
|
1932
|
-
const details = [
|
|
1933
|
-
{ label: "config", value: configPath },
|
|
1934
|
-
{ label: "contract", value: contractPath }
|
|
1935
|
-
];
|
|
1936
|
-
if (options.db) {
|
|
1937
|
-
details.push({ label: "database", value: options.db });
|
|
1938
|
-
}
|
|
1939
|
-
const header = formatStyledHeader({
|
|
2559
|
+
if (flags.json === "ndjson") {
|
|
2560
|
+
const result2 = notOk7(
|
|
2561
|
+
errorJsonFormatNotSupported({
|
|
1940
2562
|
command: "db sign",
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
flags
|
|
1945
|
-
});
|
|
1946
|
-
console.log(header);
|
|
1947
|
-
}
|
|
1948
|
-
let contractJsonContent;
|
|
1949
|
-
try {
|
|
1950
|
-
contractJsonContent = await readFile4(contractPathAbsolute, "utf-8");
|
|
1951
|
-
} catch (error) {
|
|
1952
|
-
if (error instanceof Error && error.code === "ENOENT") {
|
|
1953
|
-
throw errorFileNotFound3(contractPathAbsolute, {
|
|
1954
|
-
why: `Contract file not found at ${contractPathAbsolute}`
|
|
1955
|
-
});
|
|
1956
|
-
}
|
|
1957
|
-
throw errorUnexpected5(error instanceof Error ? error.message : String(error), {
|
|
1958
|
-
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
1959
|
-
});
|
|
1960
|
-
}
|
|
1961
|
-
const contractJson = JSON.parse(contractJsonContent);
|
|
1962
|
-
const dbUrl = options.db ?? config.db?.url;
|
|
1963
|
-
if (!dbUrl) {
|
|
1964
|
-
throw errorDatabaseUrlRequired4();
|
|
1965
|
-
}
|
|
1966
|
-
if (!config.driver) {
|
|
1967
|
-
throw errorDriverRequired4();
|
|
1968
|
-
}
|
|
1969
|
-
const driverDescriptor = config.driver;
|
|
1970
|
-
const familyInstance = config.family.create({
|
|
1971
|
-
target: config.target,
|
|
1972
|
-
adapter: config.adapter,
|
|
1973
|
-
driver: driverDescriptor,
|
|
1974
|
-
extensionPacks: config.extensionPacks ?? []
|
|
1975
|
-
});
|
|
1976
|
-
const contractIR = familyInstance.validateContractIR(contractJson);
|
|
1977
|
-
assertContractRequirementsSatisfied({
|
|
1978
|
-
contract: contractIR,
|
|
1979
|
-
family: config.family,
|
|
1980
|
-
target: config.target,
|
|
1981
|
-
adapter: config.adapter,
|
|
1982
|
-
extensionPacks: config.extensionPacks
|
|
1983
|
-
});
|
|
1984
|
-
const rawComponents = [config.target, config.adapter, ...config.extensionPacks ?? []];
|
|
1985
|
-
const frameworkComponents = assertFrameworkComponentsCompatible(
|
|
1986
|
-
config.family.familyId,
|
|
1987
|
-
config.target.targetId,
|
|
1988
|
-
rawComponents
|
|
2563
|
+
format: "ndjson",
|
|
2564
|
+
supportedFormats: ["object"]
|
|
2565
|
+
})
|
|
1989
2566
|
);
|
|
1990
|
-
const
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
frameworkComponents
|
|
2002
|
-
}),
|
|
2003
|
-
{
|
|
2004
|
-
message: "Verifying database satisfies contract",
|
|
2005
|
-
flags
|
|
2006
|
-
}
|
|
2007
|
-
);
|
|
2008
|
-
} catch (error) {
|
|
2009
|
-
throw errorRuntime4(error instanceof Error ? error.message : String(error), {
|
|
2010
|
-
why: `Failed to verify database schema: ${error instanceof Error ? error.message : String(error)}`
|
|
2011
|
-
});
|
|
2012
|
-
}
|
|
2013
|
-
if (!schemaVerifyResult.ok) {
|
|
2014
|
-
return { schemaVerifyResult, signResult: void 0 };
|
|
2015
|
-
}
|
|
2016
|
-
let signResult;
|
|
2017
|
-
try {
|
|
2018
|
-
signResult = await withSpinner(
|
|
2019
|
-
() => familyInstance.sign({
|
|
2020
|
-
driver,
|
|
2021
|
-
contractIR,
|
|
2022
|
-
contractPath: contractPathAbsolute,
|
|
2023
|
-
configPath
|
|
2024
|
-
}),
|
|
2025
|
-
{
|
|
2026
|
-
message: "Signing database...",
|
|
2027
|
-
flags
|
|
2028
|
-
}
|
|
2029
|
-
);
|
|
2030
|
-
} catch (error) {
|
|
2031
|
-
throw errorRuntime4(error instanceof Error ? error.message : String(error), {
|
|
2032
|
-
why: `Failed to sign database: ${error instanceof Error ? error.message : String(error)}`
|
|
2033
|
-
});
|
|
2034
|
-
}
|
|
2035
|
-
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
2036
|
-
console.log("");
|
|
2037
|
-
}
|
|
2038
|
-
return { schemaVerifyResult: void 0, signResult };
|
|
2039
|
-
} finally {
|
|
2040
|
-
await driver.close();
|
|
2041
|
-
}
|
|
2042
|
-
});
|
|
2043
|
-
const exitCode = handleResult(result, flags, (value) => {
|
|
2044
|
-
const { schemaVerifyResult, signResult } = value;
|
|
2045
|
-
if (schemaVerifyResult && !schemaVerifyResult.ok) {
|
|
2046
|
-
if (flags.json === "object") {
|
|
2047
|
-
console.log(formatSchemaVerifyJson(schemaVerifyResult));
|
|
2048
|
-
} else {
|
|
2049
|
-
const output = formatSchemaVerifyOutput(schemaVerifyResult, flags);
|
|
2050
|
-
if (output) {
|
|
2051
|
-
console.log(output);
|
|
2052
|
-
}
|
|
2053
|
-
}
|
|
2054
|
-
return;
|
|
2055
|
-
}
|
|
2056
|
-
if (signResult) {
|
|
2057
|
-
if (flags.json === "object") {
|
|
2058
|
-
console.log(formatSignJson(signResult));
|
|
2059
|
-
} else {
|
|
2060
|
-
const output = formatSignOutput(signResult, flags);
|
|
2061
|
-
if (output) {
|
|
2062
|
-
console.log(output);
|
|
2063
|
-
}
|
|
2567
|
+
const exitCode = handleResult(result2, flags);
|
|
2568
|
+
process.exit(exitCode);
|
|
2569
|
+
}
|
|
2570
|
+
const result = await executeDbSignCommand(options, flags);
|
|
2571
|
+
if (result.ok) {
|
|
2572
|
+
if (flags.json === "object") {
|
|
2573
|
+
console.log(formatSignJson(result.value));
|
|
2574
|
+
} else {
|
|
2575
|
+
const output = formatSignOutput(result.value, flags);
|
|
2576
|
+
if (output) {
|
|
2577
|
+
console.log(output);
|
|
2064
2578
|
}
|
|
2065
2579
|
}
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2580
|
+
process.exit(0);
|
|
2581
|
+
}
|
|
2582
|
+
const failure = result.failure;
|
|
2583
|
+
if (failure instanceof CliStructuredError) {
|
|
2584
|
+
const exitCode = handleResult(result, flags);
|
|
2070
2585
|
process.exit(exitCode);
|
|
2071
2586
|
}
|
|
2587
|
+
if (flags.json === "object") {
|
|
2588
|
+
console.log(formatSchemaVerifyJson(failure));
|
|
2589
|
+
} else {
|
|
2590
|
+
const output = formatSchemaVerifyOutput(failure, flags);
|
|
2591
|
+
if (output) {
|
|
2592
|
+
console.log(output);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
process.exit(1);
|
|
2072
2596
|
});
|
|
2073
2597
|
return command;
|
|
2074
2598
|
}
|
|
2075
2599
|
|
|
2076
2600
|
// src/commands/db-verify.ts
|
|
2077
|
-
import { readFile as
|
|
2601
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
2078
2602
|
import { relative as relative7, resolve as resolve7 } from "path";
|
|
2079
|
-
import {
|
|
2080
|
-
errorDatabaseUrlRequired as errorDatabaseUrlRequired5,
|
|
2081
|
-
errorDriverRequired as errorDriverRequired5,
|
|
2082
|
-
errorFileNotFound as errorFileNotFound4,
|
|
2083
|
-
errorHashMismatch as errorHashMismatch2,
|
|
2084
|
-
errorMarkerMissing as errorMarkerMissing2,
|
|
2085
|
-
errorRuntime as errorRuntime5,
|
|
2086
|
-
errorTargetMismatch as errorTargetMismatch2,
|
|
2087
|
-
errorUnexpected as errorUnexpected6
|
|
2088
|
-
} from "@prisma-next/core-control-plane/errors";
|
|
2603
|
+
import { notOk as notOk8, ok as ok8 } from "@prisma-next/utils/result";
|
|
2089
2604
|
import { Command as Command6 } from "commander";
|
|
2605
|
+
function mapVerifyFailure(verifyResult) {
|
|
2606
|
+
if (!verifyResult.ok && verifyResult.code) {
|
|
2607
|
+
if (verifyResult.code === "PN-RTM-3001") {
|
|
2608
|
+
return errorMarkerMissing();
|
|
2609
|
+
}
|
|
2610
|
+
if (verifyResult.code === "PN-RTM-3002") {
|
|
2611
|
+
return errorHashMismatch({
|
|
2612
|
+
expected: verifyResult.contract.coreHash,
|
|
2613
|
+
...verifyResult.marker?.coreHash ? { actual: verifyResult.marker.coreHash } : {}
|
|
2614
|
+
});
|
|
2615
|
+
}
|
|
2616
|
+
if (verifyResult.code === "PN-RTM-3003") {
|
|
2617
|
+
return errorTargetMismatch(
|
|
2618
|
+
verifyResult.target.expected,
|
|
2619
|
+
verifyResult.target.actual ?? "unknown"
|
|
2620
|
+
);
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
return errorRuntime(verifyResult.summary);
|
|
2624
|
+
}
|
|
2625
|
+
async function executeDbVerifyCommand(options, flags) {
|
|
2626
|
+
const config = await loadConfig(options.config);
|
|
2627
|
+
const configPath = options.config ? relative7(process.cwd(), resolve7(options.config)) : "prisma-next.config.ts";
|
|
2628
|
+
const contractPathAbsolute = config.contract?.output ? resolve7(config.contract.output) : resolve7("src/prisma/contract.json");
|
|
2629
|
+
const contractPath = relative7(process.cwd(), contractPathAbsolute);
|
|
2630
|
+
if (flags.json !== "object" && !flags.quiet) {
|
|
2631
|
+
const details = [
|
|
2632
|
+
{ label: "config", value: configPath },
|
|
2633
|
+
{ label: "contract", value: contractPath }
|
|
2634
|
+
];
|
|
2635
|
+
if (options.db) {
|
|
2636
|
+
details.push({ label: "database", value: options.db });
|
|
2637
|
+
}
|
|
2638
|
+
const header = formatStyledHeader({
|
|
2639
|
+
command: "db verify",
|
|
2640
|
+
description: "Check whether the database has been signed with your contract",
|
|
2641
|
+
url: "https://pris.ly/db-verify",
|
|
2642
|
+
details,
|
|
2643
|
+
flags
|
|
2644
|
+
});
|
|
2645
|
+
console.log(header);
|
|
2646
|
+
}
|
|
2647
|
+
let contractJsonContent;
|
|
2648
|
+
try {
|
|
2649
|
+
contractJsonContent = await readFile4(contractPathAbsolute, "utf-8");
|
|
2650
|
+
} catch (error) {
|
|
2651
|
+
if (error instanceof Error && error.code === "ENOENT") {
|
|
2652
|
+
return notOk8(
|
|
2653
|
+
errorFileNotFound(contractPathAbsolute, {
|
|
2654
|
+
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
2655
|
+
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
|
|
2656
|
+
})
|
|
2657
|
+
);
|
|
2658
|
+
}
|
|
2659
|
+
return notOk8(
|
|
2660
|
+
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2661
|
+
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
2662
|
+
})
|
|
2663
|
+
);
|
|
2664
|
+
}
|
|
2665
|
+
let contractJson;
|
|
2666
|
+
try {
|
|
2667
|
+
contractJson = JSON.parse(contractJsonContent);
|
|
2668
|
+
} catch (error) {
|
|
2669
|
+
return notOk8(
|
|
2670
|
+
errorContractValidationFailed(
|
|
2671
|
+
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
2672
|
+
{ where: { path: contractPathAbsolute } }
|
|
2673
|
+
)
|
|
2674
|
+
);
|
|
2675
|
+
}
|
|
2676
|
+
const dbConnection = options.db ?? config.db?.connection;
|
|
2677
|
+
if (!dbConnection) {
|
|
2678
|
+
return notOk8(
|
|
2679
|
+
errorDatabaseConnectionRequired({
|
|
2680
|
+
why: `Database connection is required for db verify (set db.connection in ${configPath}, or pass --db <url>)`
|
|
2681
|
+
})
|
|
2682
|
+
);
|
|
2683
|
+
}
|
|
2684
|
+
if (!config.driver) {
|
|
2685
|
+
return notOk8(errorDriverRequired({ why: "Config.driver is required for db verify" }));
|
|
2686
|
+
}
|
|
2687
|
+
const client = createControlClient({
|
|
2688
|
+
family: config.family,
|
|
2689
|
+
target: config.target,
|
|
2690
|
+
adapter: config.adapter,
|
|
2691
|
+
driver: config.driver,
|
|
2692
|
+
extensionPacks: config.extensionPacks ?? []
|
|
2693
|
+
});
|
|
2694
|
+
const onProgress = createProgressAdapter({ flags });
|
|
2695
|
+
try {
|
|
2696
|
+
const verifyResult = await client.verify({
|
|
2697
|
+
contractIR: contractJson,
|
|
2698
|
+
connection: dbConnection,
|
|
2699
|
+
onProgress
|
|
2700
|
+
});
|
|
2701
|
+
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
2702
|
+
console.log("");
|
|
2703
|
+
}
|
|
2704
|
+
if (!verifyResult.ok) {
|
|
2705
|
+
return notOk8(mapVerifyFailure(verifyResult));
|
|
2706
|
+
}
|
|
2707
|
+
return ok8(verifyResult);
|
|
2708
|
+
} catch (error) {
|
|
2709
|
+
if (error instanceof CliStructuredError) {
|
|
2710
|
+
return notOk8(error);
|
|
2711
|
+
}
|
|
2712
|
+
return notOk8(
|
|
2713
|
+
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2714
|
+
why: `Unexpected error during db verify: ${error instanceof Error ? error.message : String(error)}`
|
|
2715
|
+
})
|
|
2716
|
+
);
|
|
2717
|
+
} finally {
|
|
2718
|
+
await client.close();
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2090
2721
|
function createDbVerifyCommand() {
|
|
2091
2722
|
const command = new Command6("verify");
|
|
2092
2723
|
setCommandDescriptions(
|
|
@@ -2099,117 +2730,20 @@ function createDbVerifyCommand() {
|
|
|
2099
2730
|
const flags = parseGlobalFlags({});
|
|
2100
2731
|
return formatCommandHelp({ command: cmd, flags });
|
|
2101
2732
|
}
|
|
2102
|
-
}).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--json [format]", "Output as JSON (object
|
|
2733
|
+
}).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--json [format]", "Output as JSON (object)", 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) => {
|
|
2103
2734
|
const flags = parseGlobalFlags(options);
|
|
2104
|
-
|
|
2105
|
-
const
|
|
2106
|
-
|
|
2107
|
-
const contractPathAbsolute = config.contract?.output ? resolve7(config.contract.output) : resolve7("src/prisma/contract.json");
|
|
2108
|
-
const contractPath = relative7(process.cwd(), contractPathAbsolute);
|
|
2109
|
-
if (flags.json !== "object" && !flags.quiet) {
|
|
2110
|
-
const details = [
|
|
2111
|
-
{ label: "config", value: configPath },
|
|
2112
|
-
{ label: "contract", value: contractPath }
|
|
2113
|
-
];
|
|
2114
|
-
if (options.db) {
|
|
2115
|
-
details.push({ label: "database", value: options.db });
|
|
2116
|
-
}
|
|
2117
|
-
const header = formatStyledHeader({
|
|
2735
|
+
if (flags.json === "ndjson") {
|
|
2736
|
+
const result2 = notOk8(
|
|
2737
|
+
errorJsonFormatNotSupported({
|
|
2118
2738
|
command: "db verify",
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
try {
|
|
2128
|
-
contractJsonContent = await readFile5(contractPathAbsolute, "utf-8");
|
|
2129
|
-
} catch (error) {
|
|
2130
|
-
if (error instanceof Error && error.code === "ENOENT") {
|
|
2131
|
-
throw errorFileNotFound4(contractPathAbsolute, {
|
|
2132
|
-
why: `Contract file not found at ${contractPathAbsolute}`
|
|
2133
|
-
});
|
|
2134
|
-
}
|
|
2135
|
-
throw errorUnexpected6(error instanceof Error ? error.message : String(error), {
|
|
2136
|
-
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
2137
|
-
});
|
|
2138
|
-
}
|
|
2139
|
-
const contractJson = JSON.parse(contractJsonContent);
|
|
2140
|
-
const dbUrl = options.db ?? config.db?.url;
|
|
2141
|
-
if (!dbUrl) {
|
|
2142
|
-
throw errorDatabaseUrlRequired5();
|
|
2143
|
-
}
|
|
2144
|
-
if (!config.driver) {
|
|
2145
|
-
throw errorDriverRequired5();
|
|
2146
|
-
}
|
|
2147
|
-
const driverDescriptor = config.driver;
|
|
2148
|
-
const driver = await withSpinner(() => driverDescriptor.create(dbUrl), {
|
|
2149
|
-
message: "Connecting to database...",
|
|
2150
|
-
flags
|
|
2151
|
-
});
|
|
2152
|
-
try {
|
|
2153
|
-
const familyInstance = config.family.create({
|
|
2154
|
-
target: config.target,
|
|
2155
|
-
adapter: config.adapter,
|
|
2156
|
-
driver: driverDescriptor,
|
|
2157
|
-
extensionPacks: config.extensionPacks ?? []
|
|
2158
|
-
});
|
|
2159
|
-
const contractIR = familyInstance.validateContractIR(contractJson);
|
|
2160
|
-
assertContractRequirementsSatisfied({
|
|
2161
|
-
contract: contractIR,
|
|
2162
|
-
family: config.family,
|
|
2163
|
-
target: config.target,
|
|
2164
|
-
adapter: config.adapter,
|
|
2165
|
-
extensionPacks: config.extensionPacks
|
|
2166
|
-
});
|
|
2167
|
-
let verifyResult;
|
|
2168
|
-
try {
|
|
2169
|
-
verifyResult = await withSpinner(
|
|
2170
|
-
() => familyInstance.verify({
|
|
2171
|
-
driver,
|
|
2172
|
-
contractIR,
|
|
2173
|
-
expectedTargetId: config.target.targetId,
|
|
2174
|
-
contractPath: contractPathAbsolute,
|
|
2175
|
-
configPath
|
|
2176
|
-
}),
|
|
2177
|
-
{
|
|
2178
|
-
message: "Verifying database schema...",
|
|
2179
|
-
flags
|
|
2180
|
-
}
|
|
2181
|
-
);
|
|
2182
|
-
} catch (error) {
|
|
2183
|
-
throw errorUnexpected6(error instanceof Error ? error.message : String(error), {
|
|
2184
|
-
why: `Failed to verify database: ${error instanceof Error ? error.message : String(error)}`
|
|
2185
|
-
});
|
|
2186
|
-
}
|
|
2187
|
-
if (!verifyResult.ok && verifyResult.code) {
|
|
2188
|
-
if (verifyResult.code === "PN-RTM-3001") {
|
|
2189
|
-
throw errorMarkerMissing2();
|
|
2190
|
-
}
|
|
2191
|
-
if (verifyResult.code === "PN-RTM-3002") {
|
|
2192
|
-
throw errorHashMismatch2({
|
|
2193
|
-
expected: verifyResult.contract.coreHash,
|
|
2194
|
-
...verifyResult.marker?.coreHash ? { actual: verifyResult.marker.coreHash } : {}
|
|
2195
|
-
});
|
|
2196
|
-
}
|
|
2197
|
-
if (verifyResult.code === "PN-RTM-3003") {
|
|
2198
|
-
throw errorTargetMismatch2(
|
|
2199
|
-
verifyResult.target.expected,
|
|
2200
|
-
verifyResult.target.actual ?? "unknown"
|
|
2201
|
-
);
|
|
2202
|
-
}
|
|
2203
|
-
throw errorRuntime5(verifyResult.summary);
|
|
2204
|
-
}
|
|
2205
|
-
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
2206
|
-
console.log("");
|
|
2207
|
-
}
|
|
2208
|
-
return verifyResult;
|
|
2209
|
-
} finally {
|
|
2210
|
-
await driver.close();
|
|
2211
|
-
}
|
|
2212
|
-
});
|
|
2739
|
+
format: "ndjson",
|
|
2740
|
+
supportedFormats: ["object"]
|
|
2741
|
+
})
|
|
2742
|
+
);
|
|
2743
|
+
const exitCode2 = handleResult(result2, flags);
|
|
2744
|
+
process.exit(exitCode2);
|
|
2745
|
+
}
|
|
2746
|
+
const result = await executeDbVerifyCommand(options, flags);
|
|
2213
2747
|
const exitCode = handleResult(result, flags, (verifyResult) => {
|
|
2214
2748
|
if (flags.json === "object") {
|
|
2215
2749
|
console.log(formatVerifyJson(verifyResult));
|