@syntax-syllogism/flow-delta 0.1.0 → 0.1.2
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.js +321 -30
- package/dist/gitlab-report.js +15 -1
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -766,7 +766,38 @@ function toEdgeDiff(edge, status) {
|
|
|
766
766
|
// src/render/layout.ts
|
|
767
767
|
import ELK from "elkjs/lib/elk.bundled.js";
|
|
768
768
|
async function layoutDiff(diff) {
|
|
769
|
-
const
|
|
769
|
+
const union = await layoutView(diff, () => true, () => true);
|
|
770
|
+
const after = await layoutView(
|
|
771
|
+
diff,
|
|
772
|
+
(node) => node.status !== "deleted",
|
|
773
|
+
(edge) => edge.status !== "deleted"
|
|
774
|
+
);
|
|
775
|
+
const before = await layoutView(
|
|
776
|
+
diff,
|
|
777
|
+
(node) => node.status !== "added",
|
|
778
|
+
(edge) => edge.status !== "added"
|
|
779
|
+
);
|
|
780
|
+
return {
|
|
781
|
+
diff,
|
|
782
|
+
nodes: union.nodes,
|
|
783
|
+
edges: union.edges,
|
|
784
|
+
width: union.width,
|
|
785
|
+
height: union.height,
|
|
786
|
+
views: {
|
|
787
|
+
union,
|
|
788
|
+
after,
|
|
789
|
+
before
|
|
790
|
+
}
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
var elk = new ELK();
|
|
794
|
+
async function layoutView(diff, nodeVisible, edgeVisible) {
|
|
795
|
+
const nodes = diff.nodes.filter(nodeVisible);
|
|
796
|
+
const nodeIds = new Set(nodes.map((node) => node.id));
|
|
797
|
+
const edges = diff.edges.filter((edge) => edgeVisible(edge) && nodeIds.has(edge.source) && nodeIds.has(edge.target));
|
|
798
|
+
if (nodes.length === 0) {
|
|
799
|
+
return { nodes: [], edges: [], width: 40, height: 40 };
|
|
800
|
+
}
|
|
770
801
|
const graph = {
|
|
771
802
|
id: "root",
|
|
772
803
|
layoutOptions: {
|
|
@@ -775,12 +806,12 @@ async function layoutDiff(diff) {
|
|
|
775
806
|
"elk.layered.spacing.nodeNodeBetweenLayers": 60,
|
|
776
807
|
"elk.spacing.nodeNode": 40
|
|
777
808
|
},
|
|
778
|
-
children:
|
|
809
|
+
children: nodes.map((node) => ({
|
|
779
810
|
id: node.id,
|
|
780
811
|
width: estimateWidth(node.label),
|
|
781
812
|
height: 48
|
|
782
813
|
})),
|
|
783
|
-
edges:
|
|
814
|
+
edges: edges.map((edge) => ({
|
|
784
815
|
id: edge.id,
|
|
785
816
|
sources: [edge.source],
|
|
786
817
|
targets: [edge.target]
|
|
@@ -788,10 +819,10 @@ async function layoutDiff(diff) {
|
|
|
788
819
|
};
|
|
789
820
|
const laidOut = await elk.layout(graph);
|
|
790
821
|
const children = laidOut.children ?? [];
|
|
791
|
-
const
|
|
822
|
+
const edgeLayouts = laidOut.edges ?? [];
|
|
792
823
|
const nodeMap = new Map(children.map((node) => [node.id, node]));
|
|
793
|
-
const edgeMap = new Map(
|
|
794
|
-
const
|
|
824
|
+
const edgeMap = new Map(edgeLayouts.map((edge) => [edge.id, edge]));
|
|
825
|
+
const positionedNodes = nodes.map((node) => {
|
|
795
826
|
const positioned = nodeMap.get(node.id);
|
|
796
827
|
return {
|
|
797
828
|
...node,
|
|
@@ -801,14 +832,13 @@ async function layoutDiff(diff) {
|
|
|
801
832
|
height: positioned?.height ?? 48
|
|
802
833
|
};
|
|
803
834
|
});
|
|
804
|
-
const positionedEdges =
|
|
835
|
+
const positionedEdges = edges.map((edge) => ({
|
|
805
836
|
...edge,
|
|
806
837
|
sections: normalizeSections(edgeMap.get(edge.id)?.sections)
|
|
807
838
|
}));
|
|
808
|
-
const bounds = measureBounds(
|
|
839
|
+
const bounds = measureBounds(positionedNodes);
|
|
809
840
|
return {
|
|
810
|
-
|
|
811
|
-
nodes,
|
|
841
|
+
nodes: positionedNodes,
|
|
812
842
|
edges: positionedEdges,
|
|
813
843
|
width: bounds.width,
|
|
814
844
|
height: bounds.height
|
|
@@ -840,7 +870,8 @@ function renderHtml(layout) {
|
|
|
840
870
|
nodes: layout.nodes,
|
|
841
871
|
edges: layout.edges,
|
|
842
872
|
width: layout.width,
|
|
843
|
-
height: layout.height
|
|
873
|
+
height: layout.height,
|
|
874
|
+
layouts: layout.views
|
|
844
875
|
};
|
|
845
876
|
const json = JSON.stringify(data).replace(/</g, "\\u003c");
|
|
846
877
|
const s = layout.diff.summary;
|
|
@@ -877,6 +908,30 @@ function renderHtml(layout) {
|
|
|
877
908
|
header .meta .deleted:not(.zero) { color: var(--deleted); }
|
|
878
909
|
header .meta .modified:not(.zero) { color: var(--modified); }
|
|
879
910
|
header .meta .sep { color: var(--border); }
|
|
911
|
+
.header-actions { display: flex; flex-direction: column; align-items: flex-end; gap: 10px; }
|
|
912
|
+
.filters { display: flex; gap: 8px; flex-wrap: wrap; justify-content: flex-end; }
|
|
913
|
+
.filters button {
|
|
914
|
+
border: 1px solid var(--border);
|
|
915
|
+
background: #f8fafc;
|
|
916
|
+
color: var(--muted);
|
|
917
|
+
padding: 6px 11px;
|
|
918
|
+
border-radius: 999px;
|
|
919
|
+
font: inherit;
|
|
920
|
+
font-size: 12px;
|
|
921
|
+
font-weight: 600;
|
|
922
|
+
cursor: pointer;
|
|
923
|
+
transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
|
|
924
|
+
}
|
|
925
|
+
.filters button.active {
|
|
926
|
+
background: #eff6ff;
|
|
927
|
+
border-color: #93c5fd;
|
|
928
|
+
color: var(--text);
|
|
929
|
+
box-shadow: 0 0 0 1px rgba(59, 130, 246, 0.12);
|
|
930
|
+
}
|
|
931
|
+
.filters button:focus-visible {
|
|
932
|
+
outline: 2px solid #93c5fd;
|
|
933
|
+
outline-offset: 2px;
|
|
934
|
+
}
|
|
880
935
|
.legend { display: flex; gap: 14px; font-size: 12px; flex-wrap: wrap; color: var(--muted); }
|
|
881
936
|
.legend span::before { content: ""; display: inline-block; width: 9px; height: 9px; border-radius: 2px; margin-right: 6px; vertical-align: 0; }
|
|
882
937
|
.legend .added::before { background: var(--added); }
|
|
@@ -907,11 +962,16 @@ function renderHtml(layout) {
|
|
|
907
962
|
.change-value-label { margin-bottom: 4px; color: #475569; font-size: 10.5px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; }
|
|
908
963
|
.changes code { font-size: 12px; font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; background: #eef1f6; padding: 2px 5px; border-radius: 4px; overflow-wrap: anywhere; }
|
|
909
964
|
.changes pre { max-width: 100%; margin: 0; padding: 11px 12px; background: var(--surface); border: 1px solid var(--border); border-radius: 6px; white-space: pre-wrap; overflow-wrap: anywhere; font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace; font-size: 12px; line-height: 1.5; }
|
|
910
|
-
.edge { fill: none;
|
|
911
|
-
.edge.
|
|
912
|
-
.edge.
|
|
965
|
+
.edge { fill: none; }
|
|
966
|
+
.edge.unchanged { stroke-width: 1.4; opacity: 0.55; }
|
|
967
|
+
.edge.normal.unchanged { stroke: var(--edge); marker-end: url(#arrow-normal); }
|
|
968
|
+
.edge.fault.unchanged { stroke: var(--fault); stroke-dasharray: 6 4; marker-end: url(#arrow-fault); }
|
|
969
|
+
.edge.added { stroke: var(--added); stroke-width: 3; marker-end: url(#arrow-added); }
|
|
970
|
+
.edge.deleted { stroke: var(--deleted); stroke-width: 2; stroke-dasharray: 7 5; marker-end: url(#arrow-deleted); }
|
|
913
971
|
#arrow-normal path { fill: var(--edge); }
|
|
914
972
|
#arrow-fault path { fill: var(--fault); }
|
|
973
|
+
#arrow-added path { fill: var(--added); }
|
|
974
|
+
#arrow-deleted path { fill: var(--deleted); }
|
|
915
975
|
.node rect { rx: 11; ry: 11; stroke-width: 1.75; filter: drop-shadow(0 1px 2px rgba(15, 23, 42, 0.08)); }
|
|
916
976
|
.node { cursor: pointer; }
|
|
917
977
|
.node text { font-size: 12px; fill: #111827; pointer-events: none; }
|
|
@@ -933,11 +993,19 @@ function renderHtml(layout) {
|
|
|
933
993
|
<h1>${escapeHtml(layout.diff.flowName)}</h1>
|
|
934
994
|
<div class="meta">${metaHtml}</div>
|
|
935
995
|
</div>
|
|
936
|
-
<div class="
|
|
937
|
-
<
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
996
|
+
<div class="header-actions">
|
|
997
|
+
<div class="filters" role="group" aria-label="Diff view filters">
|
|
998
|
+
<button type="button" class="active" data-view-mode="all">All</button>
|
|
999
|
+
<button type="button" data-view-mode="after">After</button>
|
|
1000
|
+
<button type="button" data-view-mode="before">Before</button>
|
|
1001
|
+
<button type="button" data-view-mode="changes">Changes only</button>
|
|
1002
|
+
</div>
|
|
1003
|
+
<div class="legend">
|
|
1004
|
+
<span class="added">Added</span>
|
|
1005
|
+
<span class="deleted">Deleted</span>
|
|
1006
|
+
<span class="modified">Modified</span>
|
|
1007
|
+
<span class="unchanged">Unchanged</span>
|
|
1008
|
+
</div>
|
|
941
1009
|
</div>
|
|
942
1010
|
</header>
|
|
943
1011
|
<main>
|
|
@@ -950,6 +1018,12 @@ function renderHtml(layout) {
|
|
|
950
1018
|
<marker id="arrow-fault" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto" markerUnits="userSpaceOnUse">
|
|
951
1019
|
<path d="M0,0 L8,4 L0,8 Z"></path>
|
|
952
1020
|
</marker>
|
|
1021
|
+
<marker id="arrow-added" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto" markerUnits="userSpaceOnUse">
|
|
1022
|
+
<path d="M0,0 L8,4 L0,8 Z"></path>
|
|
1023
|
+
</marker>
|
|
1024
|
+
<marker id="arrow-deleted" markerWidth="8" markerHeight="8" refX="7" refY="4" orient="auto" markerUnits="userSpaceOnUse">
|
|
1025
|
+
<path d="M0,0 L8,4 L0,8 Z"></path>
|
|
1026
|
+
</marker>
|
|
953
1027
|
</defs>
|
|
954
1028
|
<g id="viewport">
|
|
955
1029
|
${layout.edges.map(renderEdge).join("")}
|
|
@@ -971,9 +1045,13 @@ function renderHtml(layout) {
|
|
|
971
1045
|
const panelTitle = document.getElementById("panel-title");
|
|
972
1046
|
const panelBadge = document.getElementById("panel-badge");
|
|
973
1047
|
const panelBody = document.getElementById("panel-body");
|
|
1048
|
+
const filterButtons = [...document.querySelectorAll(".filters button")];
|
|
974
1049
|
const nodesById = new Map(DATA.nodes.map((node) => [node.id, node]));
|
|
1050
|
+
const nodeElements = new Map([...document.querySelectorAll(".node")].map((node) => [node.dataset.nodeId, node]));
|
|
1051
|
+
const edgeElements = new Map([...document.querySelectorAll(".edge")].map((edge) => [edge.dataset.edgeId, edge]));
|
|
975
1052
|
let viewBox = svg.viewBox.baseVal;
|
|
976
1053
|
let dragStart = null;
|
|
1054
|
+
let selectedNodeId = null;
|
|
977
1055
|
|
|
978
1056
|
function escapeHtml(value) {
|
|
979
1057
|
return String(value)
|
|
@@ -983,12 +1061,128 @@ function renderHtml(layout) {
|
|
|
983
1061
|
.replace(/"/g, """);
|
|
984
1062
|
}
|
|
985
1063
|
|
|
986
|
-
function
|
|
1064
|
+
function isNodeVisible(node, mode) {
|
|
1065
|
+
if (mode === "after") return node.status !== "deleted";
|
|
1066
|
+
if (mode === "before") return node.status !== "added";
|
|
1067
|
+
if (mode === "changes") return node.status !== "unchanged";
|
|
1068
|
+
return true;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
function isEdgeVisible(edge, mode, visibleNodeIds) {
|
|
1072
|
+
if (!visibleNodeIds.has(edge.source) || !visibleNodeIds.has(edge.target)) {
|
|
1073
|
+
return false;
|
|
1074
|
+
}
|
|
1075
|
+
if (mode === "after") return edge.status !== "deleted";
|
|
1076
|
+
if (mode === "before") return edge.status !== "added";
|
|
1077
|
+
if (mode === "changes") {
|
|
1078
|
+
const source = nodesById.get(edge.source);
|
|
1079
|
+
const target = nodesById.get(edge.target);
|
|
1080
|
+
const sourceChanged = source?.status !== "unchanged";
|
|
1081
|
+
const targetChanged = target?.status !== "unchanged";
|
|
1082
|
+
return edge.status !== "unchanged" || (sourceChanged && targetChanged);
|
|
1083
|
+
}
|
|
1084
|
+
return true;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
function edgePath(sections) {
|
|
1088
|
+
const points = sections.flatMap((section, index) => {
|
|
1089
|
+
const start = index === 0 ? [section.startPoint] : [];
|
|
1090
|
+
const bends = section.bendPoints ?? [];
|
|
1091
|
+
const end = [section.endPoint];
|
|
1092
|
+
return [...start, ...bends, ...end];
|
|
1093
|
+
});
|
|
1094
|
+
if (points.length === 0) {
|
|
1095
|
+
return "";
|
|
1096
|
+
}
|
|
1097
|
+
return points.map((point, index) => \`\${index === 0 ? "M" : "L"} \${point.x} \${point.y}\`).join(" ");
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
function updateToolbar(mode) {
|
|
1101
|
+
filterButtons.forEach((button) => {
|
|
1102
|
+
const active = button.dataset.viewMode === mode;
|
|
1103
|
+
button.classList.toggle("active", active);
|
|
1104
|
+
button.setAttribute("aria-pressed", active ? "true" : "false");
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
function round(value) {
|
|
1109
|
+
return Math.round(value * 100) / 100;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
function collectEdgePoints(sections) {
|
|
1113
|
+
return sections.flatMap((section, index) => {
|
|
1114
|
+
const start = index === 0 ? [section.startPoint] : [];
|
|
1115
|
+
const bends = section.bendPoints ?? [];
|
|
1116
|
+
const end = [section.endPoint];
|
|
1117
|
+
return [...start, ...bends, ...end];
|
|
1118
|
+
});
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
function measureVisibleBounds(nodes, edges) {
|
|
1122
|
+
const points = [
|
|
1123
|
+
...nodes.flatMap((node) => ([
|
|
1124
|
+
{ x: node.x, y: node.y },
|
|
1125
|
+
{ x: node.x + node.width, y: node.y + node.height },
|
|
1126
|
+
])),
|
|
1127
|
+
...edges.flatMap((edge) => collectEdgePoints(edge.sections)),
|
|
1128
|
+
];
|
|
1129
|
+
|
|
1130
|
+
if (points.length === 0) {
|
|
1131
|
+
return null;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
let minX = points[0].x;
|
|
1135
|
+
let minY = points[0].y;
|
|
1136
|
+
let maxX = points[0].x;
|
|
1137
|
+
let maxY = points[0].y;
|
|
1138
|
+
for (const point of points.slice(1)) {
|
|
1139
|
+
if (point.x < minX) minX = point.x;
|
|
1140
|
+
if (point.y < minY) minY = point.y;
|
|
1141
|
+
if (point.x > maxX) maxX = point.x;
|
|
1142
|
+
if (point.y > maxY) maxY = point.y;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
return {
|
|
1146
|
+
x: minX - 20,
|
|
1147
|
+
y: minY - 20,
|
|
1148
|
+
width: (maxX - minX) + 40,
|
|
1149
|
+
height: (maxY - minY) + 40,
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
function fitViewBoxRect(bounds) {
|
|
1154
|
+
const minWidth = 720;
|
|
1155
|
+
const minHeight = 540;
|
|
1156
|
+
const width = Math.max(bounds.width, minWidth);
|
|
1157
|
+
const height = Math.max(bounds.height, minHeight);
|
|
1158
|
+
const x = bounds.x + (bounds.width - width) / 2;
|
|
1159
|
+
const y = bounds.y + (bounds.height - height) / 2;
|
|
1160
|
+
return {
|
|
1161
|
+
x: round(x),
|
|
1162
|
+
y: round(y),
|
|
1163
|
+
width: round(width),
|
|
1164
|
+
height: round(height),
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
function updateViewBox(bounds) {
|
|
1169
|
+
const box = fitViewBoxRect(bounds);
|
|
1170
|
+
viewBox.x = box.x;
|
|
1171
|
+
viewBox.y = box.y;
|
|
1172
|
+
viewBox.width = box.width;
|
|
1173
|
+
viewBox.height = box.height;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
function clearSelection(message) {
|
|
1177
|
+
selectedNodeId = null;
|
|
987
1178
|
document.querySelectorAll(".node").forEach((el) => el.classList.remove("selected"));
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1179
|
+
panel.className = "panel";
|
|
1180
|
+
panelTitle.textContent = "No visible nodes";
|
|
1181
|
+
panelBadge.textContent = "Hidden";
|
|
1182
|
+
panelBody.innerHTML = "<div class='empty'>" + escapeHtml(message) + "</div>";
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
function updatePanel(node) {
|
|
992
1186
|
panel.className = "panel " + node.status;
|
|
993
1187
|
panelTitle.textContent = node.label;
|
|
994
1188
|
panelBadge.textContent = node.status.toUpperCase();
|
|
@@ -1002,6 +1196,15 @@ function renderHtml(layout) {
|
|
|
1002
1196
|
: "<div class='empty'>No property changes.</div>";
|
|
1003
1197
|
}
|
|
1004
1198
|
|
|
1199
|
+
function selectNode(id) {
|
|
1200
|
+
selectedNodeId = id;
|
|
1201
|
+
document.querySelectorAll(".node").forEach((el) => el.classList.remove("selected"));
|
|
1202
|
+
const selected = document.querySelector(\`.node[data-node-id="\${CSS.escape(id)}"]\`);
|
|
1203
|
+
if (selected) selected.classList.add("selected");
|
|
1204
|
+
const node = nodesById.get(id);
|
|
1205
|
+
if (node) updatePanel(node);
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1005
1208
|
function formatChange(change) {
|
|
1006
1209
|
const beforeMissing = change.before === undefined;
|
|
1007
1210
|
const afterMissing = change.after === undefined;
|
|
@@ -1041,6 +1244,77 @@ function renderHtml(layout) {
|
|
|
1041
1244
|
return "<code>" + escapeHtml(String(value)) + "</code>";
|
|
1042
1245
|
}
|
|
1043
1246
|
|
|
1247
|
+
function pickInitialNode(visibleNodeIds) {
|
|
1248
|
+
return DATA.nodes.find((node) => visibleNodeIds.has(node.id) && node.status === "modified")
|
|
1249
|
+
|| DATA.nodes.find((node) => visibleNodeIds.has(node.id) && node.status !== "unchanged")
|
|
1250
|
+
|| DATA.nodes.find((node) => visibleNodeIds.has(node.id))
|
|
1251
|
+
|| null;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
function applyView(mode) {
|
|
1255
|
+
const layout = mode === "all" || mode === "changes"
|
|
1256
|
+
? DATA.layouts.union
|
|
1257
|
+
: DATA.layouts[mode];
|
|
1258
|
+
const nodeLayout = new Map(layout.nodes.map((node) => [node.id, node]));
|
|
1259
|
+
const edgeLayout = new Map(layout.edges.map((edge) => [edge.id, edge]));
|
|
1260
|
+
const visibleNodeIds = new Set();
|
|
1261
|
+
const visibleNodes = [];
|
|
1262
|
+
const visibleEdges = [];
|
|
1263
|
+
|
|
1264
|
+
DATA.nodes.forEach((node) => {
|
|
1265
|
+
const element = nodeElements.get(node.id);
|
|
1266
|
+
if (!element) return;
|
|
1267
|
+
if (!isNodeVisible(node, mode)) {
|
|
1268
|
+
element.style.display = "none";
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
const positioned = nodeLayout.get(node.id);
|
|
1272
|
+
if (!positioned) {
|
|
1273
|
+
element.style.display = "none";
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
element.style.display = "";
|
|
1277
|
+
element.setAttribute("transform", "translate(" + positioned.x + "," + positioned.y + ")");
|
|
1278
|
+
visibleNodeIds.add(node.id);
|
|
1279
|
+
visibleNodes.push(positioned);
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
DATA.edges.forEach((edge) => {
|
|
1283
|
+
const element = edgeElements.get(edge.id);
|
|
1284
|
+
if (!element) return;
|
|
1285
|
+
if (!isEdgeVisible(edge, mode, visibleNodeIds)) {
|
|
1286
|
+
element.style.display = "none";
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
const positioned = edgeLayout.get(edge.id);
|
|
1290
|
+
if (!positioned) {
|
|
1291
|
+
element.style.display = "none";
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
element.style.display = "";
|
|
1295
|
+
element.setAttribute("d", edgePath(positioned.sections));
|
|
1296
|
+
visibleEdges.push(positioned);
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
updateToolbar(mode);
|
|
1300
|
+
updateViewBox(measureVisibleBounds(visibleNodes, visibleEdges) || { x: 0, y: 0, width: layout.width, height: layout.height });
|
|
1301
|
+
|
|
1302
|
+
if (selectedNodeId && visibleNodeIds.has(selectedNodeId)) {
|
|
1303
|
+
const node = nodesById.get(selectedNodeId);
|
|
1304
|
+
if (node) {
|
|
1305
|
+
updatePanel(node);
|
|
1306
|
+
}
|
|
1307
|
+
return;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
const initialNode = pickInitialNode(visibleNodeIds);
|
|
1311
|
+
if (initialNode) {
|
|
1312
|
+
selectNode(initialNode.id);
|
|
1313
|
+
} else {
|
|
1314
|
+
clearSelection("This view has no visible nodes.");
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1044
1318
|
document.querySelectorAll(".node").forEach((node) => {
|
|
1045
1319
|
node.addEventListener("click", (event) => {
|
|
1046
1320
|
event.stopPropagation();
|
|
@@ -1076,10 +1350,13 @@ function renderHtml(layout) {
|
|
|
1076
1350
|
svg.addEventListener("pointerup", () => { dragStart = null; });
|
|
1077
1351
|
svg.addEventListener("pointercancel", () => { dragStart = null; });
|
|
1078
1352
|
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1353
|
+
filterButtons.forEach((button) => {
|
|
1354
|
+
button.addEventListener("click", () => {
|
|
1355
|
+
applyView(button.dataset.viewMode);
|
|
1356
|
+
});
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
applyView("all");
|
|
1083
1360
|
</script>
|
|
1084
1361
|
</body>
|
|
1085
1362
|
</html>`;
|
|
@@ -1107,7 +1384,7 @@ function renderEdge(edge) {
|
|
|
1107
1384
|
return "";
|
|
1108
1385
|
}
|
|
1109
1386
|
const d = points.map((point, index) => `${index === 0 ? "M" : "L"} ${point.x} ${point.y}`).join(" ");
|
|
1110
|
-
return `<path class="edge ${edge.kind}" d="${d}" data-edge-id="${escapeHtml(edge.id)}"></path>`;
|
|
1387
|
+
return `<path class="edge ${edge.kind} ${edge.status}" d="${d}" data-edge-id="${escapeHtml(edge.id)}"></path>`;
|
|
1111
1388
|
}
|
|
1112
1389
|
function renderNode(node) {
|
|
1113
1390
|
const lines = wrapLabel(node.label, 22);
|
|
@@ -1174,6 +1451,20 @@ function safeFileName(value) {
|
|
|
1174
1451
|
return value.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
1175
1452
|
}
|
|
1176
1453
|
|
|
1454
|
+
// src/util/is-main-module.ts
|
|
1455
|
+
import { realpathSync } from "node:fs";
|
|
1456
|
+
import { fileURLToPath } from "node:url";
|
|
1457
|
+
function isMainModule(metaUrl, argvPath = process.argv[1]) {
|
|
1458
|
+
if (!argvPath) {
|
|
1459
|
+
return false;
|
|
1460
|
+
}
|
|
1461
|
+
try {
|
|
1462
|
+
return realpathSync(fileURLToPath(metaUrl)) === realpathSync(argvPath);
|
|
1463
|
+
} catch {
|
|
1464
|
+
return false;
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1177
1468
|
// src/cli.ts
|
|
1178
1469
|
var CLI_USAGE = `Usage:
|
|
1179
1470
|
flow-delta --old <old.flow-meta.xml> --new <new.flow-meta.xml> [--out dir] [--json]
|
|
@@ -1345,7 +1636,7 @@ function globToRegExp(pattern) {
|
|
|
1345
1636
|
source += "$";
|
|
1346
1637
|
return new RegExp(source);
|
|
1347
1638
|
}
|
|
1348
|
-
if (import.meta.url
|
|
1639
|
+
if (isMainModule(import.meta.url)) {
|
|
1349
1640
|
void main();
|
|
1350
1641
|
}
|
|
1351
1642
|
export {
|
package/dist/gitlab-report.js
CHANGED
|
@@ -10,6 +10,20 @@ function safeFileName(value) {
|
|
|
10
10
|
return value.replace(/[^a-zA-Z0-9._-]+/g, "_");
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
// src/util/is-main-module.ts
|
|
14
|
+
import { realpathSync } from "node:fs";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
function isMainModule(metaUrl, argvPath = process.argv[1]) {
|
|
17
|
+
if (!argvPath) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return realpathSync(fileURLToPath(metaUrl)) === realpathSync(argvPath);
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
13
27
|
// src/ci/gitlab-report.ts
|
|
14
28
|
var DEFAULT_MARKER = "<!-- FlowDelta:report -->";
|
|
15
29
|
var REPORTER_USAGE = `Usage:
|
|
@@ -176,7 +190,7 @@ function normalizePath(value) {
|
|
|
176
190
|
function escapeTableCell(value) {
|
|
177
191
|
return value.replaceAll("|", "\\|").replaceAll("`", "\\`").replace(/\r?\n/g, "<br>");
|
|
178
192
|
}
|
|
179
|
-
if (import.meta.url
|
|
193
|
+
if (isMainModule(import.meta.url)) {
|
|
180
194
|
void main();
|
|
181
195
|
}
|
|
182
196
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syntax-syllogism/flow-delta",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Semantic visual diff for Salesforce Flows",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,19 +27,19 @@
|
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "node scripts/build.mjs",
|
|
29
29
|
"smoke:gitlab": "node --import tsx scripts/smoke-gitlab.ts",
|
|
30
|
-
"test": "node --import tsx --test test/parser.test.ts test/semantic-diff.test.ts test/gitlab-report.test.ts",
|
|
30
|
+
"test": "node --import tsx --test test/parser.test.ts test/semantic-diff.test.ts test/gitlab-report.test.ts test/smoke-gitlab.test.ts",
|
|
31
31
|
"test:parser": "node --import tsx --test test/parser.test.ts",
|
|
32
32
|
"prepublishOnly": "npm run build && npm test",
|
|
33
33
|
"render:fixtures": "bash bin/render-fixtures.sh"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"esbuild": "^0.28.0",
|
|
37
|
-
"@types/node": "^
|
|
37
|
+
"@types/node": "^25.9.2",
|
|
38
38
|
"@types/xml2js": "^0.4.14",
|
|
39
39
|
"tsx": "^4.19.0"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"elkjs": "^0.
|
|
42
|
+
"elkjs": "^0.11.1",
|
|
43
43
|
"xml2js": "^0.6.2"
|
|
44
44
|
},
|
|
45
45
|
"bin": {
|