@prisma-next/cli 0.8.0-dev.1 → 0.8.0-dev.10

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 (152) hide show
  1. package/README.md +7 -8
  2. package/dist/{cli-errors-D3_sMh2K.mjs → cli-errors-CF60g2cG.mjs} +40 -2
  3. package/dist/cli-errors-CF60g2cG.mjs.map +1 -0
  4. package/dist/cli.mjs +67 -19
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/{client-4d26awB-.mjs → client-XkUw4xD0.mjs} +10 -9
  7. package/dist/client-XkUw4xD0.mjs.map +1 -0
  8. package/dist/{command-helpers-BeZHkxV8.mjs → command-helpers-D3vL5yi8.mjs} +29 -6
  9. package/dist/command-helpers-D3vL5yi8.mjs.map +1 -0
  10. package/dist/commands/contract-emit.mjs +1 -1
  11. package/dist/commands/contract-infer.mjs +1 -1
  12. package/dist/commands/db-init.mjs +7 -7
  13. package/dist/commands/db-schema.mjs +5 -5
  14. package/dist/commands/db-sign.d.mts.map +1 -1
  15. package/dist/commands/db-sign.mjs +67 -25
  16. package/dist/commands/db-sign.mjs.map +1 -1
  17. package/dist/commands/db-update.d.mts.map +1 -1
  18. package/dist/commands/db-update.mjs +37 -9
  19. package/dist/commands/db-update.mjs.map +1 -1
  20. package/dist/commands/db-verify.mjs +1 -1
  21. package/dist/commands/migrate.d.mts +28 -0
  22. package/dist/commands/migrate.d.mts.map +1 -0
  23. package/dist/commands/{migration-apply.mjs → migrate.mjs} +54 -36
  24. package/dist/commands/migrate.mjs.map +1 -0
  25. package/dist/commands/migration-check.d.mts +18 -0
  26. package/dist/commands/migration-check.d.mts.map +1 -0
  27. package/dist/commands/migration-check.mjs +284 -0
  28. package/dist/commands/migration-check.mjs.map +1 -0
  29. package/dist/commands/migration-graph.d.mts +16 -0
  30. package/dist/commands/migration-graph.d.mts.map +1 -0
  31. package/dist/commands/migration-graph.mjs +141 -0
  32. package/dist/commands/migration-graph.mjs.map +1 -0
  33. package/dist/commands/migration-list.d.mts +20 -0
  34. package/dist/commands/migration-list.d.mts.map +1 -0
  35. package/dist/commands/migration-list.mjs +107 -0
  36. package/dist/commands/migration-list.mjs.map +1 -0
  37. package/dist/commands/migration-log.d.mts +21 -0
  38. package/dist/commands/migration-log.d.mts.map +1 -0
  39. package/dist/commands/migration-log.mjs +146 -0
  40. package/dist/commands/migration-log.mjs.map +1 -0
  41. package/dist/commands/migration-new.d.mts.map +1 -1
  42. package/dist/commands/migration-new.mjs +21 -20
  43. package/dist/commands/migration-new.mjs.map +1 -1
  44. package/dist/commands/migration-plan.d.mts +2 -2
  45. package/dist/commands/migration-plan.d.mts.map +1 -1
  46. package/dist/commands/migration-plan.mjs +1 -1
  47. package/dist/commands/migration-show.d.mts +1 -1
  48. package/dist/commands/migration-show.d.mts.map +1 -1
  49. package/dist/commands/migration-show.mjs +85 -47
  50. package/dist/commands/migration-show.mjs.map +1 -1
  51. package/dist/commands/migration-status.d.mts +3 -15
  52. package/dist/commands/migration-status.d.mts.map +1 -1
  53. package/dist/commands/migration-status.mjs +732 -1
  54. package/dist/commands/migration-status.mjs.map +1 -0
  55. package/dist/commands/ref.d.mts +34 -0
  56. package/dist/commands/ref.d.mts.map +1 -0
  57. package/dist/commands/{migration-ref.mjs → ref.mjs} +28 -57
  58. package/dist/commands/ref.mjs.map +1 -0
  59. package/dist/{contract-emit-DLc5GYbr.mjs → contract-emit-CgoFk9AU.mjs} +3 -3
  60. package/dist/{contract-emit-DLc5GYbr.mjs.map → contract-emit-CgoFk9AU.mjs.map} +1 -1
  61. package/dist/{contract-emit-BhKR-D9Y.mjs → contract-emit-GpxW5RLe.mjs} +6 -6
  62. package/dist/{contract-emit-BhKR-D9Y.mjs.map → contract-emit-GpxW5RLe.mjs.map} +1 -1
  63. package/dist/{contract-infer-Bnla2kuK.mjs → contract-infer-D8edZOCi.mjs} +5 -5
  64. package/dist/{contract-infer-Bnla2kuK.mjs.map → contract-infer-D8edZOCi.mjs.map} +1 -1
  65. package/dist/{contract-space-aggregate-loader-BrwKK6Q6.mjs → contract-space-aggregate-loader-D68YpuPR.mjs} +3 -3
  66. package/dist/{contract-space-aggregate-loader-BrwKK6Q6.mjs.map → contract-space-aggregate-loader-D68YpuPR.mjs.map} +1 -1
  67. package/dist/{db-verify-DitNxDiE.mjs → db-verify-DtRB9iHJ.mjs} +7 -7
  68. package/dist/{db-verify-DitNxDiE.mjs.map → db-verify-DtRB9iHJ.mjs.map} +1 -1
  69. package/dist/errors-Cw6kyTyV.mjs +56 -0
  70. package/dist/errors-Cw6kyTyV.mjs.map +1 -0
  71. package/dist/exports/control-api.d.mts +1 -1
  72. package/dist/exports/control-api.mjs +2 -2
  73. package/dist/exports/index.mjs +1 -1
  74. package/dist/exports/init-output.mjs +1 -1
  75. package/dist/{framework-components-ChqVUxR-.mjs → framework-components-xFLFpZUO.mjs} +2 -2
  76. package/dist/{framework-components-ChqVUxR-.mjs.map → framework-components-xFLFpZUO.mjs.map} +1 -1
  77. package/dist/{global-flags-Icqpxk23.d.mts → global-flags-DGmw6Kqg.d.mts} +1 -1
  78. package/dist/{global-flags-Icqpxk23.d.mts.map → global-flags-DGmw6Kqg.d.mts.map} +1 -1
  79. package/dist/{migration-status-Do4Ei0i_.mjs → graph-render-eJDcLWny.mjs} +3 -692
  80. package/dist/graph-render-eJDcLWny.mjs.map +1 -0
  81. package/dist/{init-DW94FRsD.mjs → init-Dm0QZPUA.mjs} +133 -61
  82. package/dist/init-Dm0QZPUA.mjs.map +1 -0
  83. package/dist/{inspect-live-schema-CyzAzPzF.mjs → inspect-live-schema-CPPqCips.mjs} +4 -4
  84. package/dist/{inspect-live-schema-CyzAzPzF.mjs.map → inspect-live-schema-CPPqCips.mjs.map} +1 -1
  85. package/dist/migration-cli.mjs +1 -1
  86. package/dist/migration-cli.mjs.map +1 -1
  87. package/dist/{migration-command-scaffold-Jp1rosw8.mjs → migration-command-scaffold-B_ezTTwX.mjs} +4 -4
  88. package/dist/{migration-command-scaffold-Jp1rosw8.mjs.map → migration-command-scaffold-B_ezTTwX.mjs.map} +1 -1
  89. package/dist/{migration-plan-q1pPoOCf.mjs → migration-plan-DWB-NTxH.mjs} +54 -28
  90. package/dist/migration-plan-DWB-NTxH.mjs.map +1 -0
  91. package/dist/migration-types-D2FW63pr.d.mts +15 -0
  92. package/dist/migration-types-D2FW63pr.d.mts.map +1 -0
  93. package/dist/{migrations-CTsyBXCA.mjs → migrations-DyUf5lTt.mjs} +2 -2
  94. package/dist/migrations-DyUf5lTt.mjs.map +1 -0
  95. package/dist/{output-BVj6a971.mjs → output-B60Gw5fu.mjs} +12 -11
  96. package/dist/{output-BVj6a971.mjs.map → output-B60Gw5fu.mjs.map} +1 -1
  97. package/dist/{result-handler-rmPVKIP2.mjs → result-handler-Bm_6dDYg.mjs} +2 -2
  98. package/dist/{result-handler-rmPVKIP2.mjs.map → result-handler-Bm_6dDYg.mjs.map} +1 -1
  99. package/dist/{terminal-ui-C_hFNbAn.mjs → terminal-ui-XtOQsqe9.mjs} +2 -54
  100. package/dist/terminal-ui-XtOQsqe9.mjs.map +1 -0
  101. package/dist/{types-LItU7E4l.d.mts → types-BS_wpjAY.d.mts} +2 -2
  102. package/dist/{types-LItU7E4l.d.mts.map → types-BS_wpjAY.d.mts.map} +1 -1
  103. package/dist/{verify-CiwNWM9N.mjs → verify-D7ypCCe6.mjs} +1 -1
  104. package/dist/{verify-CiwNWM9N.mjs.map → verify-D7ypCCe6.mjs.map} +1 -1
  105. package/package.json +39 -23
  106. package/src/cli.ts +78 -15
  107. package/src/commands/db-sign.ts +102 -32
  108. package/src/commands/db-update.ts +56 -4
  109. package/src/commands/init/agent-skill-install.ts +145 -43
  110. package/src/commands/init/errors.ts +2 -2
  111. package/src/commands/init/exit-codes.ts +2 -2
  112. package/src/commands/init/index.ts +1 -1
  113. package/src/commands/init/init.ts +15 -6
  114. package/src/commands/init/inputs.ts +1 -1
  115. package/src/commands/init/output.ts +22 -17
  116. package/src/commands/{migration-apply.ts → migrate.ts} +54 -70
  117. package/src/commands/migration-check/exit-codes.ts +3 -0
  118. package/src/commands/migration-check.ts +369 -0
  119. package/src/commands/migration-graph.ts +184 -0
  120. package/src/commands/migration-list.ts +155 -0
  121. package/src/commands/migration-log.ts +218 -0
  122. package/src/commands/migration-new.ts +17 -9
  123. package/src/commands/migration-plan.ts +65 -26
  124. package/src/commands/migration-show.ts +132 -60
  125. package/src/commands/migration-status.ts +77 -64
  126. package/src/commands/{migration-ref.ts → ref.ts} +32 -86
  127. package/src/control-api/operations/apply-aggregate.ts +4 -4
  128. package/src/control-api/operations/db-apply-aggregate.ts +3 -2
  129. package/src/control-api/operations/migration-apply.ts +4 -3
  130. package/src/control-api/types.ts +1 -2
  131. package/src/migration-cli.ts +1 -1
  132. package/src/utils/cli-errors.ts +37 -0
  133. package/src/utils/command-helpers.ts +28 -3
  134. package/src/utils/contract-space-aggregate-loader.ts +2 -2
  135. package/src/utils/contract-space-seed-phase.ts +1 -1
  136. package/src/utils/formatters/help.ts +12 -2
  137. package/src/utils/formatters/migrations.ts +2 -2
  138. package/dist/cli-errors-D3_sMh2K.mjs.map +0 -1
  139. package/dist/client-4d26awB-.mjs.map +0 -1
  140. package/dist/command-helpers-BeZHkxV8.mjs.map +0 -1
  141. package/dist/commands/migration-apply.d.mts +0 -51
  142. package/dist/commands/migration-apply.d.mts.map +0 -1
  143. package/dist/commands/migration-apply.mjs.map +0 -1
  144. package/dist/commands/migration-ref.d.mts +0 -45
  145. package/dist/commands/migration-ref.d.mts.map +0 -1
  146. package/dist/commands/migration-ref.mjs.map +0 -1
  147. package/dist/init-DW94FRsD.mjs.map +0 -1
  148. package/dist/migration-plan-q1pPoOCf.mjs.map +0 -1
  149. package/dist/migration-status-Do4Ei0i_.mjs.map +0 -1
  150. package/dist/migrations-CTsyBXCA.mjs.map +0 -1
  151. package/dist/terminal-ui-C_hFNbAn.mjs.map +0 -1
  152. /package/dist/{cli-errors-B9OBbled.d.mts → cli-errors-DdcjVLJV.d.mts} +0 -0
@@ -18,6 +18,8 @@ import {
18
18
  import type { MigrationMetadata } from '@prisma-next/migration-tools/metadata';
19
19
  import { findLatestMigration } from '@prisma-next/migration-tools/migration-graph';
20
20
  import { writeMigrationTs } from '@prisma-next/migration-tools/migration-ts';
21
+ import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
22
+ import { readRefs } from '@prisma-next/migration-tools/refs';
21
23
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
22
24
  import { Command } from 'commander';
23
25
  import { join, relative } from 'pathe';
@@ -28,10 +30,10 @@ import {
28
30
  errorContractValidationFailed,
29
31
  errorFileNotFound,
30
32
  errorMigrationPlanningFailed,
31
- errorRuntime,
32
33
  errorTargetMigrationNotSupported,
33
34
  errorUnexpected,
34
35
  mapMigrationToolsError,
36
+ mapRefResolutionError,
35
37
  } from '../utils/cli-errors';
36
38
  import {
37
39
  addGlobalOptions,
@@ -58,6 +60,34 @@ interface MigrationPlanOptions extends CommonCommandOptions {
58
60
  readonly from?: string;
59
61
  }
60
62
 
63
+ /**
64
+ * Load a predecessor migration's destination contract from its sibling
65
+ * `end-contract.json` on disk. The manifest no longer inlines the
66
+ * contract; the planner reads it from the canonical on-disk artefact
67
+ * authored by a previous `migration plan` run.
68
+ *
69
+ * Throws `CliStructuredError` with `errorFileNotFound` when the
70
+ * sibling file is missing — the user has likely deleted or never
71
+ * authored the snapshot, and the message names the file and points
72
+ * them at re-emitting from the source.
73
+ */
74
+ async function readPredecessorEndContract(migrationDir: string): Promise<Contract> {
75
+ const path = join(migrationDir, 'end-contract.json');
76
+ let raw: string;
77
+ try {
78
+ raw = await readFile(path, 'utf-8');
79
+ } catch (error) {
80
+ if (error instanceof Error && (error as { code?: string }).code === 'ENOENT') {
81
+ throw errorFileNotFound(path, {
82
+ why: `Predecessor migration is missing its destination contract snapshot at ${path}`,
83
+ fix: 'Re-emit the predecessor migration (`prisma-next migration plan` from its source) so its sibling `end-contract.json` is restored, then re-run this command.',
84
+ });
85
+ }
86
+ throw error;
87
+ }
88
+ return JSON.parse(raw) as Contract;
89
+ }
90
+
61
91
  export interface MigrationPlanResult {
62
92
  readonly ok: boolean;
63
93
  readonly noOp: boolean;
@@ -74,7 +104,7 @@ export interface MigrationPlanResult {
74
104
  * Surfacing these in the result (rather than only via `ui.step` log
75
105
  * lines) makes the cross-space side effect explicit to JSON consumers
76
106
  * and the success-summary renderer — the same multi-space side effect
77
- * that `migration apply` will replay.
107
+ * that `migrate` will replay.
78
108
  */
79
109
  readonly emittedExtensionDirs: readonly { readonly spaceId: string; readonly dirName: string }[];
80
110
  readonly operations: readonly {
@@ -186,24 +216,26 @@ async function executeMigrationPlanCommand(
186
216
  const { bundles, graph } = await loadMigrationPackages(appMigrationsDir);
187
217
 
188
218
  if (options.from) {
189
- const resolved = resolveBundleByPrefix(bundles, options.from);
190
- if (!resolved.ok) {
191
- const f = resolved.failure;
219
+ const refs = await readRefs(resolveMigrationPaths(options.config, config).refsDir);
220
+ const refResult = parseContractRef(options.from, { graph, refs });
221
+ if (!refResult.ok) {
222
+ return notOk(mapRefResolutionError(refResult.failure));
223
+ }
224
+ fromHash = refResult.value.hash;
225
+ const matchingBundle = bundles.find((p) => p.metadata.to === fromHash);
226
+ if (!matchingBundle) {
192
227
  return notOk(
193
- f.reason === 'ambiguous'
194
- ? errorRuntime('Multiple matching migrations found', {
195
- why: `Prefix "${options.from}" matches ${f.count} migrations in ${appMigrationsRelative}`,
196
- fix: 'Provide a longer prefix to disambiguate, or omit --from to use the latest migration target.',
197
- })
198
- : errorRuntime('Starting contract not found', {
199
- why: `No migration with to hash matching "${options.from}" exists in ${appMigrationsRelative}`,
200
- fix: 'Check that the --from hash matches a known migration target hash, or omit --from to use the latest migration target.',
201
- }),
228
+ errorUnexpected(
229
+ `No migration bundle found for --from "${options.from}" (resolved hash: ${fromHash})`,
230
+ {
231
+ why: `The ref resolved successfully but no on-disk migration package has an end-contract hash matching ${fromHash}.`,
232
+ fix: 'Provide a ref or hash that corresponds to an existing migration package, or run `migration list` to see available migrations.',
233
+ },
234
+ ),
202
235
  );
203
236
  }
204
- fromHash = resolved.value.metadata.to;
205
- fromContract = resolved.value.metadata.toContract;
206
- fromContractSourceDir = resolved.value.dirPath;
237
+ fromContractSourceDir = matchingBundle.dirPath;
238
+ fromContract = await readPredecessorEndContract(fromContractSourceDir);
207
239
  } else {
208
240
  const latestMigration = findLatestMigration(graph);
209
241
  if (latestMigration) {
@@ -212,8 +244,8 @@ async function executeMigrationPlanCommand(
212
244
  (p) => p.metadata.migrationHash === latestMigration.migrationHash,
213
245
  );
214
246
  if (leafPkg) {
215
- fromContract = leafPkg.metadata.toContract;
216
247
  fromContractSourceDir = leafPkg.dirPath;
248
+ fromContract = await readPredecessorEndContract(fromContractSourceDir);
217
249
  }
218
250
  }
219
251
  }
@@ -221,6 +253,12 @@ async function executeMigrationPlanCommand(
221
253
  if (MigrationToolsError.is(error)) {
222
254
  return notOk(mapMigrationToolsError(error));
223
255
  }
256
+ // `readPredecessorEndContract` raises a `CliStructuredError` directly
257
+ // for the missing-snapshot case so the operator gets a precise
258
+ // why/fix; pass it through unchanged rather than re-wrapping.
259
+ if (CliStructuredError.is(error)) {
260
+ return notOk(error);
261
+ }
224
262
  // Wrap unexpected (non-MigrationToolsError) failures from the migration
225
263
  // load phase in a structured CLI envelope. Letting them throw would
226
264
  // bypass `handleResult()` and crash the command — see CLI structured-
@@ -327,8 +365,6 @@ async function executeMigrationPlanCommand(
327
365
  const baseMetadata: Omit<MigrationMetadata, 'migrationHash' | 'providedInvariants'> = {
328
366
  from: fromHash,
329
367
  to: toStorageHash,
330
- fromContract,
331
- toContract: toContractJson,
332
368
  hints: {
333
369
  used: [],
334
370
  applied: [],
@@ -491,7 +527,10 @@ export function createMigrationPlanCommand(): Command {
491
527
  addGlobalOptions(command)
492
528
  .option('--config <path>', 'Path to prisma-next.config.ts')
493
529
  .option('--name <slug>', 'Name slug for the migration directory', 'migration')
494
- .option('--from <hash>', 'Explicit starting contract hash (overrides latest migration target)')
530
+ .option(
531
+ '--from <contract>',
532
+ 'Starting contract reference (hash, prefix, ref name, migration dir name, <dir>^, or ./path)',
533
+ )
495
534
  .action(async (options: MigrationPlanOptions) => {
496
535
  const flags = parseGlobalFlags(options);
497
536
  const startTime = Date.now();
@@ -557,7 +596,7 @@ export function formatMigrationPlanOutput(result: MigrationPlanResult, flags: Gl
557
596
  }
558
597
  lines.push('');
559
598
  lines.push(
560
- `Next: review the extension migrations above, then run ${green_('prisma-next migration apply')}.`,
599
+ `Next: review the extension migrations above, then run ${green_('prisma-next migrate')}.`,
561
600
  );
562
601
  }
563
602
 
@@ -628,11 +667,11 @@ export function formatMigrationPlanOutput(result: MigrationPlanResult, flags: Gl
628
667
 
629
668
  lines.push('');
630
669
  // The "Next:" hint always points at the canonical apply path
631
- // (`prisma-next migration apply`) regardless of how many spaces
632
- // were materialised — `db update` is a dev-time convenience, not
633
- // the canonical replay step.
670
+ // (`prisma-next migrate`) regardless of how many spaces were
671
+ // materialised — `db update` is a dev-time convenience, not the
672
+ // canonical replay step.
634
673
  lines.push(
635
- `Next: review ${green_(result.dir ?? '<dir>')} if needed, then run ${green_('prisma-next migration apply')}.`,
674
+ `Next: review ${green_(result.dir ?? '<dir>')} if needed, then run ${green_('prisma-next migrate')}.`,
636
675
  );
637
676
 
638
677
  if (result.preview && result.preview.statements.length > 0) {
@@ -1,6 +1,7 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import type { Contract } from '@prisma-next/contract/types';
3
3
  import {
4
+ APP_SPACE_ID,
4
5
  createControlStack,
5
6
  type MigrationPlanOperation,
6
7
  type OperationPreview,
@@ -12,6 +13,8 @@ import {
12
13
  reconstructGraph,
13
14
  } from '@prisma-next/migration-tools/migration-graph';
14
15
  import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
16
+ import { parseMigrationRef } from '@prisma-next/migration-tools/ref-resolution';
17
+ import { readRefs } from '@prisma-next/migration-tools/refs';
15
18
  import { spaceMigrationDirectory } from '@prisma-next/migration-tools/spaces';
16
19
  import { ifDefined } from '@prisma-next/utils/defined';
17
20
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
@@ -26,6 +29,7 @@ import {
26
29
  errorRuntime,
27
30
  errorUnexpected,
28
31
  mapMigrationToolsError,
32
+ mapRefResolutionError,
29
33
  } from '../utils/cli-errors';
30
34
  import {
31
35
  addGlobalOptions,
@@ -33,6 +37,7 @@ import {
33
37
  resolveMigrationPaths,
34
38
  setCommandDescriptions,
35
39
  setCommandExamples,
40
+ setCommandSeeAlso,
36
41
  } from '../utils/command-helpers';
37
42
  import { buildContractSpaceAggregate } from '../utils/contract-space-aggregate-loader';
38
43
  import { formatMigrationShowOutput } from '../utils/formatters/migrations';
@@ -237,7 +242,7 @@ async function executeMigrationShowCommand(
237
242
  ui: TerminalUI,
238
243
  ): Promise<Result<MigrationShowResult, CliStructuredError>> {
239
244
  const config = await loadConfig(options.config);
240
- const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative } =
245
+ const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative, refsDir } =
241
246
  resolveMigrationPaths(options.config, config);
242
247
 
243
248
  const contractPathAbsolute = resolveContractPath(config);
@@ -261,7 +266,91 @@ async function executeMigrationShowCommand(
261
266
  ui.stderr(header);
262
267
  }
263
268
 
264
- // Load the app contract so the aggregate loader can validate it.
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
+ const client = createControlClient({
273
+ family: config.family,
274
+ target: config.target,
275
+ adapter: config.adapter,
276
+ ...ifDefined('driver', config.driver),
277
+ extensionPacks: config.extensionPacks ?? [],
278
+ });
279
+
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.
265
354
  let contractJsonContent: string;
266
355
  try {
267
356
  contractJsonContent = await readFile(contractPathAbsolute, 'utf-8');
@@ -293,7 +382,6 @@ async function executeMigrationShowCommand(
293
382
  );
294
383
  }
295
384
 
296
- // Build the aggregate against current disk state to enumerate all spaces.
297
385
  const stack = createControlStack(config);
298
386
  const familyInstance = config.family.create(stack);
299
387
  const aggregateResult = await buildContractSpaceAggregate({
@@ -308,66 +396,41 @@ async function executeMigrationShowCommand(
308
396
  }
309
397
  const aggregate = aggregateResult.value;
310
398
 
311
- // `migration show` is an offline command; the control client is constructed
312
- // purely to dispatch the family-specific `toOperationPreview` capability and
313
- // is not connected to a database.
314
- const client = createControlClient({
315
- family: config.family,
316
- target: config.target,
317
- adapter: config.adapter,
318
- ...ifDefined('driver', config.driver),
319
- extensionPacks: config.extensionPacks ?? [],
320
- });
321
-
322
399
  const spaces: MigrationShowSpaceResult[] = [];
323
400
 
324
- // App space: honour the `target` argument (path or hash prefix) when provided.
401
+ // App space: latest leaf.
325
402
  try {
326
- let appPkg: OnDiskMigrationPackage;
327
- if (target && looksLikePath(target)) {
328
- const resolved = resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative);
329
- if (!resolved.ok) return resolved;
330
- appPkg = await readMigrationPackage(resolved.value);
331
- } else {
332
- const allPackages = await readMigrationsDir(appMigrationsDir);
333
- if (allPackages.length === 0) {
334
- return notOk(
335
- errorRuntime('No migrations found', {
336
- why: `No migration packages found in ${appMigrationsRelative}`,
337
- fix: 'Run `prisma-next migration plan` to create a migration first.',
338
- }),
339
- );
340
- }
341
- if (target) {
342
- const resolved = resolveByHashPrefix(allPackages, target);
343
- if (!resolved.ok) return resolved;
344
- appPkg = resolved.value;
345
- } else {
346
- const graph = reconstructGraph(allPackages);
347
- const latestMigration = findLatestMigration(graph);
348
- if (!latestMigration) {
349
- return notOk(
350
- errorRuntime('Could not resolve latest migration', {
351
- why: 'No latest migration found in the migration history',
352
- fix: 'The migrations directory may be corrupted. Inspect the migration.json files.',
353
- }),
354
- );
355
- }
356
- const leafPkg = allPackages.find(
357
- (p) => p.metadata.migrationHash === latestMigration.migrationHash,
358
- );
359
- if (!leafPkg) {
360
- return notOk(
361
- errorRuntime('Could not resolve latest migration', {
362
- why: `Latest migration ${latestMigration.dirName} does not match any package`,
363
- fix: 'The migrations directory may be corrupted. Inspect the migration.json files.',
364
- }),
365
- );
366
- }
367
- appPkg = leafPkg;
368
- }
403
+ const allPackages = await readMigrationsDir(appMigrationsDir);
404
+ if (allPackages.length === 0) {
405
+ return notOk(
406
+ errorRuntime('No migrations found', {
407
+ why: `No migration packages found in ${appMigrationsRelative}`,
408
+ fix: 'Run `prisma-next migration plan` to create a migration first.',
409
+ }),
410
+ );
411
+ }
412
+ const graph = reconstructGraph(allPackages);
413
+ const latestMigration = findLatestMigration(graph);
414
+ if (!latestMigration) {
415
+ return notOk(
416
+ errorRuntime('Could not resolve latest migration', {
417
+ why: 'No latest migration found in the migration history',
418
+ fix: 'The migrations directory may be corrupted. Inspect the migration.json files.',
419
+ }),
420
+ );
369
421
  }
370
- spaces.push(pkgToSpaceResult(aggregate.app.spaceId, appPkg, client));
422
+ const leafPkg = allPackages.find(
423
+ (p) => p.metadata.migrationHash === latestMigration.migrationHash,
424
+ );
425
+ if (!leafPkg) {
426
+ return notOk(
427
+ errorRuntime('Could not resolve latest migration', {
428
+ why: `Latest migration ${latestMigration.dirName} does not match any package`,
429
+ fix: 'The migrations directory may be corrupted. Inspect the migration.json files.',
430
+ }),
431
+ );
432
+ }
433
+ spaces.push(pkgToSpaceResult(aggregate.app.spaceId, leafPkg, client));
371
434
  } catch (error) {
372
435
  if (MigrationToolsError.is(error)) {
373
436
  return notOk(mapMigrationToolsError(error));
@@ -415,8 +478,17 @@ export function createMigrationShowCommand(): Command {
415
478
  'prisma-next migration show',
416
479
  'prisma-next migration show sha256:a1b2c3',
417
480
  ]);
481
+ setCommandSeeAlso(command, [
482
+ { verb: 'migration status', oneLiner: 'Show migration path and pending status' },
483
+ { verb: 'migration log', oneLiner: 'Show executed migration history' },
484
+ { verb: 'migration list', oneLiner: 'List on-disk migrations' },
485
+ { verb: 'migration graph', oneLiner: 'Show the migration graph topology' },
486
+ ]);
418
487
  addGlobalOptions(command)
419
- .argument('[target]', 'App-space migration path or migrationHash prefix (defaults to latest)')
488
+ .argument(
489
+ '[target]',
490
+ 'Migration reference: directory name, hash/prefix, or path (defaults to latest)',
491
+ )
420
492
  .option('--config <path>', 'Path to prisma-next.config.ts')
421
493
  .action(async (target: string | undefined, options: MigrationShowOptions) => {
422
494
  const flags = parseGlobalFlags(options);