@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.
- package/dist/cli.mjs +5 -5
- package/dist/{client-Cdxcme1x.mjs → client-BHe8szOW.mjs} +4 -4
- package/dist/{client-Cdxcme1x.mjs.map → client-BHe8szOW.mjs.map} +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +2 -2
- package/dist/commands/db-schema.mjs +1 -1
- package/dist/commands/db-sign.mjs +1 -1
- package/dist/commands/db-update.mjs +2 -2
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.mjs +1 -1
- package/dist/commands/migration-graph.mjs +164 -1
- package/dist/commands/migration-graph.mjs.map +1 -0
- package/dist/commands/migration-list.d.mts.map +1 -1
- package/dist/commands/migration-list.mjs +4 -6
- package/dist/commands/migration-list.mjs.map +1 -1
- package/dist/commands/migration-log.d.mts +4 -16
- package/dist/commands/migration-log.d.mts.map +1 -1
- package/dist/commands/migration-log.mjs +1 -137
- package/dist/commands/migration-show.mjs +1 -1
- package/dist/commands/migration-status.mjs +1 -1
- package/dist/{contract-infer-DaFPNrZH.mjs → contract-infer-OCn12Zvn.mjs} +2 -2
- package/dist/{contract-infer-DaFPNrZH.mjs.map → contract-infer-OCn12Zvn.mjs.map} +1 -1
- package/dist/{db-verify-BSA1a_W_.mjs → db-verify-DJxengYP.mjs} +2 -2
- package/dist/{db-verify-BSA1a_W_.mjs.map → db-verify-DJxengYP.mjs.map} +1 -1
- package/dist/exports/control-api.mjs +1 -1
- package/dist/{inspect-live-schema-Dn56wDhG.mjs → inspect-live-schema-DVZlDlnF.mjs} +2 -2
- package/dist/{inspect-live-schema-Dn56wDhG.mjs.map → inspect-live-schema-DVZlDlnF.mjs.map} +1 -1
- package/dist/{migration-command-scaffold-V52dV2Tv.mjs → migration-command-scaffold-Cs7Ky-m5.mjs} +2 -2
- package/dist/{migration-command-scaffold-V52dV2Tv.mjs.map → migration-command-scaffold-Cs7Ky-m5.mjs.map} +1 -1
- package/dist/{migration-graph-DKl_IYsF.mjs → migration-graph-tree-render-BQdhKBO8.mjs} +405 -165
- package/dist/migration-graph-tree-render-BQdhKBO8.mjs.map +1 -0
- package/dist/migration-log-BzPmks3c.mjs +203 -0
- package/dist/migration-log-BzPmks3c.mjs.map +1 -0
- package/package.json +18 -18
- package/src/commands/migration-list.ts +11 -15
- package/src/commands/migration-log.ts +23 -89
- package/src/control-api/client.ts +3 -3
- package/src/utils/formatters/migration-graph-tree-render.ts +31 -1
- package/src/utils/formatters/migration-list-data-column.ts +0 -93
- package/src/utils/formatters/migration-list-graph-topology.ts +1 -7
- package/src/utils/formatters/migration-list-render.ts +102 -70
- package/src/utils/formatters/migration-log-table.ts +190 -0
- package/dist/commands/migration-log.mjs.map +0 -1
- package/dist/migration-graph-DKl_IYsF.mjs.map +0 -1
- package/dist/migration-list-styler-COQbZmXk.mjs +0 -414
- 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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
66
|
-
|
|
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
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
|
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
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
163
|
+
return treeOutput.length === 0 ? [] : [treeOutput];
|
|
122
164
|
}
|
|
123
|
-
|
|
165
|
+
|
|
166
|
+
const indented = indentTreeBlock(treeOutput, ' ');
|
|
167
|
+
return [style.spaceHeading(`${spaceId}:`), indented];
|
|
124
168
|
}
|
|
125
169
|
|
|
126
|
-
export
|
|
127
|
-
|
|
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
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* (
|
|
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
|
-
|
|
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
|
-
...
|
|
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"}
|