@prisma-next/cli 0.3.0-dev.14 → 0.3.0-dev.16

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 (42) hide show
  1. package/dist/chunk-5MPKZYVI.js +47 -0
  2. package/dist/chunk-5MPKZYVI.js.map +1 -0
  3. package/dist/chunk-74IELXRA.js +371 -0
  4. package/dist/chunk-74IELXRA.js.map +1 -0
  5. package/dist/{chunk-MG7PBERL.js → chunk-U6QI3AZ3.js} +7 -5
  6. package/dist/{chunk-MG7PBERL.js.map → chunk-U6QI3AZ3.js.map} +1 -1
  7. package/dist/{chunk-DIJPT5TZ.js → chunk-ZG5T6OB5.js} +2 -46
  8. package/dist/chunk-ZG5T6OB5.js.map +1 -0
  9. package/dist/cli.js +593 -268
  10. package/dist/cli.js.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 +238 -275
  14. package/dist/commands/db-init.js.map +1 -1
  15. package/dist/commands/db-introspect.js +6 -4
  16. package/dist/commands/db-introspect.js.map +1 -1
  17. package/dist/commands/db-schema-verify.js +6 -4
  18. package/dist/commands/db-schema-verify.js.map +1 -1
  19. package/dist/commands/db-sign.js +6 -4
  20. package/dist/commands/db-sign.js.map +1 -1
  21. package/dist/commands/db-verify.js +6 -4
  22. package/dist/commands/db-verify.js.map +1 -1
  23. package/dist/control-api/operations/db-init.d.ts +3 -1
  24. package/dist/control-api/operations/db-init.d.ts.map +1 -1
  25. package/dist/control-api/types.d.ts +54 -1
  26. package/dist/control-api/types.d.ts.map +1 -1
  27. package/dist/exports/control-api.d.ts +1 -1
  28. package/dist/exports/control-api.d.ts.map +1 -1
  29. package/dist/exports/control-api.js +3 -234
  30. package/dist/exports/control-api.js.map +1 -1
  31. package/dist/exports/index.js +3 -2
  32. package/dist/exports/index.js.map +1 -1
  33. package/dist/utils/progress-adapter.d.ts +26 -0
  34. package/dist/utils/progress-adapter.d.ts.map +1 -0
  35. package/package.json +10 -10
  36. package/src/commands/db-init.ts +262 -355
  37. package/src/control-api/client.ts +30 -0
  38. package/src/control-api/operations/db-init.ts +116 -2
  39. package/src/control-api/types.ts +63 -1
  40. package/src/exports/control-api.ts +3 -0
  41. package/src/utils/progress-adapter.ts +86 -0
  42. package/dist/chunk-DIJPT5TZ.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1192,10 +1192,12 @@ function createContractEmitCommand() {
1192
1192
  // src/commands/db-init.ts
1193
1193
  import { readFile } from "fs/promises";
1194
1194
  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";
1195
+ import { notOk as notOk3, ok as ok3 } from "@prisma-next/utils/result";
1197
1196
  import { Command as Command2 } from "commander";
1198
1197
 
1198
+ // src/control-api/client.ts
1199
+ import { createControlPlaneStack as createControlPlaneStack2 } from "@prisma-next/core-control-plane/stack";
1200
+
1199
1201
  // src/utils/framework-components.ts
1200
1202
  import {
1201
1203
  checkContractComponentRequirements
@@ -1277,7 +1279,587 @@ function assertContractRequirementsSatisfied({
1277
1279
  }
1278
1280
  }
1279
1281
 
1282
+ // src/control-api/operations/db-init.ts
1283
+ import { notOk as notOk2, ok as ok2 } from "@prisma-next/utils/result";
1284
+ async function executeDbInit(options) {
1285
+ const { driver, familyInstance, contractIR, mode, migrations, frameworkComponents, onProgress } = options;
1286
+ const planner = migrations.createPlanner(familyInstance);
1287
+ const runner = migrations.createRunner(familyInstance);
1288
+ const introspectSpanId = "introspect";
1289
+ onProgress?.({
1290
+ action: "dbInit",
1291
+ kind: "spanStart",
1292
+ spanId: introspectSpanId,
1293
+ label: "Introspecting database schema"
1294
+ });
1295
+ const schemaIR = await familyInstance.introspect({ driver });
1296
+ onProgress?.({
1297
+ action: "dbInit",
1298
+ kind: "spanEnd",
1299
+ spanId: introspectSpanId,
1300
+ outcome: "ok"
1301
+ });
1302
+ const policy = { allowedOperationClasses: ["additive"] };
1303
+ const planSpanId = "plan";
1304
+ onProgress?.({
1305
+ action: "dbInit",
1306
+ kind: "spanStart",
1307
+ spanId: planSpanId,
1308
+ label: "Planning migration"
1309
+ });
1310
+ const plannerResult = await planner.plan({
1311
+ contract: contractIR,
1312
+ schema: schemaIR,
1313
+ policy,
1314
+ frameworkComponents
1315
+ });
1316
+ if (plannerResult.kind === "failure") {
1317
+ onProgress?.({
1318
+ action: "dbInit",
1319
+ kind: "spanEnd",
1320
+ spanId: planSpanId,
1321
+ outcome: "error"
1322
+ });
1323
+ return notOk2({
1324
+ code: "PLANNING_FAILED",
1325
+ summary: "Migration planning failed due to conflicts",
1326
+ conflicts: plannerResult.conflicts,
1327
+ why: void 0,
1328
+ meta: void 0
1329
+ });
1330
+ }
1331
+ const migrationPlan = plannerResult.plan;
1332
+ onProgress?.({
1333
+ action: "dbInit",
1334
+ kind: "spanEnd",
1335
+ spanId: planSpanId,
1336
+ outcome: "ok"
1337
+ });
1338
+ const checkMarkerSpanId = "checkMarker";
1339
+ onProgress?.({
1340
+ action: "dbInit",
1341
+ kind: "spanStart",
1342
+ spanId: checkMarkerSpanId,
1343
+ label: "Checking contract marker"
1344
+ });
1345
+ const existingMarker = await familyInstance.readMarker({ driver });
1346
+ if (existingMarker) {
1347
+ const markerMatchesDestination = existingMarker.coreHash === migrationPlan.destination.coreHash && (!migrationPlan.destination.profileHash || existingMarker.profileHash === migrationPlan.destination.profileHash);
1348
+ if (markerMatchesDestination) {
1349
+ onProgress?.({
1350
+ action: "dbInit",
1351
+ kind: "spanEnd",
1352
+ spanId: checkMarkerSpanId,
1353
+ outcome: "skipped"
1354
+ });
1355
+ const result2 = {
1356
+ mode,
1357
+ plan: { operations: [] },
1358
+ ...mode === "apply" ? {
1359
+ execution: { operationsPlanned: 0, operationsExecuted: 0 },
1360
+ marker: {
1361
+ coreHash: existingMarker.coreHash,
1362
+ profileHash: existingMarker.profileHash
1363
+ }
1364
+ } : {},
1365
+ summary: "Database already at target contract state"
1366
+ };
1367
+ return ok2(result2);
1368
+ }
1369
+ onProgress?.({
1370
+ action: "dbInit",
1371
+ kind: "spanEnd",
1372
+ spanId: checkMarkerSpanId,
1373
+ outcome: "error"
1374
+ });
1375
+ return notOk2({
1376
+ code: "MARKER_ORIGIN_MISMATCH",
1377
+ summary: "Existing contract marker does not match plan destination",
1378
+ marker: {
1379
+ coreHash: existingMarker.coreHash,
1380
+ profileHash: existingMarker.profileHash
1381
+ },
1382
+ destination: {
1383
+ coreHash: migrationPlan.destination.coreHash,
1384
+ profileHash: migrationPlan.destination.profileHash
1385
+ },
1386
+ why: void 0,
1387
+ conflicts: void 0,
1388
+ meta: void 0
1389
+ });
1390
+ }
1391
+ onProgress?.({
1392
+ action: "dbInit",
1393
+ kind: "spanEnd",
1394
+ spanId: checkMarkerSpanId,
1395
+ outcome: "ok"
1396
+ });
1397
+ if (mode === "plan") {
1398
+ const result2 = {
1399
+ mode: "plan",
1400
+ plan: { operations: migrationPlan.operations },
1401
+ summary: `Planned ${migrationPlan.operations.length} operation(s)`
1402
+ };
1403
+ return ok2(result2);
1404
+ }
1405
+ const applySpanId = "apply";
1406
+ onProgress?.({
1407
+ action: "dbInit",
1408
+ kind: "spanStart",
1409
+ spanId: applySpanId,
1410
+ label: "Applying migration plan"
1411
+ });
1412
+ const callbacks = onProgress ? {
1413
+ onOperationStart: (op) => {
1414
+ onProgress({
1415
+ action: "dbInit",
1416
+ kind: "spanStart",
1417
+ spanId: `operation:${op.id}`,
1418
+ parentSpanId: applySpanId,
1419
+ label: op.label
1420
+ });
1421
+ },
1422
+ onOperationComplete: (op) => {
1423
+ onProgress({
1424
+ action: "dbInit",
1425
+ kind: "spanEnd",
1426
+ spanId: `operation:${op.id}`,
1427
+ outcome: "ok"
1428
+ });
1429
+ }
1430
+ } : void 0;
1431
+ const runnerResult = await runner.execute({
1432
+ plan: migrationPlan,
1433
+ driver,
1434
+ destinationContract: contractIR,
1435
+ policy,
1436
+ ...callbacks ? { callbacks } : {},
1437
+ // db init plans and applies back-to-back from a fresh introspection, so per-operation
1438
+ // pre/postchecks and the idempotency probe are usually redundant overhead. We still
1439
+ // enforce marker/origin compatibility and a full schema verification after apply.
1440
+ executionChecks: {
1441
+ prechecks: false,
1442
+ postchecks: false,
1443
+ idempotencyChecks: false
1444
+ },
1445
+ frameworkComponents
1446
+ });
1447
+ if (!runnerResult.ok) {
1448
+ onProgress?.({
1449
+ action: "dbInit",
1450
+ kind: "spanEnd",
1451
+ spanId: applySpanId,
1452
+ outcome: "error"
1453
+ });
1454
+ return notOk2({
1455
+ code: "RUNNER_FAILED",
1456
+ summary: runnerResult.failure.summary,
1457
+ why: runnerResult.failure.why,
1458
+ meta: runnerResult.failure.meta,
1459
+ conflicts: void 0
1460
+ });
1461
+ }
1462
+ const execution = runnerResult.value;
1463
+ onProgress?.({
1464
+ action: "dbInit",
1465
+ kind: "spanEnd",
1466
+ spanId: applySpanId,
1467
+ outcome: "ok"
1468
+ });
1469
+ const result = {
1470
+ mode: "apply",
1471
+ plan: { operations: migrationPlan.operations },
1472
+ execution: {
1473
+ operationsPlanned: execution.operationsPlanned,
1474
+ operationsExecuted: execution.operationsExecuted
1475
+ },
1476
+ marker: migrationPlan.destination.profileHash ? {
1477
+ coreHash: migrationPlan.destination.coreHash,
1478
+ profileHash: migrationPlan.destination.profileHash
1479
+ } : { coreHash: migrationPlan.destination.coreHash },
1480
+ summary: `Applied ${execution.operationsExecuted} operation(s), marker written`
1481
+ };
1482
+ return ok2(result);
1483
+ }
1484
+
1485
+ // src/control-api/client.ts
1486
+ function createControlClient(options) {
1487
+ return new ControlClientImpl(options);
1488
+ }
1489
+ var ControlClientImpl = class {
1490
+ options;
1491
+ stack = null;
1492
+ driver = null;
1493
+ familyInstance = null;
1494
+ frameworkComponents = null;
1495
+ initialized = false;
1496
+ defaultConnection;
1497
+ constructor(options) {
1498
+ this.options = options;
1499
+ this.defaultConnection = options.connection;
1500
+ }
1501
+ init() {
1502
+ if (this.initialized) {
1503
+ return;
1504
+ }
1505
+ this.stack = createControlPlaneStack2({
1506
+ target: this.options.target,
1507
+ adapter: this.options.adapter,
1508
+ driver: this.options.driver,
1509
+ extensionPacks: this.options.extensionPacks
1510
+ });
1511
+ this.familyInstance = this.options.family.create(this.stack);
1512
+ const rawComponents = [
1513
+ this.options.target,
1514
+ this.options.adapter,
1515
+ ...this.options.extensionPacks ?? []
1516
+ ];
1517
+ this.frameworkComponents = assertFrameworkComponentsCompatible(
1518
+ this.options.family.familyId,
1519
+ this.options.target.targetId,
1520
+ rawComponents
1521
+ );
1522
+ this.initialized = true;
1523
+ }
1524
+ async connect(connection) {
1525
+ this.init();
1526
+ if (this.driver) {
1527
+ throw new Error("Already connected. Call close() before reconnecting.");
1528
+ }
1529
+ const resolvedConnection = connection ?? this.defaultConnection;
1530
+ if (resolvedConnection === void 0) {
1531
+ throw new Error(
1532
+ "No connection provided. Pass a connection to connect() or provide a default connection when creating the client."
1533
+ );
1534
+ }
1535
+ if (!this.stack?.driver) {
1536
+ throw new Error(
1537
+ "Driver is not configured. Pass a driver descriptor when creating the control client to enable database operations."
1538
+ );
1539
+ }
1540
+ this.driver = await this.stack?.driver.create(resolvedConnection);
1541
+ }
1542
+ async close() {
1543
+ if (this.driver) {
1544
+ await this.driver.close();
1545
+ this.driver = null;
1546
+ }
1547
+ }
1548
+ async ensureConnected() {
1549
+ this.init();
1550
+ if (!this.driver && this.defaultConnection !== void 0) {
1551
+ await this.connect(this.defaultConnection);
1552
+ }
1553
+ if (!this.driver || !this.familyInstance || !this.frameworkComponents) {
1554
+ throw new Error("Not connected. Call connect(connection) first.");
1555
+ }
1556
+ return {
1557
+ driver: this.driver,
1558
+ familyInstance: this.familyInstance,
1559
+ frameworkComponents: this.frameworkComponents
1560
+ };
1561
+ }
1562
+ async verify(options) {
1563
+ const { driver, familyInstance } = await this.ensureConnected();
1564
+ const contractIR = familyInstance.validateContractIR(options.contractIR);
1565
+ return familyInstance.verify({
1566
+ driver,
1567
+ contractIR,
1568
+ expectedTargetId: this.options.target.targetId,
1569
+ contractPath: ""
1570
+ });
1571
+ }
1572
+ async schemaVerify(options) {
1573
+ const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
1574
+ const contractIR = familyInstance.validateContractIR(options.contractIR);
1575
+ return familyInstance.schemaVerify({
1576
+ driver,
1577
+ contractIR,
1578
+ strict: options.strict ?? false,
1579
+ contractPath: "",
1580
+ frameworkComponents
1581
+ });
1582
+ }
1583
+ async sign(options) {
1584
+ const { driver, familyInstance } = await this.ensureConnected();
1585
+ const contractIR = familyInstance.validateContractIR(options.contractIR);
1586
+ return familyInstance.sign({
1587
+ driver,
1588
+ contractIR,
1589
+ contractPath: ""
1590
+ });
1591
+ }
1592
+ async dbInit(options) {
1593
+ const { onProgress } = options;
1594
+ if (options.connection !== void 0) {
1595
+ onProgress?.({
1596
+ action: "dbInit",
1597
+ kind: "spanStart",
1598
+ spanId: "connect",
1599
+ label: "Connecting to database..."
1600
+ });
1601
+ try {
1602
+ await this.connect(options.connection);
1603
+ onProgress?.({
1604
+ action: "dbInit",
1605
+ kind: "spanEnd",
1606
+ spanId: "connect",
1607
+ outcome: "ok"
1608
+ });
1609
+ } catch (error) {
1610
+ onProgress?.({
1611
+ action: "dbInit",
1612
+ kind: "spanEnd",
1613
+ spanId: "connect",
1614
+ outcome: "error"
1615
+ });
1616
+ throw error;
1617
+ }
1618
+ }
1619
+ const { driver, familyInstance, frameworkComponents } = await this.ensureConnected();
1620
+ if (!this.options.target.migrations) {
1621
+ throw new Error(`Target "${this.options.target.targetId}" does not support migrations`);
1622
+ }
1623
+ const contractIR = familyInstance.validateContractIR(options.contractIR);
1624
+ return executeDbInit({
1625
+ driver,
1626
+ familyInstance,
1627
+ contractIR,
1628
+ mode: options.mode,
1629
+ migrations: this.options.target.migrations,
1630
+ frameworkComponents,
1631
+ ...onProgress ? { onProgress } : {}
1632
+ });
1633
+ }
1634
+ async introspect(options) {
1635
+ const { driver, familyInstance } = await this.ensureConnected();
1636
+ const _schema = options?.schema;
1637
+ void _schema;
1638
+ return familyInstance.introspect({ driver });
1639
+ }
1640
+ };
1641
+
1642
+ // src/utils/progress-adapter.ts
1643
+ import ora2 from "ora";
1644
+ function createProgressAdapter(options) {
1645
+ const { flags } = options;
1646
+ const shouldShowProgress = !flags.quiet && flags.json !== "object" && process.stdout.isTTY;
1647
+ if (!shouldShowProgress) {
1648
+ return () => {
1649
+ };
1650
+ }
1651
+ const activeSpans = /* @__PURE__ */ new Map();
1652
+ return (event) => {
1653
+ if (event.kind === "spanStart") {
1654
+ if (event.parentSpanId) {
1655
+ console.log(` \u2192 ${event.label}...`);
1656
+ return;
1657
+ }
1658
+ const spinner = ora2({
1659
+ text: event.label,
1660
+ color: flags.color !== false ? "cyan" : false
1661
+ }).start();
1662
+ activeSpans.set(event.spanId, {
1663
+ spinner,
1664
+ startTime: Date.now()
1665
+ });
1666
+ } else if (event.kind === "spanEnd") {
1667
+ const spanState = activeSpans.get(event.spanId);
1668
+ if (spanState) {
1669
+ const elapsed = Date.now() - spanState.startTime;
1670
+ if (event.outcome === "error") {
1671
+ spanState.spinner.fail(`${spanState.spinner.text} (failed)`);
1672
+ } else if (event.outcome === "skipped") {
1673
+ spanState.spinner.info(`${spanState.spinner.text} (skipped)`);
1674
+ } else {
1675
+ spanState.spinner.succeed(`${spanState.spinner.text} (${elapsed}ms)`);
1676
+ }
1677
+ activeSpans.delete(event.spanId);
1678
+ }
1679
+ }
1680
+ };
1681
+ }
1682
+
1280
1683
  // src/commands/db-init.ts
1684
+ function mapDbInitFailure(failure) {
1685
+ if (failure.code === "PLANNING_FAILED") {
1686
+ return errorMigrationPlanningFailed({ conflicts: failure.conflicts ?? [] });
1687
+ }
1688
+ if (failure.code === "MARKER_ORIGIN_MISMATCH") {
1689
+ const mismatchParts = [];
1690
+ if (failure.marker?.coreHash !== failure.destination?.coreHash && failure.marker?.coreHash && failure.destination?.coreHash) {
1691
+ mismatchParts.push(
1692
+ `coreHash (marker: ${failure.marker.coreHash}, destination: ${failure.destination.coreHash})`
1693
+ );
1694
+ }
1695
+ if (failure.marker?.profileHash !== failure.destination?.profileHash && failure.marker?.profileHash && failure.destination?.profileHash) {
1696
+ mismatchParts.push(
1697
+ `profileHash (marker: ${failure.marker.profileHash}, destination: ${failure.destination.profileHash})`
1698
+ );
1699
+ }
1700
+ return errorRuntime(
1701
+ `Existing contract marker does not match plan destination.${mismatchParts.length > 0 ? ` Mismatch in ${mismatchParts.join(" and ")}.` : ""}`,
1702
+ {
1703
+ why: "Database has an existing contract marker that does not match the target contract",
1704
+ fix: "If bootstrapping, drop/reset the database then re-run `prisma-next db init`; otherwise reconcile schema/marker using your migration workflow",
1705
+ meta: {
1706
+ code: "MARKER_ORIGIN_MISMATCH",
1707
+ ...failure.marker?.coreHash ? { markerCoreHash: failure.marker.coreHash } : {},
1708
+ ...failure.destination?.coreHash ? { destinationCoreHash: failure.destination.coreHash } : {},
1709
+ ...failure.marker?.profileHash ? { markerProfileHash: failure.marker.profileHash } : {},
1710
+ ...failure.destination?.profileHash ? { destinationProfileHash: failure.destination.profileHash } : {}
1711
+ }
1712
+ }
1713
+ );
1714
+ }
1715
+ if (failure.code === "RUNNER_FAILED") {
1716
+ return errorRuntime(failure.summary, {
1717
+ why: failure.why ?? "Migration runner failed",
1718
+ fix: "Fix the schema mismatch (db init is additive-only), or drop/reset the database and re-run `prisma-next db init`",
1719
+ meta: {
1720
+ code: "RUNNER_FAILED",
1721
+ ...failure.meta ?? {}
1722
+ }
1723
+ });
1724
+ }
1725
+ const exhaustive = failure.code;
1726
+ throw new Error(`Unhandled DbInitFailure code: ${exhaustive}`);
1727
+ }
1728
+ async function executeDbInitCommand(options, flags, startTime) {
1729
+ const config = await loadConfig(options.config);
1730
+ const configPath = options.config ? relative3(process.cwd(), resolve3(options.config)) : "prisma-next.config.ts";
1731
+ const contractPathAbsolute = config.contract?.output ? resolve3(config.contract.output) : resolve3("src/prisma/contract.json");
1732
+ const contractPath = relative3(process.cwd(), contractPathAbsolute);
1733
+ if (flags.json !== "object" && !flags.quiet) {
1734
+ const details = [
1735
+ { label: "config", value: configPath },
1736
+ { label: "contract", value: contractPath }
1737
+ ];
1738
+ if (options.db) {
1739
+ details.push({ label: "database", value: options.db });
1740
+ }
1741
+ if (options.plan) {
1742
+ details.push({ label: "mode", value: "plan (dry run)" });
1743
+ }
1744
+ const header = formatStyledHeader({
1745
+ command: "db init",
1746
+ description: "Bootstrap a database to match the current contract",
1747
+ url: "https://pris.ly/db-init",
1748
+ details,
1749
+ flags
1750
+ });
1751
+ console.log(header);
1752
+ }
1753
+ let contractJsonContent;
1754
+ try {
1755
+ contractJsonContent = await readFile(contractPathAbsolute, "utf-8");
1756
+ } catch (error) {
1757
+ if (error instanceof Error && error.code === "ENOENT") {
1758
+ return notOk3(
1759
+ errorFileNotFound(contractPathAbsolute, {
1760
+ why: `Contract file not found at ${contractPathAbsolute}`,
1761
+ fix: `Run \`prisma-next contract emit\` to generate ${contractPath}, or update \`config.contract.output\` in ${configPath}`
1762
+ })
1763
+ );
1764
+ }
1765
+ return notOk3(
1766
+ errorUnexpected2(error instanceof Error ? error.message : String(error), {
1767
+ why: `Failed to read contract file: ${error instanceof Error ? error.message : String(error)}`
1768
+ })
1769
+ );
1770
+ }
1771
+ let contractJson;
1772
+ try {
1773
+ contractJson = JSON.parse(contractJsonContent);
1774
+ } catch (error) {
1775
+ return notOk3(
1776
+ errorContractValidationFailed(
1777
+ `Contract JSON is invalid: ${error instanceof Error ? error.message : String(error)}`,
1778
+ { where: { path: contractPathAbsolute } }
1779
+ )
1780
+ );
1781
+ }
1782
+ const dbConnection = options.db ?? config.db?.connection;
1783
+ if (!dbConnection) {
1784
+ return notOk3(
1785
+ errorDatabaseConnectionRequired({
1786
+ why: `Database connection is required for db init (set db.connection in ${configPath}, or pass --db <url>)`
1787
+ })
1788
+ );
1789
+ }
1790
+ if (!config.driver) {
1791
+ return notOk3(errorDriverRequired({ why: "Config.driver is required for db init" }));
1792
+ }
1793
+ if (!config.target.migrations) {
1794
+ return notOk3(
1795
+ errorTargetMigrationNotSupported({
1796
+ why: `Target "${config.target.id}" does not support migrations`
1797
+ })
1798
+ );
1799
+ }
1800
+ const client = createControlClient({
1801
+ family: config.family,
1802
+ target: config.target,
1803
+ adapter: config.adapter,
1804
+ driver: config.driver,
1805
+ extensionPacks: config.extensionPacks ?? []
1806
+ });
1807
+ const onProgress = createProgressAdapter({ flags });
1808
+ try {
1809
+ const result = await client.dbInit({
1810
+ contractIR: contractJson,
1811
+ mode: options.plan ? "plan" : "apply",
1812
+ connection: dbConnection,
1813
+ onProgress
1814
+ });
1815
+ if (!result.ok) {
1816
+ return notOk3(mapDbInitFailure(result.failure));
1817
+ }
1818
+ const profileHash = result.value.marker?.profileHash;
1819
+ const dbInitResult = {
1820
+ ok: true,
1821
+ mode: result.value.mode,
1822
+ plan: {
1823
+ targetId: config.target.targetId,
1824
+ destination: {
1825
+ coreHash: result.value.marker?.coreHash ?? "",
1826
+ ...profileHash ? { profileHash } : {}
1827
+ },
1828
+ operations: result.value.plan.operations.map((op) => ({
1829
+ id: op.id,
1830
+ label: op.label,
1831
+ operationClass: op.operationClass
1832
+ }))
1833
+ },
1834
+ ...result.value.execution ? {
1835
+ execution: {
1836
+ operationsPlanned: result.value.execution.operationsPlanned,
1837
+ operationsExecuted: result.value.execution.operationsExecuted
1838
+ }
1839
+ } : {},
1840
+ ...result.value.marker ? {
1841
+ marker: {
1842
+ coreHash: result.value.marker.coreHash,
1843
+ ...result.value.marker.profileHash ? { profileHash: result.value.marker.profileHash } : {}
1844
+ }
1845
+ } : {},
1846
+ summary: result.value.summary,
1847
+ timings: { total: Date.now() - startTime }
1848
+ };
1849
+ return ok3(dbInitResult);
1850
+ } catch (error) {
1851
+ if (CliStructuredError.is(error)) {
1852
+ return notOk3(error);
1853
+ }
1854
+ return notOk3(
1855
+ errorUnexpected2(error instanceof Error ? error.message : String(error), {
1856
+ why: `Unexpected error during db init: ${error instanceof Error ? error.message : String(error)}`
1857
+ })
1858
+ );
1859
+ } finally {
1860
+ await client.close();
1861
+ }
1862
+ }
1281
1863
  function createDbInitCommand() {
1282
1864
  const command = new Command2("init");
1283
1865
  setCommandDescriptions(
@@ -1293,275 +1875,18 @@ function createDbInitCommand() {
1293
1875
  }).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
1876
  const flags = parseGlobalFlags(options);
1295
1877
  const startTime = Date.now();
1296
- const result = await performAction(async () => {
1297
- if (flags.json === "ndjson") {
1298
- throw errorJsonFormatNotSupported({
1878
+ if (flags.json === "ndjson") {
1879
+ const result2 = notOk3(
1880
+ errorJsonFormatNotSupported({
1299
1881
  command: "db init",
1300
1882
  format: "ndjson",
1301
1883
  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 dbConnection = options.db ?? config.db?.connection;
1352
- if (!dbConnection) {
1353
- throw errorDatabaseConnectionRequired({
1354
- why: `Database connection is required for db init (set db.connection 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(dbConnection), {
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 = typeof dbConnection === "string" ? redactDatabaseUrl(dbConnection) : void 0;
1377
- throw errorRuntime("Database connection failed", {
1378
- why: message,
1379
- fix: "Verify the database connection, 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
- });
1884
+ })
1885
+ );
1886
+ const exitCode2 = handleResult(result2, flags);
1887
+ process.exit(exitCode2);
1888
+ }
1889
+ const result = await executeDbInitCommand(options, flags, startTime);
1565
1890
  const exitCode = handleResult(result, flags, (dbInitResult) => {
1566
1891
  if (flags.json === "object") {
1567
1892
  console.log(formatDbInitJson(dbInitResult));