@prisma-next/cli 0.11.0 → 0.12.0

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 (196) hide show
  1. package/README.md +13 -9
  2. package/dist/cli.mjs +259 -12
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/{client-oXO2WCPD.mjs → client-KgJorIvG.mjs} +72 -60
  5. package/dist/client-KgJorIvG.mjs.map +1 -0
  6. package/dist/{command-helpers-BSb0tRC8.mjs → command-helpers-Bbw1GbwL.mjs} +646 -46
  7. package/dist/command-helpers-Bbw1GbwL.mjs.map +1 -0
  8. package/dist/commands/contract-emit.d.mts.map +1 -1
  9. package/dist/commands/contract-emit.mjs +1 -1
  10. package/dist/commands/contract-infer.d.mts.map +1 -1
  11. package/dist/commands/contract-infer.mjs +1 -1
  12. package/dist/commands/db-init.d.mts.map +1 -1
  13. package/dist/commands/db-init.mjs +32 -7
  14. package/dist/commands/db-init.mjs.map +1 -1
  15. package/dist/commands/db-schema.d.mts.map +1 -1
  16. package/dist/commands/db-schema.mjs +3 -4
  17. package/dist/commands/db-schema.mjs.map +1 -1
  18. package/dist/commands/db-sign.d.mts.map +1 -1
  19. package/dist/commands/db-sign.mjs +12 -10
  20. package/dist/commands/db-sign.mjs.map +1 -1
  21. package/dist/commands/db-update.d.mts.map +1 -1
  22. package/dist/commands/db-update.mjs +41 -11
  23. package/dist/commands/db-update.mjs.map +1 -1
  24. package/dist/commands/db-verify.d.mts.map +1 -1
  25. package/dist/commands/db-verify.mjs +1 -1
  26. package/dist/commands/migrate.d.mts +6 -2
  27. package/dist/commands/migrate.d.mts.map +1 -1
  28. package/dist/commands/migrate.mjs +75 -40
  29. package/dist/commands/migrate.mjs.map +1 -1
  30. package/dist/commands/migration-check.d.mts +4 -3
  31. package/dist/commands/migration-check.d.mts.map +1 -1
  32. package/dist/commands/migration-check.mjs +1 -280
  33. package/dist/commands/migration-graph.d.mts +13 -2
  34. package/dist/commands/migration-graph.d.mts.map +1 -1
  35. package/dist/commands/migration-graph.mjs +2 -137
  36. package/dist/commands/migration-list.d.mts +64 -4
  37. package/dist/commands/migration-list.d.mts.map +1 -1
  38. package/dist/commands/migration-list.mjs +143 -56
  39. package/dist/commands/migration-list.mjs.map +1 -1
  40. package/dist/commands/migration-log.d.mts +10 -1
  41. package/dist/commands/migration-log.d.mts.map +1 -1
  42. package/dist/commands/migration-log.mjs +10 -15
  43. package/dist/commands/migration-log.mjs.map +1 -1
  44. package/dist/commands/migration-new.d.mts.map +1 -1
  45. package/dist/commands/migration-new.mjs +32 -38
  46. package/dist/commands/migration-new.mjs.map +1 -1
  47. package/dist/commands/migration-plan.d.mts +3 -2
  48. package/dist/commands/migration-plan.d.mts.map +1 -1
  49. package/dist/commands/migration-plan.mjs +1 -1
  50. package/dist/commands/migration-show.d.mts +4 -55
  51. package/dist/commands/migration-show.d.mts.map +1 -1
  52. package/dist/commands/migration-show.mjs +61 -153
  53. package/dist/commands/migration-show.mjs.map +1 -1
  54. package/dist/commands/migration-status.d.mts +12 -49
  55. package/dist/commands/migration-status.d.mts.map +1 -1
  56. package/dist/commands/migration-status.mjs +85 -81
  57. package/dist/commands/migration-status.mjs.map +1 -1
  58. package/dist/commands/ref.d.mts +1 -1
  59. package/dist/commands/ref.d.mts.map +1 -1
  60. package/dist/commands/ref.mjs +38 -10
  61. package/dist/commands/ref.mjs.map +1 -1
  62. package/dist/config-loader-B6sJjXTv.mjs.map +1 -1
  63. package/dist/config-loader.d.mts.map +1 -1
  64. package/dist/contract-at-errors-BxP-TOMl.mjs +42 -0
  65. package/dist/contract-at-errors-BxP-TOMl.mjs.map +1 -0
  66. package/dist/{contract-emit-bcrpT-wD.mjs → contract-emit-D-4jrNve.mjs} +25 -10
  67. package/dist/contract-emit-D-4jrNve.mjs.map +1 -0
  68. package/dist/{contract-emit-r4y8Zhf1.mjs → contract-emit-DxcGl4Uq.mjs} +19 -14
  69. package/dist/contract-emit-DxcGl4Uq.mjs.map +1 -0
  70. package/dist/{contract-enrichment-Dani0mMW.mjs → contract-enrichment-a0V5Y_mL.mjs} +4 -25
  71. package/dist/contract-enrichment-a0V5Y_mL.mjs.map +1 -0
  72. package/dist/{contract-infer-BmySmqVT.mjs → contract-infer-D8uEbJuu.mjs} +4 -5
  73. package/dist/{contract-infer-BmySmqVT.mjs.map → contract-infer-D8uEbJuu.mjs.map} +1 -1
  74. package/dist/contract-space-aggregate-loader-DvZwdkrr.mjs +247 -0
  75. package/dist/contract-space-aggregate-loader-DvZwdkrr.mjs.map +1 -0
  76. package/dist/{db-verify-BClPs3ph.mjs → db-verify-v_vUKXTU.mjs} +5 -7
  77. package/dist/{db-verify-BClPs3ph.mjs.map → db-verify-v_vUKXTU.mjs.map} +1 -1
  78. package/dist/exports/control-api.d.mts +3 -3
  79. package/dist/exports/control-api.d.mts.map +1 -1
  80. package/dist/exports/control-api.mjs +3 -3
  81. package/dist/exports/index.d.mts.map +1 -1
  82. package/dist/exports/index.mjs +1 -1
  83. package/dist/exports/index.mjs.map +1 -1
  84. package/dist/exports/init-output.d.mts.map +1 -1
  85. package/dist/exports/init-output.mjs +1 -1
  86. package/dist/extension-pack-inputs-IDvjRCi3.mjs +62 -0
  87. package/dist/extension-pack-inputs-IDvjRCi3.mjs.map +1 -0
  88. package/dist/{framework-components-65gOHkHB.mjs → framework-components-fYXjz_in.mjs} +2 -2
  89. package/dist/{framework-components-65gOHkHB.mjs.map → framework-components-fYXjz_in.mjs.map} +1 -1
  90. package/dist/global-flags-DEHjV8_s.d.mts +34 -0
  91. package/dist/global-flags-DEHjV8_s.d.mts.map +1 -0
  92. package/dist/{graph-render-DJVv0_uf.mjs → graph-render-rFAqZujX.mjs} +2 -2
  93. package/dist/{graph-render-DJVv0_uf.mjs.map → graph-render-rFAqZujX.mjs.map} +1 -1
  94. package/dist/{init-BCJZPWE1.mjs → init-Cv9UzWL5.mjs} +20 -269
  95. package/dist/init-Cv9UzWL5.mjs.map +1 -0
  96. package/dist/{inspect-live-schema-DSRbFoOL.mjs → inspect-live-schema-C6ohV_oQ.mjs} +4 -5
  97. package/dist/{inspect-live-schema-DSRbFoOL.mjs.map → inspect-live-schema-C6ohV_oQ.mjs.map} +1 -1
  98. package/dist/migration-check-BiBJoYYW.mjs +341 -0
  99. package/dist/migration-check-BiBJoYYW.mjs.map +1 -0
  100. package/dist/migration-cli.d.mts.map +1 -1
  101. package/dist/migration-cli.mjs +4 -4
  102. package/dist/migration-cli.mjs.map +1 -1
  103. package/dist/{migration-command-scaffold-Bzd9La5c.mjs → migration-command-scaffold-CjvwO6at.mjs} +4 -5
  104. package/dist/{migration-command-scaffold-Bzd9La5c.mjs.map → migration-command-scaffold-CjvwO6at.mjs.map} +1 -1
  105. package/dist/migration-graph-D7DVUElV.mjs +1232 -0
  106. package/dist/migration-graph-D7DVUElV.mjs.map +1 -0
  107. package/dist/migration-list-styler-BRwF4-gy.mjs +399 -0
  108. package/dist/migration-list-styler-BRwF4-gy.mjs.map +1 -0
  109. package/dist/{migration-plan-CFwqw3Gk.mjs → migration-plan-9DJ7q7_z.mjs} +372 -133
  110. package/dist/migration-plan-9DJ7q7_z.mjs.map +1 -0
  111. package/dist/{migration-types-BXWvz12q.d.mts → migration-types-D2FW63pr.d.mts} +1 -1
  112. package/dist/{migration-types-BXWvz12q.d.mts.map → migration-types-D2FW63pr.d.mts.map} +1 -1
  113. package/dist/{migrations-CwZMa1Ck.mjs → migrations-Cv2jxNNK.mjs} +12 -13
  114. package/dist/migrations-Cv2jxNNK.mjs.map +1 -0
  115. package/dist/{output-BlsrGMEF.mjs → output-B60Gw5fu.mjs} +1 -1
  116. package/dist/{output-BlsrGMEF.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
  117. package/dist/{progress-adapter-DFfvZcYL.mjs → progress-adapter-C644QK8l.mjs} +1 -1
  118. package/dist/{progress-adapter-DFfvZcYL.mjs.map → progress-adapter-C644QK8l.mjs.map} +1 -1
  119. package/dist/ref-advancement-DUZqsue6.mjs +50 -0
  120. package/dist/ref-advancement-DUZqsue6.mjs.map +1 -0
  121. package/dist/terminal-ui-5Y6mrg93.d.mts +133 -0
  122. package/dist/terminal-ui-5Y6mrg93.d.mts.map +1 -0
  123. package/dist/{types--CqjMdk0.d.mts → types-Dt_SfqFm.d.mts} +28 -28
  124. package/dist/types-Dt_SfqFm.d.mts.map +1 -0
  125. package/dist/{verify-Bom75OYI.mjs → verify-DCA9Sldu.mjs} +2 -2
  126. package/dist/{verify-Bom75OYI.mjs.map → verify-DCA9Sldu.mjs.map} +1 -1
  127. package/package.json +35 -24
  128. package/src/commands/contract-emit.ts +19 -7
  129. package/src/commands/contract-infer.ts +1 -1
  130. package/src/commands/db-init.ts +48 -2
  131. package/src/commands/db-sign.ts +9 -5
  132. package/src/commands/db-update.ts +54 -8
  133. package/src/commands/init/hygiene-gitattributes.ts +2 -2
  134. package/src/commands/init/index.ts +2 -1
  135. package/src/commands/init/templates/code-templates.ts +4 -2
  136. package/src/commands/init/templates/env.ts +13 -14
  137. package/src/commands/migrate.ts +125 -44
  138. package/src/commands/migration-check.ts +43 -83
  139. package/src/commands/migration-graph.ts +75 -60
  140. package/src/commands/migration-list.ts +220 -74
  141. package/src/commands/migration-log.ts +8 -14
  142. package/src/commands/migration-new.ts +44 -48
  143. package/src/commands/migration-plan.ts +412 -197
  144. package/src/commands/migration-show.ts +65 -284
  145. package/src/commands/migration-status.ts +127 -124
  146. package/src/commands/ref.ts +53 -8
  147. package/src/control-api/client.ts +0 -1
  148. package/src/control-api/contract-enrichment.ts +6 -42
  149. package/src/control-api/operations/{apply-aggregate.ts → apply.ts} +44 -75
  150. package/src/control-api/operations/contract-emit.ts +14 -6
  151. package/src/control-api/operations/{db-apply-aggregate.ts → db-apply.ts} +19 -19
  152. package/src/control-api/operations/db-init.ts +4 -4
  153. package/src/control-api/operations/db-update.ts +4 -4
  154. package/src/control-api/operations/db-verify.ts +15 -11
  155. package/src/control-api/operations/migration-apply.ts +56 -47
  156. package/src/control-api/types.ts +26 -27
  157. package/src/migration-cli.ts +4 -4
  158. package/src/utils/cli-errors.ts +234 -0
  159. package/src/utils/command-helpers.ts +9 -24
  160. package/src/utils/contract-at-errors.ts +96 -0
  161. package/src/utils/contract-space-aggregate-loader.ts +336 -117
  162. package/src/utils/formatters/migration-graph-layout.ts +1119 -0
  163. package/src/utils/formatters/migration-graph-rows.ts +336 -0
  164. package/src/utils/formatters/migration-graph-tree-render.ts +459 -0
  165. package/src/utils/formatters/migration-list-data-column.ts +115 -0
  166. package/src/utils/formatters/migration-list-graph-topology.ts +368 -0
  167. package/src/utils/formatters/migration-list-render.ts +191 -0
  168. package/src/utils/formatters/migration-list-styler.ts +63 -0
  169. package/src/utils/formatters/migration-list-types.ts +21 -0
  170. package/src/utils/formatters/migrations.ts +37 -46
  171. package/src/utils/glyph-mode.ts +22 -0
  172. package/src/utils/integrity-violation-to-check-failure.ts +130 -0
  173. package/src/utils/plan-resolution.ts +258 -0
  174. package/src/utils/ref-advancement.ts +68 -0
  175. package/src/utils/terminal-ui.ts +42 -1
  176. package/dist/cli-errors-Czmx92Zy.d.mts +0 -3
  177. package/dist/cli-errors-Djtz98Vm.mjs +0 -71
  178. package/dist/cli-errors-Djtz98Vm.mjs.map +0 -1
  179. package/dist/client-oXO2WCPD.mjs.map +0 -1
  180. package/dist/command-helpers-BSb0tRC8.mjs.map +0 -1
  181. package/dist/commands/migration-check.mjs.map +0 -1
  182. package/dist/commands/migration-graph.mjs.map +0 -1
  183. package/dist/contract-emit-bcrpT-wD.mjs.map +0 -1
  184. package/dist/contract-emit-r4y8Zhf1.mjs.map +0 -1
  185. package/dist/contract-enrichment-Dani0mMW.mjs.map +0 -1
  186. package/dist/contract-space-aggregate-loader-BmNQwlws.mjs +0 -160
  187. package/dist/contract-space-aggregate-loader-BmNQwlws.mjs.map +0 -1
  188. package/dist/global-flags-CdE7M0d9.d.mts +0 -15
  189. package/dist/global-flags-CdE7M0d9.d.mts.map +0 -1
  190. package/dist/init-BCJZPWE1.mjs.map +0 -1
  191. package/dist/migration-plan-CFwqw3Gk.mjs.map +0 -1
  192. package/dist/migrations-CwZMa1Ck.mjs.map +0 -1
  193. package/dist/rolldown-runtime-twds-ZHy.mjs +0 -14
  194. package/dist/terminal-ui-BiB_8KNo.mjs +0 -379
  195. package/dist/terminal-ui-BiB_8KNo.mjs.map +0 -1
  196. package/dist/types--CqjMdk0.d.mts.map +0 -1
@@ -1,6 +1,11 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
- import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { createControlStack } from '@prisma-next/framework-components/control';
4
+ import type { IntegrityViolation } from '@prisma-next/migration-tools/aggregate';
5
+ import { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';
3
6
  import { verifyMigrationHash } from '@prisma-next/migration-tools/hash';
7
+ import { readMigrationsDir } from '@prisma-next/migration-tools/io';
8
+ import { reconstructGraph } from '@prisma-next/migration-tools/migration-graph';
4
9
  import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
5
10
  import { parseMigrationRef } from '@prisma-next/migration-tools/ref-resolution';
6
11
  import { readRefs } from '@prisma-next/migration-tools/refs';
@@ -9,15 +14,20 @@ import { join, relative } from 'pathe';
9
14
  import { loadConfig } from '../config-loader';
10
15
  import {
11
16
  addGlobalOptions,
12
- loadMigrationPackages,
17
+ resolveContractPath,
13
18
  resolveMigrationPaths,
14
19
  setCommandDescriptions,
15
20
  setCommandExamples,
16
21
  setCommandSeeAlso,
17
22
  } from '../utils/command-helpers';
23
+ import { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';
18
24
  import { formatStyledHeader } from '../utils/formatters/styled';
19
25
  import type { CommonCommandOptions } from '../utils/global-flags';
20
26
  import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
27
+ import {
28
+ type CheckFailure,
29
+ integrityViolationToCheckFailure,
30
+ } from '../utils/integrity-violation-to-check-failure';
21
31
  import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
22
32
  import { INTEGRITY_FAILED, OK, PRECONDITION } from './migration-check/exit-codes';
23
33
 
@@ -25,12 +35,7 @@ interface MigrationCheckOptions extends CommonCommandOptions {
25
35
  readonly config?: string;
26
36
  }
27
37
 
28
- export interface CheckFailure {
29
- readonly pnCode: string;
30
- readonly where: string;
31
- readonly why: string;
32
- readonly fix: string;
33
- }
38
+ export type { CheckFailure } from '../utils/integrity-violation-to-check-failure';
34
39
 
35
40
  export interface MigrationCheckResult {
36
41
  readonly ok: boolean;
@@ -38,11 +43,6 @@ export interface MigrationCheckResult {
38
43
  readonly summary: string;
39
44
  }
40
45
 
41
- /**
42
- * Canonical user-facing locator for a check failure: the cwd-relative path
43
- * to the migration package directory. Surfacing the same shape across every
44
- * PN code means `--json` consumers can branch uniformly on `where`.
45
- */
46
46
  function migrationPathRelative(dirPath: string): string {
47
47
  return relative(process.cwd(), dirPath);
48
48
  }
@@ -63,21 +63,6 @@ function checkFileExists(dirPath: string, dirName: string, fileName: string): Ch
63
63
  return null;
64
64
  }
65
65
 
66
- /**
67
- * Within-migration snapshot-consistency check (PN-MIG-CHECK-005).
68
- *
69
- * Compares the migration's stored `metadata.to` against the `storageHash`
70
- * recorded in its on-disk `end-contract.json` snapshot. The two values are
71
- * independent on-disk records of the same fact (the migration's destination
72
- * contract); drift between them indicates the package is internally
73
- * corrupt. Cross-migration consistency (one migration's end-contract.json
74
- * agreeing with the next migration's start-contract.json) is a separate
75
- * check that requires shadow execution and is deferred to
76
- * `migration preflight`.
77
- *
78
- * Shared between the graph-wide and per-migration code paths so both report
79
- * the same failure for the same on-disk state.
80
- */
81
66
  function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | null {
82
67
  const endContractPath = join(pkg.dirPath, 'end-contract.json');
83
68
  if (!existsSync(endContractPath)) return null;
@@ -104,6 +89,27 @@ function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | n
104
89
  return null;
105
90
  }
106
91
 
92
+ async function loadAggregateIntegrityViolations(
93
+ config: Awaited<ReturnType<typeof loadConfig>>,
94
+ migrationsDir: string,
95
+ ): Promise<readonly IntegrityViolation[]> {
96
+ try {
97
+ const contractJsonContent = await readFile(resolveContractPath(config), 'utf-8');
98
+ const familyInstance = config.family.create(createControlStack(config));
99
+ const declaredExtensions = toDeclaredExtensionsFromRaw(config.extensionPacks ?? []);
100
+
101
+ const parsedAppContract: unknown = JSON.parse(contractJsonContent);
102
+ const aggregate = await loadContractSpaceAggregate({
103
+ migrationsDir,
104
+ deserializeContract: (json: unknown) => familyInstance.deserializeContract(json),
105
+ appContract: familyInstance.deserializeContract(parsedAppContract),
106
+ });
107
+ return aggregate.checkIntegrity({ declaredExtensions, checkContracts: true });
108
+ } catch {
109
+ return [];
110
+ }
111
+ }
112
+
107
113
  async function executeMigrationCheckCommand(
108
114
  target: string | undefined,
109
115
  options: MigrationCheckOptions,
@@ -111,10 +117,8 @@ async function executeMigrationCheckCommand(
111
117
  ui: TerminalUI,
112
118
  ): Promise<{ result: MigrationCheckResult; exitCode: number }> {
113
119
  const config = await loadConfig(options.config);
114
- const { configPath, appMigrationsDir, appMigrationsRelative, refsDir } = resolveMigrationPaths(
115
- options.config,
116
- config,
117
- );
120
+ const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative, refsDir } =
121
+ resolveMigrationPaths(options.config, config);
118
122
 
119
123
  if (!flags.json && !flags.quiet) {
120
124
  const details: Array<{ label: string; value: string }> = [
@@ -135,37 +139,9 @@ async function executeMigrationCheckCommand(
135
139
 
136
140
  const failures: CheckFailure[] = [];
137
141
 
138
- let bundles: Awaited<ReturnType<typeof loadMigrationPackages>>['bundles'];
139
- let graph: Awaited<ReturnType<typeof loadMigrationPackages>>['graph'];
140
- try {
141
- const loaded = await loadMigrationPackages(appMigrationsDir);
142
- bundles = loaded.bundles;
143
- graph = loaded.graph;
144
- } catch (error) {
145
- if (MigrationToolsError.is(error)) {
146
- const pnCode =
147
- error.code === 'MIGRATION.HASH_MISMATCH' ? 'PN-MIG-CHECK-001' : 'PN-MIG-CHECK-002';
148
- // Normalise to a cwd-relative path. `error.details.dir` is absolute
149
- // (the migration-tools layer doesn't know the caller's cwd); the
150
- // `filePath` fallback is also absolute. Surfacing the relative form
151
- // matches the rest of the command's `where` shape and keeps `--json`
152
- // consumers from having to special-case the bootstrap-failure path.
153
- const rawWhere =
154
- (error.details?.['dir'] as string) ?? (error.details?.['filePath'] as string) ?? null;
155
- const where = rawWhere ? relative(process.cwd(), rawWhere) : 'unknown';
156
- failures.push({
157
- pnCode,
158
- where,
159
- why: error.why,
160
- fix: error.fix,
161
- });
162
- return {
163
- result: { ok: false, failures, summary: `${failures.length} integrity failure(s)` },
164
- exitCode: INTEGRITY_FAILED,
165
- };
166
- }
167
- throw error;
168
- }
142
+ const loaded = await readMigrationsDir(appMigrationsDir);
143
+ const bundles: readonly OnDiskMigrationPackage[] = loaded.packages;
144
+ const graph = reconstructGraph(bundles);
169
145
 
170
146
  if (existsSync(appMigrationsDir)) {
171
147
  const loadedDirNames = new Set(bundles.map((p) => p.dirName));
@@ -236,29 +212,9 @@ async function executeMigrationCheckCommand(
236
212
  });
237
213
  }
238
214
 
239
- // PN-MIG-CHECK-005 must fire per-migration as well as graph-wide; both
240
- // call sites delegate to the shared helper so the same on-disk drift
241
- // produces the same failure regardless of how the user invoked check.
242
215
  const snapshotFailure = checkSnapshotConsistency(matchedPkg);
243
216
  if (snapshotFailure) failures.push(snapshotFailure);
244
217
  } else {
245
- for (const pkg of bundles) {
246
- for (const f of ['migration.json', 'ops.json']) {
247
- const fail = checkFileExists(pkg.dirPath, pkg.dirName, f);
248
- if (fail) failures.push(fail);
249
- }
250
-
251
- const verification = verifyMigrationHash(pkg);
252
- if (!verification.ok) {
253
- failures.push({
254
- pnCode: 'PN-MIG-CHECK-001',
255
- where: migrationFileRelative(pkg.dirPath, 'migration.json'),
256
- why: `Stored hash ${verification.storedHash} does not match recomputed hash ${verification.computedHash}`,
257
- fix: 'Re-emit the migration package or restore from version control.',
258
- });
259
- }
260
- }
261
-
262
218
  for (const pkg of bundles) {
263
219
  const snapshotFailure = checkSnapshotConsistency(pkg);
264
220
  if (snapshotFailure) failures.push(snapshotFailure);
@@ -295,6 +251,10 @@ async function executeMigrationCheckCommand(
295
251
  } catch {
296
252
  // Refs unreadable — skip ref checks
297
253
  }
254
+
255
+ for (const violation of await loadAggregateIntegrityViolations(config, migrationsDir)) {
256
+ failures.push(integrityViolationToCheckFailure(violation, migrationsDir));
257
+ }
298
258
  }
299
259
 
300
260
  if (failures.length === 0) {
@@ -1,26 +1,22 @@
1
1
  import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
2
- import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
3
2
  import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
4
- import { readRefs } from '@prisma-next/migration-tools/refs';
5
- import { notOk, ok, type Result } from '@prisma-next/utils/result';
3
+ import { ok, type Result } from '@prisma-next/utils/result';
6
4
  import { Command } from 'commander';
7
5
  import { loadConfig } from '../config-loader';
8
- import {
9
- type CliStructuredError,
10
- errorUnexpected,
11
- mapMigrationToolsError,
12
- } from '../utils/cli-errors';
6
+ import type { CliStructuredError } from '../utils/cli-errors';
13
7
  import {
14
8
  addGlobalOptions,
15
- loadMigrationPackages,
16
- readContractEnvelope,
17
9
  resolveMigrationPaths,
18
10
  setCommandDescriptions,
19
11
  setCommandExamples,
20
12
  setCommandSeeAlso,
21
13
  } from '../utils/command-helpers';
14
+ import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
22
15
  import { migrationGraphToRenderInput } from '../utils/formatters/graph-migration-mapper';
23
16
  import { graphRenderer } from '../utils/formatters/graph-render';
17
+ import { buildMigrationGraphLayout } from '../utils/formatters/migration-graph-layout';
18
+ import { buildMigrationGraphRows } from '../utils/formatters/migration-graph-rows';
19
+ import { renderMigrationGraphTree } from '../utils/formatters/migration-graph-tree-render';
24
20
  import { formatStyledHeader } from '../utils/formatters/styled';
25
21
  import type { CommonCommandOptions } from '../utils/global-flags';
26
22
  import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
@@ -31,6 +27,8 @@ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
31
27
  interface MigrationGraphOptions extends CommonCommandOptions {
32
28
  readonly config?: string;
33
29
  readonly dot?: boolean;
30
+ readonly tree?: boolean;
31
+ readonly ascii?: boolean;
34
32
  }
35
33
 
36
34
  export interface MigrationGraphResult {
@@ -41,13 +39,13 @@ export interface MigrationGraphResult {
41
39
  readonly summary: string;
42
40
  }
43
41
 
44
- async function executeMigrationGraphCommand(
42
+ export async function executeMigrationGraphCommand(
45
43
  options: MigrationGraphOptions,
46
44
  flags: GlobalFlags,
47
45
  ui: TerminalUI,
48
46
  ): Promise<Result<MigrationGraphResult, CliStructuredError>> {
49
47
  const config = await loadConfig(options.config);
50
- const { configPath, appMigrationsDir, appMigrationsRelative, refsDir } = resolveMigrationPaths(
48
+ const { configPath, appMigrationsRelative, migrationsDir } = resolveMigrationPaths(
51
49
  options.config,
52
50
  config,
53
51
  );
@@ -65,37 +63,18 @@ async function executeMigrationGraphCommand(
65
63
  ui.stderr(header);
66
64
  }
67
65
 
68
- let graph: MigrationGraph;
69
- try {
70
- ({ graph } = await loadMigrationPackages(appMigrationsDir));
71
- } catch (error) {
72
- if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
73
- return notOk(
74
- errorUnexpected(error instanceof Error ? error.message : String(error), {
75
- why: `Failed to read migrations: ${error instanceof Error ? error.message : String(error)}`,
76
- }),
77
- );
66
+ const loaded = await buildReadAggregate(config, { migrationsDir });
67
+ if (!loaded.ok) {
68
+ return loaded;
78
69
  }
79
70
 
80
- let contractHash: string | null = null;
81
- try {
82
- const envelope = await readContractEnvelope(config);
83
- contractHash = envelope.storageHash;
84
- } catch {
85
- // Contract unreadable — render graph without contract marker
86
- }
87
-
88
- let refs: readonly StatusRef[] = [];
89
- try {
90
- const allRefs = await readRefs(refsDir);
91
- refs = Object.entries(allRefs).map(([name, entry]) => ({
92
- name,
93
- hash: entry.hash,
94
- active: false,
95
- }));
96
- } catch {
97
- // Refs unreadable — render graph without ref markers
98
- }
71
+ const { aggregate, contractHash } = loaded.value;
72
+ const graph = aggregate.app.graph();
73
+ const refs: readonly StatusRef[] = Object.entries(aggregate.app.refs).map(([name, entry]) => ({
74
+ name,
75
+ hash: entry.hash,
76
+ active: false,
77
+ }));
99
78
 
100
79
  return ok({
101
80
  ok: true,
@@ -111,14 +90,18 @@ export function createMigrationGraphCommand(): Command {
111
90
  setCommandDescriptions(
112
91
  command,
113
92
  'Show the migration graph topology',
114
- 'Renders the migration graph as an ASCII tree. Offline — does not\n' +
115
- 'consult the database. Use --json for machine-readable output or\n' +
116
- '--dot for Graphviz DOT format.',
93
+ 'Renders the migration graph topology. Offline — does not consult\n' +
94
+ 'the database. Use --tree for the condensed annotated tree\n' +
95
+ '(--ascii swaps box-drawing for pipe-friendly ASCII glyphs),\n' +
96
+ '--json for machine-readable output, or --dot for Graphviz DOT\n' +
97
+ 'format.',
117
98
  );
118
99
  setCommandExamples(command, [
119
100
  'prisma-next migration graph',
120
101
  'prisma-next migration graph --json',
121
102
  'prisma-next migration graph --dot',
103
+ 'prisma-next migration graph --tree',
104
+ 'prisma-next migration graph --tree --ascii',
122
105
  ]);
123
106
  setCommandSeeAlso(command, [
124
107
  { verb: 'migration status', oneLiner: 'Show migration path and pending status' },
@@ -129,6 +112,8 @@ export function createMigrationGraphCommand(): Command {
129
112
  addGlobalOptions(command)
130
113
  .option('--config <path>', 'Path to prisma-next.config.ts')
131
114
  .option('--dot', 'Output in Graphviz DOT format')
115
+ .option('--tree', 'Experimental condensed annotated tree renderer')
116
+ .option('--ascii', 'Use ASCII glyphs for --tree (pipe-friendly)')
132
117
  .action(async (options: MigrationGraphOptions) => {
133
118
  const flags = parseGlobalFlagsOrExit(options);
134
119
  const ui = createTerminalUI(flags);
@@ -160,22 +145,52 @@ export function createMigrationGraphCommand(): Command {
160
145
  JSON.stringify({ ok: true, nodes, edges, summary: graphResult.summary }, null, 2),
161
146
  );
162
147
  } else if (!flags.quiet) {
163
- const renderInput = migrationGraphToRenderInput({
164
- graph: graphResult.graph,
165
- mode: 'offline',
166
- markerHash: undefined,
167
- contractHash: graphResult.contractHash ?? EMPTY_CONTRACT_HASH,
168
- refs: graphResult.refs,
169
- activeRefHash: undefined,
170
- activeRefName: undefined,
171
- edgeStatuses: [],
172
- });
173
- const graphOutput = graphRenderer.render(renderInput.graph, {
174
- ...renderInput.options,
175
- colorize: flags.color !== false,
176
- });
177
- ui.log(graphOutput);
178
- ui.log(`\n${graphResult.summary}`);
148
+ if (options.tree) {
149
+ const refsByHash = new Map<string, string[]>();
150
+ for (const ref of graphResult.refs) {
151
+ const existing = refsByHash.get(ref.hash);
152
+ refsByHash.set(ref.hash, existing ? [...existing, ref.name] : [ref.name]);
153
+ }
154
+ const rowModel = buildMigrationGraphRows(graphResult.graph, {
155
+ ...(graphResult.contractHash !== null
156
+ ? { contractHash: graphResult.contractHash }
157
+ : {}),
158
+ });
159
+ const layout = buildMigrationGraphLayout(rowModel);
160
+ const activeRef = graphResult.refs.find((ref) => ref.active);
161
+ const treeOutput = renderMigrationGraphTree(layout, {
162
+ refsByHash,
163
+ ...(graphResult.contractHash !== null
164
+ ? { contractHash: graphResult.contractHash }
165
+ : {}),
166
+ ...(activeRef !== undefined ? { activeRefName: activeRef.name } : {}),
167
+ colorize: flags.color !== false,
168
+ glyphMode: ui.resolveGlyphMode(options.ascii === true),
169
+ });
170
+ // Emit the rendered tree to stdout (same stream as flat `migration list`),
171
+ // not through clack's `log.message` rail: the graph is the command's
172
+ // result (and its own box-drawing is the only vertical structure it
173
+ // should carry), not a status line that needs the prompt gutter.
174
+ ui.output(treeOutput);
175
+ ui.output(`\n${graphResult.summary}`);
176
+ } else {
177
+ const renderInput = migrationGraphToRenderInput({
178
+ graph: graphResult.graph,
179
+ mode: 'offline',
180
+ markerHash: undefined,
181
+ contractHash: graphResult.contractHash ?? EMPTY_CONTRACT_HASH,
182
+ refs: graphResult.refs,
183
+ activeRefHash: undefined,
184
+ activeRefName: undefined,
185
+ edgeStatuses: [],
186
+ });
187
+ const graphOutput = graphRenderer.render(renderInput.graph, {
188
+ ...renderInput.options,
189
+ colorize: flags.color !== false,
190
+ });
191
+ ui.log(graphOutput);
192
+ ui.log(`\n${graphResult.summary}`);
193
+ }
179
194
  }
180
195
  });
181
196
  process.exit(exitCode);