@prisma-next/cli 0.11.0-dev.9 → 0.12.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 (186) hide show
  1. package/README.md +13 -9
  2. package/dist/cli.mjs +9 -10
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/{client-UnIveZxZ.mjs → client-KgJorIvG.mjs} +72 -60
  5. package/dist/client-KgJorIvG.mjs.map +1 -0
  6. package/dist/{command-helpers-CRfjbZRz.mjs → command-helpers-Bbw1GbwL.mjs} +642 -45
  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 +5 -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 +12 -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 +2 -2
  27. package/dist/commands/migrate.d.mts.map +1 -1
  28. package/dist/commands/migrate.mjs +68 -69
  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 +2 -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 +84 -80
  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 +10 -8
  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-C6rlsljO.mjs → contract-emit-D-4jrNve.mjs} +21 -7
  67. package/dist/{contract-emit-C6rlsljO.mjs.map → contract-emit-D-4jrNve.mjs.map} +1 -1
  68. package/dist/{contract-emit-mqXmapxB.mjs → contract-emit-DxcGl4Uq.mjs} +4 -6
  69. package/dist/{contract-emit-mqXmapxB.mjs.map → contract-emit-DxcGl4Uq.mjs.map} +1 -1
  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-C4jxc1aZ.mjs → contract-infer-D8uEbJuu.mjs} +3 -4
  73. package/dist/{contract-infer-C4jxc1aZ.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-1d8tDoFN.mjs → db-verify-v_vUKXTU.mjs} +5 -7
  77. package/dist/{db-verify-1d8tDoFN.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-Bexd0f4E.mjs → framework-components-fYXjz_in.mjs} +2 -2
  89. package/dist/{framework-components-Bexd0f4E.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-BE8vmJ_7.mjs → graph-render-rFAqZujX.mjs} +2 -2
  93. package/dist/{graph-render-BE8vmJ_7.mjs.map → graph-render-rFAqZujX.mjs.map} +1 -1
  94. package/dist/{init-ByoeQphC.mjs → init-Cv9UzWL5.mjs} +17 -19
  95. package/dist/init-Cv9UzWL5.mjs.map +1 -0
  96. package/dist/{inspect-live-schema-B1Q49RF0.mjs → inspect-live-schema-C6ohV_oQ.mjs} +4 -5
  97. package/dist/{inspect-live-schema-B1Q49RF0.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-oY4P1Qto.mjs → migration-command-scaffold-CjvwO6at.mjs} +4 -5
  104. package/dist/{migration-command-scaffold-oY4P1Qto.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-jdAHg_gK.mjs → migration-plan-9DJ7q7_z.mjs} +169 -189
  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-B7n518mT.mjs → migrations-Cv2jxNNK.mjs} +3 -13
  114. package/dist/migrations-Cv2jxNNK.mjs.map +1 -0
  115. package/dist/{output-CUIdfYo5.mjs → output-B60Gw5fu.mjs} +1 -1
  116. package/dist/{output-CUIdfYo5.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-DRh5Nquq.mjs → ref-advancement-DUZqsue6.mjs} +1 -1
  120. package/dist/{ref-advancement-DRh5Nquq.mjs.map → ref-advancement-DUZqsue6.mjs.map} +1 -1
  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-UWB2-rrw.d.mts → types-Dt_SfqFm.d.mts} +18 -26
  124. package/dist/types-Dt_SfqFm.d.mts.map +1 -0
  125. package/dist/{verify-C5UvbrF1.mjs → verify-DCA9Sldu.mjs} +2 -2
  126. package/dist/{verify-C5UvbrF1.mjs.map → verify-DCA9Sldu.mjs.map} +1 -1
  127. package/package.json +34 -23
  128. package/src/commands/db-sign.ts +9 -5
  129. package/src/commands/db-update.ts +9 -8
  130. package/src/commands/init/templates/env.ts +13 -14
  131. package/src/commands/migrate.ts +87 -76
  132. package/src/commands/migration-check.ts +43 -83
  133. package/src/commands/migration-graph.ts +75 -60
  134. package/src/commands/migration-list.ts +220 -74
  135. package/src/commands/migration-log.ts +8 -14
  136. package/src/commands/migration-new.ts +44 -48
  137. package/src/commands/migration-plan.ts +107 -129
  138. package/src/commands/migration-show.ts +65 -284
  139. package/src/commands/migration-status.ts +127 -124
  140. package/src/commands/ref.ts +9 -4
  141. package/src/control-api/client.ts +0 -1
  142. package/src/control-api/contract-enrichment.ts +6 -42
  143. package/src/control-api/operations/{apply-aggregate.ts → apply.ts} +44 -75
  144. package/src/control-api/operations/contract-emit.ts +7 -2
  145. package/src/control-api/operations/{db-apply-aggregate.ts → db-apply.ts} +19 -19
  146. package/src/control-api/operations/db-init.ts +4 -4
  147. package/src/control-api/operations/db-update.ts +4 -4
  148. package/src/control-api/operations/db-verify.ts +15 -11
  149. package/src/control-api/operations/migration-apply.ts +56 -47
  150. package/src/control-api/types.ts +19 -27
  151. package/src/migration-cli.ts +4 -4
  152. package/src/utils/cli-errors.ts +73 -12
  153. package/src/utils/command-helpers.ts +1 -20
  154. package/src/utils/contract-at-errors.ts +96 -0
  155. package/src/utils/contract-space-aggregate-loader.ts +336 -117
  156. package/src/utils/formatters/migration-graph-layout.ts +1119 -0
  157. package/src/utils/formatters/migration-graph-rows.ts +336 -0
  158. package/src/utils/formatters/migration-graph-tree-render.ts +459 -0
  159. package/src/utils/formatters/migration-list-data-column.ts +115 -0
  160. package/src/utils/formatters/migration-list-graph-topology.ts +368 -0
  161. package/src/utils/formatters/migration-list-render.ts +191 -0
  162. package/src/utils/formatters/migration-list-styler.ts +63 -0
  163. package/src/utils/formatters/migration-list-types.ts +21 -0
  164. package/src/utils/formatters/migrations.ts +12 -46
  165. package/src/utils/glyph-mode.ts +22 -0
  166. package/src/utils/integrity-violation-to-check-failure.ts +130 -0
  167. package/src/utils/plan-resolution.ts +134 -133
  168. package/src/utils/terminal-ui.ts +42 -1
  169. package/dist/cli-errors-Bw2GlweY.mjs +0 -175
  170. package/dist/cli-errors-Bw2GlweY.mjs.map +0 -1
  171. package/dist/client-UnIveZxZ.mjs.map +0 -1
  172. package/dist/command-helpers-CRfjbZRz.mjs.map +0 -1
  173. package/dist/commands/migration-check.mjs.map +0 -1
  174. package/dist/commands/migration-graph.mjs.map +0 -1
  175. package/dist/contract-enrichment-Dani0mMW.mjs.map +0 -1
  176. package/dist/contract-space-aggregate-loader-CGakRlKM.mjs +0 -160
  177. package/dist/contract-space-aggregate-loader-CGakRlKM.mjs.map +0 -1
  178. package/dist/global-flags-CdE7M0d9.d.mts +0 -15
  179. package/dist/global-flags-CdE7M0d9.d.mts.map +0 -1
  180. package/dist/init-ByoeQphC.mjs.map +0 -1
  181. package/dist/migration-plan-jdAHg_gK.mjs.map +0 -1
  182. package/dist/migrations-B7n518mT.mjs.map +0 -1
  183. package/dist/rolldown-runtime-twds-ZHy.mjs +0 -14
  184. package/dist/terminal-ui-BiB_8KNo.mjs +0 -379
  185. package/dist/terminal-ui-BiB_8KNo.mjs.map +0 -1
  186. package/dist/types-UWB2-rrw.d.mts.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import type { OperationPreview } from '@prisma-next/framework-components/control';
2
2
  import { cyan, green, yellow } from 'colorette';
3
3
 
4
- import type { AggregatePerSpaceExecutionEntry } from '../../control-api/types';
4
+ import type { PerSpaceExecutionEntry } from '../../control-api/types';
5
5
  import type { GlobalFlags } from '../global-flags';
6
6
  import { createColorFormatter, formatDim, isVerbose } from './helpers';
7
7
 
@@ -79,9 +79,9 @@ export interface MigrationCommandResult {
79
79
  * (extensions alphabetically, then app). Surfaces per-space markers
80
80
  * and the ops grouped by space, so the CLI summary can name which
81
81
  * space each op and marker belongs to instead of flattening them
82
- * into a single ambiguous list. See {@link AggregatePerSpaceExecutionEntry}.
82
+ * into a single ambiguous list. See {@link PerSpaceExecutionEntry}.
83
83
  */
84
- readonly perSpace?: ReadonlyArray<AggregatePerSpaceExecutionEntry>;
84
+ readonly perSpace?: ReadonlyArray<PerSpaceExecutionEntry>;
85
85
  readonly advancedRef?: { readonly name: string; readonly hash: string } | null;
86
86
  readonly plannedAdvanceRef?: { readonly name: string; readonly hash: string } | null;
87
87
  readonly summary: string;
@@ -101,7 +101,7 @@ export interface MigrationCommandResult {
101
101
  * entirely (no marker has been written yet).
102
102
  */
103
103
  export function formatPerSpaceBlock(
104
- perSpace: ReadonlyArray<AggregatePerSpaceExecutionEntry>,
104
+ perSpace: ReadonlyArray<PerSpaceExecutionEntry>,
105
105
  mode: 'plan' | 'apply',
106
106
  useColor: boolean,
107
107
  ): readonly string[] {
@@ -182,7 +182,7 @@ export function formatMigrationPlanOutput(
182
182
  );
183
183
  }
184
184
  } else if (result.plan?.operations && result.plan.operations.length > 0) {
185
- // Single-space fallback (no aggregate breakdown). Same flat tree
185
+ // App-only / no-aggregate-breakdown fallback. Same flat tree
186
186
  // we've always rendered.
187
187
  lines.push(`${formatDimText('│')}`);
188
188
  for (let i = 0; i < result.plan.operations.length; i++) {
@@ -267,7 +267,7 @@ export interface MigrationApplyCommandOutputResult {
267
267
  * alphabetically, then app). Always present for the aggregate-walking
268
268
  * `migrate` command.
269
269
  */
270
- readonly perSpace: readonly AggregatePerSpaceExecutionEntry[];
270
+ readonly perSpace: readonly PerSpaceExecutionEntry[];
271
271
  readonly timings?: {
272
272
  readonly total: number;
273
273
  };
@@ -314,9 +314,7 @@ export function formatMigrationApplyCommandOutput(
314
314
  return lines.join('\n');
315
315
  }
316
316
 
317
- interface MigrationShowSpacePresent {
318
- readonly kind: 'present';
319
- readonly spaceId: string;
317
+ interface MigrationShowPresent {
320
318
  readonly dirName: string;
321
319
  readonly dirPath: string;
322
320
  readonly from: string | null;
@@ -332,22 +330,11 @@ interface MigrationShowSpacePresent {
332
330
  readonly summary: string;
333
331
  }
334
332
 
335
- interface MigrationShowSpaceMissing {
336
- readonly kind: 'missing';
337
- readonly spaceId: string;
338
- readonly summary: string;
339
- }
340
-
341
- type MigrationShowSpaceResult = MigrationShowSpacePresent | MigrationShowSpaceMissing;
342
-
343
333
  interface MigrationShowResult {
344
- readonly spaces: readonly MigrationShowSpaceResult[];
334
+ readonly migration: MigrationShowPresent;
345
335
  }
346
336
 
347
- function formatSpaceShowBlock(
348
- space: MigrationShowSpacePresent,
349
- useColor: boolean,
350
- ): readonly string[] {
337
+ function formatSpaceShowBlock(space: MigrationShowPresent, useColor: boolean): readonly string[] {
351
338
  const formatGreen = createColorFormatter(useColor, green);
352
339
  const formatYellow = createColorFormatter(useColor, yellow);
353
340
  const formatDimText = (text: string) => formatDim(useColor, text);
@@ -403,28 +390,7 @@ export function formatMigrationShowOutput(result: MigrationShowResult, flags: Gl
403
390
  }
404
391
 
405
392
  const useColor = flags.color !== false;
406
- const formatDimText = (text: string) => formatDim(useColor, text);
407
- const multipleSpaces = result.spaces.length > 1;
408
- const lines: string[] = [];
409
-
410
- for (let i = 0; i < result.spaces.length; i++) {
411
- const space = result.spaces[i]!;
412
- if (multipleSpaces) {
413
- lines.push(formatDimText(`── ${space.spaceId} ──`));
414
- }
415
- if (space.kind === 'missing') {
416
- lines.push(formatDimText(` ${space.summary}`));
417
- } else {
418
- for (const line of formatSpaceShowBlock(space, useColor)) {
419
- lines.push(line);
420
- }
421
- }
422
- if (i < result.spaces.length - 1) {
423
- lines.push('');
424
- }
425
- }
426
-
427
- return lines.join('\n');
393
+ return formatSpaceShowBlock(result.migration, useColor).join('\n');
428
394
  }
429
395
 
430
396
  /**
@@ -475,8 +441,8 @@ export function formatMigrationApplyOutput(
475
441
  ),
476
442
  );
477
443
  } else if (result.marker) {
478
- // Single-space fallback (no aggregate breakdown surfaced e.g.
479
- // older callers / non-aggregate code paths). The label is
444
+ // App-only / no-aggregate-breakdown fallback (e.g. older callers
445
+ // / non-aggregate code paths). The label is
480
446
  // `App-space marker` (not `Signature`) so that when only one
481
447
  // marker is observable we still name what it covers explicitly.
482
448
  lines.push(`${formatDimText(` App-space marker: ${result.marker.storageHash}`)}`);
@@ -0,0 +1,22 @@
1
+ export type GlyphMode = 'unicode' | 'ascii';
2
+
3
+ export interface GlyphModeInput {
4
+ readonly isTTY: boolean;
5
+ readonly env: Readonly<Record<string, string | undefined>>;
6
+ }
7
+
8
+ function localeString(env: Readonly<Record<string, string | undefined>>): string {
9
+ return env['LC_ALL'] ?? env['LC_CTYPE'] ?? env['LANG'] ?? '';
10
+ }
11
+
12
+ function isUtf8Locale(env: Readonly<Record<string, string | undefined>>): boolean {
13
+ const locale = localeString(env);
14
+ if (locale.length === 0) return false;
15
+ return /UTF-8|utf8/i.test(locale);
16
+ }
17
+
18
+ export function detectGlyphMode(input: GlyphModeInput): GlyphMode {
19
+ if (!input.isTTY) return 'ascii';
20
+ if (!isUtf8Locale(input.env)) return 'ascii';
21
+ return 'unicode';
22
+ }
@@ -0,0 +1,130 @@
1
+ import type { IntegrityViolation } from '@prisma-next/migration-tools/aggregate';
2
+ import { join, relative } from 'pathe';
3
+
4
+ export interface CheckFailure {
5
+ readonly pnCode: string;
6
+ readonly where: string;
7
+ readonly why: string;
8
+ readonly fix: string;
9
+ }
10
+
11
+ function migrationPathRelative(dirPath: string): string {
12
+ return relative(process.cwd(), dirPath);
13
+ }
14
+
15
+ function migrationFileRelative(dirPath: string, fileName: string): string {
16
+ return join(migrationPathRelative(dirPath), fileName);
17
+ }
18
+
19
+ /**
20
+ * Map one {@link IntegrityViolation} onto a `migration check` failure row.
21
+ * Sole catalogue mapping from integrity violations to `PN-MIG-CHECK-*`.
22
+ */
23
+ export function integrityViolationToCheckFailure(
24
+ violation: IntegrityViolation,
25
+ migrationsDir: string,
26
+ ): CheckFailure {
27
+ const spaceRelative = (spaceId: string): string =>
28
+ migrationPathRelative(join(migrationsDir, spaceId));
29
+ const packageRelative = (spaceId: string, dirName: string): string =>
30
+ migrationPathRelative(join(migrationsDir, spaceId, dirName));
31
+ const refRelative = (spaceId: string, refName: string): string =>
32
+ migrationPathRelative(join(migrationsDir, spaceId, 'refs', `${refName}.json`));
33
+
34
+ switch (violation.kind) {
35
+ case 'hashMismatch':
36
+ return {
37
+ pnCode: 'PN-MIG-CHECK-001',
38
+ where: migrationFileRelative(
39
+ join(migrationsDir, violation.spaceId, violation.dirName),
40
+ 'migration.json',
41
+ ),
42
+ why: `Stored hash ${violation.stored} does not match recomputed hash ${violation.computed}`,
43
+ fix: 'Re-emit the migration package or restore from version control.',
44
+ };
45
+ case 'providedInvariantsMismatch':
46
+ return {
47
+ pnCode: 'PN-MIG-CHECK-002',
48
+ where: packageRelative(violation.spaceId, violation.dirName),
49
+ why: `Migration "${violation.dirName}" providedInvariants in migration.json disagrees with ops.json.`,
50
+ fix: 'Re-emit the migration package so migration.json and ops.json agree.',
51
+ };
52
+ case 'packageUnloadable':
53
+ return {
54
+ pnCode: 'PN-MIG-CHECK-002',
55
+ where: packageRelative(violation.spaceId, violation.dirName),
56
+ why: `Migration "${violation.dirName}" could not be loaded: ${violation.detail}`,
57
+ fix: 'Re-emit the migration package or restore from version control.',
58
+ };
59
+ case 'sameSourceAndTarget':
60
+ return {
61
+ pnCode: 'PN-MIG-CHECK-007',
62
+ where: packageRelative(violation.spaceId, violation.dirName),
63
+ why: `Migration "${violation.dirName}" in space "${violation.spaceId}" has source equal to target (${violation.hash}) with no data invariant — a true no-op self-edge.`,
64
+ fix: 'Add a data operation if this self-edge was meant to carry a data invariant, or delete the migration if it is a true no-op.',
65
+ };
66
+ case 'orphanSpaceDir':
67
+ return {
68
+ pnCode: 'PN-MIG-CHECK-008',
69
+ where: spaceRelative(violation.spaceId),
70
+ why: `Contract-space directory "${violation.spaceId}" exists on disk but no extension declares it.`,
71
+ fix: 'Remove the orphan directory, or declare the extension in `extensionPacks`.',
72
+ };
73
+ case 'declaredButUnmigrated':
74
+ return {
75
+ pnCode: 'PN-MIG-CHECK-009',
76
+ where: spaceRelative(violation.spaceId),
77
+ why: `Extension "${violation.spaceId}" is declared in \`extensionPacks\` but has no on-disk migrations directory.`,
78
+ fix: 'Re-emit the extension contract-space artefacts with `prisma-next contract emit` and migration planning, or remove the extension from `extensionPacks` if it is unused.',
79
+ };
80
+ case 'headRefMissing':
81
+ return {
82
+ pnCode: 'PN-MIG-CHECK-010',
83
+ where: refRelative(violation.spaceId, 'head'),
84
+ why: `Head ref \`refs/head.json\` is missing for contract space "${violation.spaceId}".`,
85
+ fix: 'Re-emit the contract-space migrations and head ref artefacts, or restore `refs/head.json` from version control.',
86
+ };
87
+ case 'headRefNotInGraph':
88
+ return {
89
+ pnCode: 'PN-MIG-CHECK-011',
90
+ where: refRelative(violation.spaceId, 'head'),
91
+ why: `Head ref ${violation.hash} for contract space "${violation.spaceId}" is not present in its migration graph.`,
92
+ fix: 'Re-emit the contract space migrations, or restore the missing migration package.',
93
+ };
94
+ case 'refUnreadable':
95
+ return {
96
+ pnCode: 'PN-MIG-CHECK-012',
97
+ where: refRelative(violation.spaceId, violation.refName),
98
+ why: `Ref "${violation.refName}" for contract space "${violation.spaceId}" is unreadable: ${violation.detail}`,
99
+ fix: 'Repair or remove the corrupt ref file.',
100
+ };
101
+ case 'targetMismatch':
102
+ return {
103
+ pnCode: 'PN-MIG-CHECK-013',
104
+ where: spaceRelative(violation.spaceId),
105
+ why: `Contract space "${violation.spaceId}" targets "${violation.actual}" but the project targets "${violation.expected}".`,
106
+ fix: 'Update the extension to target the configured database, or change the project target.',
107
+ };
108
+ case 'disjointness':
109
+ return {
110
+ pnCode: 'PN-MIG-CHECK-014',
111
+ where: migrationPathRelative(migrationsDir),
112
+ why: `Storage element "${violation.element}" is claimed by multiple contract spaces: ${violation.claimedBy.join(', ')}.`,
113
+ fix: 'Update the contracts so each storage element is owned by exactly one contract space.',
114
+ };
115
+ case 'contractUnreadable':
116
+ return {
117
+ pnCode: 'PN-MIG-CHECK-015',
118
+ where: migrationFileRelative(join(migrationsDir, violation.spaceId), 'contract.json'),
119
+ why: `Contract for space "${violation.spaceId}" is unreadable: ${violation.detail}`,
120
+ fix: 'Re-emit the extension contract artefacts, or fix the descriptor producing the invalid contract.',
121
+ };
122
+ case 'duplicateMigrationHash':
123
+ return {
124
+ pnCode: 'PN-MIG-CHECK-016',
125
+ where: spaceRelative(violation.spaceId),
126
+ why: `Multiple migrations in space "${violation.spaceId}" share migrationHash "${violation.migrationHash}" (${violation.dirNames.join(', ')}).`,
127
+ fix: 'Re-emit one of the conflicting packages so each migrationHash is unique.',
128
+ };
129
+ }
130
+ }
@@ -1,5 +1,5 @@
1
1
  import type { Contract } from '@prisma-next/contract/types';
2
- import type { ControlFamilyInstance } from '@prisma-next/framework-components/control';
2
+ import type { ContractSpaceMember } from '@prisma-next/migration-tools/aggregate';
3
3
  import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
4
4
  import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
5
5
  import {
@@ -7,20 +7,17 @@ import {
7
7
  findLatestMigration,
8
8
  isGraphNode,
9
9
  } from '@prisma-next/migration-tools/migration-graph';
10
- import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/package';
10
+ import type { ContractRef } from '@prisma-next/migration-tools/ref-resolution';
11
11
  import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
12
12
  import type { Refs } from '@prisma-next/migration-tools/refs';
13
- import { readRefSnapshot, readRefs } from '@prisma-next/migration-tools/refs';
14
13
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
15
14
  import {
16
15
  CliStructuredError,
17
- errorContractValidationFailed,
18
16
  errorPlanForgotTheFlag,
19
17
  errorSnapshotMissing,
20
- errorUnexpected,
21
- mapMigrationToolsError,
22
18
  mapRefResolutionError,
23
19
  } from './cli-errors';
20
+ import { mapContractAtError } from './contract-at-errors';
24
21
 
25
22
  const FULL_HASH_PATTERN = /^sha256:([0-9a-f]{64}|empty)$/;
26
23
 
@@ -48,15 +45,11 @@ export type FromResolution =
48
45
 
49
46
  export interface ResolveFromForPlanInput {
50
47
  readonly optionsFrom?: string | undefined;
51
- readonly refsDir: string;
52
- readonly bundles: readonly OnDiskMigrationPackage[];
53
- readonly graph: MigrationGraph;
54
- readonly familyInstance: ControlFamilyInstance<string, unknown>;
55
- readonly readBundleEndContract: (migrationDir: string) => Promise<Contract>;
48
+ readonly member: ContractSpaceMember;
56
49
  }
57
50
 
58
- function graphIsEmpty(bundles: readonly OnDiskMigrationPackage[]): boolean {
59
- return bundles.length === 0;
51
+ function graphIsEmpty(member: ContractSpaceMember): boolean {
52
+ return member.packages.length === 0;
60
53
  }
61
54
 
62
55
  function getReachableRefs(
@@ -86,158 +79,136 @@ export function assertFromIsGraphNode(
86
79
  }
87
80
  }
88
81
 
89
- async function deserializeSnapshotContract(
90
- familyInstance: ControlFamilyInstance<string, unknown>,
91
- contract: unknown,
92
- ): Promise<Result<Contract, CliStructuredError>> {
93
- try {
94
- return ok(familyInstance.deserializeContract(contract));
95
- } catch (error) {
96
- if (CliStructuredError.is(error)) {
97
- return notOk(error);
82
+ type RefContractResolution =
83
+ | {
84
+ kind: 'snapshot';
85
+ hash: string;
86
+ contract: Contract;
87
+ contractJson: unknown;
88
+ contractDts: string;
98
89
  }
99
- return notOk(
100
- errorContractValidationFailed(
101
- `Ref snapshot contract failed to deserialize: ${error instanceof Error ? error.message : String(error)}`,
102
- { where: { path: 'ref-snapshot' } },
103
- ),
104
- );
105
- }
106
- }
90
+ | {
91
+ kind: 'graph-node';
92
+ hash: string;
93
+ contract: Contract;
94
+ contractJson: unknown;
95
+ contractDts: string;
96
+ sourceDir: string;
97
+ };
98
+
99
+ async function resolveContractRef(
100
+ parsed: ContractRef,
101
+ member: ContractSpaceMember,
102
+ options?: { readonly explicitLabel?: string; readonly artifactRole?: 'from' | 'to' },
103
+ ): Promise<Result<RefContractResolution, CliStructuredError>> {
104
+ const { hash, provenance } = parsed;
105
+ const refName = provenance.kind === 'ref' ? provenance.refName : undefined;
107
106
 
108
- async function resolveGraphNodeFromBundle(
109
- fromHash: string,
110
- bundles: readonly OnDiskMigrationPackage[],
111
- readBundleEndContract: (migrationDir: string) => Promise<Contract>,
112
- explicitFromLabel?: string,
113
- ): Promise<Result<Extract<FromResolution, { kind: 'graph-node' }>, CliStructuredError>> {
114
- const matchingBundle = bundles.find((pkg) => pkg.metadata.to === fromHash);
115
- if (!matchingBundle) {
116
- return notOk(
117
- errorUnexpected(
118
- explicitFromLabel
119
- ? `No migration bundle found for --from "${explicitFromLabel}" (resolved hash: ${fromHash})`
120
- : `No migration bundle found for graph node ${fromHash}`,
121
- {
122
- why: `The hash ${fromHash} is a graph node but no on-disk migration package has an end-contract hash matching it.`,
123
- fix: 'Provide a ref or hash that corresponds to an existing migration package, or run `migration list` to see available migrations.',
124
- },
125
- ),
126
- );
127
- }
128
107
  try {
129
- const fromContract = await readBundleEndContract(matchingBundle.dirPath);
108
+ const at = await member.contractAt(hash, refName !== undefined ? { refName } : undefined);
109
+
110
+ if (at.provenance === 'snapshot') {
111
+ return ok({
112
+ kind: 'snapshot',
113
+ hash: at.hash,
114
+ contract: at.contract,
115
+ contractJson: at.contractJson,
116
+ contractDts: at.contractDts,
117
+ });
118
+ }
119
+
130
120
  return ok({
131
121
  kind: 'graph-node',
132
- fromHash,
133
- fromContract,
134
- sourceDir: matchingBundle.dirPath,
122
+ hash: at.hash,
123
+ contract: at.contract,
124
+ contractJson: at.contractJson,
125
+ contractDts: at.contractDts,
126
+ sourceDir: at.sourceDir,
135
127
  });
136
128
  } catch (error) {
137
- if (CliStructuredError.is(error)) {
138
- return notOk(error);
139
- }
140
- throw error;
129
+ return mapContractAtError(
130
+ error,
131
+ options?.artifactRole !== undefined ? { artifactRole: options.artifactRole } : undefined,
132
+ );
141
133
  }
142
134
  }
143
135
 
144
- async function resolveFromRefName(
145
- refName: string,
146
- fromHash: string,
136
+ async function resolveFromPolicy(
137
+ parsed: ContractRef,
147
138
  input: ResolveFromForPlanInput,
148
139
  refs: Refs,
140
+ explicitFromLabel?: string,
149
141
  ): Promise<Result<FromResolution, CliStructuredError>> {
150
- const { refsDir, bundles, graph, familyInstance, readBundleEndContract } = input;
151
- const empty = graphIsEmpty(bundles);
152
- const graphTip = findLatestMigration(graph)?.to ?? null;
153
-
154
- let snapshot: Awaited<ReturnType<typeof readRefSnapshot>>;
155
- try {
156
- snapshot = await readRefSnapshot(refsDir, refName);
157
- } catch (error) {
158
- if (MigrationToolsError.is(error)) {
159
- return notOk(mapMigrationToolsError(error));
160
- }
161
- throw error;
142
+ const resolution = await resolveContractRef(parsed, input.member, {
143
+ ...(explicitFromLabel !== undefined ? { explicitLabel: explicitFromLabel } : {}),
144
+ artifactRole: 'from',
145
+ });
146
+ if (!resolution.ok) {
147
+ return resolution;
162
148
  }
163
149
 
164
- if (snapshot) {
165
- const contractResult = await deserializeSnapshotContract(familyInstance, snapshot.contract);
166
- if (!contractResult.ok) {
167
- return contractResult;
168
- }
169
- const fromContract = contractResult.value;
170
- const { contractDts, contract: contractJson } = snapshot;
171
- if (empty) {
172
- return ok({ kind: 'auto-baseline', fromHash, fromContract, contractDts, contractJson });
173
- }
174
- try {
175
- assertFromIsGraphNode(fromHash, graph, refs, graphTip);
176
- } catch (error) {
177
- if (CliStructuredError.is(error)) {
178
- return notOk(error);
179
- }
180
- throw error;
181
- }
182
- return ok({ kind: 'snapshot', fromHash, fromContract, contractDts, contractJson });
150
+ if (resolution.value.kind === 'graph-node') {
151
+ return ok({
152
+ kind: 'graph-node',
153
+ fromHash: resolution.value.hash,
154
+ fromContract: resolution.value.contract,
155
+ sourceDir: resolution.value.sourceDir,
156
+ });
183
157
  }
184
158
 
185
- if (isGraphNode(fromHash, graph)) {
186
- return resolveGraphNodeFromBundle(fromHash, bundles, readBundleEndContract);
159
+ const { hash, contract, contractJson, contractDts } = resolution.value;
160
+ if (graphIsEmpty(input.member)) {
161
+ return ok({
162
+ kind: 'auto-baseline',
163
+ fromHash: hash,
164
+ fromContract: contract,
165
+ contractDts,
166
+ contractJson,
167
+ });
187
168
  }
188
169
 
189
- return notOk(errorSnapshotMissing(refName));
190
- }
191
-
192
- async function resolveFromHashProvenance(
193
- fromHash: string,
194
- input: ResolveFromForPlanInput,
195
- _refs: Refs,
196
- explicitFromLabel?: string,
197
- ): Promise<Result<FromResolution, CliStructuredError>> {
198
- const { bundles, graph } = input;
199
-
200
- if (isGraphNode(fromHash, graph)) {
201
- return resolveGraphNodeFromBundle(
202
- fromHash,
203
- bundles,
204
- input.readBundleEndContract,
205
- explicitFromLabel,
206
- );
170
+ const graph = input.member.graph();
171
+ const graphTip = findLatestMigration(graph)?.to ?? null;
172
+ try {
173
+ assertFromIsGraphNode(hash, graph, refs, graphTip);
174
+ } catch (error) {
175
+ if (CliStructuredError.is(error)) {
176
+ return notOk(error);
177
+ }
178
+ throw error;
207
179
  }
208
-
209
- throw new Error(
210
- `resolveFromHashProvenance: non-graph-node hash ${fromHash} should be refused via looksLikeFullHash before this helper is called`,
211
- );
180
+ return ok({
181
+ kind: 'snapshot',
182
+ fromHash: hash,
183
+ fromContract: contract,
184
+ contractDts,
185
+ contractJson,
186
+ });
212
187
  }
213
188
 
214
189
  export async function resolveFromForPlan(
215
190
  input: ResolveFromForPlanInput,
216
191
  ): Promise<Result<FromResolution, CliStructuredError>> {
217
- const { optionsFrom, refsDir, graph } = input;
218
-
219
- let refs: Refs;
220
- try {
221
- refs = await readRefs(refsDir);
222
- } catch (error) {
223
- if (MigrationToolsError.is(error)) {
224
- return notOk(mapMigrationToolsError(error));
225
- }
226
- throw error;
227
- }
192
+ const { optionsFrom, member } = input;
193
+ const graph = member.graph();
194
+ const refs = member.refs;
228
195
 
229
196
  if (optionsFrom === undefined) {
230
197
  const dbRef = refs['db'];
231
198
  if (!dbRef) {
232
199
  return ok({ kind: 'greenfield', fromHash: null, fromContract: null });
233
200
  }
234
- return resolveFromRefName('db', dbRef.hash, input, refs);
201
+ return resolveFromPolicy(
202
+ { hash: dbRef.hash, provenance: { kind: 'ref', refName: 'db' } },
203
+ input,
204
+ refs,
205
+ );
235
206
  }
236
207
 
237
208
  const refResult = parseContractRef(optionsFrom, { graph, refs });
238
209
  if (!refResult.ok) {
239
210
  if (looksLikeFullHash(optionsFrom)) {
240
- const empty = graphIsEmpty(input.bundles);
211
+ const empty = graphIsEmpty(member);
241
212
  const graphTip = findLatestMigration(graph)?.to ?? null;
242
213
  if (empty) {
243
214
  return notOk(errorSnapshotMissing(optionsFrom, { viaRef: false }));
@@ -247,11 +218,41 @@ export async function resolveFromForPlan(
247
218
  return notOk(mapRefResolutionError(refResult.failure));
248
219
  }
249
220
 
250
- const { hash: fromHash, provenance } = refResult.value;
221
+ return resolveFromPolicy(refResult.value, input, refs, optionsFrom);
222
+ }
223
+
224
+ export interface ResolveToForPlanInput {
225
+ readonly member: ContractSpaceMember;
226
+ }
227
+
228
+ export interface ResolvedContractRef {
229
+ readonly hash: string;
230
+ readonly contract: Contract;
231
+ readonly contractJson: unknown;
232
+ readonly contractDts: string;
233
+ }
234
+
235
+ export async function resolveToForPlan(
236
+ optionsTo: string,
237
+ input: ResolveToForPlanInput,
238
+ ): Promise<Result<ResolvedContractRef, CliStructuredError>> {
239
+ const { member } = input;
240
+ const graph = member.graph();
241
+ const refs = member.refs;
242
+
243
+ const refResult = parseContractRef(optionsTo, { graph, refs });
244
+ if (!refResult.ok) {
245
+ return notOk(mapRefResolutionError(refResult.failure));
246
+ }
251
247
 
252
- if (provenance.kind === 'ref') {
253
- return resolveFromRefName(provenance.refName, fromHash, input, refs);
248
+ const resolution = await resolveContractRef(refResult.value, member, {
249
+ explicitLabel: optionsTo,
250
+ artifactRole: 'to',
251
+ });
252
+ if (!resolution.ok) {
253
+ return resolution;
254
254
  }
255
255
 
256
- return resolveFromHashProvenance(fromHash, input, refs, optionsFrom);
256
+ const { hash, contract, contractJson, contractDts } = resolution.value;
257
+ return ok({ hash, contract, contractJson, contractDts });
257
258
  }