@prisma-next/cli 0.3.0-dev.3 → 0.3.0-dev.30

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