@prisma-next/cli 0.11.0-dev.3 → 0.11.0-dev.31

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 (139) hide show
  1. package/dist/cli-errors-DFF1LlfU.mjs +215 -0
  2. package/dist/cli-errors-DFF1LlfU.mjs.map +1 -0
  3. package/dist/cli.mjs +8 -9
  4. package/dist/cli.mjs.map +1 -1
  5. package/dist/{client-oXO2WCPD.mjs → client-a5NJce0-.mjs} +5 -5
  6. package/dist/{client-oXO2WCPD.mjs.map → client-a5NJce0-.mjs.map} +1 -1
  7. package/dist/{command-helpers-DtavI0wJ.mjs → command-helpers-BnqwTptC.mjs} +380 -6
  8. package/dist/command-helpers-BnqwTptC.mjs.map +1 -0
  9. package/dist/commands/contract-emit.d.mts.map +1 -1
  10. package/dist/commands/contract-emit.mjs +1 -1
  11. package/dist/commands/contract-infer.d.mts.map +1 -1
  12. package/dist/commands/contract-infer.mjs +1 -1
  13. package/dist/commands/db-init.d.mts.map +1 -1
  14. package/dist/commands/db-init.mjs +33 -7
  15. package/dist/commands/db-init.mjs.map +1 -1
  16. package/dist/commands/db-schema.d.mts.map +1 -1
  17. package/dist/commands/db-schema.mjs +3 -4
  18. package/dist/commands/db-schema.mjs.map +1 -1
  19. package/dist/commands/db-sign.d.mts.map +1 -1
  20. package/dist/commands/db-sign.mjs +6 -7
  21. package/dist/commands/db-sign.mjs.map +1 -1
  22. package/dist/commands/db-update.d.mts.map +1 -1
  23. package/dist/commands/db-update.mjs +36 -8
  24. package/dist/commands/db-update.mjs.map +1 -1
  25. package/dist/commands/db-verify.d.mts.map +1 -1
  26. package/dist/commands/db-verify.mjs +1 -1
  27. package/dist/commands/migrate.d.mts +5 -1
  28. package/dist/commands/migrate.d.mts.map +1 -1
  29. package/dist/commands/migrate.mjs +44 -9
  30. package/dist/commands/migrate.mjs.map +1 -1
  31. package/dist/commands/migration-check.d.mts.map +1 -1
  32. package/dist/commands/migration-check.mjs +2 -3
  33. package/dist/commands/migration-check.mjs.map +1 -1
  34. package/dist/commands/migration-graph.d.mts.map +1 -1
  35. package/dist/commands/migration-graph.mjs +2 -3
  36. package/dist/commands/migration-graph.mjs.map +1 -1
  37. package/dist/commands/migration-list.d.mts +57 -13
  38. package/dist/commands/migration-list.d.mts.map +1 -1
  39. package/dist/commands/migration-list.mjs +2 -103
  40. package/dist/commands/migration-log.d.mts.map +1 -1
  41. package/dist/commands/migration-log.mjs +3 -4
  42. package/dist/commands/migration-log.mjs.map +1 -1
  43. package/dist/commands/migration-new.d.mts.map +1 -1
  44. package/dist/commands/migration-new.mjs +3 -4
  45. package/dist/commands/migration-new.mjs.map +1 -1
  46. package/dist/commands/migration-plan.d.mts +1 -0
  47. package/dist/commands/migration-plan.d.mts.map +1 -1
  48. package/dist/commands/migration-plan.mjs +1 -1
  49. package/dist/commands/migration-show.d.mts +1 -1
  50. package/dist/commands/migration-show.d.mts.map +1 -1
  51. package/dist/commands/migration-show.mjs +6 -7
  52. package/dist/commands/migration-show.mjs.map +1 -1
  53. package/dist/commands/migration-status.d.mts.map +1 -1
  54. package/dist/commands/migration-status.mjs +6 -7
  55. package/dist/commands/migration-status.mjs.map +1 -1
  56. package/dist/commands/ref.d.mts +1 -1
  57. package/dist/commands/ref.d.mts.map +1 -1
  58. package/dist/commands/ref.mjs +34 -9
  59. package/dist/commands/ref.mjs.map +1 -1
  60. package/dist/config-loader-B6sJjXTv.mjs.map +1 -1
  61. package/dist/config-loader.d.mts.map +1 -1
  62. package/dist/{contract-emit-bcrpT-wD.mjs → contract-emit-DYBHfZqL.mjs} +8 -7
  63. package/dist/contract-emit-DYBHfZqL.mjs.map +1 -0
  64. package/dist/{contract-emit-uwT-Mj8-.mjs → contract-emit-aFcOi3aw.mjs} +20 -14
  65. package/dist/contract-emit-aFcOi3aw.mjs.map +1 -0
  66. package/dist/{contract-enrichment-Dani0mMW.mjs → contract-enrichment-XmUPhmsS.mjs} +4 -25
  67. package/dist/contract-enrichment-XmUPhmsS.mjs.map +1 -0
  68. package/dist/{contract-infer-pKkiCt7C.mjs → contract-infer-BpJeg-Eu.mjs} +3 -4
  69. package/dist/{contract-infer-pKkiCt7C.mjs.map → contract-infer-BpJeg-Eu.mjs.map} +1 -1
  70. package/dist/{contract-space-aggregate-loader-BmNQwlws.mjs → contract-space-aggregate-loader-EVU3n9YE.mjs} +2 -2
  71. package/dist/{contract-space-aggregate-loader-BmNQwlws.mjs.map → contract-space-aggregate-loader-EVU3n9YE.mjs.map} +1 -1
  72. package/dist/{db-verify-AoIUriL4.mjs → db-verify-CxtdGiL3.mjs} +6 -7
  73. package/dist/{db-verify-AoIUriL4.mjs.map → db-verify-CxtdGiL3.mjs.map} +1 -1
  74. package/dist/exports/control-api.d.mts +1 -1
  75. package/dist/exports/control-api.d.mts.map +1 -1
  76. package/dist/exports/control-api.mjs +3 -3
  77. package/dist/exports/index.d.mts.map +1 -1
  78. package/dist/exports/index.mjs +1 -1
  79. package/dist/exports/index.mjs.map +1 -1
  80. package/dist/exports/init-output.d.mts.map +1 -1
  81. package/dist/exports/init-output.mjs +1 -1
  82. package/dist/{framework-components-65gOHkHB.mjs → framework-components-DTcjouhS.mjs} +2 -2
  83. package/dist/{framework-components-65gOHkHB.mjs.map → framework-components-DTcjouhS.mjs.map} +1 -1
  84. package/dist/global-flags-CdE7M0d9.d.mts.map +1 -1
  85. package/dist/graph-render-DJVv0_uf.mjs.map +1 -1
  86. package/dist/{init-YX6lCJpG.mjs → init-eGkSo7hi.mjs} +5 -5
  87. package/dist/{init-YX6lCJpG.mjs.map → init-eGkSo7hi.mjs.map} +1 -1
  88. package/dist/{inspect-live-schema-LeWvkZVz.mjs → inspect-live-schema-B1GCyjAJ.mjs} +5 -5
  89. package/dist/{inspect-live-schema-LeWvkZVz.mjs.map → inspect-live-schema-B1GCyjAJ.mjs.map} +1 -1
  90. package/dist/migration-cli.d.mts.map +1 -1
  91. package/dist/migration-cli.mjs.map +1 -1
  92. package/dist/{migration-command-scaffold-BtkunvFQ.mjs → migration-command-scaffold-CNdZl60X.mjs} +5 -5
  93. package/dist/{migration-command-scaffold-BtkunvFQ.mjs.map → migration-command-scaffold-CNdZl60X.mjs.map} +1 -1
  94. package/dist/migration-list-CnYiHrNV.mjs +288 -0
  95. package/dist/migration-list-CnYiHrNV.mjs.map +1 -0
  96. package/dist/{migration-plan-C2jeH1J5.mjs → migration-plan-BnmP_yNz.mjs} +346 -88
  97. package/dist/migration-plan-BnmP_yNz.mjs.map +1 -0
  98. package/dist/{migrations-CwZMa1Ck.mjs → migrations-C7YTBnLy.mjs} +11 -2
  99. package/dist/migrations-C7YTBnLy.mjs.map +1 -0
  100. package/dist/{output-BlsrGMEF.mjs → output-CUIdfYo5.mjs} +1 -1
  101. package/dist/{output-BlsrGMEF.mjs.map → output-CUIdfYo5.mjs.map} +1 -1
  102. package/dist/{progress-adapter-DFfvZcYL.mjs → progress-adapter-xASh41wr.mjs} +1 -1
  103. package/dist/{progress-adapter-DFfvZcYL.mjs.map → progress-adapter-xASh41wr.mjs.map} +1 -1
  104. package/dist/ref-advancement-CHJ_8HxQ.mjs +50 -0
  105. package/dist/ref-advancement-CHJ_8HxQ.mjs.map +1 -0
  106. package/dist/{types--CqjMdk0.d.mts → types-UWB2-rrw.d.mts} +12 -4
  107. package/dist/types-UWB2-rrw.d.mts.map +1 -0
  108. package/dist/{verify-Bom75OYI.mjs → verify-DX4RQwq4.mjs} +2 -2
  109. package/dist/{verify-Bom75OYI.mjs.map → verify-DX4RQwq4.mjs.map} +1 -1
  110. package/package.json +20 -20
  111. package/src/commands/contract-emit.ts +19 -7
  112. package/src/commands/db-init.ts +48 -2
  113. package/src/commands/db-update.ts +45 -0
  114. package/src/commands/migrate.ts +73 -3
  115. package/src/commands/migration-list.ts +145 -74
  116. package/src/commands/migration-plan.ts +365 -128
  117. package/src/commands/ref.ts +46 -6
  118. package/src/control-api/contract-enrichment.ts +6 -42
  119. package/src/control-api/operations/contract-emit.ts +7 -4
  120. package/src/control-api/types.ts +7 -0
  121. package/src/utils/cli-errors.ts +224 -0
  122. package/src/utils/formatters/migration-list-render.ts +171 -0
  123. package/src/utils/formatters/migration-list-styler.ts +56 -0
  124. package/src/utils/formatters/migrations.ts +25 -0
  125. package/src/utils/plan-resolution.ts +257 -0
  126. package/src/utils/ref-advancement.ts +68 -0
  127. package/dist/cli-errors-Czmx92Zy.d.mts +0 -3
  128. package/dist/cli-errors-Djtz98Vm.mjs +0 -71
  129. package/dist/cli-errors-Djtz98Vm.mjs.map +0 -1
  130. package/dist/command-helpers-DtavI0wJ.mjs.map +0 -1
  131. package/dist/commands/migration-list.mjs.map +0 -1
  132. package/dist/contract-emit-bcrpT-wD.mjs.map +0 -1
  133. package/dist/contract-emit-uwT-Mj8-.mjs.map +0 -1
  134. package/dist/contract-enrichment-Dani0mMW.mjs.map +0 -1
  135. package/dist/migration-plan-C2jeH1J5.mjs.map +0 -1
  136. package/dist/migrations-CwZMa1Ck.mjs.map +0 -1
  137. package/dist/terminal-ui-BiB_8KNo.mjs +0 -379
  138. package/dist/terminal-ui-BiB_8KNo.mjs.map +0 -1
  139. package/dist/types--CqjMdk0.d.mts.map +0 -1
@@ -1,23 +1,30 @@
1
- import type { MigrationPlanOperation } from '@prisma-next/framework-components/control';
2
- import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
1
+ import { enumerateMigrationSpaces } from '@prisma-next/migration-tools/enumerate-migration-spaces';
3
2
  import { MigrationToolsError } from '@prisma-next/migration-tools/errors';
4
- import { findPath } from '@prisma-next/migration-tools/migration-graph';
3
+ 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';
8
+ import { ifDefined } from '@prisma-next/utils/defined';
5
9
  import { notOk, ok, type Result } from '@prisma-next/utils/result';
6
10
  import { Command } from 'commander';
7
11
  import { loadConfig } from '../config-loader';
8
12
  import {
9
13
  type CliStructuredError,
14
+ errorInvalidSpaceId,
15
+ errorSpaceNotFound,
10
16
  errorUnexpected,
11
17
  mapMigrationToolsError,
12
18
  } from '../utils/cli-errors';
13
19
  import {
14
20
  addGlobalOptions,
15
- loadMigrationPackages,
16
21
  resolveMigrationPaths,
17
22
  setCommandDescriptions,
18
23
  setCommandExamples,
19
24
  setCommandSeeAlso,
20
25
  } from '../utils/command-helpers';
26
+ import { renderMigrationListWithStyle } from '../utils/formatters/migration-list-render';
27
+ import { createAnsiMigrationListStyler } from '../utils/formatters/migration-list-styler';
21
28
  import { formatStyledHeader } from '../utils/formatters/styled';
22
29
  import type { CommonCommandOptions } from '../utils/global-flags';
23
30
  import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
@@ -26,30 +33,131 @@ import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
26
33
 
27
34
  interface MigrationListOptions extends CommonCommandOptions {
28
35
  readonly config?: string;
36
+ readonly space?: string;
29
37
  }
30
38
 
31
- export interface MigrationListEntry {
32
- readonly dirName: string;
33
- readonly from: string;
34
- readonly to: string;
35
- readonly migrationHash: string;
36
- readonly operationCount: number;
37
- readonly createdAt: string;
39
+ /**
40
+ * Inputs for {@link runMigrationList} — the pure-ish data-and-policy core
41
+ * of `migration list` that tests exercise directly.
42
+ *
43
+ * The core depends only on the filesystem rooted at `migrationsDir`. It
44
+ * does NOT call `loadConfig`, parse CLI flags, render a styled header,
45
+ * or write to any stream. Output rendering is the caller's concern (the
46
+ * CLI shell renders via {@link renderMigrationList}; JSON callers
47
+ * serialize the {@link MigrationListResult} directly).
48
+ */
49
+ export interface RunMigrationListInputs {
50
+ /** Absolute path to the project's `migrations/` directory. */
51
+ readonly migrationsDir: string;
52
+ /**
53
+ * Optional contract-space id to narrow the result to a single space.
54
+ * Same validation rules as {@link isValidSpaceId}. When absent, every
55
+ * on-disk space contributes.
56
+ */
57
+ readonly spaceFilter?: string;
38
58
  }
39
59
 
40
- export interface MigrationListResult {
41
- readonly ok: true;
42
- readonly migrations: readonly MigrationListEntry[];
43
- readonly summary: string;
60
+ /**
61
+ * Compute the trailing one-line summary appended below the migration
62
+ * rows. Wording follows the existing CLI's pluralization style ("N
63
+ * migration(s) on disk" for the common single-space path; multi-space
64
+ * adds "across K contract space(s)" so consumers can see the spread).
65
+ *
66
+ * The renderer suppresses the summary line when `totalMigrations === 0`
67
+ * — the empty-state line carries enough information on its own — so
68
+ * this function always returns a string even for the empty-state.
69
+ */
70
+ function computeSummary(spaces: readonly MigrationSpaceListEntry[]): string {
71
+ const totalMigrations = spaces.reduce((count, space) => count + space.migrations.length, 0);
72
+ if (spaces.length <= 1) {
73
+ return `${totalMigrations} migration(s) on disk`;
74
+ }
75
+ return `${totalMigrations} migration(s) across ${spaces.length} contract space(s)`;
44
76
  }
45
77
 
78
+ /**
79
+ * The unit-testable core of `migration list`. Given an absolute
80
+ * `migrationsDir` and an optional `spaceFilter`, enumerates every
81
+ * on-disk migration (via {@link enumerateMigrationSpaces}), narrows to
82
+ * the requested space if any, and assembles a {@link MigrationListResult}
83
+ * ready for the renderer or JSON serializer.
84
+ *
85
+ * The enumerator is the single source of truth for "what is a contract
86
+ * space": existence, the `--space` candidate-suggestion list, and
87
+ * scoping are all derived from one {@link enumerateMigrationSpaces}
88
+ * traversal. This means the reserved-name exclusion the enumerator
89
+ * applies (e.g. the per-space `refs/` subdirectory) is honoured here for
90
+ * free — a `--space refs` request resolves to `SPACE_NOT_FOUND`, not a
91
+ * synthesized empty-state.
92
+ *
93
+ * Distinct empty-state paths:
94
+ *
95
+ * - `migrations/` missing or contains no valid space directories →
96
+ * synthesizes `[{ spaceId: APP_SPACE_ID, migrations: [] }]` so the
97
+ * renderer's empty-state path can name a directory (spec § Empty-state +
98
+ * the `migrations/` missing edge case).
99
+ * - `--space <id>` on an existing-but-empty space dir → the enumerator
100
+ * surfaces `{ spaceId, migrations: [] }`; `<id>` is in the set, so it
101
+ * scopes to that entry and renders the empty-state the same way.
102
+ * - `--space <id>` on a non-existent (or reserved) space → structured
103
+ * `MIGRATION.SPACE_NOT_FOUND` error (NOT empty-state).
104
+ *
105
+ * Errors caught here:
106
+ *
107
+ * - {@link MigrationToolsError} from the enumerator → mapped through
108
+ * {@link mapMigrationToolsError} so callers see the catalogue code.
109
+ * - Anything else (filesystem etc.) → wrapped via {@link errorUnexpected}.
110
+ */
111
+ export async function runMigrationList(
112
+ inputs: RunMigrationListInputs,
113
+ ): Promise<Result<MigrationListResult, CliStructuredError>> {
114
+ const { migrationsDir, spaceFilter } = inputs;
115
+
116
+ if (spaceFilter !== undefined && !isValidSpaceId(spaceFilter)) {
117
+ return notOk(errorInvalidSpaceId(spaceFilter));
118
+ }
119
+
120
+ let spaces: readonly MigrationSpaceListEntry[];
121
+ try {
122
+ spaces = await enumerateMigrationSpaces({ projectMigrationsDir: migrationsDir });
123
+ } catch (error) {
124
+ if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
125
+ return notOk(
126
+ errorUnexpected(error instanceof Error ? error.message : String(error), {
127
+ why: `Failed to enumerate migrations: ${error instanceof Error ? error.message : String(error)}`,
128
+ }),
129
+ );
130
+ }
131
+
132
+ if (spaceFilter !== undefined && !spaces.some((s) => s.spaceId === spaceFilter)) {
133
+ return notOk(errorSpaceNotFound(spaceFilter, spaces.map((s) => s.spaceId).sort()));
134
+ }
135
+
136
+ const scopedSpaces =
137
+ spaceFilter !== undefined ? spaces.filter((s) => s.spaceId === spaceFilter) : spaces;
138
+
139
+ const resultSpaces: readonly MigrationSpaceListEntry[] =
140
+ scopedSpaces.length === 0 ? [{ spaceId: APP_SPACE_ID, migrations: [] }] : scopedSpaces;
141
+
142
+ return ok({
143
+ ok: true,
144
+ spaces: resultSpaces,
145
+ summary: computeSummary(resultSpaces),
146
+ });
147
+ }
148
+
149
+ /**
150
+ * CLI shell: loads config, resolves paths, prints the styled header on
151
+ * stderr (interactive mode only), and delegates to {@link runMigrationList}.
152
+ * Kept intentionally thin so the unit-testable surface lives in the core.
153
+ */
46
154
  async function executeMigrationListCommand(
47
155
  options: MigrationListOptions,
48
156
  flags: GlobalFlags,
49
157
  ui: TerminalUI,
50
158
  ): Promise<Result<MigrationListResult, CliStructuredError>> {
51
159
  const config = await loadConfig(options.config);
52
- const { configPath, appMigrationsDir, appMigrationsRelative } = resolveMigrationPaths(
160
+ const { configPath, migrationsDir, migrationsRelative } = resolveMigrationPaths(
53
161
  options.config,
54
162
  config,
55
163
  );
@@ -57,58 +165,20 @@ async function executeMigrationListCommand(
57
165
  if (!flags.json && !flags.quiet) {
58
166
  const header = formatStyledHeader({
59
167
  command: 'migration list',
60
- description: 'List on-disk migrations in topological order',
168
+ description: 'List on-disk migrations, latest first, per contract space',
61
169
  details: [
62
170
  { label: 'config', value: configPath },
63
- { label: 'migrations', value: appMigrationsRelative },
171
+ { label: 'migrations', value: migrationsRelative },
172
+ ...(options.space !== undefined ? [{ label: 'space', value: options.space }] : []),
64
173
  ],
65
174
  flags,
66
175
  });
67
176
  ui.stderr(header);
68
177
  }
69
178
 
70
- let bundles: Awaited<ReturnType<typeof loadMigrationPackages>>['bundles'];
71
- let graph: Awaited<ReturnType<typeof loadMigrationPackages>>['graph'];
72
- try {
73
- ({ bundles, graph } = await loadMigrationPackages(appMigrationsDir));
74
- } catch (error) {
75
- if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));
76
- return notOk(
77
- errorUnexpected(error instanceof Error ? error.message : String(error), {
78
- why: `Failed to read migrations: ${error instanceof Error ? error.message : String(error)}`,
79
- }),
80
- );
81
- }
82
-
83
- if (bundles.length === 0) {
84
- return ok({ ok: true, migrations: [], summary: 'No migrations found' });
85
- }
86
-
87
- const leaves = [...graph.nodes].filter(
88
- (n) => !graph.forwardChain.has(n) || graph.forwardChain.get(n)!.length === 0,
89
- );
90
- const targetHash =
91
- leaves.length === 1 ? leaves[0]! : ([...graph.nodes].values().next().value as string);
92
- const chain = findPath(graph, EMPTY_CONTRACT_HASH, targetHash) ?? [];
93
-
94
- const pkgByDirName = new Map(bundles.map((p) => [p.dirName, p]));
95
- const entries: MigrationListEntry[] = chain.map((edge) => {
96
- const pkg = pkgByDirName.get(edge.dirName);
97
- const ops = (pkg?.ops ?? []) as readonly MigrationPlanOperation[];
98
- return {
99
- dirName: edge.dirName,
100
- from: edge.from,
101
- to: edge.to,
102
- migrationHash: edge.migrationHash,
103
- operationCount: ops.length,
104
- createdAt: edge.createdAt,
105
- };
106
- });
107
-
108
- return ok({
109
- ok: true,
110
- migrations: entries,
111
- summary: `${entries.length} migration(s) on disk`,
179
+ return runMigrationList({
180
+ migrationsDir,
181
+ ...ifDefined('spaceFilter', options.space),
112
182
  });
113
183
  }
114
184
 
@@ -116,11 +186,19 @@ export function createMigrationListCommand(): Command {
116
186
  const command = new Command('list');
117
187
  setCommandDescriptions(
118
188
  command,
119
- 'List on-disk migrations in topological order',
120
- 'Enumerates all migration packages under migrations/<space>/ in\n' +
121
- 'topological order. Offline — does not consult the database.',
189
+ 'List on-disk migrations, latest first, per contract space',
190
+ 'Enumerates every on-disk migration under migrations/<space>/ for every\n' +
191
+ 'contract space found on disk, latest first. Offline — does not consult\n' +
192
+ 'the database. Each row shows source → destination contract hashes\n' +
193
+ '(7-char git-style), the self-edge marker (⟲), any provided invariants\n' +
194
+ '({...}), and refs landing on the destination ((production, db)). Pass\n' +
195
+ '--space <id> to narrow to a single contract space.',
122
196
  );
123
- setCommandExamples(command, ['prisma-next migration list']);
197
+ setCommandExamples(command, [
198
+ 'prisma-next migration list',
199
+ 'prisma-next migration list --space app',
200
+ 'prisma-next migration list --json',
201
+ ]);
124
202
  setCommandSeeAlso(command, [
125
203
  { verb: 'migration status', oneLiner: 'Show migration path and pending status' },
126
204
  { verb: 'migration log', oneLiner: 'Show executed migration history' },
@@ -129,6 +207,7 @@ export function createMigrationListCommand(): Command {
129
207
  ]);
130
208
  addGlobalOptions(command)
131
209
  .option('--config <path>', 'Path to prisma-next.config.ts')
210
+ .option('--space <id>', 'Narrow output to a single contract space')
132
211
  .action(async (options: MigrationListOptions) => {
133
212
  const flags = parseGlobalFlagsOrExit(options);
134
213
  const ui = createTerminalUI(flags);
@@ -137,16 +216,8 @@ export function createMigrationListCommand(): Command {
137
216
  if (flags.json) {
138
217
  ui.output(JSON.stringify(listResult, null, 2));
139
218
  } else if (!flags.quiet) {
140
- if (listResult.migrations.length === 0) {
141
- ui.log('No migrations found');
142
- } else {
143
- for (const entry of listResult.migrations) {
144
- ui.log(
145
- `${entry.dirName} ${entry.migrationHash.slice(0, 16)}… ${entry.operationCount} op(s)`,
146
- );
147
- }
148
- ui.log(`\n${listResult.summary}`);
149
- }
219
+ const styler = createAnsiMigrationListStyler({ useColor: ui.useColor });
220
+ ui.output(renderMigrationListWithStyle(listResult, styler));
150
221
  }
151
222
  });
152
223
  process.exit(exitCode);