@prisma-next/cli 0.12.0-dev.9 → 0.13.0-dev.1

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 (214) hide show
  1. package/README.md +2 -2
  2. package/dist/cli.mjs +180 -163
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/{client-KgJorIvG.mjs → client-CJzuo5wX.mjs} +222 -107
  5. package/dist/client-CJzuo5wX.mjs.map +1 -0
  6. package/dist/{command-helpers-Bbw1GbwL.mjs → command-helpers-DGMvGBeX.mjs} +318 -25
  7. package/dist/command-helpers-DGMvGBeX.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 +4 -5
  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 -3
  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 +6 -6
  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 +10 -7
  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 +37 -3
  27. package/dist/commands/migrate.d.mts.map +1 -1
  28. package/dist/commands/migrate.mjs +298 -12
  29. package/dist/commands/migrate.mjs.map +1 -1
  30. package/dist/commands/migration-check.d.mts +55 -13
  31. package/dist/commands/migration-check.d.mts.map +1 -1
  32. package/dist/commands/migration-check.mjs +3 -2
  33. package/dist/commands/migration-graph.d.mts +16 -25
  34. package/dist/commands/migration-graph.d.mts.map +1 -1
  35. package/dist/commands/migration-graph.mjs +185 -2
  36. package/dist/commands/migration-graph.mjs.map +1 -0
  37. package/dist/commands/migration-list.d.mts +26 -27
  38. package/dist/commands/migration-list.d.mts.map +1 -1
  39. package/dist/commands/migration-list.mjs +2 -190
  40. package/dist/commands/migration-log.d.mts +9 -19
  41. package/dist/commands/migration-log.d.mts.map +1 -1
  42. package/dist/commands/migration-log.mjs +1 -137
  43. package/dist/commands/migration-new.d.mts.map +1 -1
  44. package/dist/commands/migration-new.mjs +6 -5
  45. package/dist/commands/migration-new.mjs.map +1 -1
  46. package/dist/commands/migration-plan.d.mts +1 -1
  47. package/dist/commands/migration-plan.d.mts.map +1 -1
  48. package/dist/commands/migration-plan.mjs +1 -1
  49. package/dist/commands/migration-show.d.mts +17 -21
  50. package/dist/commands/migration-show.d.mts.map +1 -1
  51. package/dist/commands/migration-show.mjs +24 -36
  52. package/dist/commands/migration-show.mjs.map +1 -1
  53. package/dist/commands/migration-status.d.mts +42 -144
  54. package/dist/commands/migration-status.d.mts.map +1 -1
  55. package/dist/commands/migration-status.mjs +3 -759
  56. package/dist/commands/ref.d.mts +1 -1
  57. package/dist/commands/ref.d.mts.map +1 -1
  58. package/dist/commands/ref.mjs +4 -4
  59. package/dist/commands/ref.mjs.map +1 -1
  60. package/dist/commands/telemetry/index.d.mts +7 -0
  61. package/dist/commands/telemetry/index.d.mts.map +1 -0
  62. package/dist/commands/telemetry/index.mjs +2 -0
  63. package/dist/{config-loader-B6sJjXTv.mjs → config-loader-p9JMrekQ.mjs} +1 -1
  64. package/dist/{config-loader-B6sJjXTv.mjs.map → config-loader-p9JMrekQ.mjs.map} +1 -1
  65. package/dist/config-loader.mjs +1 -1
  66. package/dist/{contract-at-errors-BxP-TOMl.mjs → contract-at-errors-CFXsstzm.mjs} +2 -2
  67. package/dist/{contract-at-errors-BxP-TOMl.mjs.map → contract-at-errors-CFXsstzm.mjs.map} +1 -1
  68. package/dist/{contract-emit-DxcGl4Uq.mjs → contract-emit-B_qriF8B.mjs} +5 -5
  69. package/dist/{contract-emit-DxcGl4Uq.mjs.map → contract-emit-B_qriF8B.mjs.map} +1 -1
  70. package/dist/{contract-emit-D-4jrNve.mjs → contract-emit-C8HmtboH.mjs} +12 -7
  71. package/dist/contract-emit-C8HmtboH.mjs.map +1 -0
  72. package/dist/{contract-enrichment-a0V5Y_mL.mjs → contract-enrichment-gn9sWbPw.mjs} +1 -1
  73. package/dist/{contract-enrichment-a0V5Y_mL.mjs.map → contract-enrichment-gn9sWbPw.mjs.map} +1 -1
  74. package/dist/{contract-infer-D8uEbJuu.mjs → contract-infer-BYT_ra_U.mjs} +5 -5
  75. package/dist/contract-infer-BYT_ra_U.mjs.map +1 -0
  76. package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs → contract-space-aggregate-loader-ClI1KN6d.mjs} +5 -5
  77. package/dist/{contract-space-aggregate-loader-DvZwdkrr.mjs.map → contract-space-aggregate-loader-ClI1KN6d.mjs.map} +1 -1
  78. package/dist/{db-verify-v_vUKXTU.mjs → db-verify-C24FKhb7.mjs} +6 -6
  79. package/dist/{db-verify-v_vUKXTU.mjs.map → db-verify-C24FKhb7.mjs.map} +1 -1
  80. package/dist/exports/control-api.d.mts +5 -3
  81. package/dist/exports/control-api.d.mts.map +1 -1
  82. package/dist/exports/control-api.mjs +3 -3
  83. package/dist/exports/index.mjs +1 -1
  84. package/dist/exports/index.mjs.map +1 -1
  85. package/dist/exports/init-output.d.mts +1 -3
  86. package/dist/exports/init-output.d.mts.map +1 -1
  87. package/dist/exports/init-output.mjs +1 -1
  88. package/dist/{extension-pack-inputs-IDvjRCi3.mjs → extension-pack-inputs-1ySHqxKG.mjs} +1 -1
  89. package/dist/{extension-pack-inputs-IDvjRCi3.mjs.map → extension-pack-inputs-1ySHqxKG.mjs.map} +1 -1
  90. package/dist/{framework-components-fYXjz_in.mjs → framework-components-YVQHhPH7.mjs} +2 -2
  91. package/dist/{framework-components-fYXjz_in.mjs.map → framework-components-YVQHhPH7.mjs.map} +1 -1
  92. package/dist/{global-flags-DEHjV8_s.d.mts → global-flags-BpoOYtNZ.d.mts} +1 -1
  93. package/dist/{global-flags-DEHjV8_s.d.mts.map → global-flags-BpoOYtNZ.d.mts.map} +1 -1
  94. package/dist/{init-Cv9UzWL5.mjs → init-0HwB-Vh8.mjs} +5 -58
  95. package/dist/init-0HwB-Vh8.mjs.map +1 -0
  96. package/dist/{inspect-live-schema-C6ohV_oQ.mjs → inspect-live-schema-DF6IwcDl.mjs} +7 -5
  97. package/dist/inspect-live-schema-DF6IwcDl.mjs.map +1 -0
  98. package/dist/migration-check-soB5uZEQ.mjs +573 -0
  99. package/dist/migration-check-soB5uZEQ.mjs.map +1 -0
  100. package/dist/migration-cli.mjs +1 -1
  101. package/dist/migration-cli.mjs.map +1 -1
  102. package/dist/{migration-command-scaffold-CjvwO6at.mjs → migration-command-scaffold-DA-Lhx6o.mjs} +5 -5
  103. package/dist/{migration-command-scaffold-CjvwO6at.mjs.map → migration-command-scaffold-DA-Lhx6o.mjs.map} +1 -1
  104. package/dist/migration-graph-command-render-CEez7YUK.mjs +1960 -0
  105. package/dist/migration-graph-command-render-CEez7YUK.mjs.map +1 -0
  106. package/dist/migration-list-DlJJ_38Z.mjs +230 -0
  107. package/dist/migration-list-DlJJ_38Z.mjs.map +1 -0
  108. package/dist/migration-log-CG0qQAFm.mjs +222 -0
  109. package/dist/migration-log-CG0qQAFm.mjs.map +1 -0
  110. package/dist/migration-path-target-Ce6OZImp.mjs +38 -0
  111. package/dist/migration-path-target-Ce6OZImp.mjs.map +1 -0
  112. package/dist/{migration-plan-9DJ7q7_z.mjs → migration-plan-z5Ing-TD.mjs} +9 -8
  113. package/dist/migration-plan-z5Ing-TD.mjs.map +1 -0
  114. package/dist/migration-status-CgWSoI_g.mjs +446 -0
  115. package/dist/migration-status-CgWSoI_g.mjs.map +1 -0
  116. package/dist/{output-B60Gw5fu.mjs → output-mEQ74_nd.mjs} +1 -1
  117. package/dist/{output-B60Gw5fu.mjs.map → output-mEQ74_nd.mjs.map} +1 -1
  118. package/dist/{progress-adapter-C644QK8l.mjs → progress-adapter-CjAeTxY_.mjs} +1 -1
  119. package/dist/{progress-adapter-C644QK8l.mjs.map → progress-adapter-CjAeTxY_.mjs.map} +1 -1
  120. package/dist/{ref-advancement-DUZqsue6.mjs → ref-advancement-BkXlikCA.mjs} +1 -1
  121. package/dist/{ref-advancement-DUZqsue6.mjs.map → ref-advancement-BkXlikCA.mjs.map} +1 -1
  122. package/dist/schemas-CeGMYFYX.d.mts +191 -0
  123. package/dist/schemas-CeGMYFYX.d.mts.map +1 -0
  124. package/dist/schemas-KhXMzNA_.mjs +112 -0
  125. package/dist/schemas-KhXMzNA_.mjs.map +1 -0
  126. package/dist/telemetry-BIM4beEO.mjs +122 -0
  127. package/dist/telemetry-BIM4beEO.mjs.map +1 -0
  128. package/dist/{terminal-ui-5Y6mrg93.d.mts → terminal-ui-DGRNFWna.d.mts} +1 -1
  129. package/dist/terminal-ui-DGRNFWna.d.mts.map +1 -0
  130. package/dist/{types-Dt_SfqFm.d.mts → types-C_tYiJYx.d.mts} +53 -31
  131. package/dist/types-C_tYiJYx.d.mts.map +1 -0
  132. package/dist/{verify-DCA9Sldu.mjs → verify-DcOYZ1tH.mjs} +2 -2
  133. package/dist/{verify-DCA9Sldu.mjs.map → verify-DcOYZ1tH.mjs.map} +1 -1
  134. package/package.json +26 -22
  135. package/src/cli.ts +5 -0
  136. package/src/commands/contract-infer.ts +2 -2
  137. package/src/commands/db-update.ts +7 -1
  138. package/src/commands/init/index.ts +6 -35
  139. package/src/commands/init/init.ts +1 -14
  140. package/src/commands/init/inputs.ts +0 -75
  141. package/src/commands/inspect-live-schema.ts +10 -0
  142. package/src/commands/json/schemas.ts +195 -0
  143. package/src/commands/migrate.ts +527 -8
  144. package/src/commands/migration-check.ts +469 -134
  145. package/src/commands/migration-graph.ts +151 -119
  146. package/src/commands/migration-list.ts +72 -39
  147. package/src/commands/migration-log.ts +52 -102
  148. package/src/commands/migration-new.ts +2 -1
  149. package/src/commands/migration-plan.ts +2 -1
  150. package/src/commands/migration-show.ts +31 -66
  151. package/src/commands/migration-status-overlay.ts +61 -0
  152. package/src/commands/migration-status.ts +458 -1066
  153. package/src/commands/telemetry/index.ts +107 -0
  154. package/src/commands/telemetry/status.ts +67 -0
  155. package/src/control-api/client.ts +70 -9
  156. package/src/control-api/operations/contract-emit.ts +22 -2
  157. package/src/control-api/operations/db-init.ts +6 -3
  158. package/src/control-api/operations/{db-apply.ts → db-run.ts} +55 -14
  159. package/src/control-api/operations/db-update.ts +7 -4
  160. package/src/control-api/operations/db-verify.ts +15 -5
  161. package/src/control-api/operations/{migration-apply.ts → migrate.ts} +181 -80
  162. package/src/control-api/operations/{apply.ts → run-migration.ts} +33 -27
  163. package/src/control-api/types.ts +56 -29
  164. package/src/utils/cli-errors.ts +70 -2
  165. package/src/utils/formatters/errors.ts +11 -0
  166. package/src/utils/formatters/migration-graph-command-render.ts +239 -0
  167. package/src/utils/formatters/migration-graph-grid-layout.ts +1134 -0
  168. package/src/utils/formatters/migration-graph-labels.ts +408 -0
  169. package/src/utils/formatters/migration-graph-model.ts +103 -0
  170. package/src/utils/formatters/migration-graph-occlusion-render.ts +258 -0
  171. package/src/utils/formatters/migration-graph-rows.ts +128 -15
  172. package/src/utils/formatters/migration-graph-space-render.ts +188 -0
  173. package/src/utils/formatters/migration-list-data-column.ts +4 -91
  174. package/src/utils/formatters/migration-list-graph-topology.ts +72 -94
  175. package/src/utils/formatters/migration-list-render.ts +135 -71
  176. package/src/utils/formatters/migration-list-styler.ts +46 -5
  177. package/src/utils/formatters/migration-list-types.ts +5 -21
  178. package/src/utils/formatters/migration-log-table.ts +205 -0
  179. package/src/utils/formatters/migrations.ts +33 -11
  180. package/src/utils/global-flags.ts +35 -0
  181. package/src/utils/integrity-violation-to-check-failure.ts +28 -19
  182. package/src/utils/legend.ts +38 -0
  183. package/src/utils/migration-path-target.ts +60 -0
  184. package/src/utils/telemetry.ts +68 -32
  185. package/dist/client-KgJorIvG.mjs.map +0 -1
  186. package/dist/command-helpers-Bbw1GbwL.mjs.map +0 -1
  187. package/dist/commands/migration-list.mjs.map +0 -1
  188. package/dist/commands/migration-log.mjs.map +0 -1
  189. package/dist/commands/migration-status.mjs.map +0 -1
  190. package/dist/contract-emit-D-4jrNve.mjs.map +0 -1
  191. package/dist/contract-infer-D8uEbJuu.mjs.map +0 -1
  192. package/dist/graph-render-rFAqZujX.mjs +0 -1081
  193. package/dist/graph-render-rFAqZujX.mjs.map +0 -1
  194. package/dist/init-Cv9UzWL5.mjs.map +0 -1
  195. package/dist/inspect-live-schema-C6ohV_oQ.mjs.map +0 -1
  196. package/dist/migration-check-BiBJoYYW.mjs +0 -341
  197. package/dist/migration-check-BiBJoYYW.mjs.map +0 -1
  198. package/dist/migration-graph-C9WC-7eO.mjs +0 -1478
  199. package/dist/migration-graph-C9WC-7eO.mjs.map +0 -1
  200. package/dist/migration-list-styler-BRwF4-gy.mjs +0 -399
  201. package/dist/migration-list-styler-BRwF4-gy.mjs.map +0 -1
  202. package/dist/migration-plan-9DJ7q7_z.mjs.map +0 -1
  203. package/dist/migration-types-D2FW63pr.d.mts +0 -15
  204. package/dist/migration-types-D2FW63pr.d.mts.map +0 -1
  205. package/dist/migrations-Cv2jxNNK.mjs +0 -228
  206. package/dist/migrations-Cv2jxNNK.mjs.map +0 -1
  207. package/dist/terminal-ui-5Y6mrg93.d.mts.map +0 -1
  208. package/dist/types-Dt_SfqFm.d.mts.map +0 -1
  209. package/src/utils/formatters/graph-migration-mapper.ts +0 -235
  210. package/src/utils/formatters/graph-render.ts +0 -1323
  211. package/src/utils/formatters/graph-types.ts +0 -120
  212. package/src/utils/formatters/migration-graph-lane-colors.ts +0 -31
  213. package/src/utils/formatters/migration-graph-layout.ts +0 -1141
  214. package/src/utils/formatters/migration-graph-tree-render.ts +0 -768
@@ -1,17 +1,38 @@
1
1
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
2
2
  import { readFile } from 'node:fs/promises';
3
3
  import { createControlStack } from '@prisma-next/framework-components/control';
4
- import type { IntegrityViolation } from '@prisma-next/migration-tools/aggregate';
4
+ import type {
5
+ ContractSpaceAggregate,
6
+ IntegrityViolation,
7
+ } from '@prisma-next/migration-tools/aggregate';
5
8
  import { loadContractSpaceAggregate } from '@prisma-next/migration-tools/aggregate';
9
+ import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
6
10
  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';
9
11
  import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
10
- import { parseMigrationRef } from '@prisma-next/migration-tools/ref-resolution';
11
- import { readRefs } from '@prisma-next/migration-tools/refs';
12
+ import {
13
+ parseMigrationRef,
14
+ type RefResolutionError,
15
+ } from '@prisma-next/migration-tools/ref-resolution';
16
+ import type { Refs } from '@prisma-next/migration-tools/refs';
17
+ import {
18
+ isValidSpaceId,
19
+ listContractSpaceDirectories,
20
+ RESERVED_SPACE_SUBDIR_NAMES,
21
+ spaceMigrationDirectory,
22
+ spaceRefsDirectory,
23
+ } from '@prisma-next/migration-tools/spaces';
24
+ import { ifDefined } from '@prisma-next/utils/defined';
25
+ import { notOk, ok, type Result } from '@prisma-next/utils/result';
12
26
  import { Command } from 'commander';
13
27
  import { join, relative } from 'pathe';
14
28
  import { loadConfig } from '../config-loader';
29
+ import {
30
+ type CliStructuredError,
31
+ errorAmbiguousMigrationRef,
32
+ errorInvalidSpaceId,
33
+ errorSpaceNotFound,
34
+ mapRefResolutionError,
35
+ } from '../utils/cli-errors';
15
36
  import {
16
37
  addGlobalOptions,
17
38
  resolveContractPath,
@@ -20,28 +41,30 @@ import {
20
41
  setCommandExamples,
21
42
  setCommandSeeAlso,
22
43
  } from '../utils/command-helpers';
44
+ import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
23
45
  import { toDeclaredExtensionsFromRaw } from '../utils/extension-pack-inputs';
46
+ import { formatErrorJson, formatErrorOutput } from '../utils/formatters/errors';
24
47
  import { formatStyledHeader } from '../utils/formatters/styled';
25
48
  import type { CommonCommandOptions } from '../utils/global-flags';
26
49
  import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
50
+ import { integrityViolationToCheckFailure } from '../utils/integrity-violation-to-check-failure';
27
51
  import {
28
- type CheckFailure,
29
- integrityViolationToCheckFailure,
30
- } from '../utils/integrity-violation-to-check-failure';
52
+ findPackageByDirPath,
53
+ looksLikePath,
54
+ resolveAppTargetPath,
55
+ resolveTargetPathAcrossSpaces,
56
+ } from '../utils/migration-path-target';
31
57
  import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
58
+ import type { CheckFailure, MigrationCheckResult } from './json/schemas';
32
59
  import { INTEGRITY_FAILED, OK, PRECONDITION } from './migration-check/exit-codes';
33
60
 
34
61
  interface MigrationCheckOptions extends CommonCommandOptions {
35
62
  readonly config?: string;
63
+ readonly space?: string;
36
64
  }
37
65
 
38
- export type { CheckFailure } from '../utils/integrity-violation-to-check-failure';
39
-
40
- export interface MigrationCheckResult {
41
- readonly ok: boolean;
42
- readonly failures: readonly CheckFailure[];
43
- readonly summary: string;
44
- }
66
+ export type { CheckFailure, MigrationCheckResult } from './json/schemas';
67
+ export { migrationCheckResultSchema } from './json/schemas';
45
68
 
46
69
  function migrationPathRelative(dirPath: string): string {
47
70
  return relative(process.cwd(), dirPath);
@@ -51,10 +74,16 @@ function migrationFileRelative(dirPath: string, fileName: string): string {
51
74
  return join(migrationPathRelative(dirPath), fileName);
52
75
  }
53
76
 
54
- function checkFileExists(dirPath: string, dirName: string, fileName: string): CheckFailure | null {
77
+ function checkFileExists(
78
+ spaceId: string,
79
+ dirPath: string,
80
+ dirName: string,
81
+ fileName: string,
82
+ ): CheckFailure | null {
55
83
  if (!existsSync(join(dirPath, fileName))) {
56
84
  return {
57
- pnCode: 'PN-MIG-CHECK-002',
85
+ space: spaceId,
86
+ code: 'PN-MIG-CHECK-002',
58
87
  where: migrationFileRelative(dirPath, fileName),
59
88
  why: `${fileName} is missing from ${dirName}`,
60
89
  fix: 'Re-emit the migration package or restore from version control.',
@@ -63,7 +92,10 @@ function checkFileExists(dirPath: string, dirName: string, fileName: string): Ch
63
92
  return null;
64
93
  }
65
94
 
66
- function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | null {
95
+ function checkSnapshotConsistency(
96
+ spaceId: string,
97
+ pkg: OnDiskMigrationPackage,
98
+ ): CheckFailure | null {
67
99
  const endContractPath = join(pkg.dirPath, 'end-contract.json');
68
100
  if (!existsSync(endContractPath)) return null;
69
101
  try {
@@ -72,7 +104,8 @@ function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | n
72
104
  const snapshotHash = storage?.['storageHash'];
73
105
  if (typeof snapshotHash === 'string' && snapshotHash !== pkg.metadata.to) {
74
106
  return {
75
- pnCode: 'PN-MIG-CHECK-005',
107
+ space: spaceId,
108
+ code: 'PN-MIG-CHECK-005',
76
109
  where: migrationPathRelative(pkg.dirPath),
77
110
  why: `Migration "${pkg.dirName}" declares to=${pkg.metadata.to} but end-contract.json has storageHash=${snapshotHash}`,
78
111
  fix: 'Re-emit the migration package so migration.json and end-contract.json agree.',
@@ -80,7 +113,8 @@ function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | n
80
113
  }
81
114
  } catch {
82
115
  return {
83
- pnCode: 'PN-MIG-CHECK-006',
116
+ space: spaceId,
117
+ code: 'PN-MIG-CHECK-006',
84
118
  where: migrationPathRelative(pkg.dirPath),
85
119
  why: `Migration "${pkg.dirName}" has an unparseable end-contract.json.`,
86
120
  fix: 'Re-emit the migration package to repair the snapshot file.',
@@ -89,6 +123,177 @@ function checkSnapshotConsistency(pkg: OnDiskMigrationPackage): CheckFailure | n
89
123
  return null;
90
124
  }
91
125
 
126
+ /**
127
+ * One contract space's on-disk state, resolved for the explicit graph
128
+ * checks `runMigrationCheck` runs per space: the space's migration
129
+ * packages, its user-authored refs, its induced graph, and the absolute
130
+ * `migrations/<space>/` + `migrations/<space>/refs/` directories the
131
+ * file-existence and dangling-ref `where` paths are derived from.
132
+ */
133
+ export interface CheckSpace {
134
+ readonly spaceId: string;
135
+ readonly packages: readonly OnDiskMigrationPackage[];
136
+ readonly refs: Refs;
137
+ readonly graph: MigrationGraph;
138
+ readonly migrationsDir: string;
139
+ readonly refsDir: string;
140
+ }
141
+
142
+ /**
143
+ * Project the loaded {@link ContractSpaceAggregate} into the
144
+ * {@link CheckSpace} rows the multi-space check iterates — one per on-disk
145
+ * contract-space directory, in the aggregate's `app`-first ordering. Mirrors
146
+ * `migration list`'s `migrationSpaceListEntriesFromAggregate`: space
147
+ * membership matches the on-disk directories, package / ref / graph data come
148
+ * from `aggregate.space(id)`.
149
+ */
150
+ export async function enumerateCheckSpaces(
151
+ aggregate: ContractSpaceAggregate,
152
+ projectMigrationsDir: string,
153
+ ): Promise<readonly CheckSpace[]> {
154
+ const candidateDirs = await listContractSpaceDirectories(projectMigrationsDir);
155
+ const onDiskSpaceIds = new Set(
156
+ candidateDirs.filter((name) => !RESERVED_SPACE_SUBDIR_NAMES.has(name)).filter(isValidSpaceId),
157
+ );
158
+ const spaces: CheckSpace[] = [];
159
+ for (const member of aggregate.spaces()) {
160
+ const spaceId = member.spaceId;
161
+ if (!isValidSpaceId(spaceId)) continue;
162
+ if (!onDiskSpaceIds.has(spaceId)) continue;
163
+ const migrationsDir = spaceMigrationDirectory(projectMigrationsDir, spaceId);
164
+ spaces.push({
165
+ spaceId,
166
+ packages: member.packages,
167
+ refs: member.refs,
168
+ graph: member.graph(),
169
+ migrationsDir,
170
+ refsDir: spaceRefsDirectory(migrationsDir),
171
+ });
172
+ }
173
+ return spaces;
174
+ }
175
+
176
+ function checkManifestFilesPresent(space: CheckSpace): readonly CheckFailure[] {
177
+ if (!existsSync(space.migrationsDir)) return [];
178
+ const loadedDirNames = new Set(space.packages.map((p) => p.dirName));
179
+ const failures: CheckFailure[] = [];
180
+ let entries: string[];
181
+ try {
182
+ entries = readdirSync(space.migrationsDir);
183
+ } catch {
184
+ return failures;
185
+ }
186
+ for (const entry of entries) {
187
+ if (entry.startsWith('.') || entry.startsWith('_') || entry === 'refs') continue;
188
+ const entryPath = join(space.migrationsDir, entry);
189
+ try {
190
+ if (!statSync(entryPath).isDirectory()) continue;
191
+ } catch {
192
+ continue;
193
+ }
194
+ if (!loadedDirNames.has(entry)) {
195
+ for (const f of ['migration.json', 'ops.json']) {
196
+ const fail = checkFileExists(space.spaceId, entryPath, entry, f);
197
+ if (fail) failures.push(fail);
198
+ }
199
+ }
200
+ }
201
+ return failures;
202
+ }
203
+
204
+ function checkReachability(space: CheckSpace): readonly CheckFailure[] {
205
+ const allToHashes = new Set(space.packages.map((p) => p.metadata.to));
206
+ const failures: CheckFailure[] = [];
207
+ for (const pkg of space.packages) {
208
+ const isReachable =
209
+ pkg.metadata.from === null ||
210
+ allToHashes.has(pkg.metadata.from) ||
211
+ pkg.metadata.from === 'sha256:empty';
212
+ if (!isReachable) {
213
+ failures.push({
214
+ space: space.spaceId,
215
+ code: 'PN-MIG-CHECK-003',
216
+ where: migrationPathRelative(pkg.dirPath),
217
+ why: `Migration "${pkg.dirName}" starts from ${pkg.metadata.from} which no other migration produces`,
218
+ fix: 'This migration is unreachable in the graph. Delete it or re-emit a connecting migration.',
219
+ });
220
+ }
221
+ }
222
+ return failures;
223
+ }
224
+
225
+ function checkDanglingRefs(space: CheckSpace): readonly CheckFailure[] {
226
+ const failures: CheckFailure[] = [];
227
+ for (const [name, entry] of Object.entries(space.refs)) {
228
+ if (!space.graph.nodes.has(entry.hash)) {
229
+ failures.push({
230
+ space: space.spaceId,
231
+ code: 'PN-MIG-CHECK-004',
232
+ where: relative(process.cwd(), join(space.refsDir, `${name}.json`)),
233
+ why: `Ref "${name}" points at ${entry.hash} which does not exist in the migration graph`,
234
+ fix: `Update the ref with \`prisma-next ref set ${name} <valid-hash>\` or delete it.`,
235
+ });
236
+ }
237
+ }
238
+ return failures;
239
+ }
240
+
241
+ function checkSpace(space: CheckSpace): readonly CheckFailure[] {
242
+ return [
243
+ ...checkManifestFilesPresent(space),
244
+ ...space.packages
245
+ .map((pkg) => checkSnapshotConsistency(space.spaceId, pkg))
246
+ .filter((f): f is CheckFailure => f !== null),
247
+ ...checkReachability(space),
248
+ ...checkDanglingRefs(space),
249
+ ];
250
+ }
251
+
252
+ /**
253
+ * Inputs for {@link runMigrationCheck} — the multi-space policy core of
254
+ * the holistic (no-arg) `migration check`. Enumeration is supplied by the
255
+ * caller (the CLI shell builds it from {@link enumerateCheckSpaces}); the
256
+ * core does not touch config, flags, or streams.
257
+ */
258
+ export interface RunMigrationCheckInputs {
259
+ readonly spaces: readonly CheckSpace[];
260
+ readonly spaceFilter?: string;
261
+ }
262
+
263
+ /**
264
+ * Policy core of the holistic `migration check`: validates `--space`,
265
+ * narrows the pre-enumerated spaces, and runs the per-space explicit graph
266
+ * checks (file-existence, snapshot consistency, reachability, dangling
267
+ * refs), aggregating every failure into one {@link MigrationCheckResult}.
268
+ *
269
+ * `--space` validation mirrors `migration list`: an invalid id →
270
+ * {@link errorInvalidSpaceId}; an id with no on-disk space →
271
+ * {@link errorSpaceNotFound}. Both map to exit `PRECONDITION` at the shell.
272
+ * Aggregate-integrity violations (which already span every space) are folded
273
+ * in by the caller, not here.
274
+ */
275
+ export function runMigrationCheck(
276
+ inputs: RunMigrationCheckInputs,
277
+ ): Result<MigrationCheckResult, CliStructuredError> {
278
+ const { spaces, spaceFilter } = inputs;
279
+
280
+ if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {
281
+ return notOk(errorInvalidSpaceId(spaceFilter));
282
+ }
283
+ if (spaceFilter !== undefined && !spaces.some((s) => s.spaceId === spaceFilter)) {
284
+ return notOk(errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()));
285
+ }
286
+
287
+ const scopedSpaces =
288
+ spaceFilter !== undefined ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;
289
+
290
+ const failures = scopedSpaces.flatMap(checkSpace);
291
+ if (failures.length === 0) {
292
+ return ok({ ok: true, failures: [], summary: 'All checks passed' });
293
+ }
294
+ return ok({ ok: false, failures, summary: `${failures.length} integrity failure(s)` });
295
+ }
296
+
92
297
  async function loadAggregateIntegrityViolations(
93
298
  config: Awaited<ReturnType<typeof loadConfig>>,
94
299
  migrationsDir: string,
@@ -110,14 +315,21 @@ async function loadAggregateIntegrityViolations(
110
315
  }
111
316
  }
112
317
 
318
+ interface MigrationCheckOutcome {
319
+ readonly result?: MigrationCheckResult;
320
+ readonly error?: CliStructuredError;
321
+ readonly exitCode: number;
322
+ readonly resolvedSpaceId?: string;
323
+ }
324
+
113
325
  async function executeMigrationCheckCommand(
114
326
  target: string | undefined,
115
327
  options: MigrationCheckOptions,
116
328
  flags: GlobalFlags,
117
329
  ui: TerminalUI,
118
- ): Promise<{ result: MigrationCheckResult; exitCode: number }> {
330
+ ): Promise<MigrationCheckOutcome> {
119
331
  const config = await loadConfig(options.config);
120
- const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative, refsDir } =
332
+ const { configPath, migrationsDir, appMigrationsDir, appMigrationsRelative } =
121
333
  resolveMigrationPaths(options.config, config);
122
334
 
123
335
  if (!flags.json && !flags.quiet) {
@@ -137,136 +349,231 @@ async function executeMigrationCheckCommand(
137
349
  ui.stderr(header);
138
350
  }
139
351
 
140
- const failures: CheckFailure[] = [];
352
+ const loadedAggregate = await buildReadAggregate(config, { migrationsDir });
353
+ if (!loadedAggregate.ok) {
354
+ return { error: loadedAggregate.failure, exitCode: PRECONDITION };
355
+ }
141
356
 
142
- const loaded = await readMigrationsDir(appMigrationsDir);
143
- const bundles: readonly OnDiskMigrationPackage[] = loaded.packages;
144
- const graph = reconstructGraph(bundles);
357
+ const spaces = await enumerateCheckSpaces(loadedAggregate.value.aggregate, migrationsDir);
145
358
 
146
- if (existsSync(appMigrationsDir)) {
147
- const loadedDirNames = new Set(bundles.map((p) => p.dirName));
148
- try {
149
- const entries = readdirSync(appMigrationsDir);
150
- for (const entry of entries) {
151
- if (entry.startsWith('.') || entry.startsWith('_') || entry === 'refs') continue;
152
- const entryPath = join(appMigrationsDir, entry);
153
- try {
154
- if (!statSync(entryPath).isDirectory()) continue;
155
- } catch {
156
- continue;
359
+ if (target) {
360
+ return await checkSingleTarget(target, {
361
+ spaces,
362
+ ...(options.space !== undefined ? { spaceFilter: options.space } : {}),
363
+ appMigrationsDir,
364
+ appMigrationsRelative,
365
+ });
366
+ }
367
+
368
+ const checkResult = runMigrationCheck({
369
+ spaces,
370
+ ...(options.space !== undefined ? { spaceFilter: options.space } : {}),
371
+ });
372
+ if (!checkResult.ok) {
373
+ return { error: checkResult.failure, exitCode: PRECONDITION };
374
+ }
375
+
376
+ const failures: CheckFailure[] = [...checkResult.value.failures];
377
+ const allViolations = await loadAggregateIntegrityViolations(config, migrationsDir);
378
+ const scopedViolations =
379
+ options.space === undefined
380
+ ? allViolations
381
+ : allViolations.filter((v) => v.kind !== 'disjointness' && v.spaceId === options.space);
382
+ for (const violation of scopedViolations) {
383
+ failures.push(integrityViolationToCheckFailure(violation, migrationsDir));
384
+ }
385
+
386
+ if (failures.length === 0) {
387
+ return {
388
+ result: { ok: true, failures: [], summary: 'All checks passed' },
389
+ exitCode: OK,
390
+ };
391
+ }
392
+
393
+ return {
394
+ result: { ok: false, failures, summary: `${failures.length} integrity failure(s)` },
395
+ exitCode: INTEGRITY_FAILED,
396
+ };
397
+ }
398
+
399
+ interface SingleTargetInputs {
400
+ readonly spaces: readonly CheckSpace[];
401
+ readonly spaceFilter?: string;
402
+ readonly appMigrationsDir: string;
403
+ readonly appMigrationsRelative: string;
404
+ }
405
+
406
+ /**
407
+ * Ranks ref-resolution failure kinds by how informative they are, so a
408
+ * single-target check surfaces the most useful failure across spaces instead of
409
+ * whichever space failed first. `not-found` (the input matched nothing here)
410
+ * says the least; a malformed input, a wrong grammar, or an in-space ambiguity
411
+ * all say more.
412
+ */
413
+ function refFailureSpecificity(error: RefResolutionError): number {
414
+ switch (error.kind) {
415
+ case 'wrong-grammar':
416
+ return 3;
417
+ case 'ambiguous':
418
+ return 2;
419
+ case 'invalid-format':
420
+ return 1;
421
+ case 'not-found':
422
+ return 0;
423
+ }
424
+ }
425
+
426
+ /**
427
+ * Single-target (`check <ref/path>`) mode — resolves a migration reference
428
+ * across all contract spaces (or the one space narrowed by `--space <id>`).
429
+ *
430
+ * Resolution:
431
+ * - filesystem path → find the owning space by checking which space's
432
+ * `migrationsDir` contains the resolved path; falls back to app-relative
433
+ * validation when the path is outside every space dir.
434
+ * - ref → `parseMigrationRef` against each in-scope space; collect every
435
+ * (space, package) hit; 0 hits = not-found, 1 = check it, >1 = ambiguity
436
+ * error (qualify with `--space`).
437
+ *
438
+ * `--space <id>` is validated the same way the holistic path does it:
439
+ * invalid id → `errorInvalidSpaceId`; no on-disk space → `errorSpaceNotFound`.
440
+ */
441
+ async function checkSingleTarget(
442
+ target: string,
443
+ inputs: SingleTargetInputs,
444
+ ): Promise<MigrationCheckOutcome> {
445
+ const { spaces, spaceFilter, appMigrationsDir, appMigrationsRelative } = inputs;
446
+
447
+ if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {
448
+ return { error: errorInvalidSpaceId(spaceFilter), exitCode: PRECONDITION };
449
+ }
450
+ if (spaceFilter !== undefined && !spaces.some((s) => s.spaceId === spaceFilter)) {
451
+ return {
452
+ error: errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()),
453
+ exitCode: PRECONDITION,
454
+ };
455
+ }
456
+
457
+ const scopedSpaces =
458
+ spaceFilter !== undefined ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;
459
+
460
+ let matchedSpace: CheckSpace | undefined;
461
+ let matchedPkg: OnDiskMigrationPackage | undefined;
462
+
463
+ if (looksLikePath(target)) {
464
+ const resolvedPath = resolveTargetPathAcrossSpaces(target, scopedSpaces);
465
+ if (resolvedPath !== null) {
466
+ for (const space of scopedSpaces) {
467
+ const found = findPackageByDirPath(space.packages, resolvedPath);
468
+ if (found) {
469
+ matchedSpace = space;
470
+ matchedPkg = found;
471
+ break;
157
472
  }
158
- if (!loadedDirNames.has(entry)) {
159
- for (const f of ['migration.json', 'ops.json']) {
160
- const fail = checkFileExists(entryPath, entry, f);
161
- if (fail) failures.push(fail);
162
- }
473
+ }
474
+ } else {
475
+ // Path outside every space dir — fall back to app-relative validation
476
+ const resolved = resolveAppTargetPath(target, appMigrationsDir, appMigrationsRelative);
477
+ if (!resolved.ok) {
478
+ return { error: resolved.failure, exitCode: PRECONDITION };
479
+ }
480
+ const appSpace = scopedSpaces.find((s) => s.spaceId === 'app');
481
+ if (appSpace) {
482
+ matchedSpace = appSpace;
483
+ matchedPkg = findPackageByDirPath(appSpace.packages, resolved.value);
484
+ }
485
+ }
486
+ } else {
487
+ // Ref resolution: try each in-scope space, collect all hits.
488
+ const hits: Array<{ space: CheckSpace; pkg: OnDiskMigrationPackage }> = [];
489
+ let bestParseFailure: RefResolutionError | undefined;
490
+ for (const space of scopedSpaces) {
491
+ const migResult = parseMigrationRef(target, { graph: space.graph, refs: space.refs });
492
+ if (!migResult.ok) {
493
+ // Keep scanning — a later space may hold a hit that must not be discarded.
494
+ // When no space yields a hit, keep the most informative failure rather than
495
+ // whichever space failed first (the kind is space-dependent).
496
+ if (
497
+ bestParseFailure === undefined ||
498
+ refFailureSpecificity(migResult.failure) > refFailureSpecificity(bestParseFailure)
499
+ ) {
500
+ bestParseFailure = migResult.failure;
163
501
  }
502
+ continue;
503
+ }
504
+ const pkg = space.packages.find(
505
+ (p) => p.metadata.migrationHash === migResult.value.migrationHash,
506
+ );
507
+ if (pkg) {
508
+ hits.push({ space, pkg });
164
509
  }
165
- } catch {
166
- // migrations dir unreadable — skip
167
510
  }
168
- }
169
511
 
170
- if (target) {
171
- const refs = await readRefs(refsDir);
172
- const migResult = parseMigrationRef(target, { graph, refs });
173
- if (!migResult.ok) {
174
- const msg =
175
- migResult.failure.kind === 'not-found'
176
- ? `Migration "${target}" does not exist`
177
- : migResult.failure.kind === 'wrong-grammar'
178
- ? migResult.failure.message
179
- : `Invalid migration reference: "${target}"`;
512
+ if (hits.length > 1) {
513
+ const spaceIds = hits.map((h) => h.space.spaceId);
180
514
  return {
181
- result: { ok: false, failures: [], summary: msg },
515
+ error: errorAmbiguousMigrationRef(target, spaceIds),
182
516
  exitCode: PRECONDITION,
183
517
  };
184
518
  }
185
519
 
186
- const matchedPkg = bundles.find(
187
- (p) => p.metadata.migrationHash === migResult.value.migrationHash,
188
- );
189
- if (!matchedPkg) {
190
- return {
191
- result: {
192
- ok: false,
193
- failures: [],
194
- summary: `Migration package for "${target}" not found on disk`,
195
- },
196
- exitCode: PRECONDITION,
197
- };
520
+ if (hits.length === 1) {
521
+ matchedSpace = hits[0]!.space;
522
+ matchedPkg = hits[0]!.pkg;
523
+ } else if (bestParseFailure !== undefined) {
524
+ // The ref didn't resolve in any in-scope space — surface the most informative
525
+ // parse failure through the shared ref-resolution envelope (PN-RUN-3000) the
526
+ // earlier work established, rather than a bespoke string. (Ref-resolved-but-
527
+ // no-package falls through to the "not found on disk" result below.)
528
+ return { error: mapRefResolutionError(bestParseFailure), exitCode: PRECONDITION };
198
529
  }
530
+ }
199
531
 
200
- for (const f of ['migration.json', 'ops.json']) {
201
- const fail = checkFileExists(matchedPkg.dirPath, matchedPkg.dirName, f);
202
- if (fail) failures.push(fail);
203
- }
532
+ if (!matchedPkg || !matchedSpace) {
533
+ return {
534
+ result: {
535
+ ok: false,
536
+ failures: [],
537
+ summary: `Migration package for "${target}" not found on disk`,
538
+ },
539
+ exitCode: PRECONDITION,
540
+ };
541
+ }
204
542
 
205
- const verification = verifyMigrationHash(matchedPkg);
206
- if (!verification.ok) {
207
- failures.push({
208
- pnCode: 'PN-MIG-CHECK-001',
209
- where: migrationFileRelative(matchedPkg.dirPath, 'migration.json'),
210
- why: `Stored hash ${verification.storedHash} does not match recomputed hash ${verification.computedHash}`,
211
- fix: 'Re-emit the migration package or restore from version control.',
212
- });
213
- }
543
+ const failures: CheckFailure[] = [...checkManifestFilesPresent(matchedSpace)];
214
544
 
215
- const snapshotFailure = checkSnapshotConsistency(matchedPkg);
216
- if (snapshotFailure) failures.push(snapshotFailure);
217
- } else {
218
- for (const pkg of bundles) {
219
- const snapshotFailure = checkSnapshotConsistency(pkg);
220
- if (snapshotFailure) failures.push(snapshotFailure);
221
- }
545
+ for (const f of ['migration.json', 'ops.json']) {
546
+ const fail = checkFileExists(matchedSpace.spaceId, matchedPkg.dirPath, matchedPkg.dirName, f);
547
+ if (fail) failures.push(fail);
548
+ }
222
549
 
223
- const allToHashes = new Set(bundles.map((p) => p.metadata.to));
224
- for (const pkg of bundles) {
225
- const isReachable =
226
- pkg.metadata.from === null ||
227
- allToHashes.has(pkg.metadata.from) ||
228
- pkg.metadata.from === 'sha256:empty';
229
- if (!isReachable) {
230
- failures.push({
231
- pnCode: 'PN-MIG-CHECK-003',
232
- where: migrationPathRelative(pkg.dirPath),
233
- why: `Migration "${pkg.dirName}" starts from ${pkg.metadata.from} which no other migration produces`,
234
- fix: 'This migration is unreachable in the graph. Delete it or re-emit a connecting migration.',
235
- });
236
- }
237
- }
550
+ const verification = verifyMigrationHash(matchedPkg);
551
+ if (!verification.ok) {
552
+ failures.push({
553
+ space: matchedSpace.spaceId,
554
+ code: 'PN-MIG-CHECK-001',
555
+ where: migrationFileRelative(matchedPkg.dirPath, 'migration.json'),
556
+ why: `Stored hash ${verification.storedHash} does not match recomputed hash ${verification.computedHash}`,
557
+ fix: 'Re-emit the migration package or restore from version control.',
558
+ });
559
+ }
238
560
 
239
- try {
240
- const refs = await readRefs(refsDir);
241
- for (const [name, entry] of Object.entries(refs)) {
242
- if (!graph.nodes.has(entry.hash)) {
243
- failures.push({
244
- pnCode: 'PN-MIG-CHECK-004',
245
- where: relative(process.cwd(), join(refsDir, `${name}.json`)),
246
- why: `Ref "${name}" points at ${entry.hash} which does not exist in the migration graph`,
247
- fix: `Update the ref with \`prisma-next ref set ${name} <valid-hash>\` or delete it.`,
248
- });
249
- }
250
- }
251
- } catch {
252
- // Refs unreadable — skip ref checks
253
- }
561
+ const snapshotFailure = checkSnapshotConsistency(matchedSpace.spaceId, matchedPkg);
562
+ if (snapshotFailure) failures.push(snapshotFailure);
254
563
 
255
- for (const violation of await loadAggregateIntegrityViolations(config, migrationsDir)) {
256
- failures.push(integrityViolationToCheckFailure(violation, migrationsDir));
257
- }
258
- }
564
+ const resolvedSpaceId = matchedSpace.spaceId !== 'app' ? matchedSpace.spaceId : undefined;
259
565
 
260
566
  if (failures.length === 0) {
261
567
  return {
262
568
  result: { ok: true, failures: [], summary: 'All checks passed' },
263
569
  exitCode: OK,
570
+ ...ifDefined('resolvedSpaceId', resolvedSpaceId),
264
571
  };
265
572
  }
266
-
267
573
  return {
268
574
  result: { ok: false, failures, summary: `${failures.length} integrity failure(s)` },
269
575
  exitCode: INTEGRITY_FAILED,
576
+ ...ifDefined('resolvedSpaceId', resolvedSpaceId),
270
577
  };
271
578
  }
272
579
 
@@ -277,52 +584,80 @@ export function createMigrationCheckCommand(): Command {
277
584
  'Verify artifact and graph integrity',
278
585
  'Validates that on-disk migration packages are internally consistent\n' +
279
586
  '(hashes match, manifests are complete) and that the graph is well-formed\n' +
280
- '(edges connect, refs point at valid nodes). Offline does not consult\n' +
281
- 'the database.',
587
+ '(edges connect, refs point at valid nodes). The whole-graph check spans\n' +
588
+ 'every contract space by default; pass --space <id> to narrow to one. A\n' +
589
+ 'migration reference checks a single package, resolved across all contract\n' +
590
+ 'spaces (narrow with --space; an ambiguous reference is a precondition failure).\n' +
591
+ 'Offline — does not consult the database.\n' +
592
+ 'Exit codes: 0 = all checks passed, 2 = precondition failed\n' +
593
+ '(unresolved target or unknown --space), 4 = integrity failure(s) found.',
282
594
  );
283
595
  setCommandExamples(command, [
284
596
  'prisma-next migration check',
597
+ 'prisma-next migration check --space app',
285
598
  'prisma-next migration check 20260101-add-users',
599
+ 'prisma-next migration check 20260101-add-users --space app',
286
600
  'prisma-next migration check --json',
287
601
  ]);
288
602
  setCommandSeeAlso(command, [
289
603
  { verb: 'migration status', oneLiner: 'Show migration path and pending status' },
290
604
  { verb: 'migration list', oneLiner: 'List on-disk migrations' },
291
605
  { verb: 'migration graph', oneLiner: 'Show the migration graph topology' },
606
+ { verb: 'migration show', oneLiner: 'Display migration package contents' },
292
607
  ]);
293
608
  command.exitOverride();
294
609
  addGlobalOptions(command)
295
- .argument('[migration]', 'Migration reference (directory name or hash) to check')
610
+ .argument('[target]', 'Migration reference: directory name, hash/prefix, ref, or path')
296
611
  .option('--config <path>', 'Path to prisma-next.config.ts')
612
+ .option('--space <id>', 'Narrow output to a single contract space')
297
613
  .action(async (target: string | undefined, options: MigrationCheckOptions) => {
298
614
  const flags = parseGlobalFlagsOrExit(options);
299
615
  const ui = createTerminalUI(flags);
300
616
 
301
- let result: MigrationCheckResult;
302
- let exitCode: number;
617
+ let outcome: MigrationCheckOutcome;
303
618
  try {
304
- ({ result, exitCode } = await executeMigrationCheckCommand(target, options, flags, ui));
619
+ outcome = await executeMigrationCheckCommand(target, options, flags, ui);
305
620
  } catch (error) {
306
621
  const msg = error instanceof Error ? error.message : String(error);
307
- result = { ok: false, failures: [], summary: msg };
308
- exitCode = PRECONDITION;
622
+ outcome = {
623
+ result: { ok: false, failures: [], summary: msg },
624
+ exitCode: PRECONDITION,
625
+ };
626
+ }
627
+
628
+ if (outcome.error) {
629
+ const envelope = outcome.error.toEnvelope();
630
+ if (flags.json) {
631
+ ui.output(formatErrorJson(envelope));
632
+ } else if (!flags.quiet) {
633
+ ui.error(formatErrorOutput(envelope, flags));
634
+ }
635
+ process.exit(outcome.exitCode);
309
636
  }
310
637
 
638
+ const result = outcome.result ?? {
639
+ ok: false,
640
+ failures: [],
641
+ summary: 'No check result produced',
642
+ };
643
+
311
644
  if (flags.json) {
312
645
  ui.output(JSON.stringify(result, null, 2));
313
646
  } else if (!flags.quiet) {
314
647
  if (result.ok) {
315
- ui.log(`✔ ${result.summary}`);
648
+ const spaceSuffix =
649
+ outcome.resolvedSpaceId !== undefined ? ` (space: ${outcome.resolvedSpaceId})` : '';
650
+ ui.log(`✔ ${result.summary}${spaceSuffix}`);
316
651
  } else {
317
652
  for (const f of result.failures) {
318
- ui.log(`✗ [${f.pnCode}] ${f.where}: ${f.why}`);
653
+ ui.log(`✗ [${f.code}] ${f.where}: ${f.why}`);
319
654
  ui.log(` fix: ${f.fix}`);
320
655
  }
321
656
  ui.log(`\n${result.summary}`);
322
657
  }
323
658
  }
324
659
 
325
- process.exit(exitCode);
660
+ process.exit(outcome.exitCode);
326
661
  });
327
662
 
328
663
  return command;