@prisma-next/cli 0.12.0-dev.21 → 0.12.0-dev.23

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 (46) hide show
  1. package/dist/cli.mjs +5 -5
  2. package/dist/{client-Cdxcme1x.mjs → client-BHe8szOW.mjs} +4 -4
  3. package/dist/{client-Cdxcme1x.mjs.map → client-BHe8szOW.mjs.map} +1 -1
  4. package/dist/commands/contract-infer.mjs +1 -1
  5. package/dist/commands/db-init.mjs +2 -2
  6. package/dist/commands/db-schema.mjs +1 -1
  7. package/dist/commands/db-sign.mjs +1 -1
  8. package/dist/commands/db-update.mjs +2 -2
  9. package/dist/commands/db-verify.mjs +1 -1
  10. package/dist/commands/migrate.mjs +1 -1
  11. package/dist/commands/migration-graph.mjs +164 -1
  12. package/dist/commands/migration-graph.mjs.map +1 -0
  13. package/dist/commands/migration-list.d.mts.map +1 -1
  14. package/dist/commands/migration-list.mjs +4 -6
  15. package/dist/commands/migration-list.mjs.map +1 -1
  16. package/dist/commands/migration-log.d.mts +4 -16
  17. package/dist/commands/migration-log.d.mts.map +1 -1
  18. package/dist/commands/migration-log.mjs +1 -137
  19. package/dist/commands/migration-show.mjs +1 -1
  20. package/dist/commands/migration-status.mjs +1 -1
  21. package/dist/{contract-infer-DaFPNrZH.mjs → contract-infer-OCn12Zvn.mjs} +2 -2
  22. package/dist/{contract-infer-DaFPNrZH.mjs.map → contract-infer-OCn12Zvn.mjs.map} +1 -1
  23. package/dist/{db-verify-BSA1a_W_.mjs → db-verify-DJxengYP.mjs} +2 -2
  24. package/dist/{db-verify-BSA1a_W_.mjs.map → db-verify-DJxengYP.mjs.map} +1 -1
  25. package/dist/exports/control-api.mjs +1 -1
  26. package/dist/{inspect-live-schema-Dn56wDhG.mjs → inspect-live-schema-DVZlDlnF.mjs} +2 -2
  27. package/dist/{inspect-live-schema-Dn56wDhG.mjs.map → inspect-live-schema-DVZlDlnF.mjs.map} +1 -1
  28. package/dist/{migration-command-scaffold-V52dV2Tv.mjs → migration-command-scaffold-Cs7Ky-m5.mjs} +2 -2
  29. package/dist/{migration-command-scaffold-V52dV2Tv.mjs.map → migration-command-scaffold-Cs7Ky-m5.mjs.map} +1 -1
  30. package/dist/{migration-graph-DKl_IYsF.mjs → migration-graph-tree-render-BQdhKBO8.mjs} +405 -165
  31. package/dist/migration-graph-tree-render-BQdhKBO8.mjs.map +1 -0
  32. package/dist/migration-log-BzPmks3c.mjs +203 -0
  33. package/dist/migration-log-BzPmks3c.mjs.map +1 -0
  34. package/package.json +18 -18
  35. package/src/commands/migration-list.ts +11 -15
  36. package/src/commands/migration-log.ts +23 -89
  37. package/src/control-api/client.ts +3 -3
  38. package/src/utils/formatters/migration-graph-tree-render.ts +31 -1
  39. package/src/utils/formatters/migration-list-data-column.ts +0 -93
  40. package/src/utils/formatters/migration-list-graph-topology.ts +1 -7
  41. package/src/utils/formatters/migration-list-render.ts +102 -70
  42. package/src/utils/formatters/migration-log-table.ts +190 -0
  43. package/dist/commands/migration-log.mjs.map +0 -1
  44. package/dist/migration-graph-DKl_IYsF.mjs.map +0 -1
  45. package/dist/migration-list-styler-COQbZmXk.mjs +0 -414
  46. package/dist/migration-list-styler-COQbZmXk.mjs.map +0 -1
@@ -1,16 +1,12 @@
1
+ import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
2
+ import type { MigrationEdge, MigrationGraph } from '@prisma-next/migration-tools/graph';
1
3
  import type { GlyphMode } from '../glyph-mode';
4
+ import { buildMigrationGraphLayout } from './migration-graph-layout';
5
+ import { buildMigrationGraphRows } from './migration-graph-rows';
2
6
  import {
3
- computeMigrationDirNameWidth,
4
- formatMigrationDataColumn,
5
- migrationListEmptySource,
6
- migrationListForwardArrow,
7
- migrationListKindGlyph,
8
- } from './migration-list-data-column';
9
- import {
10
- classifyMigrationListGraphTopology,
11
- type MigrationEdgeKind,
12
- type MigrationListGraphTopology,
13
- } from './migration-list-graph-topology';
7
+ type MigrationEdgeAnnotation,
8
+ renderMigrationGraphTree,
9
+ } from './migration-graph-tree-render';
14
10
  import type { MigrationListEntry, MigrationListResult } from './migration-list-types';
15
11
 
16
12
  export type { GlyphMode } from '../glyph-mode';
@@ -62,42 +58,88 @@ export const IDENTITY_MIGRATION_LIST_STYLER: MigrationListStyler = {
62
58
  emptyState: (text) => text,
63
59
  };
64
60
 
65
- function resolveEdgeKind(
66
- migrationHash: string,
67
- kindByMigrationHash: ReadonlyMap<string, MigrationEdgeKind>,
68
- ): MigrationEdgeKind {
69
- return kindByMigrationHash.get(migrationHash) ?? 'forward';
61
+ function canonicalFrom(from: string | null): string {
62
+ return from ?? EMPTY_CONTRACT_HASH;
70
63
  }
71
64
 
72
- function formatMigrationRow(
73
- migration: MigrationListEntry,
74
- dirNameWidth: number,
75
- edgeKind: MigrationEdgeKind,
76
- glyphMode: GlyphMode,
77
- style: MigrationListStyler,
78
- ): string {
79
- const kindColumn = `${style.kind(migrationListKindGlyph(glyphMode, edgeKind))} `;
80
- const data = formatMigrationDataColumn(migration, {
81
- dirNameWidth,
82
- edgeKind,
83
- style,
84
- forwardArrow: migrationListForwardArrow(glyphMode),
85
- emptySource: migrationListEmptySource(glyphMode),
86
- });
87
- return `${kindColumn}${data}`;
65
+ export function migrationGraphFromListEntries(
66
+ entries: readonly MigrationListEntry[],
67
+ ): MigrationGraph {
68
+ const nodes = new Set<string>();
69
+ const forwardChain = new Map<string, MigrationEdge[]>();
70
+ const reverseChain = new Map<string, MigrationEdge[]>();
71
+ const migrationByHash = new Map<string, MigrationEdge>();
72
+
73
+ for (const entry of entries) {
74
+ const from = canonicalFrom(entry.from);
75
+ const edge: MigrationEdge = {
76
+ from,
77
+ to: entry.to,
78
+ migrationHash: entry.migrationHash,
79
+ dirName: entry.dirName,
80
+ createdAt: entry.createdAt,
81
+ invariants: entry.providedInvariants,
82
+ };
83
+ nodes.add(from);
84
+ nodes.add(entry.to);
85
+ const forward = forwardChain.get(from);
86
+ if (forward) forward.push(edge);
87
+ else forwardChain.set(from, [edge]);
88
+ const reverse = reverseChain.get(entry.to);
89
+ if (reverse) reverse.push(edge);
90
+ else reverseChain.set(entry.to, [edge]);
91
+ migrationByHash.set(entry.migrationHash, edge);
92
+ }
93
+
94
+ return { nodes, forwardChain, reverseChain, migrationByHash };
95
+ }
96
+
97
+ export function buildEdgeAnnotationsByHashFromListEntries(
98
+ entries: readonly MigrationListEntry[],
99
+ ): ReadonlyMap<string, MigrationEdgeAnnotation> {
100
+ const annotations = new Map<string, MigrationEdgeAnnotation>();
101
+ for (const entry of entries) {
102
+ annotations.set(entry.migrationHash, {
103
+ operationCount: entry.operationCount,
104
+ invariants: entry.providedInvariants,
105
+ });
106
+ }
107
+ return annotations;
108
+ }
109
+
110
+ export function buildRefsByHashFromListEntries(
111
+ entries: readonly MigrationListEntry[],
112
+ ): ReadonlyMap<string, readonly string[]> {
113
+ const refsByHash = new Map<string, readonly string[]>();
114
+ for (const entry of entries) {
115
+ if (entry.refs.length > 0) {
116
+ refsByHash.set(entry.to, entry.refs);
117
+ }
118
+ }
119
+ return refsByHash;
88
120
  }
89
121
 
90
122
  function formatEmptyStateLine(spaceId: string, style: MigrationListStyler): string {
91
123
  return style.emptyState(`There are no migrations in migrations/${spaceId}/ yet`);
92
124
  }
93
125
 
94
- function renderSpaceBlock(
126
+ function indentTreeBlock(treeOutput: string, indent: string): string {
127
+ if (treeOutput.length === 0) {
128
+ return treeOutput;
129
+ }
130
+ return treeOutput
131
+ .split('\n')
132
+ .map((line) => (line.length === 0 ? line : `${indent}${line}`))
133
+ .join('\n');
134
+ }
135
+
136
+ function renderSpaceTreeBlock(
95
137
  spaceId: string,
96
138
  migrations: readonly MigrationListEntry[],
97
139
  multiSpace: boolean,
98
140
  glyphMode: GlyphMode,
99
- kindByMigrationHash: ReadonlyMap<string, MigrationEdgeKind>,
100
141
  style: MigrationListStyler,
142
+ colorize: boolean,
101
143
  ): readonly string[] {
102
144
  if (migrations.length === 0) {
103
145
  const emptyLine = formatEmptyStateLine(spaceId, style);
@@ -107,50 +149,44 @@ function renderSpaceBlock(
107
149
  return [style.spaceHeading(`${spaceId}:`), ` ${emptyLine}`];
108
150
  }
109
151
 
110
- const dirNameWidth = computeMigrationDirNameWidth(migrations);
111
- const rows = migrations.map((entry) =>
112
- formatMigrationRow(
113
- entry,
114
- dirNameWidth,
115
- resolveEdgeKind(entry.migrationHash, kindByMigrationHash),
116
- glyphMode,
117
- style,
118
- ),
119
- );
152
+ const graph = migrationGraphFromListEntries(migrations);
153
+ const rowModel = buildMigrationGraphRows(graph);
154
+ const layout = buildMigrationGraphLayout(rowModel);
155
+ const treeOutput = renderMigrationGraphTree(layout, {
156
+ refsByHash: buildRefsByHashFromListEntries(migrations),
157
+ edgeAnnotationsByHash: buildEdgeAnnotationsByHashFromListEntries(migrations),
158
+ colorize,
159
+ glyphMode,
160
+ });
161
+
120
162
  if (!multiSpace) {
121
- return rows;
163
+ return treeOutput.length === 0 ? [] : [treeOutput];
122
164
  }
123
- return [style.spaceHeading(`${spaceId}:`), ...rows.map((row) => ` ${row}`)];
165
+
166
+ const indented = indentTreeBlock(treeOutput, ' ');
167
+ return [style.spaceHeading(`${spaceId}:`), indented];
124
168
  }
125
169
 
126
- export function buildMigrationListTopologyBySpace(
127
- result: MigrationListResult,
128
- ): ReadonlyMap<string, MigrationListGraphTopology> {
129
- const topologyBySpaceId = new Map<string, MigrationListGraphTopology>();
130
- for (const space of result.spaces) {
131
- topologyBySpaceId.set(space.spaceId, classifyMigrationListGraphTopology(space.migrations));
132
- }
133
- return topologyBySpaceId;
170
+ export interface RenderMigrationListWithStyleOptions {
171
+ readonly colorize?: boolean;
134
172
  }
135
173
 
136
174
  /**
137
- * Compose the styled `migration list` output. The renderer is
138
- * presentation-neutral every token passes through `style` before
139
- * landing in the output, so the same composition serves the pure-text
140
- * path ({@link renderMigrationList} via
141
- * {@link IDENTITY_MIGRATION_LIST_STYLER}) and the ANSI-styled CLI path
142
- * (via the ANSI styler the CLI shell wires up).
175
+ * Compose the styled `migration list` human output via the shared tree
176
+ * renderer. Each on-disk migration is one edge row with package-fact
177
+ * annotations; refs decorate destination contract nodes.
178
+ *
179
+ * `options.colorize` must match whether `style` emits ANSI (e.g. both true for
180
+ * `createAnsiMigrationListStyler({ useColor: true })`).
143
181
  */
144
182
  export function renderMigrationListWithStyle(
145
183
  result: MigrationListResult,
146
184
  style: MigrationListStyler,
147
185
  glyphMode: GlyphMode = 'unicode',
148
- topologyBySpaceId: ReadonlyMap<
149
- string,
150
- MigrationListGraphTopology
151
- > = buildMigrationListTopologyBySpace(result),
186
+ options: RenderMigrationListWithStyleOptions = {},
152
187
  ): string {
153
188
  const multiSpace = result.spaces.length > 1;
189
+ const colorize = options.colorize ?? false;
154
190
  const lines: string[] = [];
155
191
 
156
192
  for (let index = 0; index < result.spaces.length; index++) {
@@ -158,18 +194,14 @@ export function renderMigrationListWithStyle(
158
194
  if (index > 0) {
159
195
  lines.push('');
160
196
  }
161
- const topology = topologyBySpaceId.get(space.spaceId);
162
- const kindByMigrationHash =
163
- topology?.kindByMigrationHash ??
164
- classifyMigrationListGraphTopology(space.migrations).kindByMigrationHash;
165
197
  lines.push(
166
- ...renderSpaceBlock(
198
+ ...renderSpaceTreeBlock(
167
199
  space.spaceId,
168
200
  space.migrations,
169
201
  multiSpace,
170
202
  glyphMode,
171
- kindByMigrationHash,
172
203
  style,
204
+ colorize,
173
205
  ),
174
206
  );
175
207
  }
@@ -0,0 +1,190 @@
1
+ import type { LedgerEntryRecord } from '@prisma-next/contract/types';
2
+ import stringWidth from 'string-width';
3
+ import {
4
+ abbreviateContractHash,
5
+ MIGRATION_LIST_EMPTY_SOURCE,
6
+ MIGRATION_LIST_FORWARD_EDGE_GLYPH,
7
+ } from './migration-list-data-column';
8
+ import { IDENTITY_MIGRATION_LIST_STYLER, type MigrationListStyler } from './migration-list-render';
9
+
10
+ export type LedgerTimestampMode = 'local' | 'utc' | 'iso';
11
+
12
+ export interface RenderMigrationLogTableOptions {
13
+ readonly utc?: boolean;
14
+ readonly styler?: MigrationListStyler;
15
+ }
16
+
17
+ export interface SerializedLedgerEntryRecord {
18
+ readonly space: string;
19
+ readonly migrationName: string;
20
+ readonly migrationHash: string;
21
+ readonly from: string | null;
22
+ readonly to: string;
23
+ readonly appliedAt: string;
24
+ readonly operationCount: number;
25
+ }
26
+
27
+ const HEADING_APPLIED_AT = 'Applied at';
28
+ const HEADING_SPACE = 'Space';
29
+ const HEADING_MIGRATION = 'Migration';
30
+ const HEADING_CHANGE = 'Change';
31
+ const HEADING_OPS = 'Ops';
32
+ const COLUMN_SEPARATOR = ' ';
33
+ const DIVIDER_CHAR = '─';
34
+
35
+ export function sortLedgerEntries(entries: readonly LedgerEntryRecord[]): LedgerEntryRecord[] {
36
+ return [...entries].sort((left, right) => {
37
+ const timeDiff = left.appliedAt.getTime() - right.appliedAt.getTime();
38
+ if (timeDiff !== 0) {
39
+ return timeDiff;
40
+ }
41
+ const spaceDiff = left.space.localeCompare(right.space);
42
+ if (spaceDiff !== 0) {
43
+ return spaceDiff;
44
+ }
45
+ return left.migrationName.localeCompare(right.migrationName);
46
+ });
47
+ }
48
+
49
+ function pad2(value: number): string {
50
+ return String(value).padStart(2, '0');
51
+ }
52
+
53
+ export function formatLedgerAppliedAt(date: Date, mode: LedgerTimestampMode): string {
54
+ if (mode === 'iso') {
55
+ return date.toISOString();
56
+ }
57
+ if (mode === 'utc') {
58
+ return `${date.getUTCFullYear()}-${pad2(date.getUTCMonth() + 1)}-${pad2(date.getUTCDate())} ${pad2(date.getUTCHours())}:${pad2(date.getUTCMinutes())}:${pad2(date.getUTCSeconds())}Z`;
59
+ }
60
+ const offsetMinutes = -date.getTimezoneOffset();
61
+ const sign = offsetMinutes >= 0 ? '+' : '-';
62
+ const absoluteOffset = Math.abs(offsetMinutes);
63
+ const offsetHours = pad2(Math.floor(absoluteOffset / 60));
64
+ const offsetMins = pad2(absoluteOffset % 60);
65
+ return `${date.getFullYear()}-${pad2(date.getMonth() + 1)}-${pad2(date.getDate())} ${pad2(date.getHours())}:${pad2(date.getMinutes())}:${pad2(date.getSeconds())} ${sign}${offsetHours}:${offsetMins}`;
66
+ }
67
+
68
+ export function formatHashEndpoint(hash: string | null): string {
69
+ if (hash === null) {
70
+ return MIGRATION_LIST_EMPTY_SOURCE;
71
+ }
72
+ return abbreviateContractHash(hash);
73
+ }
74
+
75
+ export function formatHashTransition(from: string | null, to: string): string {
76
+ return `${formatHashEndpoint(from)} ${MIGRATION_LIST_FORWARD_EDGE_GLYPH} ${abbreviateContractHash(to)}`;
77
+ }
78
+
79
+ export function styleHashTransition(
80
+ from: string | null,
81
+ to: string,
82
+ styler: MigrationListStyler,
83
+ ): string {
84
+ const fromPart =
85
+ from === null
86
+ ? styler.glyph(MIGRATION_LIST_EMPTY_SOURCE)
87
+ : styler.sourceHash(abbreviateContractHash(from));
88
+ const arrow = styler.glyph(MIGRATION_LIST_FORWARD_EDGE_GLYPH);
89
+ const dest = styler.destHash(abbreviateContractHash(to));
90
+ return `${fromPart} ${arrow} ${dest}`;
91
+ }
92
+
93
+ function padVisible(text: string, targetWidth: number): string {
94
+ const padding = Math.max(0, targetWidth - stringWidth(text));
95
+ return text + ' '.repeat(padding);
96
+ }
97
+
98
+ function columnWidth(values: readonly string[]): number {
99
+ return values.reduce((max, value) => Math.max(max, stringWidth(value)), 0);
100
+ }
101
+
102
+ function padDividerCell(valueWidth: number): string {
103
+ return DIVIDER_CHAR.repeat(valueWidth + 2);
104
+ }
105
+
106
+ function padTextCell(value: string, valueWidth: number): string {
107
+ return ` ${padVisible(value, valueWidth)} `;
108
+ }
109
+
110
+ function padOpsCell(value: string, valueWidth: number): string {
111
+ const padding = Math.max(0, valueWidth - stringWidth(value));
112
+ return ` ${' '.repeat(padding)}${value} `;
113
+ }
114
+
115
+ export function renderMigrationLogTable(
116
+ entries: readonly LedgerEntryRecord[],
117
+ options: RenderMigrationLogTableOptions = {},
118
+ ): string {
119
+ const sorted = sortLedgerEntries(entries);
120
+ if (sorted.length === 0) {
121
+ return '';
122
+ }
123
+
124
+ const styler = options.styler ?? IDENTITY_MIGRATION_LIST_STYLER;
125
+ const showSpace = new Set(sorted.map((entry) => entry.space)).size > 1;
126
+ const timestampMode: LedgerTimestampMode = options.utc ? 'utc' : 'local';
127
+ const rows = sorted.map((entry) => ({
128
+ appliedAt: formatLedgerAppliedAt(entry.appliedAt, timestampMode),
129
+ space: entry.space,
130
+ migrationName: entry.migrationName,
131
+ transition: formatHashTransition(entry.from, entry.to),
132
+ ops: `${entry.operationCount} ops`,
133
+ from: entry.from,
134
+ to: entry.to,
135
+ }));
136
+
137
+ const appliedAtWidth = columnWidth([HEADING_APPLIED_AT, ...rows.map((row) => row.appliedAt)]);
138
+ const spaceWidth = showSpace ? columnWidth([HEADING_SPACE, ...rows.map((row) => row.space)]) : 0;
139
+ const nameWidth = columnWidth([HEADING_MIGRATION, ...rows.map((row) => row.migrationName)]);
140
+ const transitionWidth = columnWidth([HEADING_CHANGE, ...rows.map((row) => row.transition)]);
141
+ const opsWidth = columnWidth([HEADING_OPS, ...rows.map((row) => row.ops)]);
142
+
143
+ const headingParts = [padTextCell(HEADING_APPLIED_AT, appliedAtWidth)];
144
+ if (showSpace) {
145
+ headingParts.push(padTextCell(HEADING_SPACE, spaceWidth));
146
+ }
147
+ headingParts.push(
148
+ padTextCell(HEADING_MIGRATION, nameWidth),
149
+ padTextCell(HEADING_CHANGE, transitionWidth),
150
+ padOpsCell(HEADING_OPS, opsWidth),
151
+ );
152
+ const heading = headingParts.join(COLUMN_SEPARATOR);
153
+
154
+ const dividerParts = [padDividerCell(appliedAtWidth)];
155
+ if (showSpace) {
156
+ dividerParts.push(padDividerCell(spaceWidth));
157
+ }
158
+ dividerParts.push(
159
+ padDividerCell(nameWidth),
160
+ padDividerCell(transitionWidth),
161
+ padDividerCell(opsWidth),
162
+ );
163
+ const divider = dividerParts.map((cell) => styler.summary(cell)).join(COLUMN_SEPARATOR);
164
+
165
+ const dataRows = rows.map((row) => {
166
+ const parts = [padTextCell(row.appliedAt, appliedAtWidth)];
167
+ if (showSpace) {
168
+ parts.push(padTextCell(row.space, spaceWidth));
169
+ }
170
+ parts.push(
171
+ padTextCell(styler.dirName(row.migrationName), nameWidth),
172
+ padTextCell(styleHashTransition(row.from, row.to, styler), transitionWidth),
173
+ padOpsCell(row.ops, opsWidth),
174
+ );
175
+ return parts.join(COLUMN_SEPARATOR);
176
+ });
177
+
178
+ return [heading, divider, ...dataRows].join('\n');
179
+ }
180
+
181
+ export function serializeLedgerEntriesForJson(
182
+ entries: readonly LedgerEntryRecord[],
183
+ ): SerializedLedgerEntryRecord[] {
184
+ return sortLedgerEntries(entries).map(({ appliedAt, ...rest }) => ({
185
+ ...rest,
186
+ appliedAt: formatLedgerAppliedAt(appliedAt, 'iso'),
187
+ }));
188
+ }
189
+
190
+ export const MIGRATION_LOG_EMPTY_MESSAGE = 'No migrations have been applied to this database.';
@@ -1 +0,0 @@
1
- {"version":3,"file":"migration-log.mjs","names":[],"sources":["../../src/commands/migration-log.ts"],"sourcesContent":["import type { MigrationPlanOperation } from '@prisma-next/framework-components/control';\nimport { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';\nimport { MigrationToolsError } from '@prisma-next/migration-tools/errors';\nimport { findPath } from '@prisma-next/migration-tools/migration-graph';\nimport { notOk, ok, type Result } from '@prisma-next/utils/result';\nimport { cyan, dim } from 'colorette';\nimport { Command } from 'commander';\nimport { loadConfig } from '../config-loader';\nimport { createControlClient } from '../control-api/client';\nimport {\n CliStructuredError,\n errorDatabaseConnectionRequired,\n errorDriverRequired,\n errorUnexpected,\n mapMigrationToolsError,\n} from '../utils/cli-errors';\nimport {\n addGlobalOptions,\n maskConnectionUrl,\n resolveMigrationPaths,\n setCommandDescriptions,\n setCommandExamples,\n setCommandSeeAlso,\n targetSupportsMigrations,\n} from '../utils/command-helpers';\nimport { buildReadAggregate } from '../utils/contract-space-aggregate-loader';\nimport { formatStyledHeader } from '../utils/formatters/styled';\nimport type { CommonCommandOptions } from '../utils/global-flags';\nimport { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';\nimport { handleResult } from '../utils/result-handler';\nimport { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';\n\ninterface MigrationLogOptions extends CommonCommandOptions {\n readonly db?: string;\n readonly config?: string;\n}\n\nexport interface MigrationLogEntry {\n readonly dirName: string;\n readonly from: string;\n readonly to: string;\n readonly migrationHash: string;\n readonly operationCount: number;\n readonly createdAt: string;\n}\n\nexport interface MigrationLogResult {\n readonly ok: true;\n readonly markerHash: string | null;\n readonly applied: readonly MigrationLogEntry[];\n readonly summary: string;\n}\n\nexport async function executeMigrationLogCommand(\n options: MigrationLogOptions,\n flags: GlobalFlags,\n ui: TerminalUI,\n): Promise<Result<MigrationLogResult, CliStructuredError>> {\n const config = await loadConfig(options.config);\n const { configPath, appMigrationsRelative, migrationsDir } = resolveMigrationPaths(\n options.config,\n config,\n );\n\n const dbConnection = options.db ?? config.db?.connection;\n if (!dbConnection) {\n return notOk(\n errorDatabaseConnectionRequired({\n why: `Database connection is required for migration log (set db.connection in ${configPath}, or pass --db <url>)`,\n commandName: 'migration log',\n }),\n );\n }\n if (!config.driver) {\n return notOk(errorDriverRequired({ why: 'Config.driver is required for migration log' }));\n }\n if (!targetSupportsMigrations(config.target)) {\n return notOk(errorUnexpected('Target does not support migrations'));\n }\n\n if (!flags.json && !flags.quiet) {\n const header = formatStyledHeader({\n command: 'migration log',\n description: 'Show executed migration history',\n details: [\n { label: 'config', value: configPath },\n { label: 'migrations', value: appMigrationsRelative },\n ...(typeof dbConnection === 'string'\n ? [{ label: 'database', value: maskConnectionUrl(dbConnection) }]\n : []),\n ],\n flags,\n });\n ui.stderr(header);\n }\n\n const loaded = await buildReadAggregate(config, { migrationsDir });\n if (!loaded.ok) {\n return loaded;\n }\n const graph = loaded.value.aggregate.app.graph();\n const bundles = loaded.value.aggregate.app.packages;\n\n const client = createControlClient({\n family: config.family,\n target: config.target,\n adapter: config.adapter,\n driver: config.driver,\n extensionPacks: config.extensionPacks ?? [],\n });\n\n try {\n await client.connect(dbConnection);\n const marker = await client.readMarker();\n const markerHash = marker?.storageHash ?? null;\n\n if (!markerHash) {\n return ok({\n ok: true,\n markerHash: null,\n applied: [],\n summary: 'No migrations applied (database has no marker)',\n });\n }\n\n const appliedPath = findPath(graph, EMPTY_CONTRACT_HASH, markerHash);\n if (appliedPath === null) {\n return notOk(\n errorUnexpected('Database marker is not reachable from migration history', {\n why: `Marker hash ${markerHash} is not reachable from the root of the on-disk migration graph.`,\n fix: 'The database may have been migrated outside this project. Use `migration status` to inspect the current state.',\n }),\n );\n }\n const pkgByDirName = new Map(bundles.map((p) => [p.dirName, p]));\n const entries: MigrationLogEntry[] = appliedPath.map((edge) => {\n const pkg = pkgByDirName.get(edge.dirName);\n const ops = (pkg?.ops ?? []) as readonly MigrationPlanOperation[];\n return {\n dirName: edge.dirName,\n from: edge.from,\n to: edge.to,\n migrationHash: edge.migrationHash,\n operationCount: ops.length,\n createdAt: edge.createdAt,\n };\n });\n\n return ok({\n ok: true,\n markerHash,\n applied: entries,\n summary: `${entries.length} migration(s) applied`,\n });\n } catch (error) {\n if (CliStructuredError.is(error)) return notOk(error);\n if (MigrationToolsError.is(error)) return notOk(mapMigrationToolsError(error));\n return notOk(\n errorUnexpected(error instanceof Error ? error.message : String(error), {\n why: `Failed to read migration log: ${error instanceof Error ? error.message : String(error)}`,\n }),\n );\n } finally {\n await client.close();\n }\n}\n\nexport function createMigrationLogCommand(): Command {\n const command = new Command('log');\n setCommandDescriptions(\n command,\n 'Show executed migration history',\n 'Reads the database marker and displays the applied migration chain\\n' +\n 'from the initial state to the current marker position.',\n );\n setCommandExamples(command, [\n 'prisma-next migration log --db $DATABASE_URL',\n 'prisma-next migration log --json --db $DATABASE_URL',\n ]);\n setCommandSeeAlso(command, [\n { verb: 'migration status', oneLiner: 'Show migration path and pending status' },\n { verb: 'migration list', oneLiner: 'List on-disk migrations' },\n { verb: 'migration graph', oneLiner: 'Show the migration graph topology' },\n { verb: 'migration show', oneLiner: 'Display migration package contents' },\n ]);\n addGlobalOptions(command)\n .option('--db <url>', 'Database connection string')\n .option('--config <path>', 'Path to prisma-next.config.ts')\n .action(async (options: MigrationLogOptions) => {\n const flags = parseGlobalFlagsOrExit(options);\n const ui = createTerminalUI(flags);\n const result = await executeMigrationLogCommand(options, flags, ui);\n const exitCode = handleResult(result, flags, ui, (logResult) => {\n if (flags.json) {\n ui.output(JSON.stringify(logResult, null, 2));\n } else if (!flags.quiet) {\n const c = (fn: (s: string) => string, s: string) => (flags.color !== false ? fn(s) : s);\n if (logResult.applied.length === 0) {\n ui.log(logResult.summary);\n } else {\n for (const entry of logResult.applied) {\n ui.log(\n `${c(cyan, '✓')} ${entry.dirName} ${c(dim, entry.migrationHash.slice(0, 16) + '…')} ${entry.operationCount} op(s)`,\n );\n }\n ui.log(`\\n${logResult.summary}`);\n }\n }\n });\n process.exit(exitCode);\n });\n return command;\n}\n"],"mappings":";;;;;;;;;;;AAqDA,eAAsB,2BACpB,SACA,OACA,IACyD;CACzD,MAAM,SAAS,MAAM,WAAW,QAAQ,MAAM;CAC9C,MAAM,EAAE,YAAY,uBAAuB,kBAAkB,sBAC3D,QAAQ,QACR,MACF;CAEA,MAAM,eAAe,QAAQ,MAAM,OAAO,IAAI;CAC9C,IAAI,CAAC,cACH,OAAO,MACL,gCAAgC;EAC9B,KAAK,2EAA2E,WAAW;EAC3F,aAAa;CACf,CAAC,CACH;CAEF,IAAI,CAAC,OAAO,QACV,OAAO,MAAM,oBAAoB,EAAE,KAAK,8CAA8C,CAAC,CAAC;CAE1F,IAAI,CAAC,yBAAyB,OAAO,MAAM,GACzC,OAAO,MAAM,gBAAgB,oCAAoC,CAAC;CAGpE,IAAI,CAAC,MAAM,QAAQ,CAAC,MAAM,OAAO;EAC/B,MAAM,SAAS,mBAAmB;GAChC,SAAS;GACT,aAAa;GACb,SAAS;IACP;KAAE,OAAO;KAAU,OAAO;IAAW;IACrC;KAAE,OAAO;KAAc,OAAO;IAAsB;IACpD,GAAI,OAAO,iBAAiB,WACxB,CAAC;KAAE,OAAO;KAAY,OAAO,kBAAkB,YAAY;IAAE,CAAC,IAC9D,CAAC;GACP;GACA;EACF,CAAC;EACD,GAAG,OAAO,MAAM;CAClB;CAEA,MAAM,SAAS,MAAM,mBAAmB,QAAQ,EAAE,cAAc,CAAC;CACjE,IAAI,CAAC,OAAO,IACV,OAAO;CAET,MAAM,QAAQ,OAAO,MAAM,UAAU,IAAI,MAAM;CAC/C,MAAM,UAAU,OAAO,MAAM,UAAU,IAAI;CAE3C,MAAM,SAAS,oBAAoB;EACjC,QAAQ,OAAO;EACf,QAAQ,OAAO;EACf,SAAS,OAAO;EAChB,QAAQ,OAAO;EACf,gBAAgB,OAAO,kBAAkB,CAAC;CAC5C,CAAC;CAED,IAAI;EACF,MAAM,OAAO,QAAQ,YAAY;EAEjC,MAAM,cAAa,MADE,OAAO,WAAW,IACZ,eAAe;EAE1C,IAAI,CAAC,YACH,OAAO,GAAG;GACR,IAAI;GACJ,YAAY;GACZ,SAAS,CAAC;GACV,SAAS;EACX,CAAC;EAGH,MAAM,cAAc,SAAS,OAAO,qBAAqB,UAAU;EACnE,IAAI,gBAAgB,MAClB,OAAO,MACL,gBAAgB,2DAA2D;GACzE,KAAK,eAAe,WAAW;GAC/B,KAAK;EACP,CAAC,CACH;EAEF,MAAM,eAAe,IAAI,IAAI,QAAQ,KAAK,MAAM,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;EAC/D,MAAM,UAA+B,YAAY,KAAK,SAAS;GAE7D,MAAM,MADM,aAAa,IAAI,KAAK,OACnB,GAAG,OAAO,CAAC;GAC1B,OAAO;IACL,SAAS,KAAK;IACd,MAAM,KAAK;IACX,IAAI,KAAK;IACT,eAAe,KAAK;IACpB,gBAAgB,IAAI;IACpB,WAAW,KAAK;GAClB;EACF,CAAC;EAED,OAAO,GAAG;GACR,IAAI;GACJ;GACA,SAAS;GACT,SAAS,GAAG,QAAQ,OAAO;EAC7B,CAAC;CACH,SAAS,OAAO;EACd,IAAI,mBAAmB,GAAG,KAAK,GAAG,OAAO,MAAM,KAAK;EACpD,IAAI,oBAAoB,GAAG,KAAK,GAAG,OAAO,MAAM,uBAAuB,KAAK,CAAC;EAC7E,OAAO,MACL,gBAAgB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,EACtE,KAAK,iCAAiC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,IAC7F,CAAC,CACH;CACF,UAAU;EACR,MAAM,OAAO,MAAM;CACrB;AACF;AAEA,SAAgB,4BAAqC;CACnD,MAAM,UAAU,IAAI,QAAQ,KAAK;CACjC,uBACE,SACA,mCACA,4HAEF;CACA,mBAAmB,SAAS,CAC1B,gDACA,qDACF,CAAC;CACD,kBAAkB,SAAS;EACzB;GAAE,MAAM;GAAoB,UAAU;EAAyC;EAC/E;GAAE,MAAM;GAAkB,UAAU;EAA0B;EAC9D;GAAE,MAAM;GAAmB,UAAU;EAAoC;EACzE;GAAE,MAAM;GAAkB,UAAU;EAAqC;CAC3E,CAAC;CACD,iBAAiB,OAAO,EACrB,OAAO,cAAc,4BAA4B,EACjD,OAAO,mBAAmB,+BAA+B,EACzD,OAAO,OAAO,YAAiC;EAC9C,MAAM,QAAQ,uBAAuB,OAAO;EAC5C,MAAM,KAAK,iBAAiB,KAAK;EAEjC,MAAM,WAAW,aAAa,MADT,2BAA2B,SAAS,OAAO,EAAE,GAC5B,OAAO,KAAK,cAAc;GAC9D,IAAI,MAAM,MACR,GAAG,OAAO,KAAK,UAAU,WAAW,MAAM,CAAC,CAAC;QACvC,IAAI,CAAC,MAAM,OAAO;IACvB,MAAM,KAAK,IAA2B,MAAe,MAAM,UAAU,QAAQ,GAAG,CAAC,IAAI;IACrF,IAAI,UAAU,QAAQ,WAAW,GAC/B,GAAG,IAAI,UAAU,OAAO;SACnB;KACL,KAAK,MAAM,SAAS,UAAU,SAC5B,GAAG,IACD,GAAG,EAAE,MAAM,GAAG,EAAE,GAAG,MAAM,QAAQ,IAAI,EAAE,KAAK,MAAM,cAAc,MAAM,GAAG,EAAE,IAAI,GAAG,EAAE,IAAI,MAAM,eAAe,OAC/G;KAEF,GAAG,IAAI,KAAK,UAAU,SAAS;IACjC;GACF;EACF,CAAC;EACD,QAAQ,KAAK,QAAQ;CACvB,CAAC;CACH,OAAO;AACT"}