patchrelay 0.24.0 → 0.24.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/build-info.json
CHANGED
|
@@ -59,7 +59,20 @@ function RunLine({ run, index }) {
|
|
|
59
59
|
? formatDuration(run.startedAt, run.endedAt)
|
|
60
60
|
: undefined;
|
|
61
61
|
const isActive = run.status === "running";
|
|
62
|
-
return (_jsxs(Box, { children: [_jsxs(Text, { color: runStatusColor(run.status), children: [runStatusSymbol(run.status), " "] }), _jsxs(Text, { dimColor: true, children: ["
|
|
62
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: runStatusColor(run.status), children: [runStatusSymbol(run.status), " "] }), _jsxs(Text, { dimColor: true, children: ["#", index + 1, " "] }), _jsxs(Text, { children: ["(", label, ")"] }), dur && _jsxs(Text, { dimColor: true, children: [" ", dur] }), isActive && _jsx(Text, { dimColor: true, children: " ..." })] }));
|
|
63
|
+
}
|
|
64
|
+
function RunSummary({ runs }) {
|
|
65
|
+
const completed = runs.filter((r) => r.status === "completed").length;
|
|
66
|
+
const failed = runs.filter((r) => r.status === "failed").length;
|
|
67
|
+
const running = runs.filter((r) => r.status === "running").length;
|
|
68
|
+
const parts = [];
|
|
69
|
+
if (completed > 0)
|
|
70
|
+
parts.push(`${completed} completed`);
|
|
71
|
+
if (failed > 0)
|
|
72
|
+
parts.push(`${failed} failed`);
|
|
73
|
+
if (running > 0)
|
|
74
|
+
parts.push(`${running} active`);
|
|
75
|
+
return _jsxs(Text, { dimColor: true, children: [runs.length, " runs: ", parts.join(", ")] });
|
|
63
76
|
}
|
|
64
77
|
function PlanSteps({ plan }) {
|
|
65
78
|
return (_jsx(Box, { flexDirection: "column", paddingLeft: 2, children: plan.map((entry, i) => (_jsxs(Box, { gap: 1, children: [_jsxs(Text, { color: planStepColor(entry.status), children: ["[", planStepSymbol(entry.status), "]"] }), _jsx(Text, { children: entry.step })] }, `plan-${i}`))) }));
|
|
@@ -74,7 +87,8 @@ function MainPathNode({ node, isLast, runOffset, plan, activeRunId, }) {
|
|
|
74
87
|
const marker = node.isCurrent ? "\u25c9" : "\u25cb";
|
|
75
88
|
const stateColor = node.isCurrent ? "green" : "white";
|
|
76
89
|
const hasActiveRun = node.runs.some((r) => r.id === activeRunId);
|
|
77
|
-
|
|
90
|
+
const gutter = isLast && node.sideTrips.length === 0 ? " " : " \u2502 ";
|
|
91
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsxs(Text, { color: stateColor, bold: node.isCurrent, children: [" ", marker, " "] }), _jsx(Text, { color: stateColor, bold: node.isCurrent, children: stateLabel }), _jsxs(Text, { dimColor: true, children: [" ", formatTime(node.enteredAt)] })] }), node.reason && (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: gutter }), _jsx(Text, { dimColor: true, children: node.reason })] })), node.runs.length > 5 && (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: gutter }), _jsx(RunSummary, { runs: node.runs })] })), node.runs.map((run, ri) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: gutter }), _jsx(RunLine, { run: run, index: runOffset + ri })] }), run.id === activeRunId && plan && plan.length > 0 && (_jsxs(Box, { children: [_jsx(Text, { dimColor: true, children: gutter }), _jsx(PlanSteps, { plan: plan })] }))] }, `run-${run.id}`))), node.sideTrips.length > 0 && (_jsx(Box, { flexDirection: "column", children: node.sideTrips.map((trip, ti) => {
|
|
78
92
|
// Count runs before this side-trip for numbering
|
|
79
93
|
const priorSideTripRuns = node.sideTrips.slice(0, ti).reduce((acc, st) => acc + st.runs.length, 0);
|
|
80
94
|
const tripRunOffset = runOffset + node.runs.length + priorSideTripRuns;
|
|
@@ -15,7 +15,6 @@ function extractTransitions(feedEvents) {
|
|
|
15
15
|
let reason;
|
|
16
16
|
if (event.kind === "stage") {
|
|
17
17
|
if (event.status === "starting") {
|
|
18
|
-
// stage field is runType, map to factory state
|
|
19
18
|
state = RUN_TYPE_TO_STATE[event.stage] ?? event.stage;
|
|
20
19
|
reason = event.summary;
|
|
21
20
|
}
|
|
@@ -25,7 +24,6 @@ function extractTransitions(feedEvents) {
|
|
|
25
24
|
}
|
|
26
25
|
}
|
|
27
26
|
else if (event.kind === "github") {
|
|
28
|
-
// stage field is the factory state AFTER the transition
|
|
29
27
|
state = event.stage;
|
|
30
28
|
reason = event.summary;
|
|
31
29
|
}
|
|
@@ -39,52 +37,96 @@ function extractTransitions(feedEvents) {
|
|
|
39
37
|
}
|
|
40
38
|
return transitions;
|
|
41
39
|
}
|
|
42
|
-
// ─── Run
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
// ─── Run helpers ─────────────────────────────────────────────────
|
|
41
|
+
function toRunInfo(run) {
|
|
42
|
+
return {
|
|
43
|
+
id: run.id,
|
|
44
|
+
runType: run.runType,
|
|
45
|
+
status: run.status,
|
|
46
|
+
startedAt: run.startedAt,
|
|
47
|
+
endedAt: run.endedAt,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function runToState(run) {
|
|
51
|
+
return RUN_TYPE_TO_STATE[run.runType] ?? run.runType;
|
|
52
|
+
}
|
|
53
|
+
// ─── Build from runs only (no feed events) ───────────────────────
|
|
54
|
+
function buildFromRuns(runs, currentFactoryState) {
|
|
55
|
+
if (runs.length === 0)
|
|
56
|
+
return [];
|
|
57
|
+
const nodes = [];
|
|
58
|
+
const earliest = runs[0];
|
|
59
|
+
// Seed with delegated
|
|
60
|
+
nodes.push({
|
|
61
|
+
state: "delegated",
|
|
62
|
+
enteredAt: earliest.startedAt,
|
|
63
|
+
isCurrent: false,
|
|
64
|
+
runs: [],
|
|
65
|
+
sideTrips: [],
|
|
66
|
+
});
|
|
67
|
+
// Group consecutive runs by their mapped state.
|
|
68
|
+
// When the state changes, create a new node.
|
|
69
|
+
let currentState = "";
|
|
70
|
+
let currentNode = null;
|
|
47
71
|
for (const run of runs) {
|
|
48
|
-
const state =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
const state = runToState(run);
|
|
73
|
+
if (state !== currentState) {
|
|
74
|
+
currentState = state;
|
|
75
|
+
currentNode = {
|
|
76
|
+
state,
|
|
77
|
+
enteredAt: run.startedAt,
|
|
78
|
+
isCurrent: false,
|
|
79
|
+
runs: [toRunInfo(run)],
|
|
80
|
+
sideTrips: [],
|
|
81
|
+
};
|
|
82
|
+
nodes.push(currentNode);
|
|
59
83
|
}
|
|
60
84
|
else {
|
|
61
|
-
|
|
85
|
+
currentNode.runs.push(toRunInfo(run));
|
|
62
86
|
}
|
|
63
87
|
}
|
|
64
|
-
|
|
88
|
+
// If the current factory state differs from the last node's state,
|
|
89
|
+
// add a final node (e.g., implementing → failed)
|
|
90
|
+
const lastNodeState = nodes[nodes.length - 1].state;
|
|
91
|
+
if (currentFactoryState !== lastNodeState && currentFactoryState !== "delegated") {
|
|
92
|
+
const lastRun = runs[runs.length - 1];
|
|
93
|
+
nodes.push({
|
|
94
|
+
state: currentFactoryState,
|
|
95
|
+
enteredAt: lastRun.endedAt ?? lastRun.startedAt,
|
|
96
|
+
isCurrent: false,
|
|
97
|
+
runs: [],
|
|
98
|
+
sideTrips: [],
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return nodes;
|
|
65
102
|
}
|
|
66
|
-
// ───
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
103
|
+
// ─── Build from events + runs ────────────────────────────────────
|
|
104
|
+
function buildFromEvents(runs, transitions, currentFactoryState) {
|
|
105
|
+
// Build a chronological queue of runs per state
|
|
106
|
+
const runQueues = new Map();
|
|
107
|
+
for (const run of runs) {
|
|
108
|
+
const state = runToState(run);
|
|
109
|
+
const queue = runQueues.get(state);
|
|
110
|
+
if (queue) {
|
|
111
|
+
queue.push(toRunInfo(run));
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
runQueues.set(state, [toRunInfo(run)]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
70
117
|
function consumeNextRun(state) {
|
|
71
|
-
const queue =
|
|
118
|
+
const queue = runQueues.get(state);
|
|
72
119
|
if (!queue || queue.length === 0)
|
|
73
120
|
return [];
|
|
74
|
-
|
|
75
|
-
return run ? [run] : [];
|
|
121
|
+
return [queue.shift()];
|
|
76
122
|
}
|
|
77
|
-
// Walk transitions and build nodes
|
|
78
123
|
const nodes = [];
|
|
79
124
|
let currentMainNode = null;
|
|
80
125
|
let currentSideTrip = null;
|
|
81
|
-
for (
|
|
82
|
-
const t = transitions[i];
|
|
126
|
+
for (const t of transitions) {
|
|
83
127
|
const isSideTrip = SIDE_TRIP_STATES.has(t.state);
|
|
84
128
|
if (isSideTrip) {
|
|
85
|
-
// Start or continue a side-trip
|
|
86
129
|
if (currentSideTrip) {
|
|
87
|
-
// Close previous side-trip first (nested side-trip is rare but handle it)
|
|
88
130
|
closeSideTrip(currentMainNode, currentSideTrip, t.state, t.at);
|
|
89
131
|
}
|
|
90
132
|
currentSideTrip = {
|
|
@@ -96,19 +138,15 @@ export function buildStateHistory(runs, feedEvents, currentFactoryState, activeR
|
|
|
96
138
|
};
|
|
97
139
|
}
|
|
98
140
|
else {
|
|
99
|
-
// Main-path state
|
|
100
141
|
if (currentSideTrip && currentMainNode) {
|
|
101
|
-
// Close the active side-trip — we're returning to the main path
|
|
102
|
-
// Consume runs for the side-trip state now
|
|
103
142
|
currentSideTrip.runs = consumeNextRun(currentSideTrip.state);
|
|
104
143
|
currentSideTrip.returnState = t.state;
|
|
105
144
|
currentSideTrip.returnedAt = t.at;
|
|
106
145
|
currentMainNode.sideTrips.push(currentSideTrip);
|
|
107
146
|
currentSideTrip = null;
|
|
108
147
|
}
|
|
109
|
-
// Skip duplicate
|
|
148
|
+
// Skip duplicate when returning from a side-trip to the same state
|
|
110
149
|
if (currentMainNode && currentMainNode.state === t.state) {
|
|
111
|
-
// Same main-path state revisited — don't create a new node
|
|
112
150
|
continue;
|
|
113
151
|
}
|
|
114
152
|
currentMainNode = {
|
|
@@ -122,40 +160,55 @@ export function buildStateHistory(runs, feedEvents, currentFactoryState, activeR
|
|
|
122
160
|
nodes.push(currentMainNode);
|
|
123
161
|
}
|
|
124
162
|
}
|
|
125
|
-
//
|
|
163
|
+
// Close any open side-trip
|
|
126
164
|
if (currentSideTrip && currentMainNode) {
|
|
127
165
|
currentSideTrip.runs = consumeNextRun(currentSideTrip.state);
|
|
128
166
|
currentSideTrip.returnState = currentFactoryState;
|
|
129
167
|
currentMainNode.sideTrips.push(currentSideTrip);
|
|
130
168
|
}
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
169
|
+
// Distribute remaining unconsumed runs to matching nodes
|
|
170
|
+
for (const [state, remaining] of runQueues) {
|
|
171
|
+
if (remaining.length === 0)
|
|
172
|
+
continue;
|
|
173
|
+
// Find the last node (or side-trip) matching this state
|
|
174
|
+
let target;
|
|
175
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
176
|
+
const node = nodes[i];
|
|
177
|
+
if (node.state === state) {
|
|
178
|
+
target = node;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
for (let j = node.sideTrips.length - 1; j >= 0; j--) {
|
|
182
|
+
if (node.sideTrips[j].state === state) {
|
|
183
|
+
target = node.sideTrips[j];
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (target)
|
|
188
|
+
break;
|
|
189
|
+
}
|
|
190
|
+
if (target) {
|
|
191
|
+
target.runs.push(...remaining);
|
|
192
|
+
}
|
|
193
|
+
remaining.length = 0;
|
|
150
194
|
}
|
|
151
|
-
|
|
195
|
+
return nodes;
|
|
196
|
+
}
|
|
197
|
+
// ─── Main entry point ────────────────────────────────────────────
|
|
198
|
+
export function buildStateHistory(runs, feedEvents, currentFactoryState, activeRunId) {
|
|
199
|
+
const transitions = extractTransitions(feedEvents);
|
|
200
|
+
const nodes = transitions.length > 0
|
|
201
|
+
? buildFromEvents(runs, transitions, currentFactoryState)
|
|
202
|
+
: buildFromRuns(runs, currentFactoryState);
|
|
203
|
+
if (nodes.length === 0)
|
|
204
|
+
return [];
|
|
152
205
|
markCurrent(nodes, currentFactoryState);
|
|
153
|
-
// Mark active run
|
|
154
206
|
if (activeRunId !== null) {
|
|
155
207
|
markActiveRun(nodes, activeRunId);
|
|
156
208
|
}
|
|
157
209
|
return nodes;
|
|
158
210
|
}
|
|
211
|
+
// ─── Helpers ─────────────────────────────────────────────────────
|
|
159
212
|
function closeSideTrip(mainNode, sideTrip, returnState, returnedAt) {
|
|
160
213
|
if (!mainNode)
|
|
161
214
|
return;
|
|
@@ -164,22 +217,18 @@ function closeSideTrip(mainNode, sideTrip, returnState, returnedAt) {
|
|
|
164
217
|
mainNode.sideTrips.push(sideTrip);
|
|
165
218
|
}
|
|
166
219
|
function markCurrent(nodes, currentState) {
|
|
167
|
-
// If current state is a side-trip state, mark the last main node as current
|
|
168
|
-
// (the side-trip is "in progress" from that main node)
|
|
169
220
|
if (SIDE_TRIP_STATES.has(currentState)) {
|
|
170
221
|
if (nodes.length > 0) {
|
|
171
222
|
nodes[nodes.length - 1].isCurrent = true;
|
|
172
223
|
}
|
|
173
224
|
return;
|
|
174
225
|
}
|
|
175
|
-
// Find the last node matching the current state
|
|
176
226
|
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
177
227
|
if (nodes[i].state === currentState) {
|
|
178
228
|
nodes[i].isCurrent = true;
|
|
179
229
|
return;
|
|
180
230
|
}
|
|
181
231
|
}
|
|
182
|
-
// Fallback: mark the last node
|
|
183
232
|
if (nodes.length > 0) {
|
|
184
233
|
nodes[nodes.length - 1].isCurrent = true;
|
|
185
234
|
}
|