@prisma-next/cli 0.3.0-dev.18 → 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-RPYY5SM7.js → chunk-VG2R7DGF.js} +113 -3
- package/dist/chunk-VG2R7DGF.js.map +1 -0
- package/dist/cli.js +1662 -1580
- package/dist/cli.js.map +1 -1
- package/dist/commands/contract-emit.d.ts.map +1 -1
- package/dist/commands/contract-emit.js +3 -3
- package/dist/commands/db-init.js +4 -8
- package/dist/commands/db-init.js.map +1 -1
- package/dist/commands/db-introspect.js +4 -8
- package/dist/commands/db-introspect.js.map +1 -1
- package/dist/commands/db-schema-verify.js +4 -8
- package/dist/commands/db-schema-verify.js.map +1 -1
- package/dist/commands/db-sign.js +4 -8
- package/dist/commands/db-sign.js.map +1 -1
- package/dist/commands/db-verify.js +4 -8
- 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 +91 -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 -2
- package/dist/exports/index.js +3 -3
- package/package.json +10 -10
- package/src/commands/contract-emit.ts +179 -102
- package/src/control-api/client.ts +96 -0
- package/src/control-api/types.ts +107 -1
- package/src/exports/control-api.ts +9 -0
- package/dist/chunk-BO73VO4I.js +0 -45
- package/dist/chunk-BO73VO4I.js.map +0 -1
- package/dist/chunk-MPSJAVF6.js +0 -40
- package/dist/chunk-MPSJAVF6.js.map +0 -1
- package/dist/chunk-RIONCN4I.js +0 -172
- package/dist/chunk-RIONCN4I.js.map +0 -1
- package/dist/chunk-RPYY5SM7.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,596 +85,849 @@ 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
131
|
}
|
|
132
|
+
return frameworkComponents;
|
|
93
133
|
}
|
|
94
134
|
|
|
95
|
-
// src/
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
+
});
|
|
100
183
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
+
});
|
|
114
243
|
}
|
|
115
|
-
|
|
116
|
-
|
|
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);
|
|
117
257
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
+
});
|
|
124
314
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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;
|
|
160
|
-
}
|
|
161
|
-
function formatEmitOutput(result, flags) {
|
|
162
|
-
if (flags.quiet) {
|
|
163
|
-
return "";
|
|
164
|
-
}
|
|
165
|
-
const lines = [];
|
|
166
|
-
const prefix = createPrefix(flags);
|
|
167
|
-
const jsonPath = relative(process.cwd(), result.files.json);
|
|
168
|
-
const dtsPath = relative(process.cwd(), result.files.dts);
|
|
169
|
-
lines.push(`${prefix}\u2714 Emitted contract.json \u2192 ${jsonPath}`);
|
|
170
|
-
lines.push(`${prefix}\u2714 Emitted contract.d.ts \u2192 ${dtsPath}`);
|
|
171
|
-
lines.push(`${prefix} coreHash: ${result.coreHash}`);
|
|
172
|
-
if (result.profileHash) {
|
|
173
|
-
lines.push(`${prefix} profileHash: ${result.profileHash}`);
|
|
174
|
-
}
|
|
175
|
-
if (isVerbose(flags, 1)) {
|
|
176
|
-
lines.push(`${prefix} Total time: ${result.timings.total}ms`);
|
|
177
|
-
}
|
|
178
|
-
return lines.join("\n");
|
|
179
|
-
}
|
|
180
|
-
function formatEmitJson(result) {
|
|
181
|
-
const output = {
|
|
182
|
-
ok: true,
|
|
183
|
-
coreHash: result.coreHash,
|
|
184
|
-
...result.profileHash ? { profileHash: result.profileHash } : {},
|
|
185
|
-
outDir: result.outDir,
|
|
186
|
-
files: result.files,
|
|
187
|
-
timings: result.timings
|
|
188
|
-
};
|
|
189
|
-
return JSON.stringify(output, null, 2);
|
|
338
|
+
// src/control-api/client.ts
|
|
339
|
+
function createControlClient(options) {
|
|
340
|
+
return new ControlClientImpl(options);
|
|
190
341
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
lines.push(`${prefix}${formatDimText(` Fix: ${error.fix}`)}`);
|
|
203
|
-
}
|
|
204
|
-
if (error.where?.path) {
|
|
205
|
-
const whereLine = error.where.line ? `${error.where.path}:${error.where.line}` : error.where.path;
|
|
206
|
-
lines.push(`${prefix}${formatDimText(` Where: ${whereLine}`)}`);
|
|
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;
|
|
207
353
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const maxToShow = isVerbose(flags, 1) ? conflicts.length : Math.min(3, conflicts.length);
|
|
212
|
-
const header = isVerbose(flags, 1) ? " Conflicts:" : ` Conflicts (showing ${maxToShow} of ${conflicts.length}):`;
|
|
213
|
-
lines.push(`${prefix}${formatDimText(header)}`);
|
|
214
|
-
for (const conflict of conflicts.slice(0, maxToShow)) {
|
|
215
|
-
lines.push(`${prefix}${formatDimText(` - [${conflict.kind}] ${conflict.summary}`)}`);
|
|
216
|
-
}
|
|
217
|
-
if (!isVerbose(flags, 1) && conflicts.length > maxToShow) {
|
|
218
|
-
lines.push(`${prefix}${formatDimText(" Re-run with -v/--verbose to see all conflicts")}`);
|
|
219
|
-
}
|
|
354
|
+
init() {
|
|
355
|
+
if (this.initialized) {
|
|
356
|
+
return;
|
|
220
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;
|
|
221
376
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (
|
|
225
|
-
|
|
226
|
-
const header = isVerbose(flags, 1) ? " Issues:" : ` Issues (showing ${maxToShow} of ${issues.length}):`;
|
|
227
|
-
lines.push(`${prefix}${formatDimText(header)}`);
|
|
228
|
-
for (const issue of issues.slice(0, maxToShow)) {
|
|
229
|
-
const kind = issue.kind ?? "issue";
|
|
230
|
-
const message = issue.message ?? "";
|
|
231
|
-
lines.push(`${prefix}${formatDimText(` - [${kind}] ${message}`)}`);
|
|
232
|
-
}
|
|
233
|
-
if (!isVerbose(flags, 1) && issues.length > maxToShow) {
|
|
234
|
-
lines.push(`${prefix}${formatDimText(" Re-run with -v/--verbose to see all issues")}`);
|
|
235
|
-
}
|
|
377
|
+
async connect(connection) {
|
|
378
|
+
this.init();
|
|
379
|
+
if (this.driver) {
|
|
380
|
+
throw new Error("Already connected. Call close() before reconnecting.");
|
|
236
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);
|
|
237
394
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
lines.push(`${prefix}${formatDimText(` Meta: ${JSON.stringify(error.meta, null, 2)}`)}`);
|
|
243
|
-
}
|
|
244
|
-
return lines.join("\n");
|
|
245
|
-
}
|
|
246
|
-
function formatErrorJson(error) {
|
|
247
|
-
return JSON.stringify(error, null, 2);
|
|
248
|
-
}
|
|
249
|
-
function formatVerifyOutput(result, flags) {
|
|
250
|
-
if (flags.quiet) {
|
|
251
|
-
return "";
|
|
252
|
-
}
|
|
253
|
-
const lines = [];
|
|
254
|
-
const prefix = createPrefix(flags);
|
|
255
|
-
const useColor = flags.color !== false;
|
|
256
|
-
const formatGreen = createColorFormatter(useColor, green);
|
|
257
|
-
const formatRed = createColorFormatter(useColor, red);
|
|
258
|
-
const formatDimText = (text) => formatDim(useColor, text);
|
|
259
|
-
if (result.ok) {
|
|
260
|
-
lines.push(`${prefix}${formatGreen("\u2714")} ${result.summary}`);
|
|
261
|
-
lines.push(`${prefix}${formatDimText(` coreHash: ${result.contract.coreHash}`)}`);
|
|
262
|
-
if (result.contract.profileHash) {
|
|
263
|
-
lines.push(`${prefix}${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);
|
|
395
|
+
async close() {
|
|
396
|
+
if (this.driver) {
|
|
397
|
+
await this.driver.close();
|
|
398
|
+
this.driver = null;
|
|
264
399
|
}
|
|
265
|
-
} else {
|
|
266
|
-
lines.push(`${prefix}${formatRed("\u2716")} ${result.summary} (${result.code})`);
|
|
267
400
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
);
|
|
401
|
+
async ensureConnected() {
|
|
402
|
+
this.init();
|
|
403
|
+
if (!this.driver && this.defaultConnection !== void 0) {
|
|
404
|
+
await this.connect(this.defaultConnection);
|
|
273
405
|
}
|
|
274
|
-
|
|
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
|
+
};
|
|
275
414
|
}
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
case "root":
|
|
302
|
-
formattedLabel = bold(node.label);
|
|
303
|
-
break;
|
|
304
|
-
case "entity": {
|
|
305
|
-
const tableMatch = node.label.match(/^table\s+(.+)$/);
|
|
306
|
-
if (tableMatch?.[1]) {
|
|
307
|
-
const tableName = tableMatch[1];
|
|
308
|
-
formattedLabel = `${dim("table")} ${cyan(tableName)}`;
|
|
309
|
-
} else {
|
|
310
|
-
formattedLabel = cyan(node.label);
|
|
311
|
-
}
|
|
312
|
-
break;
|
|
313
|
-
}
|
|
314
|
-
case "collection": {
|
|
315
|
-
formattedLabel = dim(node.label);
|
|
316
|
-
break;
|
|
317
|
-
}
|
|
318
|
-
case "field": {
|
|
319
|
-
const columnMatch = node.label.match(/^([^:]+):\s*(.+)$/);
|
|
320
|
-
if (columnMatch?.[1] && columnMatch[2]) {
|
|
321
|
-
const columnName = columnMatch[1];
|
|
322
|
-
const rest = columnMatch[2];
|
|
323
|
-
const typeMatch = rest.match(/^([^\s(]+)\s*(\([^)]+\))$/);
|
|
324
|
-
if (typeMatch?.[1] && typeMatch[2]) {
|
|
325
|
-
const typeDisplay = typeMatch[1];
|
|
326
|
-
const nullability = typeMatch[2];
|
|
327
|
-
formattedLabel = `${cyan(columnName)}: ${typeDisplay} ${dim(nullability)}`;
|
|
328
|
-
} else {
|
|
329
|
-
formattedLabel = `${cyan(columnName)}: ${rest}`;
|
|
330
|
-
}
|
|
331
|
-
} else {
|
|
332
|
-
formattedLabel = node.label;
|
|
333
|
-
}
|
|
334
|
-
break;
|
|
335
|
-
}
|
|
336
|
-
case "index": {
|
|
337
|
-
const pkMatch = node.label.match(/^primary key:\s*(.+)$/);
|
|
338
|
-
if (pkMatch?.[1]) {
|
|
339
|
-
const columnNames = pkMatch[1];
|
|
340
|
-
formattedLabel = `${dim("primary key")}: ${cyan(columnNames)}`;
|
|
341
|
-
} else {
|
|
342
|
-
const uniqueMatch = node.label.match(/^unique\s+(.+)$/);
|
|
343
|
-
if (uniqueMatch?.[1]) {
|
|
344
|
-
const name = uniqueMatch[1];
|
|
345
|
-
formattedLabel = `${dim("unique")} ${cyan(name)}`;
|
|
346
|
-
} else {
|
|
347
|
-
const indexMatch = node.label.match(/^(unique\s+)?index\s+(.+)$/);
|
|
348
|
-
if (indexMatch?.[2]) {
|
|
349
|
-
const prefix2 = indexMatch[1] ? `${dim("unique")} ` : "";
|
|
350
|
-
const name = indexMatch[2];
|
|
351
|
-
formattedLabel = `${prefix2}${dim("index")} ${cyan(name)}`;
|
|
352
|
-
} else {
|
|
353
|
-
formattedLabel = dim(node.label);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
break;
|
|
358
|
-
}
|
|
359
|
-
case "extension": {
|
|
360
|
-
const extMatch = node.label.match(/^([^\s]+)\s+(extension is enabled)$/);
|
|
361
|
-
if (extMatch?.[1] && extMatch[2]) {
|
|
362
|
-
const extName = extMatch[1];
|
|
363
|
-
const rest = extMatch[2];
|
|
364
|
-
formattedLabel = `${cyan(extName)} ${dim(rest)}`;
|
|
365
|
-
} else {
|
|
366
|
-
formattedLabel = magenta(node.label);
|
|
367
|
-
}
|
|
368
|
-
break;
|
|
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;
|
|
369
440
|
}
|
|
370
|
-
default:
|
|
371
|
-
formattedLabel = node.label;
|
|
372
|
-
break;
|
|
373
441
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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;
|
|
389
472
|
}
|
|
390
473
|
}
|
|
391
|
-
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
prefix: childPrefix,
|
|
400
|
-
useColor,
|
|
401
|
-
formatDimText,
|
|
402
|
-
isRoot: false
|
|
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..."
|
|
403
482
|
});
|
|
404
|
-
|
|
483
|
+
try {
|
|
484
|
+
await this.connect(options.connection);
|
|
485
|
+
onProgress?.({
|
|
486
|
+
action: "schemaVerify",
|
|
487
|
+
kind: "spanEnd",
|
|
488
|
+
spanId: "connect",
|
|
489
|
+
outcome: "ok"
|
|
490
|
+
});
|
|
491
|
+
} catch (error) {
|
|
492
|
+
onProgress?.({
|
|
493
|
+
action: "schemaVerify",
|
|
494
|
+
kind: "spanEnd",
|
|
495
|
+
spanId: "connect",
|
|
496
|
+
outcome: "error"
|
|
497
|
+
});
|
|
498
|
+
throw error;
|
|
499
|
+
}
|
|
405
500
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
const lines = [];
|
|
414
|
-
const prefix = createPrefix(flags);
|
|
415
|
-
const useColor = flags.color !== false;
|
|
416
|
-
const formatDimText = (text) => formatDim(useColor, text);
|
|
417
|
-
if (schemaView) {
|
|
418
|
-
const treeLines = renderSchemaTree(schemaView.root, flags, {
|
|
419
|
-
isLast: true,
|
|
420
|
-
prefix: "",
|
|
421
|
-
useColor,
|
|
422
|
-
formatDimText,
|
|
423
|
-
isRoot: true
|
|
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..."
|
|
424
508
|
});
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
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;
|
|
434
532
|
}
|
|
435
533
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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;
|
|
472
591
|
}
|
|
473
592
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
if (columnMatch?.[1] && columnMatch[2]) {
|
|
500
|
-
const columnName = columnMatch[1];
|
|
501
|
-
const rest = columnMatch[2];
|
|
502
|
-
const typeMatch = rest.match(/^([^\s→]+)\s*→\s*([^\s(]+)\s*(\([^)]+\))$/);
|
|
503
|
-
if (typeMatch?.[1] && typeMatch[2] && typeMatch[3]) {
|
|
504
|
-
const contractType = typeMatch[1];
|
|
505
|
-
const nativeType = typeMatch[2];
|
|
506
|
-
const nullability = typeMatch[3];
|
|
507
|
-
formattedLabel = `${cyan(columnName)}: ${contractType} \u2192 ${dim(nativeType)} ${dim(nullability)}`;
|
|
508
|
-
} else {
|
|
509
|
-
formattedLabel = `${cyan(columnName)}: ${rest}`;
|
|
510
|
-
}
|
|
511
|
-
} else {
|
|
512
|
-
formattedLabel = node.name;
|
|
513
|
-
}
|
|
514
|
-
break;
|
|
515
|
-
}
|
|
516
|
-
case "type":
|
|
517
|
-
case "nullability":
|
|
518
|
-
labelColor = (text) => text;
|
|
519
|
-
formattedLabel = labelColor(node.name);
|
|
520
|
-
break;
|
|
521
|
-
case "primaryKey": {
|
|
522
|
-
const pkMatch = node.name.match(/^primary key:\s*(.+)$/);
|
|
523
|
-
if (pkMatch?.[1]) {
|
|
524
|
-
const columnNames = pkMatch[1];
|
|
525
|
-
formattedLabel = `${dim("primary key")}: ${cyan(columnNames)}`;
|
|
526
|
-
} else {
|
|
527
|
-
formattedLabel = dim(node.name);
|
|
528
|
-
}
|
|
529
|
-
break;
|
|
530
|
-
}
|
|
531
|
-
case "foreignKey":
|
|
532
|
-
case "unique":
|
|
533
|
-
case "index":
|
|
534
|
-
labelColor = dim;
|
|
535
|
-
formattedLabel = labelColor(node.name);
|
|
536
|
-
break;
|
|
537
|
-
case "extension": {
|
|
538
|
-
const dbMatch = node.name.match(/^database is\s+(.+)$/);
|
|
539
|
-
if (dbMatch?.[1]) {
|
|
540
|
-
const dbName = dbMatch[1];
|
|
541
|
-
formattedLabel = `${dim("database is")} ${cyan(dbName)}`;
|
|
542
|
-
} else {
|
|
543
|
-
const extMatch = node.name.match(/^([^\s]+)\s+(extension is enabled)$/);
|
|
544
|
-
if (extMatch?.[1] && extMatch[2]) {
|
|
545
|
-
const extName = extMatch[1];
|
|
546
|
-
const rest = extMatch[2];
|
|
547
|
-
formattedLabel = `${cyan(extName)} ${dim(rest)}`;
|
|
548
|
-
} else {
|
|
549
|
-
labelColor = magenta;
|
|
550
|
-
formattedLabel = labelColor(node.name);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
break;
|
|
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;
|
|
554
618
|
}
|
|
555
|
-
default:
|
|
556
|
-
formattedLabel = node.name;
|
|
557
|
-
break;
|
|
558
619
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
const statusGlyphColored = statusColor(statusGlyph);
|
|
563
|
-
let nodeLabel = formattedLabel;
|
|
564
|
-
if ((node.status === "fail" || node.status === "warn") && node.message && node.message.length > 0) {
|
|
565
|
-
const messageText = formatDimText(`(${node.message})`);
|
|
566
|
-
nodeLabel = `${formattedLabel} ${messageText}`;
|
|
567
|
-
}
|
|
568
|
-
if (isRoot) {
|
|
569
|
-
lines.push(`${statusGlyphColored} ${nodeLabel}`);
|
|
570
|
-
} else {
|
|
571
|
-
const treeChar = isLast ? "\u2514" : "\u251C";
|
|
572
|
-
const treePrefix = `${prefix}${formatDimText(treeChar)}\u2500 `;
|
|
573
|
-
const isRootChild = prefix === "";
|
|
574
|
-
const prefixWithoutAnsi = stripAnsi(prefix);
|
|
575
|
-
const prefixHasVerticalBar = prefixWithoutAnsi.includes("\u2502");
|
|
576
|
-
if (isRootChild) {
|
|
577
|
-
lines.push(`${treePrefix}${statusGlyphColored} ${nodeLabel}`);
|
|
578
|
-
} else if (prefixHasVerticalBar) {
|
|
579
|
-
lines.push(`${treePrefix}${statusGlyphColored} ${nodeLabel}`);
|
|
580
|
-
} else {
|
|
581
|
-
lines.push(`${formatDimText("\u2502")} ${treePrefix}${statusGlyphColored} ${nodeLabel}`);
|
|
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`);
|
|
582
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
|
+
});
|
|
583
634
|
}
|
|
584
|
-
|
|
585
|
-
const
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
prefix: childPrefix,
|
|
593
|
-
useColor,
|
|
594
|
-
formatDimText,
|
|
595
|
-
isRoot: false
|
|
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..."
|
|
596
643
|
});
|
|
597
|
-
|
|
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;
|
|
598
688
|
}
|
|
599
689
|
}
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
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;
|
|
783
|
+
}
|
|
784
|
+
function getLongDescription(command) {
|
|
785
|
+
return command._longDescription;
|
|
786
|
+
}
|
|
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;
|
|
630
803
|
} else {
|
|
631
|
-
|
|
632
|
-
lines.push(`${prefix}${formatRed("\u2716")} ${result.summary}${codeText}`);
|
|
804
|
+
flags.verbose = 0;
|
|
633
805
|
}
|
|
634
|
-
|
|
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;
|
|
635
819
|
}
|
|
636
|
-
|
|
637
|
-
|
|
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();
|
|
638
829
|
}
|
|
639
|
-
function
|
|
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) {
|
|
640
843
|
if (flags.quiet) {
|
|
641
844
|
return "";
|
|
642
845
|
}
|
|
846
|
+
const lines = [];
|
|
847
|
+
const prefix = createPrefix(flags);
|
|
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}`);
|
|
855
|
+
}
|
|
856
|
+
if (isVerbose(flags, 1)) {
|
|
857
|
+
lines.push(`${prefix} Total time: ${result.timings.total}ms`);
|
|
858
|
+
}
|
|
859
|
+
return lines.join("\n");
|
|
860
|
+
}
|
|
861
|
+
function formatEmitJson(result) {
|
|
862
|
+
const output = {
|
|
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) {
|
|
643
873
|
const lines = [];
|
|
644
874
|
const prefix = createPrefix(flags);
|
|
645
875
|
const useColor = flags.color !== false;
|
|
646
|
-
const
|
|
876
|
+
const formatRed = createColorFormatter(useColor, red);
|
|
647
877
|
const formatDimText = (text) => formatDim(useColor, text);
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
lines.push(`${prefix}${formatDimText(`
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
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}`)}`);
|
|
657
897
|
}
|
|
658
|
-
if (
|
|
659
|
-
lines.push(
|
|
660
|
-
|
|
661
|
-
|
|
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")}`);
|
|
662
916
|
}
|
|
663
|
-
lines.push(`${prefix}${formatDimText(` Total time: ${result.timings.total}ms`)}`);
|
|
664
917
|
}
|
|
665
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
|
+
}
|
|
666
925
|
return lines.join("\n");
|
|
667
926
|
}
|
|
668
|
-
function
|
|
669
|
-
return JSON.stringify(
|
|
927
|
+
function formatErrorJson(error) {
|
|
928
|
+
return JSON.stringify(error, null, 2);
|
|
670
929
|
}
|
|
671
|
-
function
|
|
930
|
+
function formatVerifyOutput(result, flags) {
|
|
672
931
|
if (flags.quiet) {
|
|
673
932
|
return "";
|
|
674
933
|
}
|
|
@@ -676,210 +935,632 @@ function formatDbInitPlanOutput(result, flags) {
|
|
|
676
935
|
const prefix = createPrefix(flags);
|
|
677
936
|
const useColor = flags.color !== false;
|
|
678
937
|
const formatGreen = createColorFormatter(useColor, green);
|
|
938
|
+
const formatRed = createColorFormatter(useColor, red);
|
|
679
939
|
const formatDimText = (text) => formatDim(useColor, text);
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
const op = result.plan.operations[i];
|
|
686
|
-
if (!op) continue;
|
|
687
|
-
const isLast = i === result.plan.operations.length - 1;
|
|
688
|
-
const treeChar = isLast ? "\u2514" : "\u251C";
|
|
689
|
-
const opClass = formatDimText(`[${op.operationClass}]`);
|
|
690
|
-
lines.push(`${prefix}${formatDimText(treeChar)}\u2500 ${op.label} ${opClass}`);
|
|
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}`)}`);
|
|
691
945
|
}
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
lines.push(`${prefix}`);
|
|
695
|
-
lines.push(
|
|
696
|
-
`${prefix}${formatDimText(`Destination hash: ${result.plan.destination.coreHash}`)}`
|
|
697
|
-
);
|
|
946
|
+
} else {
|
|
947
|
+
lines.push(`${prefix}${formatRed("\u2716")} ${result.summary} (${result.code})`);
|
|
698
948
|
}
|
|
699
949
|
if (isVerbose(flags, 1)) {
|
|
700
|
-
|
|
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`)}`);
|
|
701
956
|
}
|
|
702
|
-
lines.push(`${prefix}`);
|
|
703
|
-
lines.push(`${prefix}${formatDimText("This is a dry run. No changes were applied.")}`);
|
|
704
|
-
lines.push(`${prefix}${formatDimText("Run without --plan to apply changes.")}`);
|
|
705
957
|
return lines.join("\n");
|
|
706
958
|
}
|
|
707
|
-
function
|
|
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,
|
|
967
|
+
...result.missingCodecs ? { missingCodecs: result.missingCodecs } : {},
|
|
968
|
+
...result.meta ? { meta: result.meta } : {},
|
|
969
|
+
timings: result.timings
|
|
970
|
+
};
|
|
971
|
+
return JSON.stringify(output, null, 2);
|
|
972
|
+
}
|
|
973
|
+
function formatIntrospectJson(result) {
|
|
974
|
+
return JSON.stringify(result, null, 2);
|
|
975
|
+
}
|
|
976
|
+
function renderSchemaTree(node, flags, options) {
|
|
977
|
+
const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;
|
|
978
|
+
const lines = [];
|
|
979
|
+
let formattedLabel = node.label;
|
|
980
|
+
if (useColor) {
|
|
981
|
+
switch (node.kind) {
|
|
982
|
+
case "root":
|
|
983
|
+
formattedLabel = bold(node.label);
|
|
984
|
+
break;
|
|
985
|
+
case "entity": {
|
|
986
|
+
const tableMatch = node.label.match(/^table\s+(.+)$/);
|
|
987
|
+
if (tableMatch?.[1]) {
|
|
988
|
+
const tableName = tableMatch[1];
|
|
989
|
+
formattedLabel = `${dim("table")} ${cyan(tableName)}`;
|
|
990
|
+
} else {
|
|
991
|
+
formattedLabel = cyan(node.label);
|
|
992
|
+
}
|
|
993
|
+
break;
|
|
994
|
+
}
|
|
995
|
+
case "collection": {
|
|
996
|
+
formattedLabel = dim(node.label);
|
|
997
|
+
break;
|
|
998
|
+
}
|
|
999
|
+
case "field": {
|
|
1000
|
+
const columnMatch = node.label.match(/^([^:]+):\s*(.+)$/);
|
|
1001
|
+
if (columnMatch?.[1] && columnMatch[2]) {
|
|
1002
|
+
const columnName = columnMatch[1];
|
|
1003
|
+
const rest = columnMatch[2];
|
|
1004
|
+
const typeMatch = rest.match(/^([^\s(]+)\s*(\([^)]+\))$/);
|
|
1005
|
+
if (typeMatch?.[1] && typeMatch[2]) {
|
|
1006
|
+
const typeDisplay = typeMatch[1];
|
|
1007
|
+
const nullability = typeMatch[2];
|
|
1008
|
+
formattedLabel = `${cyan(columnName)}: ${typeDisplay} ${dim(nullability)}`;
|
|
1009
|
+
} else {
|
|
1010
|
+
formattedLabel = `${cyan(columnName)}: ${rest}`;
|
|
1011
|
+
}
|
|
1012
|
+
} else {
|
|
1013
|
+
formattedLabel = node.label;
|
|
1014
|
+
}
|
|
1015
|
+
break;
|
|
1016
|
+
}
|
|
1017
|
+
case "index": {
|
|
1018
|
+
const pkMatch = node.label.match(/^primary key:\s*(.+)$/);
|
|
1019
|
+
if (pkMatch?.[1]) {
|
|
1020
|
+
const columnNames = pkMatch[1];
|
|
1021
|
+
formattedLabel = `${dim("primary key")}: ${cyan(columnNames)}`;
|
|
1022
|
+
} else {
|
|
1023
|
+
const uniqueMatch = node.label.match(/^unique\s+(.+)$/);
|
|
1024
|
+
if (uniqueMatch?.[1]) {
|
|
1025
|
+
const name = uniqueMatch[1];
|
|
1026
|
+
formattedLabel = `${dim("unique")} ${cyan(name)}`;
|
|
1027
|
+
} else {
|
|
1028
|
+
const indexMatch = node.label.match(/^(unique\s+)?index\s+(.+)$/);
|
|
1029
|
+
if (indexMatch?.[2]) {
|
|
1030
|
+
const prefix2 = indexMatch[1] ? `${dim("unique")} ` : "";
|
|
1031
|
+
const name = indexMatch[2];
|
|
1032
|
+
formattedLabel = `${prefix2}${dim("index")} ${cyan(name)}`;
|
|
1033
|
+
} else {
|
|
1034
|
+
formattedLabel = dim(node.label);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
break;
|
|
1039
|
+
}
|
|
1040
|
+
case "extension": {
|
|
1041
|
+
const extMatch = node.label.match(/^([^\s]+)\s+(extension is enabled)$/);
|
|
1042
|
+
if (extMatch?.[1] && extMatch[2]) {
|
|
1043
|
+
const extName = extMatch[1];
|
|
1044
|
+
const rest = extMatch[2];
|
|
1045
|
+
formattedLabel = `${cyan(extName)} ${dim(rest)}`;
|
|
1046
|
+
} else {
|
|
1047
|
+
formattedLabel = magenta(node.label);
|
|
1048
|
+
}
|
|
1049
|
+
break;
|
|
1050
|
+
}
|
|
1051
|
+
default:
|
|
1052
|
+
formattedLabel = node.label;
|
|
1053
|
+
break;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (isRoot) {
|
|
1057
|
+
lines.push(formattedLabel);
|
|
1058
|
+
} else {
|
|
1059
|
+
const treeChar = isLast ? "\u2514" : "\u251C";
|
|
1060
|
+
const treePrefix = `${prefix}${formatDimText(treeChar)}\u2500 `;
|
|
1061
|
+
const isRootChild = prefix === "";
|
|
1062
|
+
const prefixWithoutAnsi = stripAnsi(prefix);
|
|
1063
|
+
const prefixHasVerticalBar = prefixWithoutAnsi.includes("\u2502");
|
|
1064
|
+
if (isRootChild) {
|
|
1065
|
+
lines.push(`${treePrefix}${formattedLabel}`);
|
|
1066
|
+
} else if (prefixHasVerticalBar) {
|
|
1067
|
+
lines.push(`${treePrefix}${formattedLabel}`);
|
|
1068
|
+
} else {
|
|
1069
|
+
lines.push(`${formatDimText("\u2502")} ${treePrefix}${formattedLabel}`);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
if (node.children && node.children.length > 0) {
|
|
1073
|
+
const childPrefix = isRoot ? "" : isLast ? `${prefix} ` : `${prefix}${formatDimText("\u2502")} `;
|
|
1074
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
1075
|
+
const child = node.children[i];
|
|
1076
|
+
if (!child) continue;
|
|
1077
|
+
const isLastChild = i === node.children.length - 1;
|
|
1078
|
+
const childLines = renderSchemaTree(child, flags, {
|
|
1079
|
+
isLast: isLastChild,
|
|
1080
|
+
prefix: childPrefix,
|
|
1081
|
+
useColor,
|
|
1082
|
+
formatDimText,
|
|
1083
|
+
isRoot: false
|
|
1084
|
+
});
|
|
1085
|
+
lines.push(...childLines);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
return lines;
|
|
1089
|
+
}
|
|
1090
|
+
function formatIntrospectOutput(result, schemaView, flags) {
|
|
708
1091
|
if (flags.quiet) {
|
|
709
1092
|
return "";
|
|
710
1093
|
}
|
|
711
1094
|
const lines = [];
|
|
712
1095
|
const prefix = createPrefix(flags);
|
|
713
1096
|
const useColor = flags.color !== false;
|
|
714
|
-
const formatGreen = createColorFormatter(useColor, green);
|
|
715
1097
|
const formatDimText = (text) => formatDim(useColor, text);
|
|
716
|
-
if (
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
}
|
|
1098
|
+
if (schemaView) {
|
|
1099
|
+
const treeLines = renderSchemaTree(schemaView.root, flags, {
|
|
1100
|
+
isLast: true,
|
|
1101
|
+
prefix: "",
|
|
1102
|
+
useColor,
|
|
1103
|
+
formatDimText,
|
|
1104
|
+
isRoot: true
|
|
1105
|
+
});
|
|
1106
|
+
const prefixedTreeLines = treeLines.map((line) => `${prefix}${line}`);
|
|
1107
|
+
lines.push(...prefixedTreeLines);
|
|
1108
|
+
} else {
|
|
1109
|
+
lines.push(`${prefix}\u2714 ${result.summary}`);
|
|
725
1110
|
if (isVerbose(flags, 1)) {
|
|
726
|
-
lines.push(`${prefix}
|
|
1111
|
+
lines.push(`${prefix} Target: ${result.target.familyId}/${result.target.id}`);
|
|
1112
|
+
if (result.meta?.dbUrl) {
|
|
1113
|
+
lines.push(`${prefix} Database: ${result.meta.dbUrl}`);
|
|
1114
|
+
}
|
|
727
1115
|
}
|
|
728
1116
|
}
|
|
729
|
-
|
|
730
|
-
}
|
|
731
|
-
function formatDbInitJson(result) {
|
|
732
|
-
return JSON.stringify(result, null, 2);
|
|
733
|
-
}
|
|
734
|
-
var LEFT_COLUMN_WIDTH = 20;
|
|
735
|
-
var RIGHT_COLUMN_MIN_WIDTH = 40;
|
|
736
|
-
var RIGHT_COLUMN_MAX_WIDTH = 90;
|
|
737
|
-
function getTerminalWidth() {
|
|
738
|
-
const terminalWidth = process.stdout.columns;
|
|
739
|
-
const defaultWidth = Number.parseInt(process.env["CLI_WIDTH"] || "80", 10);
|
|
740
|
-
return terminalWidth || defaultWidth;
|
|
741
|
-
}
|
|
742
|
-
function calculateRightColumnWidth() {
|
|
743
|
-
const terminalWidth = getTerminalWidth();
|
|
744
|
-
const availableWidth = terminalWidth - 2 - LEFT_COLUMN_WIDTH - 2;
|
|
745
|
-
return Math.max(RIGHT_COLUMN_MIN_WIDTH, Math.min(availableWidth, RIGHT_COLUMN_MAX_WIDTH));
|
|
746
|
-
}
|
|
747
|
-
function createPrismaNextBadge(useColor) {
|
|
748
|
-
if (!useColor) {
|
|
749
|
-
return "prisma-next";
|
|
750
|
-
}
|
|
751
|
-
const text = " prisma-next ";
|
|
752
|
-
const body = bgGreen(bold(text));
|
|
753
|
-
const separator = "\uE0B0";
|
|
754
|
-
const tip = green(separator);
|
|
755
|
-
return `${body}${tip}`;
|
|
756
|
-
}
|
|
757
|
-
function createPadFunction() {
|
|
758
|
-
return (s, w) => s + " ".repeat(Math.max(0, w - s.length));
|
|
759
|
-
}
|
|
760
|
-
function formatHeaderLine(options) {
|
|
761
|
-
if (options.operation) {
|
|
762
|
-
return `${options.brand} ${options.operation} \u2192 ${options.intent}`;
|
|
1117
|
+
if (isVerbose(flags, 1)) {
|
|
1118
|
+
lines.push(`${prefix}${formatDimText(` Total time: ${result.timings.total}ms`)}`);
|
|
763
1119
|
}
|
|
764
|
-
return
|
|
765
|
-
}
|
|
766
|
-
function formatReadMoreLine(options) {
|
|
767
|
-
const pad = createPadFunction();
|
|
768
|
-
const labelPadded = pad("Read more", options.maxLabelWidth);
|
|
769
|
-
const valueColored = options.useColor ? blue(options.url) : options.url;
|
|
770
|
-
return `${options.formatDimText("\u2502")} ${labelPadded} ${valueColored}`;
|
|
771
|
-
}
|
|
772
|
-
function padToFixedWidth(text, width) {
|
|
773
|
-
const actualWidth = stringWidth(text);
|
|
774
|
-
const padding = Math.max(0, width - actualWidth);
|
|
775
|
-
return text + " ".repeat(padding);
|
|
776
|
-
}
|
|
777
|
-
function wrapTextAnsi(text, width) {
|
|
778
|
-
const wrapped = wrapAnsi(text, width, { hard: false, trim: true });
|
|
779
|
-
return wrapped.split("\n");
|
|
780
|
-
}
|
|
781
|
-
function formatDefaultValue(value, useColor) {
|
|
782
|
-
const valueStr = String(value);
|
|
783
|
-
const defaultText = `default: ${valueStr}`;
|
|
784
|
-
return useColor ? dim(defaultText) : defaultText;
|
|
1120
|
+
return lines.join("\n");
|
|
785
1121
|
}
|
|
786
|
-
function
|
|
787
|
-
const {
|
|
1122
|
+
function renderSchemaVerificationTree(node, flags, options) {
|
|
1123
|
+
const { isLast, prefix, useColor, formatDimText, isRoot = false } = options;
|
|
788
1124
|
const lines = [];
|
|
789
|
-
|
|
790
|
-
|
|
1125
|
+
let statusGlyph = "";
|
|
1126
|
+
let statusColor = (text) => text;
|
|
1127
|
+
if (useColor) {
|
|
1128
|
+
switch (node.status) {
|
|
1129
|
+
case "pass":
|
|
1130
|
+
statusGlyph = "\u2714";
|
|
1131
|
+
statusColor = green;
|
|
1132
|
+
break;
|
|
1133
|
+
case "warn":
|
|
1134
|
+
statusGlyph = "\u26A0";
|
|
1135
|
+
statusColor = (text) => useColor ? yellow(text) : text;
|
|
1136
|
+
break;
|
|
1137
|
+
case "fail":
|
|
1138
|
+
statusGlyph = "\u2716";
|
|
1139
|
+
statusColor = red;
|
|
1140
|
+
break;
|
|
1141
|
+
}
|
|
1142
|
+
} else {
|
|
1143
|
+
switch (node.status) {
|
|
1144
|
+
case "pass":
|
|
1145
|
+
statusGlyph = "\u2714";
|
|
1146
|
+
break;
|
|
1147
|
+
case "warn":
|
|
1148
|
+
statusGlyph = "\u26A0";
|
|
1149
|
+
break;
|
|
1150
|
+
case "fail":
|
|
1151
|
+
statusGlyph = "\u2716";
|
|
1152
|
+
break;
|
|
1153
|
+
}
|
|
791
1154
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
const
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1155
|
+
let labelColor = (text) => text;
|
|
1156
|
+
let formattedLabel = node.name;
|
|
1157
|
+
if (useColor) {
|
|
1158
|
+
switch (node.kind) {
|
|
1159
|
+
case "contract":
|
|
1160
|
+
case "schema":
|
|
1161
|
+
labelColor = bold;
|
|
1162
|
+
formattedLabel = labelColor(node.name);
|
|
1163
|
+
break;
|
|
1164
|
+
case "table": {
|
|
1165
|
+
const tableMatch = node.name.match(/^table\s+(.+)$/);
|
|
1166
|
+
if (tableMatch?.[1]) {
|
|
1167
|
+
const tableName = tableMatch[1];
|
|
1168
|
+
formattedLabel = `${dim("table")} ${cyan(tableName)}`;
|
|
1169
|
+
} else {
|
|
1170
|
+
formattedLabel = dim(node.name);
|
|
1171
|
+
}
|
|
1172
|
+
break;
|
|
1173
|
+
}
|
|
1174
|
+
case "columns":
|
|
1175
|
+
labelColor = dim;
|
|
1176
|
+
formattedLabel = labelColor(node.name);
|
|
1177
|
+
break;
|
|
1178
|
+
case "column": {
|
|
1179
|
+
const columnMatch = node.name.match(/^([^:]+):\s*(.+)$/);
|
|
1180
|
+
if (columnMatch?.[1] && columnMatch[2]) {
|
|
1181
|
+
const columnName = columnMatch[1];
|
|
1182
|
+
const rest = columnMatch[2];
|
|
1183
|
+
const typeMatch = rest.match(/^([^\s→]+)\s*→\s*([^\s(]+)\s*(\([^)]+\))$/);
|
|
1184
|
+
if (typeMatch?.[1] && typeMatch[2] && typeMatch[3]) {
|
|
1185
|
+
const contractType = typeMatch[1];
|
|
1186
|
+
const nativeType = typeMatch[2];
|
|
1187
|
+
const nullability = typeMatch[3];
|
|
1188
|
+
formattedLabel = `${cyan(columnName)}: ${contractType} \u2192 ${dim(nativeType)} ${dim(nullability)}`;
|
|
1189
|
+
} else {
|
|
1190
|
+
formattedLabel = `${cyan(columnName)}: ${rest}`;
|
|
1191
|
+
}
|
|
1192
|
+
} else {
|
|
1193
|
+
formattedLabel = node.name;
|
|
1194
|
+
}
|
|
1195
|
+
break;
|
|
1196
|
+
}
|
|
1197
|
+
case "type":
|
|
1198
|
+
case "nullability":
|
|
1199
|
+
labelColor = (text) => text;
|
|
1200
|
+
formattedLabel = labelColor(node.name);
|
|
1201
|
+
break;
|
|
1202
|
+
case "primaryKey": {
|
|
1203
|
+
const pkMatch = node.name.match(/^primary key:\s*(.+)$/);
|
|
1204
|
+
if (pkMatch?.[1]) {
|
|
1205
|
+
const columnNames = pkMatch[1];
|
|
1206
|
+
formattedLabel = `${dim("primary key")}: ${cyan(columnNames)}`;
|
|
1207
|
+
} else {
|
|
1208
|
+
formattedLabel = dim(node.name);
|
|
1209
|
+
}
|
|
1210
|
+
break;
|
|
1211
|
+
}
|
|
1212
|
+
case "foreignKey":
|
|
1213
|
+
case "unique":
|
|
1214
|
+
case "index":
|
|
1215
|
+
labelColor = dim;
|
|
1216
|
+
formattedLabel = labelColor(node.name);
|
|
1217
|
+
break;
|
|
1218
|
+
case "extension": {
|
|
1219
|
+
const dbMatch = node.name.match(/^database is\s+(.+)$/);
|
|
1220
|
+
if (dbMatch?.[1]) {
|
|
1221
|
+
const dbName = dbMatch[1];
|
|
1222
|
+
formattedLabel = `${dim("database is")} ${cyan(dbName)}`;
|
|
1223
|
+
} else {
|
|
1224
|
+
const extMatch = node.name.match(/^([^\s]+)\s+(extension is enabled)$/);
|
|
1225
|
+
if (extMatch?.[1] && extMatch[2]) {
|
|
1226
|
+
const extName = extMatch[1];
|
|
1227
|
+
const rest = extMatch[2];
|
|
1228
|
+
formattedLabel = `${cyan(extName)} ${dim(rest)}`;
|
|
1229
|
+
} else {
|
|
1230
|
+
labelColor = magenta;
|
|
1231
|
+
formattedLabel = labelColor(node.name);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
break;
|
|
821
1235
|
}
|
|
1236
|
+
default:
|
|
1237
|
+
formattedLabel = node.name;
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
} else {
|
|
1241
|
+
formattedLabel = node.name;
|
|
1242
|
+
}
|
|
1243
|
+
const statusGlyphColored = statusColor(statusGlyph);
|
|
1244
|
+
let nodeLabel = formattedLabel;
|
|
1245
|
+
if ((node.status === "fail" || node.status === "warn") && node.message && node.message.length > 0) {
|
|
1246
|
+
const messageText = formatDimText(`(${node.message})`);
|
|
1247
|
+
nodeLabel = `${formattedLabel} ${messageText}`;
|
|
1248
|
+
}
|
|
1249
|
+
if (isRoot) {
|
|
1250
|
+
lines.push(`${statusGlyphColored} ${nodeLabel}`);
|
|
1251
|
+
} else {
|
|
1252
|
+
const treeChar = isLast ? "\u2514" : "\u251C";
|
|
1253
|
+
const treePrefix = `${prefix}${formatDimText(treeChar)}\u2500 `;
|
|
1254
|
+
const isRootChild = prefix === "";
|
|
1255
|
+
const prefixWithoutAnsi = stripAnsi(prefix);
|
|
1256
|
+
const prefixHasVerticalBar = prefixWithoutAnsi.includes("\u2502");
|
|
1257
|
+
if (isRootChild) {
|
|
1258
|
+
lines.push(`${treePrefix}${statusGlyphColored} ${nodeLabel}`);
|
|
1259
|
+
} else if (prefixHasVerticalBar) {
|
|
1260
|
+
lines.push(`${treePrefix}${statusGlyphColored} ${nodeLabel}`);
|
|
822
1261
|
} else {
|
|
823
|
-
|
|
824
|
-
const treePrefix = `${prefix}\u2500 `;
|
|
825
|
-
const treePrefixWidth = stringWidth(stripAnsi(treePrefix));
|
|
826
|
-
const remainingWidth = LEFT_COLUMN_WIDTH - treePrefixWidth;
|
|
827
|
-
const commandNamePadded = padToFixedWidth(cmd.name(), remainingWidth);
|
|
828
|
-
const commandNameColored = useColor ? cyan(commandNamePadded) : commandNamePadded;
|
|
829
|
-
const shortDescription = cmd.description() || "";
|
|
830
|
-
lines.push(`${formatDimText("\u2502")} ${treePrefix}${commandNameColored} ${shortDescription}`);
|
|
1262
|
+
lines.push(`${formatDimText("\u2502")} ${treePrefix}${statusGlyphColored} ${nodeLabel}`);
|
|
831
1263
|
}
|
|
832
1264
|
}
|
|
833
|
-
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1265
|
+
if (node.children && node.children.length > 0) {
|
|
1266
|
+
const childPrefix = isRoot ? "" : isLast ? `${prefix} ` : `${prefix}${formatDimText("\u2502")} `;
|
|
1267
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
1268
|
+
const child = node.children[i];
|
|
1269
|
+
if (!child) continue;
|
|
1270
|
+
const isLastChild = i === node.children.length - 1;
|
|
1271
|
+
const childLines = renderSchemaVerificationTree(child, flags, {
|
|
1272
|
+
isLast: isLastChild,
|
|
1273
|
+
prefix: childPrefix,
|
|
1274
|
+
useColor,
|
|
1275
|
+
formatDimText,
|
|
1276
|
+
isRoot: false
|
|
1277
|
+
});
|
|
1278
|
+
lines.push(...childLines);
|
|
846
1279
|
}
|
|
847
1280
|
}
|
|
848
1281
|
return lines;
|
|
849
1282
|
}
|
|
850
|
-
function
|
|
1283
|
+
function formatSchemaVerifyOutput(result, flags) {
|
|
1284
|
+
if (flags.quiet) {
|
|
1285
|
+
return "";
|
|
1286
|
+
}
|
|
851
1287
|
const lines = [];
|
|
852
|
-
const
|
|
1288
|
+
const prefix = createPrefix(flags);
|
|
1289
|
+
const useColor = flags.color !== false;
|
|
1290
|
+
const formatGreen = createColorFormatter(useColor, green);
|
|
1291
|
+
const formatRed = createColorFormatter(useColor, red);
|
|
853
1292
|
const formatDimText = (text) => formatDim(useColor, text);
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
}
|
|
865
|
-
if (options.url) {
|
|
866
|
-
lines.push(formatDimText("\u2502"));
|
|
1293
|
+
const treeLines = renderSchemaVerificationTree(result.schema.root, flags, {
|
|
1294
|
+
isLast: true,
|
|
1295
|
+
prefix: "",
|
|
1296
|
+
useColor,
|
|
1297
|
+
formatDimText,
|
|
1298
|
+
isRoot: true
|
|
1299
|
+
});
|
|
1300
|
+
const prefixedTreeLines = treeLines.map((line) => `${prefix}${line}`);
|
|
1301
|
+
lines.push(...prefixedTreeLines);
|
|
1302
|
+
if (isVerbose(flags, 1)) {
|
|
1303
|
+
lines.push(`${prefix}${formatDimText(` Total time: ${result.timings.total}ms`)}`);
|
|
867
1304
|
lines.push(
|
|
868
|
-
|
|
869
|
-
url: options.url,
|
|
870
|
-
maxLabelWidth: LEFT_COLUMN_WIDTH,
|
|
871
|
-
useColor,
|
|
872
|
-
formatDimText
|
|
873
|
-
})
|
|
1305
|
+
`${prefix}${formatDimText(` pass=${result.schema.counts.pass} warn=${result.schema.counts.warn} fail=${result.schema.counts.fail}`)}`
|
|
874
1306
|
);
|
|
875
1307
|
}
|
|
876
|
-
lines.push(
|
|
877
|
-
|
|
878
|
-
|
|
1308
|
+
lines.push("");
|
|
1309
|
+
if (result.ok) {
|
|
1310
|
+
lines.push(`${prefix}${formatGreen("\u2714")} ${result.summary}`);
|
|
1311
|
+
} else {
|
|
1312
|
+
const codeText = result.code ? ` (${result.code})` : "";
|
|
1313
|
+
lines.push(`${prefix}${formatRed("\u2716")} ${result.summary}${codeText}`);
|
|
1314
|
+
}
|
|
1315
|
+
return lines.join("\n");
|
|
879
1316
|
}
|
|
880
|
-
function
|
|
881
|
-
|
|
882
|
-
|
|
1317
|
+
function formatSchemaVerifyJson(result) {
|
|
1318
|
+
return JSON.stringify(result, null, 2);
|
|
1319
|
+
}
|
|
1320
|
+
function formatSignOutput(result, flags) {
|
|
1321
|
+
if (flags.quiet) {
|
|
1322
|
+
return "";
|
|
1323
|
+
}
|
|
1324
|
+
const lines = [];
|
|
1325
|
+
const prefix = createPrefix(flags);
|
|
1326
|
+
const useColor = flags.color !== false;
|
|
1327
|
+
const formatGreen = createColorFormatter(useColor, green);
|
|
1328
|
+
const formatDimText = (text) => formatDim(useColor, text);
|
|
1329
|
+
if (result.ok) {
|
|
1330
|
+
lines.push(`${prefix}${formatGreen("\u2714")} Database signed`);
|
|
1331
|
+
const previousHash = result.marker.previous?.coreHash ?? "none";
|
|
1332
|
+
const currentHash = result.contract.coreHash;
|
|
1333
|
+
lines.push(`${prefix}${formatDimText(` from: ${previousHash}`)}`);
|
|
1334
|
+
lines.push(`${prefix}${formatDimText(` to: ${currentHash}`)}`);
|
|
1335
|
+
if (isVerbose(flags, 1)) {
|
|
1336
|
+
if (result.contract.profileHash) {
|
|
1337
|
+
lines.push(`${prefix}${formatDimText(` profileHash: ${result.contract.profileHash}`)}`);
|
|
1338
|
+
}
|
|
1339
|
+
if (result.marker.previous?.profileHash) {
|
|
1340
|
+
lines.push(
|
|
1341
|
+
`${prefix}${formatDimText(` previous profileHash: ${result.marker.previous.profileHash}`)}`
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
lines.push(`${prefix}${formatDimText(` Total time: ${result.timings.total}ms`)}`);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
return lines.join("\n");
|
|
1348
|
+
}
|
|
1349
|
+
function formatSignJson(result) {
|
|
1350
|
+
return JSON.stringify(result, null, 2);
|
|
1351
|
+
}
|
|
1352
|
+
function formatDbInitPlanOutput(result, flags) {
|
|
1353
|
+
if (flags.quiet) {
|
|
1354
|
+
return "";
|
|
1355
|
+
}
|
|
1356
|
+
const lines = [];
|
|
1357
|
+
const prefix = createPrefix(flags);
|
|
1358
|
+
const useColor = flags.color !== false;
|
|
1359
|
+
const formatGreen = createColorFormatter(useColor, green);
|
|
1360
|
+
const formatDimText = (text) => formatDim(useColor, text);
|
|
1361
|
+
const operationCount = result.plan?.operations.length ?? 0;
|
|
1362
|
+
lines.push(`${prefix}${formatGreen("\u2714")} Planned ${operationCount} operation(s)`);
|
|
1363
|
+
if (result.plan?.operations && result.plan.operations.length > 0) {
|
|
1364
|
+
lines.push(`${prefix}${formatDimText("\u2502")}`);
|
|
1365
|
+
for (let i = 0; i < result.plan.operations.length; i++) {
|
|
1366
|
+
const op = result.plan.operations[i];
|
|
1367
|
+
if (!op) continue;
|
|
1368
|
+
const isLast = i === result.plan.operations.length - 1;
|
|
1369
|
+
const treeChar = isLast ? "\u2514" : "\u251C";
|
|
1370
|
+
const opClass = formatDimText(`[${op.operationClass}]`);
|
|
1371
|
+
lines.push(`${prefix}${formatDimText(treeChar)}\u2500 ${op.label} ${opClass}`);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
if (result.plan?.destination) {
|
|
1375
|
+
lines.push(`${prefix}`);
|
|
1376
|
+
lines.push(
|
|
1377
|
+
`${prefix}${formatDimText(`Destination hash: ${result.plan.destination.coreHash}`)}`
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1380
|
+
if (isVerbose(flags, 1)) {
|
|
1381
|
+
lines.push(`${prefix}${formatDimText(`Total time: ${result.timings.total}ms`)}`);
|
|
1382
|
+
}
|
|
1383
|
+
lines.push(`${prefix}`);
|
|
1384
|
+
lines.push(`${prefix}${formatDimText("This is a dry run. No changes were applied.")}`);
|
|
1385
|
+
lines.push(`${prefix}${formatDimText("Run without --plan to apply changes.")}`);
|
|
1386
|
+
return lines.join("\n");
|
|
1387
|
+
}
|
|
1388
|
+
function formatDbInitApplyOutput(result, flags) {
|
|
1389
|
+
if (flags.quiet) {
|
|
1390
|
+
return "";
|
|
1391
|
+
}
|
|
1392
|
+
const lines = [];
|
|
1393
|
+
const prefix = createPrefix(flags);
|
|
1394
|
+
const useColor = flags.color !== false;
|
|
1395
|
+
const formatGreen = createColorFormatter(useColor, green);
|
|
1396
|
+
const formatDimText = (text) => formatDim(useColor, text);
|
|
1397
|
+
if (result.ok) {
|
|
1398
|
+
const executed = result.execution?.operationsExecuted ?? 0;
|
|
1399
|
+
lines.push(`${prefix}${formatGreen("\u2714")} Applied ${executed} operation(s)`);
|
|
1400
|
+
if (result.marker) {
|
|
1401
|
+
lines.push(`${prefix}${formatDimText(` Marker written: ${result.marker.coreHash}`)}`);
|
|
1402
|
+
if (result.marker.profileHash) {
|
|
1403
|
+
lines.push(`${prefix}${formatDimText(` Profile hash: ${result.marker.profileHash}`)}`);
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
if (isVerbose(flags, 1)) {
|
|
1407
|
+
lines.push(`${prefix}${formatDimText(` Total time: ${result.timings.total}ms`)}`);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
return lines.join("\n");
|
|
1411
|
+
}
|
|
1412
|
+
function formatDbInitJson(result) {
|
|
1413
|
+
return JSON.stringify(result, null, 2);
|
|
1414
|
+
}
|
|
1415
|
+
var LEFT_COLUMN_WIDTH = 20;
|
|
1416
|
+
var RIGHT_COLUMN_MIN_WIDTH = 40;
|
|
1417
|
+
var RIGHT_COLUMN_MAX_WIDTH = 90;
|
|
1418
|
+
function getTerminalWidth() {
|
|
1419
|
+
const terminalWidth = process.stdout.columns;
|
|
1420
|
+
const defaultWidth = Number.parseInt(process.env["CLI_WIDTH"] || "80", 10);
|
|
1421
|
+
return terminalWidth || defaultWidth;
|
|
1422
|
+
}
|
|
1423
|
+
function calculateRightColumnWidth() {
|
|
1424
|
+
const terminalWidth = getTerminalWidth();
|
|
1425
|
+
const availableWidth = terminalWidth - 2 - LEFT_COLUMN_WIDTH - 2;
|
|
1426
|
+
return Math.max(RIGHT_COLUMN_MIN_WIDTH, Math.min(availableWidth, RIGHT_COLUMN_MAX_WIDTH));
|
|
1427
|
+
}
|
|
1428
|
+
function createPrismaNextBadge(useColor) {
|
|
1429
|
+
if (!useColor) {
|
|
1430
|
+
return "prisma-next";
|
|
1431
|
+
}
|
|
1432
|
+
const text = " prisma-next ";
|
|
1433
|
+
const body = bgGreen(bold(text));
|
|
1434
|
+
const separator = "\uE0B0";
|
|
1435
|
+
const tip = green(separator);
|
|
1436
|
+
return `${body}${tip}`;
|
|
1437
|
+
}
|
|
1438
|
+
function createPadFunction() {
|
|
1439
|
+
return (s, w) => s + " ".repeat(Math.max(0, w - s.length));
|
|
1440
|
+
}
|
|
1441
|
+
function formatHeaderLine(options) {
|
|
1442
|
+
if (options.operation) {
|
|
1443
|
+
return `${options.brand} ${options.operation} \u2192 ${options.intent}`;
|
|
1444
|
+
}
|
|
1445
|
+
return `${options.brand} ${options.intent}`;
|
|
1446
|
+
}
|
|
1447
|
+
function formatReadMoreLine(options) {
|
|
1448
|
+
const pad = createPadFunction();
|
|
1449
|
+
const labelPadded = pad("Read more", options.maxLabelWidth);
|
|
1450
|
+
const valueColored = options.useColor ? blue(options.url) : options.url;
|
|
1451
|
+
return `${options.formatDimText("\u2502")} ${labelPadded} ${valueColored}`;
|
|
1452
|
+
}
|
|
1453
|
+
function padToFixedWidth(text, width) {
|
|
1454
|
+
const actualWidth = stringWidth(text);
|
|
1455
|
+
const padding = Math.max(0, width - actualWidth);
|
|
1456
|
+
return text + " ".repeat(padding);
|
|
1457
|
+
}
|
|
1458
|
+
function wrapTextAnsi(text, width) {
|
|
1459
|
+
const wrapped = wrapAnsi(text, width, { hard: false, trim: true });
|
|
1460
|
+
return wrapped.split("\n");
|
|
1461
|
+
}
|
|
1462
|
+
function formatDefaultValue(value, useColor) {
|
|
1463
|
+
const valueStr = String(value);
|
|
1464
|
+
const defaultText = `default: ${valueStr}`;
|
|
1465
|
+
return useColor ? dim(defaultText) : defaultText;
|
|
1466
|
+
}
|
|
1467
|
+
function renderCommandTree(options) {
|
|
1468
|
+
const { commands, useColor, formatDimText, hasItemsAfter, continuationPrefix } = options;
|
|
1469
|
+
const lines = [];
|
|
1470
|
+
if (commands.length === 0) {
|
|
1471
|
+
return lines;
|
|
1472
|
+
}
|
|
1473
|
+
for (let i = 0; i < commands.length; i++) {
|
|
1474
|
+
const cmd = commands[i];
|
|
1475
|
+
if (!cmd) continue;
|
|
1476
|
+
const subcommands = cmd.commands.filter((subcmd) => !subcmd.name().startsWith("_"));
|
|
1477
|
+
const isLastCommand = i === commands.length - 1;
|
|
1478
|
+
if (subcommands.length > 0) {
|
|
1479
|
+
const prefix = isLastCommand && !hasItemsAfter ? formatDimText("\u2514") : formatDimText("\u251C");
|
|
1480
|
+
const treePrefix = `${prefix}\u2500 `;
|
|
1481
|
+
const treePrefixWidth = stringWidth(stripAnsi(treePrefix));
|
|
1482
|
+
const remainingWidth = LEFT_COLUMN_WIDTH - treePrefixWidth;
|
|
1483
|
+
const commandNamePadded = padToFixedWidth(cmd.name(), remainingWidth);
|
|
1484
|
+
const commandNameColored = useColor ? cyan(commandNamePadded) : commandNamePadded;
|
|
1485
|
+
lines.push(`${formatDimText("\u2502")} ${treePrefix}${commandNameColored}`);
|
|
1486
|
+
for (let j = 0; j < subcommands.length; j++) {
|
|
1487
|
+
const subcmd = subcommands[j];
|
|
1488
|
+
if (!subcmd) continue;
|
|
1489
|
+
const isLastSubcommand = j === subcommands.length - 1;
|
|
1490
|
+
const shortDescription = subcmd.description() || "";
|
|
1491
|
+
const treeChar = isLastSubcommand ? "\u2514" : "\u251C";
|
|
1492
|
+
const continuation = continuationPrefix ?? (isLastCommand && isLastSubcommand && !hasItemsAfter ? " " : formatDimText("\u2502"));
|
|
1493
|
+
const continuationStr = continuation === " " ? " " : continuation;
|
|
1494
|
+
const subTreePrefix = `${continuationStr} ${formatDimText(treeChar)}\u2500 `;
|
|
1495
|
+
const subTreePrefixWidth = stringWidth(stripAnsi(subTreePrefix));
|
|
1496
|
+
const subRemainingWidth = LEFT_COLUMN_WIDTH - subTreePrefixWidth;
|
|
1497
|
+
const subcommandNamePadded = padToFixedWidth(subcmd.name(), subRemainingWidth);
|
|
1498
|
+
const subcommandNameColored = useColor ? cyan(subcommandNamePadded) : subcommandNamePadded;
|
|
1499
|
+
lines.push(
|
|
1500
|
+
`${formatDimText("\u2502")} ${subTreePrefix}${subcommandNameColored} ${shortDescription}`
|
|
1501
|
+
);
|
|
1502
|
+
}
|
|
1503
|
+
} else {
|
|
1504
|
+
const prefix = isLastCommand && !hasItemsAfter ? formatDimText("\u2514") : formatDimText("\u251C");
|
|
1505
|
+
const treePrefix = `${prefix}\u2500 `;
|
|
1506
|
+
const treePrefixWidth = stringWidth(stripAnsi(treePrefix));
|
|
1507
|
+
const remainingWidth = LEFT_COLUMN_WIDTH - treePrefixWidth;
|
|
1508
|
+
const commandNamePadded = padToFixedWidth(cmd.name(), remainingWidth);
|
|
1509
|
+
const commandNameColored = useColor ? cyan(commandNamePadded) : commandNamePadded;
|
|
1510
|
+
const shortDescription = cmd.description() || "";
|
|
1511
|
+
lines.push(`${formatDimText("\u2502")} ${treePrefix}${commandNameColored} ${shortDescription}`);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
return lines;
|
|
1515
|
+
}
|
|
1516
|
+
function formatMultilineDescription(options) {
|
|
1517
|
+
const lines = [];
|
|
1518
|
+
const formatGreen = (text) => options.useColor ? green(text) : text;
|
|
1519
|
+
const rightColumnWidth = calculateRightColumnWidth();
|
|
1520
|
+
const totalWidth = 2 + LEFT_COLUMN_WIDTH + 2 + rightColumnWidth;
|
|
1521
|
+
const wrapWidth = totalWidth - 2;
|
|
1522
|
+
for (const descLine of options.descriptionLines) {
|
|
1523
|
+
const formattedLine = descLine.replace(/Prisma Next/g, (match) => formatGreen(match));
|
|
1524
|
+
const wrappedLines = wrapTextAnsi(formattedLine, wrapWidth);
|
|
1525
|
+
for (const wrappedLine of wrappedLines) {
|
|
1526
|
+
lines.push(`${options.formatDimText("\u2502")} ${wrappedLine}`);
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
return lines;
|
|
1530
|
+
}
|
|
1531
|
+
function formatStyledHeader(options) {
|
|
1532
|
+
const lines = [];
|
|
1533
|
+
const useColor = options.flags.color !== false;
|
|
1534
|
+
const formatDimText = (text) => formatDim(useColor, text);
|
|
1535
|
+
const brand = createPrismaNextBadge(useColor);
|
|
1536
|
+
const operation = useColor ? bold(options.command) : options.command;
|
|
1537
|
+
const intent = formatDimText(options.description);
|
|
1538
|
+
lines.push(formatHeaderLine({ brand, operation, intent }));
|
|
1539
|
+
lines.push(formatDimText("\u2502"));
|
|
1540
|
+
for (const detail of options.details) {
|
|
1541
|
+
const labelWithColon = `${detail.label}:`;
|
|
1542
|
+
const labelPadded = padToFixedWidth(labelWithColon, LEFT_COLUMN_WIDTH);
|
|
1543
|
+
const labelColored = useColor ? cyan(labelPadded) : labelPadded;
|
|
1544
|
+
lines.push(`${formatDimText("\u2502")} ${labelColored} ${detail.value}`);
|
|
1545
|
+
}
|
|
1546
|
+
if (options.url) {
|
|
1547
|
+
lines.push(formatDimText("\u2502"));
|
|
1548
|
+
lines.push(
|
|
1549
|
+
formatReadMoreLine({
|
|
1550
|
+
url: options.url,
|
|
1551
|
+
maxLabelWidth: LEFT_COLUMN_WIDTH,
|
|
1552
|
+
useColor,
|
|
1553
|
+
formatDimText
|
|
1554
|
+
})
|
|
1555
|
+
);
|
|
1556
|
+
}
|
|
1557
|
+
lines.push(formatDimText("\u2514"));
|
|
1558
|
+
return `${lines.join("\n")}
|
|
1559
|
+
`;
|
|
1560
|
+
}
|
|
1561
|
+
function formatSuccessMessage(flags) {
|
|
1562
|
+
const useColor = flags.color !== false;
|
|
1563
|
+
const formatGreen = createColorFormatter(useColor, green);
|
|
883
1564
|
return `${formatGreen("\u2714")} Success`;
|
|
884
1565
|
}
|
|
885
1566
|
function getCommandDocsUrl(commandPath) {
|
|
@@ -985,835 +1666,64 @@ function formatRootHelp(options) {
|
|
|
985
1666
|
lines.push(formatHeaderLine({ brand, operation: "", intent }));
|
|
986
1667
|
lines.push(formatDimText("\u2502"));
|
|
987
1668
|
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
|
-
|
|
1251
|
-
// src/control-api/operations/db-init.ts
|
|
1252
|
-
import { notOk as notOk2, ok as ok2 } from "@prisma-next/utils/result";
|
|
1253
|
-
async function executeDbInit(options) {
|
|
1254
|
-
const { driver, familyInstance, contractIR, mode, migrations, frameworkComponents, onProgress } = options;
|
|
1255
|
-
const planner = migrations.createPlanner(familyInstance);
|
|
1256
|
-
const runner = migrations.createRunner(familyInstance);
|
|
1257
|
-
const introspectSpanId = "introspect";
|
|
1258
|
-
onProgress?.({
|
|
1259
|
-
action: "dbInit",
|
|
1260
|
-
kind: "spanStart",
|
|
1261
|
-
spanId: introspectSpanId,
|
|
1262
|
-
label: "Introspecting database schema"
|
|
1263
|
-
});
|
|
1264
|
-
const schemaIR = await familyInstance.introspect({ driver });
|
|
1265
|
-
onProgress?.({
|
|
1266
|
-
action: "dbInit",
|
|
1267
|
-
kind: "spanEnd",
|
|
1268
|
-
spanId: introspectSpanId,
|
|
1269
|
-
outcome: "ok"
|
|
1270
|
-
});
|
|
1271
|
-
const policy = { allowedOperationClasses: ["additive"] };
|
|
1272
|
-
const planSpanId = "plan";
|
|
1273
|
-
onProgress?.({
|
|
1274
|
-
action: "dbInit",
|
|
1275
|
-
kind: "spanStart",
|
|
1276
|
-
spanId: planSpanId,
|
|
1277
|
-
label: "Planning migration"
|
|
1278
|
-
});
|
|
1279
|
-
const plannerResult = await planner.plan({
|
|
1280
|
-
contract: contractIR,
|
|
1281
|
-
schema: schemaIR,
|
|
1282
|
-
policy,
|
|
1283
|
-
frameworkComponents
|
|
1284
|
-
});
|
|
1285
|
-
if (plannerResult.kind === "failure") {
|
|
1286
|
-
onProgress?.({
|
|
1287
|
-
action: "dbInit",
|
|
1288
|
-
kind: "spanEnd",
|
|
1289
|
-
spanId: planSpanId,
|
|
1290
|
-
outcome: "error"
|
|
1291
|
-
});
|
|
1292
|
-
return notOk2({
|
|
1293
|
-
code: "PLANNING_FAILED",
|
|
1294
|
-
summary: "Migration planning failed due to conflicts",
|
|
1295
|
-
conflicts: plannerResult.conflicts,
|
|
1296
|
-
why: void 0,
|
|
1297
|
-
meta: void 0
|
|
1298
|
-
});
|
|
1299
|
-
}
|
|
1300
|
-
const migrationPlan = plannerResult.plan;
|
|
1301
|
-
onProgress?.({
|
|
1302
|
-
action: "dbInit",
|
|
1303
|
-
kind: "spanEnd",
|
|
1304
|
-
spanId: planSpanId,
|
|
1305
|
-
outcome: "ok"
|
|
1306
|
-
});
|
|
1307
|
-
const checkMarkerSpanId = "checkMarker";
|
|
1308
|
-
onProgress?.({
|
|
1309
|
-
action: "dbInit",
|
|
1310
|
-
kind: "spanStart",
|
|
1311
|
-
spanId: checkMarkerSpanId,
|
|
1312
|
-
label: "Checking contract marker"
|
|
1313
|
-
});
|
|
1314
|
-
const existingMarker = await familyInstance.readMarker({ driver });
|
|
1315
|
-
if (existingMarker) {
|
|
1316
|
-
const markerMatchesDestination = existingMarker.coreHash === migrationPlan.destination.coreHash && (!migrationPlan.destination.profileHash || existingMarker.profileHash === migrationPlan.destination.profileHash);
|
|
1317
|
-
if (markerMatchesDestination) {
|
|
1318
|
-
onProgress?.({
|
|
1319
|
-
action: "dbInit",
|
|
1320
|
-
kind: "spanEnd",
|
|
1321
|
-
spanId: checkMarkerSpanId,
|
|
1322
|
-
outcome: "skipped"
|
|
1323
|
-
});
|
|
1324
|
-
const result2 = {
|
|
1325
|
-
mode,
|
|
1326
|
-
plan: { operations: [] },
|
|
1327
|
-
...mode === "apply" ? {
|
|
1328
|
-
execution: { operationsPlanned: 0, operationsExecuted: 0 },
|
|
1329
|
-
marker: {
|
|
1330
|
-
coreHash: existingMarker.coreHash,
|
|
1331
|
-
profileHash: existingMarker.profileHash
|
|
1332
|
-
}
|
|
1333
|
-
} : {},
|
|
1334
|
-
summary: "Database already at target contract state"
|
|
1335
|
-
};
|
|
1336
|
-
return ok2(result2);
|
|
1337
|
-
}
|
|
1338
|
-
onProgress?.({
|
|
1339
|
-
action: "dbInit",
|
|
1340
|
-
kind: "spanEnd",
|
|
1341
|
-
spanId: checkMarkerSpanId,
|
|
1342
|
-
outcome: "error"
|
|
1343
|
-
});
|
|
1344
|
-
return notOk2({
|
|
1345
|
-
code: "MARKER_ORIGIN_MISMATCH",
|
|
1346
|
-
summary: "Existing contract marker does not match plan destination",
|
|
1347
|
-
marker: {
|
|
1348
|
-
coreHash: existingMarker.coreHash,
|
|
1349
|
-
profileHash: existingMarker.profileHash
|
|
1350
|
-
},
|
|
1351
|
-
destination: {
|
|
1352
|
-
coreHash: migrationPlan.destination.coreHash,
|
|
1353
|
-
profileHash: migrationPlan.destination.profileHash
|
|
1354
|
-
},
|
|
1355
|
-
why: void 0,
|
|
1356
|
-
conflicts: void 0,
|
|
1357
|
-
meta: void 0
|
|
1358
|
-
});
|
|
1359
|
-
}
|
|
1360
|
-
onProgress?.({
|
|
1361
|
-
action: "dbInit",
|
|
1362
|
-
kind: "spanEnd",
|
|
1363
|
-
spanId: checkMarkerSpanId,
|
|
1364
|
-
outcome: "ok"
|
|
1365
|
-
});
|
|
1366
|
-
if (mode === "plan") {
|
|
1367
|
-
const result2 = {
|
|
1368
|
-
mode: "plan",
|
|
1369
|
-
plan: { operations: migrationPlan.operations },
|
|
1370
|
-
summary: `Planned ${migrationPlan.operations.length} operation(s)`
|
|
1371
|
-
};
|
|
1372
|
-
return ok2(result2);
|
|
1373
|
-
}
|
|
1374
|
-
const applySpanId = "apply";
|
|
1375
|
-
onProgress?.({
|
|
1376
|
-
action: "dbInit",
|
|
1377
|
-
kind: "spanStart",
|
|
1378
|
-
spanId: applySpanId,
|
|
1379
|
-
label: "Applying migration plan"
|
|
1380
|
-
});
|
|
1381
|
-
const callbacks = onProgress ? {
|
|
1382
|
-
onOperationStart: (op) => {
|
|
1383
|
-
onProgress({
|
|
1384
|
-
action: "dbInit",
|
|
1385
|
-
kind: "spanStart",
|
|
1386
|
-
spanId: `operation:${op.id}`,
|
|
1387
|
-
parentSpanId: applySpanId,
|
|
1388
|
-
label: op.label
|
|
1389
|
-
});
|
|
1390
|
-
},
|
|
1391
|
-
onOperationComplete: (op) => {
|
|
1392
|
-
onProgress({
|
|
1393
|
-
action: "dbInit",
|
|
1394
|
-
kind: "spanEnd",
|
|
1395
|
-
spanId: `operation:${op.id}`,
|
|
1396
|
-
outcome: "ok"
|
|
1397
|
-
});
|
|
1398
|
-
}
|
|
1399
|
-
} : void 0;
|
|
1400
|
-
const runnerResult = await runner.execute({
|
|
1401
|
-
plan: migrationPlan,
|
|
1402
|
-
driver,
|
|
1403
|
-
destinationContract: contractIR,
|
|
1404
|
-
policy,
|
|
1405
|
-
...callbacks ? { callbacks } : {},
|
|
1406
|
-
// db init plans and applies back-to-back from a fresh introspection, so per-operation
|
|
1407
|
-
// pre/postchecks and the idempotency probe are usually redundant overhead. We still
|
|
1408
|
-
// enforce marker/origin compatibility and a full schema verification after apply.
|
|
1409
|
-
executionChecks: {
|
|
1410
|
-
prechecks: false,
|
|
1411
|
-
postchecks: false,
|
|
1412
|
-
idempotencyChecks: false
|
|
1413
|
-
},
|
|
1414
|
-
frameworkComponents
|
|
1415
|
-
});
|
|
1416
|
-
if (!runnerResult.ok) {
|
|
1417
|
-
onProgress?.({
|
|
1418
|
-
action: "dbInit",
|
|
1419
|
-
kind: "spanEnd",
|
|
1420
|
-
spanId: applySpanId,
|
|
1421
|
-
outcome: "error"
|
|
1422
|
-
});
|
|
1423
|
-
return notOk2({
|
|
1424
|
-
code: "RUNNER_FAILED",
|
|
1425
|
-
summary: runnerResult.failure.summary,
|
|
1426
|
-
why: runnerResult.failure.why,
|
|
1427
|
-
meta: runnerResult.failure.meta,
|
|
1428
|
-
conflicts: void 0
|
|
1429
|
-
});
|
|
1430
|
-
}
|
|
1431
|
-
const execution = runnerResult.value;
|
|
1432
|
-
onProgress?.({
|
|
1433
|
-
action: "dbInit",
|
|
1434
|
-
kind: "spanEnd",
|
|
1435
|
-
spanId: applySpanId,
|
|
1436
|
-
outcome: "ok"
|
|
1437
|
-
});
|
|
1438
|
-
const result = {
|
|
1439
|
-
mode: "apply",
|
|
1440
|
-
plan: { operations: migrationPlan.operations },
|
|
1441
|
-
execution: {
|
|
1442
|
-
operationsPlanned: execution.operationsPlanned,
|
|
1443
|
-
operationsExecuted: execution.operationsExecuted
|
|
1444
|
-
},
|
|
1445
|
-
marker: migrationPlan.destination.profileHash ? {
|
|
1446
|
-
coreHash: migrationPlan.destination.coreHash,
|
|
1447
|
-
profileHash: migrationPlan.destination.profileHash
|
|
1448
|
-
} : { coreHash: migrationPlan.destination.coreHash },
|
|
1449
|
-
summary: `Applied ${execution.operationsExecuted} operation(s), marker written`
|
|
1450
|
-
};
|
|
1451
|
-
return ok2(result);
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
// src/control-api/client.ts
|
|
1455
|
-
function createControlClient(options) {
|
|
1456
|
-
return new ControlClientImpl(options);
|
|
1457
|
-
}
|
|
1458
|
-
var ControlClientImpl = class {
|
|
1459
|
-
options;
|
|
1460
|
-
stack = null;
|
|
1461
|
-
driver = null;
|
|
1462
|
-
familyInstance = null;
|
|
1463
|
-
frameworkComponents = null;
|
|
1464
|
-
initialized = false;
|
|
1465
|
-
defaultConnection;
|
|
1466
|
-
constructor(options) {
|
|
1467
|
-
this.options = options;
|
|
1468
|
-
this.defaultConnection = options.connection;
|
|
1469
|
-
}
|
|
1470
|
-
init() {
|
|
1471
|
-
if (this.initialized) {
|
|
1472
|
-
return;
|
|
1473
|
-
}
|
|
1474
|
-
this.stack = createControlPlaneStack2({
|
|
1475
|
-
target: this.options.target,
|
|
1476
|
-
adapter: this.options.adapter,
|
|
1477
|
-
driver: this.options.driver,
|
|
1478
|
-
extensionPacks: this.options.extensionPacks
|
|
1479
|
-
});
|
|
1480
|
-
this.familyInstance = this.options.family.create(this.stack);
|
|
1481
|
-
const rawComponents = [
|
|
1482
|
-
this.options.target,
|
|
1483
|
-
this.options.adapter,
|
|
1484
|
-
...this.options.extensionPacks ?? []
|
|
1485
|
-
];
|
|
1486
|
-
this.frameworkComponents = assertFrameworkComponentsCompatible(
|
|
1487
|
-
this.options.family.familyId,
|
|
1488
|
-
this.options.target.targetId,
|
|
1489
|
-
rawComponents
|
|
1490
|
-
);
|
|
1491
|
-
this.initialized = true;
|
|
1492
|
-
}
|
|
1493
|
-
async connect(connection) {
|
|
1494
|
-
this.init();
|
|
1495
|
-
if (this.driver) {
|
|
1496
|
-
throw new Error("Already connected. Call close() before reconnecting.");
|
|
1497
|
-
}
|
|
1498
|
-
const resolvedConnection = connection ?? this.defaultConnection;
|
|
1499
|
-
if (resolvedConnection === void 0) {
|
|
1500
|
-
throw new Error(
|
|
1501
|
-
"No connection provided. Pass a connection to connect() or provide a default connection when creating the client."
|
|
1502
|
-
);
|
|
1503
|
-
}
|
|
1504
|
-
if (!this.stack?.driver) {
|
|
1505
|
-
throw new Error(
|
|
1506
|
-
"Driver is not configured. Pass a driver descriptor when creating the control client to enable database operations."
|
|
1507
|
-
);
|
|
1508
|
-
}
|
|
1509
|
-
this.driver = await this.stack?.driver.create(resolvedConnection);
|
|
1510
|
-
}
|
|
1511
|
-
async close() {
|
|
1512
|
-
if (this.driver) {
|
|
1513
|
-
await this.driver.close();
|
|
1514
|
-
this.driver = null;
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
async ensureConnected() {
|
|
1518
|
-
this.init();
|
|
1519
|
-
if (!this.driver && this.defaultConnection !== void 0) {
|
|
1520
|
-
await this.connect(this.defaultConnection);
|
|
1521
|
-
}
|
|
1522
|
-
if (!this.driver || !this.familyInstance || !this.frameworkComponents) {
|
|
1523
|
-
throw new Error("Not connected. Call connect(connection) first.");
|
|
1524
|
-
}
|
|
1525
|
-
return {
|
|
1526
|
-
driver: this.driver,
|
|
1527
|
-
familyInstance: this.familyInstance,
|
|
1528
|
-
frameworkComponents: this.frameworkComponents
|
|
1529
|
-
};
|
|
1530
|
-
}
|
|
1531
|
-
async verify(options) {
|
|
1532
|
-
const { onProgress } = options;
|
|
1533
|
-
if (options.connection !== void 0) {
|
|
1534
|
-
onProgress?.({
|
|
1535
|
-
action: "verify",
|
|
1536
|
-
kind: "spanStart",
|
|
1537
|
-
spanId: "connect",
|
|
1538
|
-
label: "Connecting to database..."
|
|
1539
|
-
});
|
|
1540
|
-
try {
|
|
1541
|
-
await this.connect(options.connection);
|
|
1542
|
-
onProgress?.({
|
|
1543
|
-
action: "verify",
|
|
1544
|
-
kind: "spanEnd",
|
|
1545
|
-
spanId: "connect",
|
|
1546
|
-
outcome: "ok"
|
|
1547
|
-
});
|
|
1548
|
-
} catch (error) {
|
|
1549
|
-
onProgress?.({
|
|
1550
|
-
action: "verify",
|
|
1551
|
-
kind: "spanEnd",
|
|
1552
|
-
spanId: "connect",
|
|
1553
|
-
outcome: "error"
|
|
1554
|
-
});
|
|
1555
|
-
throw error;
|
|
1556
|
-
}
|
|
1557
|
-
}
|
|
1558
|
-
const { driver, familyInstance } = await this.ensureConnected();
|
|
1559
|
-
const contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
1560
|
-
onProgress?.({
|
|
1561
|
-
action: "verify",
|
|
1562
|
-
kind: "spanStart",
|
|
1563
|
-
spanId: "verify",
|
|
1564
|
-
label: "Verifying contract marker..."
|
|
1565
|
-
});
|
|
1566
|
-
try {
|
|
1567
|
-
const result = await familyInstance.verify({
|
|
1568
|
-
driver,
|
|
1569
|
-
contractIR,
|
|
1570
|
-
expectedTargetId: this.options.target.targetId,
|
|
1571
|
-
contractPath: ""
|
|
1572
|
-
});
|
|
1573
|
-
onProgress?.({
|
|
1574
|
-
action: "verify",
|
|
1575
|
-
kind: "spanEnd",
|
|
1576
|
-
spanId: "verify",
|
|
1577
|
-
outcome: result.ok ? "ok" : "error"
|
|
1578
|
-
});
|
|
1579
|
-
return result;
|
|
1580
|
-
} catch (error) {
|
|
1581
|
-
onProgress?.({
|
|
1582
|
-
action: "verify",
|
|
1583
|
-
kind: "spanEnd",
|
|
1584
|
-
spanId: "verify",
|
|
1585
|
-
outcome: "error"
|
|
1586
|
-
});
|
|
1587
|
-
throw error;
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
async schemaVerify(options) {
|
|
1591
|
-
const { onProgress } = options;
|
|
1592
|
-
if (options.connection !== void 0) {
|
|
1593
|
-
onProgress?.({
|
|
1594
|
-
action: "schemaVerify",
|
|
1595
|
-
kind: "spanStart",
|
|
1596
|
-
spanId: "connect",
|
|
1597
|
-
label: "Connecting to database..."
|
|
1598
|
-
});
|
|
1599
|
-
try {
|
|
1600
|
-
await this.connect(options.connection);
|
|
1601
|
-
onProgress?.({
|
|
1602
|
-
action: "schemaVerify",
|
|
1603
|
-
kind: "spanEnd",
|
|
1604
|
-
spanId: "connect",
|
|
1605
|
-
outcome: "ok"
|
|
1606
|
-
});
|
|
1607
|
-
} catch (error) {
|
|
1608
|
-
onProgress?.({
|
|
1609
|
-
action: "schemaVerify",
|
|
1610
|
-
kind: "spanEnd",
|
|
1611
|
-
spanId: "connect",
|
|
1612
|
-
outcome: "error"
|
|
1613
|
-
});
|
|
1614
|
-
throw error;
|
|
1615
|
-
}
|
|
1616
|
-
}
|
|
1617
|
-
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
1618
|
-
const contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
1619
|
-
onProgress?.({
|
|
1620
|
-
action: "schemaVerify",
|
|
1621
|
-
kind: "spanStart",
|
|
1622
|
-
spanId: "schemaVerify",
|
|
1623
|
-
label: "Verifying database schema..."
|
|
1624
|
-
});
|
|
1625
|
-
try {
|
|
1626
|
-
const result = await familyInstance.schemaVerify({
|
|
1627
|
-
driver,
|
|
1628
|
-
contractIR,
|
|
1629
|
-
strict: options.strict ?? false,
|
|
1630
|
-
contractPath: "",
|
|
1631
|
-
frameworkComponents
|
|
1632
|
-
});
|
|
1633
|
-
onProgress?.({
|
|
1634
|
-
action: "schemaVerify",
|
|
1635
|
-
kind: "spanEnd",
|
|
1636
|
-
spanId: "schemaVerify",
|
|
1637
|
-
outcome: result.ok ? "ok" : "error"
|
|
1638
|
-
});
|
|
1639
|
-
return result;
|
|
1640
|
-
} catch (error) {
|
|
1641
|
-
onProgress?.({
|
|
1642
|
-
action: "schemaVerify",
|
|
1643
|
-
kind: "spanEnd",
|
|
1644
|
-
spanId: "schemaVerify",
|
|
1645
|
-
outcome: "error"
|
|
1646
|
-
});
|
|
1647
|
-
throw error;
|
|
1648
|
-
}
|
|
1649
|
-
}
|
|
1650
|
-
async sign(options) {
|
|
1651
|
-
const { onProgress } = options;
|
|
1652
|
-
if (options.connection !== void 0) {
|
|
1653
|
-
onProgress?.({
|
|
1654
|
-
action: "sign",
|
|
1655
|
-
kind: "spanStart",
|
|
1656
|
-
spanId: "connect",
|
|
1657
|
-
label: "Connecting to database..."
|
|
1658
|
-
});
|
|
1659
|
-
try {
|
|
1660
|
-
await this.connect(options.connection);
|
|
1661
|
-
onProgress?.({
|
|
1662
|
-
action: "sign",
|
|
1663
|
-
kind: "spanEnd",
|
|
1664
|
-
spanId: "connect",
|
|
1665
|
-
outcome: "ok"
|
|
1666
|
-
});
|
|
1667
|
-
} catch (error) {
|
|
1668
|
-
onProgress?.({
|
|
1669
|
-
action: "sign",
|
|
1670
|
-
kind: "spanEnd",
|
|
1671
|
-
spanId: "connect",
|
|
1672
|
-
outcome: "error"
|
|
1673
|
-
});
|
|
1674
|
-
throw error;
|
|
1675
|
-
}
|
|
1676
|
-
}
|
|
1677
|
-
const { driver, familyInstance } = await this.ensureConnected();
|
|
1678
|
-
const contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
1679
|
-
onProgress?.({
|
|
1680
|
-
action: "sign",
|
|
1681
|
-
kind: "spanStart",
|
|
1682
|
-
spanId: "sign",
|
|
1683
|
-
label: "Signing database..."
|
|
1684
|
-
});
|
|
1685
|
-
try {
|
|
1686
|
-
const result = await familyInstance.sign({
|
|
1687
|
-
driver,
|
|
1688
|
-
contractIR,
|
|
1689
|
-
contractPath: options.contractPath ?? "",
|
|
1690
|
-
...options.configPath ? { configPath: options.configPath } : {}
|
|
1691
|
-
});
|
|
1692
|
-
onProgress?.({
|
|
1693
|
-
action: "sign",
|
|
1694
|
-
kind: "spanEnd",
|
|
1695
|
-
spanId: "sign",
|
|
1696
|
-
outcome: "ok"
|
|
1697
|
-
});
|
|
1698
|
-
return result;
|
|
1699
|
-
} catch (error) {
|
|
1700
|
-
onProgress?.({
|
|
1701
|
-
action: "sign",
|
|
1702
|
-
kind: "spanEnd",
|
|
1703
|
-
spanId: "sign",
|
|
1704
|
-
outcome: "error"
|
|
1705
|
-
});
|
|
1706
|
-
throw error;
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
1709
|
-
async dbInit(options) {
|
|
1710
|
-
const { onProgress } = options;
|
|
1711
|
-
if (options.connection !== void 0) {
|
|
1712
|
-
onProgress?.({
|
|
1713
|
-
action: "dbInit",
|
|
1714
|
-
kind: "spanStart",
|
|
1715
|
-
spanId: "connect",
|
|
1716
|
-
label: "Connecting to database..."
|
|
1717
|
-
});
|
|
1718
|
-
try {
|
|
1719
|
-
await this.connect(options.connection);
|
|
1720
|
-
onProgress?.({
|
|
1721
|
-
action: "dbInit",
|
|
1722
|
-
kind: "spanEnd",
|
|
1723
|
-
spanId: "connect",
|
|
1724
|
-
outcome: "ok"
|
|
1725
|
-
});
|
|
1726
|
-
} catch (error) {
|
|
1727
|
-
onProgress?.({
|
|
1728
|
-
action: "dbInit",
|
|
1729
|
-
kind: "spanEnd",
|
|
1730
|
-
spanId: "connect",
|
|
1731
|
-
outcome: "error"
|
|
1732
|
-
});
|
|
1733
|
-
throw error;
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
|
|
1737
|
-
if (!this.options.target.migrations) {
|
|
1738
|
-
throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
|
|
1739
|
-
}
|
|
1740
|
-
const contractIR = familyInstance.validateContractIR(options.contractIR);
|
|
1741
|
-
return executeDbInit({
|
|
1742
|
-
driver,
|
|
1743
|
-
familyInstance,
|
|
1744
|
-
contractIR,
|
|
1745
|
-
mode: options.mode,
|
|
1746
|
-
migrations: this.options.target.migrations,
|
|
1747
|
-
frameworkComponents,
|
|
1748
|
-
...onProgress ? { onProgress } : {}
|
|
1749
|
-
});
|
|
1750
|
-
}
|
|
1751
|
-
async introspect(options) {
|
|
1752
|
-
const onProgress = options?.onProgress;
|
|
1753
|
-
if (options?.connection !== void 0) {
|
|
1754
|
-
onProgress?.({
|
|
1755
|
-
action: "introspect",
|
|
1756
|
-
kind: "spanStart",
|
|
1757
|
-
spanId: "connect",
|
|
1758
|
-
label: "Connecting to database..."
|
|
1759
|
-
});
|
|
1760
|
-
try {
|
|
1761
|
-
await this.connect(options.connection);
|
|
1762
|
-
onProgress?.({
|
|
1763
|
-
action: "introspect",
|
|
1764
|
-
kind: "spanEnd",
|
|
1765
|
-
spanId: "connect",
|
|
1766
|
-
outcome: "ok"
|
|
1767
|
-
});
|
|
1768
|
-
} catch (error) {
|
|
1769
|
-
onProgress?.({
|
|
1770
|
-
action: "introspect",
|
|
1771
|
-
kind: "spanEnd",
|
|
1772
|
-
spanId: "connect",
|
|
1773
|
-
outcome: "error"
|
|
1774
|
-
});
|
|
1775
|
-
throw error;
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
const { driver, familyInstance } = await this.ensureConnected();
|
|
1779
|
-
const _schema = options?.schema;
|
|
1780
|
-
void _schema;
|
|
1781
|
-
onProgress?.({
|
|
1782
|
-
action: "introspect",
|
|
1783
|
-
kind: "spanStart",
|
|
1784
|
-
spanId: "introspect",
|
|
1785
|
-
label: "Introspecting database schema..."
|
|
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
|
|
1786
1684
|
});
|
|
1787
|
-
|
|
1788
|
-
const result = await familyInstance.introspect({ driver });
|
|
1789
|
-
onProgress?.({
|
|
1790
|
-
action: "introspect",
|
|
1791
|
-
kind: "spanEnd",
|
|
1792
|
-
spanId: "introspect",
|
|
1793
|
-
outcome: "ok"
|
|
1794
|
-
});
|
|
1795
|
-
return result;
|
|
1796
|
-
} catch (error) {
|
|
1797
|
-
onProgress?.({
|
|
1798
|
-
action: "introspect",
|
|
1799
|
-
kind: "spanEnd",
|
|
1800
|
-
spanId: "introspect",
|
|
1801
|
-
outcome: "error"
|
|
1802
|
-
});
|
|
1803
|
-
throw error;
|
|
1804
|
-
}
|
|
1685
|
+
lines.push(...treeLines);
|
|
1805
1686
|
}
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1687
|
+
if (topLevelCommands.length > 0 && globalOptions.length > 0) {
|
|
1688
|
+
lines.push(formatDimText("\u2502"));
|
|
1689
|
+
}
|
|
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}`);
|
|
1709
|
+
}
|
|
1810
1710
|
}
|
|
1811
|
-
return void 0;
|
|
1812
1711
|
}
|
|
1813
|
-
|
|
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 }));
|
|
1719
|
+
}
|
|
1720
|
+
lines.push(formatDimText("\u2514"));
|
|
1721
|
+
return `${lines.join("\n")}
|
|
1722
|
+
`;
|
|
1723
|
+
}
|
|
1814
1724
|
|
|
1815
1725
|
// src/utils/progress-adapter.ts
|
|
1816
|
-
import
|
|
1726
|
+
import ora from "ora";
|
|
1817
1727
|
function createProgressAdapter(options) {
|
|
1818
1728
|
const { flags } = options;
|
|
1819
1729
|
const shouldShowProgress = !flags.quiet && flags.json !== "object" && process.stdout.isTTY;
|
|
@@ -1828,7 +1738,7 @@ function createProgressAdapter(options) {
|
|
|
1828
1738
|
console.log(` \u2192 ${event.label}...`);
|
|
1829
1739
|
return;
|
|
1830
1740
|
}
|
|
1831
|
-
const spinner =
|
|
1741
|
+
const spinner = ora({
|
|
1832
1742
|
text: event.label,
|
|
1833
1743
|
color: flags.color !== false ? "cyan" : false
|
|
1834
1744
|
}).start();
|
|
@@ -1853,7 +1763,179 @@ function createProgressAdapter(options) {
|
|
|
1853
1763
|
};
|
|
1854
1764
|
}
|
|
1855
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));
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
});
|
|
1929
|
+
process.exit(exitCode);
|
|
1930
|
+
});
|
|
1931
|
+
return command;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1856
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";
|
|
1857
1939
|
function mapDbInitFailure(failure) {
|
|
1858
1940
|
if (failure.code === "PLANNING_FAILED") {
|
|
1859
1941
|
return errorMigrationPlanningFailed({ conflicts: failure.conflicts ?? [] });
|
|
@@ -1928,14 +2010,14 @@ async function executeDbInitCommand(options, flags, startTime) {
|
|
|
1928
2010
|
contractJsonContent = await readFile(contractPathAbsolute, "utf-8");
|
|
1929
2011
|
} catch (error) {
|
|
1930
2012
|
if (error instanceof Error && error.code === "ENOENT") {
|
|
1931
|
-
return
|
|
2013
|
+
return notOk4(
|
|
1932
2014
|
errorFileNotFound(contractPathAbsolute, {
|
|
1933
2015
|
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
1934
2016
|
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
|
|
1935
2017
|
})
|
|
1936
2018
|
);
|
|
1937
2019
|
}
|
|
1938
|
-
return
|
|
2020
|
+
return notOk4(
|
|
1939
2021
|
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
1940
2022
|
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
1941
2023
|
})
|
|
@@ -1945,7 +2027,7 @@ async function executeDbInitCommand(options, flags, startTime) {
|
|
|
1945
2027
|
try {
|
|
1946
2028
|
contractJson = JSON.parse(contractJsonContent);
|
|
1947
2029
|
} catch (error) {
|
|
1948
|
-
return
|
|
2030
|
+
return notOk4(
|
|
1949
2031
|
errorContractValidationFailed(
|
|
1950
2032
|
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
1951
2033
|
{ where: { path: contractPathAbsolute } }
|
|
@@ -1954,17 +2036,17 @@ async function executeDbInitCommand(options, flags, startTime) {
|
|
|
1954
2036
|
}
|
|
1955
2037
|
const dbConnection = options.db ?? config.db?.connection;
|
|
1956
2038
|
if (!dbConnection) {
|
|
1957
|
-
return
|
|
2039
|
+
return notOk4(
|
|
1958
2040
|
errorDatabaseConnectionRequired({
|
|
1959
2041
|
why: `Database connection is required for db init (set db.connection in ${configPath}, or pass --db <url>)`
|
|
1960
2042
|
})
|
|
1961
2043
|
);
|
|
1962
2044
|
}
|
|
1963
2045
|
if (!config.driver) {
|
|
1964
|
-
return
|
|
2046
|
+
return notOk4(errorDriverRequired({ why: "Config.driver is required for db init" }));
|
|
1965
2047
|
}
|
|
1966
2048
|
if (!config.target.migrations) {
|
|
1967
|
-
return
|
|
2049
|
+
return notOk4(
|
|
1968
2050
|
errorTargetMigrationNotSupported({
|
|
1969
2051
|
why: `Target "${config.target.id}" does not support migrations`
|
|
1970
2052
|
})
|
|
@@ -1986,7 +2068,7 @@ async function executeDbInitCommand(options, flags, startTime) {
|
|
|
1986
2068
|
onProgress
|
|
1987
2069
|
});
|
|
1988
2070
|
if (!result.ok) {
|
|
1989
|
-
return
|
|
2071
|
+
return notOk4(mapDbInitFailure(result.failure));
|
|
1990
2072
|
}
|
|
1991
2073
|
const profileHash = result.value.marker?.profileHash;
|
|
1992
2074
|
const dbInitResult = {
|
|
@@ -2019,12 +2101,12 @@ async function executeDbInitCommand(options, flags, startTime) {
|
|
|
2019
2101
|
summary: result.value.summary,
|
|
2020
2102
|
timings: { total: Date.now() - startTime }
|
|
2021
2103
|
};
|
|
2022
|
-
return
|
|
2104
|
+
return ok4(dbInitResult);
|
|
2023
2105
|
} catch (error) {
|
|
2024
2106
|
if (CliStructuredError.is(error)) {
|
|
2025
|
-
return
|
|
2107
|
+
return notOk4(error);
|
|
2026
2108
|
}
|
|
2027
|
-
return
|
|
2109
|
+
return notOk4(
|
|
2028
2110
|
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2029
2111
|
why: `Unexpected error during db init: ${error instanceof Error ? error.message : String(error)}`
|
|
2030
2112
|
})
|
|
@@ -2049,7 +2131,7 @@ function createDbInitCommand() {
|
|
|
2049
2131
|
const flags = parseGlobalFlags(options);
|
|
2050
2132
|
const startTime = Date.now();
|
|
2051
2133
|
if (flags.json === "ndjson") {
|
|
2052
|
-
const result2 =
|
|
2134
|
+
const result2 = notOk4(
|
|
2053
2135
|
errorJsonFormatNotSupported({
|
|
2054
2136
|
command: "db init",
|
|
2055
2137
|
format: "ndjson",
|
|
@@ -2077,7 +2159,7 @@ function createDbInitCommand() {
|
|
|
2077
2159
|
|
|
2078
2160
|
// src/commands/db-introspect.ts
|
|
2079
2161
|
import { relative as relative4, resolve as resolve4 } from "path";
|
|
2080
|
-
import { notOk as
|
|
2162
|
+
import { notOk as notOk5, ok as ok5 } from "@prisma-next/utils/result";
|
|
2081
2163
|
import { Command as Command3 } from "commander";
|
|
2082
2164
|
async function executeDbIntrospectCommand(options, flags, startTime) {
|
|
2083
2165
|
const config = await loadConfig(options.config);
|
|
@@ -2104,14 +2186,14 @@ async function executeDbIntrospectCommand(options, flags, startTime) {
|
|
|
2104
2186
|
}
|
|
2105
2187
|
const dbConnection = options.db ?? config.db?.connection;
|
|
2106
2188
|
if (!dbConnection) {
|
|
2107
|
-
return
|
|
2189
|
+
return notOk5(
|
|
2108
2190
|
errorDatabaseConnectionRequired({
|
|
2109
2191
|
why: `Database connection is required for db introspect (set db.connection in ${configPath}, or pass --db <url>)`
|
|
2110
2192
|
})
|
|
2111
2193
|
);
|
|
2112
2194
|
}
|
|
2113
2195
|
if (!config.driver) {
|
|
2114
|
-
return
|
|
2196
|
+
return notOk5(errorDriverRequired({ why: "Config.driver is required for db introspect" }));
|
|
2115
2197
|
}
|
|
2116
2198
|
const client = createControlClient({
|
|
2117
2199
|
family: config.family,
|
|
@@ -2148,12 +2230,12 @@ async function executeDbIntrospectCommand(options, flags, startTime) {
|
|
|
2148
2230
|
total: totalTime
|
|
2149
2231
|
}
|
|
2150
2232
|
};
|
|
2151
|
-
return
|
|
2233
|
+
return ok5({ introspectResult, schemaView });
|
|
2152
2234
|
} catch (error) {
|
|
2153
2235
|
if (error instanceof CliStructuredError) {
|
|
2154
|
-
return
|
|
2236
|
+
return notOk5(error);
|
|
2155
2237
|
}
|
|
2156
|
-
return
|
|
2238
|
+
return notOk5(
|
|
2157
2239
|
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2158
2240
|
why: `Unexpected error during db introspect: ${error instanceof Error ? error.message : String(error)}`
|
|
2159
2241
|
})
|
|
@@ -2178,7 +2260,7 @@ function createDbIntrospectCommand() {
|
|
|
2178
2260
|
const flags = parseGlobalFlags(options);
|
|
2179
2261
|
const startTime = Date.now();
|
|
2180
2262
|
if (flags.json === "ndjson") {
|
|
2181
|
-
const result2 =
|
|
2263
|
+
const result2 = notOk5(
|
|
2182
2264
|
errorJsonFormatNotSupported({
|
|
2183
2265
|
command: "db introspect",
|
|
2184
2266
|
format: "ndjson",
|
|
@@ -2208,7 +2290,7 @@ function createDbIntrospectCommand() {
|
|
|
2208
2290
|
// src/commands/db-schema-verify.ts
|
|
2209
2291
|
import { readFile as readFile2 } from "fs/promises";
|
|
2210
2292
|
import { relative as relative5, resolve as resolve5 } from "path";
|
|
2211
|
-
import { notOk as
|
|
2293
|
+
import { notOk as notOk6, ok as ok6 } from "@prisma-next/utils/result";
|
|
2212
2294
|
import { Command as Command4 } from "commander";
|
|
2213
2295
|
async function executeDbSchemaVerifyCommand(options, flags) {
|
|
2214
2296
|
const config = await loadConfig(options.config);
|
|
@@ -2237,14 +2319,14 @@ async function executeDbSchemaVerifyCommand(options, flags) {
|
|
|
2237
2319
|
contractJsonContent = await readFile2(contractPathAbsolute, "utf-8");
|
|
2238
2320
|
} catch (error) {
|
|
2239
2321
|
if (error instanceof Error && error.code === "ENOENT") {
|
|
2240
|
-
return
|
|
2322
|
+
return notOk6(
|
|
2241
2323
|
errorFileNotFound(contractPathAbsolute, {
|
|
2242
2324
|
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
2243
2325
|
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
|
|
2244
2326
|
})
|
|
2245
2327
|
);
|
|
2246
2328
|
}
|
|
2247
|
-
return
|
|
2329
|
+
return notOk6(
|
|
2248
2330
|
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2249
2331
|
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
2250
2332
|
})
|
|
@@ -2254,7 +2336,7 @@ async function executeDbSchemaVerifyCommand(options, flags) {
|
|
|
2254
2336
|
try {
|
|
2255
2337
|
contractJson = JSON.parse(contractJsonContent);
|
|
2256
2338
|
} catch (error) {
|
|
2257
|
-
return
|
|
2339
|
+
return notOk6(
|
|
2258
2340
|
errorContractValidationFailed(
|
|
2259
2341
|
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
2260
2342
|
{ where: { path: contractPathAbsolute } }
|
|
@@ -2263,14 +2345,14 @@ async function executeDbSchemaVerifyCommand(options, flags) {
|
|
|
2263
2345
|
}
|
|
2264
2346
|
const dbConnection = options.db ?? config.db?.connection;
|
|
2265
2347
|
if (!dbConnection) {
|
|
2266
|
-
return
|
|
2348
|
+
return notOk6(
|
|
2267
2349
|
errorDatabaseConnectionRequired({
|
|
2268
2350
|
why: `Database connection is required for db schema-verify (set db.connection in ${configPath}, or pass --db <url>)`
|
|
2269
2351
|
})
|
|
2270
2352
|
);
|
|
2271
2353
|
}
|
|
2272
2354
|
if (!config.driver) {
|
|
2273
|
-
return
|
|
2355
|
+
return notOk6(errorDriverRequired({ why: "Config.driver is required for db schema-verify" }));
|
|
2274
2356
|
}
|
|
2275
2357
|
const client = createControlClient({
|
|
2276
2358
|
family: config.family,
|
|
@@ -2290,12 +2372,12 @@ async function executeDbSchemaVerifyCommand(options, flags) {
|
|
|
2290
2372
|
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
2291
2373
|
console.log("");
|
|
2292
2374
|
}
|
|
2293
|
-
return
|
|
2375
|
+
return ok6(schemaVerifyResult);
|
|
2294
2376
|
} catch (error) {
|
|
2295
2377
|
if (error instanceof CliStructuredError) {
|
|
2296
|
-
return
|
|
2378
|
+
return notOk6(error);
|
|
2297
2379
|
}
|
|
2298
|
-
return
|
|
2380
|
+
return notOk6(
|
|
2299
2381
|
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2300
2382
|
why: `Unexpected error during db schema-verify: ${error instanceof Error ? error.message : String(error)}`
|
|
2301
2383
|
})
|
|
@@ -2319,7 +2401,7 @@ function createDbSchemaVerifyCommand() {
|
|
|
2319
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) => {
|
|
2320
2402
|
const flags = parseGlobalFlags(options);
|
|
2321
2403
|
if (flags.json === "ndjson") {
|
|
2322
|
-
const result2 =
|
|
2404
|
+
const result2 = notOk6(
|
|
2323
2405
|
errorJsonFormatNotSupported({
|
|
2324
2406
|
command: "db schema-verify",
|
|
2325
2407
|
format: "ndjson",
|
|
@@ -2352,7 +2434,7 @@ function createDbSchemaVerifyCommand() {
|
|
|
2352
2434
|
// src/commands/db-sign.ts
|
|
2353
2435
|
import { readFile as readFile3 } from "fs/promises";
|
|
2354
2436
|
import { relative as relative6, resolve as resolve6 } from "path";
|
|
2355
|
-
import { notOk as
|
|
2437
|
+
import { notOk as notOk7, ok as ok7 } from "@prisma-next/utils/result";
|
|
2356
2438
|
import { Command as Command5 } from "commander";
|
|
2357
2439
|
async function executeDbSignCommand(options, flags) {
|
|
2358
2440
|
const config = await loadConfig(options.config);
|
|
@@ -2381,14 +2463,14 @@ async function executeDbSignCommand(options, flags) {
|
|
|
2381
2463
|
contractJsonContent = await readFile3(contractPathAbsolute, "utf-8");
|
|
2382
2464
|
} catch (error) {
|
|
2383
2465
|
if (error instanceof Error && error.code === "ENOENT") {
|
|
2384
|
-
return
|
|
2466
|
+
return notOk7(
|
|
2385
2467
|
errorFileNotFound(contractPathAbsolute, {
|
|
2386
2468
|
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
2387
2469
|
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
|
|
2388
2470
|
})
|
|
2389
2471
|
);
|
|
2390
2472
|
}
|
|
2391
|
-
return
|
|
2473
|
+
return notOk7(
|
|
2392
2474
|
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2393
2475
|
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
2394
2476
|
})
|
|
@@ -2398,7 +2480,7 @@ async function executeDbSignCommand(options, flags) {
|
|
|
2398
2480
|
try {
|
|
2399
2481
|
contractJson = JSON.parse(contractJsonContent);
|
|
2400
2482
|
} catch (error) {
|
|
2401
|
-
return
|
|
2483
|
+
return notOk7(
|
|
2402
2484
|
errorContractValidationFailed(
|
|
2403
2485
|
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
2404
2486
|
{ where: { path: contractPathAbsolute } }
|
|
@@ -2407,14 +2489,14 @@ async function executeDbSignCommand(options, flags) {
|
|
|
2407
2489
|
}
|
|
2408
2490
|
const dbConnection = options.db ?? config.db?.connection;
|
|
2409
2491
|
if (!dbConnection) {
|
|
2410
|
-
return
|
|
2492
|
+
return notOk7(
|
|
2411
2493
|
errorDatabaseConnectionRequired({
|
|
2412
2494
|
why: `Database connection is required for db sign (set db.connection in ${configPath}, or pass --db <url>)`
|
|
2413
2495
|
})
|
|
2414
2496
|
);
|
|
2415
2497
|
}
|
|
2416
2498
|
if (!config.driver) {
|
|
2417
|
-
return
|
|
2499
|
+
return notOk7(errorDriverRequired({ why: "Config.driver is required for db sign" }));
|
|
2418
2500
|
}
|
|
2419
2501
|
const client = createControlClient({
|
|
2420
2502
|
family: config.family,
|
|
@@ -2435,7 +2517,7 @@ async function executeDbSignCommand(options, flags) {
|
|
|
2435
2517
|
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
2436
2518
|
console.log("");
|
|
2437
2519
|
}
|
|
2438
|
-
return
|
|
2520
|
+
return notOk7(schemaVerifyResult);
|
|
2439
2521
|
}
|
|
2440
2522
|
const signResult = await client.sign({
|
|
2441
2523
|
contractIR: contractJson,
|
|
@@ -2446,12 +2528,12 @@ async function executeDbSignCommand(options, flags) {
|
|
|
2446
2528
|
if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
|
|
2447
2529
|
console.log("");
|
|
2448
2530
|
}
|
|
2449
|
-
return
|
|
2531
|
+
return ok7(signResult);
|
|
2450
2532
|
} catch (error) {
|
|
2451
2533
|
if (error instanceof CliStructuredError) {
|
|
2452
|
-
return
|
|
2534
|
+
return notOk7(error);
|
|
2453
2535
|
}
|
|
2454
|
-
return
|
|
2536
|
+
return notOk7(
|
|
2455
2537
|
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2456
2538
|
why: `Unexpected error during db sign: ${error instanceof Error ? error.message : String(error)}`
|
|
2457
2539
|
})
|
|
@@ -2475,7 +2557,7 @@ function createDbSignCommand() {
|
|
|
2475
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) => {
|
|
2476
2558
|
const flags = parseGlobalFlags(options);
|
|
2477
2559
|
if (flags.json === "ndjson") {
|
|
2478
|
-
const result2 =
|
|
2560
|
+
const result2 = notOk7(
|
|
2479
2561
|
errorJsonFormatNotSupported({
|
|
2480
2562
|
command: "db sign",
|
|
2481
2563
|
format: "ndjson",
|
|
@@ -2518,7 +2600,7 @@ function createDbSignCommand() {
|
|
|
2518
2600
|
// src/commands/db-verify.ts
|
|
2519
2601
|
import { readFile as readFile4 } from "fs/promises";
|
|
2520
2602
|
import { relative as relative7, resolve as resolve7 } from "path";
|
|
2521
|
-
import { notOk as
|
|
2603
|
+
import { notOk as notOk8, ok as ok8 } from "@prisma-next/utils/result";
|
|
2522
2604
|
import { Command as Command6 } from "commander";
|
|
2523
2605
|
function mapVerifyFailure(verifyResult) {
|
|
2524
2606
|
if (!verifyResult.ok && verifyResult.code) {
|
|
@@ -2567,14 +2649,14 @@ async function executeDbVerifyCommand(options, flags) {
|
|
|
2567
2649
|
contractJsonContent = await readFile4(contractPathAbsolute, "utf-8");
|
|
2568
2650
|
} catch (error) {
|
|
2569
2651
|
if (error instanceof Error && error.code === "ENOENT") {
|
|
2570
|
-
return
|
|
2652
|
+
return notOk8(
|
|
2571
2653
|
errorFileNotFound(contractPathAbsolute, {
|
|
2572
2654
|
why: `Contract file not found at ${contractPathAbsolute}`,
|
|
2573
2655
|
fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
|
|
2574
2656
|
})
|
|
2575
2657
|
);
|
|
2576
2658
|
}
|
|
2577
|
-
return
|
|
2659
|
+
return notOk8(
|
|
2578
2660
|
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2579
2661
|
why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
|
|
2580
2662
|
})
|
|
@@ -2584,7 +2666,7 @@ async function executeDbVerifyCommand(options, flags) {
|
|
|
2584
2666
|
try {
|
|
2585
2667
|
contractJson = JSON.parse(contractJsonContent);
|
|
2586
2668
|
} catch (error) {
|
|
2587
|
-
return
|
|
2669
|
+
return notOk8(
|
|
2588
2670
|
errorContractValidationFailed(
|
|
2589
2671
|
`Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
|
|
2590
2672
|
{ where: { path: contractPathAbsolute } }
|
|
@@ -2593,14 +2675,14 @@ async function executeDbVerifyCommand(options, flags) {
|
|
|
2593
2675
|
}
|
|
2594
2676
|
const dbConnection = options.db ?? config.db?.connection;
|
|
2595
2677
|
if (!dbConnection) {
|
|
2596
|
-
return
|
|
2678
|
+
return notOk8(
|
|
2597
2679
|
errorDatabaseConnectionRequired({
|
|
2598
2680
|
why: `Database connection is required for db verify (set db.connection in ${configPath}, or pass --db <url>)`
|
|
2599
2681
|
})
|
|
2600
2682
|
);
|
|
2601
2683
|
}
|
|
2602
2684
|
if (!config.driver) {
|
|
2603
|
-
return
|
|
2685
|
+
return notOk8(errorDriverRequired({ why: "Config.driver is required for db verify" }));
|
|
2604
2686
|
}
|
|
2605
2687
|
const client = createControlClient({
|
|
2606
2688
|
family: config.family,
|
|
@@ -2620,14 +2702,14 @@ async function executeDbVerifyCommand(options, flags) {
|
|
|
2620
2702
|
console.log("");
|
|
2621
2703
|
}
|
|
2622
2704
|
if (!verifyResult.ok) {
|
|
2623
|
-
return
|
|
2705
|
+
return notOk8(mapVerifyFailure(verifyResult));
|
|
2624
2706
|
}
|
|
2625
|
-
return
|
|
2707
|
+
return ok8(verifyResult);
|
|
2626
2708
|
} catch (error) {
|
|
2627
2709
|
if (error instanceof CliStructuredError) {
|
|
2628
|
-
return
|
|
2710
|
+
return notOk8(error);
|
|
2629
2711
|
}
|
|
2630
|
-
return
|
|
2712
|
+
return notOk8(
|
|
2631
2713
|
errorUnexpected2(error instanceof Error ? error.message : String(error), {
|
|
2632
2714
|
why: `Unexpected error during db verify: ${error instanceof Error ? error.message : String(error)}`
|
|
2633
2715
|
})
|
|
@@ -2651,7 +2733,7 @@ function createDbVerifyCommand() {
|
|
|
2651
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) => {
|
|
2652
2734
|
const flags = parseGlobalFlags(options);
|
|
2653
2735
|
if (flags.json === "ndjson") {
|
|
2654
|
-
const result2 =
|
|
2736
|
+
const result2 = notOk8(
|
|
2655
2737
|
errorJsonFormatNotSupported({
|
|
2656
2738
|
command: "db verify",
|
|
2657
2739
|
format: "ndjson",
|