@prisma-next/cli 0.12.0-dev.28 → 0.12.0-dev.29

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 (102) hide show
  1. package/dist/cli.mjs +12 -12
  2. package/dist/{client-nygCs15r.mjs → client-xeWpMlq1.mjs} +4 -4
  3. package/dist/client-xeWpMlq1.mjs.map +1 -0
  4. package/dist/{command-helpers-D7TK5Y9e.mjs → command-helpers-DK_5ItoJ.mjs} +16 -2
  5. package/dist/command-helpers-DK_5ItoJ.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 +3 -3
  9. package/dist/commands/db-schema.mjs +3 -3
  10. package/dist/commands/db-sign.mjs +4 -4
  11. package/dist/commands/db-update.mjs +4 -4
  12. package/dist/commands/db-verify.mjs +1 -1
  13. package/dist/commands/migrate.d.mts +1 -1
  14. package/dist/commands/migrate.mjs +4 -4
  15. package/dist/commands/migration-check.mjs +1 -1
  16. package/dist/commands/migration-graph.d.mts +12 -15
  17. package/dist/commands/migration-graph.d.mts.map +1 -1
  18. package/dist/commands/migration-graph.mjs +84 -51
  19. package/dist/commands/migration-graph.mjs.map +1 -1
  20. package/dist/commands/migration-list.d.mts +13 -4
  21. package/dist/commands/migration-list.d.mts.map +1 -1
  22. package/dist/commands/migration-list.mjs +1 -187
  23. package/dist/commands/migration-log.d.mts +2 -2
  24. package/dist/commands/migration-log.mjs +1 -1
  25. package/dist/commands/migration-new.mjs +3 -3
  26. package/dist/commands/migration-plan.d.mts +1 -1
  27. package/dist/commands/migration-plan.mjs +1 -1
  28. package/dist/commands/migration-show.d.mts +1 -1
  29. package/dist/commands/migration-show.mjs +2 -2
  30. package/dist/commands/migration-status.d.mts +20 -2
  31. package/dist/commands/migration-status.d.mts.map +1 -1
  32. package/dist/commands/migration-status.mjs +2 -3
  33. package/dist/commands/ref.d.mts +1 -1
  34. package/dist/commands/ref.mjs +2 -2
  35. package/dist/commands/telemetry/index.mjs +1 -1
  36. package/dist/{contract-at-errors-CK3qoqZf.mjs → contract-at-errors-DG3kjgoz.mjs} +2 -2
  37. package/dist/{contract-at-errors-CK3qoqZf.mjs.map → contract-at-errors-DG3kjgoz.mjs.map} +1 -1
  38. package/dist/{contract-emit-Dzf73HdD.mjs → contract-emit-BO0l6fnT.mjs} +3 -3
  39. package/dist/{contract-emit-Dzf73HdD.mjs.map → contract-emit-BO0l6fnT.mjs.map} +1 -1
  40. package/dist/{contract-emit-DwlIz5Zg.mjs → contract-emit-C0Bs0VRj.mjs} +3 -3
  41. package/dist/{contract-emit-DwlIz5Zg.mjs.map → contract-emit-C0Bs0VRj.mjs.map} +1 -1
  42. package/dist/{contract-infer-Bzh___GO.mjs → contract-infer-2wtPflGH.mjs} +3 -3
  43. package/dist/{contract-infer-Bzh___GO.mjs.map → contract-infer-2wtPflGH.mjs.map} +1 -1
  44. package/dist/{contract-space-aggregate-loader-5zmOENc4.mjs → contract-space-aggregate-loader-Dbr3-jHF.mjs} +2 -2
  45. package/dist/{contract-space-aggregate-loader-5zmOENc4.mjs.map → contract-space-aggregate-loader-Dbr3-jHF.mjs.map} +1 -1
  46. package/dist/{db-verify-CNz036sw.mjs → db-verify-CxHiSiTG.mjs} +4 -4
  47. package/dist/{db-verify-CNz036sw.mjs.map → db-verify-CxHiSiTG.mjs.map} +1 -1
  48. package/dist/exports/control-api.d.mts +1 -1
  49. package/dist/exports/control-api.mjs +2 -2
  50. package/dist/exports/index.mjs +1 -1
  51. package/dist/exports/init-output.mjs +1 -1
  52. package/dist/{framework-components-CyM_xYCY.mjs → framework-components-CxOVKAAh.mjs} +2 -2
  53. package/dist/{framework-components-CyM_xYCY.mjs.map → framework-components-CxOVKAAh.mjs.map} +1 -1
  54. package/dist/{global-flags-DEHjV8_s.d.mts → global-flags-DG4uY5tV.d.mts} +1 -1
  55. package/dist/{global-flags-DEHjV8_s.d.mts.map → global-flags-DG4uY5tV.d.mts.map} +1 -1
  56. package/dist/{init-DJsQpr_6.mjs → init-R272pxux.mjs} +4 -4
  57. package/dist/{init-DJsQpr_6.mjs.map → init-R272pxux.mjs.map} +1 -1
  58. package/dist/{inspect-live-schema-DE76Ou4D.mjs → inspect-live-schema-RekOwfi5.mjs} +3 -3
  59. package/dist/{inspect-live-schema-DE76Ou4D.mjs.map → inspect-live-schema-RekOwfi5.mjs.map} +1 -1
  60. package/dist/{migration-check-CL2MzDRX.mjs → migration-check-Dc0cOhKH.mjs} +2 -2
  61. package/dist/{migration-check-CL2MzDRX.mjs.map → migration-check-Dc0cOhKH.mjs.map} +1 -1
  62. package/dist/{migration-command-scaffold-194pA8F5.mjs → migration-command-scaffold-ApB3NxWY.mjs} +3 -3
  63. package/dist/{migration-command-scaffold-194pA8F5.mjs.map → migration-command-scaffold-ApB3NxWY.mjs.map} +1 -1
  64. package/dist/{migration-graph-tree-render-CVmV9sWr.mjs → migration-graph-space-render-dmLLWift.mjs} +389 -210
  65. package/dist/migration-graph-space-render-dmLLWift.mjs.map +1 -0
  66. package/dist/migration-list-C5sXrl0U.mjs +228 -0
  67. package/dist/migration-list-C5sXrl0U.mjs.map +1 -0
  68. package/dist/{migration-log-CP6skD5b.mjs → migration-log-DD_vCbYW.mjs} +4 -4
  69. package/dist/{migration-log-CP6skD5b.mjs.map → migration-log-DD_vCbYW.mjs.map} +1 -1
  70. package/dist/{migration-plan-D61N1hID.mjs → migration-plan-CeTjQOIG.mjs} +5 -5
  71. package/dist/{migration-plan-D61N1hID.mjs.map → migration-plan-CeTjQOIG.mjs.map} +1 -1
  72. package/dist/{migration-status--ejfYqWS.mjs → migration-status-qV8ctwPy.mjs} +61 -45
  73. package/dist/migration-status-qV8ctwPy.mjs.map +1 -0
  74. package/dist/{output-B60Gw5fu.mjs → output-CF_hqzI-.mjs} +1 -1
  75. package/dist/{output-B60Gw5fu.mjs.map → output-CF_hqzI-.mjs.map} +1 -1
  76. package/dist/{telemetry-CnfdMrpv.mjs → telemetry-S-NGi9U6.mjs} +2 -2
  77. package/dist/{telemetry-CnfdMrpv.mjs.map → telemetry-S-NGi9U6.mjs.map} +1 -1
  78. package/dist/{types-BYwWOyYJ.d.mts → types-Mh7mdPHM.d.mts} +1 -1
  79. package/dist/{types-BYwWOyYJ.d.mts.map → types-Mh7mdPHM.d.mts.map} +1 -1
  80. package/dist/{verify-By66Zu3y.mjs → verify-BdI-BgYi.mjs} +2 -2
  81. package/dist/{verify-By66Zu3y.mjs.map → verify-BdI-BgYi.mjs.map} +1 -1
  82. package/package.json +18 -18
  83. package/src/commands/migration-graph.ts +125 -58
  84. package/src/commands/migration-list.ts +43 -9
  85. package/src/commands/migration-status.ts +106 -74
  86. package/src/control-api/operations/db-apply.ts +7 -4
  87. package/src/utils/cli-errors.ts +17 -0
  88. package/src/utils/formatters/migration-graph-lane-colors.ts +164 -1
  89. package/src/utils/formatters/migration-graph-rows.ts +128 -15
  90. package/src/utils/formatters/migration-graph-space-render.ts +138 -0
  91. package/src/utils/formatters/migration-graph-tree-render.ts +149 -239
  92. package/src/utils/formatters/migration-list-data-column.ts +6 -0
  93. package/src/utils/formatters/migration-list-render.ts +43 -23
  94. package/src/utils/formatters/migration-list-styler.ts +48 -5
  95. package/src/utils/legend.ts +38 -0
  96. package/dist/client-nygCs15r.mjs.map +0 -1
  97. package/dist/command-helpers-D7TK5Y9e.mjs.map +0 -1
  98. package/dist/commands/migration-list.mjs.map +0 -1
  99. package/dist/migration-graph-tree-render-CVmV9sWr.mjs.map +0 -1
  100. package/dist/migration-status--ejfYqWS.mjs.map +0 -1
  101. package/dist/migration-types-D2FW63pr.d.mts +0 -15
  102. package/dist/migration-types-D2FW63pr.d.mts.map +0 -1
@@ -1,4 +1,5 @@
1
1
  import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
2
+ import { ifDefined } from '@prisma-next/utils/defined';
2
3
  import { ok, type Result } from '@prisma-next/utils/result';
3
4
  import { Command } from 'commander';
4
5
  import { loadConfig } from '../config-loader';
@@ -11,55 +12,75 @@ import {
11
12
  setCommandSeeAlso,
12
13
  } from '../utils/command-helpers';
13
14
  import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
14
- import { buildMigrationGraphLayout } from '../utils/formatters/migration-graph-layout';
15
- import { buildMigrationGraphRows } from '../utils/formatters/migration-graph-rows';
16
15
  import {
17
- renderMigrationGraphLegend,
18
- renderMigrationGraphTree,
19
- } from '../utils/formatters/migration-graph-tree-render';
16
+ computeGlobalMaxDirNameWidth,
17
+ computeGlobalMaxEdgeTreePrefixWidth,
18
+ indentMigrationGraphTreeBlock,
19
+ renderMigrationGraphSpaceTree,
20
+ } from '../utils/formatters/migration-graph-space-render';
21
+ import { renderMigrationGraphLegend } from '../utils/formatters/migration-graph-tree-render';
20
22
  import { formatStyledHeader } from '../utils/formatters/styled';
21
23
  import type { CommonCommandOptions } from '../utils/global-flags';
22
24
  import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
23
- import type { StatusRef } from '../utils/migration-types';
25
+ import { shouldShowLegend, validateLegendOptions } from '../utils/legend';
24
26
  import { handleResult } from '../utils/result-handler';
25
27
  import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
28
+ import {
29
+ listRefsByContractHash,
30
+ migrationSpaceListEntriesFromAggregate,
31
+ runMigrationList,
32
+ } from './migration-list';
26
33
 
27
34
  interface MigrationGraphOptions extends CommonCommandOptions {
28
35
  readonly config?: string;
29
36
  readonly dot?: boolean;
37
+ readonly space?: string;
30
38
  readonly ascii?: boolean;
31
39
  readonly legend?: boolean;
32
40
  }
33
41
 
34
- /**
35
- * The legend is decoration printed alongside the command header on stderr, so
36
- * it is suppressed for the machine-readable / silent paths (`--json`, `--dot`,
37
- * `--quiet`) exactly as the header is.
38
- */
39
- export function migrationGraphShowsLegend(
40
- options: { readonly legend?: boolean; readonly dot?: boolean },
41
- flags: GlobalFlags,
42
- ): boolean {
43
- return (
44
- options.legend === true && options.dot !== true && flags.json !== true && flags.quiet !== true
45
- );
42
+ export interface MigrationGraphTreeSection {
43
+ readonly spaceId: string;
44
+ readonly tree: string;
45
+ readonly showHeading: boolean;
46
46
  }
47
47
 
48
48
  export interface MigrationGraphResult {
49
49
  readonly ok: true;
50
+ /** App-space graph for `--json` / `--dot` (unchanged machine output). */
50
51
  readonly graph: MigrationGraph;
51
- readonly contractHash: string | null;
52
- readonly refs: readonly StatusRef[];
52
+ readonly treeSections: readonly MigrationGraphTreeSection[];
53
53
  readonly summary: string;
54
54
  }
55
55
 
56
+ function computeGraphSummary(graph: MigrationGraph): string {
57
+ return `${graph.nodes.size} node(s), ${graph.migrationByHash.size} edge(s)`;
58
+ }
59
+
60
+ export function formatMigrationGraphHumanOutput(result: MigrationGraphResult): string {
61
+ const sections: string[] = [];
62
+ for (const section of result.treeSections) {
63
+ if (section.showHeading) {
64
+ sections.push(`${section.spaceId}:`);
65
+ }
66
+ if (section.tree.length > 0) {
67
+ sections.push(section.tree);
68
+ } else {
69
+ sections.push('(no migrations)');
70
+ }
71
+ sections.push('');
72
+ }
73
+ sections.push(result.summary);
74
+ return sections.join('\n').trimEnd();
75
+ }
76
+
56
77
  export async function executeMigrationGraphCommand(
57
78
  options: MigrationGraphOptions,
58
79
  flags: GlobalFlags,
59
80
  ui: TerminalUI,
60
81
  ): Promise<Result<MigrationGraphResult, CliStructuredError>> {
61
82
  const config = await loadConfig(options.config);
62
- const { configPath, appMigrationsRelative, migrationsDir } = resolveMigrationPaths(
83
+ const { configPath, migrationsRelative, migrationsDir } = resolveMigrationPaths(
63
84
  options.config,
64
85
  config,
65
86
  );
@@ -70,12 +91,13 @@ export async function executeMigrationGraphCommand(
70
91
  description: 'Show the migration graph topology',
71
92
  details: [
72
93
  { label: 'config', value: configPath },
73
- { label: 'migrations', value: appMigrationsRelative },
94
+ { label: 'migrations', value: migrationsRelative },
95
+ ...(options.space !== undefined ? [{ label: 'space', value: options.space }] : []),
74
96
  ],
75
97
  flags,
76
98
  });
77
99
  ui.stderr(header);
78
- if (migrationGraphShowsLegend(options, flags)) {
100
+ if (shouldShowLegend(options, flags)) {
79
101
  ui.stderr(
80
102
  renderMigrationGraphLegend({
81
103
  colorize: flags.color !== false,
@@ -91,20 +113,72 @@ export async function executeMigrationGraphCommand(
91
113
  return loaded;
92
114
  }
93
115
 
94
- const { aggregate, contractHash } = loaded.value;
95
- const graph = aggregate.app.graph();
96
- const refs: readonly StatusRef[] = Object.entries(aggregate.app.refs).map(([name, entry]) => ({
97
- name,
98
- hash: entry.hash,
99
- active: false,
100
- }));
116
+ const { aggregate, contractHash: liveContractHash } = loaded.value;
117
+ const appGraph = aggregate.app.graph();
118
+
119
+ const listSpaces = await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir);
120
+ const listResult = runMigrationList({
121
+ spaces: listSpaces,
122
+ ...ifDefined('spaceFilter', options.space),
123
+ });
124
+ if (!listResult.ok) {
125
+ return listResult;
126
+ }
127
+
128
+ const scopedSpaces = listResult.value.spaces;
129
+ const showSpaceHeadings = scopedSpaces.length > 1;
130
+ const glyphMode = ui.resolveGlyphMode(options.ascii === true);
131
+ const colorize = flags.color !== false;
132
+
133
+ const globalLayoutInputs = showSpaceHeadings
134
+ ? scopedSpaces
135
+ .filter((spaceEntry) => spaceEntry.migrations.length > 0)
136
+ .map((spaceEntry) => ({
137
+ graph: aggregate.space(spaceEntry.spaceId)!.graph(),
138
+ liveContractHash,
139
+ }))
140
+ : [];
141
+ const globalMaxEdgeTreePrefixWidth =
142
+ globalLayoutInputs.length > 0
143
+ ? computeGlobalMaxEdgeTreePrefixWidth(globalLayoutInputs)
144
+ : undefined;
145
+ const globalMaxDirNameWidth =
146
+ globalLayoutInputs.length > 0 ? computeGlobalMaxDirNameWidth(globalLayoutInputs) : undefined;
147
+
148
+ const treeSections: MigrationGraphTreeSection[] = [];
149
+ for (const spaceEntry of scopedSpaces) {
150
+ const member = aggregate.space(spaceEntry.spaceId);
151
+ if (member === undefined) {
152
+ continue;
153
+ }
154
+ const graph = member.graph();
155
+ const tree =
156
+ spaceEntry.migrations.length === 0
157
+ ? ''
158
+ : renderMigrationGraphSpaceTree({
159
+ graph,
160
+ migrations: spaceEntry.migrations,
161
+ liveContractHash,
162
+ glyphMode,
163
+ colorize,
164
+ refsByHash: listRefsByContractHash(member),
165
+ ...(globalMaxEdgeTreePrefixWidth !== undefined ? { globalMaxEdgeTreePrefixWidth } : {}),
166
+ ...(globalMaxDirNameWidth !== undefined ? { globalMaxDirNameWidth } : {}),
167
+ });
168
+ const displayTree =
169
+ showSpaceHeadings && tree.length > 0 ? indentMigrationGraphTreeBlock(tree, ' ') : tree;
170
+ treeSections.push({
171
+ spaceId: spaceEntry.spaceId,
172
+ tree: displayTree,
173
+ showHeading: showSpaceHeadings,
174
+ });
175
+ }
101
176
 
102
177
  return ok({
103
178
  ok: true,
104
- graph,
105
- contractHash,
106
- refs,
107
- summary: `${graph.nodes.size} node(s), ${graph.migrationByHash.size} edge(s)`,
179
+ graph: appGraph,
180
+ treeSections,
181
+ summary: computeGraphSummary(appGraph),
108
182
  });
109
183
  }
110
184
 
@@ -124,6 +198,7 @@ export function createMigrationGraphCommand(): Command {
124
198
  'prisma-next migration graph --dot',
125
199
  'prisma-next migration graph --ascii',
126
200
  'prisma-next migration graph --legend',
201
+ 'prisma-next migration graph --space app',
127
202
  ]);
128
203
  setCommandSeeAlso(command, [
129
204
  { verb: 'migration status', oneLiner: 'Show migration path and pending status' },
@@ -133,12 +208,17 @@ export function createMigrationGraphCommand(): Command {
133
208
  ]);
134
209
  addGlobalOptions(command)
135
210
  .option('--config <path>', 'Path to prisma-next.config.ts')
211
+ .option('--space <id>', 'Narrow output to a single contract space')
136
212
  .option('--dot', 'Output in Graphviz DOT format')
137
213
  .option('--ascii', 'Use ASCII glyphs (pipe-friendly)')
138
214
  .option('--legend', 'Print a key for the tree glyphs and lane colors')
139
215
  .action(async (options: MigrationGraphOptions) => {
140
216
  const flags = parseGlobalFlagsOrExit(options);
141
217
  const ui = createTerminalUI(flags);
218
+ const legendValidation = validateLegendOptions(options, flags);
219
+ if (!legendValidation.ok) {
220
+ process.exit(handleResult(legendValidation, flags, ui));
221
+ }
142
222
  const result = await executeMigrationGraphCommand(options, flags, ui);
143
223
  const exitCode = handleResult(result, flags, ui, (graphResult) => {
144
224
  if (options.dot) {
@@ -159,32 +239,19 @@ export function createMigrationGraphCommand(): Command {
159
239
  migrationHash: e.migrationHash,
160
240
  }));
161
241
  ui.output(
162
- JSON.stringify({ ok: true, nodes, edges, summary: graphResult.summary }, null, 2),
242
+ JSON.stringify(
243
+ {
244
+ ok: true,
245
+ nodes,
246
+ edges,
247
+ summary: `${graphResult.graph.nodes.size} node(s), ${graphResult.graph.migrationByHash.size} edge(s)`,
248
+ },
249
+ null,
250
+ 2,
251
+ ),
163
252
  );
164
253
  } else if (!flags.quiet) {
165
- const refsByHash = new Map<string, string[]>();
166
- for (const ref of graphResult.refs) {
167
- const existing = refsByHash.get(ref.hash);
168
- refsByHash.set(ref.hash, existing ? [...existing, ref.name] : [ref.name]);
169
- }
170
- const rowModel = buildMigrationGraphRows(graphResult.graph, {
171
- ...(graphResult.contractHash !== null
172
- ? { contractHash: graphResult.contractHash }
173
- : {}),
174
- });
175
- const layout = buildMigrationGraphLayout(rowModel);
176
- const activeRef = graphResult.refs.find((ref) => ref.active);
177
- const treeOutput = renderMigrationGraphTree(layout, {
178
- refsByHash,
179
- ...(graphResult.contractHash !== null
180
- ? { contractHash: graphResult.contractHash }
181
- : {}),
182
- ...(activeRef !== undefined ? { activeRefName: activeRef.name } : {}),
183
- colorize: flags.color !== false,
184
- glyphMode: ui.resolveGlyphMode(options.ascii === true),
185
- });
186
- ui.output(treeOutput);
187
- ui.output(`\n${graphResult.summary}`);
254
+ ui.output(formatMigrationGraphHumanOutput(graphResult));
188
255
  }
189
256
  });
190
257
  process.exit(exitCode);
@@ -2,6 +2,7 @@ import type {
2
2
  ContractSpaceAggregate,
3
3
  ContractSpaceMember,
4
4
  } from '@prisma-next/migration-tools/aggregate';
5
+ import type { MigrationGraph } from '@prisma-next/migration-tools/graph';
5
6
  import { HEAD_REF_NAME, refsByContractHash } from '@prisma-next/migration-tools/refs';
6
7
  import {
7
8
  APP_SPACE_ID,
@@ -26,6 +27,7 @@ import {
26
27
  setCommandSeeAlso,
27
28
  } from '../utils/command-helpers';
28
29
  import { buildReadAggregate } from '../utils/contract-space-aggregate-loader';
30
+ import { renderMigrationGraphLegend } from '../utils/formatters/migration-graph-tree-render';
29
31
  import { renderMigrationListWithStyle } from '../utils/formatters/migration-list-render';
30
32
  import { createAnsiMigrationListStyler } from '../utils/formatters/migration-list-styler';
31
33
  import type {
@@ -37,6 +39,7 @@ import { formatStyledHeader } from '../utils/formatters/styled';
37
39
  import type { CommonCommandOptions } from '../utils/global-flags';
38
40
  import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
39
41
  import type { GlyphMode } from '../utils/glyph-mode';
42
+ import { shouldShowLegend, validateLegendOptions } from '../utils/legend';
40
43
  import { handleResult } from '../utils/result-handler';
41
44
  import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
42
45
 
@@ -128,11 +131,20 @@ interface MigrationListOptions extends CommonCommandOptions {
128
131
  readonly config?: string;
129
132
  readonly space?: string;
130
133
  readonly ascii?: boolean;
134
+ readonly legend?: boolean;
135
+ }
136
+
137
+ export interface MigrationListExecuteResult {
138
+ readonly list: MigrationListResult;
139
+ readonly liveContractHash: string;
140
+ readonly aggregate: ContractSpaceAggregate;
131
141
  }
132
142
 
133
143
  export interface MigrationListHumanRenderOptions {
134
144
  readonly glyphMode: GlyphMode;
135
145
  readonly useColor: boolean;
146
+ readonly liveContractHash: string;
147
+ readonly graphForSpace: (spaceId: string) => MigrationGraph | undefined;
136
148
  }
137
149
 
138
150
  export function renderMigrationListHumanOutput(
@@ -142,6 +154,8 @@ export function renderMigrationListHumanOutput(
142
154
  const styler = createAnsiMigrationListStyler({ useColor: options.useColor });
143
155
  return renderMigrationListWithStyle(result, styler, options.glyphMode, {
144
156
  colorize: options.useColor,
157
+ liveContractHash: options.liveContractHash,
158
+ graphForSpace: options.graphForSpace,
145
159
  });
146
160
  }
147
161
 
@@ -210,7 +224,7 @@ export async function executeMigrationListCommand(
210
224
  options: MigrationListOptions,
211
225
  flags: GlobalFlags,
212
226
  ui: TerminalUI,
213
- ): Promise<Result<MigrationListResult, CliStructuredError>> {
227
+ ): Promise<Result<MigrationListExecuteResult, CliStructuredError>> {
214
228
  const config = await loadConfig(options.config);
215
229
  const { configPath, migrationsDir, migrationsRelative } = resolveMigrationPaths(
216
230
  options.config,
@@ -229,6 +243,15 @@ export async function executeMigrationListCommand(
229
243
  flags,
230
244
  });
231
245
  ui.stderr(header);
246
+ if (shouldShowLegend(options, flags)) {
247
+ ui.stderr(
248
+ renderMigrationGraphLegend({
249
+ colorize: flags.color !== false,
250
+ glyphMode: ui.resolveGlyphMode(options.ascii === true),
251
+ }),
252
+ );
253
+ ui.stderr('');
254
+ }
232
255
  }
233
256
 
234
257
  const loaded = await buildReadAggregate(config, { migrationsDir });
@@ -236,15 +259,18 @@ export async function executeMigrationListCommand(
236
259
  return notOk(loaded.failure);
237
260
  }
238
261
 
239
- const spaces = await migrationSpaceListEntriesFromAggregate(
240
- loaded.value.aggregate,
241
- migrationsDir,
242
- );
262
+ const { aggregate, contractHash: liveContractHash } = loaded.value;
263
+
264
+ const spaces = await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir);
243
265
 
244
- return runMigrationList({
266
+ const listResult = runMigrationList({
245
267
  spaces,
246
268
  ...ifDefined('spaceFilter', options.space),
247
269
  });
270
+ if (!listResult.ok) {
271
+ return listResult;
272
+ }
273
+ return ok({ list: listResult.value, liveContractHash, aggregate });
248
274
  }
249
275
 
250
276
  export function createMigrationListCommand(): Command {
@@ -263,6 +289,7 @@ export function createMigrationListCommand(): Command {
263
289
  'prisma-next migration list',
264
290
  'prisma-next migration list --space app',
265
291
  'prisma-next migration list --ascii',
292
+ 'prisma-next migration list --legend',
266
293
  'prisma-next migration list --json',
267
294
  ]);
268
295
  setCommandSeeAlso(command, [
@@ -275,18 +302,25 @@ export function createMigrationListCommand(): Command {
275
302
  .option('--config <path>', 'Path to prisma-next.config.ts')
276
303
  .option('--space <id>', 'Narrow output to a single contract space')
277
304
  .option('--ascii', 'Use ASCII kind glyphs (pipe-friendly)')
305
+ .option('--legend', 'Print a key for the tree glyphs and lane colors')
278
306
  .action(async (options: MigrationListOptions) => {
279
307
  const flags = parseGlobalFlagsOrExit(options);
280
308
  const ui = createTerminalUI(flags);
309
+ const legendValidation = validateLegendOptions(options, flags);
310
+ if (!legendValidation.ok) {
311
+ process.exit(handleResult(legendValidation, flags, ui));
312
+ }
281
313
  const result = await executeMigrationListCommand(options, flags, ui);
282
- const exitCode = handleResult(result, flags, ui, (listResult) => {
314
+ const exitCode = handleResult(result, flags, ui, ({ list, liveContractHash, aggregate }) => {
283
315
  if (flags.json) {
284
- ui.output(JSON.stringify(listResult, null, 2));
316
+ ui.output(JSON.stringify(list, null, 2));
285
317
  } else if (!flags.quiet) {
286
318
  ui.output(
287
- renderMigrationListHumanOutput(listResult, {
319
+ renderMigrationListHumanOutput(list, {
288
320
  glyphMode: ui.resolveGlyphMode(options.ascii === true),
289
321
  useColor: ui.useColor,
322
+ liveContractHash,
323
+ graphForSpace: (spaceId) => aggregate.space(spaceId)?.graph(),
290
324
  }),
291
325
  );
292
326
  }