prisma-next 0.12.0-dev.22 → 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 +6 -6
- package/dist/commands/migrate.d.mts +1 -1
- package/dist/commands/migration-graph.d.mts +4 -4
- package/dist/commands/migration-graph.mjs +164 -1
- package/dist/commands/migration-graph.mjs.map +1 -0
- package/dist/commands/migration-list.d.mts +3 -3
- 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 +3 -3
- package/dist/commands/migration-log.mjs +1 -1
- 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-status.d.mts +1 -1
- package/dist/commands/ref.d.mts +1 -1
- package/dist/commands/telemetry/index.mjs +1 -1
- package/dist/exports/control-api.d.mts +1 -1
- package/dist/exports/init-output.mjs +1 -1
- package/dist/{global-flags-DSkV6iYT.d.mts → global-flags-DG4uY5tV.d.mts} +1 -1
- package/dist/{global-flags-DSkV6iYT.d.mts.map → global-flags-DG4uY5tV.d.mts.map} +1 -1
- package/dist/{init-S2vxszo_.mjs → init-B6kKrmf7.mjs} +2 -2
- package/dist/{init-S2vxszo_.mjs.map → init-B6kKrmf7.mjs.map} +1 -1
- package/dist/{migration-graph-CeBB07Cc.mjs → migration-graph-tree-render-BQdhKBO8.mjs} +405 -165
- package/dist/migration-graph-tree-render-BQdhKBO8.mjs.map +1 -0
- package/dist/{migration-log-Cj-T-r0o.mjs → migration-log-BzPmks3c.mjs} +2 -2
- package/dist/{migration-log-Cj-T-r0o.mjs.map → migration-log-BzPmks3c.mjs.map} +1 -1
- package/dist/{migration-plan-BQAbZkj_.mjs → migration-plan-CaeKCKp4.mjs} +1 -1
- package/dist/{migration-plan-BQAbZkj_.mjs.map → migration-plan-CaeKCKp4.mjs.map} +1 -1
- package/dist/{migration-types-Bhmj0RSa.d.mts → migration-types-CAQ-0TEE.d.mts} +1 -1
- package/dist/{migration-types-Bhmj0RSa.d.mts.map → migration-types-CAQ-0TEE.d.mts.map} +1 -1
- package/dist/{output-BD61elic.mjs → output-CF_hqzI-.mjs} +1 -1
- package/dist/{output-BD61elic.mjs.map → output-CF_hqzI-.mjs.map} +1 -1
- package/dist/{telemetry-Bu85x2Gy.mjs → telemetry-Q88WHwlv.mjs} +1 -1
- package/dist/{telemetry-Bu85x2Gy.mjs.map → telemetry-Q88WHwlv.mjs.map} +1 -1
- package/dist/{terminal-ui-BgLiAOYi.d.mts → terminal-ui-C3xGyxW-.d.mts} +1 -1
- package/dist/{terminal-ui-BgLiAOYi.d.mts.map → terminal-ui-C3xGyxW-.d.mts.map} +1 -1
- package/dist/{types-C8OcDFBe.d.mts → types-DiC683UW.d.mts} +1 -1
- package/dist/{types-C8OcDFBe.d.mts.map → types-DiC683UW.d.mts.map} +1 -1
- package/package.json +11 -11
- package/dist/migration-graph-CeBB07Cc.mjs.map +0 -1
- package/dist/migration-list-styler-CsMECsY4.mjs +0 -414
- package/dist/migration-list-styler-CsMECsY4.mjs.map +0 -1
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { E as formatStyledHeader, _ as parseGlobalFlagsOrExit, b as handleResult, d as setCommandSeeAlso, l as setCommandDescriptions, s as resolveMigrationPaths, t as addGlobalOptions, u as setCommandExamples, v as createTerminalUI } from "./command-helpers-Cmdqyhz9.mjs";
|
|
3
|
-
import { r as buildReadAggregate } from "./contract-space-aggregate-loader-CirAEsM8.mjs";
|
|
4
|
-
import { i as migrationGraphToRenderInput, n as graphRenderer } from "./graph-render-rFAqZujX.mjs";
|
|
5
|
-
import { c as migrationListForwardArrow, l as classifyMigrationGraphTopology, n as createAnsiMigrationListStyler, s as migrationListEmptySource, t as CONTRACT_MARKER_NAME } from "./migration-list-styler-CsMECsY4.mjs";
|
|
6
|
-
import { Command } from "commander";
|
|
7
|
-
import { ok } from "@prisma-next/utils/result";
|
|
8
|
-
import { bold, createColors } from "colorette";
|
|
1
|
+
import { bold, createColors, cyan, cyanBright, dim, green, yellow } from "colorette";
|
|
9
2
|
import stringWidth from "string-width";
|
|
10
3
|
import { EMPTY_CONTRACT_HASH } from "@prisma-next/migration-tools/constants";
|
|
11
4
|
//#region src/utils/formatters/migration-graph-layout.ts
|
|
@@ -655,6 +648,224 @@ function buildMigrationGraphLayout(rowModel) {
|
|
|
655
648
|
};
|
|
656
649
|
}
|
|
657
650
|
//#endregion
|
|
651
|
+
//#region src/utils/formatters/migration-list-graph-topology.ts
|
|
652
|
+
function compareDirNameDesc(a, b) {
|
|
653
|
+
return b.dirName.localeCompare(a.dirName);
|
|
654
|
+
}
|
|
655
|
+
function bumpDegree(map, key) {
|
|
656
|
+
map.set(key, (map.get(key) ?? 0) + 1);
|
|
657
|
+
}
|
|
658
|
+
function compareNodesRootFirst(a, b) {
|
|
659
|
+
if (a === EMPTY_CONTRACT_HASH) return -1;
|
|
660
|
+
if (b === EMPTY_CONTRACT_HASH) return 1;
|
|
661
|
+
return a.localeCompare(b);
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Shortest-path distance of each node from the forward roots, over the given
|
|
665
|
+
* candidate edges. Roots are the in-degree-0 nodes (baseline first, then lex);
|
|
666
|
+
* a rooted component therefore distances every node by how many forward steps
|
|
667
|
+
* it sits from a root. A component with no root (a pure cycle) is seeded from
|
|
668
|
+
* its single lexically-smallest node so the cycle still gets a stable layering.
|
|
669
|
+
*
|
|
670
|
+
* Crucially this is *shortest* path, not longest: a backward (rollback) edge
|
|
671
|
+
* `deep → shallow` never offers a shorter route to the already-shallower
|
|
672
|
+
* target, so it is inert here. Distances are thus stable whether or not the
|
|
673
|
+
* rollbacks are still in the candidate set — which is what lets the peel below
|
|
674
|
+
* tell a genuine back-edge (target strictly shallower than source) apart from a
|
|
675
|
+
* forward edge that merely happens to share the back-edge's cycle.
|
|
676
|
+
*/
|
|
677
|
+
function forwardDistances(nodes, candidates) {
|
|
678
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
679
|
+
for (const node of nodes) inDegree.set(node, 0);
|
|
680
|
+
for (const edge of candidates) bumpDegree(inDegree, edge.to);
|
|
681
|
+
const roots = [...nodes].filter((node) => (inDegree.get(node) ?? 0) === 0);
|
|
682
|
+
roots.sort(compareNodesRootFirst);
|
|
683
|
+
const seeds = roots.length > 0 ? roots : [...nodes].sort(compareNodesRootFirst).slice(0, 1);
|
|
684
|
+
const dist = /* @__PURE__ */ new Map();
|
|
685
|
+
for (const seed of seeds) dist.set(seed, 0);
|
|
686
|
+
const maxPasses = nodes.size;
|
|
687
|
+
for (let pass = 0; pass < maxPasses; pass++) {
|
|
688
|
+
let changed = false;
|
|
689
|
+
for (const edge of candidates) {
|
|
690
|
+
const base = dist.get(edge.from);
|
|
691
|
+
if (base === void 0) continue;
|
|
692
|
+
const next = base + 1;
|
|
693
|
+
if (next < (dist.get(edge.to) ?? Number.POSITIVE_INFINITY)) {
|
|
694
|
+
dist.set(edge.to, next);
|
|
695
|
+
changed = true;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
if (!changed) break;
|
|
699
|
+
}
|
|
700
|
+
for (const node of nodes) if (!dist.has(node)) dist.set(node, 0);
|
|
701
|
+
return dist;
|
|
702
|
+
}
|
|
703
|
+
function canReachForward(start, goal, candidates) {
|
|
704
|
+
if (start === goal) return true;
|
|
705
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
706
|
+
for (const edge of candidates) {
|
|
707
|
+
const bucket = outgoing.get(edge.from);
|
|
708
|
+
if (bucket) bucket.push(edge.to);
|
|
709
|
+
else outgoing.set(edge.from, [edge.to]);
|
|
710
|
+
}
|
|
711
|
+
const visited = new Set([start]);
|
|
712
|
+
const queue = [start];
|
|
713
|
+
while (queue.length > 0) {
|
|
714
|
+
const node = queue.shift();
|
|
715
|
+
if (node === void 0) continue;
|
|
716
|
+
for (const next of outgoing.get(node) ?? []) {
|
|
717
|
+
if (next === goal) return true;
|
|
718
|
+
if (!visited.has(next)) {
|
|
719
|
+
visited.add(next);
|
|
720
|
+
queue.push(next);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
return false;
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Demote node-skipping rollbacks left forward by the DFS. An edge `from → to`
|
|
728
|
+
* is a rollback exactly when both hold:
|
|
729
|
+
* 1. `to` is a forward-ancestor of `from` — `to` can still reach `from` over
|
|
730
|
+
* the other forward edges, so the edge closes a cycle; and
|
|
731
|
+
* 2. `to` is strictly shallower than `from` (smaller forward distance) — the
|
|
732
|
+
* edge points back toward the root rather than advancing history.
|
|
733
|
+
*
|
|
734
|
+
* Condition 2 is the discriminator: in a cycle created by a rollback every edge
|
|
735
|
+
* satisfies condition 1, but only the rollback itself runs deep → shallow. The
|
|
736
|
+
* forward chain edges run shallow → deep and are never peeled, however many
|
|
737
|
+
* rollbacks converge on the same target. Tight back-edges whose source and
|
|
738
|
+
* target sit at the same distance (mutual two-node cycles) are already resolved
|
|
739
|
+
* by the DFS immediate-parent rule, so they never reach this pass. One edge is
|
|
740
|
+
* peeled per iteration (dirName-descending tie-break) and distances/reachability
|
|
741
|
+
* are recomputed, making the outcome independent of edge input order.
|
|
742
|
+
*/
|
|
743
|
+
function peelNodeSkippingRollbacks(nodes, kindByMigrationHash, nonSelf) {
|
|
744
|
+
let candidates = nonSelf.filter((edge) => kindByMigrationHash.get(edge.hash) === "forward");
|
|
745
|
+
while (candidates.length > 0) {
|
|
746
|
+
const dist = forwardDistances(nodes, candidates);
|
|
747
|
+
const backEdges = candidates.filter((edge) => {
|
|
748
|
+
if ((dist.get(edge.to) ?? 0) >= (dist.get(edge.from) ?? 0)) return false;
|
|
749
|
+
const without = candidates.filter((candidate) => candidate !== edge);
|
|
750
|
+
return canReachForward(edge.to, edge.from, without);
|
|
751
|
+
});
|
|
752
|
+
if (backEdges.length === 0) break;
|
|
753
|
+
backEdges.sort(compareDirNameDesc);
|
|
754
|
+
const rollback = backEdges[0];
|
|
755
|
+
if (rollback === void 0) break;
|
|
756
|
+
kindByMigrationHash.set(rollback.hash, "rollback");
|
|
757
|
+
candidates = candidates.filter((edge) => edge !== rollback);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* DFS with dirName-descending traversal. A GRAY target is a rollback only when it
|
|
762
|
+
* is the immediate DFS parent of the source — cross-links to other GRAY nodes
|
|
763
|
+
* stay forward. A follow-up peel pass demotes node-skipping rollbacks (target is
|
|
764
|
+
* a forward-ancestor of the source and sits strictly shallower than it).
|
|
765
|
+
*/
|
|
766
|
+
function classifyNormalizedEdges(edges) {
|
|
767
|
+
const nodes = /* @__PURE__ */ new Set();
|
|
768
|
+
const kindByMigrationHash = /* @__PURE__ */ new Map();
|
|
769
|
+
const outgoingByFrom = /* @__PURE__ */ new Map();
|
|
770
|
+
const nonSelf = [];
|
|
771
|
+
for (const edge of edges) {
|
|
772
|
+
nodes.add(edge.from);
|
|
773
|
+
nodes.add(edge.to);
|
|
774
|
+
if (edge.from === edge.to) {
|
|
775
|
+
kindByMigrationHash.set(edge.hash, "self");
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
nonSelf.push(edge);
|
|
779
|
+
const bucket = outgoingByFrom.get(edge.from);
|
|
780
|
+
if (bucket) bucket.push(edge);
|
|
781
|
+
else outgoingByFrom.set(edge.from, [edge]);
|
|
782
|
+
}
|
|
783
|
+
for (const bucket of outgoingByFrom.values()) bucket.sort(compareDirNameDesc);
|
|
784
|
+
const nonSelfInDegree = /* @__PURE__ */ new Map();
|
|
785
|
+
for (const node of nodes) nonSelfInDegree.set(node, 0);
|
|
786
|
+
for (const bucket of outgoingByFrom.values()) for (const edge of bucket) bumpDegree(nonSelfInDegree, edge.to);
|
|
787
|
+
const dfsRoots = [];
|
|
788
|
+
for (const node of nodes) if ((nonSelfInDegree.get(node) ?? 0) === 0) dfsRoots.push(node);
|
|
789
|
+
dfsRoots.sort((a, b) => {
|
|
790
|
+
if (a === EMPTY_CONTRACT_HASH) return -1;
|
|
791
|
+
if (b === EMPTY_CONTRACT_HASH) return 1;
|
|
792
|
+
return a.localeCompare(b);
|
|
793
|
+
});
|
|
794
|
+
if (dfsRoots.length === 0) dfsRoots.push(...[...nodes].sort((a, b) => a.localeCompare(b)));
|
|
795
|
+
const WHITE = 0;
|
|
796
|
+
const GRAY = 1;
|
|
797
|
+
const BLACK = 2;
|
|
798
|
+
const color = /* @__PURE__ */ new Map();
|
|
799
|
+
const dfsParent = /* @__PURE__ */ new Map();
|
|
800
|
+
for (const node of nodes) color.set(node, WHITE);
|
|
801
|
+
const stack = [];
|
|
802
|
+
function isImmediateDfsParent(ancestor, node) {
|
|
803
|
+
return dfsParent.get(node) === ancestor;
|
|
804
|
+
}
|
|
805
|
+
function pushFrame(node, parent) {
|
|
806
|
+
color.set(node, GRAY);
|
|
807
|
+
dfsParent.set(node, parent);
|
|
808
|
+
stack.push({
|
|
809
|
+
node,
|
|
810
|
+
outgoing: outgoingByFrom.get(node) ?? [],
|
|
811
|
+
index: 0
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
function runDfsFrom(root) {
|
|
815
|
+
if (color.get(root) !== WHITE) return;
|
|
816
|
+
pushFrame(root, void 0);
|
|
817
|
+
while (stack.length > 0) {
|
|
818
|
+
const frame = stack[stack.length - 1];
|
|
819
|
+
if (frame === void 0) break;
|
|
820
|
+
if (frame.index >= frame.outgoing.length) {
|
|
821
|
+
color.set(frame.node, BLACK);
|
|
822
|
+
stack.pop();
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
const edge = frame.outgoing[frame.index];
|
|
826
|
+
frame.index += 1;
|
|
827
|
+
if (edge === void 0) continue;
|
|
828
|
+
const v = edge.to;
|
|
829
|
+
const vColor = color.get(v);
|
|
830
|
+
if (vColor === GRAY && isImmediateDfsParent(v, frame.node)) kindByMigrationHash.set(edge.hash, "rollback");
|
|
831
|
+
else {
|
|
832
|
+
kindByMigrationHash.set(edge.hash, "forward");
|
|
833
|
+
if (vColor === WHITE) pushFrame(v, frame.node);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
for (const root of dfsRoots) runDfsFrom(root);
|
|
838
|
+
const remainingWhite = [...nodes].filter((node) => color.get(node) === WHITE);
|
|
839
|
+
remainingWhite.sort((a, b) => a.localeCompare(b));
|
|
840
|
+
for (const root of remainingWhite) runDfsFrom(root);
|
|
841
|
+
peelNodeSkippingRollbacks(nodes, kindByMigrationHash, nonSelf);
|
|
842
|
+
const forwardInDegree = /* @__PURE__ */ new Map();
|
|
843
|
+
const forwardOutDegree = /* @__PURE__ */ new Map();
|
|
844
|
+
for (const edge of edges) {
|
|
845
|
+
if (kindByMigrationHash.get(edge.hash) !== "forward") continue;
|
|
846
|
+
bumpDegree(forwardOutDegree, edge.from);
|
|
847
|
+
bumpDegree(forwardInDegree, edge.to);
|
|
848
|
+
}
|
|
849
|
+
return {
|
|
850
|
+
kindByMigrationHash,
|
|
851
|
+
forwardInDegree,
|
|
852
|
+
forwardOutDegree
|
|
853
|
+
};
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Classify forward/rollback/self for a `MigrationGraph` edge set (Tier-3).
|
|
857
|
+
*/
|
|
858
|
+
function classifyMigrationGraphTopology(graph) {
|
|
859
|
+
const normalized = [];
|
|
860
|
+
for (const edges of graph.forwardChain.values()) for (const edge of edges) normalized.push({
|
|
861
|
+
hash: edge.migrationHash,
|
|
862
|
+
from: edge.from,
|
|
863
|
+
to: edge.to,
|
|
864
|
+
dirName: edge.dirName
|
|
865
|
+
});
|
|
866
|
+
return classifyNormalizedEdges(normalized);
|
|
867
|
+
}
|
|
868
|
+
//#endregion
|
|
658
869
|
//#region src/utils/formatters/migration-graph-rows.ts
|
|
659
870
|
/**
|
|
660
871
|
* Return the weakly-connected components of `graph` as an array of node sets,
|
|
@@ -869,6 +1080,179 @@ function laneColorForColumn(column) {
|
|
|
869
1080
|
if (column <= 0) return (text) => text;
|
|
870
1081
|
return LANE_COLOR_CYCLE[(column - 1) % LANE_COLOR_CYCLE.length] ?? ((text) => text);
|
|
871
1082
|
}
|
|
1083
|
+
function migrationListForwardArrow(glyphMode) {
|
|
1084
|
+
return glyphMode === "ascii" ? "->" : "→";
|
|
1085
|
+
}
|
|
1086
|
+
function migrationListEmptySource(glyphMode) {
|
|
1087
|
+
return glyphMode === "ascii" ? "-" : "∅";
|
|
1088
|
+
}
|
|
1089
|
+
function abbreviateContractHash(hash) {
|
|
1090
|
+
return (hash.startsWith("sha256:") ? hash.slice(7) : hash).slice(0, 7);
|
|
1091
|
+
}
|
|
1092
|
+
//#endregion
|
|
1093
|
+
//#region src/utils/formatters/migration-list-render.ts
|
|
1094
|
+
const IDENTITY_MIGRATION_LIST_STYLER = {
|
|
1095
|
+
kind: (text) => text,
|
|
1096
|
+
dirName: (text) => text,
|
|
1097
|
+
sourceHash: (text) => text,
|
|
1098
|
+
destHash: (text) => text,
|
|
1099
|
+
glyph: (text) => text,
|
|
1100
|
+
lane: (text) => text,
|
|
1101
|
+
invariants: (ids) => `{${ids.join(", ")}}`,
|
|
1102
|
+
refs: (names) => `(${names.join(", ")})`,
|
|
1103
|
+
spaceHeading: (text) => text,
|
|
1104
|
+
summary: (text) => text,
|
|
1105
|
+
emptyState: (text) => text
|
|
1106
|
+
};
|
|
1107
|
+
function canonicalFrom(from) {
|
|
1108
|
+
return from ?? EMPTY_CONTRACT_HASH;
|
|
1109
|
+
}
|
|
1110
|
+
function migrationGraphFromListEntries(entries) {
|
|
1111
|
+
const nodes = /* @__PURE__ */ new Set();
|
|
1112
|
+
const forwardChain = /* @__PURE__ */ new Map();
|
|
1113
|
+
const reverseChain = /* @__PURE__ */ new Map();
|
|
1114
|
+
const migrationByHash = /* @__PURE__ */ new Map();
|
|
1115
|
+
for (const entry of entries) {
|
|
1116
|
+
const from = canonicalFrom(entry.from);
|
|
1117
|
+
const edge = {
|
|
1118
|
+
from,
|
|
1119
|
+
to: entry.to,
|
|
1120
|
+
migrationHash: entry.migrationHash,
|
|
1121
|
+
dirName: entry.dirName,
|
|
1122
|
+
createdAt: entry.createdAt,
|
|
1123
|
+
invariants: entry.providedInvariants
|
|
1124
|
+
};
|
|
1125
|
+
nodes.add(from);
|
|
1126
|
+
nodes.add(entry.to);
|
|
1127
|
+
const forward = forwardChain.get(from);
|
|
1128
|
+
if (forward) forward.push(edge);
|
|
1129
|
+
else forwardChain.set(from, [edge]);
|
|
1130
|
+
const reverse = reverseChain.get(entry.to);
|
|
1131
|
+
if (reverse) reverse.push(edge);
|
|
1132
|
+
else reverseChain.set(entry.to, [edge]);
|
|
1133
|
+
migrationByHash.set(entry.migrationHash, edge);
|
|
1134
|
+
}
|
|
1135
|
+
return {
|
|
1136
|
+
nodes,
|
|
1137
|
+
forwardChain,
|
|
1138
|
+
reverseChain,
|
|
1139
|
+
migrationByHash
|
|
1140
|
+
};
|
|
1141
|
+
}
|
|
1142
|
+
function buildEdgeAnnotationsByHashFromListEntries(entries) {
|
|
1143
|
+
const annotations = /* @__PURE__ */ new Map();
|
|
1144
|
+
for (const entry of entries) annotations.set(entry.migrationHash, {
|
|
1145
|
+
operationCount: entry.operationCount,
|
|
1146
|
+
invariants: entry.providedInvariants
|
|
1147
|
+
});
|
|
1148
|
+
return annotations;
|
|
1149
|
+
}
|
|
1150
|
+
function buildRefsByHashFromListEntries(entries) {
|
|
1151
|
+
const refsByHash = /* @__PURE__ */ new Map();
|
|
1152
|
+
for (const entry of entries) if (entry.refs.length > 0) refsByHash.set(entry.to, entry.refs);
|
|
1153
|
+
return refsByHash;
|
|
1154
|
+
}
|
|
1155
|
+
function formatEmptyStateLine(spaceId, style) {
|
|
1156
|
+
return style.emptyState(`There are no migrations in migrations/${spaceId}/ yet`);
|
|
1157
|
+
}
|
|
1158
|
+
function indentTreeBlock(treeOutput, indent) {
|
|
1159
|
+
if (treeOutput.length === 0) return treeOutput;
|
|
1160
|
+
return treeOutput.split("\n").map((line) => line.length === 0 ? line : `${indent}${line}`).join("\n");
|
|
1161
|
+
}
|
|
1162
|
+
function renderSpaceTreeBlock(spaceId, migrations, multiSpace, glyphMode, style, colorize) {
|
|
1163
|
+
if (migrations.length === 0) {
|
|
1164
|
+
const emptyLine = formatEmptyStateLine(spaceId, style);
|
|
1165
|
+
if (!multiSpace) return [emptyLine];
|
|
1166
|
+
return [style.spaceHeading(`${spaceId}:`), ` ${emptyLine}`];
|
|
1167
|
+
}
|
|
1168
|
+
const treeOutput = renderMigrationGraphTree(buildMigrationGraphLayout(buildMigrationGraphRows(migrationGraphFromListEntries(migrations))), {
|
|
1169
|
+
refsByHash: buildRefsByHashFromListEntries(migrations),
|
|
1170
|
+
edgeAnnotationsByHash: buildEdgeAnnotationsByHashFromListEntries(migrations),
|
|
1171
|
+
colorize,
|
|
1172
|
+
glyphMode
|
|
1173
|
+
});
|
|
1174
|
+
if (!multiSpace) return treeOutput.length === 0 ? [] : [treeOutput];
|
|
1175
|
+
const indented = indentTreeBlock(treeOutput, " ");
|
|
1176
|
+
return [style.spaceHeading(`${spaceId}:`), indented];
|
|
1177
|
+
}
|
|
1178
|
+
/**
|
|
1179
|
+
* Compose the styled `migration list` human output via the shared tree
|
|
1180
|
+
* renderer. Each on-disk migration is one edge row with package-fact
|
|
1181
|
+
* annotations; refs decorate destination contract nodes.
|
|
1182
|
+
*
|
|
1183
|
+
* `options.colorize` must match whether `style` emits ANSI (e.g. both true for
|
|
1184
|
+
* `createAnsiMigrationListStyler({ useColor: true })`).
|
|
1185
|
+
*/
|
|
1186
|
+
function renderMigrationListWithStyle(result, style, glyphMode = "unicode", options = {}) {
|
|
1187
|
+
const multiSpace = result.spaces.length > 1;
|
|
1188
|
+
const colorize = options.colorize ?? false;
|
|
1189
|
+
const lines = [];
|
|
1190
|
+
for (let index = 0; index < result.spaces.length; index++) {
|
|
1191
|
+
const space = result.spaces[index];
|
|
1192
|
+
if (index > 0) lines.push("");
|
|
1193
|
+
lines.push(...renderSpaceTreeBlock(space.spaceId, space.migrations, multiSpace, glyphMode, style, colorize));
|
|
1194
|
+
}
|
|
1195
|
+
if (result.spaces.reduce((count, space) => count + space.migrations.length, 0) > 0) {
|
|
1196
|
+
lines.push("");
|
|
1197
|
+
lines.push(style.summary(result.summary));
|
|
1198
|
+
}
|
|
1199
|
+
return lines.join("\n");
|
|
1200
|
+
}
|
|
1201
|
+
//#endregion
|
|
1202
|
+
//#region src/utils/formatters/migration-list-styler.ts
|
|
1203
|
+
/**
|
|
1204
|
+
* The current contract overlay marker. Unlike user refs, this names the user's
|
|
1205
|
+
* declared desired state — the implicit base/target for `plan` / `migrate` —
|
|
1206
|
+
* not a stored label. It is emphasized (bold) so it stands out from plain refs
|
|
1207
|
+
* (including the live-database `db` marker, which is just another ref).
|
|
1208
|
+
*/
|
|
1209
|
+
const CONTRACT_MARKER_NAME = "contract";
|
|
1210
|
+
function styleRefName(name) {
|
|
1211
|
+
return name === "contract" ? bold(green(name)) : green(name);
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Build a {@link MigrationListStyler} that decorates `migration list`
|
|
1215
|
+
* tokens with ANSI SGR codes. When `useColor` is `false` (non-TTY,
|
|
1216
|
+
* `--no-color`, `NO_COLOR=1`, piped output) the function returns the
|
|
1217
|
+
* shared identity styler so callers get plain text with zero ANSI
|
|
1218
|
+
* bytes — pipe-friendly by construction.
|
|
1219
|
+
*
|
|
1220
|
+
* Palette:
|
|
1221
|
+
*
|
|
1222
|
+
* - `dirName`: bold
|
|
1223
|
+
* - `sourceHash`: dim cyan
|
|
1224
|
+
* - `destHash`: bright cyan
|
|
1225
|
+
* - `kind` (`*` / `↩` / `⟲`): bright — the signal; lanes and arrows dim
|
|
1226
|
+
* - `glyph` (`→` / `⟲` / `∅`): dim
|
|
1227
|
+
* - `lane` (graph gutter lines `│` and fan/join connectors `├─┐` / `├─┘`): dim
|
|
1228
|
+
* - `invariants` (`{...}`): yellow
|
|
1229
|
+
* - `refs` (`(...)`): green; the `contract` desired-state marker inside is
|
|
1230
|
+
* green-bold (the active ref is bolded separately by the tree styler)
|
|
1231
|
+
* - `spaceHeading` (`<spaceId>:`): bold
|
|
1232
|
+
* - `summary`: dim
|
|
1233
|
+
* - `emptyState`: dim
|
|
1234
|
+
*/
|
|
1235
|
+
function createAnsiMigrationListStyler(opts) {
|
|
1236
|
+
if (!opts.useColor) return IDENTITY_MIGRATION_LIST_STYLER;
|
|
1237
|
+
return {
|
|
1238
|
+
kind: (text) => text,
|
|
1239
|
+
dirName: (text) => bold(text),
|
|
1240
|
+
sourceHash: (text) => dim(cyan(text)),
|
|
1241
|
+
destHash: (text) => cyanBright(text),
|
|
1242
|
+
glyph: (text) => dim(text),
|
|
1243
|
+
lane: (text) => dim(text),
|
|
1244
|
+
invariants: (ids) => yellow(`{${ids.join(", ")}}`),
|
|
1245
|
+
refs: (names) => {
|
|
1246
|
+
const open = green("(");
|
|
1247
|
+
const close = green(")");
|
|
1248
|
+
const separator = green(", ");
|
|
1249
|
+
return open + names.map(styleRefName).join(separator) + close;
|
|
1250
|
+
},
|
|
1251
|
+
spaceHeading: (text) => bold(text),
|
|
1252
|
+
summary: (text) => dim(text),
|
|
1253
|
+
emptyState: (text) => dim(text)
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
872
1256
|
//#endregion
|
|
873
1257
|
//#region src/utils/formatters/migration-graph-tree-render.ts
|
|
874
1258
|
const LABEL_GAP = 2;
|
|
@@ -1231,6 +1615,15 @@ function createTreeStyler(opts) {
|
|
|
1231
1615
|
}
|
|
1232
1616
|
};
|
|
1233
1617
|
}
|
|
1618
|
+
function formatEdgeAnnotationSuffix(migrationHash, opts, style) {
|
|
1619
|
+
const annotation = opts.edgeAnnotationsByHash?.get(migrationHash);
|
|
1620
|
+
if (annotation === void 0) return "";
|
|
1621
|
+
const segments = [];
|
|
1622
|
+
if (annotation.operationCount !== void 0) segments.push(`${annotation.operationCount} ops`);
|
|
1623
|
+
if (annotation.invariants !== void 0 && annotation.invariants.length > 0) segments.push(style.invariants(annotation.invariants));
|
|
1624
|
+
if (segments.length === 0) return "";
|
|
1625
|
+
return ` ${segments.join(" ")}`;
|
|
1626
|
+
}
|
|
1234
1627
|
function formatEdgeHashColumn(edge, style, hashLength, palette) {
|
|
1235
1628
|
if (edge.kind === "self") {
|
|
1236
1629
|
const hash = abbreviateHash(edge.from, hashLength, palette.emptySource);
|
|
@@ -1335,7 +1728,8 @@ function renderMigrationGraphTree(model, opts) {
|
|
|
1335
1728
|
const laneIndex = row.laneIndex ?? 0;
|
|
1336
1729
|
const dirName = `${(opts.colorize && laneIndex > NEUTRAL_LANE ? (text) => forcedBold(laneColorForColumn(laneIndex)(text)) : style.dirName)(edge.dirName)}${dirNamePadding}`;
|
|
1337
1730
|
const hashColumn = formatEdgeHashColumn(edge, style, hashLength, palette);
|
|
1338
|
-
|
|
1731
|
+
const annotationSuffix = formatEdgeAnnotationSuffix(edge.migrationHash, opts, style);
|
|
1732
|
+
lines.push(trimTrailingWhitespace(`${gutterPad}${dirName}${hashColumn}${annotationSuffix}`));
|
|
1339
1733
|
}
|
|
1340
1734
|
return lines.join("\n");
|
|
1341
1735
|
}
|
|
@@ -1365,160 +1759,6 @@ function renderMigrationGraphLegend(opts) {
|
|
|
1365
1759
|
].join("\n");
|
|
1366
1760
|
}
|
|
1367
1761
|
//#endregion
|
|
1368
|
-
|
|
1369
|
-
/**
|
|
1370
|
-
* `--legend` describes the `--tree` visual language, so passing it auto-enables
|
|
1371
|
-
* the tree path (it has nothing to say about the legacy dagre default).
|
|
1372
|
-
*/
|
|
1373
|
-
function migrationGraphUsesTree(options) {
|
|
1374
|
-
return options.tree === true || options.legend === true;
|
|
1375
|
-
}
|
|
1376
|
-
/**
|
|
1377
|
-
* The legend is decoration printed alongside the command header on stderr, so
|
|
1378
|
-
* it is suppressed for the machine-readable / silent paths (`--json`, `--dot`,
|
|
1379
|
-
* `--quiet`) exactly as the header is.
|
|
1380
|
-
*/
|
|
1381
|
-
function migrationGraphShowsLegend(options, flags) {
|
|
1382
|
-
return options.legend === true && options.dot !== true && flags.json !== true && flags.quiet !== true;
|
|
1383
|
-
}
|
|
1384
|
-
async function executeMigrationGraphCommand(options, flags, ui) {
|
|
1385
|
-
const config = await loadConfig(options.config);
|
|
1386
|
-
const { configPath, appMigrationsRelative, migrationsDir } = resolveMigrationPaths(options.config, config);
|
|
1387
|
-
if (!flags.json && !flags.quiet) {
|
|
1388
|
-
const header = formatStyledHeader({
|
|
1389
|
-
command: "migration graph",
|
|
1390
|
-
description: "Show the migration graph topology",
|
|
1391
|
-
details: [{
|
|
1392
|
-
label: "config",
|
|
1393
|
-
value: configPath
|
|
1394
|
-
}, {
|
|
1395
|
-
label: "migrations",
|
|
1396
|
-
value: appMigrationsRelative
|
|
1397
|
-
}],
|
|
1398
|
-
flags
|
|
1399
|
-
});
|
|
1400
|
-
ui.stderr(header);
|
|
1401
|
-
if (migrationGraphShowsLegend(options, flags)) {
|
|
1402
|
-
ui.stderr(renderMigrationGraphLegend({
|
|
1403
|
-
colorize: flags.color !== false,
|
|
1404
|
-
glyphMode: ui.resolveGlyphMode(options.ascii === true)
|
|
1405
|
-
}));
|
|
1406
|
-
ui.stderr("");
|
|
1407
|
-
}
|
|
1408
|
-
}
|
|
1409
|
-
const loaded = await buildReadAggregate(config, { migrationsDir });
|
|
1410
|
-
if (!loaded.ok) return loaded;
|
|
1411
|
-
const { aggregate, contractHash } = loaded.value;
|
|
1412
|
-
const graph = aggregate.app.graph();
|
|
1413
|
-
return ok({
|
|
1414
|
-
ok: true,
|
|
1415
|
-
graph,
|
|
1416
|
-
contractHash,
|
|
1417
|
-
refs: Object.entries(aggregate.app.refs).map(([name, entry]) => ({
|
|
1418
|
-
name,
|
|
1419
|
-
hash: entry.hash,
|
|
1420
|
-
active: false
|
|
1421
|
-
})),
|
|
1422
|
-
summary: `${graph.nodes.size} node(s), ${graph.migrationByHash.size} edge(s)`
|
|
1423
|
-
});
|
|
1424
|
-
}
|
|
1425
|
-
function createMigrationGraphCommand() {
|
|
1426
|
-
const command = new Command("graph");
|
|
1427
|
-
setCommandDescriptions(command, "Show the migration graph topology", "Renders the migration graph topology. Offline — does not consult\nthe database. Use --tree for the condensed annotated tree\n(--ascii swaps box-drawing for pipe-friendly ASCII glyphs),\n--json for machine-readable output, or --dot for Graphviz DOT\nformat.");
|
|
1428
|
-
setCommandExamples(command, [
|
|
1429
|
-
"prisma-next migration graph",
|
|
1430
|
-
"prisma-next migration graph --json",
|
|
1431
|
-
"prisma-next migration graph --dot",
|
|
1432
|
-
"prisma-next migration graph --tree",
|
|
1433
|
-
"prisma-next migration graph --tree --ascii",
|
|
1434
|
-
"prisma-next migration graph --legend"
|
|
1435
|
-
]);
|
|
1436
|
-
setCommandSeeAlso(command, [
|
|
1437
|
-
{
|
|
1438
|
-
verb: "migration status",
|
|
1439
|
-
oneLiner: "Show migration path and pending status"
|
|
1440
|
-
},
|
|
1441
|
-
{
|
|
1442
|
-
verb: "migration log",
|
|
1443
|
-
oneLiner: "Show executed migration history"
|
|
1444
|
-
},
|
|
1445
|
-
{
|
|
1446
|
-
verb: "migration list",
|
|
1447
|
-
oneLiner: "List on-disk migrations"
|
|
1448
|
-
},
|
|
1449
|
-
{
|
|
1450
|
-
verb: "migration show",
|
|
1451
|
-
oneLiner: "Display migration package contents"
|
|
1452
|
-
}
|
|
1453
|
-
]);
|
|
1454
|
-
addGlobalOptions(command).option("--config <path>", "Path to prisma-next.config.ts").option("--dot", "Output in Graphviz DOT format").option("--tree", "Experimental condensed annotated tree renderer").option("--ascii", "Use ASCII glyphs for --tree (pipe-friendly)").option("--legend", "Print a key for the --tree glyphs and lane colors (implies --tree)").action(async (options) => {
|
|
1455
|
-
const flags = parseGlobalFlagsOrExit(options);
|
|
1456
|
-
const ui = createTerminalUI(flags);
|
|
1457
|
-
const exitCode = handleResult(await executeMigrationGraphCommand(options, flags, ui), flags, ui, (graphResult) => {
|
|
1458
|
-
if (options.dot) {
|
|
1459
|
-
const lines = ["digraph migrations {"];
|
|
1460
|
-
for (const edge of graphResult.graph.migrationByHash.values()) {
|
|
1461
|
-
const from = edge.from.slice(0, 12);
|
|
1462
|
-
const to = edge.to.slice(0, 12);
|
|
1463
|
-
lines.push(` "${from}" -> "${to}" [label="${edge.dirName}"];`);
|
|
1464
|
-
}
|
|
1465
|
-
lines.push("}");
|
|
1466
|
-
ui.output(lines.join("\n"));
|
|
1467
|
-
} else if (flags.json) {
|
|
1468
|
-
const nodes = [...graphResult.graph.nodes];
|
|
1469
|
-
const edges = [...graphResult.graph.migrationByHash.values()].map((e) => ({
|
|
1470
|
-
dirName: e.dirName,
|
|
1471
|
-
from: e.from,
|
|
1472
|
-
to: e.to,
|
|
1473
|
-
migrationHash: e.migrationHash
|
|
1474
|
-
}));
|
|
1475
|
-
ui.output(JSON.stringify({
|
|
1476
|
-
ok: true,
|
|
1477
|
-
nodes,
|
|
1478
|
-
edges,
|
|
1479
|
-
summary: graphResult.summary
|
|
1480
|
-
}, null, 2));
|
|
1481
|
-
} else if (!flags.quiet) if (migrationGraphUsesTree(options)) {
|
|
1482
|
-
const refsByHash = /* @__PURE__ */ new Map();
|
|
1483
|
-
for (const ref of graphResult.refs) {
|
|
1484
|
-
const existing = refsByHash.get(ref.hash);
|
|
1485
|
-
refsByHash.set(ref.hash, existing ? [...existing, ref.name] : [ref.name]);
|
|
1486
|
-
}
|
|
1487
|
-
const layout = buildMigrationGraphLayout(buildMigrationGraphRows(graphResult.graph, { ...graphResult.contractHash !== null ? { contractHash: graphResult.contractHash } : {} }));
|
|
1488
|
-
const activeRef = graphResult.refs.find((ref) => ref.active);
|
|
1489
|
-
const treeOutput = renderMigrationGraphTree(layout, {
|
|
1490
|
-
refsByHash,
|
|
1491
|
-
...graphResult.contractHash !== null ? { contractHash: graphResult.contractHash } : {},
|
|
1492
|
-
...activeRef !== void 0 ? { activeRefName: activeRef.name } : {},
|
|
1493
|
-
colorize: flags.color !== false,
|
|
1494
|
-
glyphMode: ui.resolveGlyphMode(options.ascii === true)
|
|
1495
|
-
});
|
|
1496
|
-
ui.output(treeOutput);
|
|
1497
|
-
ui.output(`\n${graphResult.summary}`);
|
|
1498
|
-
} else {
|
|
1499
|
-
const renderInput = migrationGraphToRenderInput({
|
|
1500
|
-
graph: graphResult.graph,
|
|
1501
|
-
mode: "offline",
|
|
1502
|
-
markerHash: void 0,
|
|
1503
|
-
contractHash: graphResult.contractHash ?? EMPTY_CONTRACT_HASH,
|
|
1504
|
-
refs: graphResult.refs,
|
|
1505
|
-
activeRefHash: void 0,
|
|
1506
|
-
activeRefName: void 0,
|
|
1507
|
-
edgeStatuses: []
|
|
1508
|
-
});
|
|
1509
|
-
const graphOutput = graphRenderer.render(renderInput.graph, {
|
|
1510
|
-
...renderInput.options,
|
|
1511
|
-
colorize: flags.color !== false
|
|
1512
|
-
});
|
|
1513
|
-
ui.log(graphOutput);
|
|
1514
|
-
ui.log(`\n${graphResult.summary}`);
|
|
1515
|
-
}
|
|
1516
|
-
});
|
|
1517
|
-
process.exit(exitCode);
|
|
1518
|
-
});
|
|
1519
|
-
return command;
|
|
1520
|
-
}
|
|
1521
|
-
//#endregion
|
|
1522
|
-
export { migrationGraphUsesTree as i, executeMigrationGraphCommand as n, migrationGraphShowsLegend as r, createMigrationGraphCommand as t };
|
|
1762
|
+
export { renderMigrationListWithStyle as a, buildMigrationGraphLayout as c, IDENTITY_MIGRATION_LIST_STYLER as i, renderMigrationGraphTree as n, abbreviateContractHash as o, createAnsiMigrationListStyler as r, buildMigrationGraphRows as s, renderMigrationGraphLegend as t };
|
|
1523
1763
|
|
|
1524
|
-
//# sourceMappingURL=migration-graph-
|
|
1764
|
+
//# sourceMappingURL=migration-graph-tree-render-BQdhKBO8.mjs.map
|