@prisma-next/cli 0.11.0-dev.59 → 0.11.0-dev.60

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 (128) hide show
  1. package/dist/cli.mjs +9 -9
  2. package/dist/{client-Ls2SAhrZ.mjs → client-CD3om3R0.mjs} +7 -7
  3. package/dist/{client-Ls2SAhrZ.mjs.map → client-CD3om3R0.mjs.map} +1 -1
  4. package/dist/{command-helpers-DTpEJCgI.mjs → command-helpers-CoceqqMl.mjs} +221 -42
  5. package/dist/command-helpers-CoceqqMl.mjs.map +1 -0
  6. package/dist/commands/contract-emit.mjs +1 -1
  7. package/dist/commands/contract-infer.mjs +1 -1
  8. package/dist/commands/db-init.mjs +5 -6
  9. package/dist/commands/db-init.mjs.map +1 -1
  10. package/dist/commands/db-schema.mjs +3 -3
  11. package/dist/commands/db-sign.d.mts.map +1 -1
  12. package/dist/commands/db-sign.mjs +12 -9
  13. package/dist/commands/db-sign.mjs.map +1 -1
  14. package/dist/commands/db-update.d.mts.map +1 -1
  15. package/dist/commands/db-update.mjs +12 -10
  16. package/dist/commands/db-update.mjs.map +1 -1
  17. package/dist/commands/db-verify.mjs +1 -1
  18. package/dist/commands/migrate.d.mts +1 -1
  19. package/dist/commands/migrate.mjs +8 -9
  20. package/dist/commands/migrate.mjs.map +1 -1
  21. package/dist/commands/migration-check.mjs +1 -1
  22. package/dist/commands/migration-graph.d.mts +11 -2
  23. package/dist/commands/migration-graph.d.mts.map +1 -1
  24. package/dist/commands/migration-graph.mjs +15 -29
  25. package/dist/commands/migration-graph.mjs.map +1 -1
  26. package/dist/commands/migration-list.d.mts +59 -48
  27. package/dist/commands/migration-list.d.mts.map +1 -1
  28. package/dist/commands/migration-list.mjs +2 -2
  29. package/dist/commands/migration-log.d.mts +10 -1
  30. package/dist/commands/migration-log.d.mts.map +1 -1
  31. package/dist/commands/migration-log.mjs +10 -14
  32. package/dist/commands/migration-log.mjs.map +1 -1
  33. package/dist/commands/migration-new.mjs +5 -6
  34. package/dist/commands/migration-new.mjs.map +1 -1
  35. package/dist/commands/migration-plan.d.mts +1 -1
  36. package/dist/commands/migration-plan.mjs +1 -1
  37. package/dist/commands/migration-show.d.mts +1 -1
  38. package/dist/commands/migration-show.mjs +3 -4
  39. package/dist/commands/migration-show.mjs.map +1 -1
  40. package/dist/commands/migration-status.d.mts +1 -1
  41. package/dist/commands/migration-status.d.mts.map +1 -1
  42. package/dist/commands/migration-status.mjs +7 -28
  43. package/dist/commands/migration-status.mjs.map +1 -1
  44. package/dist/commands/ref.d.mts +1 -1
  45. package/dist/commands/ref.d.mts.map +1 -1
  46. package/dist/commands/ref.mjs +10 -7
  47. package/dist/commands/ref.mjs.map +1 -1
  48. package/dist/{contract-at-errors-B98TC1wK.mjs → contract-at-errors-Bhf2jnkp.mjs} +2 -2
  49. package/dist/{contract-at-errors-B98TC1wK.mjs.map → contract-at-errors-Bhf2jnkp.mjs.map} +1 -1
  50. package/dist/{contract-emit-CS3vF-w9.mjs → contract-emit-C47r1loe.mjs} +4 -5
  51. package/dist/{contract-emit-CS3vF-w9.mjs.map → contract-emit-C47r1loe.mjs.map} +1 -1
  52. package/dist/{contract-emit-BWLCn2PH.mjs → contract-emit-DxEfEc-M.mjs} +16 -5
  53. package/dist/{contract-emit-BWLCn2PH.mjs.map → contract-emit-DxEfEc-M.mjs.map} +1 -1
  54. package/dist/{contract-enrichment-XmUPhmsS.mjs → contract-enrichment-a0V5Y_mL.mjs} +1 -1
  55. package/dist/{contract-enrichment-XmUPhmsS.mjs.map → contract-enrichment-a0V5Y_mL.mjs.map} +1 -1
  56. package/dist/{contract-infer-BtefFYF-.mjs → contract-infer-B5EhTqdR.mjs} +3 -3
  57. package/dist/{contract-infer-BtefFYF-.mjs.map → contract-infer-B5EhTqdR.mjs.map} +1 -1
  58. package/dist/{contract-space-aggregate-loader-DX_1n2SA.mjs → contract-space-aggregate-loader-lafgkTwG.mjs} +81 -4
  59. package/dist/contract-space-aggregate-loader-lafgkTwG.mjs.map +1 -0
  60. package/dist/{db-verify-aHw2nzH2.mjs → db-verify-B7fbkGnp.mjs} +5 -6
  61. package/dist/{db-verify-aHw2nzH2.mjs.map → db-verify-B7fbkGnp.mjs.map} +1 -1
  62. package/dist/exports/control-api.d.mts +1 -1
  63. package/dist/exports/control-api.mjs +3 -3
  64. package/dist/exports/index.mjs +1 -1
  65. package/dist/exports/init-output.mjs +1 -1
  66. package/dist/{extension-pack-inputs-BiY86HbQ.mjs → extension-pack-inputs-IDvjRCi3.mjs} +1 -1
  67. package/dist/{extension-pack-inputs-BiY86HbQ.mjs.map → extension-pack-inputs-IDvjRCi3.mjs.map} +1 -1
  68. package/dist/{framework-components-BwuEBcyk.mjs → framework-components-R_O3y5IW.mjs} +2 -2
  69. package/dist/{framework-components-BwuEBcyk.mjs.map → framework-components-R_O3y5IW.mjs.map} +1 -1
  70. package/dist/global-flags-2SPgqWma.d.mts +34 -0
  71. package/dist/global-flags-2SPgqWma.d.mts.map +1 -0
  72. package/dist/{graph-render-eJDcLWny.mjs → graph-render-rFAqZujX.mjs} +2 -2
  73. package/dist/{graph-render-eJDcLWny.mjs.map → graph-render-rFAqZujX.mjs.map} +1 -1
  74. package/dist/{init-DOE4Q9YK.mjs → init-BQNpPozW.mjs} +4 -5
  75. package/dist/{init-DOE4Q9YK.mjs.map → init-BQNpPozW.mjs.map} +1 -1
  76. package/dist/{inspect-live-schema-IS8jWaJy.mjs → inspect-live-schema-CQJnuPgD.mjs} +4 -5
  77. package/dist/{inspect-live-schema-IS8jWaJy.mjs.map → inspect-live-schema-CQJnuPgD.mjs.map} +1 -1
  78. package/dist/{migration-check-BFdael8w.mjs → migration-check-CKfQlAWR.mjs} +5 -5
  79. package/dist/{migration-check-BFdael8w.mjs.map → migration-check-CKfQlAWR.mjs.map} +1 -1
  80. package/dist/{migration-command-scaffold-DojkenVv.mjs → migration-command-scaffold-CE931d1-.mjs} +4 -5
  81. package/dist/{migration-command-scaffold-DojkenVv.mjs.map → migration-command-scaffold-CE931d1-.mjs.map} +1 -1
  82. package/dist/{migration-list-hj86sCtZ.mjs → migration-list-A3bJ4j5e.mjs} +181 -47
  83. package/dist/migration-list-A3bJ4j5e.mjs.map +1 -0
  84. package/dist/{migration-plan-Bt6wxUIv.mjs → migration-plan-ZZm8C0s-.mjs} +8 -9
  85. package/dist/{migration-plan-Bt6wxUIv.mjs.map → migration-plan-ZZm8C0s-.mjs.map} +1 -1
  86. package/dist/{migration-types-D2FW63pr.d.mts → migration-types-q64xAI_J.d.mts} +1 -1
  87. package/dist/{migration-types-D2FW63pr.d.mts.map → migration-types-q64xAI_J.d.mts.map} +1 -1
  88. package/dist/{migrations-CVLh0Kv4.mjs → migrations-CjO1DsYe.mjs} +2 -2
  89. package/dist/{migrations-CVLh0Kv4.mjs.map → migrations-CjO1DsYe.mjs.map} +1 -1
  90. package/dist/{output-CF_hqzI-.mjs → output-DEg3SSnJ.mjs} +1 -1
  91. package/dist/{output-CF_hqzI-.mjs.map → output-DEg3SSnJ.mjs.map} +1 -1
  92. package/dist/{progress-adapter-xASh41wr.mjs → progress-adapter-C644QK8l.mjs} +1 -1
  93. package/dist/{progress-adapter-xASh41wr.mjs.map → progress-adapter-C644QK8l.mjs.map} +1 -1
  94. package/dist/{ref-advancement-DRh5Nquq.mjs → ref-advancement-DUZqsue6.mjs} +1 -1
  95. package/dist/{ref-advancement-DRh5Nquq.mjs.map → ref-advancement-DUZqsue6.mjs.map} +1 -1
  96. package/dist/terminal-ui-sLZt2cxc.d.mts +133 -0
  97. package/dist/terminal-ui-sLZt2cxc.d.mts.map +1 -0
  98. package/dist/{types-BuatV9YW.d.mts → types-DK-ge7eR.d.mts} +1 -1
  99. package/dist/{types-BuatV9YW.d.mts.map → types-DK-ge7eR.d.mts.map} +1 -1
  100. package/dist/{verify-BiWm4XwD.mjs → verify-vl983Ed-.mjs} +2 -2
  101. package/dist/{verify-BiWm4XwD.mjs.map → verify-vl983Ed-.mjs.map} +1 -1
  102. package/package.json +18 -18
  103. package/src/commands/db-sign.ts +9 -5
  104. package/src/commands/db-update.ts +9 -8
  105. package/src/commands/migration-graph.ts +15 -41
  106. package/src/commands/migration-list.ts +126 -68
  107. package/src/commands/migration-log.ts +8 -14
  108. package/src/commands/migration-status.ts +4 -30
  109. package/src/commands/ref.ts +9 -4
  110. package/src/control-api/types.ts +3 -3
  111. package/src/utils/command-helpers.ts +0 -24
  112. package/src/utils/contract-space-aggregate-loader.ts +116 -1
  113. package/src/utils/formatters/migration-list-data-column.ts +2 -2
  114. package/src/utils/formatters/migration-list-graph-layout.ts +2 -2
  115. package/src/utils/formatters/migration-list-graph-render.ts +2 -5
  116. package/src/utils/formatters/migration-list-graph-topology.ts +158 -0
  117. package/src/utils/formatters/migration-list-render.ts +9 -12
  118. package/src/utils/formatters/migration-list-types.ts +21 -0
  119. package/dist/cli-errors-DQY629C7.mjs +0 -220
  120. package/dist/cli-errors-DQY629C7.mjs.map +0 -1
  121. package/dist/command-helpers-DTpEJCgI.mjs.map +0 -1
  122. package/dist/contract-space-aggregate-loader-DX_1n2SA.mjs.map +0 -1
  123. package/dist/global-flags-Bo6nCRUS.d.mts +0 -15
  124. package/dist/global-flags-Bo6nCRUS.d.mts.map +0 -1
  125. package/dist/glyph-mode-VIjULGFF.d.mts +0 -5
  126. package/dist/glyph-mode-VIjULGFF.d.mts.map +0 -1
  127. package/dist/migration-list-hj86sCtZ.mjs.map +0 -1
  128. package/dist/rolldown-runtime-twds-ZHy.mjs +0 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma-next/cli",
3
- "version": "0.11.0-dev.59",
3
+ "version": "0.11.0-dev.60",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -14,15 +14,15 @@
14
14
  "dependencies": {
15
15
  "@clack/prompts": "^1.4.0",
16
16
  "@dagrejs/dagre": "^3.0.0",
17
- "@prisma-next/config": "0.11.0-dev.59",
18
- "@prisma-next/contract": "0.11.0-dev.59",
19
- "@prisma-next/emitter": "0.11.0-dev.59",
20
- "@prisma-next/errors": "0.11.0-dev.59",
21
- "@prisma-next/framework-components": "0.11.0-dev.59",
22
- "@prisma-next/migration-tools": "0.11.0-dev.59",
23
- "@prisma-next/psl-printer": "0.11.0-dev.59",
24
- "@prisma-next/cli-telemetry": "0.11.0-dev.59",
25
- "@prisma-next/utils": "0.11.0-dev.59",
17
+ "@prisma-next/config": "0.11.0-dev.60",
18
+ "@prisma-next/contract": "0.11.0-dev.60",
19
+ "@prisma-next/emitter": "0.11.0-dev.60",
20
+ "@prisma-next/errors": "0.11.0-dev.60",
21
+ "@prisma-next/framework-components": "0.11.0-dev.60",
22
+ "@prisma-next/migration-tools": "0.11.0-dev.60",
23
+ "@prisma-next/psl-printer": "0.11.0-dev.60",
24
+ "@prisma-next/cli-telemetry": "0.11.0-dev.60",
25
+ "@prisma-next/utils": "0.11.0-dev.60",
26
26
  "arktype": "^2.2.0",
27
27
  "c12": "^3.3.4",
28
28
  "ci-info": "^4.3.1",
@@ -39,14 +39,14 @@
39
39
  "wrap-ansi": "^10.0.0"
40
40
  },
41
41
  "devDependencies": {
42
- "@prisma-next/sql-contract": "0.11.0-dev.59",
43
- "@prisma-next/sql-contract-emitter": "0.11.0-dev.59",
44
- "@prisma-next/sql-contract-ts": "0.11.0-dev.59",
45
- "@prisma-next/sql-operations": "0.11.0-dev.59",
46
- "@prisma-next/sql-runtime": "0.11.0-dev.59",
47
- "@prisma-next/test-utils": "0.11.0-dev.59",
48
- "@prisma-next/tsconfig": "0.11.0-dev.59",
49
- "@prisma-next/tsdown": "0.11.0-dev.59",
42
+ "@prisma-next/sql-contract": "0.11.0-dev.60",
43
+ "@prisma-next/sql-contract-emitter": "0.11.0-dev.60",
44
+ "@prisma-next/sql-contract-ts": "0.11.0-dev.60",
45
+ "@prisma-next/sql-operations": "0.11.0-dev.60",
46
+ "@prisma-next/sql-runtime": "0.11.0-dev.60",
47
+ "@prisma-next/test-utils": "0.11.0-dev.60",
48
+ "@prisma-next/tsconfig": "0.11.0-dev.60",
49
+ "@prisma-next/tsdown": "0.11.0-dev.60",
50
50
  "@types/node": "25.6.0",
51
51
  "tsdown": "0.22.0",
52
52
  "typescript": "5.9.3",
@@ -5,7 +5,6 @@ import type {
5
5
  } from '@prisma-next/framework-components/control';
6
6
  import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
7
7
  import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
8
- import { readRefs } from '@prisma-next/migration-tools/refs';
9
8
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
10
9
  import { Command } from 'commander';
11
10
  import { join, relative, resolve } from 'pathe';
@@ -25,13 +24,13 @@ import {
25
24
  } from '../utils/cli-errors';
26
25
  import {
27
26
  addGlobalOptions,
28
- loadMigrationPackages,
29
27
  maskConnectionUrl,
30
28
  resolveContractPath,
31
29
  resolveMigrationPaths,
32
30
  setCommandDescriptions,
33
31
  setCommandExamples,
34
32
  } from '../utils/command-helpers';
33
+ import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
35
34
  import { formatStyledHeader } from '../utils/formatters/styled';
36
35
  import {
37
36
  formatSchemaVerifyJson,
@@ -99,9 +98,14 @@ async function executeDbSignCommand(
99
98
 
100
99
  if (effectiveContractArg) {
101
100
  try {
102
- const { appMigrationsDir, refsDir } = resolveMigrationPaths(options.config, config);
103
- const { bundles, graph } = await loadMigrationPackages(appMigrationsDir);
104
- const refs = await readRefs(refsDir);
101
+ const { migrationsDir } = resolveMigrationPaths(options.config, config);
102
+ const loaded = await buildReadAggregate(config, { migrationsDir });
103
+ if (!loaded.ok) {
104
+ return notOk(loaded.failure);
105
+ }
106
+ const graph = loaded.value.aggregate.app.graph();
107
+ const bundles = loaded.value.aggregate.app.packages;
108
+ const refs = loaded.value.aggregate.app.refs;
105
109
  const refResult = parseContractRef(effectiveContractArg, { graph, refs });
106
110
  if (!refResult.ok) {
107
111
  return notOk(mapRefResolutionError(refResult.failure));
@@ -1,7 +1,6 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
3
3
  import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
4
- import { readRefs } from '@prisma-next/migration-tools/refs';
5
4
  import { ifDefined } from '@prisma-next/utils/defined';
6
5
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
7
6
  import { Command } from 'commander';
@@ -21,12 +20,12 @@ import {
21
20
  } from '../utils/cli-errors';
22
21
  import type { MigrationCommandOptions } from '../utils/command-helpers';
23
22
  import {
24
- loadMigrationPackages,
25
23
  resolveMigrationPaths,
26
24
  sanitizeErrorMessage,
27
25
  setCommandDescriptions,
28
26
  setCommandExamples,
29
27
  } from '../utils/command-helpers';
28
+ import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
30
29
  import {
31
30
  formatMigrationApplyOutput,
32
31
  formatMigrationJson,
@@ -109,15 +108,17 @@ async function executeDbUpdateCommand(
109
108
  const { client, config, dbConnection, onProgress, contractPathAbsolute } = ctxResult.value;
110
109
  let { contractJson } = ctxResult.value;
111
110
  let contractJsonPathForSnapshot = contractPathAbsolute;
112
- const { migrationsDir, appMigrationsDir, refsDir } = resolveMigrationPaths(
113
- options.config,
114
- config,
115
- );
111
+ const { migrationsDir, refsDir } = resolveMigrationPaths(options.config, config);
116
112
 
117
113
  if (options.to) {
118
114
  try {
119
- const { bundles, graph } = await loadMigrationPackages(appMigrationsDir);
120
- const refs = await readRefs(refsDir);
115
+ const loaded = await buildReadAggregate(config, { migrationsDir });
116
+ if (!loaded.ok) {
117
+ return notOk(loaded.failure);
118
+ }
119
+ const graph = loaded.value.aggregate.app.graph();
120
+ const bundles = loaded.value.aggregate.app.packages;
121
+ const refs = loaded.value.aggregate.app.refs;
121
122
  const refResult = parseContractRef(options.to, { graph, refs });
122
123
  if (!refResult.ok) {
123
124
  return notOk(mapRefResolutionError(refResult.failure));
@@ -1,24 +1,17 @@
1
1
  import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
2
- import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
3
2
  import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
4
- import { readRefs } from '@prisma-next/migration-tools/refs';
5
- import { notOk, ok, type Result } from '@prisma-next/utils/result';
3
+ import { ok, type Result } from '@prisma-next/utils/result';
6
4
  import { Command } from 'commander';
7
5
  import { loadConfig } from '../config-loader';
8
- import {
9
- type CliStructuredError,
10
- errorUnexpected,
11
- mapMigrationToolsError,
12
- } from '../utils/cli-errors';
6
+ import type { CliStructuredError } from '../utils/cli-errors';
13
7
  import {
14
8
  addGlobalOptions,
15
- loadMigrationPackages,
16
- readContractEnvelope,
17
9
  resolveMigrationPaths,
18
10
  setCommandDescriptions,
19
11
  setCommandExamples,
20
12
  setCommandSeeAlso,
21
13
  } from '../utils/command-helpers';
14
+ import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
22
15
  import { migrationGraphToRenderInput } from '../utils/formatters/graph-migration-mapper';
23
16
  import { graphRenderer } from '../utils/formatters/graph-render';
24
17
  import { formatStyledHeader } from '../utils/formatters/styled';
@@ -41,13 +34,13 @@ export interface MigrationGraphResult {
41
34
  readonly summary: string;
42
35
  }
43
36
 
44
- async function executeMigrationGraphCommand(
37
+ export async function executeMigrationGraphCommand(
45
38
  options: MigrationGraphOptions,
46
39
  flags: GlobalFlags,
47
40
  ui: TerminalUI,
48
41
  ): Promise<Result<MigrationGraphResult, CliStructuredError>> {
49
42
  const config = await loadConfig(options.config);
50
- const { configPath, appMigrationsDir, appMigrationsRelative, refsDir } = resolveMigrationPaths(
43
+ const { configPath, appMigrationsRelative, migrationsDir } = resolveMigrationPaths(
51
44
  options.config,
52
45
  config,
53
46
  );
@@ -65,37 +58,18 @@ async function executeMigrationGraphCommand(
65
58
  ui.stderr(header);
66
59
  }
67
60
 
68
- let graph: MigrationGraph;
69
- try {
70
- ({ graph } = await loadMigrationPackages(appMigrationsDir));
71
- } catch (error) {
72
- if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
73
- return notOk(
74
- errorUnexpected(error instanceof Error ? error.message : String(error), {
75
- why: `Failed to read migrations: ${error instanceof Error ? error.message : String(error)}`,
76
- }),
77
- );
78
- }
79
-
80
- let contractHash: string | null = null;
81
- try {
82
- const envelope = await readContractEnvelope(config);
83
- contractHash = envelope.storageHash;
84
- } catch {
85
- // Contract unreadable — render graph without contract marker
61
+ const loaded = await buildReadAggregate(config, { migrationsDir });
62
+ if (!loaded.ok) {
63
+ return loaded;
86
64
  }
87
65
 
88
- let refs: readonly StatusRef[] = [];
89
- try {
90
- const allRefs = await readRefs(refsDir);
91
- refs = Object.entries(allRefs).map(([name, entry]) => ({
92
- name,
93
- hash: entry.hash,
94
- active: false,
95
- }));
96
- } catch {
97
- // Refs unreadable — render graph without ref markers
98
- }
66
+ const { aggregate, contractHash } = loaded.value;
67
+ const graph = aggregate.app.graph();
68
+ const refs: readonly StatusRef[] = Object.entries(aggregate.app.refs).map(([name, entry]) => ({
69
+ name,
70
+ hash: entry.hash,
71
+ active: false,
72
+ }));
99
73
 
100
74
  return ok({
101
75
  ok: true,
@@ -1,10 +1,14 @@
1
- import { enumerateMigrationSpaces } from '@prisma-next/migration-tools/enumerate-migration-spaces';
2
- import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
3
1
  import type {
4
- MigrationListResult,
5
- MigrationSpaceListEntry,
6
- } from '@prisma-next/migration-tools/migration-list-types';
7
- import { APP_SPACE_ID, isValidSpaceId } from '@prisma-next/migration-tools/spaces';
2
+ ContractSpaceAggregate,
3
+ ContractSpaceMember,
4
+ } from '@prisma-next/migration-tools/aggregate';
5
+ import { HEAD_REF_NAME, refsByContractHash } from '@prisma-next/migration-tools/refs';
6
+ import {
7
+ APP_SPACE_ID,
8
+ isValidSpaceId,
9
+ listContractSpaceDirectories,
10
+ RESERVED_SPACE_SUBDIR_NAMES,
11
+ } from '@prisma-next/migration-tools/spaces';
8
12
  import { ifDefined } from '@prisma-next/utils/defined';
9
13
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
10
14
  import { Command } from 'commander';
@@ -13,8 +17,6 @@ import {
13
17
  type CliStructuredError,
14
18
  errorInvalidSpaceId,
15
19
  errorSpaceNotFound,
16
- errorUnexpected,
17
- mapMigrationToolsError,
18
20
  } from '../utils/cli-errors';
19
21
  import {
20
22
  addGlobalOptions,
@@ -23,6 +25,7 @@ import {
23
25
  setCommandExamples,
24
26
  setCommandSeeAlso,
25
27
  } from '../utils/command-helpers';
28
+ import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
26
29
  import {
27
30
  type GlyphMode,
28
31
  renderMigrationListGraphResult,
@@ -32,12 +35,101 @@ import {
32
35
  renderMigrationListWithStyle,
33
36
  } from '../utils/formatters/migration-list-render';
34
37
  import { createAnsiMigrationListStyler } from '../utils/formatters/migration-list-styler';
38
+ import type {
39
+ MigrationListEntry,
40
+ MigrationListResult,
41
+ MigrationSpaceListEntry,
42
+ } from '../utils/formatters/migration-list-types';
35
43
  import { formatStyledHeader } from '../utils/formatters/styled';
36
44
  import type { CommonCommandOptions } from '../utils/global-flags';
37
45
  import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
38
46
  import { handleResult } from '../utils/result-handler';
39
47
  import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
40
48
 
49
+ function compareSpaceIds(a: string, b: string): number {
50
+ if (a === APP_SPACE_ID) return b === APP_SPACE_ID ? 0 : -1;
51
+ if (b === APP_SPACE_ID) return 1;
52
+ if (a < b) return -1;
53
+ if (a > b) return 1;
54
+ return 0;
55
+ }
56
+
57
+ function compareDirNamesDescending(a: MigrationListEntry, b: MigrationListEntry): number {
58
+ if (a.dirName < b.dirName) return 1;
59
+ if (a.dirName > b.dirName) return -1;
60
+ return 0;
61
+ }
62
+
63
+ /**
64
+ * Ref names decorating a space's destination contract hashes. The
65
+ * tolerant `member.refs` deliberately omits the structural `head.json`;
66
+ * for extension spaces the old enumerator surfaced it as a `head`
67
+ * decoration on the tip migration, so fold `member.headRef` back in to
68
+ * keep that output. The app space synthesises its head, so it carries
69
+ * no on-disk `head` ref to restore.
70
+ */
71
+ function listRefsByContractHash(
72
+ member: ContractSpaceMember,
73
+ ): ReadonlyMap<string, readonly string[]> {
74
+ const byHash = new Map(refsByContractHash(member.refs));
75
+ if (member.spaceId !== APP_SPACE_ID && member.headRef !== null) {
76
+ const hash = member.headRef.hash;
77
+ const bucket = byHash.get(hash) ?? [];
78
+ if (!bucket.includes(HEAD_REF_NAME)) {
79
+ byHash.set(hash, [...bucket, HEAD_REF_NAME].sort());
80
+ }
81
+ }
82
+ return byHash;
83
+ }
84
+
85
+ async function orderedOnDiskSpaceIds(projectMigrationsDir: string): Promise<readonly string[]> {
86
+ const candidateDirs = await listContractSpaceDirectories(projectMigrationsDir);
87
+ return candidateDirs
88
+ .filter((name) => !RESERVED_SPACE_SUBDIR_NAMES.has(name))
89
+ .filter(isValidSpaceId)
90
+ .sort(compareSpaceIds);
91
+ }
92
+
93
+ /**
94
+ * Project the loaded {@link ContractSpaceAggregate} into the render-ready
95
+ * {@link MigrationSpaceListEntry} rows `migration list` displays.
96
+ *
97
+ * Space membership matches the on-disk contract-space directories (not the
98
+ * aggregate's always-present synthesized app member when `migrations/app/`
99
+ * is absent); package and ref data come from `aggregate.space(id)`.
100
+ */
101
+ export async function migrationSpaceListEntriesFromAggregate(
102
+ aggregate: ContractSpaceAggregate,
103
+ projectMigrationsDir: string,
104
+ ): Promise<readonly MigrationSpaceListEntry[]> {
105
+ const spaceIds = await orderedOnDiskSpaceIds(projectMigrationsDir);
106
+ const spaces: MigrationSpaceListEntry[] = [];
107
+
108
+ for (const spaceId of spaceIds) {
109
+ const member = aggregate.space(spaceId);
110
+ if (member === undefined) {
111
+ continue;
112
+ }
113
+ const refsByHash = listRefsByContractHash(member);
114
+ const migrations: MigrationListEntry[] = member.packages
115
+ .map((pkg) => ({
116
+ dirName: pkg.dirName,
117
+ from: pkg.metadata.from,
118
+ to: pkg.metadata.to,
119
+ migrationHash: pkg.metadata.migrationHash,
120
+ operationCount: pkg.ops.length,
121
+ createdAt: pkg.metadata.createdAt,
122
+ refs: refsByHash.get(pkg.metadata.to) ?? [],
123
+ providedInvariants: pkg.metadata.providedInvariants,
124
+ }))
125
+ .sort(compareDirNamesDescending);
126
+
127
+ spaces.push({ spaceId, migrations });
128
+ }
129
+
130
+ return spaces;
131
+ }
132
+
41
133
  interface MigrationListOptions extends CommonCommandOptions {
42
134
  readonly config?: string;
43
135
  readonly space?: string;
@@ -64,23 +156,15 @@ export function renderMigrationListHumanOutput(
64
156
  }
65
157
 
66
158
  /**
67
- * Inputs for {@link runMigrationList} — the pure-ish data-and-policy core
68
- * of `migration list` that tests exercise directly.
159
+ * Inputs for {@link runMigrationList} — the policy core of `migration list`
160
+ * that tests exercise directly.
69
161
  *
70
- * The core depends only on the filesystem rooted at `migrationsDir`. It
71
- * does NOT call `loadConfig`, parse CLI flags, render a styled header,
72
- * or write to any stream. Output rendering is the caller's concern (the
73
- * CLI shell renders via {@link renderMigrationList}; JSON callers
74
- * serialize the {@link MigrationListResult} directly).
162
+ * The core does not call `loadConfig`, parse CLI flags, render a styled
163
+ * header, or write to any stream. Enumeration is supplied by the caller
164
+ * (the CLI shell builds it from {@link migrationSpaceListEntriesFromAggregate}).
75
165
  */
76
166
  export interface RunMigrationListInputs {
77
- /** Absolute path to the project's `migrations/` directory. */
78
- readonly migrationsDir: string;
79
- /**
80
- * Optional contract-space id to narrow the result to a single space.
81
- * Same validation rules as {@link isValidSpaceId}. When absent, every
82
- * on-disk space contributes.
83
- */
167
+ readonly spaces: readonly MigrationSpaceListEntry[];
84
168
  readonly spaceFilter?: string;
85
169
  }
86
170
 
@@ -93,59 +177,23 @@ function computeSummary(spaces: readonly MigrationSpaceListEntry[]): string {
93
177
  }
94
178
 
95
179
  /**
96
- * The unit-testable core of `migration list`. Given an absolute
97
- * `migrationsDir` and an optional `spaceFilter`, enumerates every
98
- * on-disk migration (via {@link enumerateMigrationSpaces}), narrows to
99
- * the requested space if any, and assembles a {@link MigrationListResult}
100
- * ready for the renderer or JSON serializer.
101
- *
102
- * The enumerator is the single source of truth for "what is a contract
103
- * space": existence, the `--space` candidate-suggestion list, and
104
- * scoping are all derived from one {@link enumerateMigrationSpaces}
105
- * traversal. This means the reserved-name exclusion the enumerator
106
- * applies (e.g. the per-space `refs/` subdirectory) is honoured here for
107
- * free — a `--space refs` request resolves to `SPACE_NOT_FOUND`, not a
108
- * synthesized empty-state.
109
- *
110
- * Distinct empty-state paths:
180
+ * Policy core of `migration list`: validates `--space`, narrows the
181
+ * pre-enumerated spaces, and assembles a {@link MigrationListResult}.
111
182
  *
112
183
  * - `migrations/` missing or contains no valid space directories →
113
- * synthesizes `[{ spaceId: APP_SPACE_ID, migrations: [] }]` so the
114
- * renderer's empty-state path can name a directory (spec § Empty-state +
115
- * the `migrations/` missing edge case).
116
- * - `--space <id>` on an existing-but-empty space dir → the enumerator
117
- * surfaces `{ spaceId, migrations: [] }`; `<id>` is in the set, so it
118
- * scopes to that entry and renders the empty-state the same way.
119
- * - `--space <id>` on a non-existent (or reserved) space → structured
120
- * `MIGRATION.SPACE_NOT_FOUND` error (NOT empty-state).
121
- *
122
- * Errors caught here:
123
- *
124
- * - {@link MigrationToolsError} from the enumerator → mapped through
125
- * {@link mapMigrationToolsError} so callers see the catalogue code.
126
- * - Anything else (filesystem etc.) → wrapped via {@link errorUnexpected}.
184
+ * caller passes `spaces: []`; this synthesizes `[{ spaceId: APP_SPACE_ID, migrations: [] }]`.
185
+ * - `--space <id>` on an existing-but-empty space `{ spaceId, migrations: [] }` in the input.
186
+ * - `--space <id>` on a non-existent (or reserved) space → `SPACE_NOT_FOUND`.
127
187
  */
128
- export async function runMigrationList(
188
+ export function runMigrationList(
129
189
  inputs: RunMigrationListInputs,
130
- ): Promise<Result<MigrationListResult, CliStructuredError>> {
131
- const { migrationsDir, spaceFilter } = inputs;
190
+ ): Result<MigrationListResult, CliStructuredError> {
191
+ const { spaces, spaceFilter } = inputs;
132
192
 
133
193
  if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {
134
194
  return notOk(errorInvalidSpaceId(spaceFilter));
135
195
  }
136
196
 
137
- let spaces: readonly MigrationSpaceListEntry[];
138
- try {
139
- spaces = await enumerateMigrationSpaces({ projectMigrationsDir: migrationsDir });
140
- } catch (error) {
141
- if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
142
- return notOk(
143
- errorUnexpected(error instanceof Error ? error.message : String(error), {
144
- why: `Failed to enumerate migrations: ${error instanceof Error ? error.message : String(error)}`,
145
- }),
146
- );
147
- }
148
-
149
197
  if (spaceFilter !== undefined && !spaces.some((s) => s.spaceId === spaceFilter)) {
150
198
  return notOk(errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()));
151
199
  }
@@ -168,7 +216,7 @@ export async function runMigrationList(
168
216
  * stderr (interactive mode only), and delegates to {@link runMigrationList}.
169
217
  * Kept intentionally thin so the unit-testable surface lives in the core.
170
218
  */
171
- async function executeMigrationListCommand(
219
+ export async function executeMigrationListCommand(
172
220
  options: MigrationListOptions,
173
221
  flags: GlobalFlags,
174
222
  ui: TerminalUI,
@@ -193,8 +241,18 @@ async function executeMigrationListCommand(
193
241
  ui.stderr(header);
194
242
  }
195
243
 
196
- return runMigrationList({
244
+ const loaded = await buildReadAggregate(config, { migrationsDir });
245
+ if (!loaded.ok) {
246
+ return notOk(loaded.failure);
247
+ }
248
+
249
+ const spaces = await migrationSpaceListEntriesFromAggregate(
250
+ loaded.value.aggregate,
197
251
  migrationsDir,
252
+ );
253
+
254
+ return runMigrationList({
255
+ spaces,
198
256
  ...ifDefined('spaceFilter', options.space),
199
257
  });
200
258
  }
@@ -16,7 +16,6 @@ import {
16
16
  } from '../utils/cli-errors';
17
17
  import {
18
18
  addGlobalOptions,
19
- loadMigrationPackages,
20
19
  maskConnectionUrl,
21
20
  resolveMigrationPaths,
22
21
  setCommandDescriptions,
@@ -24,6 +23,7 @@ import {
24
23
  setCommandSeeAlso,
25
24
  targetSupportsMigrations,
26
25
  } from '../utils/command-helpers';
26
+ import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
27
27
  import { formatStyledHeader } from '../utils/formatters/styled';
28
28
  import type { CommonCommandOptions } from '../utils/global-flags';
29
29
  import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
@@ -51,13 +51,13 @@ export interface MigrationLogResult {
51
51
  readonly summary: string;
52
52
  }
53
53
 
54
- async function executeMigrationLogCommand(
54
+ export async function executeMigrationLogCommand(
55
55
  options: MigrationLogOptions,
56
56
  flags: GlobalFlags,
57
57
  ui: TerminalUI,
58
58
  ): Promise<Result<MigrationLogResult, CliStructuredError>> {
59
59
  const config = await loadConfig(options.config);
60
- const { configPath, appMigrationsDir, appMigrationsRelative } = resolveMigrationPaths(
60
+ const { configPath, appMigrationsRelative, migrationsDir } = resolveMigrationPaths(
61
61
  options.config,
62
62
  config,
63
63
  );
@@ -94,18 +94,12 @@ async function executeMigrationLogCommand(
94
94
  ui.stderr(header);
95
95
  }
96
96
 
97
- let bundles: Awaited<ReturnType<typeof loadMigrationPackages>>['bundles'];
98
- let graph: Awaited<ReturnType<typeof loadMigrationPackages>>['graph'];
99
- try {
100
- ({ bundles, graph } = await loadMigrationPackages(appMigrationsDir));
101
- } catch (error) {
102
- if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
103
- return notOk(
104
- errorUnexpected(error instanceof Error ? error.message : String(error), {
105
- why: `Failed to read migrations: ${error instanceof Error ? error.message : String(error)}`,
106
- }),
107
- );
97
+ const loaded = await buildReadAggregate(config, { migrationsDir });
98
+ if (!loaded.ok) {
99
+ return loaded;
108
100
  }
101
+ const graph = loaded.value.aggregate.app.graph();
102
+ const bundles = loaded.value.aggregate.app.packages;
109
103
 
110
104
  const client = createControlClient({
111
105
  family: config.family,
@@ -26,7 +26,6 @@ import type { OnDiskMigrationPackage } from '@prisma-next/migration-tools/packag
26
26
  import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
27
27
  import type { RefEntry, Refs } from '@prisma-next/migration-tools/refs';
28
28
  import { readRefs } from '@prisma-next/migration-tools/refs';
29
- import { blindCast } from '@prisma-next/utils/casts';
30
29
  import { ifDefined } from '@prisma-next/utils/defined';
31
30
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
32
31
  import { cyan, dim, magenta, yellow } from 'colorette';
@@ -54,6 +53,8 @@ import {
54
53
  toStructuralEdge,
55
54
  } from '../utils/command-helpers';
56
55
  import {
56
+ appContractStandInFromIdentity,
57
+ loadContractRawSafely,
57
58
  refuseContractSpaceIntegrity,
58
59
  refusePackageCorruptionOnAggregate,
59
60
  } from '../utils/contract-space-aggregate-loader';
@@ -531,33 +532,6 @@ export async function loadAggregateStatusSpaces(args: {
531
532
  * problem via a status diagnostic, no need to double-surface.
532
533
  */
533
534
 
534
- function appContractShellForAggregateLoad(args: {
535
- readonly contractHash: string;
536
- readonly targetId: string;
537
- readonly targetFamily: string;
538
- }): Contract {
539
- return blindCast<Contract, 'status aggregate load without contract.json'>({
540
- storage: { storageHash: args.contractHash },
541
- schemaVersion: '0.0.0',
542
- target: args.targetId,
543
- targetFamily: args.targetFamily,
544
- models: {},
545
- profileHash: EMPTY_CONTRACT_HASH,
546
- });
547
- }
548
-
549
- async function loadContractRawSafely(config: {
550
- contract?: { output?: string };
551
- }): Promise<unknown | null> {
552
- try {
553
- const path = (await import('../utils/command-helpers')).resolveContractPath(config);
554
- const raw = await (await import('node:fs/promises')).readFile(path, 'utf-8');
555
- return JSON.parse(raw) as unknown;
556
- } catch {
557
- return null;
558
- }
559
- }
560
-
561
535
  async function validateOnlineMarkerRead(
562
536
  config: Awaited<ReturnType<typeof loadConfig>>,
563
537
  dbConnection: unknown,
@@ -637,12 +611,12 @@ async function executeMigrationStatusCommand(
637
611
  const stack = createControlStack(config);
638
612
  const familyInstance = config.family.create(stack);
639
613
  const deserializeContract = (json: unknown): Contract => familyInstance.deserializeContract(json);
640
- const appContractShell = appContractShellForAggregateLoad({
614
+ const appContractStandIn = appContractStandInFromIdentity({
641
615
  contractHash,
642
616
  targetId: config.target.id,
643
617
  targetFamily: config.target.familyId,
644
618
  });
645
- let appContractForLoad: Contract = appContractShell;
619
+ let appContractForLoad: Contract = appContractStandIn;
646
620
  if (contractRawForAggregate !== null) {
647
621
  try {
648
622
  appContractForLoad = deserializeContract(contractRawForAggregate);
@@ -28,10 +28,10 @@ import {
28
28
  } from '../utils/cli-errors';
29
29
  import {
30
30
  addGlobalOptions,
31
- loadMigrationPackages,
32
31
  resolveMigrationPaths,
33
32
  setCommandDescriptions,
34
33
  } from '../utils/command-helpers';
34
+ import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
35
35
  import { formatCommandHelp } from '../utils/formatters/help';
36
36
  import { parseGlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
37
37
  import { readContractIR } from '../utils/ref-advancement';
@@ -81,9 +81,14 @@ export async function executeRefSetCommand(
81
81
 
82
82
  try {
83
83
  const config = await loadConfig(options.config);
84
- const { appMigrationsDir, refsDir } = resolveMigrationPaths(options.config, config);
85
- const { graph, bundles } = await loadMigrationPackages(appMigrationsDir);
86
- const refs = await readRefs(refsDir);
84
+ const { migrationsDir, refsDir } = resolveMigrationPaths(options.config, config);
85
+ const loaded = await buildReadAggregate(config, { migrationsDir });
86
+ if (!loaded.ok) {
87
+ return notOk(loaded.failure);
88
+ }
89
+ const graph = loaded.value.aggregate.app.graph();
90
+ const bundles = loaded.value.aggregate.app.packages;
91
+ const refs = loaded.value.aggregate.app.refs;
87
92
 
88
93
  let resolvedHash: string;
89
94
  if (validateRefValue(contractInput)) {
@@ -577,9 +577,9 @@ export interface MigrationApplyOptions {
577
577
 
578
578
  /**
579
579
  * A single on-disk migration package surfaced to the operation. The
580
- * SQL family already produces this shape via `loadMigrationPackages`;
581
- * the operation hands it through to the framework-neutral aggregate
582
- * loader's `appMigrationPackages` slot.
580
+ * SQL family surfaces this shape from the tolerant contract-space
581
+ * aggregate's app packages; the operation hands it through to the
582
+ * framework-neutral aggregate loader's `appMigrationPackages` slot.
583
583
  *
584
584
  * (Originally named `MigrationApplyStep` for the legacy single-space
585
585
  * apply path; the name is kept for compatibility with existing CLI