@prisma/orm-toolchain 8.0.0-rc.1-dev.38 → 8.0.0-rc.1-dev.39

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 (56) hide show
  1. package/dist/bin__prisma-next.mjs +8 -301
  2. package/dist/bin__prisma-next.mjs.map +1 -1
  3. package/dist/cli.mjs +1944 -256
  4. package/dist/cli.mjs.map +1 -1
  5. package/dist/cli__commands__migrate.mjs +1 -1
  6. package/dist/cli__commands__migration-check.d.mts +1 -1
  7. package/dist/cli__commands__migration-check.mjs +1 -1
  8. package/dist/cli__commands__migration-graph.d.mts +1 -1
  9. package/dist/cli__commands__migration-graph.mjs +1 -1
  10. package/dist/cli__commands__migration-list.d.mts +1 -1
  11. package/dist/cli__commands__migration-list.mjs +1 -1
  12. package/dist/cli__commands__migration-log.d.mts +1 -1
  13. package/dist/cli__commands__migration-log.mjs +1 -1
  14. package/dist/cli__commands__migration-show.d.mts +1 -1
  15. package/dist/cli__commands__migration-status.d.mts +1 -1
  16. package/dist/cli__commands__migration-status.mjs +2 -2
  17. package/dist/cli__control-api.d.mts +1 -1
  18. package/dist/cli__control-api.mjs +1 -1
  19. package/dist/cli__init-output.d.mts +1 -1
  20. package/dist/cli__init-output.mjs +1 -1
  21. package/dist/code-templates-CSLH8721-CWRA4Lru.mjs +295 -0
  22. package/dist/code-templates-CSLH8721-CWRA4Lru.mjs.map +1 -0
  23. package/dist/init-CcJxAIeS-CITTa8YU.mjs +800 -0
  24. package/dist/init-CcJxAIeS-CITTa8YU.mjs.map +1 -0
  25. package/dist/{migrate-CgSnzAUm-aGstaO5N.mjs → migrate-BVEyoBor-CN_EOCzx.mjs} +3 -3
  26. package/dist/{migrate-CgSnzAUm-aGstaO5N.mjs.map → migrate-BVEyoBor-CN_EOCzx.mjs.map} +1 -1
  27. package/dist/{migrate-failure-CnoUcDms-taa9pfd7.mjs → migrate-failure-CKMEnA_x-ByHkYjn5.mjs} +3 -3
  28. package/dist/{migrate-failure-CnoUcDms-taa9pfd7.mjs.map → migrate-failure-CKMEnA_x-ByHkYjn5.mjs.map} +1 -1
  29. package/dist/{migration-graph-D2dbLCkl-CCHL0aDA.mjs → migration-graph-D4sgTiXa-BLtiLIk7.mjs} +4 -4
  30. package/dist/{migration-graph-D2dbLCkl-CCHL0aDA.mjs.map → migration-graph-D4sgTiXa-BLtiLIk7.mjs.map} +1 -1
  31. package/dist/{migration-graph-command-render-BeuEyf2B-BNH9oUB_.mjs → migration-graph-command-render-h0VdTkSM-C3rPZEw1.mjs} +17 -3
  32. package/dist/migration-graph-command-render-h0VdTkSM-C3rPZEw1.mjs.map +1 -0
  33. package/dist/{migration-graph-sections-0lKA5l1b-CTHQquu7.mjs → migration-graph-sections-B8WSWSkJ-CyaRo0T6.mjs} +3 -3
  34. package/dist/{migration-graph-sections-0lKA5l1b-CTHQquu7.mjs.map → migration-graph-sections-B8WSWSkJ-CyaRo0T6.mjs.map} +1 -1
  35. package/dist/{migration-list-DCx0KOFp-B3MiGnpa.mjs → migration-list-BIL-re1m-CZY4m3d0.mjs} +3 -3
  36. package/dist/{migration-list-DCx0KOFp-B3MiGnpa.mjs.map → migration-list-BIL-re1m-CZY4m3d0.mjs.map} +1 -1
  37. package/dist/{migration-log-CJ6_9_uj-uWyc3vlC.mjs → migration-log-BdBKYAUW-DTPxKZtD.mjs} +4 -4
  38. package/dist/{migration-log-CJ6_9_uj-uWyc3vlC.mjs.map → migration-log-BdBKYAUW-DTPxKZtD.mjs.map} +1 -1
  39. package/dist/{migration-log-table-DRiOnvzf-zTQBThqe.mjs → migration-log-table-eES9lPjv-BCFm8qJr.mjs} +3 -3
  40. package/dist/{migration-log-table-DRiOnvzf-zTQBThqe.mjs.map → migration-log-table-eES9lPjv-BCFm8qJr.mjs.map} +1 -1
  41. package/dist/{migration-status-S88DjM8c-DLHnCKfW.mjs → migration-status-DzPVkkB3-BA7jjITd.mjs} +5 -4
  42. package/dist/{migration-status-S88DjM8c-DLHnCKfW.mjs.map → migration-status-DzPVkkB3-BA7jjITd.mjs.map} +1 -1
  43. package/dist/{output-DWNYqZGc-D2sRgLAm.mjs → output-DP3woAjV-BxCr9nHA.mjs} +21 -11
  44. package/dist/output-DP3woAjV-BxCr9nHA.mjs.map +1 -0
  45. package/dist/{init-Bco4QkKJ-DqUZVrBU.mjs → redact-secrets-kQ0KKV1u-MnVSKe-u.mjs} +740 -1516
  46. package/dist/redact-secrets-kQ0KKV1u-MnVSKe-u.mjs.map +1 -0
  47. package/dist/{schemas-Bm2e3Hk1-qLKF0B36.d.mts → schemas-BGgjiKV0-Rh8RlMCl.d.mts} +4 -2
  48. package/dist/schemas-BGgjiKV0-Rh8RlMCl.d.mts.map +1 -0
  49. package/dist/{schemas-BMkqaqvM-CLaqlat4.mjs → schemas-CYSD83Cu-Bff4PPoi.mjs} +3 -2
  50. package/dist/schemas-CYSD83Cu-Bff4PPoi.mjs.map +1 -0
  51. package/package.json +13 -13
  52. package/dist/init-Bco4QkKJ-DqUZVrBU.mjs.map +0 -1
  53. package/dist/migration-graph-command-render-BeuEyf2B-BNH9oUB_.mjs.map +0 -1
  54. package/dist/output-DWNYqZGc-D2sRgLAm.mjs.map +0 -1
  55. package/dist/schemas-BMkqaqvM-CLaqlat4.mjs.map +0 -1
  56. package/dist/schemas-Bm2e3Hk1-qLKF0B36.d.mts.map +0 -1
package/dist/cli.mjs CHANGED
@@ -1,47 +1,55 @@
1
1
  import { i as loadConfig } from "./exports-BZLNbtkY.mjs";
2
- import { I as requireLiveDatabase, L as runCommandAction, M as errorTargetMigrationNotSupported, O as errorRunnerFailed, P as errorUnexpected, a as errorConfigValidation$1, b as errorMigrationPlanningFailed, c as errorContractValidationFailed, d as errorDriverRequired, f as errorFileNotFound, g as errorLegendHumanOnly, k as errorRuntime$1, l as errorDatabaseConnectionRequired, o as errorConsentPlanMismatch, t as CliStructuredError$1$1, u as errorDestructiveChanges, x as errorNoMigrations, y as errorMigrationPackageNotFound } from "./cli-errors-BG6bItjG-DwdKIeGz.mjs";
2
+ import { I as requireLiveDatabase, L as runCommandAction, M as errorTargetMigrationNotSupported, O as errorRunnerFailed, P as errorUnexpected, a as errorConfigValidation$1, b as errorMigrationPlanningFailed, c as errorContractValidationFailed, d as errorDriverRequired, f as errorFileNotFound, g as errorLegendHumanOnly, k as errorRuntime$1, l as errorDatabaseConnectionRequired, o as errorConsentPlanMismatch, r as chooseAction, t as CliStructuredError$1$1, u as errorDestructiveChanges, x as errorNoMigrations, y as errorMigrationPackageNotFound } from "./cli-errors-BG6bItjG-DwdKIeGz.mjs";
3
3
  import { r as getEmittedArtifactPaths } from "./exports-BCLg4CiV-05kuwcb0.mjs";
4
4
  import { n as executeContractEmit } from "./contract-emit-CZQZ1i4z-p9LSGmmQ.mjs";
5
5
  import { h as renderPreviewStatement, m as previewBlockHeader } from "./terminal-ui-Cqz4LVL7-B0mcVdLy.mjs";
6
6
  import { c as spaceMigrationDirectory } from "./verify-contract-spaces-DhJTb0zM-_oh6W6lp.mjs";
7
- import { S as targetSupportsMigrations, f as maskConnectionUrl, n as closeQuietly, v as sanitizeErrorMessage } from "./command-helpers-CwLvFwMB-BWwUQhlT.mjs";
7
+ import { S as targetSupportsMigrations, f as maskConnectionUrl, h as readContractEnvelope, n as closeQuietly, v as sanitizeErrorMessage } from "./command-helpers-CwLvFwMB-BWwUQhlT.mjs";
8
8
  import { t as createContractEmitCommand } from "./contract-emit-BQFpcb2A-BwT9iVdb.mjs";
9
9
  import { n as executeFormat, t as createFormatCommand } from "./format-cbJY7l6v-CjMUDTAs.mjs";
10
- import { a as loadContractSpaceAggregateForCli, r as buildReadAggregate, s as refuseContractSpaceIntegrity, u as toDeclaredExtensionsFromRaw } from "./contract-space-aggregate-loader-CoLTf3Ce-pQDru2w0.mjs";
10
+ import { c as targetLabel, i as scaffoldSpecifierResolverFor, l as targetPackageName, n as dbFile, o as starterSchema, r as defaultSchemaPath, t as configFile } from "./code-templates-CSLH8721-CWRA4Lru.mjs";
11
+ import { a as loadContractSpaceAggregateForCli, i as loadContractRawSafely, l as refusePackageCorruptionOnAggregate, r as buildReadAggregate, s as refuseContractSpaceIntegrity, u as toDeclaredExtensionsFromRaw } from "./contract-space-aggregate-loader-CoLTf3Ce-pQDru2w0.mjs";
11
12
  import { t as createControlClient } from "./client-DU9qtBxW-BjVHiKzE.mjs";
12
13
  import { a as readContractIR, o as resolveRefAdvancementFields, t as advanceRefSafely } from "./ref-advancement-DaZBzm7m-WoqnFAyr.mjs";
13
14
  import { t as mapDbInitFailure } from "./db-init-failure-BmCcHUfc-BqDxyrJE.mjs";
14
15
  import { t as resolveContractRefToSnapshot } from "./contract-snapshot-resolution-DvZvbNcZ-DHFCWe_k.mjs";
15
16
  import { t as mapCaughtMigrationError } from "./caught-errors-DLPzfzIW-CQ9BoXD-.mjs";
16
17
  import { t as mapContractAtError } from "./contract-at-errors-Dptf8RE_-B5gr04Sc.mjs";
17
- import { i as refuseUnknownInvariants, n as refuseMarkerOutsideGraph } from "./invariants-1zL5-9Mc-BCeOLxd_.mjs";
18
+ import { i as refuseUnknownInvariants, n as refuseMarkerOutsideGraph, r as refuseMissingInvariantPath, t as hasMigrationPath } from "./invariants-1zL5-9Mc-BCeOLxd_.mjs";
18
19
  import { t as executeMigrateShowPlan } from "./migrate-show-kVUG1xdo-bVTW8tu4.mjs";
19
20
  import { n as resolveMigrationRef, t as resolveContractRef } from "./ref-resolution-DAK4OK3x-B-8ZZc0f.mjs";
20
- import { n as migrationSpaceListEntriesFromAggregate, r as runMigrationList } from "./migration-list-BQTfSGbT-DoNqbNsu.mjs";
21
- import { S as toneSpans, b as renderMigrationListWithStyle, d as createToneMigrationListStyler, n as TONE_MIGRATION_GRAPH_PALETTE, v as renderMigrationGraphLegend, x as toneDrawing } from "./migration-graph-command-render-BeuEyf2B-BNH9oUB_.mjs";
22
- import { n as migrateShowRunListRows, r as renderMigrateShowGraph, t as mapMigrateFailure } from "./migrate-failure-CnoUcDms-taa9pfd7.mjs";
21
+ import { n as migrationSpaceListEntriesFromAggregate, r as runMigrationList, t as listRefsByContractHash } from "./migration-list-BQTfSGbT-DoNqbNsu.mjs";
22
+ import { C as toneSpans, S as toneDrawing, b as renderMigrationGraphSpaceTrees, d as createToneMigrationListStyler, m as indentMigrationGraphTreeBlock, n as TONE_MIGRATION_GRAPH_PALETTE, v as renderMigrationGraphLegend, x as renderMigrationListWithStyle } from "./migration-graph-command-render-h0VdTkSM-C3rPZEw1.mjs";
23
+ import { n as migrateShowRunListRows, r as renderMigrateShowGraph, t as mapMigrateFailure } from "./migrate-failure-CKMEnA_x-ByHkYjn5.mjs";
23
24
  import { n as looksLikePath, r as resolveAppTargetPath, t as findPackageByDirPath } from "./migration-path-target-BNTSTPAc-D7dQ4K7x.mjs";
24
25
  import { t as buildMigrationSpaceGraphEntries } from "./migration-graph-B30X5398-BH3zB9Mn.mjs";
25
- import { n as renderMigrationGraphDot, r as renderMigrationGraphSections, t as buildMigrationGraphTreeSections } from "./migration-graph-sections-0lKA5l1b-CTHQquu7.mjs";
26
- import { a as sortLedgerEntries, i as serializeLedgerEntriesForJson, n as formatLedgerAppliedAt, o as styleHashTransition, t as MIGRATION_LOG_EMPTY_MESSAGE } from "./migration-log-table-DRiOnvzf-zTQBThqe.mjs";
26
+ import { n as renderMigrationGraphDot, r as renderMigrationGraphSections, t as buildMigrationGraphTreeSections } from "./migration-graph-sections-B8WSWSkJ-CyaRo0T6.mjs";
27
+ import { a as sortLedgerEntries, i as serializeLedgerEntriesForJson, n as formatLedgerAppliedAt, o as styleHashTransition, t as MIGRATION_LOG_EMPTY_MESSAGE } from "./migration-log-table-eES9lPjv-BCFm8qJr.mjs";
28
+ import { t as executeMigrationNewCommand } from "./migration-new-Bp5dPKnF-D-Fb6vEp.mjs";
29
+ import { t as executeMigrationPlanCommand } from "./migration-plan-C6KrFHJT-CRkTsE7L.mjs";
30
+ import { a as statusForMigrationHash, i as readMigrationRefs, n as deriveStatusEdgeAnnotations, r as originHashForStatus, t as appliedHashesFromLedger } from "./refs-fQLFcMoq-aTBeUU9H.mjs";
27
31
  import { n as executeRefListCommand, r as executeRefSetCommand, t as executeRefDeleteCommand } from "./ref-C8oaaeky-ColNkWpr.mjs";
32
+ import { A as isRecognisedPnpmResolutionError, B as removeDependency, C as findStaleArtifacts, E as formatRunCommand, F as mergeTsConfig, G as validateSchemaPath, H as resolveAuthoring, I as minimalProjectReadmeMd, L as probeServerVersion, M as mergeGitattributes, N as mergeGitignore, O as formatSkillSourceUrl, P as mergePackageScripts, R as quickReferenceMd, S as errorInitWriteFailed, U as resolveProjectSkillInstallCommands, V as requiredGitattributesLines, W as resolveTarget, _ as errorInitProbeFailed, a as TsConfigParseError, b as errorInitStrictProbeWithoutProbe, d as envFileContent, g as errorInitMissingFlags, h as errorInitInvalidTsconfig, i as REQUIRED_SCRIPTS, j as legacySkillDirs, l as ensureEsmModuleType, m as errorInitInvalidManifest, n as DEFAULT_SKILL_SOURCES, o as buildCatalogWarnings, r as MIN_SERVER_VERSION, s as defaultTsConfig, t as DEFAULT_SKILL_AGENTS, u as envExampleContent, x as errorInitUserAborted, z as redactSecrets } from "./redact-secrets-kQ0KKV1u-MnVSKe-u.mjs";
33
+ import { n as buildNextSteps, t as InitOutputSchema } from "./output-DP3woAjV-BxCr9nHA.mjs";
28
34
  import { CliStructuredError } from "@prisma/orm-framework/errors/control";
29
35
  import { ifDefined } from "@prisma/orm-framework/utils/defined";
30
- import { basename, dirname, join, normalize, relative, resolve } from "pathe";
31
- import { existsSync, unlinkSync, writeFileSync } from "node:fs";
32
- import { DOCS_BASE, isStructuredError } from "@prisma/orm-framework/utils/structured-error";
36
+ import { basename, dirname, isAbsolute, join, normalize, relative, resolve } from "pathe";
37
+ import { existsSync, mkdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
38
+ import { DOCS_BASE, docsUrlFor, isStructuredError } from "@prisma/orm-framework/utils/structured-error";
33
39
  import { blindCast, castAs } from "@prisma/orm-framework/utils/casts";
34
40
  import { InternalError, assertNever } from "@prisma/orm-framework/utils/internal-error";
35
41
  import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
36
42
  import { APP_SPACE_ID, createControlStack } from "@prisma/orm-framework/components/control";
43
+ import { type } from "arktype";
37
44
  import { collectConfigIssues } from "@prisma/orm-framework/config/config-validation";
38
45
  import { tmpdir } from "node:os";
46
+ import { detect } from "package-manager-detector/detect";
39
47
  import { printPsl } from "@prisma/orm-framework/psl-printer";
40
48
  import { defineCommand, defineCommandFamily, defineConfigSection, flag, positional } from "@prisma/cli-engine";
41
49
  import { CliStructuredError as CliStructuredError$1, notOk, ok } from "@prisma/cli-engine/protocol";
42
50
  import { pathToFileURL } from "node:url";
43
51
  import { build } from "esbuild";
44
- //#region ../../../1-framework/3-tooling/cli/dist/load-config-pL46nF5F.mjs
52
+ //#region ../../../1-framework/3-tooling/cli/dist/load-config-DWFCZvpn.mjs
45
53
  const MISSING_CONFIG_ACTION = {
46
54
  kind: "run-command",
47
55
  label: "Create a config file",
@@ -608,7 +616,7 @@ function perSpaceBlocks(perSpace, mode) {
608
616
  roots: spaceNodes(perSpace, mode)
609
617
  }, ...destructiveWarningBlocks(hasDestructive)];
610
618
  }
611
- function operationBlocks$1(result) {
619
+ function operationBlocks$2(result) {
612
620
  const perSpace = result.perSpace;
613
621
  if (perSpace !== void 0 && perSpace.length > 0) return perSpaceBlocks(perSpace, result.mode);
614
622
  if (result.plan.operations.length === 0) return [];
@@ -619,7 +627,7 @@ function operationBlocks$1(result) {
619
627
  }, ...destructiveWarningBlocks(hasDestructive)];
620
628
  }
621
629
  /** The statements a database would run, printed verbatim rather than laid out. */
622
- function previewBlocks$1(preview) {
630
+ function previewBlocks$2(preview) {
623
631
  if (preview === void 0) return [];
624
632
  const header = {
625
633
  kind: "summary",
@@ -666,7 +674,7 @@ function applySummaryText(result) {
666
674
  if (executed === 0) return `Database already matches contract${across}`;
667
675
  return `Applied ${executed} operation(s)${across}`;
668
676
  }
669
- function planBlocks(result) {
677
+ function planBlocks$1(result) {
670
678
  const planned = result.plannedAdvanceRef;
671
679
  return [
672
680
  {
@@ -675,7 +683,7 @@ function planBlocks(result) {
675
683
  text: planSummaryText(result)
676
684
  },
677
685
  ...plannerWarningBlocks(result),
678
- ...operationBlocks$1(result),
686
+ ...operationBlocks$2(result),
679
687
  {
680
688
  kind: "fields",
681
689
  rows: [{
@@ -694,7 +702,7 @@ function planBlocks(result) {
694
702
  tone: "identifier"
695
703
  }]
696
704
  }],
697
- ...previewBlocks$1(result.plan.preview),
705
+ ...previewBlocks$2(result.plan.preview),
698
706
  {
699
707
  kind: "summary",
700
708
  status: "info",
@@ -736,7 +744,7 @@ function applyBlocks(result) {
736
744
  text: applySummaryText(result)
737
745
  },
738
746
  ...plannerWarningBlocks(result),
739
- ...operationBlocks$1(result),
747
+ ...operationBlocks$2(result),
740
748
  ...fallbackMarkerBlocks(result),
741
749
  ...advanced === null || advanced === void 0 ? [] : [{
742
750
  kind: "summary",
@@ -750,7 +758,7 @@ function applyBlocks(result) {
750
758
  }
751
759
  /** What a `db init` / `db update` result looks like in human mode. */
752
760
  function migrationResultBlocks(result) {
753
- return result.mode === "plan" ? planBlocks(result) : applyBlocks(result);
761
+ return result.mode === "plan" ? planBlocks$1(result) : applyBlocks(result);
754
762
  }
755
763
  /** Where the user goes next, as typed actions rather than trailing prose. */
756
764
  function migrationResultNextActions(result, applyCommand) {
@@ -769,6 +777,16 @@ function appMigrationsDirFor(config, cwd) {
769
777
  return spaceMigrationDirectory(migrationsDirFor(config, cwd), APP_SPACE_ID);
770
778
  }
771
779
  /**
780
+ * The config file an operation should anchor its project paths on. The engine
781
+ * loads the config and hands a handler the value but not the path, and
782
+ * `--config` is an engine flag the handler never sees, so a handler names the
783
+ * invocation directory's file. That equals the loaded file for every
784
+ * invocation whose config sits in the invocation directory.
785
+ */
786
+ function projectConfigPathFor(cwd) {
787
+ return resolve(cwd, "prisma-next.config.ts");
788
+ }
789
+ /**
772
790
  * Where refs live. The framework keeps them under the app subspace rather than
773
791
  * at the migrations root.
774
792
  */
@@ -842,7 +860,7 @@ async function prepareMigrationRun(inputs) {
842
860
  refsDir: appRefsDirFor(config, cwd)
843
861
  });
844
862
  }
845
- function initPresentations(inputs) {
863
+ function initPresentations$1(inputs) {
846
864
  const { document, database, dryRun } = inputs;
847
865
  return {
848
866
  human: () => [{
@@ -974,7 +992,7 @@ function createDbInitCommand(createClient) {
974
992
  severity: "verbose",
975
993
  text: `Total time: ${document.timings.total}ms`
976
994
  });
977
- return ok(ctx.present({ data: document }, initPresentations({
995
+ return ok(ctx.present({ data: document }, initPresentations$1({
978
996
  document,
979
997
  contractPath: prepared.value.contractDisplayPath,
980
998
  database: prepared.value.database,
@@ -1478,62 +1496,122 @@ function createFormatCommand$1(execute = executeFormat) {
1478
1496
  });
1479
1497
  }
1480
1498
  const formatCommand = createFormatCommand$1();
1481
- const STATUS_ACTION = runCommandAction("Check every space against the database", "prisma-next migration status");
1499
+ /** The invocation that finishes what a failed `init` phase started. */
1500
+ const EMIT_COMMAND = "prisma-next contract emit";
1482
1501
  /**
1483
- * The route the migration will take. The graph is a drawing — the engine cannot
1484
- * derive a lane gutter and the ordered list under it is drawn with the same
1485
- * widths so every arrow lands in one column.
1502
+ * The command's own record of a phase that failed after the scaffold was
1503
+ * written. The scaffold is on disk and the run has a result to report, so each
1504
+ * of these rides a completed envelope as a finding rather than replacing it.
1486
1505
  */
1487
- function showPresentations$1(inputs) {
1488
- const { document, graph, runList } = inputs;
1489
- const count = document.migrations.length;
1506
+ function initFinding(code, summary, parts) {
1490
1507
  return {
1491
- human: () => [
1492
- {
1493
- kind: "fields",
1494
- rail: true,
1495
- rows: [
1496
- {
1497
- label: "migrations",
1498
- value: inputs.migrationsDir
1499
- },
1500
- ...inputs.database === void 0 ? [] : [{
1501
- label: "database",
1502
- value: inputs.database
1503
- }],
1504
- ...inputs.from === void 0 ? [] : [{
1505
- label: "from",
1506
- value: inputs.from
1507
- }],
1508
- ...inputs.to === void 0 ? [] : [{
1509
- label: "to",
1510
- value: inputs.to
1511
- }]
1512
- ]
1513
- },
1514
- ...graph.length === 0 ? [] : [{
1515
- kind: "drawing",
1516
- lines: toneDrawing(graph)
1517
- }],
1518
- ...count === 0 ? [{
1519
- kind: "summary",
1520
- status: "info",
1521
- text: document.summary
1522
- }] : [{
1523
- kind: "summary",
1524
- status: "info",
1525
- text: `The following ${count} migration${count === 1 ? "" : "s"} will run:`
1526
- }, {
1527
- kind: "drawing",
1528
- lines: toneDrawing(runList.join("\n"))
1529
- }]
1530
- ],
1531
- json: () => document
1508
+ code,
1509
+ severity: "error",
1510
+ summary,
1511
+ why: parts.why,
1512
+ nextActions: parts.nextActions,
1513
+ meta: parts.meta,
1514
+ docsUrl: docsUrlFor(code)
1532
1515
  };
1533
1516
  }
1534
- function applyPresentations(inputs) {
1517
+ /**
1518
+ * The dependency install failed. The engine composed and announced the command
1519
+ * line, so its own next action — which carries the redacted spelling — is the
1520
+ * one that tells the user what to run; `init` adds the emit that was waiting
1521
+ * on it.
1522
+ */
1523
+ function installFailedFinding(failure, filesWritten) {
1524
+ return initFinding("CLI.INIT_INSTALL_FAILED", "Failed to install dependencies", {
1525
+ why: failure.why ?? failure.message,
1526
+ nextActions: [...failure.nextActions, runCommandAction("Emit the contract once the dependencies are installed", EMIT_COMMAND)],
1527
+ meta: {
1528
+ filesWritten,
1529
+ install: failure.meta ?? {}
1530
+ }
1531
+ });
1532
+ }
1533
+ /** The first emit failed against the freshly written scaffold. */
1534
+ function emitFailedFinding(cause, filesWritten) {
1535
+ return initFinding("CLI.INIT_EMIT_FAILED", "Failed to emit contract", {
1536
+ why: `\`${EMIT_COMMAND}\` failed: ${cause}`,
1537
+ nextActions: [chooseAction("Fix the problem the contract source reports, then emit again"), runCommandAction("Emit the contract", EMIT_COMMAND)],
1538
+ meta: {
1539
+ filesWritten,
1540
+ cause
1541
+ }
1542
+ });
1543
+ }
1544
+ /**
1545
+ * An agent-skill install failed; the project itself is complete without it.
1546
+ * The remedy is the skill-install commands themselves, which run against any
1547
+ * directory — advising a re-run of `init` would put the user's schema at risk
1548
+ * to fix something `init` no longer needs to be involved in.
1549
+ */
1550
+ function skillInstallFailedFinding(failure, filesWritten, skillCommands) {
1551
+ return initFinding("CLI.INIT_SKILL_INSTALL_FAILED", "Failed to install Prisma Next skills", {
1552
+ why: failure.why ?? failure.message,
1553
+ nextActions: [...failure.nextActions, ...skillCommands.map((command) => runCommandAction("Install the Prisma Next skills", command))],
1554
+ meta: {
1555
+ filesWritten,
1556
+ skillInstall: failure.meta ?? {}
1557
+ }
1558
+ });
1559
+ }
1560
+ function fileNodes(paths) {
1561
+ return paths.map((path) => ({
1562
+ label: path,
1563
+ tone: "identifier"
1564
+ }));
1565
+ }
1566
+ function scaffoldTree(document) {
1567
+ const roots = [{
1568
+ label: "written",
1569
+ tone: "heading",
1570
+ children: fileNodes(document.filesWritten)
1571
+ }];
1572
+ if (document.filesDeleted.length > 0) roots.push({
1573
+ label: "removed (stale artifacts and retired skill directories)",
1574
+ tone: "heading",
1575
+ children: fileNodes(document.filesDeleted)
1576
+ });
1577
+ const installed = document.packagesInstalled;
1578
+ if (installed.status === "installed") roots.push({
1579
+ label: "installed",
1580
+ tone: "heading",
1581
+ children: [...fileNodes(installed.deps), ...installed.devDeps.map((dep) => ({
1582
+ label: `${dep} (dev)`,
1583
+ tone: "identifier"
1584
+ }))]
1585
+ });
1586
+ return {
1587
+ kind: "tree",
1588
+ roots
1589
+ };
1590
+ }
1591
+ /**
1592
+ * The scaffold's follow-up steps, typed. `nextSteps` in the result document
1593
+ * keeps its numbered prose for consumers that already read it; these are the
1594
+ * same advice in the shape an agent can act on.
1595
+ */
1596
+ function buildInitNextActions(inputs) {
1597
+ const actions = [chooseAction("Set DATABASE_URL in your environment (export it or add it to .env)")];
1598
+ if (!inputs.contractEmitted) actions.push(runCommandAction("Emit the contract", EMIT_COMMAND));
1599
+ actions.push({
1600
+ kind: "edit-file",
1601
+ label: `Edit your schema at ${inputs.schemaPath}, then emit again`
1602
+ });
1603
+ actions.push(chooseAction("Open prisma-next.md for a quick reference on writing your first typed query"));
1604
+ if (inputs.skillRegistered) actions.push(chooseAction("Prisma Next skills are registered with your agent runtime — ask it to add a model, run a query, or plan a migration"));
1605
+ return actions;
1606
+ }
1607
+ const DONE_TEXT = "Done. Open prisma-next.md to get started.";
1608
+ const INCOMPLETE_TEXT = "Scaffold written. Finish the steps above to complete setup.";
1609
+ /**
1610
+ * `init` writes files rather than data another program reads, so it supplies
1611
+ * no `stdout` payload: human mode writes nothing to stdout.
1612
+ */
1613
+ function initPresentations(inputs) {
1535
1614
  const { document } = inputs;
1536
- const advanced = document.advancedRef;
1537
1615
  return {
1538
1616
  human: () => [
1539
1617
  {
@@ -1541,133 +1619,985 @@ function applyPresentations(inputs) {
1541
1619
  rail: true,
1542
1620
  rows: [
1543
1621
  {
1544
- label: "migrations",
1545
- value: inputs.migrationsDir
1622
+ label: "target",
1623
+ value: document.target
1546
1624
  },
1547
- ...inputs.database === void 0 ? [] : [{
1548
- label: "database",
1549
- value: inputs.database
1550
- }],
1551
- ...inputs.to === void 0 ? [] : [{
1552
- label: "to",
1553
- value: inputs.to
1554
- }]
1625
+ {
1626
+ label: "authoring",
1627
+ value: document.authoring
1628
+ },
1629
+ {
1630
+ label: "schema",
1631
+ value: document.schemaPath
1632
+ }
1555
1633
  ]
1556
1634
  },
1557
- {
1635
+ scaffoldTree(document),
1636
+ inputs.complete ? {
1558
1637
  kind: "summary",
1559
1638
  status: "ok",
1560
- text: document.summary
1561
- },
1562
- ...document.perSpace.length === 0 ? [] : perSpaceBlocks(document.perSpace, "apply"),
1563
- ...advanced === null || advanced === void 0 ? [] : [{
1639
+ text: DONE_TEXT
1640
+ } : {
1564
1641
  kind: "summary",
1565
- status: "ok",
1566
- text: [{ text: `Advanced ref "${advanced.name}" → ` }, {
1567
- text: advanced.hash,
1568
- tone: "identifier"
1569
- }]
1570
- }]
1642
+ status: "warn",
1643
+ text: INCOMPLETE_TEXT
1644
+ }
1571
1645
  ],
1572
1646
  json: () => document,
1573
- next: () => [STATUS_ACTION]
1647
+ next: () => inputs.nextActions
1574
1648
  };
1575
1649
  }
1576
1650
  /**
1577
- * `--to` as a contract the app graph knows. A ref target keeps the invariants
1578
- * the ref declares; a bare hash carries none. Omitting `--to` targets the
1579
- * emitted contract, which needs no resolution at all.
1651
+ * Emits the contract for the project `init` has just scaffolded.
1652
+ *
1653
+ * The config file is the one this run wrote, so it is read here rather than
1654
+ * through `needs.config`: `init` runs where no config exists yet, and the
1655
+ * engine loads a section before the handler starts. The emitter and the config
1656
+ * loader are imported at execution time so a run that skips the install — and
1657
+ * therefore skips the emit — never pays for either.
1580
1658
  */
1581
- function resolveRequestedTarget(to, refs, graph) {
1582
- if (to === void 0) return ok({
1583
- entry: void 0,
1584
- refName: void 0
1585
- });
1586
- const resolved = resolveContractRef(to, {
1587
- graph,
1588
- refs
1659
+ async function emitScaffoldedContract(ctx) {
1660
+ const { executeContractEmit } = await import("./contract-emit-Bzt11N4v-BQ2Pr8KG.mjs");
1661
+ const { loadConfigForSections } = await import("./exports-BZLNbtkY.mjs").then((n) => n.t);
1662
+ const configPath = join(ctx.cwd, "prisma-next.config.ts");
1663
+ const loaded = await loadConfigForSections(configPath, [
1664
+ "contract",
1665
+ "family",
1666
+ "target",
1667
+ "adapter",
1668
+ "extensions"
1669
+ ]);
1670
+ if (!loaded.ok) throw loaded.failure;
1671
+ await executeContractEmit({
1672
+ config: loaded.value,
1673
+ cwd: ctx.cwd,
1674
+ configPath
1589
1675
  });
1590
- if (!resolved.ok) return notOk(normalizeError(resolved.failure));
1591
- if (resolved.value.provenance.kind !== "ref") return ok({
1592
- entry: {
1593
- hash: resolved.value.hash,
1594
- invariants: []
1676
+ }
1677
+ const CONFIG_FILE = "prisma-next.config.ts";
1678
+ const QUICK_REFERENCE_FILE = "prisma-next.md";
1679
+ const ENV_EXAMPLE_FILE = ".env.example";
1680
+ /**
1681
+ * The generated files a run replaces outright, in the order it writes them.
1682
+ * Everything else the scaffold touches it merges (`tsconfig.json`,
1683
+ * `package.json`, the git files) or keeps (`.env`, `README.md`), so replacing
1684
+ * one of these is the destructive act consent is asked for.
1685
+ *
1686
+ * `.env.example` is written the same way but deliberately left out: it is a
1687
+ * conventional filename in projects that have never seen Prisma Next, and a
1688
+ * consent token demanded for it would fire on first runs. The scaffold warns
1689
+ * when it replaces one instead.
1690
+ */
1691
+ function generatedFilesInitReplaces(schemaPath) {
1692
+ return [
1693
+ schemaPath,
1694
+ CONFIG_FILE,
1695
+ join(dirname(schemaPath), "db.ts"),
1696
+ QUICK_REFERENCE_FILE
1697
+ ];
1698
+ }
1699
+ const MANAGERS = /* @__PURE__ */ new Set([
1700
+ "pnpm",
1701
+ "npm",
1702
+ "yarn",
1703
+ "bun",
1704
+ "deno"
1705
+ ]);
1706
+ function isPackageManager(name) {
1707
+ return MANAGERS.has(name);
1708
+ }
1709
+ /**
1710
+ * The manager the scaffolded quick reference and README name in their example
1711
+ * commands. It resolves the way the engine's package operations resolve theirs
1712
+ * — the project at `cwd` first, then the manager that invoked this process —
1713
+ * so the documentation `init` writes names the manager `init` drove. The
1714
+ * capability itself reports no manager back on success, which is why this is
1715
+ * worked out a second time here.
1716
+ */
1717
+ async function resolveScaffoldPackageManager(ctx) {
1718
+ const detected = await detect({ cwd: ctx.cwd });
1719
+ if (detected !== null && isPackageManager(detected.name)) return detected.name;
1720
+ const invoking = ctx.env["npm_config_user_agent"]?.split("/")[0];
1721
+ return invoking !== void 0 && isPackageManager(invoking) ? invoking : "npm";
1722
+ }
1723
+ /**
1724
+ * True when the parsed manifest declares `name` directly in `dependencies` or
1725
+ * `devDependencies`. Transitive presence is deliberately ignored: detecting it
1726
+ * needs the lockfile, and the realistic risk is a direct pin being clobbered.
1727
+ */
1728
+ function hasDirectDep(parsed, name) {
1729
+ for (const field of ["dependencies", "devDependencies"]) {
1730
+ const value = parsed[field];
1731
+ if (value !== null && typeof value === "object" && name in value) return true;
1732
+ }
1733
+ return false;
1734
+ }
1735
+ /**
1736
+ * npm package names are lowercase and URL-safe; `basename(cwd)` happily
1737
+ * returns "My Project" or ".hidden", both of which npm refuses to read.
1738
+ */
1739
+ function sanitisePackageName(raw) {
1740
+ const trimmed = raw.toLowerCase().replace(/[^a-z0-9._~-]/g, "-").replace(/^[._-]+/, "").replace(/-+/g, "-");
1741
+ return trimmed.length > 0 ? trimmed : "my-app";
1742
+ }
1743
+ /**
1744
+ * The manifest `init` writes for a directory that has none. Private, so a
1745
+ * stray publish cannot leak the placeholder, and ESM so the scaffolded import
1746
+ * attributes load under Node's loader.
1747
+ */
1748
+ function defaultPackageJsonContent(rawName) {
1749
+ return `${JSON.stringify({
1750
+ name: sanitisePackageName(rawName),
1751
+ version: "0.0.0",
1752
+ private: true,
1753
+ type: "module"
1754
+ }, null, 2)}\n`;
1755
+ }
1756
+ function hasProjectManifest(cwd) {
1757
+ return existsSync(join(cwd, "package.json")) || existsSync(join(cwd, "deno.json")) || existsSync(join(cwd, "deno.jsonc"));
1758
+ }
1759
+ /**
1760
+ * Computes every write before touching disk: reads and parses each file the
1761
+ * scaffold merges with, so a malformed manifest or tsconfig fails with the
1762
+ * user's project byte-identical to how it started.
1763
+ */
1764
+ function planScaffold(ctx) {
1765
+ const { cwd, inputs, packageManager, resolveImportSpecifier } = ctx;
1766
+ const warnings = [];
1767
+ const schemaDir = dirname(inputs.schemaPath);
1768
+ const configContractPath = isAbsolute(inputs.schemaPath) ? inputs.schemaPath : `./${inputs.schemaPath}`;
1769
+ const runPrefix = formatRunCommand(packageManager, "prisma-next", "").trimEnd();
1770
+ const files = [
1771
+ {
1772
+ path: inputs.schemaPath,
1773
+ content: starterSchema(inputs.target, inputs.authoring, resolveImportSpecifier)
1595
1774
  },
1596
- refName: void 0
1775
+ {
1776
+ path: CONFIG_FILE,
1777
+ content: configFile(inputs.target, configContractPath, resolveImportSpecifier)
1778
+ },
1779
+ {
1780
+ path: join(schemaDir, "db.ts"),
1781
+ content: dbFile(inputs.target, resolveImportSpecifier)
1782
+ },
1783
+ {
1784
+ path: QUICK_REFERENCE_FILE,
1785
+ content: quickReferenceMd(inputs.target, inputs.authoring, inputs.schemaPath, runPrefix, resolveImportSpecifier)
1786
+ },
1787
+ {
1788
+ path: ENV_EXAMPLE_FILE,
1789
+ content: envExampleContent(inputs.target)
1790
+ }
1791
+ ];
1792
+ if (existsSync(join(cwd, ENV_EXAMPLE_FILE))) warnings.push(`${ENV_EXAMPLE_FILE} already existed and was replaced with the Prisma Next template.`);
1793
+ const filesToDelete = inputs.reinit ? [...findStaleArtifacts(cwd, schemaDir)] : [];
1794
+ const dirsToDelete = legacySkillDirs().filter((rel) => existsSync(join(cwd, rel)));
1795
+ if (inputs.writeEnv) if (existsSync(join(cwd, ".env"))) warnings.push(".env already exists; leaving it untouched. Compare with .env.example for any new keys.");
1796
+ else files.push({
1797
+ path: ".env",
1798
+ content: envFileContent(inputs.target)
1597
1799
  });
1598
- const refName = resolved.value.provenance.refName;
1599
- return ok({
1600
- entry: refs[refName],
1601
- refName
1800
+ const tsconfigPath = join(cwd, "tsconfig.json");
1801
+ if (existsSync(tsconfigPath)) {
1802
+ const existing = readFileSync(tsconfigPath, "utf-8");
1803
+ let merged;
1804
+ try {
1805
+ merged = mergeTsConfig(existing);
1806
+ } catch (error) {
1807
+ if (error instanceof TsConfigParseError) throw errorInitInvalidTsconfig({
1808
+ path: "tsconfig.json",
1809
+ cause: error.message
1810
+ });
1811
+ throw error;
1812
+ }
1813
+ files.push({
1814
+ path: "tsconfig.json",
1815
+ content: merged,
1816
+ note: "Updated tsconfig.json with required compiler options."
1817
+ });
1818
+ } else files.push({
1819
+ path: "tsconfig.json",
1820
+ content: defaultTsConfig()
1602
1821
  });
1603
- }
1604
- function createMigrateCommand(createClient) {
1605
- return defineOrmCommand({
1606
- help: {
1607
- summary: "Apply planned migrations to advance the database",
1608
- description: "Walks every contract space (app + extensions) and applies pending on-disk\nmigrations in canonical order (extensions alphabetically, then app). It\nreplays the on-disk migration graph and never invents an edge. Use --to to\ntarget a specific contract (hash, ref name, or migration directory) and\n--show for a read-only preview of the route it would take.",
1609
- examples: [
1610
- "migrate",
1611
- "migrate --db $DATABASE_URL",
1612
- "migrate --to production",
1613
- "migrate --show",
1614
- "migrate --show --from @contract --to production"
1615
- ]
1616
- },
1617
- args: { flags: {
1618
- db: dbFlag,
1619
- to: flag.string({
1620
- brief: "Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)",
1621
- placeholder: "contract"
1622
- }),
1623
- advanceRef: flag.string({
1624
- brief: "Advance the named ref to the post-apply marker after success",
1625
- placeholder: "name"
1626
- }),
1627
- show: flag.boolean({ brief: "Preview the migration route without applying (read-only)" }),
1628
- from: flag.string({
1629
- brief: "From-state for the --show preview (@contract, @db, hash, ref name, or dir)",
1630
- placeholder: "contract"
1631
- })
1632
- } },
1633
- needs: { config: ormConfigSection },
1634
- handler: async (args, ctx) => {
1635
- const migrationsRelative = displayPath(migrationsDirFor(ctx.config, ctx.cwd), ctx.cwd);
1636
- if (args.flags.show) {
1637
- const planned = await executeMigrateShowPlan({
1638
- config: ctx.config,
1639
- cwd: ctx.cwd,
1640
- createClient,
1641
- ...ifDefined("db", args.flags.db),
1642
- ...ifDefined("to", args.flags.to),
1643
- ...ifDefined("from", args.flags.from)
1644
- });
1645
- if (!planned.ok) return notOk(normalizeError(planned.failure));
1646
- const plan = planned.value;
1647
- const paint = {
1648
- colorize: true,
1649
- glyphMode: "unicode",
1650
- styler: createToneMigrationListStyler(),
1651
- palette: TONE_MIGRATION_GRAPH_PALETTE
1652
- };
1653
- const rendering = renderMigrateShowGraph(plan, paint);
1654
- const document = {
1655
- ok: true,
1656
- migrations: plan.migrations,
1657
- summary: plan.summary
1658
- };
1659
- const dbConnection = args.flags.db ?? ctx.config.db?.connection;
1660
- return ok(ctx.present({ data: document }, showPresentations$1({
1661
- document,
1662
- graph: rendering.graphOutput,
1663
- runList: migrateShowRunListRows(plan.migrations, rendering, paint),
1664
- migrationsDir: migrationsRelative,
1665
- database: args.flags.from === void 0 && typeof dbConnection === "string" ? maskConnectionUrl(dbConnection) : void 0,
1666
- from: args.flags.from,
1667
- to: args.flags.to
1668
- })));
1822
+ const gitignorePath = join(cwd, ".gitignore");
1823
+ const nextGitignore = mergeGitignore(existsSync(gitignorePath) ? readFileSync(gitignorePath, "utf-8") : void 0);
1824
+ if (nextGitignore !== null) files.push({
1825
+ path: ".gitignore",
1826
+ content: nextGitignore
1827
+ });
1828
+ const gitattributesPath = join(cwd, ".gitattributes");
1829
+ const nextGitattributes = mergeGitattributes(existsSync(gitattributesPath) ? readFileSync(gitattributesPath, "utf-8") : void 0, requiredGitattributesLines(schemaDir, inputs.target));
1830
+ if (nextGitattributes !== null) files.push({
1831
+ path: ".gitattributes",
1832
+ content: nextGitattributes
1833
+ });
1834
+ const manifestPath = join(cwd, "package.json");
1835
+ const manifestExisted = existsSync(manifestPath);
1836
+ const synthesiseManifest = !manifestExisted && !hasProjectManifest(cwd);
1837
+ let parsedManifest = null;
1838
+ if (manifestExisted || synthesiseManifest) {
1839
+ const raw = manifestExisted ? readFileSync(manifestPath, "utf-8") : defaultPackageJsonContent(basename(cwd));
1840
+ try {
1841
+ parsedManifest = JSON.parse(raw);
1842
+ } catch (error) {
1843
+ if (error instanceof SyntaxError) throw errorInitInvalidManifest({
1844
+ path: "package.json",
1845
+ cause: error.message
1846
+ });
1847
+ throw error;
1848
+ }
1849
+ let working = raw;
1850
+ let changed = synthesiseManifest;
1851
+ if (inputs.removePreviousFacade !== null) {
1852
+ const next = removeDependency(working, inputs.removePreviousFacade);
1853
+ if (next !== null) {
1854
+ working = next;
1855
+ changed = true;
1669
1856
  }
1670
- const startedAt = Date.now();
1857
+ }
1858
+ const { content: withScripts, warnings: scriptWarnings } = mergePackageScripts(working, REQUIRED_SCRIPTS);
1859
+ if (withScripts !== null) {
1860
+ working = withScripts;
1861
+ changed = true;
1862
+ }
1863
+ const { content: withType, warning: typeWarning } = ensureEsmModuleType(working);
1864
+ if (withType !== null) {
1865
+ working = withType;
1866
+ changed = true;
1867
+ }
1868
+ if (changed) files.push({
1869
+ path: "package.json",
1870
+ content: working
1871
+ });
1872
+ warnings.push(...scriptWarnings);
1873
+ if (typeWarning !== null) warnings.push(typeWarning);
1874
+ if (synthesiseManifest) warnings.push("No package.json found in the target directory; created a minimal one. Edit `name` / `version` to taste.");
1875
+ }
1876
+ if (existsSync(join(cwd, "src/index.ts"))) if (existsSync(join(cwd, "README.md"))) warnings.push("README.md already exists; leaving it untouched.");
1877
+ else {
1878
+ const declaredName = parsedManifest !== null && typeof parsedManifest["name"] === "string" ? parsedManifest["name"] : basename(cwd);
1879
+ files.push({
1880
+ path: "README.md",
1881
+ content: minimalProjectReadmeMd(inputs.target, inputs.schemaPath, sanitisePackageName(declaredName), packageManager)
1882
+ });
1883
+ }
1884
+ return {
1885
+ files,
1886
+ filesToDelete,
1887
+ dirsToDelete,
1888
+ warnings,
1889
+ hasTypesNode: parsedManifest !== null && hasDirectDep(parsedManifest, "@types/node")
1890
+ };
1891
+ }
1892
+ /**
1893
+ * Plans every write, then performs them. Everything that can fail on the
1894
+ * user's existing files fails during planning, so a failure leaves the project
1895
+ * as it was; after the first write, the scaffold is on disk to keep, and a
1896
+ * write that fails partway names the files that already landed.
1897
+ */
1898
+ function scaffoldProject(ctx) {
1899
+ const resolveImportSpecifier = scaffoldSpecifierResolverFor(ctx.inputs.target);
1900
+ const plan = planScaffold({
1901
+ ...ctx,
1902
+ resolveImportSpecifier
1903
+ });
1904
+ const filesWritten = [];
1905
+ const filesDeleted = [];
1906
+ const notes = [];
1907
+ for (const file of plan.files) {
1908
+ const target = join(ctx.cwd, file.path);
1909
+ try {
1910
+ mkdirSync(dirname(target), { recursive: true });
1911
+ writeFileSync(target, file.content, "utf-8");
1912
+ } catch (error) {
1913
+ throw errorInitWriteFailed({
1914
+ path: file.path,
1915
+ cause: error instanceof Error ? error.message : String(error),
1916
+ filesWritten
1917
+ });
1918
+ }
1919
+ filesWritten.push(file.path);
1920
+ if (file.note !== void 0) notes.push(file.note);
1921
+ }
1922
+ for (const rel of plan.filesToDelete) {
1923
+ const target = join(ctx.cwd, rel);
1924
+ if (!existsSync(target)) continue;
1925
+ try {
1926
+ unlinkSync(target);
1927
+ filesDeleted.push(rel);
1928
+ } catch (error) {
1929
+ if (!(error instanceof Error && Reflect.get(error, "code") === "ENOENT")) throw error;
1930
+ }
1931
+ }
1932
+ for (const rel of plan.dirsToDelete) {
1933
+ rmSync(join(ctx.cwd, rel), {
1934
+ recursive: true,
1935
+ force: true
1936
+ });
1937
+ filesDeleted.push(rel);
1938
+ }
1939
+ return {
1940
+ filesWritten,
1941
+ filesDeleted,
1942
+ warnings: plan.warnings,
1943
+ notes,
1944
+ hasTypesNode: plan.hasTypesNode,
1945
+ resolveImportSpecifier
1946
+ };
1947
+ }
1948
+ const REQUIRED_FLAG_PROMPTS = /* @__PURE__ */ new Set(["target", "authoring"]);
1949
+ function formatFileList(files) {
1950
+ const last = files.at(-1) ?? "";
1951
+ return files.length <= 1 ? last : `${files.slice(0, -1).join(", ")} and ${last}`;
1952
+ }
1953
+ /**
1954
+ * Names the files at stake rather than the category they belong to: the user
1955
+ * knows `contract.prisma`, not "every generated file".
1956
+ */
1957
+ function consentQuestion(replaced) {
1958
+ const written = replaced.length === 1 ? "it" : "them";
1959
+ return `Re-initializing replaces ${formatFileList(replaced)} with a fresh scaffold, losing anything you wrote in ${written}.`;
1960
+ }
1961
+ /**
1962
+ * The engine raises this when a prompt has no default and the session cannot
1963
+ * show it. For the two prompts that stand in for a required flag, that is the
1964
+ * same condition `init` has always reported as a missing flag.
1965
+ */
1966
+ function isPromptRequired(error) {
1967
+ return error instanceof Error && Reflect.get(error, "code") === "CLI.PROMPT_REQUIRED";
1968
+ }
1969
+ /**
1970
+ * `--confirm` matches a token, so the token has to be something the user can
1971
+ * see and type. The working directory's name is it; a directory with no name
1972
+ * of its own (the filesystem root) falls back to its path.
1973
+ *
1974
+ * Trimmed, because the engine compares the typed answer trimmed: an untrimmed
1975
+ * token from a directory named `my app ` is one no keystroke sequence matches.
1976
+ */
1977
+ function consentToken(cwd) {
1978
+ const name = basename(cwd).trim();
1979
+ return name.length > 0 ? name : cwd.trim();
1980
+ }
1981
+ async function askTarget(prompt) {
1982
+ return prompt.select("What database are you using?", [{
1983
+ value: "postgres",
1984
+ label: "PostgreSQL"
1985
+ }, {
1986
+ value: "mongo",
1987
+ label: "MongoDB"
1988
+ }]);
1989
+ }
1990
+ async function askAuthoring(prompt) {
1991
+ return prompt.select("How do you want to write your schema?", [{
1992
+ value: "psl",
1993
+ label: "Prisma Schema Language (.prisma)"
1994
+ }, {
1995
+ value: "typescript",
1996
+ label: "TypeScript (.ts)"
1997
+ }]);
1998
+ }
1999
+ /**
2000
+ * The schema path the user typed goes through the same validator the flag
2001
+ * does, so an answer the engine accepted still has to agree with the authoring
2002
+ * style.
2003
+ */
2004
+ async function askSchemaPath(prompt, authoring) {
2005
+ return validateSchemaPath(await prompt.text("Where should the schema file go?", { default: defaultSchemaPath(authoring) }), authoring);
2006
+ }
2007
+ /**
2008
+ * The facade a previous `init` installed for the other target, when this run
2009
+ * switches targets and one is still declared. Reads every name a previous run
2010
+ * could have written, because older versions scaffolded the workspace name.
2011
+ */
2012
+ function previousFacade(cwd, target) {
2013
+ const manifestPath = join(cwd, "package.json");
2014
+ if (!existsSync(manifestPath)) return;
2015
+ const otherTarget = target === "postgres" ? "mongo" : "postgres";
2016
+ const candidates = [targetPackageName(otherTarget, scaffoldSpecifierResolverFor(otherTarget)), targetPackageName(otherTarget)];
2017
+ let parsed;
2018
+ try {
2019
+ parsed = JSON.parse(readFileSync(manifestPath, "utf-8"));
2020
+ } catch {
2021
+ return;
2022
+ }
2023
+ const deps = parsed["dependencies"];
2024
+ if (deps === null || typeof deps !== "object" || Array.isArray(deps)) return;
2025
+ const declared = deps;
2026
+ return candidates.find((name) => Object.hasOwn(declared, name));
2027
+ }
2028
+ async function resolveRemovePreviousFacade(ctx) {
2029
+ if (!ctx.reinit || ctx.keepPreviousFacade) return null;
2030
+ const facade = previousFacade(ctx.cwd, ctx.target);
2031
+ if (facade === void 0) return null;
2032
+ const otherTarget = ctx.target === "postgres" ? "mongo" : "postgres";
2033
+ return await ctx.prompt.confirm(`Switching from ${targetLabel(otherTarget)} to ${targetLabel(ctx.target)} — remove ${facade} from package.json dependencies?`, { default: true }) ? facade : null;
2034
+ }
2035
+ /**
2036
+ * Resolves every input from the flags and, where a flag is absent, from the
2037
+ * engine's prompt surface.
2038
+ *
2039
+ * Order matters twice. Every flag value is validated before anything is asked,
2040
+ * so a typo costs a typo rather than a typed consent token. And consent comes
2041
+ * as soon as the schema path is known, because that is the point at which the
2042
+ * question can name the files it is about — and before any question whose
2043
+ * answer only matters if the run proceeds.
2044
+ */
2045
+ async function resolveInitInputs(ctx) {
2046
+ const { cwd, flags, prompt } = ctx;
2047
+ if (flags.strictProbe && !flags.probeDb) throw errorInitStrictProbeWithoutProbe();
2048
+ const flagTarget = resolveTarget(flags.target);
2049
+ const flagAuthoring = resolveAuthoring(flags.authoring);
2050
+ let target;
2051
+ let authoring;
2052
+ try {
2053
+ target = flagTarget ?? await askTarget(prompt);
2054
+ authoring = flagAuthoring ?? await askAuthoring(prompt);
2055
+ } catch (error) {
2056
+ if (!isPromptRequired(error)) throw error;
2057
+ throw errorInitMissingFlags({
2058
+ missing: [...flagTarget === void 0 ? ["target"] : [], ...flagAuthoring === void 0 ? ["authoring"] : []].filter((flag) => REQUIRED_FLAG_PROMPTS.has(flag)),
2059
+ why: "This session cannot prompt, so the answers have to arrive as flags."
2060
+ });
2061
+ }
2062
+ const schemaPath = flags.schemaPath !== void 0 ? validateSchemaPath(flags.schemaPath, authoring) : await askSchemaPath(prompt, authoring);
2063
+ const replaced = generatedFilesInitReplaces(schemaPath).filter((relative) => existsSync(join(cwd, relative)));
2064
+ const reinit = replaced.length > 0;
2065
+ if (reinit) {
2066
+ if (!await prompt.consent(consentQuestion(replaced), { token: consentToken(cwd) })) throw errorInitUserAborted();
2067
+ }
2068
+ const writeEnv = flags.writeEnv || await prompt.confirm("Also write a .env file from .env.example? (gitignored)", { default: false });
2069
+ const removePreviousFacade = await resolveRemovePreviousFacade({
2070
+ cwd,
2071
+ target,
2072
+ reinit,
2073
+ keepPreviousFacade: flags.keepPreviousFacade,
2074
+ prompt
2075
+ });
2076
+ return {
2077
+ target,
2078
+ authoring,
2079
+ schemaPath,
2080
+ install: !flags.skipInstall,
2081
+ writeEnv,
2082
+ probeDb: flags.probeDb,
2083
+ strictProbe: flags.strictProbe,
2084
+ reinit,
2085
+ removePreviousFacade,
2086
+ installProjectSkill: !flags.skipSkills
2087
+ };
2088
+ }
2089
+ function metaString(failure, key) {
2090
+ const value = failure.meta?.[key];
2091
+ return typeof value === "string" ? value : "";
2092
+ }
2093
+ /**
2094
+ * pnpm reported a specifier the published artifact leaked, which npm installs
2095
+ * happily. The engine redacts credentials out of the stderr it returns but
2096
+ * leaves error codes alone, which is what this reads.
2097
+ */
2098
+ function pnpmLeakedASpecifier(failure) {
2099
+ return metaString(failure, "manager") === "pnpm" && isRecognisedPnpmResolutionError(metaString(failure, "stderrTail"));
2100
+ }
2101
+ /**
2102
+ * The engine redacts the stderr it hands back, and this redacts it again
2103
+ * before quoting it: what the engine strips is its own business, and registry
2104
+ * auth material is not all URL-shaped.
2105
+ */
2106
+ function fallbackWarning(failure) {
2107
+ const firstLine = redactSecrets(metaString(failure, "stderrTail")).trim().split("\n")[0] ?? "";
2108
+ return [
2109
+ "pnpm could not install: a published Prisma Next dependency leaked a `workspace:*` or `catalog:` specifier.",
2110
+ "Falling back to npm so init can complete.",
2111
+ firstLine === "" ? "" : ` pnpm error: ${firstLine}`,
2112
+ "Both installs ran under npm, which writes a package-lock.json beside the pnpm lockfile — delete whichever of the two you do not want to keep.",
2113
+ "Once the offending package republishes a clean version, re-run `pnpm install` to switch back."
2114
+ ].filter((line) => line !== "").join("\n");
2115
+ }
2116
+ /** The pnpm failure the npm retry was meant to recover from, kept alongside the npm one. */
2117
+ function retriedWarning(failure) {
2118
+ const firstLine = redactSecrets(metaString(failure, "stderrTail")).trim().split("\n")[0] ?? "";
2119
+ return ["pnpm failed first with a leaked `workspace:*` or `catalog:` specifier, so init retried with npm.", firstLine === "" ? "" : ` pnpm error: ${firstLine}`].filter((line) => line !== "").join("\n");
2120
+ }
2121
+ /**
2122
+ * Adds the runtime and development dependencies through the engine's package
2123
+ * manager. The retry is `init`'s alone: the engine spells and runs the
2124
+ * command, and this decides — from the stderr it returned — that another
2125
+ * manager is worth a try.
2126
+ */
2127
+ async function installProjectDependencies(ctx) {
2128
+ const pair = async (manager) => {
2129
+ const runtimeDeps = await ctx.packages.install({
2130
+ packages: ctx.deps,
2131
+ cwd: ctx.cwd,
2132
+ ...ifDefined("manager", manager)
2133
+ });
2134
+ if (!runtimeDeps.ok) return runtimeDeps.failure;
2135
+ const developmentDeps = await ctx.packages.install({
2136
+ packages: ctx.devDeps,
2137
+ dev: true,
2138
+ cwd: ctx.cwd,
2139
+ ...ifDefined("manager", manager)
2140
+ });
2141
+ return developmentDeps.ok ? void 0 : developmentDeps.failure;
2142
+ };
2143
+ const failure = await pair();
2144
+ if (failure === void 0) return {
2145
+ failure: void 0,
2146
+ manager: void 0,
2147
+ warnings: ctx.catalogWarnings
2148
+ };
2149
+ if (!pnpmLeakedASpecifier(failure)) return {
2150
+ failure,
2151
+ manager: void 0,
2152
+ warnings: []
2153
+ };
2154
+ const retryFailure = await pair("npm");
2155
+ if (retryFailure !== void 0) return {
2156
+ failure: retryFailure,
2157
+ manager: void 0,
2158
+ warnings: [retriedWarning(failure)]
2159
+ };
2160
+ return {
2161
+ failure: void 0,
2162
+ manager: "npm",
2163
+ warnings: [fallbackWarning(failure)]
2164
+ };
2165
+ }
2166
+ function skillArgs(url, skill) {
2167
+ return [
2168
+ "add",
2169
+ url,
2170
+ "--agent",
2171
+ ...DEFAULT_SKILL_AGENTS,
2172
+ "--skill",
2173
+ skill,
2174
+ "-y"
2175
+ ];
2176
+ }
2177
+ /**
2178
+ * Registers every Prisma Next skill with the agent runtimes, in order. The
2179
+ * first failure stops the loop: the user opted into Prisma Next by running
2180
+ * `init`, and a half-installed skill set leaves the project ambiguous.
2181
+ */
2182
+ async function installAgentSkills(ctx) {
2183
+ for (const source of DEFAULT_SKILL_SOURCES) {
2184
+ const result = await ctx.packages.run({
2185
+ package: "skills@latest",
2186
+ args: skillArgs(formatSkillSourceUrl(source, ctx.env), source.skill),
2187
+ cwd: ctx.cwd,
2188
+ ...ifDefined("manager", ctx.manager)
2189
+ });
2190
+ if (!result.ok) return result.failure;
2191
+ }
2192
+ }
2193
+ /** The scaffold is on disk from here on, so each of these is a finding. */
2194
+ const INIT_EXIT_CODES = {
2195
+ 4: "scaffold written; dependency install failed",
2196
+ 5: "scaffold written and installed; contract emit failed",
2197
+ 6: "scaffold complete; agent-skill install failed"
2198
+ };
2199
+ function probeWarning(outcome, strictProbe) {
2200
+ switch (outcome.kind) {
2201
+ case "ok": return {
2202
+ warning: void 0,
2203
+ fatal: void 0
2204
+ };
2205
+ case "below-minimum": return {
2206
+ warning: outcome.message,
2207
+ fatal: void 0
2208
+ };
2209
+ case "no-database-url":
2210
+ case "connection-failed":
2211
+ case "driver-missing": return strictProbe ? {
2212
+ warning: void 0,
2213
+ fatal: outcome.message
2214
+ } : {
2215
+ warning: outcome.message,
2216
+ fatal: void 0
2217
+ };
2218
+ }
2219
+ }
2220
+ function causeMessage(error) {
2221
+ return error instanceof Error ? error.message : String(error);
2222
+ }
2223
+ const createInitCommand = (injected) => defineOrmCommand({
2224
+ help: {
2225
+ summary: "Initialize a new Prisma Next project",
2226
+ description: "Scaffolds config, schema, and runtime files, installs dependencies,\nand emits the contract. Gets you from zero to typed queries in one step.\n\nRun it interactively for a guided setup, or supply --target and --authoring\nfor a fully scriptable run (CI, AI coding agents, automation).",
2227
+ examples: [
2228
+ "init",
2229
+ "init --target postgres --authoring psl",
2230
+ "init --target mongodb --authoring typescript --json",
2231
+ "init --skip-install",
2232
+ "init --skip-skills",
2233
+ "init --target postgres --keep-previous-facade"
2234
+ ]
2235
+ },
2236
+ args: { flags: {
2237
+ target: flag.string({
2238
+ brief: "Database target: postgres or mongodb",
2239
+ placeholder: "db"
2240
+ }),
2241
+ authoring: flag.string({
2242
+ brief: "Schema authoring style: psl or typescript",
2243
+ placeholder: "style"
2244
+ }),
2245
+ schemaPath: flag.string({
2246
+ brief: "Where to write the starter schema",
2247
+ placeholder: "path"
2248
+ }),
2249
+ writeEnv: flag.boolean({ brief: "Write a .env file from .env.example (gitignored)" }),
2250
+ probeDb: flag.boolean({ brief: "Connect to DATABASE_URL once and check the server version" }),
2251
+ strictProbe: flag.boolean({ brief: "Treat a failed --probe-db as fatal" }),
2252
+ skipInstall: flag.boolean({ brief: "Skip dependency installation and contract emission" }),
2253
+ skipSkills: flag.boolean({ brief: "Skip the Prisma Next agent-skill install" }),
2254
+ keepPreviousFacade: flag.boolean({ brief: "Keep the previous target package in package.json when switching targets" })
2255
+ } },
2256
+ exitCodes: INIT_EXIT_CODES,
2257
+ installsPackages: true,
2258
+ handler: async (args, ctx) => {
2259
+ const warnings = [];
2260
+ const warn = (text) => {
2261
+ warnings.push(text);
2262
+ ctx.report({
2263
+ kind: "message",
2264
+ severity: "warn",
2265
+ text
2266
+ });
2267
+ };
2268
+ const inputs = await resolveInitInputs({
2269
+ cwd: ctx.cwd,
2270
+ flags: args.flags,
2271
+ prompt: ctx.prompt
2272
+ });
2273
+ const packageManager = await resolveScaffoldPackageManager({
2274
+ cwd: ctx.cwd,
2275
+ env: ctx.env
2276
+ });
2277
+ const scaffold = scaffoldProject({
2278
+ cwd: ctx.cwd,
2279
+ inputs,
2280
+ packageManager
2281
+ });
2282
+ for (const warning of scaffold.warnings) warn(warning);
2283
+ for (const note of scaffold.notes) ctx.report({
2284
+ kind: "message",
2285
+ severity: "info",
2286
+ text: note
2287
+ });
2288
+ const deps = [targetPackageName(inputs.target, scaffold.resolveImportSpecifier), "dotenv"];
2289
+ const devDeps = scaffold.hasTypesNode ? ["prisma-next"] : ["prisma-next", "@types/node"];
2290
+ const findings = [];
2291
+ const extraActions = [];
2292
+ let installedManager;
2293
+ let packagesInstalled = "skipped";
2294
+ let contractEmitted = false;
2295
+ let skillRegistered = false;
2296
+ const settle = (exitCode) => {
2297
+ const installed = packagesInstalled === "installed";
2298
+ const document = {
2299
+ ok: true,
2300
+ target: inputs.target === "mongo" ? "mongodb" : "postgres",
2301
+ authoring: inputs.authoring,
2302
+ schemaPath: inputs.schemaPath,
2303
+ filesWritten: scaffold.filesWritten,
2304
+ filesDeleted: scaffold.filesDeleted,
2305
+ packagesInstalled: {
2306
+ status: packagesInstalled,
2307
+ deps: installed ? deps : [],
2308
+ devDeps: installed ? devDeps : []
2309
+ },
2310
+ contractEmitted,
2311
+ nextSteps: buildNextSteps({
2312
+ target: inputs.target === "mongo" ? "mongodb" : "postgres",
2313
+ packagesInstalled,
2314
+ contractEmitted,
2315
+ emitCommand: EMIT_COMMAND,
2316
+ schemaPath: inputs.schemaPath,
2317
+ skillRegistered
2318
+ }),
2319
+ warnings
2320
+ };
2321
+ const validated = InitOutputSchema(document);
2322
+ if (validated instanceof type.errors) return notOk(new CliStructuredError$1("CLI.INIT_INVALID_OUTPUT_DOCUMENT", "Init produced an invalid output document", {
2323
+ why: `The success document failed schema validation: ${String(validated)}`,
2324
+ nextActions: [chooseAction("This is a bug in prisma-next. Please report it with `-v` output.")],
2325
+ docsUrl: docsUrlFor("CLI.INIT_INVALID_OUTPUT_DOCUMENT")
2326
+ }));
2327
+ return ok(ctx.present({
2328
+ data: document,
2329
+ exitCode,
2330
+ diagnostics: findings
2331
+ }, initPresentations({
2332
+ document,
2333
+ complete: exitCode === 0,
2334
+ nextActions: [...extraActions, ...buildInitNextActions({
2335
+ contractEmitted,
2336
+ schemaPath: inputs.schemaPath,
2337
+ skillRegistered
2338
+ })]
2339
+ })));
2340
+ };
2341
+ if (inputs.install) {
2342
+ const outcome = await installProjectDependencies({
2343
+ packages: ctx.packages,
2344
+ cwd: ctx.cwd,
2345
+ deps,
2346
+ devDeps,
2347
+ catalogWarnings: packageManager === "pnpm" ? buildCatalogWarnings(ctx.cwd, [...deps, ...devDeps]) : []
2348
+ });
2349
+ for (const warning of outcome.warnings) warn(warning);
2350
+ if (outcome.failure !== void 0) {
2351
+ packagesInstalled = "failed";
2352
+ findings.push(installFailedFinding(outcome.failure, scaffold.filesWritten));
2353
+ return settle(4);
2354
+ }
2355
+ packagesInstalled = "installed";
2356
+ installedManager = outcome.manager;
2357
+ const emitStep = "Emit the contract";
2358
+ ctx.report({
2359
+ kind: "step-started",
2360
+ step: emitStep
2361
+ });
2362
+ try {
2363
+ await injected.emitScaffoldedContract({ cwd: ctx.cwd });
2364
+ contractEmitted = true;
2365
+ ctx.report({
2366
+ kind: "step-finished",
2367
+ step: emitStep,
2368
+ outcome: "ok"
2369
+ });
2370
+ } catch (error) {
2371
+ ctx.report({
2372
+ kind: "step-finished",
2373
+ step: emitStep,
2374
+ outcome: "failed"
2375
+ });
2376
+ findings.push(emitFailedFinding(causeMessage(error), scaffold.filesWritten));
2377
+ return settle(5);
2378
+ }
2379
+ } else extraActions.push(chooseAction(`Install the project dependencies with your package manager: ${deps.join(", ")} (and ${devDeps.join(", ")} as development dependencies)`));
2380
+ if (inputs.probeDb) {
2381
+ const probe = probeWarning(await probeServerVersion({
2382
+ baseDir: ctx.cwd,
2383
+ target: inputs.target,
2384
+ databaseUrl: ctx.env["DATABASE_URL"],
2385
+ minVersion: MIN_SERVER_VERSION[inputs.target]
2386
+ }, {}), inputs.strictProbe);
2387
+ if (probe.warning !== void 0) warn(probe.warning);
2388
+ if (probe.fatal !== void 0) return notOk(normalizeError(errorInitProbeFailed({
2389
+ cause: probe.fatal,
2390
+ filesWritten: scaffold.filesWritten
2391
+ })));
2392
+ }
2393
+ const skillCommands = resolveProjectSkillInstallCommands(installedManager ?? packageManager, ctx.env);
2394
+ if (inputs.installProjectSkill) {
2395
+ const failure = await installAgentSkills({
2396
+ packages: ctx.packages,
2397
+ cwd: ctx.cwd,
2398
+ env: ctx.env,
2399
+ manager: installedManager
2400
+ });
2401
+ if (failure !== void 0) {
2402
+ findings.push(skillInstallFailedFinding(failure, scaffold.filesWritten, skillCommands));
2403
+ return settle(6);
2404
+ }
2405
+ skillRegistered = true;
2406
+ } else warn(`Skipped the Prisma Next agent-skill install (--skip-skills). To install them later, run: ${skillCommands.map((command) => `\`${command}\``).join(" && ")}`);
2407
+ return settle(0);
2408
+ }
2409
+ });
2410
+ const initCommand = createInitCommand({ emitScaffoldedContract });
2411
+ const STATUS_ACTION = runCommandAction("Check every space against the database", "prisma-next migration status");
2412
+ /**
2413
+ * The route the migration will take. The graph is a drawing — the engine cannot
2414
+ * derive a lane gutter — and the ordered list under it is drawn with the same
2415
+ * widths so every arrow lands in one column.
2416
+ */
2417
+ function showPresentations$1(inputs) {
2418
+ const { document, graph, runList } = inputs;
2419
+ const count = document.migrations.length;
2420
+ return {
2421
+ human: () => [
2422
+ {
2423
+ kind: "fields",
2424
+ rail: true,
2425
+ rows: [
2426
+ {
2427
+ label: "migrations",
2428
+ value: inputs.migrationsDir
2429
+ },
2430
+ ...inputs.database === void 0 ? [] : [{
2431
+ label: "database",
2432
+ value: inputs.database
2433
+ }],
2434
+ ...inputs.from === void 0 ? [] : [{
2435
+ label: "from",
2436
+ value: inputs.from
2437
+ }],
2438
+ ...inputs.to === void 0 ? [] : [{
2439
+ label: "to",
2440
+ value: inputs.to
2441
+ }]
2442
+ ]
2443
+ },
2444
+ ...graph.length === 0 ? [] : [{
2445
+ kind: "drawing",
2446
+ lines: toneDrawing(graph)
2447
+ }],
2448
+ ...count === 0 ? [{
2449
+ kind: "summary",
2450
+ status: "info",
2451
+ text: document.summary
2452
+ }] : [{
2453
+ kind: "summary",
2454
+ status: "info",
2455
+ text: `The following ${count} migration${count === 1 ? "" : "s"} will run:`
2456
+ }, {
2457
+ kind: "drawing",
2458
+ lines: toneDrawing(runList.join("\n"))
2459
+ }]
2460
+ ],
2461
+ json: () => document
2462
+ };
2463
+ }
2464
+ function applyPresentations(inputs) {
2465
+ const { document } = inputs;
2466
+ const advanced = document.advancedRef;
2467
+ return {
2468
+ human: () => [
2469
+ {
2470
+ kind: "fields",
2471
+ rail: true,
2472
+ rows: [
2473
+ {
2474
+ label: "migrations",
2475
+ value: inputs.migrationsDir
2476
+ },
2477
+ ...inputs.database === void 0 ? [] : [{
2478
+ label: "database",
2479
+ value: inputs.database
2480
+ }],
2481
+ ...inputs.to === void 0 ? [] : [{
2482
+ label: "to",
2483
+ value: inputs.to
2484
+ }]
2485
+ ]
2486
+ },
2487
+ {
2488
+ kind: "summary",
2489
+ status: "ok",
2490
+ text: document.summary
2491
+ },
2492
+ ...document.perSpace.length === 0 ? [] : perSpaceBlocks(document.perSpace, "apply"),
2493
+ ...advanced === null || advanced === void 0 ? [] : [{
2494
+ kind: "summary",
2495
+ status: "ok",
2496
+ text: [{ text: `Advanced ref "${advanced.name}" → ` }, {
2497
+ text: advanced.hash,
2498
+ tone: "identifier"
2499
+ }]
2500
+ }]
2501
+ ],
2502
+ json: () => document,
2503
+ next: () => [STATUS_ACTION]
2504
+ };
2505
+ }
2506
+ /**
2507
+ * `--to` as a contract the app graph knows. A ref target keeps the invariants
2508
+ * the ref declares; a bare hash carries none. Omitting `--to` targets the
2509
+ * emitted contract, which needs no resolution at all.
2510
+ */
2511
+ function resolveRequestedTarget(to, refs, graph) {
2512
+ if (to === void 0) return ok({
2513
+ entry: void 0,
2514
+ refName: void 0
2515
+ });
2516
+ const resolved = resolveContractRef(to, {
2517
+ graph,
2518
+ refs
2519
+ });
2520
+ if (!resolved.ok) return notOk(normalizeError(resolved.failure));
2521
+ if (resolved.value.provenance.kind !== "ref") return ok({
2522
+ entry: {
2523
+ hash: resolved.value.hash,
2524
+ invariants: []
2525
+ },
2526
+ refName: void 0
2527
+ });
2528
+ const refName = resolved.value.provenance.refName;
2529
+ return ok({
2530
+ entry: refs[refName],
2531
+ refName
2532
+ });
2533
+ }
2534
+ function createMigrateCommand(createClient) {
2535
+ return defineOrmCommand({
2536
+ help: {
2537
+ summary: "Apply planned migrations to advance the database",
2538
+ description: "Walks every contract space (app + extensions) and applies pending on-disk\nmigrations in canonical order (extensions alphabetically, then app). It\nreplays the on-disk migration graph and never invents an edge. Use --to to\ntarget a specific contract (hash, ref name, or migration directory) and\n--show for a read-only preview of the route it would take.",
2539
+ examples: [
2540
+ "migrate",
2541
+ "migrate --db $DATABASE_URL",
2542
+ "migrate --to production",
2543
+ "migrate --show",
2544
+ "migrate --show --from @contract --to production"
2545
+ ]
2546
+ },
2547
+ args: { flags: {
2548
+ db: dbFlag,
2549
+ to: flag.string({
2550
+ brief: "Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)",
2551
+ placeholder: "contract"
2552
+ }),
2553
+ advanceRef: flag.string({
2554
+ brief: "Advance the named ref to the post-apply marker after success",
2555
+ placeholder: "name"
2556
+ }),
2557
+ show: flag.boolean({ brief: "Preview the migration route without applying (read-only)" }),
2558
+ from: flag.string({
2559
+ brief: "From-state for the --show preview (@contract, @db, hash, ref name, or dir)",
2560
+ placeholder: "contract"
2561
+ })
2562
+ } },
2563
+ needs: { config: ormConfigSection },
2564
+ handler: async (args, ctx) => {
2565
+ const migrationsRelative = displayPath(migrationsDirFor(ctx.config, ctx.cwd), ctx.cwd);
2566
+ if (args.flags.show) {
2567
+ const planned = await executeMigrateShowPlan({
2568
+ config: ctx.config,
2569
+ cwd: ctx.cwd,
2570
+ createClient,
2571
+ ...ifDefined("db", args.flags.db),
2572
+ ...ifDefined("to", args.flags.to),
2573
+ ...ifDefined("from", args.flags.from)
2574
+ });
2575
+ if (!planned.ok) return notOk(normalizeError(planned.failure));
2576
+ const plan = planned.value;
2577
+ const paint = {
2578
+ colorize: true,
2579
+ glyphMode: "unicode",
2580
+ styler: createToneMigrationListStyler(),
2581
+ palette: TONE_MIGRATION_GRAPH_PALETTE
2582
+ };
2583
+ const rendering = renderMigrateShowGraph(plan, paint);
2584
+ const document = {
2585
+ ok: true,
2586
+ migrations: plan.migrations,
2587
+ summary: plan.summary
2588
+ };
2589
+ const dbConnection = args.flags.db ?? ctx.config.db?.connection;
2590
+ return ok(ctx.present({ data: document }, showPresentations$1({
2591
+ document,
2592
+ graph: rendering.graphOutput,
2593
+ runList: migrateShowRunListRows(plan.migrations, rendering, paint),
2594
+ migrationsDir: migrationsRelative,
2595
+ database: args.flags.from === void 0 && typeof dbConnection === "string" ? maskConnectionUrl(dbConnection) : void 0,
2596
+ from: args.flags.from,
2597
+ to: args.flags.to
2598
+ })));
2599
+ }
2600
+ const startedAt = Date.now();
1671
2601
  const prepared = await prepareMigrationRun({
1672
2602
  config: ctx.config,
1673
2603
  cwd: ctx.cwd,
@@ -2020,85 +2950,392 @@ function ledgerGrid(entries, options) {
2020
2950
  })
2021
2951
  };
2022
2952
  }
2023
- function logPresentations(inputs) {
2024
- const grid = inputs.grid;
2953
+ function logPresentations(inputs) {
2954
+ const grid = inputs.grid;
2955
+ return {
2956
+ human: () => [...inputs.database === void 0 ? [] : [{
2957
+ kind: "fields",
2958
+ rail: true,
2959
+ rows: [{
2960
+ label: "database",
2961
+ value: inputs.database
2962
+ }]
2963
+ }], ...grid === void 0 ? [{
2964
+ kind: "summary",
2965
+ status: "info",
2966
+ text: MIGRATION_LOG_EMPTY_MESSAGE
2967
+ }] : [{
2968
+ kind: "table",
2969
+ columns: grid.headings,
2970
+ rows: grid.rows
2971
+ }]],
2972
+ json: () => inputs.document
2973
+ };
2974
+ }
2975
+ const migrationLogCommand = defineOrmCommand({
2976
+ help: {
2977
+ summary: "Show executed migration history",
2978
+ description: "Reads the database ledger and displays every applied migration edge in\nchronological order, including rollbacks and re-applies, merged across\nall contract spaces. Requires a database connection.",
2979
+ examples: [
2980
+ "migration log",
2981
+ "migration log --db $DATABASE_URL",
2982
+ "migration log --utc",
2983
+ "migration log --json"
2984
+ ]
2985
+ },
2986
+ args: { flags: {
2987
+ db: dbFlag,
2988
+ utc: flag.boolean({ brief: "Render human timestamps in UTC instead of local time" }),
2989
+ ascii: flag.boolean({ brief: "Use ASCII glyphs (pipe-friendly)" })
2990
+ } },
2991
+ needs: { config: ormConfigSection },
2992
+ handler: async (args, ctx) => {
2993
+ const dbConnection = args.flags.db ?? ctx.config.db?.connection;
2994
+ const missingDb = requireLiveDatabase({
2995
+ dbConnection,
2996
+ hasDriver: ctx.config.driver !== void 0,
2997
+ why: "migration log needs a database connection and driver to read the ledger (set db.connection in prisma-next.config.ts, or pass --db <url>)",
2998
+ commandName: "migration log"
2999
+ });
3000
+ if (missingDb !== null) return notOk(normalizeError(missingDb));
3001
+ if (!targetSupportsMigrations(ctx.config.target)) return notOk(normalizeError(errorTargetMigrationNotSupported({ why: "migration log reads the ledger a migration runner writes, and the configured target provides no runner." })));
3002
+ const client = createControlClient({
3003
+ family: ctx.config.family,
3004
+ target: ctx.config.target,
3005
+ adapter: ctx.config.adapter,
3006
+ ...ifDefined("driver", ctx.config.driver),
3007
+ extensions: ctx.config.extensions ?? []
3008
+ });
3009
+ let entries;
3010
+ try {
3011
+ await client.connect(dbConnection);
3012
+ entries = await client.readLedger();
3013
+ } catch (error) {
3014
+ return notOk(normalizeError(mapCaughtMigrationError(error) ?? errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read migration log: ${error instanceof Error ? error.message : String(error)}` })));
3015
+ } finally {
3016
+ await closeQuietly(client);
3017
+ }
3018
+ const records = serializeLedgerEntriesForJson(entries);
3019
+ const document = {
3020
+ ok: true,
3021
+ records,
3022
+ summary: `${records.length} migration(s) applied`
3023
+ };
3024
+ const glyphMode = args.flags.ascii ? "ascii" : "unicode";
3025
+ return ok(ctx.present({ data: document }, logPresentations({
3026
+ document,
3027
+ grid: entries.length === 0 ? void 0 : ledgerGrid(entries, {
3028
+ utc: args.flags.utc,
3029
+ glyphMode
3030
+ }),
3031
+ database: typeof dbConnection === "string" ? maskConnectionUrl(dbConnection) : void 0
3032
+ })));
3033
+ }
3034
+ });
3035
+ function newPresentations(inputs) {
3036
+ const { document } = inputs;
3037
+ const migrationTs = join(document.dir, "migration.ts");
3038
+ return {
3039
+ human: () => [
3040
+ {
3041
+ kind: "fields",
3042
+ rail: true,
3043
+ rows: [{
3044
+ label: "contract",
3045
+ value: inputs.contractPath
3046
+ }, {
3047
+ label: "migrations",
3048
+ value: inputs.appMigrationsRelative
3049
+ }]
3050
+ },
3051
+ {
3052
+ kind: "summary",
3053
+ status: "ok",
3054
+ text: document.summary
3055
+ },
3056
+ {
3057
+ kind: "fields",
3058
+ rows: [{
3059
+ label: "from",
3060
+ value: document.from === null ? [{
3061
+ text: "(baseline)",
3062
+ tone: "muted"
3063
+ }] : [{
3064
+ text: document.from,
3065
+ tone: "identifier"
3066
+ }]
3067
+ }, {
3068
+ label: "to",
3069
+ value: [{
3070
+ text: document.to,
3071
+ tone: "identifier"
3072
+ }]
3073
+ }]
3074
+ }
3075
+ ],
3076
+ json: () => document,
3077
+ next: () => [{
3078
+ kind: "edit-file",
3079
+ label: `Write the migration body in ${migrationTs}`
3080
+ }, runCommandAction("Run it to self-emit ops.json and attest the package", `node "${migrationTs}"`)]
3081
+ };
3082
+ }
3083
+ const migrationNewCommand = defineOrmCommand({
3084
+ help: {
3085
+ summary: "Scaffold a new migration for manual authoring",
3086
+ description: "Creates a migration package with a migration.ts file for manual authoring.\nWrite the migration body in migration.ts, then run the file with Node\n(`node migration.ts`) to self-emit ops.json and attest the package.\nOffline — does not consult the database.",
3087
+ examples: [
3088
+ "migration new --name split-name",
3089
+ "migration new --name custom-fk --from abc123",
3090
+ "migration new --json"
3091
+ ]
3092
+ },
3093
+ args: { flags: {
3094
+ name: flag.string({
3095
+ brief: "Migration name (used in directory name)",
3096
+ placeholder: "slug"
3097
+ }),
3098
+ from: flag.string({
3099
+ brief: "Starting contract hash (default: latest migration target)",
3100
+ placeholder: "hash"
3101
+ })
3102
+ } },
3103
+ needs: { config: ormConfigSection },
3104
+ handler: async (args, ctx) => {
3105
+ const scaffolded = await executeMigrationNewCommand({
3106
+ config: ctx.config,
3107
+ cwd: ctx.cwd,
3108
+ configPath: projectConfigPathFor(ctx.cwd),
3109
+ ...ifDefined("name", args.flags.name),
3110
+ ...ifDefined("from", args.flags.from)
3111
+ });
3112
+ if (!scaffolded.ok) return notOk(normalizeError(scaffolded.failure));
3113
+ const contractPath = contractPathFor(ctx.config, ctx.cwd);
3114
+ return ok(ctx.present({ data: scaffolded.value }, newPresentations({
3115
+ document: scaffolded.value,
3116
+ contractPath: contractPath === void 0 ? "(unset)" : displayPath(contractPath, ctx.cwd),
3117
+ appMigrationsRelative: displayPath(appMigrationsDirFor(ctx.config, ctx.cwd), ctx.cwd)
3118
+ })));
3119
+ }
3120
+ });
3121
+ function hashRow(label, hash) {
3122
+ return {
3123
+ label,
3124
+ value: hash === null ? [{
3125
+ text: "(baseline)",
3126
+ tone: "muted"
3127
+ }] : [{
3128
+ text: hash,
3129
+ tone: "identifier"
3130
+ }]
3131
+ };
3132
+ }
3133
+ /**
3134
+ * Where the run put things: the contract edge it planned, then every directory
3135
+ * it wrote — the app-space package, the auto-baseline package that precedes it,
3136
+ * and any extension-space package the seed phase materialised.
3137
+ */
3138
+ function outcomeFields(result, migrationsRelative) {
3139
+ return {
3140
+ kind: "fields",
3141
+ rows: [
3142
+ hashRow("from", result.from),
3143
+ hashRow("to", result.to),
3144
+ ...result.baselineDir === void 0 ? [] : [{
3145
+ label: "baseline",
3146
+ value: result.baselineDir
3147
+ }],
3148
+ ...result.dir === void 0 ? [] : [{
3149
+ label: "app space",
3150
+ value: result.dir
3151
+ }],
3152
+ ...result.emittedExtensionDirs.map((entry) => ({
3153
+ label: `space ${entry.spaceId}`,
3154
+ value: join(migrationsRelative, entry.spaceId, entry.dirName)
3155
+ }))
3156
+ ]
3157
+ };
3158
+ }
3159
+ function operationNodes$1(result) {
3160
+ return result.operations.map((operation) => operation.operationClass === "destructive" ? {
3161
+ label: operation.label,
3162
+ status: "warn"
3163
+ } : { label: operation.label });
3164
+ }
3165
+ function operationBlocks$1(result) {
3166
+ if (result.operations.length === 0) return [];
3167
+ const destructive = result.operations.some((operation) => operation.operationClass === "destructive");
3168
+ return [{
3169
+ kind: "tree",
3170
+ roots: [{
3171
+ label: result.dir ?? "operations",
3172
+ children: operationNodes$1(result)
3173
+ }]
3174
+ }, ...destructive ? [{
3175
+ kind: "summary",
3176
+ status: "warn",
3177
+ text: "This migration contains destructive operations that may cause data loss."
3178
+ }] : []];
3179
+ }
3180
+ /** Statements a database would run, printed verbatim rather than laid out. */
3181
+ function previewBlocks$1(result) {
3182
+ const preview = result.preview;
3183
+ if (preview === void 0) return [];
3184
+ const statements = preview.statements.map((statement) => statement.text.trim()).filter((text) => text.length > 0).map((text) => text.endsWith(";") ? text : `${text};`);
3185
+ if (statements.length === 0) return [];
3186
+ return [{
3187
+ kind: "summary",
3188
+ status: "info",
3189
+ tone: "muted",
3190
+ text: previewBlockHeader(preview)
3191
+ }, {
3192
+ kind: "drawing",
3193
+ lines: statements
3194
+ }];
3195
+ }
3196
+ function planBlocks(result, migrationsRelative) {
3197
+ const outcome = outcomeFields(result, migrationsRelative);
3198
+ if (result.noOp) return [{
3199
+ kind: "summary",
3200
+ status: "ok",
3201
+ text: "No changes detected"
3202
+ }, outcome];
3203
+ if (result.pendingPlaceholders === true) return [{
3204
+ kind: "summary",
3205
+ status: "warn",
3206
+ text: result.summary
3207
+ }, outcome];
3208
+ return [
3209
+ {
3210
+ kind: "summary",
3211
+ status: "ok",
3212
+ text: result.summary
3213
+ },
3214
+ ...operationBlocks$1(result),
3215
+ outcome,
3216
+ ...previewBlocks$1(result)
3217
+ ];
3218
+ }
3219
+ function planNextActions(result, migrationsRelative) {
3220
+ if (result.pendingPlaceholders === true) {
3221
+ const migrationTs = join(result.dir ?? "<dir>", "migration.ts");
3222
+ return [{
3223
+ kind: "edit-file",
3224
+ label: `Replace each placeholder(...) call in ${migrationTs} with your query`
3225
+ }, runCommandAction("Run it to self-emit ops.json and attest the package", `node "${migrationTs}"`)];
3226
+ }
3227
+ const written = [
3228
+ ...result.baselineDir === void 0 ? [] : [result.baselineDir],
3229
+ ...result.dir === void 0 ? [] : [result.dir],
3230
+ ...result.emittedExtensionDirs.map((entry) => join(migrationsRelative, entry.spaceId, entry.dirName))
3231
+ ];
3232
+ if (written.length === 0) return [];
3233
+ return [{
3234
+ kind: "edit-file",
3235
+ label: `Review ${written.join(" and ")}`
3236
+ }, runCommandAction("Apply the migration", "prisma-next migrate")];
3237
+ }
3238
+ function planPresentations(inputs) {
2025
3239
  return {
2026
- human: () => [...inputs.database === void 0 ? [] : [{
3240
+ human: () => [{
2027
3241
  kind: "fields",
2028
3242
  rail: true,
2029
- rows: [{
2030
- label: "database",
2031
- value: inputs.database
2032
- }]
2033
- }], ...grid === void 0 ? [{
2034
- kind: "summary",
2035
- status: "info",
2036
- text: MIGRATION_LOG_EMPTY_MESSAGE
2037
- }] : [{
2038
- kind: "table",
2039
- columns: grid.headings,
2040
- rows: grid.rows
2041
- }]],
2042
- json: () => inputs.document
3243
+ rows: [
3244
+ {
3245
+ label: "contract",
3246
+ value: inputs.contractPath
3247
+ },
3248
+ {
3249
+ label: "migrations",
3250
+ value: inputs.appMigrationsRelative
3251
+ },
3252
+ ...inputs.from === void 0 ? [] : [{
3253
+ label: "from",
3254
+ value: inputs.from
3255
+ }],
3256
+ ...inputs.to === void 0 ? [] : [{
3257
+ label: "to",
3258
+ value: inputs.to
3259
+ }],
3260
+ ...inputs.name === void 0 ? [] : [{
3261
+ label: "name",
3262
+ value: inputs.name
3263
+ }]
3264
+ ]
3265
+ }, ...planBlocks(inputs.document, inputs.migrationsRelative)],
3266
+ json: () => inputs.document,
3267
+ next: () => planNextActions(inputs.document, inputs.migrationsRelative)
2043
3268
  };
2044
3269
  }
2045
- const migrationLogCommand = defineOrmCommand({
3270
+ const migrationPlanCommand = defineOrmCommand({
2046
3271
  help: {
2047
- summary: "Show executed migration history",
2048
- description: "Reads the database ledger and displays every applied migration edge in\nchronological order, including rollbacks and re-applies, merged across\nall contract spaces. Requires a database connection.",
3272
+ summary: "Plan a migration from contract changes",
3273
+ description: "Compares the emitted contract against the latest on-disk migration state\nand produces a new migration package with the required operations.\nOffline does not consult the database.",
2049
3274
  examples: [
2050
- "migration log",
2051
- "migration log --db $DATABASE_URL",
2052
- "migration log --utc",
2053
- "migration log --json"
3275
+ "migration plan",
3276
+ "migration plan --name add-users-table",
3277
+ "migration plan --to <migration-dir>^ --name rollback",
3278
+ "migration plan --json"
2054
3279
  ]
2055
3280
  },
2056
3281
  args: { flags: {
2057
- db: dbFlag,
2058
- utc: flag.boolean({ brief: "Render human timestamps in UTC instead of local time" }),
2059
- ascii: flag.boolean({ brief: "Use ASCII glyphs (pipe-friendly)" })
3282
+ name: flag.string({
3283
+ brief: "Name slug for the migration directory",
3284
+ placeholder: "slug"
3285
+ }),
3286
+ from: flag.string({
3287
+ brief: "Starting contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)",
3288
+ placeholder: "contract"
3289
+ }),
3290
+ to: flag.string({
3291
+ brief: "Destination contract reference; defaults to the emitted contract. Same grammar as --from",
3292
+ placeholder: "contract"
3293
+ })
2060
3294
  } },
2061
3295
  needs: { config: ormConfigSection },
2062
3296
  handler: async (args, ctx) => {
2063
- const dbConnection = args.flags.db ?? ctx.config.db?.connection;
2064
- const missingDb = requireLiveDatabase({
2065
- dbConnection,
2066
- hasDriver: ctx.config.driver !== void 0,
2067
- why: "migration log needs a database connection and driver to read the ledger (set db.connection in prisma-next.config.ts, or pass --db <url>)",
2068
- commandName: "migration log"
2069
- });
2070
- if (missingDb !== null) return notOk(normalizeError(missingDb));
2071
- if (!targetSupportsMigrations(ctx.config.target)) return notOk(normalizeError(errorTargetMigrationNotSupported({ why: "migration log reads the ledger a migration runner writes, and the configured target provides no runner." })));
2072
- const client = createControlClient({
2073
- family: ctx.config.family,
2074
- target: ctx.config.target,
2075
- adapter: ctx.config.adapter,
2076
- ...ifDefined("driver", ctx.config.driver),
2077
- extensions: ctx.config.extensions ?? []
2078
- });
2079
- let entries;
2080
- try {
2081
- await client.connect(dbConnection);
2082
- entries = await client.readLedger();
2083
- } catch (error) {
2084
- return notOk(normalizeError(mapCaughtMigrationError(error) ?? errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read migration log: ${error instanceof Error ? error.message : String(error)}` })));
2085
- } finally {
2086
- await closeQuietly(client);
2087
- }
2088
- const records = serializeLedgerEntriesForJson(entries);
2089
- const document = {
2090
- ok: true,
2091
- records,
2092
- summary: `${records.length} migration(s) applied`
3297
+ const seeded = (record) => {
3298
+ if (record.action !== "updated") return;
3299
+ const step = `Seed contract space ${record.spaceId}`;
3300
+ ctx.report({
3301
+ kind: "step-started",
3302
+ step,
3303
+ id: record.spaceId
3304
+ });
3305
+ ctx.report({
3306
+ kind: "step-finished",
3307
+ step,
3308
+ id: record.spaceId,
3309
+ outcome: "ok",
3310
+ data: {
3311
+ newHash: record.newHash,
3312
+ newMigrationDirs: record.newMigrationDirs
3313
+ }
3314
+ });
2093
3315
  };
2094
- const glyphMode = args.flags.ascii ? "ascii" : "unicode";
2095
- return ok(ctx.present({ data: document }, logPresentations({
2096
- document,
2097
- grid: entries.length === 0 ? void 0 : ledgerGrid(entries, {
2098
- utc: args.flags.utc,
2099
- glyphMode
2100
- }),
2101
- database: typeof dbConnection === "string" ? maskConnectionUrl(dbConnection) : void 0
3316
+ const planned = await executeMigrationPlanCommand({
3317
+ config: ctx.config,
3318
+ cwd: ctx.cwd,
3319
+ configPath: projectConfigPathFor(ctx.cwd),
3320
+ ...ifDefined("name", args.flags.name),
3321
+ ...ifDefined("from", args.flags.from),
3322
+ ...ifDefined("to", args.flags.to)
3323
+ }, Date.now(), { onSeeded: seeded });
3324
+ if (!planned.ok) return notOk(normalizeError(planned.failure));
3325
+ ctx.report({
3326
+ kind: "message",
3327
+ severity: "verbose",
3328
+ text: `Total time: ${planned.value.timings.total}ms`
3329
+ });
3330
+ const contractPath = contractPathFor(ctx.config, ctx.cwd);
3331
+ return ok(ctx.present({ data: planned.value }, planPresentations({
3332
+ document: planned.value,
3333
+ contractPath: contractPath === void 0 ? "(unset)" : displayPath(contractPath, ctx.cwd),
3334
+ appMigrationsRelative: displayPath(appMigrationsDirFor(ctx.config, ctx.cwd), ctx.cwd),
3335
+ migrationsRelative: displayPath(migrationsDirFor(ctx.config, ctx.cwd), ctx.cwd),
3336
+ from: args.flags.from,
3337
+ to: args.flags.to,
3338
+ name: args.flags.name
2102
3339
  })));
2103
3340
  }
2104
3341
  });
@@ -2336,6 +3573,426 @@ const migrationShowCommand = defineOrmCommand({
2336
3573
  })));
2337
3574
  }
2338
3575
  });
3576
+ const EMIT_CONTRACT = runCommandAction("Regenerate the contract", "prisma-next contract emit");
3577
+ function contractUnreadableFinding(reason) {
3578
+ const message = `Could not read contract: ${reason}`;
3579
+ return {
3580
+ document: {
3581
+ code: "CONTRACT.UNREADABLE",
3582
+ severity: "warn",
3583
+ message,
3584
+ hints: ["Run 'prisma-next contract emit' to generate a valid contract"]
3585
+ },
3586
+ diagnostic: {
3587
+ code: "CONTRACT.UNREADABLE",
3588
+ severity: "warn",
3589
+ summary: message,
3590
+ why: "The status tree falls back to the on-disk migration graph when the emitted contract cannot be read.",
3591
+ nextActions: [EMIT_CONTRACT]
3592
+ }
3593
+ };
3594
+ }
3595
+ function markerNotInHistoryFinding(space) {
3596
+ const message = `Database was updated outside the migration system (marker for space "${space}" does not match any migration)`;
3597
+ return {
3598
+ document: {
3599
+ code: "MIGRATION.MARKER_NOT_IN_HISTORY",
3600
+ severity: "warn",
3601
+ message,
3602
+ hints: ["Run 'prisma-next db sign' to overwrite the marker if the database already matches the contract", "Run 'prisma-next db update' to push the current contract to the database"]
3603
+ },
3604
+ diagnostic: {
3605
+ code: "MIGRATION.MARKER_NOT_IN_HISTORY",
3606
+ severity: "warn",
3607
+ summary: message,
3608
+ why: "The marker the database carries names no contract in the on-disk migration graph.",
3609
+ meta: { space },
3610
+ nextActions: [runCommandAction("Overwrite the marker if the database already matches the contract", "prisma-next db sign"), runCommandAction("Or push the current contract to the database", "prisma-next db update")]
3611
+ }
3612
+ };
3613
+ }
3614
+ function missingInvariantsFinding(inputs) {
3615
+ const message = `missing invariant(s): ${inputs.missing.join(", ")}`;
3616
+ return {
3617
+ document: {
3618
+ code: "MIGRATION.MISSING_INVARIANTS",
3619
+ severity: "warn",
3620
+ ...ifDefined("ref", inputs.refName),
3621
+ invariants: [...inputs.missing],
3622
+ message
3623
+ },
3624
+ diagnostic: {
3625
+ code: "MIGRATION.MISSING_INVARIANTS",
3626
+ severity: "warn",
3627
+ summary: message,
3628
+ why: inputs.refName === void 0 ? "The database marker does not carry every invariant the target requires." : `The database marker does not carry every invariant \`${inputs.refName}\` requires.`,
3629
+ nextActions: [runCommandAction("Apply the migrations that provide them", "prisma-next migrate")],
3630
+ meta: {
3631
+ invariants: [...inputs.missing],
3632
+ ...ifDefined("ref", inputs.refName)
3633
+ }
3634
+ }
3635
+ };
3636
+ }
3637
+ const SHORT_HASH_LENGTH = 12;
3638
+ function shortDisplayHash(hash) {
3639
+ return hash.slice(0, SHORT_HASH_LENGTH);
3640
+ }
3641
+ const NO_DATABASE_STATE = {
3642
+ markersBySpace: /* @__PURE__ */ new Map(),
3643
+ ledgersBySpace: /* @__PURE__ */ new Map()
3644
+ };
3645
+ async function readDatabaseState(inputs) {
3646
+ const { client } = inputs;
3647
+ try {
3648
+ await client.connect(inputs.connection);
3649
+ const markersBySpace = new Map(await client.readAllMarkers());
3650
+ const ledgersBySpace = /* @__PURE__ */ new Map();
3651
+ for (const spaceId of inputs.spaceIds) ledgersBySpace.set(spaceId, await client.readLedger(spaceId));
3652
+ return ok({
3653
+ markersBySpace,
3654
+ ledgersBySpace
3655
+ });
3656
+ } catch (error) {
3657
+ return notOk(normalizeError(CliStructuredError$1.is(error) ? error : errorUnexpected(error instanceof Error ? error.message : String(error), { why: `Failed to read database state: ${error instanceof Error ? error.message : String(error)}` })));
3658
+ } finally {
3659
+ await closeQuietly(client);
3660
+ }
3661
+ }
3662
+ function buildNoPathSummary(args) {
3663
+ const markerPart = args.markerHash !== void 0 ? `the database state (${shortDisplayHash(args.markerHash)})` : "the database state";
3664
+ const targetShort = shortDisplayHash(args.targetHash);
3665
+ if (!args.explicitTarget) return `No migration path from ${markerPart} to the application's contract (${targetShort}). Run \`prisma-next migration plan --name <name>\` to author one.`;
3666
+ return `No migration path from ${markerPart} to ${args.refName !== void 0 ? `the target (${targetShort} via \`${args.refName}\`)` : `the target (${targetShort})`}. Run \`prisma-next migration plan --name <name>\` to author one, or pass \`--to <contract>\` to pick a reachable target.`;
3667
+ }
3668
+ function buildStatusHeadline(args) {
3669
+ if (args.markerDiverged && args.markerHash !== void 0) return `Database marker ${shortDisplayHash(args.markerHash)} is not in the on-disk migration graph`;
3670
+ if (args.pendingCount === 0) return "Up to date";
3671
+ return `${args.pendingCount} pending — run \`prisma-next migrate --to ${shortDisplayHash(args.targetHash)}\``;
3672
+ }
3673
+ /**
3674
+ * One space's section: its heading, where more than one space is in scope, on
3675
+ * top of its tree. Heading and tree are one drawing so the heading stays a
3676
+ * bare label — a `summary` would put a status glyph in front of it.
3677
+ */
3678
+ function sectionBlock(section) {
3679
+ const heading = section.showHeading ? [[{
3680
+ text: `${section.space}:`,
3681
+ tone: "heading"
3682
+ }]] : [];
3683
+ const body = section.tree.length === 0 ? [[{
3684
+ text: "(no migrations)",
3685
+ tone: "muted"
3686
+ }]] : toneDrawing(section.tree);
3687
+ return {
3688
+ kind: "drawing",
3689
+ lines: [...heading, ...body]
3690
+ };
3691
+ }
3692
+ function statusPresentations(inputs) {
3693
+ const legend = inputs.legend;
3694
+ return {
3695
+ human: () => [
3696
+ {
3697
+ kind: "fields",
3698
+ rail: true,
3699
+ rows: [
3700
+ {
3701
+ label: "migrations",
3702
+ value: inputs.migrationsDir
3703
+ },
3704
+ ...inputs.database === void 0 ? [] : [{
3705
+ label: "database",
3706
+ value: inputs.database
3707
+ }],
3708
+ ...inputs.ref === void 0 ? [] : [{
3709
+ label: "ref",
3710
+ value: inputs.ref
3711
+ }],
3712
+ ...inputs.from === void 0 ? [] : [{
3713
+ label: "from",
3714
+ value: inputs.from
3715
+ }],
3716
+ ...inputs.space === void 0 ? [] : [{
3717
+ label: "space",
3718
+ value: inputs.space
3719
+ }]
3720
+ ]
3721
+ },
3722
+ ...legend === void 0 ? [] : [{
3723
+ kind: "drawing",
3724
+ lines: toneDrawing(legend)
3725
+ }],
3726
+ ...inputs.sections.map(sectionBlock),
3727
+ inputs.headline
3728
+ ],
3729
+ json: () => inputs.document
3730
+ };
3731
+ }
3732
+ const migrationStatusCommand = defineOrmCommand({
3733
+ help: {
3734
+ summary: "Show migration path and pending status",
3735
+ description: "Shows which migrations are pending between the database marker and the\ntarget contract. Requires a database connection. Pass --from for an\noffline path preview without a database. Use `migration graph` for\ntopology, `migration log` for history, and `migration list` for on-disk\nenumeration.",
3736
+ examples: [
3737
+ "migration status",
3738
+ "migration status --db $DATABASE_URL",
3739
+ "migration status --to production",
3740
+ "migration status --from abc123 --to production",
3741
+ "migration status --ascii",
3742
+ "migration status --legend"
3743
+ ]
3744
+ },
3745
+ args: { flags: {
3746
+ db: dbFlag,
3747
+ space: flag.string({
3748
+ brief: "Narrow output to a single contract space",
3749
+ placeholder: "id"
3750
+ }),
3751
+ to: flag.string({
3752
+ brief: "Target contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)",
3753
+ placeholder: "contract"
3754
+ }),
3755
+ from: flag.string({
3756
+ brief: "Origin contract reference; same grammar as --to. Supplying it switches to offline path computation",
3757
+ placeholder: "contract"
3758
+ }),
3759
+ legend: flag.boolean({ brief: "Print a key for the tree glyphs and lane colors" }),
3760
+ ascii: flag.boolean({ brief: "Use ASCII glyphs (pipe-friendly)" })
3761
+ } },
3762
+ needs: { config: ormConfigSection },
3763
+ handler: async (args, ctx) => {
3764
+ const migrationsDir = migrationsDirFor(ctx.config, ctx.cwd);
3765
+ const dbConnection = args.flags.db ?? ctx.config.db?.connection;
3766
+ const hasDriver = ctx.config.driver !== void 0;
3767
+ const usingFromOverride = args.flags.from !== void 0;
3768
+ if (!usingFromOverride) {
3769
+ const missingDb = requireLiveDatabase({
3770
+ dbConnection,
3771
+ hasDriver,
3772
+ why: "migration status needs a database connection to read the marker and ledger (or pass --from for an offline path preview)",
3773
+ retryCommand: "prisma-next migration status --from <contract>"
3774
+ });
3775
+ if (missingDb !== null) return notOk(normalizeError(missingDb));
3776
+ }
3777
+ const refsResult = await readMigrationRefs(appRefsDirFor(ctx.config, ctx.cwd));
3778
+ if (!refsResult.ok) return notOk(normalizeError(refsResult.failure));
3779
+ const refs = refsResult.value;
3780
+ const findings = [];
3781
+ const loaded = await buildReadAggregate(ctx.config, { migrationsDir });
3782
+ if (!loaded.ok) return notOk(normalizeError(loaded.failure));
3783
+ const { aggregate, contractHash } = loaded.value;
3784
+ const contractConfig = { contract: ifDefined("output", contractPathFor(ctx.config, ctx.cwd)) };
3785
+ try {
3786
+ await readContractEnvelope(contractConfig);
3787
+ } catch (error) {
3788
+ findings.push(contractUnreadableFinding(error instanceof Error ? error.message : "unknown error"));
3789
+ }
3790
+ if (await loadContractRawSafely(contractConfig) !== null) {
3791
+ const corruption = refusePackageCorruptionOnAggregate(aggregate);
3792
+ if (corruption) return notOk(normalizeError(corruption));
3793
+ }
3794
+ const appGraph = aggregate.app.graph();
3795
+ let activeRefHash;
3796
+ let activeRefName;
3797
+ let activeRefEntry;
3798
+ if (args.flags.to !== void 0) {
3799
+ const resolved = resolveContractRef(args.flags.to, {
3800
+ graph: appGraph,
3801
+ refs
3802
+ });
3803
+ if (!resolved.ok) return notOk(normalizeError(resolved.failure));
3804
+ activeRefHash = resolved.value.hash;
3805
+ if (resolved.value.provenance.kind === "ref") {
3806
+ activeRefName = resolved.value.provenance.refName;
3807
+ activeRefEntry = refs[activeRefName];
3808
+ }
3809
+ }
3810
+ let fromOverrideHash;
3811
+ if (args.flags.from !== void 0) {
3812
+ const resolved = resolveContractRef(args.flags.from, {
3813
+ graph: appGraph,
3814
+ refs
3815
+ });
3816
+ if (!resolved.ok) return notOk(normalizeError(resolved.failure));
3817
+ fromOverrideHash = resolved.value.hash;
3818
+ }
3819
+ const listed = runMigrationList({
3820
+ spaces: await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir),
3821
+ ...ifDefined("spaceFilter", args.flags.space)
3822
+ });
3823
+ if (!listed.ok) return notOk(normalizeError(listed.failure));
3824
+ const scopedSpaces = listed.value.spaces;
3825
+ const connects = dbConnection !== void 0 && hasDriver && !usingFromOverride;
3826
+ let database = NO_DATABASE_STATE;
3827
+ if (connects) {
3828
+ const read = await readDatabaseState({
3829
+ client: createControlClient({
3830
+ family: ctx.config.family,
3831
+ target: ctx.config.target,
3832
+ adapter: ctx.config.adapter,
3833
+ ...ifDefined("driver", ctx.config.driver),
3834
+ extensions: ctx.config.extensions ?? []
3835
+ }),
3836
+ connection: dbConnection,
3837
+ spaceIds: scopedSpaces.map((entry) => entry.space)
3838
+ });
3839
+ if (!read.ok) return notOk(read.failure);
3840
+ database = read.value;
3841
+ }
3842
+ const appMarker = database.markersBySpace.get(aggregate.app.spaceId);
3843
+ if (activeRefEntry !== void 0 && activeRefEntry.invariants.length > 0 && connects) {
3844
+ const unknown = refuseUnknownInvariants({
3845
+ graph: appGraph,
3846
+ markerInvariants: appMarker?.invariants ?? [],
3847
+ refInvariants: activeRefEntry.invariants,
3848
+ ...ifDefined("refName", activeRefName)
3849
+ });
3850
+ if (unknown) return notOk(normalizeError(unknown));
3851
+ }
3852
+ const glyphMode = args.flags.ascii ? "ascii" : "unicode";
3853
+ const styler = createToneMigrationListStyler();
3854
+ const showSpaceHeadings = scopedSpaces.length > 1;
3855
+ const statusSpaces = [];
3856
+ const renderInputs = [];
3857
+ const emptySpaces = [];
3858
+ let divergedMarker;
3859
+ let noPath;
3860
+ let headlineTargetHash = activeRefHash ?? contractHash;
3861
+ let totalPending = 0;
3862
+ for (const entry of scopedSpaces) {
3863
+ const space = aggregate.space(entry.space);
3864
+ if (space === void 0) continue;
3865
+ const graph = space.graph();
3866
+ const spaceContractHash = space.contract().storage.storageHash;
3867
+ const targetHash = activeRefHash ?? spaceContractHash;
3868
+ if (entry.space === aggregate.app.spaceId) headlineTargetHash = targetHash;
3869
+ const markerHash = usingFromOverride ? fromOverrideHash : database.markersBySpace.get(entry.space)?.storageHash;
3870
+ const originHash = originHashForStatus(markerHash);
3871
+ const markerInGraph = markerHash === void 0 || graph.nodes.has(markerHash) || markerHash === spaceContractHash;
3872
+ if (connects && markerInGraph && originHash !== targetHash && noPath === void 0 && !hasMigrationPath(graph, originHash, targetHash)) noPath = {
3873
+ markerHash,
3874
+ targetHash
3875
+ };
3876
+ if (connects && markerHash !== void 0 && !markerInGraph) {
3877
+ divergedMarker ??= {
3878
+ space: entry.space,
3879
+ markerHash
3880
+ };
3881
+ findings.push(markerNotInHistoryFinding(entry.space));
3882
+ }
3883
+ const ledger = database.ledgersBySpace.get(entry.space) ?? [];
3884
+ const annotations = deriveStatusEdgeAnnotations({
3885
+ graph,
3886
+ targetHash,
3887
+ originHash,
3888
+ appliedMigrationHashes: connects ? appliedHashesFromLedger(ledger) : /* @__PURE__ */ new Set(),
3889
+ showAppliedOverlay: connects
3890
+ });
3891
+ const migrations = entry.migrations.map((migration) => ({
3892
+ ...migration,
3893
+ status: statusForMigrationHash(migration.hash, annotations)
3894
+ }));
3895
+ totalPending += migrations.filter((migration) => migration.status === "pending").length;
3896
+ statusSpaces.push({
3897
+ space: entry.space,
3898
+ currentContract: markerHash ?? null,
3899
+ targetContract: targetHash,
3900
+ migrations
3901
+ });
3902
+ if (graph.nodes.size === 0) {
3903
+ emptySpaces.push(entry.space);
3904
+ continue;
3905
+ }
3906
+ renderInputs.push({
3907
+ spaceId: entry.space,
3908
+ graph,
3909
+ migrations: entry.migrations,
3910
+ liveContractHash: contractHash,
3911
+ refsByHash: listRefsByContractHash(space),
3912
+ statusOverlayByHash: annotations,
3913
+ colorize: true,
3914
+ glyphMode,
3915
+ styler,
3916
+ palette: TONE_MIGRATION_GRAPH_PALETTE,
3917
+ isAppSpace: entry.space === aggregate.app.spaceId,
3918
+ ...connects && markerHash !== void 0 ? { dbHash: markerHash } : {}
3919
+ });
3920
+ }
3921
+ const requiredInvariants = [...activeRefEntry?.invariants ?? []].sort();
3922
+ if (connects && requiredInvariants.length > 0) {
3923
+ const held = new Set(appMarker?.invariants ?? []);
3924
+ const missing = requiredInvariants.filter((id) => !held.has(id));
3925
+ if (missing.length > 0) {
3926
+ findings.push(missingInvariantsFinding({
3927
+ missing,
3928
+ refName: activeRefName
3929
+ }));
3930
+ if (activeRefHash !== void 0) {
3931
+ const unreachable = refuseMissingInvariantPath({
3932
+ graph: appGraph,
3933
+ originHash: originHashForStatus(appMarker?.storageHash),
3934
+ targetHash: activeRefHash,
3935
+ missing,
3936
+ ...ifDefined("refName", activeRefName)
3937
+ });
3938
+ if (unreachable) return notOk(normalizeError(unreachable));
3939
+ }
3940
+ }
3941
+ }
3942
+ const rendered = renderMigrationGraphSpaceTrees(renderInputs);
3943
+ const treesBySpace = new Map(renderInputs.map((input, index) => [input.spaceId, rendered[index] ?? ""]));
3944
+ const sections = scopedSpaces.map((entry) => {
3945
+ const tree = treesBySpace.get(entry.space) ?? "";
3946
+ return {
3947
+ space: entry.space,
3948
+ tree: showSpaceHeadings && tree.length > 0 ? indentMigrationGraphTreeBlock(tree, " ") : tree,
3949
+ showHeading: showSpaceHeadings
3950
+ };
3951
+ });
3952
+ const summary = scopedSpaces.every((entry) => entry.migrations.length === 0) ? "No migrations found" : noPath !== void 0 ? buildNoPathSummary({
3953
+ markerHash: noPath.markerHash,
3954
+ targetHash: noPath.targetHash,
3955
+ explicitTarget: args.flags.to !== void 0,
3956
+ refName: activeRefName
3957
+ }) : buildStatusHeadline({
3958
+ pendingCount: totalPending,
3959
+ targetHash: headlineTargetHash,
3960
+ markerDiverged: divergedMarker !== void 0,
3961
+ markerHash: divergedMarker?.markerHash
3962
+ });
3963
+ const diagnostics = findings.map((finding) => finding.diagnostic);
3964
+ const document = {
3965
+ ok: true,
3966
+ spaces: statusSpaces,
3967
+ summary,
3968
+ diagnostics: findings.map((finding) => finding.document)
3969
+ };
3970
+ const alarming = divergedMarker !== void 0 || totalPending > 0 || noPath !== void 0;
3971
+ return ok(ctx.present({
3972
+ data: document,
3973
+ diagnostics
3974
+ }, statusPresentations({
3975
+ document,
3976
+ sections,
3977
+ headline: {
3978
+ kind: "summary",
3979
+ status: alarming ? "warn" : "ok",
3980
+ text: summary
3981
+ },
3982
+ migrationsDir: displayPath(migrationsDir, ctx.cwd),
3983
+ database: connects && typeof dbConnection === "string" ? maskConnectionUrl(dbConnection) : void 0,
3984
+ ref: activeRefName,
3985
+ from: args.flags.from,
3986
+ space: args.flags.space,
3987
+ legend: args.flags.legend ? renderMigrationGraphLegend({
3988
+ colorize: true,
3989
+ glyphMode,
3990
+ styler,
3991
+ palette: TONE_MIGRATION_GRAPH_PALETTE
3992
+ }) : void 0
3993
+ })));
3994
+ }
3995
+ });
2339
3996
  function deletePresentations(document) {
2340
3997
  return {
2341
3998
  human: () => [{
@@ -2509,25 +4166,56 @@ const ormCommandFamily = defineCommandFamily({
2509
4166
  "db schema": dbSchemaCommand,
2510
4167
  "db update": dbUpdateCommand,
2511
4168
  format: formatCommand,
4169
+ init: initCommand,
2512
4170
  migrate: migrateCommand,
2513
4171
  "migration graph": migrationGraphCommand,
2514
4172
  "migration list": migrationListCommand,
2515
4173
  "migration log": migrationLogCommand,
4174
+ "migration new": migrationNewCommand,
4175
+ "migration plan": migrationPlanCommand,
2516
4176
  "migration show": migrationShowCommand,
4177
+ "migration status": migrationStatusCommand,
2517
4178
  "ref delete": refDeleteCommand,
2518
4179
  "ref list": refListCommand,
2519
4180
  "ref set": refSetCommand
2520
4181
  },
2521
4182
  docsBaseUrl: DOCS_BASE_URL,
2522
- redirects: [{
2523
- from: "migration apply",
2524
- replacement: "{bin} migrate --to <contract>",
2525
- reason: "Applying a migration is a move to a target contract, not a verb of its own."
2526
- }, {
2527
- from: "migration ref",
2528
- replacement: "{bin} ref set|list|delete",
2529
- reason: "Refs are managed by their own command, for every space rather than migrations alone."
2530
- }]
4183
+ redirects: [
4184
+ {
4185
+ from: "migration apply",
4186
+ replacement: "{bin} migrate --to <contract>",
4187
+ reason: "Applying a migration is a move to a target contract, not a verb of its own."
4188
+ },
4189
+ {
4190
+ from: "migration ref",
4191
+ replacement: "{bin} ref set|list|delete",
4192
+ reason: "Refs are managed by their own command, for every space rather than migrations alone."
4193
+ },
4194
+ {
4195
+ from: "migration status",
4196
+ flag: "graph",
4197
+ replacement: "{bin} migration graph",
4198
+ reason: "Topology is its own command rather than a mode of the status report."
4199
+ },
4200
+ {
4201
+ from: "migration status",
4202
+ flag: "all",
4203
+ replacement: "{bin} migration log --db <url>",
4204
+ reason: "Execution history is read from the database ledger, which is what migration log does."
4205
+ },
4206
+ {
4207
+ from: "migration status",
4208
+ flag: "limit",
4209
+ replacement: "{bin} migration log --db <url>",
4210
+ reason: "Execution history is read from the database ledger, which is what migration log does."
4211
+ },
4212
+ {
4213
+ from: "migration status",
4214
+ flag: "ref",
4215
+ replacement: "{bin} migration status --to <contract>",
4216
+ reason: "One flag names the target contract, whether it is a ref, a hash or a migration."
4217
+ }
4218
+ ]
2531
4219
  });
2532
4220
  /** The file the ORM's loader reads when `--config` names none. */
2533
4221
  const ORM_CONFIG_FILENAME = "prisma-next.config.ts";