@prisma-next/cli 0.3.0-dev.17 → 0.3.0-dev.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ZG5T6OB5.js → chunk-AGOTG4L3.js} +43 -1
- package/dist/chunk-AGOTG4L3.js.map +1 -0
- package/dist/chunk-HLLI4YL7.js +180 -0
- package/dist/chunk-HLLI4YL7.js.map +1 -0
- package/dist/chunk-VG2R7DGF.js +735 -0
- package/dist/chunk-VG2R7DGF.js.map +1 -0
- package/dist/cli.js +1621 -1382
- package/dist/cli.js.map +1 -1
- package/dist/commands/contract-emit.d.ts.map +1 -1
- package/dist/commands/contract-emit.js +3 -4
- package/dist/commands/db-init.js +4 -49
- package/dist/commands/db-init.js.map +1 -1
- package/dist/commands/db-introspect.d.ts.map +1 -1
- package/dist/commands/db-introspect.js +106 -136
- package/dist/commands/db-introspect.js.map +1 -1
- package/dist/commands/db-schema-verify.d.ts.map +1 -1
- package/dist/commands/db-schema-verify.js +118 -110
- package/dist/commands/db-schema-verify.js.map +1 -1
- package/dist/commands/db-sign.d.ts.map +1 -1
- package/dist/commands/db-sign.js +150 -153
- package/dist/commands/db-sign.js.map +1 -1
- package/dist/commands/db-verify.d.ts.map +1 -1
- package/dist/commands/db-verify.js +140 -119
- package/dist/commands/db-verify.js.map +1 -1
- package/dist/control-api/client.d.ts.map +1 -1
- package/dist/control-api/types.d.ts +132 -1
- package/dist/control-api/types.d.ts.map +1 -1
- package/dist/exports/control-api.d.ts +1 -1
- package/dist/exports/control-api.d.ts.map +1 -1
- package/dist/exports/control-api.js +1 -3
- package/dist/exports/index.js +3 -4
- package/dist/exports/index.js.map +1 -1
- package/package.json +10 -10
- package/src/commands/contract-emit.ts +179 -102
- package/src/commands/db-introspect.ts +151 -178
- package/src/commands/db-schema-verify.ts +150 -143
- package/src/commands/db-sign.ts +202 -196
- package/src/commands/db-verify.ts +179 -149
- package/src/control-api/client.ts +352 -22
- package/src/control-api/types.ts +149 -1
- package/src/exports/control-api.ts +9 -0
- package/dist/chunk-5MPKZYVI.js +0 -47
- package/dist/chunk-5MPKZYVI.js.map +0 -1
- package/dist/chunk-6EPKRATC.js +0 -91
- package/dist/chunk-6EPKRATC.js.map +0 -1
- package/dist/chunk-74IELXRA.js +0 -371
- package/dist/chunk-74IELXRA.js.map +0 -1
- package/dist/chunk-U6QI3AZ3.js +0 -133
- package/dist/chunk-U6QI3AZ3.js.map +0 -1
- package/dist/chunk-VI2YETW7.js +0 -38
- package/dist/chunk-VI2YETW7.js.map +0 -1
- package/dist/chunk-ZG5T6OB5.js.map +0 -1
- package/dist/utils/action.d.ts +0 -16
- package/dist/utils/action.d.ts.map +0 -1
- package/dist/utils/spinner.d.ts +0 -29
- package/dist/utils/spinner.d.ts.map +0 -1
- package/src/utils/action.ts +0 -43
- package/src/utils/spinner.ts +0 -67
package/dist/cli.js
CHANGED
|
@@ -7,7 +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 {
|
|
10
|
+
import { notOk as notOk3, ok as ok3 } from "@prisma-next/utils/result";
|
|
11
11
|
import { Command } from "commander";
|
|
12
12
|
|
|
13
13
|
// src/config-loader.ts
|
|
@@ -53,8 +53,14 @@ async function loadConfig(configPath) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
// src/
|
|
57
|
-
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";
|
|
58
64
|
|
|
59
65
|
// src/utils/cli-errors.ts
|
|
60
66
|
import {
|
|
@@ -79,210 +85,885 @@ import {
|
|
|
79
85
|
errorUnexpected as errorUnexpected2
|
|
80
86
|
} from "@prisma-next/core-control-plane/errors";
|
|
81
87
|
|
|
82
|
-
// src/utils/
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
+
});
|
|
90
130
|
}
|
|
91
|
-
throw error;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// src/utils/command-helpers.ts
|
|
96
|
-
function setCommandDescriptions(command, shortDescription, longDescription) {
|
|
97
|
-
command.description(shortDescription);
|
|
98
|
-
if (longDescription) {
|
|
99
|
-
command._longDescription = longDescription;
|
|
100
131
|
}
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
function getLongDescription(command) {
|
|
104
|
-
return command._longDescription;
|
|
132
|
+
return frameworkComponents;
|
|
105
133
|
}
|
|
106
134
|
|
|
107
|
-
// src/
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
+
});
|
|
114
183
|
}
|
|
115
|
-
|
|
116
|
-
|
|
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
|
+
});
|
|
117
243
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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);
|
|
124
257
|
}
|
|
125
|
-
|
|
126
|
-
|
|
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
|
+
});
|
|
282
|
+
}
|
|
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
|
+
});
|
|
127
314
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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);
|
|
138
336
|
}
|
|
139
337
|
|
|
140
|
-
// src/
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
import stringWidth from "string-width";
|
|
144
|
-
import stripAnsi from "strip-ansi";
|
|
145
|
-
import wrapAnsi from "wrap-ansi";
|
|
146
|
-
function formatTimestamp() {
|
|
147
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
148
|
-
}
|
|
149
|
-
function createPrefix(flags) {
|
|
150
|
-
return flags.timestamps ? `[${formatTimestamp()}] ` : "";
|
|
151
|
-
}
|
|
152
|
-
function isVerbose(flags, level) {
|
|
153
|
-
return (flags.verbose ?? 0) >= level;
|
|
154
|
-
}
|
|
155
|
-
function createColorFormatter(useColor, colorFn) {
|
|
156
|
-
return useColor ? colorFn : (text) => text;
|
|
157
|
-
}
|
|
158
|
-
function formatDim(useColor, text) {
|
|
159
|
-
return useColor ? dim(text) : text;
|
|
338
|
+
// src/control-api/client.ts
|
|
339
|
+
function createControlClient(options) {
|
|
340
|
+
return new ControlClientImpl(options);
|
|
160
341
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (result.profileHash) {
|
|
173
|
-
lines.push(`${prefix} profileHash: ${result.profileHash}`);
|
|
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;
|
|
174
353
|
}
|
|
175
|
-
|
|
176
|
-
|
|
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;
|
|
177
376
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const formatRed = createColorFormatter(useColor, red);
|
|
196
|
-
const formatDimText = (text) => formatDim(useColor, text);
|
|
197
|
-
lines.push(`${prefix}${formatRed("\u2716")} ${error.summary} (${error.code})`);
|
|
198
|
-
if (error.why) {
|
|
199
|
-
lines.push(`${prefix}${formatDimText(` Why: ${error.why}`)}`);
|
|
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);
|
|
200
394
|
}
|
|
201
|
-
|
|
202
|
-
|
|
395
|
+
async close() {
|
|
396
|
+
if (this.driver) {
|
|
397
|
+
await this.driver.close();
|
|
398
|
+
this.driver = null;
|
|
399
|
+
}
|
|
203
400
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
+
};
|
|
207
414
|
}
|
|
208
|
-
|
|
209
|
-
const
|
|
210
|
-
if (
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
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;
|
|
219
440
|
}
|
|
220
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
|
+
}
|
|
221
473
|
}
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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;
|
|
235
499
|
}
|
|
236
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
|
+
}
|
|
237
533
|
}
|
|
238
|
-
|
|
239
|
-
|
|
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
|
+
}
|
|
240
592
|
}
|
|
241
|
-
|
|
242
|
-
|
|
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
|
+
});
|
|
243
634
|
}
|
|
244
|
-
|
|
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
|
+
};
|
|
775
|
+
|
|
776
|
+
// src/utils/command-helpers.ts
|
|
777
|
+
function setCommandDescriptions(command, shortDescription, longDescription) {
|
|
778
|
+
command.description(shortDescription);
|
|
779
|
+
if (longDescription) {
|
|
780
|
+
command._longDescription = longDescription;
|
|
781
|
+
}
|
|
782
|
+
return command;
|
|
245
783
|
}
|
|
246
|
-
function
|
|
247
|
-
return
|
|
784
|
+
function getLongDescription(command) {
|
|
785
|
+
return command._longDescription;
|
|
248
786
|
}
|
|
249
|
-
|
|
787
|
+
|
|
788
|
+
// src/utils/global-flags.ts
|
|
789
|
+
function parseGlobalFlags(options) {
|
|
790
|
+
const flags = {};
|
|
791
|
+
if (options.json === true || options.json === "object") {
|
|
792
|
+
flags.json = "object";
|
|
793
|
+
} else if (options.json === "ndjson") {
|
|
794
|
+
flags.json = "ndjson";
|
|
795
|
+
}
|
|
796
|
+
if (options.quiet || options.q) {
|
|
797
|
+
flags.quiet = true;
|
|
798
|
+
}
|
|
799
|
+
if (options.vv || options.trace) {
|
|
800
|
+
flags.verbose = 2;
|
|
801
|
+
} else if (options.verbose || options.v) {
|
|
802
|
+
flags.verbose = 1;
|
|
803
|
+
} else {
|
|
804
|
+
flags.verbose = 0;
|
|
805
|
+
}
|
|
806
|
+
if (options.timestamps) {
|
|
807
|
+
flags.timestamps = true;
|
|
808
|
+
}
|
|
809
|
+
if (process.env["NO_COLOR"] || flags.json) {
|
|
810
|
+
flags.color = false;
|
|
811
|
+
} else if (options["no-color"]) {
|
|
812
|
+
flags.color = false;
|
|
813
|
+
} else if (options.color !== void 0) {
|
|
814
|
+
flags.color = options.color;
|
|
815
|
+
} else {
|
|
816
|
+
flags.color = process.stdout.isTTY && !process.env["CI"];
|
|
817
|
+
}
|
|
818
|
+
return flags;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// src/utils/output.ts
|
|
822
|
+
import { relative } from "path";
|
|
823
|
+
import { bgGreen, blue, bold, cyan, dim, green, magenta, red, yellow } from "colorette";
|
|
824
|
+
import stringWidth from "string-width";
|
|
825
|
+
import stripAnsi from "strip-ansi";
|
|
826
|
+
import wrapAnsi from "wrap-ansi";
|
|
827
|
+
function formatTimestamp() {
|
|
828
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
829
|
+
}
|
|
830
|
+
function createPrefix(flags) {
|
|
831
|
+
return flags.timestamps ? `[${formatTimestamp()}] ` : "";
|
|
832
|
+
}
|
|
833
|
+
function isVerbose(flags, level) {
|
|
834
|
+
return (flags.verbose ?? 0) >= level;
|
|
835
|
+
}
|
|
836
|
+
function createColorFormatter(useColor, colorFn) {
|
|
837
|
+
return useColor ? colorFn : (text) => text;
|
|
838
|
+
}
|
|
839
|
+
function formatDim(useColor, text) {
|
|
840
|
+
return useColor ? dim(text) : text;
|
|
841
|
+
}
|
|
842
|
+
function formatEmitOutput(result, flags) {
|
|
250
843
|
if (flags.quiet) {
|
|
251
844
|
return "";
|
|
252
845
|
}
|
|
253
846
|
const lines = [];
|
|
254
847
|
const prefix = createPrefix(flags);
|
|
255
|
-
const
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
lines.push(`${prefix}
|
|
262
|
-
if (result.contract.profileHash) {
|
|
263
|
-
lines.push(`${prefix}${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);
|
|
264
|
-
}
|
|
265
|
-
} else {
|
|
266
|
-
lines.push(`${prefix}${formatRed("\u2716")} ${result.summary} (${result.code})`);
|
|
848
|
+
const jsonPath = relative(process.cwd(), result.files.json);
|
|
849
|
+
const dtsPath = relative(process.cwd(), result.files.dts);
|
|
850
|
+
lines.push(`${prefix}\u2714 Emitted contract.json \u2192 ${jsonPath}`);
|
|
851
|
+
lines.push(`${prefix}\u2714 Emitted contract.d.ts \u2192 ${dtsPath}`);
|
|
852
|
+
lines.push(`${prefix} coreHash: ${result.coreHash}`);
|
|
853
|
+
if (result.profileHash) {
|
|
854
|
+
lines.push(`${prefix} profileHash: ${result.profileHash}`);
|
|
267
855
|
}
|
|
268
856
|
if (isVerbose(flags, 1)) {
|
|
269
|
-
|
|
270
|
-
lines.push(
|
|
271
|
-
`${prefix}${formatDimText(" Codec coverage check skipped (helper returned no supported types)")}`
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
lines.push(`${prefix}${formatDimText(` Total time: ${result.timings.total}ms`)}`);
|
|
857
|
+
lines.push(`${prefix} Total time: ${result.timings.total}ms`);
|
|
275
858
|
}
|
|
276
859
|
return lines.join("\n");
|
|
277
860
|
}
|
|
278
|
-
function
|
|
861
|
+
function formatEmitJson(result) {
|
|
279
862
|
const output = {
|
|
280
|
-
ok:
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
863
|
+
ok: true,
|
|
864
|
+
coreHash: result.coreHash,
|
|
865
|
+
...result.profileHash ? { profileHash: result.profileHash } : {},
|
|
866
|
+
outDir: result.outDir,
|
|
867
|
+
files: result.files,
|
|
868
|
+
timings: result.timings
|
|
869
|
+
};
|
|
870
|
+
return JSON.stringify(output, null, 2);
|
|
871
|
+
}
|
|
872
|
+
function formatErrorOutput(error, flags) {
|
|
873
|
+
const lines = [];
|
|
874
|
+
const prefix = createPrefix(flags);
|
|
875
|
+
const useColor = flags.color !== false;
|
|
876
|
+
const formatRed = createColorFormatter(useColor, red);
|
|
877
|
+
const formatDimText = (text) => formatDim(useColor, text);
|
|
878
|
+
lines.push(`${prefix}${formatRed("\u2716")} ${error.summary} (${error.code})`);
|
|
879
|
+
if (error.why) {
|
|
880
|
+
lines.push(`${prefix}${formatDimText(` Why: ${error.why}`)}`);
|
|
881
|
+
}
|
|
882
|
+
if (error.fix) {
|
|
883
|
+
lines.push(`${prefix}${formatDimText(` Fix: ${error.fix}`)}`);
|
|
884
|
+
}
|
|
885
|
+
if (error.where?.path) {
|
|
886
|
+
const whereLine = error.where.line ? `${error.where.path}:${error.where.line}` : error.where.path;
|
|
887
|
+
lines.push(`${prefix}${formatDimText(` Where: ${whereLine}`)}`);
|
|
888
|
+
}
|
|
889
|
+
if (error.meta?.["conflicts"]) {
|
|
890
|
+
const conflicts = error.meta["conflicts"];
|
|
891
|
+
if (conflicts.length > 0) {
|
|
892
|
+
const maxToShow = isVerbose(flags, 1) ? conflicts.length : Math.min(3, conflicts.length);
|
|
893
|
+
const header = isVerbose(flags, 1) ? " Conflicts:" : ` Conflicts (showing ${maxToShow} of ${conflicts.length}):`;
|
|
894
|
+
lines.push(`${prefix}${formatDimText(header)}`);
|
|
895
|
+
for (const conflict of conflicts.slice(0, maxToShow)) {
|
|
896
|
+
lines.push(`${prefix}${formatDimText(` - [${conflict.kind}] ${conflict.summary}`)}`);
|
|
897
|
+
}
|
|
898
|
+
if (!isVerbose(flags, 1) && conflicts.length > maxToShow) {
|
|
899
|
+
lines.push(`${prefix}${formatDimText(" Re-run with -v/--verbose to see all conflicts")}`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
if (error.meta?.["issues"]) {
|
|
904
|
+
const issues = error.meta["issues"];
|
|
905
|
+
if (issues.length > 0) {
|
|
906
|
+
const maxToShow = isVerbose(flags, 1) ? issues.length : Math.min(3, issues.length);
|
|
907
|
+
const header = isVerbose(flags, 1) ? " Issues:" : ` Issues (showing ${maxToShow} of ${issues.length}):`;
|
|
908
|
+
lines.push(`${prefix}${formatDimText(header)}`);
|
|
909
|
+
for (const issue of issues.slice(0, maxToShow)) {
|
|
910
|
+
const kind = issue.kind ?? "issue";
|
|
911
|
+
const message = issue.message ?? "";
|
|
912
|
+
lines.push(`${prefix}${formatDimText(` - [${kind}] ${message}`)}`);
|
|
913
|
+
}
|
|
914
|
+
if (!isVerbose(flags, 1) && issues.length > maxToShow) {
|
|
915
|
+
lines.push(`${prefix}${formatDimText(" Re-run with -v/--verbose to see all issues")}`);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
if (error.docsUrl && isVerbose(flags, 1)) {
|
|
920
|
+
lines.push(formatDimText(error.docsUrl));
|
|
921
|
+
}
|
|
922
|
+
if (isVerbose(flags, 2) && error.meta) {
|
|
923
|
+
lines.push(`${prefix}${formatDimText(` Meta: ${JSON.stringify(error.meta, null, 2)}`)}`);
|
|
924
|
+
}
|
|
925
|
+
return lines.join("\n");
|
|
926
|
+
}
|
|
927
|
+
function formatErrorJson(error) {
|
|
928
|
+
return JSON.stringify(error, null, 2);
|
|
929
|
+
}
|
|
930
|
+
function formatVerifyOutput(result, flags) {
|
|
931
|
+
if (flags.quiet) {
|
|
932
|
+
return "";
|
|
933
|
+
}
|
|
934
|
+
const lines = [];
|
|
935
|
+
const prefix = createPrefix(flags);
|
|
936
|
+
const useColor = flags.color !== false;
|
|
937
|
+
const formatGreen = createColorFormatter(useColor, green);
|
|
938
|
+
const formatRed = createColorFormatter(useColor, red);
|
|
939
|
+
const formatDimText = (text) => formatDim(useColor, text);
|
|
940
|
+
if (result.ok) {
|
|
941
|
+
lines.push(`${prefix}${formatGreen("\u2714")} ${result.summary}`);
|
|
942
|
+
lines.push(`${prefix}${formatDimText(` coreHash: ${result.contract.coreHash}`)}`);
|
|
943
|
+
if (result.contract.profileHash) {
|
|
944
|
+
lines.push(`${prefix}${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);
|
|
945
|
+
}
|
|
946
|
+
} else {
|
|
947
|
+
lines.push(`${prefix}${formatRed("\u2716")} ${result.summary} (${result.code})`);
|
|
948
|
+
}
|
|
949
|
+
if (isVerbose(flags, 1)) {
|
|
950
|
+
if (result.codecCoverageSkipped) {
|
|
951
|
+
lines.push(
|
|
952
|
+
`${prefix}${formatDimText(" Codec coverage check skipped (helper returned no supported types)")}`
|
|
953
|
+
);
|
|
954
|
+
}
|
|
955
|
+
lines.push(`${prefix}${formatDimText(` Total time: ${result.timings.total}ms`)}`);
|
|
956
|
+
}
|
|
957
|
+
return lines.join("\n");
|
|
958
|
+
}
|
|
959
|
+
function formatVerifyJson(result) {
|
|
960
|
+
const output = {
|
|
961
|
+
ok: result.ok,
|
|
962
|
+
...result.code ? { code: result.code } : {},
|
|
963
|
+
summary: result.summary,
|
|
964
|
+
contract: result.contract,
|
|
965
|
+
...result.marker ? { marker: result.marker } : {},
|
|
966
|
+
target: result.target,
|
|
286
967
|
...result.missingCodecs ? { missingCodecs: result.missingCodecs } : {},
|
|
287
968
|
...result.meta ? { meta: result.meta } : {},
|
|
288
969
|
timings: result.timings
|
|
@@ -949,698 +1630,100 @@ function formatCommandHelp(options) {
|
|
|
949
1630
|
if (opt.defaultValue !== void 0) {
|
|
950
1631
|
const emptyLabel = " ".repeat(LEFT_COLUMN_WIDTH);
|
|
951
1632
|
const defaultText = formatDefaultValue(opt.defaultValue, useColor);
|
|
952
|
-
lines.push(`${formatDimText("\u2502")} ${emptyLabel} ${defaultText}`);
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
const docsUrl = getCommandDocsUrl(commandPath);
|
|
957
|
-
if (docsUrl) {
|
|
958
|
-
lines.push(formatDimText("\u2502"));
|
|
959
|
-
lines.push(
|
|
960
|
-
formatReadMoreLine({
|
|
961
|
-
url: docsUrl,
|
|
962
|
-
maxLabelWidth: LEFT_COLUMN_WIDTH,
|
|
963
|
-
useColor,
|
|
964
|
-
formatDimText
|
|
965
|
-
})
|
|
966
|
-
);
|
|
967
|
-
}
|
|
968
|
-
if (longDescription) {
|
|
969
|
-
lines.push(formatDimText("\u2502"));
|
|
970
|
-
const descriptionLines = longDescription.split("\n").filter((line) => line.trim().length > 0);
|
|
971
|
-
lines.push(...formatMultilineDescription({ descriptionLines, useColor, formatDimText }));
|
|
972
|
-
}
|
|
973
|
-
lines.push(formatDimText("\u2514"));
|
|
974
|
-
return `${lines.join("\n")}
|
|
975
|
-
`;
|
|
976
|
-
}
|
|
977
|
-
function formatRootHelp(options) {
|
|
978
|
-
const { program: program2, flags } = options;
|
|
979
|
-
const lines = [];
|
|
980
|
-
const useColor = flags.color !== false;
|
|
981
|
-
const formatDimText = (text) => formatDim(useColor, text);
|
|
982
|
-
const brand = createPrismaNextBadge(useColor);
|
|
983
|
-
const shortDescription = "Manage your data layer";
|
|
984
|
-
const intent = formatDimText(shortDescription);
|
|
985
|
-
lines.push(formatHeaderLine({ brand, operation: "", intent }));
|
|
986
|
-
lines.push(formatDimText("\u2502"));
|
|
987
|
-
const topLevelCommands = program2.commands.filter(
|
|
988
|
-
(cmd) => !cmd.name().startsWith("_") && cmd.name() !== "help"
|
|
989
|
-
);
|
|
990
|
-
const globalOptions = program2.options.map((opt) => {
|
|
991
|
-
const flags2 = opt.flags;
|
|
992
|
-
const description = opt.description || "";
|
|
993
|
-
const defaultValue = opt.defaultValue;
|
|
994
|
-
return { flags: flags2, description, defaultValue };
|
|
995
|
-
});
|
|
996
|
-
if (topLevelCommands.length > 0) {
|
|
997
|
-
const hasItemsAfter = globalOptions.length > 0;
|
|
998
|
-
const treeLines = renderCommandTree({
|
|
999
|
-
commands: topLevelCommands,
|
|
1000
|
-
useColor,
|
|
1001
|
-
formatDimText,
|
|
1002
|
-
hasItemsAfter
|
|
1003
|
-
});
|
|
1004
|
-
lines.push(...treeLines);
|
|
1005
|
-
}
|
|
1006
|
-
if (topLevelCommands.length > 0 && globalOptions.length > 0) {
|
|
1007
|
-
lines.push(formatDimText("\u2502"));
|
|
1008
|
-
}
|
|
1009
|
-
if (globalOptions.length > 0) {
|
|
1010
|
-
for (const opt of globalOptions) {
|
|
1011
|
-
const flagsPadded = padToFixedWidth(opt.flags, LEFT_COLUMN_WIDTH);
|
|
1012
|
-
let flagsColored = flagsPadded;
|
|
1013
|
-
if (useColor) {
|
|
1014
|
-
flagsColored = flagsPadded.replace(/(<[^>]+>)/g, (match) => magenta(match));
|
|
1015
|
-
flagsColored = cyan(flagsColored);
|
|
1016
|
-
}
|
|
1017
|
-
const rightColumnWidth = calculateRightColumnWidth();
|
|
1018
|
-
const wrappedDescription = wrapTextAnsi(opt.description, rightColumnWidth);
|
|
1019
|
-
lines.push(`${formatDimText("\u2502")} ${flagsColored} ${wrappedDescription[0] || ""}`);
|
|
1020
|
-
for (let i = 1; i < wrappedDescription.length; i++) {
|
|
1021
|
-
const emptyLabel = " ".repeat(LEFT_COLUMN_WIDTH);
|
|
1022
|
-
lines.push(`${formatDimText("\u2502")} ${emptyLabel} ${wrappedDescription[i] || ""}`);
|
|
1023
|
-
}
|
|
1024
|
-
if (opt.defaultValue !== void 0) {
|
|
1025
|
-
const emptyLabel = " ".repeat(LEFT_COLUMN_WIDTH);
|
|
1026
|
-
const defaultText = formatDefaultValue(opt.defaultValue, useColor);
|
|
1027
|
-
lines.push(`${formatDimText("\u2502")} ${emptyLabel} ${defaultText}`);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
const formatGreen = (text) => useColor ? green(text) : text;
|
|
1032
|
-
const descriptionLines = [
|
|
1033
|
-
`Use ${formatGreen("Prisma Next")} to define your data layer as a contract. Sign your database and application with the same contract to guarantee compatibility. Plan and apply migrations to safely evolve your schema.`
|
|
1034
|
-
];
|
|
1035
|
-
if (descriptionLines.length > 0) {
|
|
1036
|
-
lines.push(formatDimText("\u2502"));
|
|
1037
|
-
lines.push(...formatMultilineDescription({ descriptionLines, useColor, formatDimText }));
|
|
1038
|
-
}
|
|
1039
|
-
lines.push(formatDimText("\u2514"));
|
|
1040
|
-
return `${lines.join("\n")}
|
|
1041
|
-
`;
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
// src/utils/result-handler.ts
|
|
1045
|
-
function handleResult(result, flags, onSuccess) {
|
|
1046
|
-
if (result.ok) {
|
|
1047
|
-
if (onSuccess) {
|
|
1048
|
-
onSuccess(result.value);
|
|
1049
|
-
}
|
|
1050
|
-
return 0;
|
|
1051
|
-
}
|
|
1052
|
-
const envelope = result.failure.toEnvelope();
|
|
1053
|
-
if (flags.json) {
|
|
1054
|
-
console.error(formatErrorJson(envelope));
|
|
1055
|
-
} else {
|
|
1056
|
-
console.error(formatErrorOutput(envelope, flags));
|
|
1057
|
-
}
|
|
1058
|
-
const exitCode = result.failure.domain === "CLI" ? 2 : 1;
|
|
1059
|
-
return exitCode;
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
// src/utils/spinner.ts
|
|
1063
|
-
import ora from "ora";
|
|
1064
|
-
async function withSpinner(operation, options) {
|
|
1065
|
-
const { message, flags } = options;
|
|
1066
|
-
const shouldShowSpinner = !flags.quiet && flags.json !== "object" && process.stdout.isTTY;
|
|
1067
|
-
if (!shouldShowSpinner) {
|
|
1068
|
-
return operation();
|
|
1069
|
-
}
|
|
1070
|
-
const startTime = Date.now();
|
|
1071
|
-
const spinner = ora({
|
|
1072
|
-
text: message,
|
|
1073
|
-
color: flags.color !== false ? "cyan" : false
|
|
1074
|
-
}).start();
|
|
1075
|
-
try {
|
|
1076
|
-
const result = await operation();
|
|
1077
|
-
const elapsed = Date.now() - startTime;
|
|
1078
|
-
spinner.succeed(`${message} (${elapsed}ms)`);
|
|
1079
|
-
return result;
|
|
1080
|
-
} catch (error) {
|
|
1081
|
-
spinner.fail(`${message} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1082
|
-
throw error;
|
|
1083
|
-
}
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
// src/commands/contract-emit.ts
|
|
1087
|
-
function createContractEmitCommand() {
|
|
1088
|
-
const command = new Command("emit");
|
|
1089
|
-
setCommandDescriptions(
|
|
1090
|
-
command,
|
|
1091
|
-
"Write your contract to JSON and sign it",
|
|
1092
|
-
"Reads your contract source (TypeScript or Prisma schema) and emits contract.json and\ncontract.d.ts. The contract.json contains the canonical contract structure, and\ncontract.d.ts provides TypeScript types for type-safe query building."
|
|
1093
|
-
);
|
|
1094
|
-
command.configureHelp({
|
|
1095
|
-
formatHelp: (cmd) => {
|
|
1096
|
-
const flags = parseGlobalFlags({});
|
|
1097
|
-
return formatCommandHelp({ command: cmd, flags });
|
|
1098
|
-
}
|
|
1099
|
-
}).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) => {
|
|
1100
|
-
const flags = parseGlobalFlags(options);
|
|
1101
|
-
const result = await performAction(async () => {
|
|
1102
|
-
const config = await loadConfig(options.config);
|
|
1103
|
-
if (!config.contract) {
|
|
1104
|
-
throw errorContractConfigMissing2({
|
|
1105
|
-
why: "Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ..., types: ... }"
|
|
1106
|
-
});
|
|
1107
|
-
}
|
|
1108
|
-
const contractConfig = config.contract;
|
|
1109
|
-
if (!contractConfig.output || !contractConfig.types) {
|
|
1110
|
-
throw errorContractConfigMissing2({
|
|
1111
|
-
why: "Contract config must have output and types paths. This should not happen if defineConfig() was used."
|
|
1112
|
-
});
|
|
1113
|
-
}
|
|
1114
|
-
const outputJsonPath = resolve2(contractConfig.output);
|
|
1115
|
-
const outputDtsPath = resolve2(contractConfig.types);
|
|
1116
|
-
if (flags.json !== "object" && !flags.quiet) {
|
|
1117
|
-
const configPath = options.config ? relative2(process.cwd(), resolve2(options.config)) : "prisma-next.config.ts";
|
|
1118
|
-
const contractPath = relative2(process.cwd(), outputJsonPath);
|
|
1119
|
-
const typesPath = relative2(process.cwd(), outputDtsPath);
|
|
1120
|
-
const header = formatStyledHeader({
|
|
1121
|
-
command: "contract emit",
|
|
1122
|
-
description: "Write your contract to JSON and sign it",
|
|
1123
|
-
url: "https://pris.ly/contract-emit",
|
|
1124
|
-
details: [
|
|
1125
|
-
{ label: "config", value: configPath },
|
|
1126
|
-
{ label: "contract", value: contractPath },
|
|
1127
|
-
{ label: "types", value: typesPath }
|
|
1128
|
-
],
|
|
1129
|
-
flags
|
|
1130
|
-
});
|
|
1131
|
-
console.log(header);
|
|
1132
|
-
}
|
|
1133
|
-
const stack = createControlPlaneStack({
|
|
1134
|
-
target: config.target,
|
|
1135
|
-
adapter: config.adapter,
|
|
1136
|
-
driver: config.driver,
|
|
1137
|
-
extensionPacks: config.extensionPacks
|
|
1138
|
-
});
|
|
1139
|
-
const familyInstance = config.family.create(stack);
|
|
1140
|
-
let contractRaw;
|
|
1141
|
-
if (typeof contractConfig.source === "function") {
|
|
1142
|
-
contractRaw = await contractConfig.source();
|
|
1143
|
-
} else {
|
|
1144
|
-
contractRaw = contractConfig.source;
|
|
1145
|
-
}
|
|
1146
|
-
const emitResult = await withSpinner(
|
|
1147
|
-
() => familyInstance.emitContract({ contractIR: contractRaw }),
|
|
1148
|
-
{
|
|
1149
|
-
message: "Emitting contract...",
|
|
1150
|
-
flags
|
|
1151
|
-
}
|
|
1152
|
-
);
|
|
1153
|
-
mkdirSync(dirname2(outputJsonPath), { recursive: true });
|
|
1154
|
-
mkdirSync(dirname2(outputDtsPath), { recursive: true });
|
|
1155
|
-
writeFileSync(outputJsonPath, emitResult.contractJson, "utf-8");
|
|
1156
|
-
writeFileSync(outputDtsPath, emitResult.contractDts, "utf-8");
|
|
1157
|
-
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
1158
|
-
console.log("");
|
|
1159
|
-
}
|
|
1160
|
-
return {
|
|
1161
|
-
coreHash: emitResult.coreHash,
|
|
1162
|
-
profileHash: emitResult.profileHash,
|
|
1163
|
-
outDir: dirname2(outputJsonPath),
|
|
1164
|
-
files: {
|
|
1165
|
-
json: outputJsonPath,
|
|
1166
|
-
dts: outputDtsPath
|
|
1167
|
-
},
|
|
1168
|
-
timings: {
|
|
1169
|
-
total: 0
|
|
1170
|
-
// Timing is handled by emitContract internally if needed
|
|
1171
|
-
}
|
|
1172
|
-
};
|
|
1173
|
-
});
|
|
1174
|
-
const exitCode = handleResult(result, flags, (emitResult) => {
|
|
1175
|
-
if (flags.json === "object") {
|
|
1176
|
-
console.log(formatEmitJson(emitResult));
|
|
1177
|
-
} else {
|
|
1178
|
-
const output = formatEmitOutput(emitResult, flags);
|
|
1179
|
-
if (output) {
|
|
1180
|
-
console.log(output);
|
|
1181
|
-
}
|
|
1182
|
-
if (!flags.quiet) {
|
|
1183
|
-
console.log(formatSuccessMessage(flags));
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
});
|
|
1187
|
-
process.exit(exitCode);
|
|
1188
|
-
});
|
|
1189
|
-
return command;
|
|
1190
|
-
}
|
|
1191
|
-
|
|
1192
|
-
// src/commands/db-init.ts
|
|
1193
|
-
import { readFile } from "fs/promises";
|
|
1194
|
-
import { relative as relative3, resolve as resolve3 } from "path";
|
|
1195
|
-
import { notOk as notOk3, ok as ok3 } from "@prisma-next/utils/result";
|
|
1196
|
-
import { Command as Command2 } from "commander";
|
|
1197
|
-
|
|
1198
|
-
// src/control-api/client.ts
|
|
1199
|
-
import { createControlPlaneStack as createControlPlaneStack2 } from "@prisma-next/core-control-plane/stack";
|
|
1200
|
-
|
|
1201
|
-
// src/utils/framework-components.ts
|
|
1202
|
-
import {
|
|
1203
|
-
checkContractComponentRequirements
|
|
1204
|
-
} from "@prisma-next/contract/framework-components";
|
|
1205
|
-
function assertFrameworkComponentsCompatible(expectedFamilyId, expectedTargetId, frameworkComponents) {
|
|
1206
|
-
for (let i = 0; i < frameworkComponents.length; i++) {
|
|
1207
|
-
const component = frameworkComponents[i];
|
|
1208
|
-
if (typeof component !== "object" || component === null) {
|
|
1209
|
-
throw errorConfigValidation("frameworkComponents[]", {
|
|
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
|
-
});
|
|
1224
|
-
}
|
|
1225
|
-
if (!Object.hasOwn(record, "familyId")) {
|
|
1226
|
-
throw errorConfigValidation("frameworkComponents[].familyId", {
|
|
1227
|
-
why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'familyId' property`
|
|
1228
|
-
});
|
|
1229
|
-
}
|
|
1230
|
-
const familyId = record["familyId"];
|
|
1231
|
-
if (familyId !== expectedFamilyId) {
|
|
1232
|
-
throw errorConfigValidation("frameworkComponents[].familyId", {
|
|
1233
|
-
why: `Framework component at index ${i} (kind: ${String(kind)}) has familyId '${String(familyId)}' but expected '${expectedFamilyId}'`
|
|
1234
|
-
});
|
|
1235
|
-
}
|
|
1236
|
-
if (!Object.hasOwn(record, "targetId")) {
|
|
1237
|
-
throw errorConfigValidation("frameworkComponents[].targetId", {
|
|
1238
|
-
why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'targetId' property`
|
|
1239
|
-
});
|
|
1240
|
-
}
|
|
1241
|
-
const targetId = record["targetId"];
|
|
1242
|
-
if (targetId !== expectedTargetId) {
|
|
1243
|
-
throw errorConfigValidation("frameworkComponents[].targetId", {
|
|
1244
|
-
why: `Framework component at index ${i} (kind: ${String(kind)}) has targetId '${String(targetId)}' but expected '${expectedTargetId}'`
|
|
1245
|
-
});
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
return frameworkComponents;
|
|
1249
|
-
}
|
|
1250
|
-
function assertContractRequirementsSatisfied({
|
|
1251
|
-
contract,
|
|
1252
|
-
stack
|
|
1253
|
-
}) {
|
|
1254
|
-
const providedComponentIds = /* @__PURE__ */ new Set([stack.target.id, stack.adapter.id]);
|
|
1255
|
-
for (const extension of stack.extensionPacks) {
|
|
1256
|
-
providedComponentIds.add(extension.id);
|
|
1257
|
-
}
|
|
1258
|
-
const result = checkContractComponentRequirements({
|
|
1259
|
-
contract,
|
|
1260
|
-
expectedTargetFamily: stack.target.familyId,
|
|
1261
|
-
expectedTargetId: stack.target.targetId,
|
|
1262
|
-
providedComponentIds
|
|
1263
|
-
});
|
|
1264
|
-
if (result.familyMismatch) {
|
|
1265
|
-
throw errorConfigValidation("contract.targetFamily", {
|
|
1266
|
-
why: `Contract was emitted for family '${result.familyMismatch.actual}' but CLI config is wired to '${result.familyMismatch.expected}'.`
|
|
1267
|
-
});
|
|
1268
|
-
}
|
|
1269
|
-
if (result.targetMismatch) {
|
|
1270
|
-
throw errorConfigValidation("contract.target", {
|
|
1271
|
-
why: `Contract target '${result.targetMismatch.actual}' does not match CLI target '${result.targetMismatch.expected}'.`
|
|
1272
|
-
});
|
|
1273
|
-
}
|
|
1274
|
-
if (result.missingExtensionPackIds.length > 0) {
|
|
1275
|
-
throw errorContractMissingExtensionPacks({
|
|
1276
|
-
missingExtensionPacks: result.missingExtensionPackIds,
|
|
1277
|
-
providedComponentIds: [...providedComponentIds]
|
|
1278
|
-
});
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
|
|
1282
|
-
// src/control-api/operations/db-init.ts
|
|
1283
|
-
import { notOk as notOk2, ok as ok2 } from "@prisma-next/utils/result";
|
|
1284
|
-
async function executeDbInit(options) {
|
|
1285
|
-
const { driver, familyInstance, contractIR, mode, migrations, frameworkComponents, onProgress } = options;
|
|
1286
|
-
const planner = migrations.createPlanner(familyInstance);
|
|
1287
|
-
const runner = migrations.createRunner(familyInstance);
|
|
1288
|
-
const introspectSpanId = "introspect";
|
|
1289
|
-
onProgress?.({
|
|
1290
|
-
action: "dbInit",
|
|
1291
|
-
kind: "spanStart",
|
|
1292
|
-
spanId: introspectSpanId,
|
|
1293
|
-
label: "Introspecting database schema"
|
|
1294
|
-
});
|
|
1295
|
-
const schemaIR = await familyInstance.introspect({ driver });
|
|
1296
|
-
onProgress?.({
|
|
1297
|
-
action: "dbInit",
|
|
1298
|
-
kind: "spanEnd",
|
|
1299
|
-
spanId: introspectSpanId,
|
|
1300
|
-
outcome: "ok"
|
|
1301
|
-
});
|
|
1302
|
-
const policy = { allowedOperationClasses: ["additive"] };
|
|
1303
|
-
const planSpanId = "plan";
|
|
1304
|
-
onProgress?.({
|
|
1305
|
-
action: "dbInit",
|
|
1306
|
-
kind: "spanStart",
|
|
1307
|
-
spanId: planSpanId,
|
|
1308
|
-
label: "Planning migration"
|
|
1309
|
-
});
|
|
1310
|
-
const plannerResult = await planner.plan({
|
|
1311
|
-
contract: contractIR,
|
|
1312
|
-
schema: schemaIR,
|
|
1313
|
-
policy,
|
|
1314
|
-
frameworkComponents
|
|
1315
|
-
});
|
|
1316
|
-
if (plannerResult.kind === "failure") {
|
|
1317
|
-
onProgress?.({
|
|
1318
|
-
action: "dbInit",
|
|
1319
|
-
kind: "spanEnd",
|
|
1320
|
-
spanId: planSpanId,
|
|
1321
|
-
outcome: "error"
|
|
1322
|
-
});
|
|
1323
|
-
return notOk2({
|
|
1324
|
-
code: "PLANNING_FAILED",
|
|
1325
|
-
summary: "Migration planning failed due to conflicts",
|
|
1326
|
-
conflicts: plannerResult.conflicts,
|
|
1327
|
-
why: void 0,
|
|
1328
|
-
meta: void 0
|
|
1329
|
-
});
|
|
1330
|
-
}
|
|
1331
|
-
const migrationPlan = plannerResult.plan;
|
|
1332
|
-
onProgress?.({
|
|
1333
|
-
action: "dbInit",
|
|
1334
|
-
kind: "spanEnd",
|
|
1335
|
-
spanId: planSpanId,
|
|
1336
|
-
outcome: "ok"
|
|
1337
|
-
});
|
|
1338
|
-
const checkMarkerSpanId = "checkMarker";
|
|
1339
|
-
onProgress?.({
|
|
1340
|
-
action: "dbInit",
|
|
1341
|
-
kind: "spanStart",
|
|
1342
|
-
spanId: checkMarkerSpanId,
|
|
1343
|
-
label: "Checking contract marker"
|
|
1344
|
-
});
|
|
1345
|
-
const existingMarker = await familyInstance.readMarker({ driver });
|
|
1346
|
-
if (existingMarker) {
|
|
1347
|
-
const markerMatchesDestination = existingMarker.coreHash === migrationPlan.destination.coreHash && (!migrationPlan.destination.profileHash || existingMarker.profileHash === migrationPlan.destination.profileHash);
|
|
1348
|
-
if (markerMatchesDestination) {
|
|
1349
|
-
onProgress?.({
|
|
1350
|
-
action: "dbInit",
|
|
1351
|
-
kind: "spanEnd",
|
|
1352
|
-
spanId: checkMarkerSpanId,
|
|
1353
|
-
outcome: "skipped"
|
|
1354
|
-
});
|
|
1355
|
-
const result2 = {
|
|
1356
|
-
mode,
|
|
1357
|
-
plan: { operations: [] },
|
|
1358
|
-
...mode === "apply" ? {
|
|
1359
|
-
execution: { operationsPlanned: 0, operationsExecuted: 0 },
|
|
1360
|
-
marker: {
|
|
1361
|
-
coreHash: existingMarker.coreHash,
|
|
1362
|
-
profileHash: existingMarker.profileHash
|
|
1363
|
-
}
|
|
1364
|
-
} : {},
|
|
1365
|
-
summary: "Database already at target contract state"
|
|
1366
|
-
};
|
|
1367
|
-
return ok2(result2);
|
|
1368
|
-
}
|
|
1369
|
-
onProgress?.({
|
|
1370
|
-
action: "dbInit",
|
|
1371
|
-
kind: "spanEnd",
|
|
1372
|
-
spanId: checkMarkerSpanId,
|
|
1373
|
-
outcome: "error"
|
|
1374
|
-
});
|
|
1375
|
-
return notOk2({
|
|
1376
|
-
code: "MARKER_ORIGIN_MISMATCH",
|
|
1377
|
-
summary: "Existing contract marker does not match plan destination",
|
|
1378
|
-
marker: {
|
|
1379
|
-
coreHash: existingMarker.coreHash,
|
|
1380
|
-
profileHash: existingMarker.profileHash
|
|
1381
|
-
},
|
|
1382
|
-
destination: {
|
|
1383
|
-
coreHash: migrationPlan.destination.coreHash,
|
|
1384
|
-
profileHash: migrationPlan.destination.profileHash
|
|
1385
|
-
},
|
|
1386
|
-
why: void 0,
|
|
1387
|
-
conflicts: void 0,
|
|
1388
|
-
meta: void 0
|
|
1389
|
-
});
|
|
1390
|
-
}
|
|
1391
|
-
onProgress?.({
|
|
1392
|
-
action: "dbInit",
|
|
1393
|
-
kind: "spanEnd",
|
|
1394
|
-
spanId: checkMarkerSpanId,
|
|
1395
|
-
outcome: "ok"
|
|
1396
|
-
});
|
|
1397
|
-
if (mode === "plan") {
|
|
1398
|
-
const result2 = {
|
|
1399
|
-
mode: "plan",
|
|
1400
|
-
plan: { operations: migrationPlan.operations },
|
|
1401
|
-
summary: `Planned ${migrationPlan.operations.length} operation(s)`
|
|
1402
|
-
};
|
|
1403
|
-
return ok2(result2);
|
|
1404
|
-
}
|
|
1405
|
-
const applySpanId = "apply";
|
|
1406
|
-
onProgress?.({
|
|
1407
|
-
action: "dbInit",
|
|
1408
|
-
kind: "spanStart",
|
|
1409
|
-
spanId: applySpanId,
|
|
1410
|
-
label: "Applying migration plan"
|
|
1411
|
-
});
|
|
1412
|
-
const callbacks = onProgress ? {
|
|
1413
|
-
onOperationStart: (op) => {
|
|
1414
|
-
onProgress({
|
|
1415
|
-
action: "dbInit",
|
|
1416
|
-
kind: "spanStart",
|
|
1417
|
-
spanId: `operation:${op.id}`,
|
|
1418
|
-
parentSpanId: applySpanId,
|
|
1419
|
-
label: op.label
|
|
1420
|
-
});
|
|
1421
|
-
},
|
|
1422
|
-
onOperationComplete: (op) => {
|
|
1423
|
-
onProgress({
|
|
1424
|
-
action: "dbInit",
|
|
1425
|
-
kind: "spanEnd",
|
|
1426
|
-
spanId: `operation:${op.id}`,
|
|
1427
|
-
outcome: "ok"
|
|
1428
|
-
});
|
|
1429
|
-
}
|
|
1430
|
-
} : void 0;
|
|
1431
|
-
const runnerResult = await runner.execute({
|
|
1432
|
-
plan: migrationPlan,
|
|
1433
|
-
driver,
|
|
1434
|
-
destinationContract: contractIR,
|
|
1435
|
-
policy,
|
|
1436
|
-
...callbacks ? { callbacks } : {},
|
|
1437
|
-
// db init plans and applies back-to-back from a fresh introspection, so per-operation
|
|
1438
|
-
// pre/postchecks and the idempotency probe are usually redundant overhead. We still
|
|
1439
|
-
// enforce marker/origin compatibility and a full schema verification after apply.
|
|
1440
|
-
executionChecks: {
|
|
1441
|
-
prechecks: false,
|
|
1442
|
-
postchecks: false,
|
|
1443
|
-
idempotencyChecks: false
|
|
1444
|
-
},
|
|
1445
|
-
frameworkComponents
|
|
1446
|
-
});
|
|
1447
|
-
if (!runnerResult.ok) {
|
|
1448
|
-
onProgress?.({
|
|
1449
|
-
action: "dbInit",
|
|
1450
|
-
kind: "spanEnd",
|
|
1451
|
-
spanId: applySpanId,
|
|
1452
|
-
outcome: "error"
|
|
1453
|
-
});
|
|
1454
|
-
return notOk2({
|
|
1455
|
-
code: "RUNNER_FAILED",
|
|
1456
|
-
summary: runnerResult.failure.summary,
|
|
1457
|
-
why: runnerResult.failure.why,
|
|
1458
|
-
meta: runnerResult.failure.meta,
|
|
1459
|
-
conflicts: void 0
|
|
1460
|
-
});
|
|
1461
|
-
}
|
|
1462
|
-
const execution = runnerResult.value;
|
|
1463
|
-
onProgress?.({
|
|
1464
|
-
action: "dbInit",
|
|
1465
|
-
kind: "spanEnd",
|
|
1466
|
-
spanId: applySpanId,
|
|
1467
|
-
outcome: "ok"
|
|
1468
|
-
});
|
|
1469
|
-
const result = {
|
|
1470
|
-
mode: "apply",
|
|
1471
|
-
plan: { operations: migrationPlan.operations },
|
|
1472
|
-
execution: {
|
|
1473
|
-
operationsPlanned: execution.operationsPlanned,
|
|
1474
|
-
operationsExecuted: execution.operationsExecuted
|
|
1475
|
-
},
|
|
1476
|
-
marker: migrationPlan.destination.profileHash ? {
|
|
1477
|
-
coreHash: migrationPlan.destination.coreHash,
|
|
1478
|
-
profileHash: migrationPlan.destination.profileHash
|
|
1479
|
-
} : { coreHash: migrationPlan.destination.coreHash },
|
|
1480
|
-
summary: `Applied ${execution.operationsExecuted} operation(s), marker written`
|
|
1481
|
-
};
|
|
1482
|
-
return ok2(result);
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
// src/control-api/client.ts
|
|
1486
|
-
function createControlClient(options) {
|
|
1487
|
-
return new ControlClientImpl(options);
|
|
1488
|
-
}
|
|
1489
|
-
var ControlClientImpl = class {
|
|
1490
|
-
options;
|
|
1491
|
-
stack = null;
|
|
1492
|
-
driver = null;
|
|
1493
|
-
familyInstance = null;
|
|
1494
|
-
frameworkComponents = null;
|
|
1495
|
-
initialized = false;
|
|
1496
|
-
defaultConnection;
|
|
1497
|
-
constructor(options) {
|
|
1498
|
-
this.options = options;
|
|
1499
|
-
this.defaultConnection = options.connection;
|
|
1500
|
-
}
|
|
1501
|
-
init() {
|
|
1502
|
-
if (this.initialized) {
|
|
1503
|
-
return;
|
|
1504
|
-
}
|
|
1505
|
-
this.stack = createControlPlaneStack2({
|
|
1506
|
-
target: this.options.target,
|
|
1507
|
-
adapter: this.options.adapter,
|
|
1508
|
-
driver: this.options.driver,
|
|
1509
|
-
extensionPacks: this.options.extensionPacks
|
|
1510
|
-
});
|
|
1511
|
-
this.familyInstance = this.options.family.create(this.stack);
|
|
1512
|
-
const rawComponents = [
|
|
1513
|
-
this.options.target,
|
|
1514
|
-
this.options.adapter,
|
|
1515
|
-
...this.options.extensionPacks ?? []
|
|
1516
|
-
];
|
|
1517
|
-
this.frameworkComponents = assertFrameworkComponentsCompatible(
|
|
1518
|
-
this.options.family.familyId,
|
|
1519
|
-
this.options.target.targetId,
|
|
1520
|
-
rawComponents
|
|
1521
|
-
);
|
|
1522
|
-
this.initialized = true;
|
|
1523
|
-
}
|
|
1524
|
-
async connect(connection) {
|
|
1525
|
-
this.init();
|
|
1526
|
-
if (this.driver) {
|
|
1527
|
-
throw new Error("Already connected. Call close() before reconnecting.");
|
|
1528
|
-
}
|
|
1529
|
-
const resolvedConnection = connection ?? this.defaultConnection;
|
|
1530
|
-
if (resolvedConnection === void 0) {
|
|
1531
|
-
throw new Error(
|
|
1532
|
-
"No connection provided. Pass a connection to connect() or provide a default connection when creating the client."
|
|
1533
|
-
);
|
|
1534
|
-
}
|
|
1535
|
-
if (!this.stack?.driver) {
|
|
1536
|
-
throw new Error(
|
|
1537
|
-
"Driver is not configured. Pass a driver descriptor when creating the control client to enable database operations."
|
|
1538
|
-
);
|
|
1539
|
-
}
|
|
1540
|
-
this.driver = await this.stack?.driver.create(resolvedConnection);
|
|
1541
|
-
}
|
|
1542
|
-
async close() {
|
|
1543
|
-
if (this.driver) {
|
|
1544
|
-
await this.driver.close();
|
|
1545
|
-
this.driver = null;
|
|
1633
|
+
lines.push(`${formatDimText("\u2502")} ${emptyLabel} ${defaultText}`);
|
|
1634
|
+
}
|
|
1546
1635
|
}
|
|
1547
1636
|
}
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
frameworkComponents: this.frameworkComponents
|
|
1560
|
-
};
|
|
1637
|
+
const docsUrl = getCommandDocsUrl(commandPath);
|
|
1638
|
+
if (docsUrl) {
|
|
1639
|
+
lines.push(formatDimText("\u2502"));
|
|
1640
|
+
lines.push(
|
|
1641
|
+
formatReadMoreLine({
|
|
1642
|
+
url: docsUrl,
|
|
1643
|
+
maxLabelWidth: LEFT_COLUMN_WIDTH,
|
|
1644
|
+
useColor,
|
|
1645
|
+
formatDimText
|
|
1646
|
+
})
|
|
1647
|
+
);
|
|
1561
1648
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
const
|
|
1565
|
-
|
|
1566
|
-
driver,
|
|
1567
|
-
contractIR,
|
|
1568
|
-
expectedTargetId: this.options.target.targetId,
|
|
1569
|
-
contractPath: ""
|
|
1570
|
-
});
|
|
1649
|
+
if (longDescription) {
|
|
1650
|
+
lines.push(formatDimText("\u2502"));
|
|
1651
|
+
const descriptionLines = longDescription.split("\n").filter((line) => line.trim().length > 0);
|
|
1652
|
+
lines.push(...formatMultilineDescription({ descriptionLines, useColor, formatDimText }));
|
|
1571
1653
|
}
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1654
|
+
lines.push(formatDimText("\u2514"));
|
|
1655
|
+
return `${lines.join("\n")}
|
|
1656
|
+
`;
|
|
1657
|
+
}
|
|
1658
|
+
function formatRootHelp(options) {
|
|
1659
|
+
const { program: program2, flags } = options;
|
|
1660
|
+
const lines = [];
|
|
1661
|
+
const useColor = flags.color !== false;
|
|
1662
|
+
const formatDimText = (text) => formatDim(useColor, text);
|
|
1663
|
+
const brand = createPrismaNextBadge(useColor);
|
|
1664
|
+
const shortDescription = "Manage your data layer";
|
|
1665
|
+
const intent = formatDimText(shortDescription);
|
|
1666
|
+
lines.push(formatHeaderLine({ brand, operation: "", intent }));
|
|
1667
|
+
lines.push(formatDimText("\u2502"));
|
|
1668
|
+
const topLevelCommands = program2.commands.filter(
|
|
1669
|
+
(cmd) => !cmd.name().startsWith("_") && cmd.name() !== "help"
|
|
1670
|
+
);
|
|
1671
|
+
const globalOptions = program2.options.map((opt) => {
|
|
1672
|
+
const flags2 = opt.flags;
|
|
1673
|
+
const description = opt.description || "";
|
|
1674
|
+
const defaultValue = opt.defaultValue;
|
|
1675
|
+
return { flags: flags2, description, defaultValue };
|
|
1676
|
+
});
|
|
1677
|
+
if (topLevelCommands.length > 0) {
|
|
1678
|
+
const hasItemsAfter = globalOptions.length > 0;
|
|
1679
|
+
const treeLines = renderCommandTree({
|
|
1680
|
+
commands: topLevelCommands,
|
|
1681
|
+
useColor,
|
|
1682
|
+
formatDimText,
|
|
1683
|
+
hasItemsAfter
|
|
1581
1684
|
});
|
|
1685
|
+
lines.push(...treeLines);
|
|
1582
1686
|
}
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
const contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
1586
|
-
return familyInstance.sign({
|
|
1587
|
-
driver,
|
|
1588
|
-
contractIR,
|
|
1589
|
-
contractPath: ""
|
|
1590
|
-
});
|
|
1687
|
+
if (topLevelCommands.length > 0 && globalOptions.length > 0) {
|
|
1688
|
+
lines.push(formatDimText("\u2502"));
|
|
1591
1689
|
}
|
|
1592
|
-
|
|
1593
|
-
const
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
action: "dbInit",
|
|
1612
|
-
kind: "spanEnd",
|
|
1613
|
-
spanId: "connect",
|
|
1614
|
-
outcome: "error"
|
|
1615
|
-
});
|
|
1616
|
-
throw error;
|
|
1690
|
+
if (globalOptions.length > 0) {
|
|
1691
|
+
for (const opt of globalOptions) {
|
|
1692
|
+
const flagsPadded = padToFixedWidth(opt.flags, LEFT_COLUMN_WIDTH);
|
|
1693
|
+
let flagsColored = flagsPadded;
|
|
1694
|
+
if (useColor) {
|
|
1695
|
+
flagsColored = flagsPadded.replace(/(<[^>]+>)/g, (match) => magenta(match));
|
|
1696
|
+
flagsColored = cyan(flagsColored);
|
|
1697
|
+
}
|
|
1698
|
+
const rightColumnWidth = calculateRightColumnWidth();
|
|
1699
|
+
const wrappedDescription = wrapTextAnsi(opt.description, rightColumnWidth);
|
|
1700
|
+
lines.push(`${formatDimText("\u2502")} ${flagsColored} ${wrappedDescription[0] || ""}`);
|
|
1701
|
+
for (let i = 1; i < wrappedDescription.length; i++) {
|
|
1702
|
+
const emptyLabel = " ".repeat(LEFT_COLUMN_WIDTH);
|
|
1703
|
+
lines.push(`${formatDimText("\u2502")} ${emptyLabel} ${wrappedDescription[i] || ""}`);
|
|
1704
|
+
}
|
|
1705
|
+
if (opt.defaultValue !== void 0) {
|
|
1706
|
+
const emptyLabel = " ".repeat(LEFT_COLUMN_WIDTH);
|
|
1707
|
+
const defaultText = formatDefaultValue(opt.defaultValue, useColor);
|
|
1708
|
+
lines.push(`${formatDimText("\u2502")} ${emptyLabel} ${defaultText}`);
|
|
1617
1709
|
}
|
|
1618
1710
|
}
|
|
1619
|
-
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
1620
|
-
if (!this.options.target.migrations) {
|
|
1621
|
-
throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
|
|
1622
|
-
}
|
|
1623
|
-
const contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
1624
|
-
return executeDbInit({
|
|
1625
|
-
driver,
|
|
1626
|
-
familyInstance,
|
|
1627
|
-
contractIR,
|
|
1628
|
-
mode: options.mode,
|
|
1629
|
-
migrations: this.options.target.migrations,
|
|
1630
|
-
frameworkComponents,
|
|
1631
|
-
...onProgress ? { onProgress } : {}
|
|
1632
|
-
});
|
|
1633
1711
|
}
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1712
|
+
const formatGreen = (text) => useColor ? green(text) : text;
|
|
1713
|
+
const descriptionLines = [
|
|
1714
|
+
`Use ${formatGreen("Prisma Next")} to define your data layer as a contract. Sign your database and application with the same contract to guarantee compatibility. Plan and apply migrations to safely evolve your schema.`
|
|
1715
|
+
];
|
|
1716
|
+
if (descriptionLines.length > 0) {
|
|
1717
|
+
lines.push(formatDimText("\u2502"));
|
|
1718
|
+
lines.push(...formatMultilineDescription({ descriptionLines, useColor, formatDimText }));
|
|
1639
1719
|
}
|
|
1640
|
-
|
|
1720
|
+
lines.push(formatDimText("\u2514"));
|
|
1721
|
+
return `${lines.join("\n")}
|
|
1722
|
+
`;
|
|
1723
|
+
}
|
|
1641
1724
|
|
|
1642
1725
|
// src/utils/progress-adapter.ts
|
|
1643
|
-
import
|
|
1726
|
+
import ora from "ora";
|
|
1644
1727
|
function createProgressAdapter(options) {
|
|
1645
1728
|
const { flags } = options;
|
|
1646
1729
|
const shouldShowProgress = !flags.quiet && flags.json !== "object" && process.stdout.isTTY;
|
|
@@ -1648,39 +1731,211 @@ function createProgressAdapter(options) {
|
|
|
1648
1731
|
return () => {
|
|
1649
1732
|
};
|
|
1650
1733
|
}
|
|
1651
|
-
const activeSpans = /* @__PURE__ */ new Map();
|
|
1652
|
-
return (event) => {
|
|
1653
|
-
if (event.kind === "spanStart") {
|
|
1654
|
-
if (event.parentSpanId) {
|
|
1655
|
-
console.log(` \u2192 ${event.label}...`);
|
|
1656
|
-
return;
|
|
1657
|
-
}
|
|
1658
|
-
const spinner =
|
|
1659
|
-
text: event.label,
|
|
1660
|
-
color: flags.color !== false ? "cyan" : false
|
|
1661
|
-
}).start();
|
|
1662
|
-
activeSpans.set(event.spanId, {
|
|
1663
|
-
spinner,
|
|
1664
|
-
startTime: Date.now()
|
|
1665
|
-
});
|
|
1666
|
-
} else if (event.kind === "spanEnd") {
|
|
1667
|
-
const spanState = activeSpans.get(event.spanId);
|
|
1668
|
-
if (spanState) {
|
|
1669
|
-
const elapsed = Date.now() - spanState.startTime;
|
|
1670
|
-
if (event.outcome === "error") {
|
|
1671
|
-
spanState.spinner.fail(`${spanState.spinner.text} (failed)`);
|
|
1672
|
-
} else if (event.outcome === "skipped") {
|
|
1673
|
-
spanState.spinner.info(`${spanState.spinner.text} (skipped)`);
|
|
1674
|
-
} else {
|
|
1675
|
-
spanState.spinner.succeed(`${spanState.spinner.text} (${elapsed}ms)`);
|
|
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
|
+
|
|
1766
|
+
// src/utils/result-handler.ts
|
|
1767
|
+
function handleResult(result, flags, onSuccess) {
|
|
1768
|
+
if (result.ok) {
|
|
1769
|
+
if (onSuccess) {
|
|
1770
|
+
onSuccess(result.value);
|
|
1771
|
+
}
|
|
1772
|
+
return 0;
|
|
1773
|
+
}
|
|
1774
|
+
const envelope = result.failure.toEnvelope();
|
|
1775
|
+
if (flags.json) {
|
|
1776
|
+
console.error(formatErrorJson(envelope));
|
|
1777
|
+
} else {
|
|
1778
|
+
console.error(formatErrorOutput(envelope, flags));
|
|
1779
|
+
}
|
|
1780
|
+
const exitCode = result.failure.domain === "CLI" ? 2 : 1;
|
|
1781
|
+
return exitCode;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
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 });
|
|
1856
|
+
try {
|
|
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);
|
|
1887
|
+
} catch (error) {
|
|
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();
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
function createContractEmitCommand() {
|
|
1901
|
+
const command = new Command("emit");
|
|
1902
|
+
setCommandDescriptions(
|
|
1903
|
+
command,
|
|
1904
|
+
"Write your contract to JSON and sign it",
|
|
1905
|
+
"Reads your contract source (TypeScript or Prisma schema) and emits contract.json and\ncontract.d.ts. The contract.json contains the canonical contract structure, and\ncontract.d.ts provides TypeScript types for type-safe query building."
|
|
1906
|
+
);
|
|
1907
|
+
command.configureHelp({
|
|
1908
|
+
formatHelp: (cmd) => {
|
|
1909
|
+
const flags = parseGlobalFlags({});
|
|
1910
|
+
return formatCommandHelp({ command: cmd, flags });
|
|
1911
|
+
}
|
|
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) => {
|
|
1913
|
+
const flags = parseGlobalFlags(options);
|
|
1914
|
+
const startTime = Date.now();
|
|
1915
|
+
const result = await executeContractEmitCommand(options, flags, startTime);
|
|
1916
|
+
const exitCode = handleResult(result, flags, (emitResult) => {
|
|
1917
|
+
if (flags.json === "object") {
|
|
1918
|
+
console.log(formatEmitJson(emitResult));
|
|
1919
|
+
} else {
|
|
1920
|
+
const output = formatEmitOutput(emitResult, flags);
|
|
1921
|
+
if (output) {
|
|
1922
|
+
console.log(output);
|
|
1923
|
+
}
|
|
1924
|
+
if (!flags.quiet) {
|
|
1925
|
+
console.log(formatSuccessMessage(flags));
|
|
1676
1926
|
}
|
|
1677
|
-
activeSpans.delete(event.spanId);
|
|
1678
1927
|
}
|
|
1679
|
-
}
|
|
1680
|
-
|
|
1928
|
+
});
|
|
1929
|
+
process.exit(exitCode);
|
|
1930
|
+
});
|
|
1931
|
+
return command;
|
|
1681
1932
|
}
|
|
1682
1933
|
|
|
1683
1934
|
// src/commands/db-init.ts
|
|
1935
|
+
import { readFile } from "fs/promises";
|
|
1936
|
+
import { relative as relative3, resolve as resolve3 } from "path";
|
|
1937
|
+
import { notOk as notOk4, ok as ok4 } from "@prisma-next/utils/result";
|
|
1938
|
+
import { Command as Command2 } from "commander";
|
|
1684
1939
|
function mapDbInitFailure(failure) {
|
|
1685
1940
|
if (failure.code === "PLANNING_FAILED") {
|
|
1686
1941
|
return errorMigrationPlanningFailed({ conflicts: failure.conflicts ?? [] });
|
|
@@ -1755,14 +2010,14 @@ async function executeDbInitCommand(options, flags, startTime) {
|
|
|
1755
2010
|
contractJsonContent = await readFile(contractPathAbsolute, "utf-8");
|
|
1756
2011
|
} catch (error) {
|
|
1757
2012
|
if (error instanceof Error && error.code === "ENOENT") {
|
|
1758
|
-
return
|
|
2013
|
+
return notOk4(
|
|
1759
2014
|
errorFileNotFound(contractPathAbsolute, {
|
|
1760
2015
|
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
1761
2016
|
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
|
|
1762
2017
|
})
|
|
1763
2018
|
);
|
|
1764
2019
|
}
|
|
1765
|
-
return
|
|
2020
|
+
return notOk4(
|
|
1766
2021
|
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
1767
2022
|
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
1768
2023
|
})
|
|
@@ -1772,7 +2027,7 @@ async function executeDbInitCommand(options, flags, startTime) {
|
|
|
1772
2027
|
try {
|
|
1773
2028
|
contractJson = JSON.parse(contractJsonContent);
|
|
1774
2029
|
} catch (error) {
|
|
1775
|
-
return
|
|
2030
|
+
return notOk4(
|
|
1776
2031
|
errorContractValidationFailed(
|
|
1777
2032
|
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
1778
2033
|
{ where: { path: contractPathAbsolute } }
|
|
@@ -1781,17 +2036,17 @@ async function executeDbInitCommand(options, flags, startTime) {
|
|
|
1781
2036
|
}
|
|
1782
2037
|
const dbConnection = options.db ?? config.db?.connection;
|
|
1783
2038
|
if (!dbConnection) {
|
|
1784
|
-
return
|
|
2039
|
+
return notOk4(
|
|
1785
2040
|
errorDatabaseConnectionRequired({
|
|
1786
2041
|
why: `Database connection is required for db init (set db.connection in ${configPath}, or pass --db <url>)`
|
|
1787
2042
|
})
|
|
1788
2043
|
);
|
|
1789
2044
|
}
|
|
1790
2045
|
if (!config.driver) {
|
|
1791
|
-
return
|
|
2046
|
+
return notOk4(errorDriverRequired({ why: "Config.driver is required for db init" }));
|
|
1792
2047
|
}
|
|
1793
2048
|
if (!config.target.migrations) {
|
|
1794
|
-
return
|
|
2049
|
+
return notOk4(
|
|
1795
2050
|
errorTargetMigrationNotSupported({
|
|
1796
2051
|
why: `Target "${config.target.id}" does not support migrations`
|
|
1797
2052
|
})
|
|
@@ -1813,7 +2068,7 @@ async function executeDbInitCommand(options, flags, startTime) {
|
|
|
1813
2068
|
onProgress
|
|
1814
2069
|
});
|
|
1815
2070
|
if (!result.ok) {
|
|
1816
|
-
return
|
|
2071
|
+
return notOk4(mapDbInitFailure(result.failure));
|
|
1817
2072
|
}
|
|
1818
2073
|
const profileHash = result.value.marker?.profileHash;
|
|
1819
2074
|
const dbInitResult = {
|
|
@@ -1846,12 +2101,12 @@ async function executeDbInitCommand(options, flags, startTime) {
|
|
|
1846
2101
|
summary: result.value.summary,
|
|
1847
2102
|
timings: { total: Date.now() - startTime }
|
|
1848
2103
|
};
|
|
1849
|
-
return
|
|
2104
|
+
return ok4(dbInitResult);
|
|
1850
2105
|
} catch (error) {
|
|
1851
2106
|
if (CliStructuredError.is(error)) {
|
|
1852
|
-
return
|
|
2107
|
+
return notOk4(error);
|
|
1853
2108
|
}
|
|
1854
|
-
return
|
|
2109
|
+
return notOk4(
|
|
1855
2110
|
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
1856
2111
|
why: `Unexpected error during db init: ${error instanceof Error ? error.message : String(error)}`
|
|
1857
2112
|
})
|
|
@@ -1876,7 +2131,7 @@ function createDbInitCommand() {
|
|
|
1876
2131
|
const flags = parseGlobalFlags(options);
|
|
1877
2132
|
const startTime = Date.now();
|
|
1878
2133
|
if (flags.json === "ndjson") {
|
|
1879
|
-
const result2 =
|
|
2134
|
+
const result2 = notOk4(
|
|
1880
2135
|
errorJsonFormatNotSupported({
|
|
1881
2136
|
command: "db init",
|
|
1882
2137
|
format: "ndjson",
|
|
@@ -1903,16 +2158,92 @@ function createDbInitCommand() {
|
|
|
1903
2158
|
}
|
|
1904
2159
|
|
|
1905
2160
|
// src/commands/db-introspect.ts
|
|
1906
|
-
import { readFile as readFile2 } from "fs/promises";
|
|
1907
2161
|
import { relative as relative4, resolve as resolve4 } from "path";
|
|
1908
|
-
import {
|
|
1909
|
-
errorDatabaseConnectionRequired as errorDatabaseConnectionRequired2,
|
|
1910
|
-
errorDriverRequired as errorDriverRequired2,
|
|
1911
|
-
errorRuntime as errorRuntime2,
|
|
1912
|
-
errorUnexpected as errorUnexpected3
|
|
1913
|
-
} from "@prisma-next/core-control-plane/errors";
|
|
1914
|
-
import { createControlPlaneStack as createControlPlaneStack3 } from "@prisma-next/core-control-plane/types";
|
|
2162
|
+
import { notOk as notOk5, ok as ok5 } from "@prisma-next/utils/result";
|
|
1915
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
|
+
}
|
|
1916
2247
|
function createDbIntrospectCommand() {
|
|
1917
2248
|
const command = new Command3("introspect");
|
|
1918
2249
|
setCommandDescriptions(
|
|
@@ -1925,128 +2256,21 @@ function createDbIntrospectCommand() {
|
|
|
1925
2256
|
const flags = parseGlobalFlags({});
|
|
1926
2257
|
return formatCommandHelp({ command: cmd, flags });
|
|
1927
2258
|
}
|
|
1928
|
-
}).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) => {
|
|
1929
2260
|
const flags = parseGlobalFlags(options);
|
|
1930
|
-
const
|
|
1931
|
-
|
|
1932
|
-
const
|
|
1933
|
-
|
|
1934
|
-
let contractIR;
|
|
1935
|
-
if (config.contract?.output) {
|
|
1936
|
-
const contractPath = resolve4(config.contract.output);
|
|
1937
|
-
try {
|
|
1938
|
-
const contractJsonContent = await readFile2(contractPath, "utf-8");
|
|
1939
|
-
contractIR = JSON.parse(contractJsonContent);
|
|
1940
|
-
} catch (error) {
|
|
1941
|
-
if (error instanceof Error && error.code !== "ENOENT") {
|
|
1942
|
-
throw errorUnexpected3(error.message, {
|
|
1943
|
-
why: `Failed to read contract file: ${error.message}`
|
|
1944
|
-
});
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
}
|
|
1948
|
-
if (flags.json !== "object" && !flags.quiet) {
|
|
1949
|
-
const details = [
|
|
1950
|
-
{ label: "config", value: configPath }
|
|
1951
|
-
];
|
|
1952
|
-
if (options.db) {
|
|
1953
|
-
const maskedUrl = options.db.replace(/:([^:@]+)@/, ":****@");
|
|
1954
|
-
details.push({ label: "database", value: maskedUrl });
|
|
1955
|
-
} else if (config.db?.connection && typeof config.db.connection === "string") {
|
|
1956
|
-
const maskedUrl = config.db.connection.replace(/:([^:@]+)@/, ":****@");
|
|
1957
|
-
details.push({ label: "database", value: maskedUrl });
|
|
1958
|
-
}
|
|
1959
|
-
const header = formatStyledHeader({
|
|
2261
|
+
const startTime = Date.now();
|
|
2262
|
+
if (flags.json === "ndjson") {
|
|
2263
|
+
const result2 = notOk5(
|
|
2264
|
+
errorJsonFormatNotSupported({
|
|
1960
2265
|
command: "db introspect",
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
if (!dbConnection) {
|
|
1970
|
-
throw errorDatabaseConnectionRequired2();
|
|
1971
|
-
}
|
|
1972
|
-
if (!config.driver) {
|
|
1973
|
-
throw errorDriverRequired2();
|
|
1974
|
-
}
|
|
1975
|
-
const driverDescriptor = config.driver;
|
|
1976
|
-
const driver = await withSpinner(() => driverDescriptor.create(dbConnection), {
|
|
1977
|
-
message: "Connecting to database...",
|
|
1978
|
-
flags
|
|
1979
|
-
});
|
|
1980
|
-
try {
|
|
1981
|
-
const stack = createControlPlaneStack3({
|
|
1982
|
-
target: config.target,
|
|
1983
|
-
adapter: config.adapter,
|
|
1984
|
-
driver: driverDescriptor,
|
|
1985
|
-
extensionPacks: config.extensionPacks
|
|
1986
|
-
});
|
|
1987
|
-
const familyInstance = config.family.create(stack);
|
|
1988
|
-
if (contractIR) {
|
|
1989
|
-
const validatedContract = familyInstance.validateContractIR(contractIR);
|
|
1990
|
-
assertContractRequirementsSatisfied({ contract: validatedContract, stack });
|
|
1991
|
-
contractIR = validatedContract;
|
|
1992
|
-
}
|
|
1993
|
-
let schemaIR;
|
|
1994
|
-
try {
|
|
1995
|
-
schemaIR = await withSpinner(
|
|
1996
|
-
() => familyInstance.introspect({
|
|
1997
|
-
driver,
|
|
1998
|
-
contractIR
|
|
1999
|
-
}),
|
|
2000
|
-
{
|
|
2001
|
-
message: "Introspecting database schema...",
|
|
2002
|
-
flags
|
|
2003
|
-
}
|
|
2004
|
-
);
|
|
2005
|
-
} catch (error) {
|
|
2006
|
-
throw errorRuntime2(error instanceof Error ? error.message : String(error), {
|
|
2007
|
-
why: `Failed to introspect database: ${error instanceof Error ? error.message : String(error)}`
|
|
2008
|
-
});
|
|
2009
|
-
}
|
|
2010
|
-
let schemaView;
|
|
2011
|
-
if (familyInstance.toSchemaView) {
|
|
2012
|
-
try {
|
|
2013
|
-
schemaView = familyInstance.toSchemaView(schemaIR);
|
|
2014
|
-
} catch (error) {
|
|
2015
|
-
if (flags.verbose) {
|
|
2016
|
-
console.error(
|
|
2017
|
-
`Warning: Failed to project schema to view: ${error instanceof Error ? error.message : String(error)}`
|
|
2018
|
-
);
|
|
2019
|
-
}
|
|
2020
|
-
}
|
|
2021
|
-
}
|
|
2022
|
-
const totalTime = Date.now() - startTime;
|
|
2023
|
-
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
2024
|
-
console.log("");
|
|
2025
|
-
}
|
|
2026
|
-
const connectionForMeta = typeof dbConnection === "string" ? dbConnection.replace(/:([^:@]+)@/, ":****@") : void 0;
|
|
2027
|
-
const introspectResult = {
|
|
2028
|
-
ok: true,
|
|
2029
|
-
summary: "Schema introspected successfully",
|
|
2030
|
-
target: {
|
|
2031
|
-
familyId: config.family.familyId,
|
|
2032
|
-
id: config.target.targetId
|
|
2033
|
-
},
|
|
2034
|
-
schema: schemaIR,
|
|
2035
|
-
...configPath || connectionForMeta ? {
|
|
2036
|
-
meta: {
|
|
2037
|
-
...configPath ? { configPath } : {},
|
|
2038
|
-
...connectionForMeta ? { dbUrl: connectionForMeta } : {}
|
|
2039
|
-
}
|
|
2040
|
-
} : {},
|
|
2041
|
-
timings: {
|
|
2042
|
-
total: totalTime
|
|
2043
|
-
}
|
|
2044
|
-
};
|
|
2045
|
-
return { introspectResult, schemaView };
|
|
2046
|
-
} finally {
|
|
2047
|
-
await driver.close();
|
|
2048
|
-
}
|
|
2049
|
-
});
|
|
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);
|
|
2050
2274
|
const exitCode = handleResult(result, flags, (value) => {
|
|
2051
2275
|
const { introspectResult, schemaView } = value;
|
|
2052
2276
|
if (flags.json === "object") {
|
|
@@ -2064,17 +2288,104 @@ function createDbIntrospectCommand() {
|
|
|
2064
2288
|
}
|
|
2065
2289
|
|
|
2066
2290
|
// src/commands/db-schema-verify.ts
|
|
2067
|
-
import { readFile as
|
|
2291
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
2068
2292
|
import { relative as relative5, resolve as resolve5 } from "path";
|
|
2069
|
-
import {
|
|
2070
|
-
errorDatabaseConnectionRequired as errorDatabaseConnectionRequired3,
|
|
2071
|
-
errorDriverRequired as errorDriverRequired3,
|
|
2072
|
-
errorFileNotFound as errorFileNotFound2,
|
|
2073
|
-
errorRuntime as errorRuntime3,
|
|
2074
|
-
errorUnexpected as errorUnexpected4
|
|
2075
|
-
} from "@prisma-next/core-control-plane/errors";
|
|
2076
|
-
import { createControlPlaneStack as createControlPlaneStack4 } from "@prisma-next/core-control-plane/types";
|
|
2293
|
+
import { notOk as notOk6, ok as ok6 } from "@prisma-next/utils/result";
|
|
2077
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
|
+
}
|
|
2078
2389
|
function createDbSchemaVerifyCommand() {
|
|
2079
2390
|
const command = new Command4("schema-verify");
|
|
2080
2391
|
setCommandDescriptions(
|
|
@@ -2086,102 +2397,21 @@ function createDbSchemaVerifyCommand() {
|
|
|
2086
2397
|
formatHelp: (cmd) => {
|
|
2087
2398
|
const flags = parseGlobalFlags({});
|
|
2088
2399
|
return formatCommandHelp({ command: cmd, flags });
|
|
2089
|
-
}
|
|
2090
|
-
}).option("--db <url>", "Database connection string").option("--config <path>", "Path to prisma-next.config.ts").option("--json [format]", "Output as JSON (object
|
|
2091
|
-
const flags = parseGlobalFlags(options);
|
|
2092
|
-
|
|
2093
|
-
const
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
}
|
|
2105
|
-
const header = formatStyledHeader({
|
|
2106
|
-
command: "db schema-verify",
|
|
2107
|
-
description: "Check whether the database schema satisfies your contract",
|
|
2108
|
-
url: "https://pris.ly/db-schema-verify",
|
|
2109
|
-
details,
|
|
2110
|
-
flags
|
|
2111
|
-
});
|
|
2112
|
-
console.log(header);
|
|
2113
|
-
}
|
|
2114
|
-
let contractJsonContent;
|
|
2115
|
-
try {
|
|
2116
|
-
contractJsonContent = await readFile3(contractPathAbsolute, "utf-8");
|
|
2117
|
-
} catch (error) {
|
|
2118
|
-
if (error instanceof Error && error.code === "ENOENT") {
|
|
2119
|
-
throw errorFileNotFound2(contractPathAbsolute, {
|
|
2120
|
-
why: `Contract file not found at ${contractPathAbsolute}`
|
|
2121
|
-
});
|
|
2122
|
-
}
|
|
2123
|
-
throw errorUnexpected4(error instanceof Error ? error.message : String(error), {
|
|
2124
|
-
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
2125
|
-
});
|
|
2126
|
-
}
|
|
2127
|
-
const contractJson = JSON.parse(contractJsonContent);
|
|
2128
|
-
if (!config.driver) {
|
|
2129
|
-
throw errorDriverRequired3();
|
|
2130
|
-
}
|
|
2131
|
-
const driverDescriptor = config.driver;
|
|
2132
|
-
const stack = createControlPlaneStack4({
|
|
2133
|
-
target: config.target,
|
|
2134
|
-
adapter: config.adapter,
|
|
2135
|
-
driver: driverDescriptor,
|
|
2136
|
-
extensionPacks: config.extensionPacks
|
|
2137
|
-
});
|
|
2138
|
-
const familyInstance = config.family.create(stack);
|
|
2139
|
-
const contractIR = familyInstance.validateContractIR(contractJson);
|
|
2140
|
-
assertContractRequirementsSatisfied({ contract: contractIR, stack });
|
|
2141
|
-
const dbConnection = options.db ?? config.db?.connection;
|
|
2142
|
-
if (!dbConnection) {
|
|
2143
|
-
throw errorDatabaseConnectionRequired3();
|
|
2144
|
-
}
|
|
2145
|
-
const driver = await withSpinner(() => driverDescriptor.create(dbConnection), {
|
|
2146
|
-
message: "Connecting to database...",
|
|
2147
|
-
flags
|
|
2148
|
-
});
|
|
2149
|
-
try {
|
|
2150
|
-
const rawComponents = [config.target, config.adapter, ...config.extensionPacks ?? []];
|
|
2151
|
-
const frameworkComponents = assertFrameworkComponentsCompatible(
|
|
2152
|
-
config.family.familyId,
|
|
2153
|
-
config.target.targetId,
|
|
2154
|
-
rawComponents
|
|
2155
|
-
);
|
|
2156
|
-
let schemaVerifyResult;
|
|
2157
|
-
try {
|
|
2158
|
-
schemaVerifyResult = await withSpinner(
|
|
2159
|
-
() => familyInstance.schemaVerify({
|
|
2160
|
-
driver,
|
|
2161
|
-
contractIR,
|
|
2162
|
-
strict: options.strict ?? false,
|
|
2163
|
-
contractPath: contractPathAbsolute,
|
|
2164
|
-
configPath,
|
|
2165
|
-
frameworkComponents
|
|
2166
|
-
}),
|
|
2167
|
-
{
|
|
2168
|
-
message: "Verifying database schema...",
|
|
2169
|
-
flags
|
|
2170
|
-
}
|
|
2171
|
-
);
|
|
2172
|
-
} catch (error) {
|
|
2173
|
-
throw errorRuntime3(error instanceof Error ? error.message : String(error), {
|
|
2174
|
-
why: `Failed to verify database schema: ${error instanceof Error ? error.message : String(error)}`
|
|
2175
|
-
});
|
|
2176
|
-
}
|
|
2177
|
-
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
2178
|
-
console.log("");
|
|
2179
|
-
}
|
|
2180
|
-
return schemaVerifyResult;
|
|
2181
|
-
} finally {
|
|
2182
|
-
await driver.close();
|
|
2183
|
-
}
|
|
2184
|
-
});
|
|
2400
|
+
}
|
|
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) => {
|
|
2402
|
+
const flags = parseGlobalFlags(options);
|
|
2403
|
+
if (flags.json === "ndjson") {
|
|
2404
|
+
const result2 = notOk6(
|
|
2405
|
+
errorJsonFormatNotSupported({
|
|
2406
|
+
command: "db schema-verify",
|
|
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);
|
|
2185
2415
|
const exitCode = handleResult(result, flags, (schemaVerifyResult) => {
|
|
2186
2416
|
if (flags.json === "object") {
|
|
2187
2417
|
console.log(formatSchemaVerifyJson(schemaVerifyResult));
|
|
@@ -2202,17 +2432,116 @@ function createDbSchemaVerifyCommand() {
|
|
|
2202
2432
|
}
|
|
2203
2433
|
|
|
2204
2434
|
// src/commands/db-sign.ts
|
|
2205
|
-
import { readFile as
|
|
2435
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
2206
2436
|
import { relative as relative6, resolve as resolve6 } from "path";
|
|
2207
|
-
import {
|
|
2208
|
-
errorDatabaseConnectionRequired as errorDatabaseConnectionRequired4,
|
|
2209
|
-
errorDriverRequired as errorDriverRequired4,
|
|
2210
|
-
errorFileNotFound as errorFileNotFound3,
|
|
2211
|
-
errorRuntime as errorRuntime4,
|
|
2212
|
-
errorUnexpected as errorUnexpected5
|
|
2213
|
-
} from "@prisma-next/core-control-plane/errors";
|
|
2214
|
-
import { createControlPlaneStack as createControlPlaneStack5 } from "@prisma-next/core-control-plane/types";
|
|
2437
|
+
import { notOk as notOk7, ok as ok7 } from "@prisma-next/utils/result";
|
|
2215
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
|
+
}
|
|
2216
2545
|
function createDbSignCommand() {
|
|
2217
2546
|
const command = new Command5("sign");
|
|
2218
2547
|
setCommandDescriptions(
|
|
@@ -2225,168 +2554,170 @@ function createDbSignCommand() {
|
|
|
2225
2554
|
const flags = parseGlobalFlags({});
|
|
2226
2555
|
return formatCommandHelp({ command: cmd, flags });
|
|
2227
2556
|
}
|
|
2228
|
-
}).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) => {
|
|
2229
2558
|
const flags = parseGlobalFlags(options);
|
|
2230
|
-
|
|
2231
|
-
const
|
|
2232
|
-
|
|
2233
|
-
const contractPathAbsolute = config.contract?.output ? resolve6(config.contract.output) : resolve6("src/prisma/contract.json");
|
|
2234
|
-
const contractPath = relative6(process.cwd(), contractPathAbsolute);
|
|
2235
|
-
if (flags.json !== "object" && !flags.quiet) {
|
|
2236
|
-
const details = [
|
|
2237
|
-
{ label: "config", value: configPath },
|
|
2238
|
-
{ label: "contract", value: contractPath }
|
|
2239
|
-
];
|
|
2240
|
-
if (options.db) {
|
|
2241
|
-
details.push({ label: "database", value: options.db });
|
|
2242
|
-
}
|
|
2243
|
-
const header = formatStyledHeader({
|
|
2559
|
+
if (flags.json === "ndjson") {
|
|
2560
|
+
const result2 = notOk7(
|
|
2561
|
+
errorJsonFormatNotSupported({
|
|
2244
2562
|
command: "db sign",
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
flags
|
|
2249
|
-
});
|
|
2250
|
-
console.log(header);
|
|
2251
|
-
}
|
|
2252
|
-
let contractJsonContent;
|
|
2253
|
-
try {
|
|
2254
|
-
contractJsonContent = await readFile4(contractPathAbsolute, "utf-8");
|
|
2255
|
-
} catch (error) {
|
|
2256
|
-
if (error instanceof Error && error.code === "ENOENT") {
|
|
2257
|
-
throw errorFileNotFound3(contractPathAbsolute, {
|
|
2258
|
-
why: `Contract file not found at ${contractPathAbsolute}`
|
|
2259
|
-
});
|
|
2260
|
-
}
|
|
2261
|
-
throw errorUnexpected5(error instanceof Error ? error.message : String(error), {
|
|
2262
|
-
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
2263
|
-
});
|
|
2264
|
-
}
|
|
2265
|
-
const contractJson = JSON.parse(contractJsonContent);
|
|
2266
|
-
const dbConnection = options.db ?? config.db?.connection;
|
|
2267
|
-
if (!dbConnection) {
|
|
2268
|
-
throw errorDatabaseConnectionRequired4();
|
|
2269
|
-
}
|
|
2270
|
-
if (!config.driver) {
|
|
2271
|
-
throw errorDriverRequired4();
|
|
2272
|
-
}
|
|
2273
|
-
const driverDescriptor = config.driver;
|
|
2274
|
-
const stack = createControlPlaneStack5({
|
|
2275
|
-
target: config.target,
|
|
2276
|
-
adapter: config.adapter,
|
|
2277
|
-
driver: driverDescriptor,
|
|
2278
|
-
extensionPacks: config.extensionPacks
|
|
2279
|
-
});
|
|
2280
|
-
const familyInstance = config.family.create(stack);
|
|
2281
|
-
const contractIR = familyInstance.validateContractIR(contractJson);
|
|
2282
|
-
assertContractRequirementsSatisfied({ contract: contractIR, stack });
|
|
2283
|
-
const rawComponents = [config.target, config.adapter, ...config.extensionPacks ?? []];
|
|
2284
|
-
const frameworkComponents = assertFrameworkComponentsCompatible(
|
|
2285
|
-
config.family.familyId,
|
|
2286
|
-
config.target.targetId,
|
|
2287
|
-
rawComponents
|
|
2563
|
+
format: "ndjson",
|
|
2564
|
+
supportedFormats: ["object"]
|
|
2565
|
+
})
|
|
2288
2566
|
);
|
|
2289
|
-
const
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
frameworkComponents
|
|
2301
|
-
}),
|
|
2302
|
-
{
|
|
2303
|
-
message: "Verifying database satisfies contract",
|
|
2304
|
-
flags
|
|
2305
|
-
}
|
|
2306
|
-
);
|
|
2307
|
-
} catch (error) {
|
|
2308
|
-
throw errorRuntime4(error instanceof Error ? error.message : String(error), {
|
|
2309
|
-
why: `Failed to verify database schema: ${error instanceof Error ? error.message : String(error)}`
|
|
2310
|
-
});
|
|
2311
|
-
}
|
|
2312
|
-
if (!schemaVerifyResult.ok) {
|
|
2313
|
-
return { schemaVerifyResult, signResult: void 0 };
|
|
2314
|
-
}
|
|
2315
|
-
let signResult;
|
|
2316
|
-
try {
|
|
2317
|
-
signResult = await withSpinner(
|
|
2318
|
-
() => familyInstance.sign({
|
|
2319
|
-
driver,
|
|
2320
|
-
contractIR,
|
|
2321
|
-
contractPath: contractPathAbsolute,
|
|
2322
|
-
configPath
|
|
2323
|
-
}),
|
|
2324
|
-
{
|
|
2325
|
-
message: "Signing database...",
|
|
2326
|
-
flags
|
|
2327
|
-
}
|
|
2328
|
-
);
|
|
2329
|
-
} catch (error) {
|
|
2330
|
-
throw errorRuntime4(error instanceof Error ? error.message : String(error), {
|
|
2331
|
-
why: `Failed to sign database: ${error instanceof Error ? error.message : String(error)}`
|
|
2332
|
-
});
|
|
2333
|
-
}
|
|
2334
|
-
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
2335
|
-
console.log("");
|
|
2336
|
-
}
|
|
2337
|
-
return { schemaVerifyResult: void 0, signResult };
|
|
2338
|
-
} finally {
|
|
2339
|
-
await driver.close();
|
|
2340
|
-
}
|
|
2341
|
-
});
|
|
2342
|
-
const exitCode = handleResult(result, flags, (value) => {
|
|
2343
|
-
const { schemaVerifyResult, signResult } = value;
|
|
2344
|
-
if (schemaVerifyResult && !schemaVerifyResult.ok) {
|
|
2345
|
-
if (flags.json === "object") {
|
|
2346
|
-
console.log(formatSchemaVerifyJson(schemaVerifyResult));
|
|
2347
|
-
} else {
|
|
2348
|
-
const output = formatSchemaVerifyOutput(schemaVerifyResult, flags);
|
|
2349
|
-
if (output) {
|
|
2350
|
-
console.log(output);
|
|
2351
|
-
}
|
|
2352
|
-
}
|
|
2353
|
-
return;
|
|
2354
|
-
}
|
|
2355
|
-
if (signResult) {
|
|
2356
|
-
if (flags.json === "object") {
|
|
2357
|
-
console.log(formatSignJson(signResult));
|
|
2358
|
-
} else {
|
|
2359
|
-
const output = formatSignOutput(signResult, flags);
|
|
2360
|
-
if (output) {
|
|
2361
|
-
console.log(output);
|
|
2362
|
-
}
|
|
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);
|
|
2363
2578
|
}
|
|
2364
2579
|
}
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2580
|
+
process.exit(0);
|
|
2581
|
+
}
|
|
2582
|
+
const failure = result.failure;
|
|
2583
|
+
if (failure instanceof CliStructuredError) {
|
|
2584
|
+
const exitCode = handleResult(result, flags);
|
|
2369
2585
|
process.exit(exitCode);
|
|
2370
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);
|
|
2371
2596
|
});
|
|
2372
2597
|
return command;
|
|
2373
2598
|
}
|
|
2374
2599
|
|
|
2375
2600
|
// src/commands/db-verify.ts
|
|
2376
|
-
import { readFile as
|
|
2601
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
2377
2602
|
import { relative as relative7, resolve as resolve7 } from "path";
|
|
2378
|
-
import {
|
|
2379
|
-
errorDatabaseConnectionRequired as errorDatabaseConnectionRequired5,
|
|
2380
|
-
errorDriverRequired as errorDriverRequired5,
|
|
2381
|
-
errorFileNotFound as errorFileNotFound4,
|
|
2382
|
-
errorHashMismatch as errorHashMismatch2,
|
|
2383
|
-
errorMarkerMissing as errorMarkerMissing2,
|
|
2384
|
-
errorRuntime as errorRuntime5,
|
|
2385
|
-
errorTargetMismatch as errorTargetMismatch2,
|
|
2386
|
-
errorUnexpected as errorUnexpected6
|
|
2387
|
-
} from "@prisma-next/core-control-plane/errors";
|
|
2388
|
-
import { createControlPlaneStack as createControlPlaneStack6 } from "@prisma-next/core-control-plane/types";
|
|
2603
|
+
import { notOk as notOk8, ok as ok8 } from "@prisma-next/utils/result";
|
|
2389
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
|
+
}
|
|
2390
2721
|
function createDbVerifyCommand() {
|
|
2391
2722
|
const command = new Command6("verify");
|
|
2392
2723
|
setCommandDescriptions(
|
|
@@ -2399,112 +2730,20 @@ function createDbVerifyCommand() {
|
|
|
2399
2730
|
const flags = parseGlobalFlags({});
|
|
2400
2731
|
return formatCommandHelp({ command: cmd, flags });
|
|
2401
2732
|
}
|
|
2402
|
-
}).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) => {
|
|
2403
2734
|
const flags = parseGlobalFlags(options);
|
|
2404
|
-
|
|
2405
|
-
const
|
|
2406
|
-
|
|
2407
|
-
const contractPathAbsolute = config.contract?.output ? resolve7(config.contract.output) : resolve7("src/prisma/contract.json");
|
|
2408
|
-
const contractPath = relative7(process.cwd(), contractPathAbsolute);
|
|
2409
|
-
if (flags.json !== "object" && !flags.quiet) {
|
|
2410
|
-
const details = [
|
|
2411
|
-
{ label: "config", value: configPath },
|
|
2412
|
-
{ label: "contract", value: contractPath }
|
|
2413
|
-
];
|
|
2414
|
-
if (options.db) {
|
|
2415
|
-
details.push({ label: "database", value: options.db });
|
|
2416
|
-
}
|
|
2417
|
-
const header = formatStyledHeader({
|
|
2735
|
+
if (flags.json === "ndjson") {
|
|
2736
|
+
const result2 = notOk8(
|
|
2737
|
+
errorJsonFormatNotSupported({
|
|
2418
2738
|
command: "db verify",
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
try {
|
|
2428
|
-
contractJsonContent = await readFile5(contractPathAbsolute, "utf-8");
|
|
2429
|
-
} catch (error) {
|
|
2430
|
-
if (error instanceof Error && error.code === "ENOENT") {
|
|
2431
|
-
throw errorFileNotFound4(contractPathAbsolute, {
|
|
2432
|
-
why: `Contract file not found at ${contractPathAbsolute}`
|
|
2433
|
-
});
|
|
2434
|
-
}
|
|
2435
|
-
throw errorUnexpected6(error instanceof Error ? error.message : String(error), {
|
|
2436
|
-
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
2437
|
-
});
|
|
2438
|
-
}
|
|
2439
|
-
const contractJson = JSON.parse(contractJsonContent);
|
|
2440
|
-
const dbConnection = options.db ?? config.db?.connection;
|
|
2441
|
-
if (!dbConnection) {
|
|
2442
|
-
throw errorDatabaseConnectionRequired5();
|
|
2443
|
-
}
|
|
2444
|
-
if (!config.driver) {
|
|
2445
|
-
throw errorDriverRequired5();
|
|
2446
|
-
}
|
|
2447
|
-
const driverDescriptor = config.driver;
|
|
2448
|
-
const driver = await withSpinner(() => driverDescriptor.create(dbConnection), {
|
|
2449
|
-
message: "Connecting to database...",
|
|
2450
|
-
flags
|
|
2451
|
-
});
|
|
2452
|
-
try {
|
|
2453
|
-
const stack = createControlPlaneStack6({
|
|
2454
|
-
target: config.target,
|
|
2455
|
-
adapter: config.adapter,
|
|
2456
|
-
driver: driverDescriptor,
|
|
2457
|
-
extensionPacks: config.extensionPacks
|
|
2458
|
-
});
|
|
2459
|
-
const familyInstance = config.family.create(stack);
|
|
2460
|
-
const contractIR = familyInstance.validateContractIR(contractJson);
|
|
2461
|
-
assertContractRequirementsSatisfied({ contract: contractIR, stack });
|
|
2462
|
-
let verifyResult;
|
|
2463
|
-
try {
|
|
2464
|
-
verifyResult = await withSpinner(
|
|
2465
|
-
() => familyInstance.verify({
|
|
2466
|
-
driver,
|
|
2467
|
-
contractIR,
|
|
2468
|
-
expectedTargetId: config.target.targetId,
|
|
2469
|
-
contractPath: contractPathAbsolute,
|
|
2470
|
-
configPath
|
|
2471
|
-
}),
|
|
2472
|
-
{
|
|
2473
|
-
message: "Verifying database schema...",
|
|
2474
|
-
flags
|
|
2475
|
-
}
|
|
2476
|
-
);
|
|
2477
|
-
} catch (error) {
|
|
2478
|
-
throw errorUnexpected6(error instanceof Error ? error.message : String(error), {
|
|
2479
|
-
why: `Failed to verify database: ${error instanceof Error ? error.message : String(error)}`
|
|
2480
|
-
});
|
|
2481
|
-
}
|
|
2482
|
-
if (!verifyResult.ok && verifyResult.code) {
|
|
2483
|
-
if (verifyResult.code === "PN-RTM-3001") {
|
|
2484
|
-
throw errorMarkerMissing2();
|
|
2485
|
-
}
|
|
2486
|
-
if (verifyResult.code === "PN-RTM-3002") {
|
|
2487
|
-
throw errorHashMismatch2({
|
|
2488
|
-
expected: verifyResult.contract.coreHash,
|
|
2489
|
-
...verifyResult.marker?.coreHash ? { actual: verifyResult.marker.coreHash } : {}
|
|
2490
|
-
});
|
|
2491
|
-
}
|
|
2492
|
-
if (verifyResult.code === "PN-RTM-3003") {
|
|
2493
|
-
throw errorTargetMismatch2(
|
|
2494
|
-
verifyResult.target.expected,
|
|
2495
|
-
verifyResult.target.actual ?? "unknown"
|
|
2496
|
-
);
|
|
2497
|
-
}
|
|
2498
|
-
throw errorRuntime5(verifyResult.summary);
|
|
2499
|
-
}
|
|
2500
|
-
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
2501
|
-
console.log("");
|
|
2502
|
-
}
|
|
2503
|
-
return verifyResult;
|
|
2504
|
-
} finally {
|
|
2505
|
-
await driver.close();
|
|
2506
|
-
}
|
|
2507
|
-
});
|
|
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);
|
|
2508
2747
|
const exitCode = handleResult(result, flags, (verifyResult) => {
|
|
2509
2748
|
if (flags.json === "object") {
|
|
2510
2749
|
console.log(formatVerifyJson(verifyResult));
|