@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
@@ -6,16 +6,10 @@ import {
6
6
  type MigrationPlanOperation,
7
7
  type OperationPreview,
8
8
  } from '@prisma-next/framework-components/control';
9
- import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
10
- import { readMigrationPackage, readMigrationsDir } from '@prisma-next/migration-tools/io';
11
- import {
12
- findLatestMigration,
13
- reconstructGraph,
14
- } from '@prisma-next/migration-tools/migration-graph';
9
+ import { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';
15
10
  import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
16
11
  import { parseMigrationRef } from '@prisma-next/migration-tools/ref-resolution';
17
- import { readRefs } from '@prisma-next/migration-tools/refs';
18
- import { spaceMigrationDirectory } from '@prisma-next/migration-tools/spaces';
12
+ import { castAs } from '@prisma-next/utils/casts';
19
13
  import { ifDefined } from '@prisma-next/utils/defined';
20
14
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
21
15
  import { Command } from 'commander';
@@ -28,7 +22,6 @@ import {
28
22
  errorFileNotFound,
29
23
  errorRuntime,
30
24
  errorUnexpected,
31
- mapMigrationToolsError,
32
25
  mapRefResolutionError,
33
26
  } from '../utils/cli-errors';
34
27
  import {
@@ -39,7 +32,6 @@ import {
39
32
  setCommandExamples,
40
33
  setCommandSeeAlso,
41
34
  } from '../utils/command-helpers';
42
- import { buildContractSpaceAggregate } from '../utils/contract-space-aggregate-loader';
43
35
  import { formatMigrationShowOutput } from '../utils/formatters/migrations';
44
36
  import { formatStyledHeader } from '../utils/formatters/styled';
45
37
  import type { CommonCommandOptions } from '../utils/global-flags';
@@ -51,11 +43,7 @@ interface MigrationShowOptions extends CommonCommandOptions {
51
43
  readonly config?: string;
52
44
  }
53
45
 
54
- /**
55
- * Details of one space's latest (or targeted) migration package.
56
- */
57
- export interface MigrationShowSpacePresent {
58
- readonly kind: 'present';
46
+ export interface MigrationShowPresent {
59
47
  readonly spaceId: string;
60
48
  readonly dirName: string;
61
49
  readonly dirPath: string;
@@ -68,56 +56,19 @@ export interface MigrationShowSpacePresent {
68
56
  readonly label: string;
69
57
  readonly operationClass: string;
70
58
  }[];
71
- /**
72
- * Family-agnostic textual preview of the migration's operations. Always
73
- * defined; statements is empty for a no-op migration or a family that does
74
- * not implement the `OperationPreviewCapable` capability.
75
- */
76
59
  readonly preview: OperationPreview;
77
60
  readonly summary: string;
78
61
  }
79
62
 
80
- /**
81
- * Placeholder for a loaded contract space that has no on-disk migration
82
- * package — the extension descriptor declared the space but no migrations
83
- * directory has been materialised for it yet. Surfaces the space in the
84
- * response so JSON consumers see every loaded extension instead of having
85
- * silently-skipped entries.
86
- */
87
- export interface MigrationShowSpaceMissing {
88
- readonly kind: 'missing';
89
- readonly spaceId: string;
90
- readonly summary: string;
91
- }
92
-
93
- export type MigrationShowSpaceResult = MigrationShowSpacePresent | MigrationShowSpaceMissing;
94
-
95
63
  export interface MigrationShowResult {
96
64
  readonly ok: true;
97
- /**
98
- * Per-space results, ordered: app first, then extensions alphabetically
99
- * (matching the aggregate's canonical ordering).
100
- */
101
- readonly spaces: readonly MigrationShowSpaceResult[];
65
+ readonly migration: MigrationShowPresent;
102
66
  }
103
67
 
104
68
  function looksLikePath(target: string): boolean {
105
69
  return target.includes('/') || target.includes('\\');
106
70
  }
107
71
 
108
- /**
109
- * Validate that a path-like `migration show` target resolves inside the app
110
- * migrations directory. The returned result is always emitted under
111
- * `aggregate.app.spaceId`, so accepting an extension-space (or otherwise
112
- * external) path here would silently mislabel the result. Returns the
113
- * resolved absolute path on success.
114
- *
115
- * `pathe.relative` can return an absolute path when the target cannot be
116
- * expressed relative to the base (e.g. on Windows when `target` is on a
117
- * different drive than `appMigrationsDir`). That case does not start with
118
- * `..`, so the absolute-check below is required to reject cross-drive
119
- * targets rather than mislabeling them as app-space.
120
- */
121
72
  export function resolveAppTargetPath(
122
73
  target: string,
123
74
  appMigrationsDir: string,
@@ -141,83 +92,14 @@ export function resolveAppTargetPath(
141
92
  return ok(targetPath);
142
93
  }
143
94
 
144
- export function resolveByHashPrefix(
145
- packages: readonly OnDiskMigrationPackage[],
146
- prefix: string,
147
- ): Result<OnDiskMigrationPackage, CliStructuredError> {
148
- const normalizedPrefix = prefix.startsWith('sha256:') ? prefix : `sha256:${prefix}`;
149
- const matches = packages.filter((p) => p.metadata.migrationHash.startsWith(normalizedPrefix));
150
-
151
- if (matches.length === 1) {
152
- return ok(matches[0]!);
153
- }
154
-
155
- if (matches.length === 0) {
156
- return notOk(
157
- errorRuntime('No migration found matching prefix', {
158
- why: `No migration has a migrationHash starting with "${normalizedPrefix}"`,
159
- fix: 'Run `prisma-next migration show` (no argument) to see the latest migration, or check the migrations directory for available packages.',
160
- }),
161
- );
162
- }
163
-
164
- const candidates = matches.map((p) => ` ${p.dirName} ${p.metadata.migrationHash}`).join('\n');
165
- return notOk(
166
- errorRuntime('Ambiguous hash prefix', {
167
- why: `Multiple migrations match prefix "${normalizedPrefix}":\n${candidates}`,
168
- fix: 'Provide a longer prefix to uniquely identify the migration.',
169
- }),
170
- );
171
- }
172
-
173
- /**
174
- * Resolve the latest migration from a space directory.
175
- *
176
- * Returns `ok(null)` only when the directory is empty or absent (ENOENT is
177
- * absorbed by `readMigrationsDir`). If `readMigrationsDir` returned packages
178
- * but `findLatestMigration` cannot pick a leaf, the on-disk history is
179
- * corrupt — return a runtime error rather than collapsing it to a `missing`
180
- * placeholder, which would hide the corruption from the caller.
181
- */
182
- export async function resolveLatestFromDir(
183
- spaceDir: string,
184
- ): Promise<Result<OnDiskMigrationPackage | null, CliStructuredError>> {
185
- try {
186
- const allPackages = await readMigrationsDir(spaceDir);
187
- if (allPackages.length === 0) return ok(null);
188
- const graph = reconstructGraph(allPackages);
189
- const latestMigration = findLatestMigration(graph);
190
- if (!latestMigration) {
191
- return notOk(
192
- errorRuntime('Could not resolve latest migration', {
193
- why: `No latest migration found in ${relative(process.cwd(), spaceDir)}`,
194
- fix: 'The migrations directory may be corrupted. Inspect the migration.json files.',
195
- }),
196
- );
197
- }
198
- const leafPkg = allPackages.find(
199
- (p) => p.metadata.migrationHash === latestMigration.migrationHash,
200
- );
201
- return ok(leafPkg ?? null);
202
- } catch (error) {
203
- if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
204
- return notOk(
205
- errorUnexpected(error instanceof Error ? error.message : String(error), {
206
- why: `Failed to read migrations: ${error instanceof Error ? error.message : String(error)}`,
207
- }),
208
- );
209
- }
210
- }
211
-
212
- function pkgToSpaceResult(
95
+ function pkgToPresent(
213
96
  spaceId: string,
214
97
  pkg: OnDiskMigrationPackage,
215
98
  client: ReturnType<typeof createControlClient>,
216
- ): MigrationShowSpacePresent {
217
- const ops = pkg.ops as readonly MigrationPlanOperation[];
99
+ ): MigrationShowPresent {
100
+ const ops = castAs<readonly MigrationPlanOperation[]>(pkg.ops);
218
101
  const preview: OperationPreview = client.toOperationPreview(ops) ?? { statements: [] };
219
102
  return {
220
- kind: 'present',
221
103
  spaceId,
222
104
  dirName: pkg.dirName,
223
105
  dirPath: relative(process.cwd(), pkg.dirPath),
@@ -235,40 +117,42 @@ function pkgToSpaceResult(
235
117
  };
236
118
  }
237
119
 
120
+ function findPackageByDirPath(
121
+ packages: readonly OnDiskMigrationPackage[],
122
+ resolvedDirPath: string,
123
+ ): OnDiskMigrationPackage | undefined {
124
+ const normalized = resolve(resolvedDirPath);
125
+ return packages.find((p) => resolve(p.dirPath) === normalized);
126
+ }
127
+
238
128
  async function executeMigrationShowCommand(
239
- target: string | undefined,
129
+ target: string,
240
130
  options: MigrationShowOptions,
241
131
  flags: GlobalFlags,
242
132
  ui: TerminalUI,
243
133
  ): Promise<Result<MigrationShowResult, CliStructuredError>> {
244
134
  const config = await loadConfig(options.config);
245
- const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative, refsDir } =
135
+ const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative } =
246
136
  resolveMigrationPaths(options.config, config);
247
137
 
248
138
  const contractPathAbsolute = resolveContractPath(config);
249
139
  const contractPath = relative(process.cwd(), contractPathAbsolute);
250
140
 
251
141
  if (!flags.json && !flags.quiet) {
252
- const details: Array<{ label: string; value: string }> = [
253
- { label: 'config', value: configPath },
254
- { label: 'contract', value: contractPath },
255
- { label: 'migrations', value: appMigrationsRelative },
256
- ];
257
- if (target) {
258
- details.push({ label: 'target', value: target });
259
- }
260
142
  const header = formatStyledHeader({
261
143
  command: 'migration show',
262
144
  description: 'Display migration package contents',
263
- details,
145
+ details: [
146
+ { label: 'config', value: configPath },
147
+ { label: 'contract', value: contractPath },
148
+ { label: 'migrations', value: appMigrationsRelative },
149
+ { label: 'target', value: target },
150
+ ],
264
151
  flags,
265
152
  });
266
153
  ui.stderr(header);
267
154
  }
268
155
 
269
- // `migration show` is an offline command; the control client is constructed
270
- // purely to dispatch the family-specific `toOperationPreview` capability and
271
- // is not connected to a database.
272
156
  const client = createControlClient({
273
157
  family: config.family,
274
158
  target: config.target,
@@ -277,85 +161,11 @@ async function executeMigrationShowCommand(
277
161
  extensionPacks: config.extensionPacks ?? [],
278
162
  });
279
163
 
280
- // Explicit-target path. Read the app-space migrations directory directly
281
- // and resolve `target` against the app graph. We deliberately skip
282
- // `buildContractSpaceAggregate` here for two reasons:
283
- //
284
- // 1. Functional: the user asked about ONE specific migration. They don't
285
- // need extension-space enumeration; resolving + rendering the named
286
- // package is enough.
287
- // 2. UX: the aggregate's layout-integrity check (PN-MIG-5001) fires when
288
- // an extension is declared but its migrations directory hasn't been
289
- // materialised. Gating an offline read-only inspect command on that
290
- // check forces users to run `migrate` against a database before they
291
- // can see what a migration contains — which contradicts what an
292
- // offline read-only verb should require.
293
- //
294
- // Same pattern as `migration list`, `migration graph`, `migration check`:
295
- // those verbs read `appMigrationsDir` directly without ever consulting
296
- // the aggregate.
297
- if (target) {
298
- try {
299
- let appPkg: OnDiskMigrationPackage;
300
- if (looksLikePath(target)) {
301
- const resolved = resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative);
302
- if (!resolved.ok) return resolved;
303
- appPkg = await readMigrationPackage(resolved.value);
304
- } else {
305
- const allPackages = await readMigrationsDir(appMigrationsDir);
306
- if (allPackages.length === 0) {
307
- return notOk(
308
- errorRuntime('No migrations found', {
309
- why: `No migration packages found in ${appMigrationsRelative}`,
310
- fix: 'Run `prisma-next migration plan` to create a migration first.',
311
- }),
312
- );
313
- }
314
- const graph = reconstructGraph(allPackages);
315
- const refs = await readRefs(refsDir);
316
- const migResult = parseMigrationRef(target, { graph, refs });
317
- if (!migResult.ok) {
318
- return notOk(mapRefResolutionError(migResult.failure));
319
- }
320
- const matchedPkg = allPackages.find(
321
- (p) => p.metadata.migrationHash === migResult.value.migrationHash,
322
- );
323
- if (!matchedPkg) {
324
- return notOk(
325
- errorRuntime('Migration package not found', {
326
- why: `Resolved migration "${migResult.value.dirName}" but the package was not loaded`,
327
- fix: 'The migrations directory may be corrupted. Inspect the migration.json files.',
328
- }),
329
- );
330
- }
331
- appPkg = matchedPkg;
332
- }
333
- return ok({
334
- ok: true,
335
- spaces: [pkgToSpaceResult(APP_SPACE_ID, appPkg, client)],
336
- });
337
- } catch (error) {
338
- if (MigrationToolsError.is(error)) {
339
- return notOk(mapMigrationToolsError(error));
340
- }
341
- return notOk(
342
- errorUnexpected(error instanceof Error ? error.message : String(error), {
343
- why: `Failed to read app-space migration: ${error instanceof Error ? error.message : String(error)}`,
344
- }),
345
- );
346
- }
347
- }
348
-
349
- // No-target path. Enumerate the latest migration per space (app +
350
- // extensions). The aggregate-loader is needed here because we need to
351
- // know which extension spaces are declared; its layout-integrity check
352
- // is appropriate at this entry point because the user is asking the
353
- // system to report on every loaded space.
354
164
  let contractJsonContent: string;
355
165
  try {
356
166
  contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');
357
167
  } catch (error) {
358
- if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {
168
+ if (error instanceof Error && (error as NodeJS.ErrnoException).code === 'ENOENT') {
359
169
  return notOk(
360
170
  errorFileNotFound(contractPathAbsolute, {
361
171
  why: `Contract file not found at ${contractPathAbsolute}`,
@@ -370,15 +180,14 @@ async function executeMigrationShowCommand(
370
180
  );
371
181
  }
372
182
 
373
- // Construct the family instance up-front so the on-disk app contract
374
- // read crosses the serializer seam (`familyInstance.deserializeContract`)
375
- // at the read site. See TML-2536.
376
183
  const stack = createControlStack(config);
377
184
  const familyInstance = config.family.create(stack);
378
185
 
379
186
  let appContract: Contract;
380
187
  try {
381
- appContract = familyInstance.deserializeContract(JSON.parse(contractJsonContent) as unknown);
188
+ appContract = familyInstance.deserializeContract(
189
+ castAs<unknown>(JSON.parse(contractJsonContent)),
190
+ );
382
191
  } catch (error) {
383
192
  return notOk(
384
193
  errorContractValidationFailed(
@@ -388,85 +197,61 @@ async function executeMigrationShowCommand(
388
197
  );
389
198
  }
390
199
 
391
- const aggregateResult = await buildContractSpaceAggregate({
392
- targetId: config.target.targetId,
200
+ const aggregate = await loadContractSpaceAggregate({
393
201
  migrationsDir,
394
202
  appContract,
395
- extensionPacks: config.extensionPacks ?? [],
396
203
  deserializeContract: (json: unknown) => familyInstance.deserializeContract(json),
397
204
  });
398
- if (!aggregateResult.ok) {
399
- return notOk(aggregateResult.failure);
400
- }
401
- const aggregate = aggregateResult.value;
402
205
 
403
- const spaces: MigrationShowSpaceResult[] = [];
206
+ const packages = aggregate.app.packages;
207
+ const graph = aggregate.app.graph();
208
+ const refs = aggregate.app.refs;
404
209
 
405
- // App space: latest leaf.
406
- try {
407
- const allPackages = await readMigrationsDir(appMigrationsDir);
408
- if (allPackages.length === 0) {
210
+ let appPkg: OnDiskMigrationPackage;
211
+ if (looksLikePath(target)) {
212
+ const resolved = resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative);
213
+ if (!resolved.ok) return resolved;
214
+ const matched = findPackageByDirPath(packages, resolved.value);
215
+ if (!matched) {
409
216
  return notOk(
410
- errorRuntime('No migrations found', {
411
- why: `No migration packages found in ${appMigrationsRelative}`,
412
- fix: 'Run `prisma-next migration plan` to create a migration first.',
217
+ errorRuntime('Migration package not found', {
218
+ why: `No loaded migration package at ${relative(process.cwd(), resolved.value)}`,
219
+ fix: 'Pass a directory name, hash prefix, or path to an on-disk app-space migration package.',
413
220
  }),
414
221
  );
415
222
  }
416
- const graph = reconstructGraph(allPackages);
417
- const latestMigration = findLatestMigration(graph);
418
- if (!latestMigration) {
223
+ appPkg = matched;
224
+ } else {
225
+ if (packages.length === 0) {
419
226
  return notOk(
420
- errorRuntime('Could not resolve latest migration', {
421
- why: 'No latest migration found in the migration history',
422
- fix: 'The migrations directory may be corrupted. Inspect the migration.json files.',
227
+ errorRuntime('No migrations found', {
228
+ why: `No migration packages found in ${appMigrationsRelative}`,
229
+ fix: 'Run `prisma-next migration plan` to create a migration first.',
423
230
  }),
424
231
  );
425
232
  }
426
- const leafPkg = allPackages.find(
427
- (p) => p.metadata.migrationHash === latestMigration.migrationHash,
233
+ const migResult = parseMigrationRef(target, { graph, refs });
234
+ if (!migResult.ok) {
235
+ return notOk(mapRefResolutionError(migResult.failure));
236
+ }
237
+ const matchedPkg = packages.find(
238
+ (p) => p.metadata.migrationHash === migResult.value.migrationHash,
428
239
  );
429
- if (!leafPkg) {
240
+ if (!matchedPkg) {
430
241
  return notOk(
431
- errorRuntime('Could not resolve latest migration', {
432
- why: `Latest migration ${latestMigration.dirName} does not match any package`,
242
+ errorRuntime('Migration package not found', {
243
+ why: `Resolved migration "${migResult.value.dirName}" but the package was not loaded`,
433
244
  fix: 'The migrations directory may be corrupted. Inspect the migration.json files.',
434
245
  }),
435
246
  );
436
247
  }
437
- spaces.push(pkgToSpaceResult(aggregate.app.spaceId, leafPkg, client));
438
- } catch (error) {
439
- if (MigrationToolsError.is(error)) {
440
- return notOk(mapMigrationToolsError(error));
441
- }
442
- return notOk(
443
- errorUnexpected(error instanceof Error ? error.message : String(error), {
444
- why: `Failed to read app-space migration: ${error instanceof Error ? error.message : String(error)}`,
445
- }),
446
- );
447
- }
448
-
449
- // Extension spaces: always emit one entry per loaded extension so the
450
- // response enumerates every space the aggregate knows about. Spaces
451
- // with no on-disk migration package yet (e.g. an extension was declared
452
- // but never `migrate`d) become `kind: 'missing'` placeholders instead
453
- // of being silently skipped.
454
- for (const ext of aggregate.extensions) {
455
- const extSpaceDir = spaceMigrationDirectory(migrationsDir, ext.spaceId);
456
- const extPkgResult = await resolveLatestFromDir(extSpaceDir);
457
- if (!extPkgResult.ok) return extPkgResult;
458
- if (extPkgResult.value !== null) {
459
- spaces.push(pkgToSpaceResult(ext.spaceId, extPkgResult.value, client));
460
- } else {
461
- spaces.push({
462
- kind: 'missing',
463
- spaceId: ext.spaceId,
464
- summary: 'No on-disk migration package for this space',
465
- });
466
- }
248
+ appPkg = matchedPkg;
467
249
  }
468
250
 
469
- return ok({ ok: true, spaces });
251
+ return ok({
252
+ ok: true,
253
+ migration: pkgToPresent(APP_SPACE_ID, appPkg, client),
254
+ });
470
255
  }
471
256
 
472
257
  export function createMigrationShowCommand(): Command {
@@ -474,12 +259,11 @@ export function createMigrationShowCommand(): Command {
474
259
  setCommandDescriptions(
475
260
  command,
476
261
  'Display migration package contents',
477
- 'Shows the operations, statement preview, and metadata for every loaded contract\n' +
478
- 'space (app + extensions). Accepts a directory path or hash prefix to target a\n' +
479
- 'specific app-space migration; defaults to the latest per space.',
262
+ 'Shows the operations, statement preview, and metadata for one app-space migration.\n' +
263
+ 'Accepts a directory path, directory name, or hash prefix.',
480
264
  );
481
265
  setCommandExamples(command, [
482
- 'prisma-next migration show',
266
+ 'prisma-next migration show 20260101_100000_add_user',
483
267
  'prisma-next migration show sha256:a1b2c3',
484
268
  ]);
485
269
  setCommandSeeAlso(command, [
@@ -489,12 +273,9 @@ export function createMigrationShowCommand(): Command {
489
273
  { verb: 'migration graph', oneLiner: 'Show the migration graph topology' },
490
274
  ]);
491
275
  addGlobalOptions(command)
492
- .argument(
493
- '[target]',
494
- 'Migration reference: directory name, hash/prefix, or path (defaults to latest)',
495
- )
276
+ .argument('<target>', 'Migration reference: directory name, hash/prefix, or path')
496
277
  .option('--config <path>', 'Path to prisma-next.config.ts')
497
- .action(async (target: string | undefined, options: MigrationShowOptions) => {
278
+ .action(async (target: string, options: MigrationShowOptions) => {
498
279
  const flags = parseGlobalFlagsOrExit(options);
499
280
 
500
281
  const ui = createTerminalUI(flags);