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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "everything-dev",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "exports": {
@@ -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
- name: string;
10
- status: ProcessStatus;
11
- port: number;
12
- message?: string;
13
- source?: "local" | "remote";
14
- proxyTarget?: string;
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
- source: string;
19
- line: string;
20
- timestamp: number;
21
- isError?: boolean;
18
+ source: string;
19
+ line: string;
20
+ timestamp: number;
21
+ isError?: boolean;
22
22
  }
23
23
 
24
24
  interface DevViewProps {
25
- processes: ProcessState[];
26
- logs: LogEntry[];
27
- description: string;
28
- proxyTarget?: string;
29
- onExit?: () => void;
30
- onExportLogs?: () => void;
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
- 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
- }
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
- return name === "host" ? "#00ffff" : name === "ui" ? "#ff00ff" : "#0080ff";
47
+ return name === "host" ? "#00ffff" : name === "ui" ? "#ff00ff" : "#0080ff";
48
48
  }
49
49
 
50
50
  function ProcessRow({ proc }: { proc: ProcessState }) {
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
- );
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
- 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
- );
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
- 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
- }
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
- processes,
111
- logs,
112
- description,
113
- proxyTarget,
114
- onExit,
115
- onExportLogs,
110
+ processes,
111
+ logs,
112
+ description,
113
+ proxyTarget,
114
+ onExit,
115
+ onExportLogs,
116
116
  }: DevViewProps) {
117
- const { exit } = useApp();
118
-
119
- useInput((input, key) => {
120
- if (input === "q" || (key.ctrl && input === "c")) {
121
- onExit?.();
122
- exit();
123
- }
124
- if (input === "l") {
125
- onExportLogs?.();
126
- exit();
127
- }
128
- });
129
-
130
- const readyCount = processes.filter((p) => p.status === "ready").length;
131
- const total = processes.length;
132
- const allReady = readyCount === total;
133
- const hostProcess = processes.find((p) => p.name === "host");
134
- const hostPort = hostProcess?.port || 3000;
135
-
136
- const recentLogs = logs.slice(-12);
137
-
138
- return (
139
- <Box flexDirection="column">
140
- <Box marginBottom={0}>
141
- <Text color="#00ffff">{frames.top(52)}</Text>
142
- </Box>
143
- <Box>
144
- <Text>
145
- {" "}
146
- {icons.run} {gradients.cyber(description.toUpperCase())}
147
- </Text>
148
- </Box>
149
- <Box marginBottom={1}>
150
- <Text color="#00ffff">{frames.bottom(52)}</Text>
151
- </Box>
152
-
153
- {allReady && (
154
- <Box marginBottom={1} flexDirection="column">
155
- <Box>
156
- <Text color="#00ff41">
157
- {" "}
158
- {icons.app} APP READY
159
- </Text>
160
- </Box>
161
- <Box>
162
- <Text color="#00ff41" bold>
163
- {" "}
164
- {icons.arrow} http://localhost:{hostPort}
165
- </Text>
166
- </Box>
167
- </Box>
168
- )}
169
-
170
- {proxyTarget && (
171
- <Box marginBottom={1}>
172
- <Text color="#ffaa00">
173
- {" "}
174
- {icons.arrow} API PROXY → {truncateUrl(proxyTarget, 38)}
175
- </Text>
176
- </Box>
177
- )}
178
-
179
- <Box marginTop={0} marginBottom={0}>
180
- <Text>{colors.dim(divider(52))}</Text>
181
- </Box>
182
-
183
- {processes.map((proc) => (
184
- <ProcessRow key={proc.name} proc={proc} />
185
- ))}
186
-
187
- <Box marginTop={1} marginBottom={0}>
188
- <Text>{colors.dim(divider(52))}</Text>
189
- </Box>
190
-
191
- <Box marginTop={0}>
192
- <Text color={allReady ? "#00ff41" : "#00ffff"}>
193
- {" "}
194
- {allReady
195
- ? `${icons.ok} All ${total} services running`
196
- : `${icons.scan} ${readyCount}/${total} ready`}
197
- </Text>
198
- <Text color="gray">
199
- {" "}
200
- {icons.dot} q quit {icons.dot} l logs
201
- </Text>
202
- </Box>
203
-
204
- {recentLogs.length > 0 && (
205
- <>
206
- <Box marginTop={1} marginBottom={0}>
207
- <Text>{colors.dim(divider(52))}</Text>
208
- </Box>
209
- <Box flexDirection="column" marginTop={0}>
210
- {recentLogs.map((entry, i) => (
211
- <LogLine key={`${entry.timestamp}-${i}`} entry={entry} />
212
- ))}
213
- </Box>
214
- </>
215
- )}
216
- </Box>
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
- updateProcess: (
222
- name: string,
223
- status: ProcessStatus,
224
- message?: string,
225
- ) => void;
226
- addLog: (source: string, line: string, isError?: boolean) => void;
227
- unmount: () => void;
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
- initialProcesses: ProcessState[],
232
- description: string,
233
- env: Record<string, string>,
234
- onExit?: () => void,
235
- onExportLogs?: () => void,
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
- let processes = [...initialProcesses];
238
- let logs: LogEntry[] = [];
239
- let rerender: (() => void) | null = null;
240
- const proxyTarget = env.API_PROXY;
241
-
242
- const updateProcess = (
243
- name: string,
244
- status: ProcessStatus,
245
- message?: string,
246
- ) => {
247
- processes = processes.map((p) =>
248
- p.name === name ? { ...p, status, message } : p,
249
- );
250
- rerender?.();
251
- };
252
-
253
- const addLog = (source: string, line: string, isError = false) => {
254
- logs = [...logs, { source, line, timestamp: Date.now(), isError }];
255
- if (logs.length > 100) logs = logs.slice(-100);
256
- rerender?.();
257
- };
258
-
259
- function DevViewWrapper() {
260
- const [, forceUpdate] = useState(0);
261
-
262
- useEffect(() => {
263
- rerender = () => forceUpdate((n) => n + 1);
264
- return () => {
265
- rerender = null;
266
- };
267
- }, []);
268
-
269
- return (
270
- <DevView
271
- processes={processes}
272
- logs={logs}
273
- description={description}
274
- proxyTarget={proxyTarget}
275
- onExit={onExit}
276
- onExportLogs={onExportLogs}
277
- />
278
- );
279
- }
280
-
281
- const { unmount } = render(<DevViewWrapper />);
282
-
283
- return { updateProcess, addLog, unmount };
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
  }