everything-dev 0.3.1 → 0.3.3
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/package.json +1 -1
- package/src/components/dev-view.tsx +254 -243
- package/src/components/streaming-view.ts +128 -97
- package/src/lib/process.ts +1 -0
- package/src/lib/session-recorder/server.ts +225 -224
- package/src/plugin.ts +114 -107
package/package.json
CHANGED
|
@@ -6,279 +6,290 @@ import { colors, divider, frames, gradients, icons } from "../utils/theme";
|
|
|
6
6
|
export type ProcessStatus = "pending" | "starting" | "ready" | "error";
|
|
7
7
|
|
|
8
8
|
export interface ProcessState {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
name: string;
|
|
10
|
+
status: ProcessStatus;
|
|
11
|
+
port: number;
|
|
12
|
+
message?: string;
|
|
13
|
+
source?: "local" | "remote";
|
|
14
|
+
proxyTarget?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
export interface LogEntry {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
source: string;
|
|
19
|
+
line: string;
|
|
20
|
+
timestamp: number;
|
|
21
|
+
isError?: boolean;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
interface DevViewProps {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
processes: ProcessState[];
|
|
26
|
+
logs: LogEntry[];
|
|
27
|
+
description: string;
|
|
28
|
+
proxyTarget?: string;
|
|
29
|
+
onExit?: () => Promise<void> | void;
|
|
30
|
+
onExportLogs?: () => Promise<void> | void;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
function StatusIcon({ status }: { status: ProcessStatus }) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
switch (status) {
|
|
35
|
+
case "pending":
|
|
36
|
+
return <Text color="gray">{icons.pending}</Text>;
|
|
37
|
+
case "starting":
|
|
38
|
+
return <Text color="#00ffff">{icons.scan}</Text>;
|
|
39
|
+
case "ready":
|
|
40
|
+
return <Text color="#00ff41">{icons.ok}</Text>;
|
|
41
|
+
case "error":
|
|
42
|
+
return <Text color="#ff3366">{icons.err}</Text>;
|
|
43
|
+
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function getServiceColor(name: string): string {
|
|
47
|
-
|
|
47
|
+
return name === "host" ? "#00ffff" : name === "ui" ? "#ff00ff" : "#0080ff";
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
function ProcessRow({ proc }: { proc: ProcessState }) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
51
|
+
const color = getServiceColor(proc.name);
|
|
52
|
+
const portStr = proc.port > 0 ? `:${proc.port}` : "";
|
|
53
|
+
const sourceLabel = proc.source ? ` (${proc.source})` : "";
|
|
54
|
+
|
|
55
|
+
const statusText =
|
|
56
|
+
proc.status === "pending"
|
|
57
|
+
? "waiting"
|
|
58
|
+
: proc.status === "starting"
|
|
59
|
+
? "starting"
|
|
60
|
+
: proc.status === "ready"
|
|
61
|
+
? "running"
|
|
62
|
+
: "failed";
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<Box>
|
|
66
|
+
<Text>{" "}</Text>
|
|
67
|
+
<StatusIcon status={proc.status} />
|
|
68
|
+
<Text> </Text>
|
|
69
|
+
<Text color={color} bold>
|
|
70
|
+
{proc.name.toUpperCase().padEnd(6)}
|
|
71
|
+
</Text>
|
|
72
|
+
<Text color="gray">{sourceLabel.padEnd(10)}</Text>
|
|
73
|
+
<Text color={proc.status === "ready" ? "#00ff41" : "gray"}>
|
|
74
|
+
{statusText}
|
|
75
|
+
</Text>
|
|
76
|
+
{proc.port > 0 && <Text color="#00ffff"> {portStr}</Text>}
|
|
77
|
+
</Box>
|
|
78
|
+
);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
function LogLine({ entry }: { entry: LogEntry }) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
82
|
+
const color = getServiceColor(entry.source);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Box>
|
|
86
|
+
<Text color={color}>[{entry.source}]</Text>
|
|
87
|
+
<Text color={entry.isError ? "#ff3366" : undefined}>
|
|
88
|
+
{" "}
|
|
89
|
+
{linkify(entry.line)}
|
|
90
|
+
</Text>
|
|
91
|
+
</Box>
|
|
92
|
+
);
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
function truncateUrl(url: string, maxLen: number): string {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
96
|
+
if (url.length <= maxLen) return url;
|
|
97
|
+
try {
|
|
98
|
+
const parsed = new URL(url);
|
|
99
|
+
const host = parsed.host;
|
|
100
|
+
if (host.length > maxLen - 10) {
|
|
101
|
+
return `${host.slice(0, maxLen - 13)}...`;
|
|
102
|
+
}
|
|
103
|
+
return host;
|
|
104
|
+
} catch {
|
|
105
|
+
return `${url.slice(0, maxLen - 3)}...`;
|
|
106
|
+
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
function DevView({
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
110
|
+
processes,
|
|
111
|
+
logs,
|
|
112
|
+
description,
|
|
113
|
+
proxyTarget,
|
|
114
|
+
onExit,
|
|
115
|
+
onExportLogs,
|
|
116
116
|
}: DevViewProps) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
117
|
+
const { exit } = useApp();
|
|
118
|
+
const [isShuttingDown, setIsShuttingDown] = useState(false);
|
|
119
|
+
|
|
120
|
+
useInput((input, key) => {
|
|
121
|
+
if (isShuttingDown) return;
|
|
122
|
+
|
|
123
|
+
if (input === "q" || (key.ctrl && input === "c")) {
|
|
124
|
+
setIsShuttingDown(true);
|
|
125
|
+
// Run cleanup async, then force exit
|
|
126
|
+
Promise.resolve(onExit?.()).then(() => {
|
|
127
|
+
exit();
|
|
128
|
+
process.exit(0);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (input === "l") {
|
|
132
|
+
setIsShuttingDown(true);
|
|
133
|
+
// Run export logs async, then force exit
|
|
134
|
+
Promise.resolve(onExportLogs?.()).then(() => {
|
|
135
|
+
exit();
|
|
136
|
+
process.exit(0);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const readyCount = processes.filter((p) => p.status === "ready").length;
|
|
142
|
+
const total = processes.length;
|
|
143
|
+
const allReady = readyCount === total;
|
|
144
|
+
const hostProcess = processes.find((p) => p.name === "host");
|
|
145
|
+
const hostPort = hostProcess?.port || 3000;
|
|
146
|
+
|
|
147
|
+
const recentLogs = logs.slice(-12);
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<Box flexDirection="column">
|
|
151
|
+
<Box marginBottom={0}>
|
|
152
|
+
<Text color="#00ffff">{frames.top(52)}</Text>
|
|
153
|
+
</Box>
|
|
154
|
+
<Box>
|
|
155
|
+
<Text>
|
|
156
|
+
{" "}
|
|
157
|
+
{icons.run} {gradients.cyber(description.toUpperCase())}
|
|
158
|
+
</Text>
|
|
159
|
+
</Box>
|
|
160
|
+
<Box marginBottom={1}>
|
|
161
|
+
<Text color="#00ffff">{frames.bottom(52)}</Text>
|
|
162
|
+
</Box>
|
|
163
|
+
|
|
164
|
+
{allReady && (
|
|
165
|
+
<Box marginBottom={1} flexDirection="column">
|
|
166
|
+
<Box>
|
|
167
|
+
<Text color="#00ff41">
|
|
168
|
+
{" "}
|
|
169
|
+
{icons.app} APP READY
|
|
170
|
+
</Text>
|
|
171
|
+
</Box>
|
|
172
|
+
<Box>
|
|
173
|
+
<Text color="#00ff41" bold>
|
|
174
|
+
{" "}
|
|
175
|
+
{icons.arrow} http://localhost:{hostPort}
|
|
176
|
+
</Text>
|
|
177
|
+
</Box>
|
|
178
|
+
</Box>
|
|
179
|
+
)}
|
|
180
|
+
|
|
181
|
+
{proxyTarget && (
|
|
182
|
+
<Box marginBottom={1}>
|
|
183
|
+
<Text color="#ffaa00">
|
|
184
|
+
{" "}
|
|
185
|
+
{icons.arrow} API PROXY → {truncateUrl(proxyTarget, 38)}
|
|
186
|
+
</Text>
|
|
187
|
+
</Box>
|
|
188
|
+
)}
|
|
189
|
+
|
|
190
|
+
<Box marginTop={0} marginBottom={0}>
|
|
191
|
+
<Text>{colors.dim(divider(52))}</Text>
|
|
192
|
+
</Box>
|
|
193
|
+
|
|
194
|
+
{processes.map((proc) => (
|
|
195
|
+
<ProcessRow key={proc.name} proc={proc} />
|
|
196
|
+
))}
|
|
197
|
+
|
|
198
|
+
<Box marginTop={1} marginBottom={0}>
|
|
199
|
+
<Text>{colors.dim(divider(52))}</Text>
|
|
200
|
+
</Box>
|
|
201
|
+
|
|
202
|
+
<Box marginTop={0}>
|
|
203
|
+
<Text color={allReady ? "#00ff41" : "#00ffff"}>
|
|
204
|
+
{" "}
|
|
205
|
+
{allReady
|
|
206
|
+
? `${icons.ok} All ${total} services running`
|
|
207
|
+
: `${icons.scan} ${readyCount}/${total} ready`}
|
|
208
|
+
</Text>
|
|
209
|
+
<Text color="gray">
|
|
210
|
+
{" "}
|
|
211
|
+
{icons.dot} q quit {icons.dot} l logs
|
|
212
|
+
</Text>
|
|
213
|
+
</Box>
|
|
214
|
+
|
|
215
|
+
{recentLogs.length > 0 && (
|
|
216
|
+
<>
|
|
217
|
+
<Box marginTop={1} marginBottom={0}>
|
|
218
|
+
<Text>{colors.dim(divider(52))}</Text>
|
|
219
|
+
</Box>
|
|
220
|
+
<Box flexDirection="column" marginTop={0}>
|
|
221
|
+
{recentLogs.map((entry, i) => (
|
|
222
|
+
<LogLine key={`${entry.timestamp}-${i}`} entry={entry} />
|
|
223
|
+
))}
|
|
224
|
+
</Box>
|
|
225
|
+
</>
|
|
226
|
+
)}
|
|
227
|
+
</Box>
|
|
228
|
+
);
|
|
218
229
|
}
|
|
219
230
|
|
|
220
231
|
export interface DevViewHandle {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
232
|
+
updateProcess: (
|
|
233
|
+
name: string,
|
|
234
|
+
status: ProcessStatus,
|
|
235
|
+
message?: string,
|
|
236
|
+
) => void;
|
|
237
|
+
addLog: (source: string, line: string, isError?: boolean) => void;
|
|
238
|
+
unmount: () => void;
|
|
228
239
|
}
|
|
229
240
|
|
|
230
241
|
export function renderDevView(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
242
|
+
initialProcesses: ProcessState[],
|
|
243
|
+
description: string,
|
|
244
|
+
env: Record<string, string>,
|
|
245
|
+
onExit?: () => Promise<void> | void,
|
|
246
|
+
onExportLogs?: () => Promise<void> | void,
|
|
236
247
|
): DevViewHandle {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
248
|
+
let processes = [...initialProcesses];
|
|
249
|
+
let logs: LogEntry[] = [];
|
|
250
|
+
let rerender: (() => void) | null = null;
|
|
251
|
+
const proxyTarget = env.API_PROXY;
|
|
252
|
+
|
|
253
|
+
const updateProcess = (
|
|
254
|
+
name: string,
|
|
255
|
+
status: ProcessStatus,
|
|
256
|
+
message?: string,
|
|
257
|
+
) => {
|
|
258
|
+
processes = processes.map((p) =>
|
|
259
|
+
p.name === name ? { ...p, status, message } : p,
|
|
260
|
+
);
|
|
261
|
+
rerender?.();
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
const addLog = (source: string, line: string, isError = false) => {
|
|
265
|
+
logs = [...logs, { source, line, timestamp: Date.now(), isError }];
|
|
266
|
+
if (logs.length > 100) logs = logs.slice(-100);
|
|
267
|
+
rerender?.();
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
function DevViewWrapper() {
|
|
271
|
+
const [, forceUpdate] = useState(0);
|
|
272
|
+
|
|
273
|
+
useEffect(() => {
|
|
274
|
+
rerender = () => forceUpdate((n) => n + 1);
|
|
275
|
+
return () => {
|
|
276
|
+
rerender = null;
|
|
277
|
+
};
|
|
278
|
+
}, []);
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<DevView
|
|
282
|
+
processes={processes}
|
|
283
|
+
logs={logs}
|
|
284
|
+
description={description}
|
|
285
|
+
proxyTarget={proxyTarget}
|
|
286
|
+
onExit={onExit}
|
|
287
|
+
onExportLogs={onExportLogs}
|
|
288
|
+
/>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const { unmount } = render(<DevViewWrapper />);
|
|
293
|
+
|
|
294
|
+
return { updateProcess, addLog, unmount };
|
|
284
295
|
}
|