@prisma-next/cli 0.3.0-dev.9 → 0.3.0-pr.100.2

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 (66) 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.js +1502 -942
  9. package/dist/cli.js.map +1 -1
  10. package/dist/commands/contract-emit.d.ts.map +1 -1
  11. package/dist/commands/contract-emit.js +3 -2
  12. package/dist/commands/db-init.d.ts.map +1 -1
  13. package/dist/commands/db-init.js +205 -285
  14. package/dist/commands/db-init.js.map +1 -1
  15. package/dist/commands/db-introspect.d.ts.map +1 -1
  16. package/dist/commands/db-introspect.js +108 -139
  17. package/dist/commands/db-introspect.js.map +1 -1
  18. package/dist/commands/db-schema-verify.d.ts.map +1 -1
  19. package/dist/commands/db-schema-verify.js +120 -109
  20. package/dist/commands/db-schema-verify.js.map +1 -1
  21. package/dist/commands/db-sign.d.ts.map +1 -1
  22. package/dist/commands/db-sign.js +152 -152
  23. package/dist/commands/db-sign.js.map +1 -1
  24. package/dist/commands/db-verify.d.ts.map +1 -1
  25. package/dist/commands/db-verify.js +142 -118
  26. package/dist/commands/db-verify.js.map +1 -1
  27. package/dist/control-api/client.d.ts +13 -0
  28. package/dist/control-api/client.d.ts.map +1 -0
  29. package/dist/control-api/operations/db-init.d.ts +29 -0
  30. package/dist/control-api/operations/db-init.d.ts.map +1 -0
  31. package/dist/control-api/types.d.ts +387 -0
  32. package/dist/control-api/types.d.ts.map +1 -0
  33. package/dist/exports/control-api.d.ts +13 -0
  34. package/dist/exports/control-api.d.ts.map +1 -0
  35. package/dist/exports/control-api.js +7 -0
  36. package/dist/exports/control-api.js.map +1 -0
  37. package/dist/exports/index.js +3 -2
  38. package/dist/exports/index.js.map +1 -1
  39. package/dist/utils/cli-errors.d.ts +1 -1
  40. package/dist/utils/cli-errors.d.ts.map +1 -1
  41. package/dist/utils/progress-adapter.d.ts +26 -0
  42. package/dist/utils/progress-adapter.d.ts.map +1 -0
  43. package/package.json +20 -16
  44. package/src/commands/contract-emit.ts +179 -102
  45. package/src/commands/db-init.ts +263 -355
  46. package/src/commands/db-introspect.ts +151 -180
  47. package/src/commands/db-schema-verify.ts +151 -145
  48. package/src/commands/db-sign.ts +202 -196
  49. package/src/commands/db-verify.ts +180 -151
  50. package/src/control-api/client.ts +589 -0
  51. package/src/control-api/operations/db-init.ts +281 -0
  52. package/src/control-api/types.ts +461 -0
  53. package/src/exports/control-api.ts +46 -0
  54. package/src/utils/cli-errors.ts +1 -1
  55. package/src/utils/progress-adapter.ts +86 -0
  56. package/dist/chunk-BZMBKEEQ.js.map +0 -1
  57. package/dist/chunk-CVNWLFXO.js +0 -91
  58. package/dist/chunk-CVNWLFXO.js.map +0 -1
  59. package/dist/chunk-QUPBU4KV.js +0 -131
  60. package/dist/chunk-QUPBU4KV.js.map +0 -1
  61. package/dist/utils/action.d.ts +0 -16
  62. package/dist/utils/action.d.ts.map +0 -1
  63. package/dist/utils/spinner.d.ts +0 -29
  64. package/dist/utils/spinner.d.ts.map +0 -1
  65. package/src/utils/action.ts +0 -43
  66. package/src/utils/spinner.ts +0 -67
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import { Command as Command7 } from "commander";
7
7
  import { mkdirSync, writeFileSync } from "fs";
8
8
  import { dirname as dirname2, relative as relative2, resolve as resolve2 } from "path";
9
9
  import { errorContractConfigMissing as errorContractConfigMissing2 } from "@prisma-next/core-control-plane/errors";
10
- import { createControlPlaneStack } from "@prisma-next/core-control-plane/types";
10
+ import { notOk as notOk3, ok as ok3 } from "@prisma-next/utils/result";
11
11
  import { Command } from "commander";
12
12
 
13
13
  // src/config-loader.ts
@@ -53,8 +53,14 @@ async function loadConfig(configPath) {
53
53
  }
54
54
  }
55
55
 
56
- // src/utils/action.ts
57
- import { notOk, ok } from "@prisma-next/utils/result";
56
+ // src/control-api/client.ts
57
+ import { createControlPlaneStack } from "@prisma-next/core-control-plane/stack";
58
+ import { notOk as notOk2, ok as ok2 } from "@prisma-next/utils/result";
59
+
60
+ // src/utils/framework-components.ts
61
+ import {
62
+ checkContractComponentRequirements
63
+ } from "@prisma-next/contract/framework-components";
58
64
 
59
65
  // src/utils/cli-errors.ts
60
66
  import {
@@ -64,7 +70,7 @@ import {
64
70
  errorContractConfigMissing,
65
71
  errorContractMissingExtensionPacks,
66
72
  errorContractValidationFailed,
67
- errorDatabaseUrlRequired,
73
+ errorDatabaseConnectionRequired,
68
74
  errorDriverRequired,
69
75
  errorFamilyReadMarkerSqlRequired,
70
76
  errorFileNotFound,
@@ -79,18 +85,693 @@ import {
79
85
  errorUnexpected as errorUnexpected2
80
86
  } from "@prisma-next/core-control-plane/errors";
81
87
 
82
- // src/utils/action.ts
83
- async function performAction(fn) {
84
- try {
85
- const value = await fn();
86
- return ok(value);
87
- } catch (error) {
88
- if (error instanceof CliStructuredError) {
89
- return notOk(error);
88
+ // src/utils/framework-components.ts
89
+ function assertFrameworkComponentsCompatible(expectedFamilyId, expectedTargetId, frameworkComponents) {
90
+ for (let i = 0; i < frameworkComponents.length; i++) {
91
+ const component = frameworkComponents[i];
92
+ if (typeof component !== "object" || component === null) {
93
+ throw errorConfigValidation("frameworkComponents[]", {
94
+ why: `Framework component at index ${i} must be an object`
95
+ });
96
+ }
97
+ const record = component;
98
+ if (!Object.hasOwn(record, "kind")) {
99
+ throw errorConfigValidation("frameworkComponents[].kind", {
100
+ why: `Framework component at index ${i} must have 'kind' property`
101
+ });
102
+ }
103
+ const kind = record["kind"];
104
+ if (kind !== "target" && kind !== "adapter" && kind !== "extension" && kind !== "driver") {
105
+ throw errorConfigValidation("frameworkComponents[].kind", {
106
+ why: `Framework component at index ${i} has invalid kind '${String(kind)}' (must be 'target', 'adapter', 'extension', or 'driver')`
107
+ });
108
+ }
109
+ if (!Object.hasOwn(record, "familyId")) {
110
+ throw errorConfigValidation("frameworkComponents[].familyId", {
111
+ why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'familyId' property`
112
+ });
113
+ }
114
+ const familyId = record["familyId"];
115
+ if (familyId !== expectedFamilyId) {
116
+ throw errorConfigValidation("frameworkComponents[].familyId", {
117
+ why: `Framework component at index ${i} (kind: ${String(kind)}) has familyId '${String(familyId)}' but expected '${expectedFamilyId}'`
118
+ });
119
+ }
120
+ if (!Object.hasOwn(record, "targetId")) {
121
+ throw errorConfigValidation("frameworkComponents[].targetId", {
122
+ why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'targetId' property`
123
+ });
124
+ }
125
+ const targetId = record["targetId"];
126
+ if (targetId !== expectedTargetId) {
127
+ throw errorConfigValidation("frameworkComponents[].targetId", {
128
+ why: `Framework component at index ${i} (kind: ${String(kind)}) has targetId '${String(targetId)}' but expected '${expectedTargetId}'`
129
+ });
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
+ });
90
282
  }
91
- 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
+ });
92
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);
93
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
+ };
94
775
 
95
776
  // src/utils/command-helpers.ts
96
777
  function setCommandDescriptions(command, shortDescription, longDescription) {
@@ -1041,6 +1722,47 @@ function formatRootHelp(options) {
1041
1722
  `;
1042
1723
  }
1043
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
+
1044
1766
  // src/utils/result-handler.ts
1045
1767
  function handleResult(result, flags, onSuccess) {
1046
1768
  if (result.ok) {
@@ -1059,31 +1781,122 @@ function handleResult(result, flags, onSuccess) {
1059
1781
  return exitCode;
1060
1782
  }
1061
1783
 
1062
- // src/utils/spinner.ts
1063
- import ora from "ora";
1064
- async function withSpinner(operation, options) {
1065
- const { message, flags } = options;
1066
- const shouldShowSpinner = !flags.quiet && flags.json !== "object" && process.stdout.isTTY;
1067
- if (!shouldShowSpinner) {
1068
- return operation();
1069
- }
1070
- const startTime = Date.now();
1071
- const spinner = ora({
1072
- text: message,
1073
- color: flags.color !== false ? "cyan" : false
1074
- }).start();
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 });
1075
1856
  try {
1076
- const result = await operation();
1077
- const elapsed = Date.now() - startTime;
1078
- spinner.succeed(`${message} (${elapsed}ms)`);
1079
- 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);
1080
1887
  } catch (error) {
1081
- spinner.fail(`${message} failed: ${error instanceof Error ? error.message : String(error)}`);
1082
- 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();
1083
1898
  }
1084
1899
  }
1085
-
1086
- // src/commands/contract-emit.ts
1087
1900
  function createContractEmitCommand() {
1088
1901
  const command = new Command("emit");
1089
1902
  setCommandDescriptions(
@@ -1098,79 +1911,8 @@ function createContractEmitCommand() {
1098
1911
  }
1099
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) => {
1100
1913
  const flags = parseGlobalFlags(options);
1101
- const result = await performAction(async () => {
1102
- const config = await loadConfig(options.config);
1103
- if (!config.contract) {
1104
- throw errorContractConfigMissing2({
1105
- why: "Config.contract is required for emit. Define it in your config: contract: { source: ..., output: ..., types: ... }"
1106
- });
1107
- }
1108
- const contractConfig = config.contract;
1109
- if (!contractConfig.output || !contractConfig.types) {
1110
- throw errorContractConfigMissing2({
1111
- why: "Contract config must have output and types paths. This should not happen if defineConfig() was used."
1112
- });
1113
- }
1114
- const outputJsonPath = resolve2(contractConfig.output);
1115
- const outputDtsPath = resolve2(contractConfig.types);
1116
- if (flags.json !== "object" && !flags.quiet) {
1117
- const configPath = options.config ? relative2(process.cwd(), resolve2(options.config)) : "prisma-next.config.ts";
1118
- const contractPath = relative2(process.cwd(), outputJsonPath);
1119
- const typesPath = relative2(process.cwd(), outputDtsPath);
1120
- const header = formatStyledHeader({
1121
- command: "contract emit",
1122
- description: "Write your contract to JSON and sign it",
1123
- url: "https://pris.ly/contract-emit",
1124
- details: [
1125
- { label: "config", value: configPath },
1126
- { label: "contract", value: contractPath },
1127
- { label: "types", value: typesPath }
1128
- ],
1129
- flags
1130
- });
1131
- console.log(header);
1132
- }
1133
- const stack = createControlPlaneStack({
1134
- target: config.target,
1135
- adapter: config.adapter,
1136
- driver: config.driver,
1137
- extensionPacks: config.extensionPacks
1138
- });
1139
- const familyInstance = config.family.create(stack);
1140
- let contractRaw;
1141
- if (typeof contractConfig.source === "function") {
1142
- contractRaw = await contractConfig.source();
1143
- } else {
1144
- contractRaw = contractConfig.source;
1145
- }
1146
- const emitResult = await withSpinner(
1147
- () => familyInstance.emitContract({ contractIR: contractRaw }),
1148
- {
1149
- message: "Emitting contract...",
1150
- flags
1151
- }
1152
- );
1153
- mkdirSync(dirname2(outputJsonPath), { recursive: true });
1154
- mkdirSync(dirname2(outputDtsPath), { recursive: true });
1155
- writeFileSync(outputJsonPath, emitResult.contractJson, "utf-8");
1156
- writeFileSync(outputDtsPath, emitResult.contractDts, "utf-8");
1157
- if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
1158
- console.log("");
1159
- }
1160
- return {
1161
- coreHash: emitResult.coreHash,
1162
- profileHash: emitResult.profileHash,
1163
- outDir: dirname2(outputJsonPath),
1164
- files: {
1165
- json: outputJsonPath,
1166
- dts: outputDtsPath
1167
- },
1168
- timings: {
1169
- total: 0
1170
- // Timing is handled by emitContract internally if needed
1171
- }
1172
- };
1173
- });
1914
+ const startTime = Date.now();
1915
+ const result = await executeContractEmitCommand(options, flags, startTime);
1174
1916
  const exitCode = handleResult(result, flags, (emitResult) => {
1175
1917
  if (flags.json === "object") {
1176
1918
  console.log(formatEmitJson(emitResult));
@@ -1192,92 +1934,187 @@ function createContractEmitCommand() {
1192
1934
  // src/commands/db-init.ts
1193
1935
  import { readFile } from "fs/promises";
1194
1936
  import { relative as relative3, resolve as resolve3 } from "path";
1195
- import { createControlPlaneStack as createControlPlaneStack2 } from "@prisma-next/core-control-plane/types";
1196
- import { redactDatabaseUrl } from "@prisma-next/utils/redact-db-url";
1937
+ import { notOk as notOk4, ok as ok4 } from "@prisma-next/utils/result";
1197
1938
  import { Command as Command2 } from "commander";
1198
-
1199
- // src/utils/framework-components.ts
1200
- import {
1201
- checkContractComponentRequirements
1202
- } from "@prisma-next/contract/framework-components";
1203
- function assertFrameworkComponentsCompatible(expectedFamilyId, expectedTargetId, frameworkComponents) {
1204
- for (let i = 0; i < frameworkComponents.length; i++) {
1205
- const component = frameworkComponents[i];
1206
- if (typeof component !== "object" || component === null) {
1207
- throw errorConfigValidation("frameworkComponents[]", {
1208
- why: `Framework component at index ${i} must be an object`
1209
- });
1210
- }
1211
- const record = component;
1212
- if (!Object.hasOwn(record, "kind")) {
1213
- throw errorConfigValidation("frameworkComponents[].kind", {
1214
- why: `Framework component at index ${i} must have 'kind' property`
1215
- });
1216
- }
1217
- const kind = record["kind"];
1218
- if (kind !== "target" && kind !== "adapter" && kind !== "extension" && kind !== "driver") {
1219
- throw errorConfigValidation("frameworkComponents[].kind", {
1220
- why: `Framework component at index ${i} has invalid kind '${String(kind)}' (must be 'target', 'adapter', 'extension', or 'driver')`
1221
- });
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
+ );
1222
1949
  }
1223
- if (!Object.hasOwn(record, "familyId")) {
1224
- throw errorConfigValidation("frameworkComponents[].familyId", {
1225
- why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'familyId' property`
1226
- });
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
+ );
1227
1954
  }
1228
- const familyId = record["familyId"];
1229
- if (familyId !== expectedFamilyId) {
1230
- throw errorConfigValidation("frameworkComponents[].familyId", {
1231
- why: `Framework component at index ${i} (kind: ${String(kind)}) has familyId '${String(familyId)}' but expected '${expectedFamilyId}'`
1232
- });
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 });
1233
1995
  }
1234
- if (!Object.hasOwn(record, "targetId")) {
1235
- throw errorConfigValidation("frameworkComponents[].targetId", {
1236
- why: `Framework component at index ${i} (kind: ${String(kind)}) must have 'targetId' property`
1237
- });
1996
+ if (options.plan) {
1997
+ details.push({ label: "mode", value: "plan (dry run)" });
1238
1998
  }
1239
- const targetId = record["targetId"];
1240
- if (targetId !== expectedTargetId) {
1241
- throw errorConfigValidation("frameworkComponents[].targetId", {
1242
- why: `Framework component at index ${i} (kind: ${String(kind)}) has targetId '${String(targetId)}' but expected '${expectedTargetId}'`
1243
- });
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
+ );
1244
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
+ );
1245
2025
  }
1246
- return frameworkComponents;
1247
- }
1248
- function assertContractRequirementsSatisfied({
1249
- contract,
1250
- stack
1251
- }) {
1252
- const providedComponentIds = /* @__PURE__ */ new Set([stack.target.id, stack.adapter.id]);
1253
- for (const extension of stack.extensionPacks) {
1254
- providedComponentIds.add(extension.id);
1255
- }
1256
- const result = checkContractComponentRequirements({
1257
- contract,
1258
- expectedTargetFamily: stack.target.familyId,
1259
- expectedTargetId: stack.target.targetId,
1260
- providedComponentIds
1261
- });
1262
- if (result.familyMismatch) {
1263
- throw errorConfigValidation("contract.targetFamily", {
1264
- why: `Contract was emitted for family '${result.familyMismatch.actual}' but CLI config is wired to '${result.familyMismatch.expected}'.`
1265
- });
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
+ );
1266
2036
  }
1267
- if (result.targetMismatch) {
1268
- throw errorConfigValidation("contract.target", {
1269
- why: `Contract target '${result.targetMismatch.actual}' does not match CLI target '${result.targetMismatch.expected}'.`
1270
- });
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
+ );
2044
+ }
2045
+ if (!config.driver) {
2046
+ return notOk4(errorDriverRequired({ why: "Config.driver is required for db init" }));
1271
2047
  }
1272
- if (result.missingExtensionPackIds.length > 0) {
1273
- throw errorContractMissingExtensionPacks({
1274
- missingExtensionPacks: result.missingExtensionPackIds,
1275
- providedComponentIds: [...providedComponentIds]
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
1276
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();
1277
2116
  }
1278
2117
  }
1279
-
1280
- // src/commands/db-init.ts
1281
2118
  function createDbInitCommand() {
1282
2119
  const command = new Command2("init");
1283
2120
  setCommandDescriptions(
@@ -1293,275 +2130,18 @@ function createDbInitCommand() {
1293
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) => {
1294
2131
  const flags = parseGlobalFlags(options);
1295
2132
  const startTime = Date.now();
1296
- const result = await performAction(async () => {
1297
- if (flags.json === "ndjson") {
1298
- throw errorJsonFormatNotSupported({
2133
+ if (flags.json === "ndjson") {
2134
+ const result2 = notOk4(
2135
+ errorJsonFormatNotSupported({
1299
2136
  command: "db init",
1300
2137
  format: "ndjson",
1301
2138
  supportedFormats: ["object"]
1302
- });
1303
- }
1304
- const config = await loadConfig(options.config);
1305
- const configPath = options.config ? relative3(process.cwd(), resolve3(options.config)) : "prisma-next.config.ts";
1306
- const contractPathAbsolute = config.contract?.output ? resolve3(config.contract.output) : resolve3("src/prisma/contract.json");
1307
- const contractPath = relative3(process.cwd(), contractPathAbsolute);
1308
- if (flags.json !== "object" && !flags.quiet) {
1309
- const details = [
1310
- { label: "config", value: configPath },
1311
- { label: "contract", value: contractPath }
1312
- ];
1313
- if (options.db) {
1314
- details.push({ label: "database", value: options.db });
1315
- }
1316
- if (options.plan) {
1317
- details.push({ label: "mode", value: "plan (dry run)" });
1318
- }
1319
- const header = formatStyledHeader({
1320
- command: "db init",
1321
- description: "Bootstrap a database to match the current contract",
1322
- url: "https://pris.ly/db-init",
1323
- details,
1324
- flags
1325
- });
1326
- console.log(header);
1327
- }
1328
- let contractJsonContent;
1329
- try {
1330
- contractJsonContent = await readFile(contractPathAbsolute, "utf-8");
1331
- } catch (error) {
1332
- if (error instanceof Error && error.code === "ENOENT") {
1333
- throw errorFileNotFound(contractPathAbsolute, {
1334
- why: `Contract file not found at ${contractPathAbsolute}`,
1335
- fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
1336
- });
1337
- }
1338
- throw errorUnexpected2(error instanceof Error ? error.message : String(error), {
1339
- why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
1340
- });
1341
- }
1342
- let contractJson;
1343
- try {
1344
- contractJson = JSON.parse(contractJsonContent);
1345
- } catch (error) {
1346
- throw errorContractValidationFailed(
1347
- `Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
1348
- { where: { path: contractPathAbsolute } }
1349
- );
1350
- }
1351
- const dbUrl = options.db ?? config.db?.url;
1352
- if (!dbUrl) {
1353
- throw errorDatabaseUrlRequired({
1354
- why: `Database URL is required for db init (set db.url in ${configPath}, or pass --db <url>)`
1355
- });
1356
- }
1357
- if (!config.driver) {
1358
- throw errorDriverRequired({ why: "Config.driver is required for db init" });
1359
- }
1360
- const driverDescriptor = config.driver;
1361
- if (!config.target.migrations) {
1362
- throw errorTargetMigrationNotSupported({
1363
- why: `Target "${config.target.id}" does not support migrations`
1364
- });
1365
- }
1366
- const migrations = config.target.migrations;
1367
- let driver;
1368
- try {
1369
- driver = await withSpinner(() => driverDescriptor.create(dbUrl), {
1370
- message: "Connecting to database...",
1371
- flags
1372
- });
1373
- } catch (error) {
1374
- const message = error instanceof Error ? error.message : String(error);
1375
- const code = error.code;
1376
- const redacted = redactDatabaseUrl(dbUrl);
1377
- throw errorRuntime("Database connection failed", {
1378
- why: message,
1379
- fix: "Verify the database URL, ensure the database is reachable, and confirm credentials/permissions",
1380
- meta: {
1381
- ...typeof code !== "undefined" ? { code } : {},
1382
- ...redacted
1383
- }
1384
- });
1385
- }
1386
- try {
1387
- const stack = createControlPlaneStack2({
1388
- target: config.target,
1389
- adapter: config.adapter,
1390
- driver: driverDescriptor,
1391
- extensionPacks: config.extensionPacks
1392
- });
1393
- const familyInstance = config.family.create(stack);
1394
- const rawComponents = [config.target, config.adapter, ...config.extensionPacks ?? []];
1395
- const frameworkComponents = assertFrameworkComponentsCompatible(
1396
- config.family.familyId,
1397
- config.target.targetId,
1398
- rawComponents
1399
- );
1400
- const contractIR = familyInstance.validateContractIR(contractJson);
1401
- assertContractRequirementsSatisfied({ contract: contractIR, stack });
1402
- const planner = migrations.createPlanner(familyInstance);
1403
- const runner = migrations.createRunner(familyInstance);
1404
- const schemaIR = await withSpinner(() => familyInstance.introspect({ driver }), {
1405
- message: "Introspecting database schema...",
1406
- flags
1407
- });
1408
- const policy = { allowedOperationClasses: ["additive"] };
1409
- const plannerResult = await withSpinner(
1410
- async () => planner.plan({
1411
- contract: contractIR,
1412
- schema: schemaIR,
1413
- policy,
1414
- frameworkComponents
1415
- }),
1416
- {
1417
- message: "Planning migration...",
1418
- flags
1419
- }
1420
- );
1421
- if (plannerResult.kind === "failure") {
1422
- throw errorMigrationPlanningFailed({ conflicts: plannerResult.conflicts });
1423
- }
1424
- const migrationPlan = plannerResult.plan;
1425
- const existingMarker = await familyInstance.readMarker({ driver });
1426
- if (existingMarker) {
1427
- const markerMatchesDestination = existingMarker.coreHash === migrationPlan.destination.coreHash && (!migrationPlan.destination.profileHash || existingMarker.profileHash === migrationPlan.destination.profileHash);
1428
- if (markerMatchesDestination) {
1429
- const dbInitResult2 = {
1430
- ok: true,
1431
- mode: options.plan ? "plan" : "apply",
1432
- plan: {
1433
- targetId: migrationPlan.targetId,
1434
- destination: migrationPlan.destination,
1435
- operations: []
1436
- },
1437
- ...options.plan ? {} : {
1438
- execution: { operationsPlanned: 0, operationsExecuted: 0 },
1439
- marker: {
1440
- coreHash: existingMarker.coreHash,
1441
- profileHash: existingMarker.profileHash
1442
- }
1443
- },
1444
- summary: "Database already at target contract state",
1445
- timings: { total: Date.now() - startTime }
1446
- };
1447
- return dbInitResult2;
1448
- }
1449
- const coreHashMismatch = existingMarker.coreHash !== migrationPlan.destination.coreHash;
1450
- const profileHashMismatch = migrationPlan.destination.profileHash && existingMarker.profileHash !== migrationPlan.destination.profileHash;
1451
- const mismatchParts = [];
1452
- if (coreHashMismatch) {
1453
- mismatchParts.push(
1454
- `coreHash (marker: ${existingMarker.coreHash}, destination: ${migrationPlan.destination.coreHash})`
1455
- );
1456
- }
1457
- if (profileHashMismatch) {
1458
- mismatchParts.push(
1459
- `profileHash (marker: ${existingMarker.profileHash}, destination: ${migrationPlan.destination.profileHash})`
1460
- );
1461
- }
1462
- throw errorRuntime(
1463
- `Existing contract marker does not match plan destination. Mismatch in ${mismatchParts.join(" and ")}.`,
1464
- {
1465
- why: "Database has an existing contract marker that does not match the target contract",
1466
- fix: "If bootstrapping, drop/reset the database then re-run `prisma-next db init`; otherwise reconcile schema/marker using your migration workflow",
1467
- meta: {
1468
- code: "MARKER_ORIGIN_MISMATCH",
1469
- markerCoreHash: existingMarker.coreHash,
1470
- destinationCoreHash: migrationPlan.destination.coreHash,
1471
- ...existingMarker.profileHash ? { markerProfileHash: existingMarker.profileHash } : {},
1472
- ...migrationPlan.destination.profileHash ? { destinationProfileHash: migrationPlan.destination.profileHash } : {}
1473
- }
1474
- }
1475
- );
1476
- }
1477
- if (options.plan) {
1478
- const dbInitResult2 = {
1479
- ok: true,
1480
- mode: "plan",
1481
- plan: {
1482
- targetId: migrationPlan.targetId,
1483
- destination: migrationPlan.destination,
1484
- operations: migrationPlan.operations.map((op) => ({
1485
- id: op.id,
1486
- label: op.label,
1487
- operationClass: op.operationClass
1488
- }))
1489
- },
1490
- summary: `Planned ${migrationPlan.operations.length} operation(s)`,
1491
- timings: { total: Date.now() - startTime }
1492
- };
1493
- return dbInitResult2;
1494
- }
1495
- if (!flags.quiet && flags.json !== "object") {
1496
- console.log("Applying migration plan and verifying schema...");
1497
- }
1498
- const callbacks = {
1499
- onOperationStart: (op) => {
1500
- if (!flags.quiet && flags.json !== "object") {
1501
- console.log(` \u2192 ${op.label}...`);
1502
- }
1503
- },
1504
- onOperationComplete: (_op) => {
1505
- }
1506
- };
1507
- const runnerResult = await runner.execute({
1508
- plan: migrationPlan,
1509
- driver,
1510
- destinationContract: contractIR,
1511
- policy,
1512
- callbacks,
1513
- // db init plans and applies back-to-back from a fresh introspection, so per-operation
1514
- // pre/postchecks and the idempotency probe are usually redundant overhead. We still
1515
- // enforce marker/origin compatibility and a full schema verification after apply.
1516
- executionChecks: {
1517
- prechecks: false,
1518
- postchecks: false,
1519
- idempotencyChecks: false
1520
- },
1521
- frameworkComponents
1522
- });
1523
- if (!runnerResult.ok) {
1524
- const meta = {
1525
- code: runnerResult.failure.code,
1526
- ...runnerResult.failure.meta ?? {}
1527
- };
1528
- const sqlState = typeof meta["sqlState"] === "string" ? meta["sqlState"] : void 0;
1529
- 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;
1530
- throw errorRuntime(runnerResult.failure.summary, {
1531
- why: runnerResult.failure.why ?? `Migration runner failed: ${runnerResult.failure.code}`,
1532
- ...fix ? { fix } : {},
1533
- meta
1534
- });
1535
- }
1536
- const execution = runnerResult.value;
1537
- const dbInitResult = {
1538
- ok: true,
1539
- mode: "apply",
1540
- plan: {
1541
- targetId: migrationPlan.targetId,
1542
- destination: migrationPlan.destination,
1543
- operations: migrationPlan.operations.map((op) => ({
1544
- id: op.id,
1545
- label: op.label,
1546
- operationClass: op.operationClass
1547
- }))
1548
- },
1549
- execution: {
1550
- operationsPlanned: execution.operationsPlanned,
1551
- operationsExecuted: execution.operationsExecuted
1552
- },
1553
- marker: migrationPlan.destination.profileHash ? {
1554
- coreHash: migrationPlan.destination.coreHash,
1555
- profileHash: migrationPlan.destination.profileHash
1556
- } : { coreHash: migrationPlan.destination.coreHash },
1557
- summary: `Applied ${execution.operationsExecuted} operation(s), marker written`,
1558
- timings: { total: Date.now() - startTime }
1559
- };
1560
- return dbInitResult;
1561
- } finally {
1562
- await driver.close();
1563
- }
1564
- });
2139
+ })
2140
+ );
2141
+ const exitCode2 = handleResult(result2, flags);
2142
+ process.exit(exitCode2);
2143
+ }
2144
+ const result = await executeDbInitCommand(options, flags, startTime);
1565
2145
  const exitCode = handleResult(result, flags, (dbInitResult) => {
1566
2146
  if (flags.json === "object") {
1567
2147
  console.log(formatDbInitJson(dbInitResult));
@@ -1578,16 +2158,92 @@ function createDbInitCommand() {
1578
2158
  }
1579
2159
 
1580
2160
  // src/commands/db-introspect.ts
1581
- import { readFile as readFile2 } from "fs/promises";
1582
2161
  import { relative as relative4, resolve as resolve4 } from "path";
1583
- import {
1584
- errorDatabaseUrlRequired as errorDatabaseUrlRequired2,
1585
- errorDriverRequired as errorDriverRequired2,
1586
- errorRuntime as errorRuntime2,
1587
- errorUnexpected as errorUnexpected3
1588
- } from "@prisma-next/core-control-plane/errors";
1589
- import { createControlPlaneStack as createControlPlaneStack3 } from "@prisma-next/core-control-plane/types";
2162
+ import { notOk as notOk5, ok as ok5 } from "@prisma-next/utils/result";
1590
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
+ }
1591
2247
  function createDbIntrospectCommand() {
1592
2248
  const command = new Command3("introspect");
1593
2249
  setCommandDescriptions(
@@ -1600,132 +2256,21 @@ function createDbIntrospectCommand() {
1600
2256
  const flags = parseGlobalFlags({});
1601
2257
  return formatCommandHelp({ command: cmd, flags });
1602
2258
  }
1603
- }).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) => {
1604
2260
  const flags = parseGlobalFlags(options);
1605
- const result = await performAction(async () => {
1606
- const startTime = Date.now();
1607
- const config = await loadConfig(options.config);
1608
- const configPath = options.config ? relative4(process.cwd(), resolve4(options.config)) : "prisma-next.config.ts";
1609
- let contractIR;
1610
- if (config.contract?.output) {
1611
- const contractPath = resolve4(config.contract.output);
1612
- try {
1613
- const contractJsonContent = await readFile2(contractPath, "utf-8");
1614
- contractIR = JSON.parse(contractJsonContent);
1615
- } catch (error) {
1616
- if (error instanceof Error && error.code !== "ENOENT") {
1617
- throw errorUnexpected3(error.message, {
1618
- why: `Failed to read contract file: ${error.message}`
1619
- });
1620
- }
1621
- }
1622
- }
1623
- if (flags.json !== "object" && !flags.quiet) {
1624
- const details = [
1625
- { label: "config", value: configPath }
1626
- ];
1627
- if (options.db) {
1628
- const maskedUrl = options.db.replace(/:([^:@]+)@/, ":****@");
1629
- details.push({ label: "database", value: maskedUrl });
1630
- } else if (config.db?.url) {
1631
- const maskedUrl = config.db.url.replace(/:([^:@]+)@/, ":****@");
1632
- details.push({ label: "database", value: maskedUrl });
1633
- }
1634
- const header = formatStyledHeader({
2261
+ const startTime = Date.now();
2262
+ if (flags.json === "ndjson") {
2263
+ const result2 = notOk5(
2264
+ errorJsonFormatNotSupported({
1635
2265
  command: "db introspect",
1636
- description: "Inspect the database schema",
1637
- url: "https://pris.ly/db-introspect",
1638
- details,
1639
- flags
1640
- });
1641
- console.log(header);
1642
- }
1643
- const dbUrl = options.db ?? config.db?.url;
1644
- if (!dbUrl) {
1645
- throw errorDatabaseUrlRequired2();
1646
- }
1647
- if (!config.driver) {
1648
- throw errorDriverRequired2();
1649
- }
1650
- const driverDescriptor = config.driver;
1651
- const driver = await withSpinner(() => driverDescriptor.create(dbUrl), {
1652
- message: "Connecting to database...",
1653
- flags
1654
- });
1655
- try {
1656
- const stack = createControlPlaneStack3({
1657
- target: config.target,
1658
- adapter: config.adapter,
1659
- driver: driverDescriptor,
1660
- extensionPacks: config.extensionPacks
1661
- });
1662
- const familyInstance = config.family.create(stack);
1663
- if (contractIR) {
1664
- const validatedContract = familyInstance.validateContractIR(contractIR);
1665
- assertContractRequirementsSatisfied({ contract: validatedContract, stack });
1666
- contractIR = validatedContract;
1667
- }
1668
- let schemaIR;
1669
- try {
1670
- schemaIR = await withSpinner(
1671
- () => familyInstance.introspect({
1672
- driver,
1673
- contractIR
1674
- }),
1675
- {
1676
- message: "Introspecting database schema...",
1677
- flags
1678
- }
1679
- );
1680
- } catch (error) {
1681
- throw errorRuntime2(error instanceof Error ? error.message : String(error), {
1682
- why: `Failed to introspect database: ${error instanceof Error ? error.message : String(error)}`
1683
- });
1684
- }
1685
- let schemaView;
1686
- if (familyInstance.toSchemaView) {
1687
- try {
1688
- schemaView = familyInstance.toSchemaView(schemaIR);
1689
- } catch (error) {
1690
- if (flags.verbose) {
1691
- console.error(
1692
- `Warning: Failed to project schema to view: ${error instanceof Error ? error.message : String(error)}`
1693
- );
1694
- }
1695
- }
1696
- }
1697
- const totalTime = Date.now() - startTime;
1698
- if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
1699
- console.log("");
1700
- }
1701
- const introspectResult = {
1702
- ok: true,
1703
- summary: "Schema introspected successfully",
1704
- target: {
1705
- familyId: config.family.familyId,
1706
- id: config.target.targetId
1707
- },
1708
- schema: schemaIR,
1709
- ...configPath || options.db || config.db?.url ? {
1710
- meta: {
1711
- ...configPath ? { configPath } : {},
1712
- ...options.db || config.db?.url ? {
1713
- dbUrl: (options.db ?? config.db?.url ?? "").replace(
1714
- /:([^:@]+)@/,
1715
- ":****@"
1716
- )
1717
- } : {}
1718
- }
1719
- } : {},
1720
- timings: {
1721
- total: totalTime
1722
- }
1723
- };
1724
- return { introspectResult, schemaView };
1725
- } finally {
1726
- await driver.close();
1727
- }
1728
- });
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);
1729
2274
  const exitCode = handleResult(result, flags, (value) => {
1730
2275
  const { introspectResult, schemaView } = value;
1731
2276
  if (flags.json === "object") {
@@ -1743,17 +2288,104 @@ function createDbIntrospectCommand() {
1743
2288
  }
1744
2289
 
1745
2290
  // src/commands/db-schema-verify.ts
1746
- import { readFile as readFile3 } from "fs/promises";
2291
+ import { readFile as readFile2 } from "fs/promises";
1747
2292
  import { relative as relative5, resolve as resolve5 } from "path";
1748
- import {
1749
- errorDatabaseUrlRequired as errorDatabaseUrlRequired3,
1750
- errorDriverRequired as errorDriverRequired3,
1751
- errorFileNotFound as errorFileNotFound2,
1752
- errorRuntime as errorRuntime3,
1753
- errorUnexpected as errorUnexpected4
1754
- } from "@prisma-next/core-control-plane/errors";
1755
- import { createControlPlaneStack as createControlPlaneStack4 } from "@prisma-next/core-control-plane/types";
2293
+ import { notOk as notOk6, ok as ok6 } from "@prisma-next/utils/result";
1756
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
+ }
1757
2389
  function createDbSchemaVerifyCommand() {
1758
2390
  const command = new Command4("schema-verify");
1759
2391
  setCommandDescriptions(
@@ -1766,101 +2398,20 @@ function createDbSchemaVerifyCommand() {
1766
2398
  const flags = parseGlobalFlags({});
1767
2399
  return formatCommandHelp({ command: cmd, flags });
1768
2400
  }
1769
- }).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) => {
1770
2402
  const flags = parseGlobalFlags(options);
1771
- const result = await performAction(async () => {
1772
- const config = await loadConfig(options.config);
1773
- const configPath = options.config ? relative5(process.cwd(), resolve5(options.config)) : "prisma-next.config.ts";
1774
- const contractPathAbsolute = config.contract?.output ? resolve5(config.contract.output) : resolve5("src/prisma/contract.json");
1775
- const contractPath = relative5(process.cwd(), contractPathAbsolute);
1776
- if (flags.json !== "object" && !flags.quiet) {
1777
- const details = [
1778
- { label: "config", value: configPath },
1779
- { label: "contract", value: contractPath }
1780
- ];
1781
- if (options.db) {
1782
- details.push({ label: "database", value: options.db });
1783
- }
1784
- const header = formatStyledHeader({
2403
+ if (flags.json === "ndjson") {
2404
+ const result2 = notOk6(
2405
+ errorJsonFormatNotSupported({
1785
2406
  command: "db schema-verify",
1786
- description: "Check whether the database schema satisfies your contract",
1787
- url: "https://pris.ly/db-schema-verify",
1788
- details,
1789
- flags
1790
- });
1791
- console.log(header);
1792
- }
1793
- let contractJsonContent;
1794
- try {
1795
- contractJsonContent = await readFile3(contractPathAbsolute, "utf-8");
1796
- } catch (error) {
1797
- if (error instanceof Error && error.code === "ENOENT") {
1798
- throw errorFileNotFound2(contractPathAbsolute, {
1799
- why: `Contract file not found at ${contractPathAbsolute}`
1800
- });
1801
- }
1802
- throw errorUnexpected4(error instanceof Error ? error.message : String(error), {
1803
- why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
1804
- });
1805
- }
1806
- const contractJson = JSON.parse(contractJsonContent);
1807
- if (!config.driver) {
1808
- throw errorDriverRequired3();
1809
- }
1810
- const driverDescriptor = config.driver;
1811
- const stack = createControlPlaneStack4({
1812
- target: config.target,
1813
- adapter: config.adapter,
1814
- driver: driverDescriptor,
1815
- extensionPacks: config.extensionPacks
1816
- });
1817
- const familyInstance = config.family.create(stack);
1818
- const contractIR = familyInstance.validateContractIR(contractJson);
1819
- assertContractRequirementsSatisfied({ contract: contractIR, stack });
1820
- const dbUrl = options.db ?? config.db?.url;
1821
- if (!dbUrl) {
1822
- throw errorDatabaseUrlRequired3();
1823
- }
1824
- const driver = await withSpinner(() => driverDescriptor.create(dbUrl), {
1825
- message: "Connecting to database...",
1826
- flags
1827
- });
1828
- try {
1829
- const rawComponents = [config.target, config.adapter, ...config.extensionPacks ?? []];
1830
- const frameworkComponents = assertFrameworkComponentsCompatible(
1831
- config.family.familyId,
1832
- config.target.targetId,
1833
- rawComponents
1834
- );
1835
- let schemaVerifyResult;
1836
- try {
1837
- schemaVerifyResult = await withSpinner(
1838
- () => familyInstance.schemaVerify({
1839
- driver,
1840
- contractIR,
1841
- strict: options.strict ?? false,
1842
- contractPath: contractPathAbsolute,
1843
- configPath,
1844
- frameworkComponents
1845
- }),
1846
- {
1847
- message: "Verifying database schema...",
1848
- flags
1849
- }
1850
- );
1851
- } catch (error) {
1852
- throw errorRuntime3(error instanceof Error ? error.message : String(error), {
1853
- why: `Failed to verify database schema: ${error instanceof Error ? error.message : String(error)}`
1854
- });
1855
- }
1856
- if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
1857
- console.log("");
1858
- }
1859
- return schemaVerifyResult;
1860
- } finally {
1861
- await driver.close();
1862
- }
1863
- });
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);
1864
2415
  const exitCode = handleResult(result, flags, (schemaVerifyResult) => {
1865
2416
  if (flags.json === "object") {
1866
2417
  console.log(formatSchemaVerifyJson(schemaVerifyResult));
@@ -1881,17 +2432,116 @@ function createDbSchemaVerifyCommand() {
1881
2432
  }
1882
2433
 
1883
2434
  // src/commands/db-sign.ts
1884
- import { readFile as readFile4 } from "fs/promises";
2435
+ import { readFile as readFile3 } from "fs/promises";
1885
2436
  import { relative as relative6, resolve as resolve6 } from "path";
1886
- import {
1887
- errorDatabaseUrlRequired as errorDatabaseUrlRequired4,
1888
- errorDriverRequired as errorDriverRequired4,
1889
- errorFileNotFound as errorFileNotFound3,
1890
- errorRuntime as errorRuntime4,
1891
- errorUnexpected as errorUnexpected5
1892
- } from "@prisma-next/core-control-plane/errors";
1893
- import { createControlPlaneStack as createControlPlaneStack5 } from "@prisma-next/core-control-plane/types";
2437
+ import { notOk as notOk7, ok as ok7 } from "@prisma-next/utils/result";
1894
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
+ }
1895
2545
  function createDbSignCommand() {
1896
2546
  const command = new Command5("sign");
1897
2547
  setCommandDescriptions(
@@ -1904,168 +2554,170 @@ function createDbSignCommand() {
1904
2554
  const flags = parseGlobalFlags({});
1905
2555
  return formatCommandHelp({ command: cmd, flags });
1906
2556
  }
1907
- }).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) => {
1908
2558
  const flags = parseGlobalFlags(options);
1909
- const result = await performAction(async () => {
1910
- const config = await loadConfig(options.config);
1911
- const configPath = options.config ? relative6(process.cwd(), resolve6(options.config)) : "prisma-next.config.ts";
1912
- const contractPathAbsolute = config.contract?.output ? resolve6(config.contract.output) : resolve6("src/prisma/contract.json");
1913
- const contractPath = relative6(process.cwd(), contractPathAbsolute);
1914
- if (flags.json !== "object" && !flags.quiet) {
1915
- const details = [
1916
- { label: "config", value: configPath },
1917
- { label: "contract", value: contractPath }
1918
- ];
1919
- if (options.db) {
1920
- details.push({ label: "database", value: options.db });
1921
- }
1922
- const header = formatStyledHeader({
2559
+ if (flags.json === "ndjson") {
2560
+ const result2 = notOk7(
2561
+ errorJsonFormatNotSupported({
1923
2562
  command: "db sign",
1924
- description: "Sign the database with your contract so you can safely run queries",
1925
- url: "https://pris.ly/db-sign",
1926
- details,
1927
- flags
1928
- });
1929
- console.log(header);
1930
- }
1931
- let contractJsonContent;
1932
- try {
1933
- contractJsonContent = await readFile4(contractPathAbsolute, "utf-8");
1934
- } catch (error) {
1935
- if (error instanceof Error && error.code === "ENOENT") {
1936
- throw errorFileNotFound3(contractPathAbsolute, {
1937
- why: `Contract file not found at ${contractPathAbsolute}`
1938
- });
1939
- }
1940
- throw errorUnexpected5(error instanceof Error ? error.message : String(error), {
1941
- why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
1942
- });
1943
- }
1944
- const contractJson = JSON.parse(contractJsonContent);
1945
- const dbUrl = options.db ?? config.db?.url;
1946
- if (!dbUrl) {
1947
- throw errorDatabaseUrlRequired4();
1948
- }
1949
- if (!config.driver) {
1950
- throw errorDriverRequired4();
1951
- }
1952
- const driverDescriptor = config.driver;
1953
- const stack = createControlPlaneStack5({
1954
- target: config.target,
1955
- adapter: config.adapter,
1956
- driver: driverDescriptor,
1957
- extensionPacks: config.extensionPacks
1958
- });
1959
- const familyInstance = config.family.create(stack);
1960
- const contractIR = familyInstance.validateContractIR(contractJson);
1961
- assertContractRequirementsSatisfied({ contract: contractIR, stack });
1962
- const rawComponents = [config.target, config.adapter, ...config.extensionPacks ?? []];
1963
- const frameworkComponents = assertFrameworkComponentsCompatible(
1964
- config.family.familyId,
1965
- config.target.targetId,
1966
- rawComponents
2563
+ format: "ndjson",
2564
+ supportedFormats: ["object"]
2565
+ })
1967
2566
  );
1968
- const driver = await driverDescriptor.create(dbUrl);
1969
- try {
1970
- let schemaVerifyResult;
1971
- try {
1972
- schemaVerifyResult = await withSpinner(
1973
- () => familyInstance.schemaVerify({
1974
- driver,
1975
- contractIR,
1976
- strict: false,
1977
- contractPath: contractPathAbsolute,
1978
- configPath,
1979
- frameworkComponents
1980
- }),
1981
- {
1982
- message: "Verifying database satisfies contract",
1983
- flags
1984
- }
1985
- );
1986
- } catch (error) {
1987
- throw errorRuntime4(error instanceof Error ? error.message : String(error), {
1988
- why: `Failed to verify database schema: ${error instanceof Error ? error.message : String(error)}`
1989
- });
1990
- }
1991
- if (!schemaVerifyResult.ok) {
1992
- return { schemaVerifyResult, signResult: void 0 };
1993
- }
1994
- let signResult;
1995
- try {
1996
- signResult = await withSpinner(
1997
- () => familyInstance.sign({
1998
- driver,
1999
- contractIR,
2000
- contractPath: contractPathAbsolute,
2001
- configPath
2002
- }),
2003
- {
2004
- message: "Signing database...",
2005
- flags
2006
- }
2007
- );
2008
- } catch (error) {
2009
- throw errorRuntime4(error instanceof Error ? error.message : String(error), {
2010
- why: `Failed to sign database: ${error instanceof Error ? error.message : String(error)}`
2011
- });
2012
- }
2013
- if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
2014
- console.log("");
2015
- }
2016
- return { schemaVerifyResult: void 0, signResult };
2017
- } finally {
2018
- await driver.close();
2019
- }
2020
- });
2021
- const exitCode = handleResult(result, flags, (value) => {
2022
- const { schemaVerifyResult, signResult } = value;
2023
- if (schemaVerifyResult && !schemaVerifyResult.ok) {
2024
- if (flags.json === "object") {
2025
- console.log(formatSchemaVerifyJson(schemaVerifyResult));
2026
- } else {
2027
- const output = formatSchemaVerifyOutput(schemaVerifyResult, flags);
2028
- if (output) {
2029
- console.log(output);
2030
- }
2031
- }
2032
- return;
2033
- }
2034
- if (signResult) {
2035
- if (flags.json === "object") {
2036
- console.log(formatSignJson(signResult));
2037
- } else {
2038
- const output = formatSignOutput(signResult, flags);
2039
- if (output) {
2040
- console.log(output);
2041
- }
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);
2042
2578
  }
2043
2579
  }
2044
- });
2045
- if (result.ok && result.value.schemaVerifyResult && !result.value.schemaVerifyResult.ok) {
2046
- process.exit(1);
2047
- } else {
2580
+ process.exit(0);
2581
+ }
2582
+ const failure = result.failure;
2583
+ if (failure instanceof CliStructuredError) {
2584
+ const exitCode = handleResult(result, flags);
2048
2585
  process.exit(exitCode);
2049
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);
2050
2596
  });
2051
2597
  return command;
2052
2598
  }
2053
2599
 
2054
2600
  // src/commands/db-verify.ts
2055
- import { readFile as readFile5 } from "fs/promises";
2601
+ import { readFile as readFile4 } from "fs/promises";
2056
2602
  import { relative as relative7, resolve as resolve7 } from "path";
2057
- import {
2058
- errorDatabaseUrlRequired as errorDatabaseUrlRequired5,
2059
- errorDriverRequired as errorDriverRequired5,
2060
- errorFileNotFound as errorFileNotFound4,
2061
- errorHashMismatch as errorHashMismatch2,
2062
- errorMarkerMissing as errorMarkerMissing2,
2063
- errorRuntime as errorRuntime5,
2064
- errorTargetMismatch as errorTargetMismatch2,
2065
- errorUnexpected as errorUnexpected6
2066
- } from "@prisma-next/core-control-plane/errors";
2067
- import { createControlPlaneStack as createControlPlaneStack6 } from "@prisma-next/core-control-plane/types";
2603
+ import { notOk as notOk8, ok as ok8 } from "@prisma-next/utils/result";
2068
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
+ }
2069
2721
  function createDbVerifyCommand() {
2070
2722
  const command = new Command6("verify");
2071
2723
  setCommandDescriptions(
@@ -2078,112 +2730,20 @@ function createDbVerifyCommand() {
2078
2730
  const flags = parseGlobalFlags({});
2079
2731
  return formatCommandHelp({ command: cmd, flags });
2080
2732
  }
2081
- }).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) => {
2082
2734
  const flags = parseGlobalFlags(options);
2083
- const result = await performAction(async () => {
2084
- const config = await loadConfig(options.config);
2085
- const configPath = options.config ? relative7(process.cwd(), resolve7(options.config)) : "prisma-next.config.ts";
2086
- const contractPathAbsolute = config.contract?.output ? resolve7(config.contract.output) : resolve7("src/prisma/contract.json");
2087
- const contractPath = relative7(process.cwd(), contractPathAbsolute);
2088
- if (flags.json !== "object" && !flags.quiet) {
2089
- const details = [
2090
- { label: "config", value: configPath },
2091
- { label: "contract", value: contractPath }
2092
- ];
2093
- if (options.db) {
2094
- details.push({ label: "database", value: options.db });
2095
- }
2096
- const header = formatStyledHeader({
2735
+ if (flags.json === "ndjson") {
2736
+ const result2 = notOk8(
2737
+ errorJsonFormatNotSupported({
2097
2738
  command: "db verify",
2098
- description: "Check whether the database has been signed with your contract",
2099
- url: "https://pris.ly/db-verify",
2100
- details,
2101
- flags
2102
- });
2103
- console.log(header);
2104
- }
2105
- let contractJsonContent;
2106
- try {
2107
- contractJsonContent = await readFile5(contractPathAbsolute, "utf-8");
2108
- } catch (error) {
2109
- if (error instanceof Error && error.code === "ENOENT") {
2110
- throw errorFileNotFound4(contractPathAbsolute, {
2111
- why: `Contract file not found at ${contractPathAbsolute}`
2112
- });
2113
- }
2114
- throw errorUnexpected6(error instanceof Error ? error.message : String(error), {
2115
- why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
2116
- });
2117
- }
2118
- const contractJson = JSON.parse(contractJsonContent);
2119
- const dbUrl = options.db ?? config.db?.url;
2120
- if (!dbUrl) {
2121
- throw errorDatabaseUrlRequired5();
2122
- }
2123
- if (!config.driver) {
2124
- throw errorDriverRequired5();
2125
- }
2126
- const driverDescriptor = config.driver;
2127
- const driver = await withSpinner(() => driverDescriptor.create(dbUrl), {
2128
- message: "Connecting to database...",
2129
- flags
2130
- });
2131
- try {
2132
- const stack = createControlPlaneStack6({
2133
- target: config.target,
2134
- adapter: config.adapter,
2135
- driver: driverDescriptor,
2136
- extensionPacks: config.extensionPacks
2137
- });
2138
- const familyInstance = config.family.create(stack);
2139
- const contractIR = familyInstance.validateContractIR(contractJson);
2140
- assertContractRequirementsSatisfied({ contract: contractIR, stack });
2141
- let verifyResult;
2142
- try {
2143
- verifyResult = await withSpinner(
2144
- () => familyInstance.verify({
2145
- driver,
2146
- contractIR,
2147
- expectedTargetId: config.target.targetId,
2148
- contractPath: contractPathAbsolute,
2149
- configPath
2150
- }),
2151
- {
2152
- message: "Verifying database schema...",
2153
- flags
2154
- }
2155
- );
2156
- } catch (error) {
2157
- throw errorUnexpected6(error instanceof Error ? error.message : String(error), {
2158
- why: `Failed to verify database: ${error instanceof Error ? error.message : String(error)}`
2159
- });
2160
- }
2161
- if (!verifyResult.ok && verifyResult.code) {
2162
- if (verifyResult.code === "PN-RTM-3001") {
2163
- throw errorMarkerMissing2();
2164
- }
2165
- if (verifyResult.code === "PN-RTM-3002") {
2166
- throw errorHashMismatch2({
2167
- expected: verifyResult.contract.coreHash,
2168
- ...verifyResult.marker?.coreHash ? { actual: verifyResult.marker.coreHash } : {}
2169
- });
2170
- }
2171
- if (verifyResult.code === "PN-RTM-3003") {
2172
- throw errorTargetMismatch2(
2173
- verifyResult.target.expected,
2174
- verifyResult.target.actual ?? "unknown"
2175
- );
2176
- }
2177
- throw errorRuntime5(verifyResult.summary);
2178
- }
2179
- if (!flags.quiet && flags.json !== "object" && process.stdout.isTTY) {
2180
- console.log("");
2181
- }
2182
- return verifyResult;
2183
- } finally {
2184
- await driver.close();
2185
- }
2186
- });
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);
2187
2747
  const exitCode = handleResult(result, flags, (verifyResult) => {
2188
2748
  if (flags.json === "object") {
2189
2749
  console.log(formatVerifyJson(verifyResult));