@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.
- package/dist/cli.mjs +12 -12
- package/dist/{client-nygCs15r.mjs → client-xeWpMlq1.mjs} +4 -4
- package/dist/client-xeWpMlq1.mjs.map +1 -0
- package/dist/{command-helpers-D7TK5Y9e.mjs → command-helpers-DK_5ItoJ.mjs} +16 -2
- package/dist/command-helpers-DK_5ItoJ.mjs.map +1 -0
- package/dist/commands/contract-emit.mjs +1 -1
- package/dist/commands/contract-infer.mjs +1 -1
- package/dist/commands/db-init.mjs +3 -3
- package/dist/commands/db-schema.mjs +3 -3
- package/dist/commands/db-sign.mjs +4 -4
- package/dist/commands/db-update.mjs +4 -4
- package/dist/commands/db-verify.mjs +1 -1
- package/dist/commands/migrate.d.mts +1 -1
- package/dist/commands/migrate.mjs +4 -4
- package/dist/commands/migration-check.mjs +1 -1
- package/dist/commands/migration-graph.d.mts +12 -15
- package/dist/commands/migration-graph.d.mts.map +1 -1
- package/dist/commands/migration-graph.mjs +84 -51
- package/dist/commands/migration-graph.mjs.map +1 -1
- package/dist/commands/migration-list.d.mts +13 -4
- package/dist/commands/migration-list.d.mts.map +1 -1
- package/dist/commands/migration-list.mjs +1 -187
- package/dist/commands/migration-log.d.mts +2 -2
- package/dist/commands/migration-log.mjs +1 -1
- package/dist/commands/migration-new.mjs +3 -3
- package/dist/commands/migration-plan.d.mts +1 -1
- package/dist/commands/migration-plan.mjs +1 -1
- package/dist/commands/migration-show.d.mts +1 -1
- package/dist/commands/migration-show.mjs +2 -2
- package/dist/commands/migration-status.d.mts +20 -2
- package/dist/commands/migration-status.d.mts.map +1 -1
- package/dist/commands/migration-status.mjs +2 -3
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/ref.mjs +2 -2
- package/dist/commands/telemetry/index.mjs +1 -1
- package/dist/{contract-at-errors-CK3qoqZf.mjs → contract-at-errors-DG3kjgoz.mjs} +2 -2
- package/dist/{contract-at-errors-CK3qoqZf.mjs.map → contract-at-errors-DG3kjgoz.mjs.map} +1 -1
- package/dist/{contract-emit-Dzf73HdD.mjs → contract-emit-BO0l6fnT.mjs} +3 -3
- package/dist/{contract-emit-Dzf73HdD.mjs.map → contract-emit-BO0l6fnT.mjs.map} +1 -1
- package/dist/{contract-emit-DwlIz5Zg.mjs → contract-emit-C0Bs0VRj.mjs} +3 -3
- package/dist/{contract-emit-DwlIz5Zg.mjs.map → contract-emit-C0Bs0VRj.mjs.map} +1 -1
- package/dist/{contract-infer-Bzh___GO.mjs → contract-infer-2wtPflGH.mjs} +3 -3
- package/dist/{contract-infer-Bzh___GO.mjs.map → contract-infer-2wtPflGH.mjs.map} +1 -1
- package/dist/{contract-space-aggregate-loader-5zmOENc4.mjs → contract-space-aggregate-loader-Dbr3-jHF.mjs} +2 -2
- package/dist/{contract-space-aggregate-loader-5zmOENc4.mjs.map → contract-space-aggregate-loader-Dbr3-jHF.mjs.map} +1 -1
- package/dist/{db-verify-CNz036sw.mjs → db-verify-CxHiSiTG.mjs} +4 -4
- package/dist/{db-verify-CNz036sw.mjs.map → db-verify-CxHiSiTG.mjs.map} +1 -1
- package/dist/exports/control-api.d.mts +1 -1
- package/dist/exports/control-api.mjs +2 -2
- package/dist/exports/index.mjs +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{framework-components-CyM_xYCY.mjs → framework-components-CxOVKAAh.mjs} +2 -2
- package/dist/{framework-components-CyM_xYCY.mjs.map → framework-components-CxOVKAAh.mjs.map} +1 -1
- package/dist/{global-flags-DEHjV8_s.d.mts → global-flags-DG4uY5tV.d.mts} +1 -1
- package/dist/{global-flags-DEHjV8_s.d.mts.map → global-flags-DG4uY5tV.d.mts.map} +1 -1
- package/dist/{init-DJsQpr_6.mjs → init-R272pxux.mjs} +4 -4
- package/dist/{init-DJsQpr_6.mjs.map → init-R272pxux.mjs.map} +1 -1
- package/dist/{inspect-live-schema-DE76Ou4D.mjs → inspect-live-schema-RekOwfi5.mjs} +3 -3
- package/dist/{inspect-live-schema-DE76Ou4D.mjs.map → inspect-live-schema-RekOwfi5.mjs.map} +1 -1
- package/dist/{migration-check-CL2MzDRX.mjs → migration-check-Dc0cOhKH.mjs} +2 -2
- package/dist/{migration-check-CL2MzDRX.mjs.map → migration-check-Dc0cOhKH.mjs.map} +1 -1
- package/dist/{migration-command-scaffold-194pA8F5.mjs → migration-command-scaffold-ApB3NxWY.mjs} +3 -3
- package/dist/{migration-command-scaffold-194pA8F5.mjs.map → migration-command-scaffold-ApB3NxWY.mjs.map} +1 -1
- package/dist/{migration-graph-tree-render-CVmV9sWr.mjs → migration-graph-space-render-dmLLWift.mjs} +389 -210
- package/dist/migration-graph-space-render-dmLLWift.mjs.map +1 -0
- package/dist/migration-list-C5sXrl0U.mjs +228 -0
- package/dist/migration-list-C5sXrl0U.mjs.map +1 -0
- package/dist/{migration-log-CP6skD5b.mjs → migration-log-DD_vCbYW.mjs} +4 -4
- package/dist/{migration-log-CP6skD5b.mjs.map → migration-log-DD_vCbYW.mjs.map} +1 -1
- package/dist/{migration-plan-D61N1hID.mjs → migration-plan-CeTjQOIG.mjs} +5 -5
- package/dist/{migration-plan-D61N1hID.mjs.map → migration-plan-CeTjQOIG.mjs.map} +1 -1
- package/dist/{migration-status--ejfYqWS.mjs → migration-status-qV8ctwPy.mjs} +61 -45
- package/dist/migration-status-qV8ctwPy.mjs.map +1 -0
- package/dist/{output-B60Gw5fu.mjs → output-CF_hqzI-.mjs} +1 -1
- package/dist/{output-B60Gw5fu.mjs.map → output-CF_hqzI-.mjs.map} +1 -1
- package/dist/{telemetry-CnfdMrpv.mjs → telemetry-S-NGi9U6.mjs} +2 -2
- package/dist/{telemetry-CnfdMrpv.mjs.map → telemetry-S-NGi9U6.mjs.map} +1 -1
- package/dist/{types-BYwWOyYJ.d.mts → types-Mh7mdPHM.d.mts} +1 -1
- package/dist/{types-BYwWOyYJ.d.mts.map → types-Mh7mdPHM.d.mts.map} +1 -1
- package/dist/{verify-By66Zu3y.mjs → verify-BdI-BgYi.mjs} +2 -2
- package/dist/{verify-By66Zu3y.mjs.map → verify-BdI-BgYi.mjs.map} +1 -1
- package/package.json +18 -18
- package/src/commands/migration-graph.ts +125 -58
- package/src/commands/migration-list.ts +43 -9
- package/src/commands/migration-status.ts +106 -74
- package/src/control-api/operations/db-apply.ts +7 -4
- package/src/utils/cli-errors.ts +17 -0
- package/src/utils/formatters/migration-graph-lane-colors.ts +164 -1
- package/src/utils/formatters/migration-graph-rows.ts +128 -15
- package/src/utils/formatters/migration-graph-space-render.ts +138 -0
- package/src/utils/formatters/migration-graph-tree-render.ts +149 -239
- package/src/utils/formatters/migration-list-data-column.ts +6 -0
- package/src/utils/formatters/migration-list-render.ts +43 -23
- package/src/utils/formatters/migration-list-styler.ts +48 -5
- package/src/utils/legend.ts +38 -0
- package/dist/client-nygCs15r.mjs.map +0 -1
- package/dist/command-helpers-D7TK5Y9e.mjs.map +0 -1
- package/dist/commands/migration-list.mjs.map +0 -1
- package/dist/migration-graph-tree-render-CVmV9sWr.mjs.map +0 -1
- package/dist/migration-status--ejfYqWS.mjs.map +0 -1
- package/dist/migration-types-D2FW63pr.d.mts +0 -15
- package/dist/migration-types-D2FW63pr.d.mts.map +0 -1
|
@@ -3,18 +3,13 @@ import type {
|
|
|
3
3
|
ContractMarkerRecordLike,
|
|
4
4
|
ContractSpaceMember,
|
|
5
5
|
} from '@prisma-next/migration-tools/aggregate';
|
|
6
|
-
import { requireHeadRef } from '@prisma-next/migration-tools/aggregate';
|
|
7
6
|
import { EMPTY_CONTRACT_HASH } from '@prisma-next/migration-tools/constants';
|
|
8
7
|
import {
|
|
9
8
|
errorNoInvariantPath,
|
|
10
9
|
errorUnknownInvariant,
|
|
11
10
|
MigrationToolsError,
|
|
12
11
|
} from '@prisma-next/migration-tools/errors';
|
|
13
|
-
import {
|
|
14
|
-
findPath,
|
|
15
|
-
findPathWithDecision,
|
|
16
|
-
findReachableLeaves,
|
|
17
|
-
} from '@prisma-next/migration-tools/migration-graph';
|
|
12
|
+
import { findPath, findPathWithDecision } from '@prisma-next/migration-tools/migration-graph';
|
|
18
13
|
import { parseContractRef } from '@prisma-next/migration-tools/ref-resolution';
|
|
19
14
|
import type { RefEntry, Refs } from '@prisma-next/migration-tools/refs';
|
|
20
15
|
import { readRefs } from '@prisma-next/migration-tools/refs';
|
|
@@ -47,16 +42,19 @@ import {
|
|
|
47
42
|
loadContractRawSafely,
|
|
48
43
|
refusePackageCorruptionOnAggregate,
|
|
49
44
|
} from '../utils/contract-space-aggregate-loader';
|
|
50
|
-
import { buildMigrationGraphLayout } from '../utils/formatters/migration-graph-layout';
|
|
51
|
-
import { buildMigrationGraphRows } from '../utils/formatters/migration-graph-rows';
|
|
52
45
|
import {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
computeGlobalMaxDirNameWidth,
|
|
47
|
+
computeGlobalMaxEdgeTreePrefixWidth,
|
|
48
|
+
indentMigrationGraphTreeBlock,
|
|
49
|
+
renderMigrationGraphSpaceTree,
|
|
50
|
+
} from '../utils/formatters/migration-graph-space-render';
|
|
51
|
+
import type { MigrationEdgeAnnotation } from '../utils/formatters/migration-graph-tree-render';
|
|
52
|
+
import { renderMigrationGraphLegend } from '../utils/formatters/migration-graph-tree-render';
|
|
56
53
|
import type { MigrationListEntry } from '../utils/formatters/migration-list-types';
|
|
57
54
|
import { formatStyledHeader } from '../utils/formatters/styled';
|
|
58
55
|
import type { CommonCommandOptions } from '../utils/global-flags';
|
|
59
56
|
import { type GlobalFlags, parseGlobalFlagsOrExit } from '../utils/global-flags';
|
|
57
|
+
import { shouldShowLegend, validateLegendOptions } from '../utils/legend';
|
|
60
58
|
import type { StatusDiagnostic } from '../utils/migration-types';
|
|
61
59
|
import { handleResult } from '../utils/result-handler';
|
|
62
60
|
import { createTerminalUI, type TerminalUI } from '../utils/terminal-ui';
|
|
@@ -77,6 +75,7 @@ interface MigrationStatusOptions extends CommonCommandOptions {
|
|
|
77
75
|
readonly to?: string;
|
|
78
76
|
readonly from?: string;
|
|
79
77
|
readonly space?: string;
|
|
78
|
+
readonly legend?: boolean;
|
|
80
79
|
}
|
|
81
80
|
|
|
82
81
|
export interface MigrationStatusMigrationEntry extends MigrationListEntry {
|
|
@@ -112,26 +111,8 @@ function shortDisplayHash(hash: string): string {
|
|
|
112
111
|
return stripped.slice(0, 12);
|
|
113
112
|
}
|
|
114
113
|
|
|
115
|
-
function
|
|
116
|
-
|
|
117
|
-
contractHash: string,
|
|
118
|
-
activeRefHash: string | undefined,
|
|
119
|
-
): string | undefined {
|
|
120
|
-
const graph = member.graph();
|
|
121
|
-
if (activeRefHash !== undefined && graph.nodes.has(activeRefHash)) {
|
|
122
|
-
return activeRefHash;
|
|
123
|
-
}
|
|
124
|
-
if (graph.nodes.has(contractHash)) {
|
|
125
|
-
return contractHash;
|
|
126
|
-
}
|
|
127
|
-
if (graph.nodes.size === 0) {
|
|
128
|
-
return requireHeadRef(member).hash;
|
|
129
|
-
}
|
|
130
|
-
const leaves = findReachableLeaves(graph, EMPTY_CONTRACT_HASH);
|
|
131
|
-
if (leaves.length === 1) {
|
|
132
|
-
return leaves[0];
|
|
133
|
-
}
|
|
134
|
-
return undefined;
|
|
114
|
+
function resolveTarget(contractHash: string, activeRefHash: string | undefined): string {
|
|
115
|
+
return activeRefHash ?? contractHash;
|
|
135
116
|
}
|
|
136
117
|
|
|
137
118
|
function buildStatusMigrations(
|
|
@@ -146,28 +127,35 @@ function buildStatusMigrations(
|
|
|
146
127
|
|
|
147
128
|
function renderSpaceTree(args: {
|
|
148
129
|
readonly member: ContractSpaceMember;
|
|
149
|
-
readonly
|
|
130
|
+
readonly liveContractHash: string;
|
|
131
|
+
readonly migrations: readonly MigrationListEntry[];
|
|
150
132
|
readonly markerHash: string | undefined;
|
|
151
133
|
readonly showDbMarker: boolean;
|
|
152
|
-
readonly
|
|
153
|
-
readonly edgeAnnotations: ReadonlyMap<string, MigrationEdgeAnnotation>;
|
|
134
|
+
readonly statusOverlay: ReadonlyMap<string, MigrationEdgeAnnotation>;
|
|
154
135
|
readonly colorize: boolean;
|
|
155
136
|
readonly glyphMode: 'unicode' | 'ascii';
|
|
137
|
+
readonly globalMaxEdgeTreePrefixWidth?: number;
|
|
138
|
+
readonly globalMaxDirNameWidth?: number;
|
|
156
139
|
}): string {
|
|
157
140
|
const graph = args.member.graph();
|
|
158
141
|
if (graph.nodes.size === 0) {
|
|
159
142
|
return '';
|
|
160
143
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
refsByHash,
|
|
166
|
-
|
|
167
|
-
contractHash: args.contractHash,
|
|
168
|
-
edgeAnnotationsByHash: args.edgeAnnotations,
|
|
144
|
+
return renderMigrationGraphSpaceTree({
|
|
145
|
+
graph,
|
|
146
|
+
migrations: args.migrations,
|
|
147
|
+
liveContractHash: args.liveContractHash,
|
|
148
|
+
refsByHash: listRefsByContractHash(args.member),
|
|
149
|
+
statusOverlayByHash: args.statusOverlay,
|
|
169
150
|
colorize: args.colorize,
|
|
170
151
|
glyphMode: args.glyphMode,
|
|
152
|
+
...(args.showDbMarker && args.markerHash !== undefined ? { dbHash: args.markerHash } : {}),
|
|
153
|
+
...(args.globalMaxEdgeTreePrefixWidth !== undefined
|
|
154
|
+
? { globalMaxEdgeTreePrefixWidth: args.globalMaxEdgeTreePrefixWidth }
|
|
155
|
+
: {}),
|
|
156
|
+
...(args.globalMaxDirNameWidth !== undefined
|
|
157
|
+
? { globalMaxDirNameWidth: args.globalMaxDirNameWidth }
|
|
158
|
+
: {}),
|
|
171
159
|
});
|
|
172
160
|
}
|
|
173
161
|
|
|
@@ -175,6 +163,27 @@ function countPending(migrations: readonly MigrationStatusMigrationEntry[]): num
|
|
|
175
163
|
return migrations.filter((m) => m.status === 'pending').length;
|
|
176
164
|
}
|
|
177
165
|
|
|
166
|
+
export function buildNoPathSummary(args: {
|
|
167
|
+
readonly markerHash: string | undefined;
|
|
168
|
+
readonly targetHash: string;
|
|
169
|
+
readonly explicitTarget: boolean;
|
|
170
|
+
readonly refName: string | undefined;
|
|
171
|
+
}): string {
|
|
172
|
+
const markerPart =
|
|
173
|
+
args.markerHash !== undefined
|
|
174
|
+
? `the database state (${shortDisplayHash(args.markerHash)})`
|
|
175
|
+
: 'the database state';
|
|
176
|
+
const targetShort = shortDisplayHash(args.targetHash);
|
|
177
|
+
if (!args.explicitTarget) {
|
|
178
|
+
return `No migration path from ${markerPart} to the application's contract (${targetShort}). Run \`prisma-next migration plan --name <name>\` to author one.`;
|
|
179
|
+
}
|
|
180
|
+
const targetLabel =
|
|
181
|
+
args.refName !== undefined
|
|
182
|
+
? `the target (${targetShort} via \`${args.refName}\`)`
|
|
183
|
+
: `the target (${targetShort})`;
|
|
184
|
+
return `No migration path from ${markerPart} to ${targetLabel}. Run \`prisma-next migration plan --name <name>\` to author one, or pass \`--to <contract>\` to pick a reachable target.`;
|
|
185
|
+
}
|
|
186
|
+
|
|
178
187
|
export function buildStatusHeadline(args: {
|
|
179
188
|
readonly pendingCount: number;
|
|
180
189
|
readonly targetHash: string;
|
|
@@ -185,7 +194,7 @@ export function buildStatusHeadline(args: {
|
|
|
185
194
|
return `Database marker ${shortDisplayHash(args.markerHash)} is not in the on-disk migration graph`;
|
|
186
195
|
}
|
|
187
196
|
if (args.pendingCount === 0) {
|
|
188
|
-
return '
|
|
197
|
+
return 'Up to date';
|
|
189
198
|
}
|
|
190
199
|
return `${args.pendingCount} pending — run \`prisma-next migrate --to ${shortDisplayHash(args.targetHash)}\``;
|
|
191
200
|
}
|
|
@@ -361,6 +370,15 @@ async function executeMigrationStatusCommand(
|
|
|
361
370
|
flags,
|
|
362
371
|
});
|
|
363
372
|
ui.stderr(header);
|
|
373
|
+
if (shouldShowLegend(options, flags)) {
|
|
374
|
+
ui.stderr(
|
|
375
|
+
renderMigrationGraphLegend({
|
|
376
|
+
colorize: flags.color !== false,
|
|
377
|
+
glyphMode: ui.resolveGlyphMode(false),
|
|
378
|
+
}),
|
|
379
|
+
);
|
|
380
|
+
ui.stderr('');
|
|
381
|
+
}
|
|
364
382
|
}
|
|
365
383
|
|
|
366
384
|
const listSpaces = await migrationSpaceListEntriesFromAggregate(aggregate, migrationsDir);
|
|
@@ -440,7 +458,21 @@ async function executeMigrationStatusCommand(
|
|
|
440
458
|
let markerCannotReachTarget = false;
|
|
441
459
|
let headlineTargetHash = activeRefHash ?? contractHash;
|
|
442
460
|
let totalPending = 0;
|
|
443
|
-
|
|
461
|
+
|
|
462
|
+
const globalLayoutInputs = showSpaceHeadings
|
|
463
|
+
? scopedSpaces
|
|
464
|
+
.filter((spaceEntry) => spaceEntry.migrations.length > 0)
|
|
465
|
+
.map((spaceEntry) => ({
|
|
466
|
+
graph: aggregate.space(spaceEntry.spaceId)!.graph(),
|
|
467
|
+
liveContractHash: contractHash,
|
|
468
|
+
}))
|
|
469
|
+
: [];
|
|
470
|
+
const globalMaxEdgeTreePrefixWidth =
|
|
471
|
+
globalLayoutInputs.length > 0
|
|
472
|
+
? computeGlobalMaxEdgeTreePrefixWidth(globalLayoutInputs)
|
|
473
|
+
: undefined;
|
|
474
|
+
const globalMaxDirNameWidth =
|
|
475
|
+
globalLayoutInputs.length > 0 ? computeGlobalMaxDirNameWidth(globalLayoutInputs) : undefined;
|
|
444
476
|
|
|
445
477
|
for (const spaceEntry of scopedSpaces) {
|
|
446
478
|
const member = aggregate.space(spaceEntry.spaceId);
|
|
@@ -449,20 +481,7 @@ async function executeMigrationStatusCommand(
|
|
|
449
481
|
}
|
|
450
482
|
const graph = member.graph();
|
|
451
483
|
const spaceContractHash = member.contract().storage.storageHash;
|
|
452
|
-
const targetHash =
|
|
453
|
-
if (targetHash === undefined) {
|
|
454
|
-
hasAmbiguousTarget = true;
|
|
455
|
-
diagnostics.push({
|
|
456
|
-
code: 'MIGRATION.DIVERGED',
|
|
457
|
-
severity: 'warn',
|
|
458
|
-
message: 'There are multiple valid migration paths — you must select a target',
|
|
459
|
-
hints: [
|
|
460
|
-
"Use '--to <contract>' to select a target",
|
|
461
|
-
"Or 'prisma-next ref set <name> <hash>' to create one",
|
|
462
|
-
],
|
|
463
|
-
});
|
|
464
|
-
continue;
|
|
465
|
-
}
|
|
484
|
+
const targetHash = resolveTarget(spaceContractHash, activeRefHash);
|
|
466
485
|
if (spaceEntry.spaceId === aggregate.app.spaceId) {
|
|
467
486
|
headlineTargetHash = targetHash;
|
|
468
487
|
}
|
|
@@ -477,9 +496,8 @@ async function executeMigrationStatusCommand(
|
|
|
477
496
|
if (
|
|
478
497
|
connected &&
|
|
479
498
|
!usingFromOverride &&
|
|
480
|
-
markerHash !== undefined &&
|
|
481
499
|
markerInGraph &&
|
|
482
|
-
|
|
500
|
+
originHash !== targetHash &&
|
|
483
501
|
findPath(graph, originHash, targetHash) === null
|
|
484
502
|
) {
|
|
485
503
|
markerCannotReachTarget = true;
|
|
@@ -511,13 +529,15 @@ async function executeMigrationStatusCommand(
|
|
|
511
529
|
});
|
|
512
530
|
const tree = renderSpaceTree({
|
|
513
531
|
member,
|
|
514
|
-
|
|
532
|
+
liveContractHash: contractHash,
|
|
533
|
+
migrations: spaceEntry.migrations,
|
|
515
534
|
markerHash,
|
|
516
535
|
showDbMarker,
|
|
517
|
-
|
|
518
|
-
edgeAnnotations: annotations,
|
|
536
|
+
statusOverlay: annotations,
|
|
519
537
|
colorize,
|
|
520
538
|
glyphMode,
|
|
539
|
+
...(globalMaxEdgeTreePrefixWidth !== undefined ? { globalMaxEdgeTreePrefixWidth } : {}),
|
|
540
|
+
...(globalMaxDirNameWidth !== undefined ? { globalMaxDirNameWidth } : {}),
|
|
521
541
|
});
|
|
522
542
|
const migrations = buildStatusMigrations(spaceEntry.migrations, annotations);
|
|
523
543
|
const pending = countPending(migrations);
|
|
@@ -529,9 +549,11 @@ async function executeMigrationStatusCommand(
|
|
|
529
549
|
targetHash,
|
|
530
550
|
migrations,
|
|
531
551
|
});
|
|
552
|
+
const displayTree =
|
|
553
|
+
showSpaceHeadings && tree.length > 0 ? indentMigrationGraphTreeBlock(tree, ' ') : tree;
|
|
532
554
|
treeSections.push({
|
|
533
555
|
spaceId: spaceEntry.spaceId,
|
|
534
|
-
tree,
|
|
556
|
+
tree: displayTree,
|
|
535
557
|
showHeading: showSpaceHeadings,
|
|
536
558
|
});
|
|
537
559
|
}
|
|
@@ -567,16 +589,19 @@ async function executeMigrationStatusCommand(
|
|
|
567
589
|
}
|
|
568
590
|
|
|
569
591
|
const appMarkerHash = markersBySpace.get(aggregate.app.spaceId)?.storageHash;
|
|
570
|
-
const summary =
|
|
571
|
-
?
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
592
|
+
const summary = markerCannotReachTarget
|
|
593
|
+
? buildNoPathSummary({
|
|
594
|
+
markerHash: appMarkerHash,
|
|
595
|
+
targetHash: headlineTargetHash,
|
|
596
|
+
explicitTarget: options.to !== undefined,
|
|
597
|
+
refName: activeRefName,
|
|
598
|
+
})
|
|
599
|
+
: buildStatusHeadline({
|
|
600
|
+
pendingCount: totalPending,
|
|
601
|
+
targetHash: headlineTargetHash,
|
|
602
|
+
markerDiverged,
|
|
603
|
+
markerHash: appMarkerHash,
|
|
604
|
+
});
|
|
580
605
|
|
|
581
606
|
if (scopedSpaces.every((s) => s.migrations.length === 0)) {
|
|
582
607
|
return ok({
|
|
@@ -613,6 +638,7 @@ export function createMigrationStatusCommand(): Command {
|
|
|
613
638
|
'prisma-next migration status --db $DATABASE_URL',
|
|
614
639
|
'prisma-next migration status --to production --db $DATABASE_URL',
|
|
615
640
|
'prisma-next migration status --from sha256:abc --to production',
|
|
641
|
+
'prisma-next migration status --legend --from sha256:abc --to production',
|
|
616
642
|
]);
|
|
617
643
|
setCommandSeeAlso(command, [
|
|
618
644
|
{ verb: 'migration log', oneLiner: 'Show executed migration history' },
|
|
@@ -632,10 +658,16 @@ export function createMigrationStatusCommand(): Command {
|
|
|
632
658
|
'--from <contract>',
|
|
633
659
|
'Origin contract reference; same grammar as --to. Supplying --from switches to offline path computation.',
|
|
634
660
|
)
|
|
661
|
+
.option('--legend', 'Print a key for the tree glyphs and lane colors')
|
|
635
662
|
.action(async (options: MigrationStatusOptions) => {
|
|
636
663
|
const flags = parseGlobalFlagsOrExit(options);
|
|
637
664
|
const ui = createTerminalUI(flags);
|
|
638
665
|
|
|
666
|
+
const legendValidation = validateLegendOptions(options, flags);
|
|
667
|
+
if (!legendValidation.ok) {
|
|
668
|
+
process.exit(handleResult(legendValidation, flags, ui));
|
|
669
|
+
}
|
|
670
|
+
|
|
639
671
|
const result = await executeMigrationStatusCommand(options, flags, ui);
|
|
640
672
|
|
|
641
673
|
const exitCode = handleResult(result, flags, ui, (statusResult) => {
|
|
@@ -33,7 +33,12 @@ import type {
|
|
|
33
33
|
OnControlProgress,
|
|
34
34
|
PerSpaceExecutionEntry,
|
|
35
35
|
} from '../types';
|
|
36
|
-
import {
|
|
36
|
+
import {
|
|
37
|
+
applyMigration,
|
|
38
|
+
buildPerSpaceBreakdown,
|
|
39
|
+
collectOrdered,
|
|
40
|
+
type OrderedResolution,
|
|
41
|
+
} from './apply';
|
|
37
42
|
import { stripOperations } from './migration-helpers';
|
|
38
43
|
|
|
39
44
|
/**
|
|
@@ -255,9 +260,7 @@ export async function executeApply<TFamilyId extends string, TTargetId extends s
|
|
|
255
260
|
}
|
|
256
261
|
|
|
257
262
|
function aggregatePlannerWarnings(
|
|
258
|
-
orderedResolutions:
|
|
259
|
-
readonly entry: { readonly warnings?: readonly MigrationPlannerConflict[] };
|
|
260
|
-
}>,
|
|
263
|
+
orderedResolutions: readonly OrderedResolution[],
|
|
261
264
|
): readonly MigrationPlannerConflict[] | undefined {
|
|
262
265
|
const warnings = orderedResolutions.flatMap((r) => r.entry.warnings ?? []);
|
|
263
266
|
return warnings.length > 0 ? warnings : undefined;
|
package/src/utils/cli-errors.ts
CHANGED
|
@@ -108,6 +108,23 @@ export function errorRefSetEmptySentinel(hash: string): CliStructuredError {
|
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* `--legend` was combined with a machine-readable or silent output flag.
|
|
113
|
+
* The legend is human-only decoration on stderr.
|
|
114
|
+
*/
|
|
115
|
+
export function errorLegendHumanOnly(
|
|
116
|
+
conflictingFlag: '--json' | '--dot' | '--quiet',
|
|
117
|
+
): CliStructuredError {
|
|
118
|
+
return errorRuntime('`--legend` is only available for human-readable output', {
|
|
119
|
+
why: `\`--legend\` prints a glyph key to stderr and cannot be combined with ${conflictingFlag}.`,
|
|
120
|
+
fix: `Omit ${conflictingFlag} to print the legend alongside the tree, or omit --legend when using ${conflictingFlag}.`,
|
|
121
|
+
meta: {
|
|
122
|
+
code: 'MIGRATION.LEGEND_HUMAN_ONLY',
|
|
123
|
+
conflictingFlag,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
111
128
|
/**
|
|
112
129
|
* `--space <id>` was given a value that doesn't satisfy the contract-space
|
|
113
130
|
* naming rule (`[a-z][a-z0-9_-]{0,63}` per `isValidSpaceId`). Fires before
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createColors } from 'colorette';
|
|
2
|
+
import type { StructuralCell } from './migration-graph-layout';
|
|
2
3
|
|
|
3
4
|
export type LaneColorizer = (text: string) => string;
|
|
4
5
|
|
|
@@ -13,6 +14,12 @@ export const LANE_COLOR_CYCLE: readonly LaneColorizer[] = [
|
|
|
13
14
|
red,
|
|
14
15
|
];
|
|
15
16
|
|
|
17
|
+
/**
|
|
18
|
+
* The leftmost lane (column 0) renders neutral — no palette hue. Columns ≥ 1
|
|
19
|
+
* rotate through {@link LANE_COLOR_CYCLE}.
|
|
20
|
+
*/
|
|
21
|
+
export const NEUTRAL_LANE_COLUMN = 0;
|
|
22
|
+
|
|
16
23
|
/**
|
|
17
24
|
* The hue for a gutter column. The leftmost lane (column 0) is **neutral** — it
|
|
18
25
|
* has nothing to be told apart from in the common single-lane linear case, so
|
|
@@ -23,9 +30,165 @@ export const LANE_COLOR_CYCLE: readonly LaneColorizer[] = [
|
|
|
23
30
|
* branch identity, exactly like `git log --graph`.
|
|
24
31
|
*/
|
|
25
32
|
export function laneColorForColumn(column: number): LaneColorizer {
|
|
26
|
-
if (column <=
|
|
33
|
+
if (column <= NEUTRAL_LANE_COLUMN) {
|
|
27
34
|
return (text) => text;
|
|
28
35
|
}
|
|
29
36
|
const colorizer = LANE_COLOR_CYCLE[(column - 1) % LANE_COLOR_CYCLE.length];
|
|
30
37
|
return colorizer ?? ((text) => text);
|
|
31
38
|
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Style a structural glyph by its resolved colour column. Column 0 and the
|
|
42
|
+
* neutral sentinel render dim (`dimLane`); columns ≥ 1 take a palette hue.
|
|
43
|
+
*/
|
|
44
|
+
export function stylerForLaneColumn(
|
|
45
|
+
colorColumn: number,
|
|
46
|
+
colorize: boolean,
|
|
47
|
+
dimLane: (text: string) => string,
|
|
48
|
+
): LaneColorizer {
|
|
49
|
+
if (!colorize || colorColumn <= NEUTRAL_LANE_COLUMN) {
|
|
50
|
+
return dimLane;
|
|
51
|
+
}
|
|
52
|
+
return laneColorForColumn(colorColumn);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The colour-source column for each cell of a row, resolved together because a
|
|
57
|
+
* routed back-arc spans columns and must read as **one hue** rather than a
|
|
58
|
+
* per-column "rainbow".
|
|
59
|
+
*/
|
|
60
|
+
export interface RowArcLaneColors {
|
|
61
|
+
/** Colour column for a cell's structural glyph (lane / spine / arc body). */
|
|
62
|
+
readonly lane: readonly number[];
|
|
63
|
+
/** Colour column for a node arc-pair's connector half (`◂` / `─`). */
|
|
64
|
+
readonly connector: readonly number[];
|
|
65
|
+
/**
|
|
66
|
+
* Colour column for the trailing `─` of a landing tee (`┴─`). The junction
|
|
67
|
+
* (`lane`) keeps its own column; the dash leads into the next converging arc.
|
|
68
|
+
*/
|
|
69
|
+
readonly dash: readonly number[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Resolve per-cell colour columns for a node/arc row. Scanning right-to-left
|
|
74
|
+
* lets each arc segment inherit the hue of the arc it leads into.
|
|
75
|
+
*/
|
|
76
|
+
export function resolveRowArcLaneColors(cells: readonly StructuralCell[]): RowArcLaneColors {
|
|
77
|
+
const lane = new Array<number>(cells.length);
|
|
78
|
+
const connector = new Array<number>(cells.length);
|
|
79
|
+
const dash = new Array<number>(cells.length);
|
|
80
|
+
let arcCorner = NEUTRAL_LANE_COLUMN;
|
|
81
|
+
let landingAnchor = NEUTRAL_LANE_COLUMN;
|
|
82
|
+
for (let column = cells.length - 1; column >= 0; column--) {
|
|
83
|
+
const cell = cells[column];
|
|
84
|
+
connector[column] = landingAnchor !== NEUTRAL_LANE_COLUMN ? landingAnchor : arcCorner;
|
|
85
|
+
switch (cell?.kind) {
|
|
86
|
+
case 'arc-branch-corner':
|
|
87
|
+
arcCorner = column;
|
|
88
|
+
lane[column] = column;
|
|
89
|
+
dash[column] = column;
|
|
90
|
+
break;
|
|
91
|
+
case 'arc-land-corner':
|
|
92
|
+
arcCorner = column;
|
|
93
|
+
landingAnchor = column;
|
|
94
|
+
lane[column] = column;
|
|
95
|
+
dash[column] = column;
|
|
96
|
+
break;
|
|
97
|
+
case 'arc-branch-tee':
|
|
98
|
+
lane[column] = column;
|
|
99
|
+
dash[column] = column;
|
|
100
|
+
break;
|
|
101
|
+
case 'arc-land-tee':
|
|
102
|
+
lane[column] = column;
|
|
103
|
+
dash[column] = landingAnchor === NEUTRAL_LANE_COLUMN ? column : landingAnchor;
|
|
104
|
+
landingAnchor = column;
|
|
105
|
+
break;
|
|
106
|
+
case 'arc-crossing':
|
|
107
|
+
case 'arc-land-bridge': {
|
|
108
|
+
const served = landingAnchor !== NEUTRAL_LANE_COLUMN ? landingAnchor : arcCorner;
|
|
109
|
+
lane[column] = served;
|
|
110
|
+
dash[column] = served;
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case 'horizontal-pass':
|
|
114
|
+
lane[column] = arcCorner === NEUTRAL_LANE_COLUMN ? column : arcCorner;
|
|
115
|
+
dash[column] = lane[column] ?? column;
|
|
116
|
+
break;
|
|
117
|
+
case 'node':
|
|
118
|
+
lane[column] = column;
|
|
119
|
+
dash[column] = column;
|
|
120
|
+
arcCorner = NEUTRAL_LANE_COLUMN;
|
|
121
|
+
landingAnchor = NEUTRAL_LANE_COLUMN;
|
|
122
|
+
break;
|
|
123
|
+
default:
|
|
124
|
+
lane[column] = column;
|
|
125
|
+
dash[column] = column;
|
|
126
|
+
arcCorner = NEUTRAL_LANE_COLUMN;
|
|
127
|
+
landingAnchor = NEUTRAL_LANE_COLUMN;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return { lane, connector, dash };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Per-cell colour for a forward branch/merge connector row, split into the
|
|
135
|
+
* cell's junction `glyph` and its trailing `dash`.
|
|
136
|
+
*/
|
|
137
|
+
export interface ConnectorLaneColors {
|
|
138
|
+
/** Colour column for a cell's junction glyph (`├` / `┬` / `┴` / `╮` / `╯`). */
|
|
139
|
+
readonly glyph: readonly number[];
|
|
140
|
+
/** Colour column for a tee's trailing `─` — the branch it leads into. */
|
|
141
|
+
readonly dash: readonly number[];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Resolve per-cell connector colours. Scanning right-to-left, a corner or an
|
|
146
|
+
* intermediate tee anchors its own lane, but a tee's trailing dash leads into
|
|
147
|
+
* the branch on its right.
|
|
148
|
+
*/
|
|
149
|
+
export function resolveConnectorLaneColors(
|
|
150
|
+
cells: readonly StructuralCell[],
|
|
151
|
+
startLane: number,
|
|
152
|
+
): ConnectorLaneColors {
|
|
153
|
+
const glyph = new Array<number>(cells.length);
|
|
154
|
+
const dash = new Array<number>(cells.length);
|
|
155
|
+
let owner = NEUTRAL_LANE_COLUMN;
|
|
156
|
+
for (let column = cells.length - 1; column >= 0; column--) {
|
|
157
|
+
const cell = cells[column];
|
|
158
|
+
switch (cell?.kind) {
|
|
159
|
+
case 'branch-corner':
|
|
160
|
+
case 'merge-corner':
|
|
161
|
+
owner = column;
|
|
162
|
+
glyph[column] = column;
|
|
163
|
+
dash[column] = column;
|
|
164
|
+
break;
|
|
165
|
+
case 'branch-tee':
|
|
166
|
+
case 'merge-tee':
|
|
167
|
+
if (column === startLane) {
|
|
168
|
+
const served = owner === NEUTRAL_LANE_COLUMN ? column : owner;
|
|
169
|
+
glyph[column] = column;
|
|
170
|
+
dash[column] = served;
|
|
171
|
+
} else {
|
|
172
|
+
dash[column] = owner === NEUTRAL_LANE_COLUMN ? column : owner;
|
|
173
|
+
glyph[column] = column;
|
|
174
|
+
owner = column;
|
|
175
|
+
}
|
|
176
|
+
break;
|
|
177
|
+
case 'arc-crossing':
|
|
178
|
+
glyph[column] = column;
|
|
179
|
+
dash[column] = owner === NEUTRAL_LANE_COLUMN ? column : owner;
|
|
180
|
+
owner = column;
|
|
181
|
+
break;
|
|
182
|
+
case 'horizontal-pass': {
|
|
183
|
+
const served = owner === NEUTRAL_LANE_COLUMN ? column : owner;
|
|
184
|
+
glyph[column] = served;
|
|
185
|
+
dash[column] = served;
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
default:
|
|
189
|
+
glyph[column] = column;
|
|
190
|
+
dash[column] = column;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return { glyph, dash };
|
|
194
|
+
}
|