@prisma-next/cli 0.3.0-dev.17 → 0.3.0-dev.19

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