everything-dev 0.0.1

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.
@@ -0,0 +1,243 @@
1
+ import { Box, render, Text, useApp, useInput } from "ink";
2
+ import { useEffect, useState } from "react";
3
+ import { colors, divider, gradients, icons, frames } from "../utils/theme";
4
+
5
+ export type ProcessStatus = "pending" | "starting" | "ready" | "error";
6
+
7
+ export interface ProcessState {
8
+ name: string;
9
+ status: ProcessStatus;
10
+ port: number;
11
+ message?: string;
12
+ source?: "local" | "remote";
13
+ }
14
+
15
+ export interface LogEntry {
16
+ source: string;
17
+ line: string;
18
+ timestamp: number;
19
+ isError?: boolean;
20
+ }
21
+
22
+ interface DevViewProps {
23
+ processes: ProcessState[];
24
+ logs: LogEntry[];
25
+ description: string;
26
+ onExit?: () => void;
27
+ onExportLogs?: () => void;
28
+ }
29
+
30
+ function StatusIcon({ status }: { status: ProcessStatus }) {
31
+ switch (status) {
32
+ case "pending":
33
+ return <Text color="gray">{icons.pending}</Text>;
34
+ case "starting":
35
+ return <Text color="#00ffff">{icons.scan}</Text>;
36
+ case "ready":
37
+ return <Text color="#00ff41">{icons.ok}</Text>;
38
+ case "error":
39
+ return <Text color="#ff3366">{icons.err}</Text>;
40
+ }
41
+ }
42
+
43
+ function getServiceColor(name: string): string {
44
+ return name === "host" ? "#00ffff" : name === "ui" ? "#ff00ff" : "#0080ff";
45
+ }
46
+
47
+ function ProcessRow({ proc }: { proc: ProcessState }) {
48
+ const color = getServiceColor(proc.name);
49
+ const portStr = proc.port > 0 ? `:${proc.port}` : "";
50
+ const sourceLabel = proc.source ? ` (${proc.source})` : "";
51
+
52
+ const statusText =
53
+ proc.status === "pending"
54
+ ? "waiting"
55
+ : proc.status === "starting"
56
+ ? "starting"
57
+ : proc.status === "ready"
58
+ ? "running"
59
+ : "failed";
60
+
61
+ return (
62
+ <Box>
63
+ <Text>{" "}</Text>
64
+ <StatusIcon status={proc.status} />
65
+ <Text> </Text>
66
+ <Text color={color} bold>{proc.name.toUpperCase().padEnd(6)}</Text>
67
+ <Text color="gray">{sourceLabel.padEnd(10)}</Text>
68
+ <Text color={proc.status === "ready" ? "#00ff41" : "gray"}>
69
+ {statusText}
70
+ </Text>
71
+ {proc.port > 0 && (
72
+ <Text color="#00ffff"> {portStr}</Text>
73
+ )}
74
+ </Box>
75
+ );
76
+ }
77
+
78
+ function LogLine({ entry }: { entry: LogEntry }) {
79
+ const color = getServiceColor(entry.source);
80
+
81
+ return (
82
+ <Box>
83
+ <Text color={color}>[{entry.source}]</Text>
84
+ <Text color={entry.isError ? "#ff3366" : undefined}> {entry.line}</Text>
85
+ </Box>
86
+ );
87
+ }
88
+
89
+ function DevView({
90
+ processes,
91
+ logs,
92
+ description,
93
+ onExit,
94
+ onExportLogs,
95
+ }: DevViewProps) {
96
+ const { exit } = useApp();
97
+
98
+ useInput((input, key) => {
99
+ if (input === "q" || (key.ctrl && input === "c")) {
100
+ onExit?.();
101
+ exit();
102
+ }
103
+ if (input === "l") {
104
+ onExportLogs?.();
105
+ exit();
106
+ }
107
+ });
108
+
109
+ const readyCount = processes.filter((p) => p.status === "ready").length;
110
+ const total = processes.length;
111
+ const allReady = readyCount === total;
112
+ const hostProcess = processes.find((p) => p.name === "host");
113
+ const hostPort = hostProcess?.port || 3000;
114
+
115
+ const recentLogs = logs.slice(-12);
116
+
117
+ return (
118
+ <Box flexDirection="column">
119
+ <Box marginBottom={0}>
120
+ <Text color="#00ffff">{frames.top(52)}</Text>
121
+ </Box>
122
+ <Box>
123
+ <Text>
124
+ {" "}
125
+ {icons.run} {gradients.cyber(description.toUpperCase())}
126
+ </Text>
127
+ </Box>
128
+ <Box marginBottom={1}>
129
+ <Text color="#00ffff">{frames.bottom(52)}</Text>
130
+ </Box>
131
+
132
+ {allReady && (
133
+ <Box marginBottom={1} flexDirection="column">
134
+ <Box>
135
+ <Text color="#00ff41">{" "}{icons.app} APP READY</Text>
136
+ </Box>
137
+ <Box>
138
+ <Text color="#00ff41" bold>{" "}{icons.arrow} http://localhost:{hostPort}</Text>
139
+ </Box>
140
+ </Box>
141
+ )}
142
+
143
+ <Box marginTop={0} marginBottom={0}>
144
+ <Text>{colors.dim(divider(52))}</Text>
145
+ </Box>
146
+
147
+ {processes.map((proc) => (
148
+ <ProcessRow key={proc.name} proc={proc} />
149
+ ))}
150
+
151
+ <Box marginTop={1} marginBottom={0}>
152
+ <Text>{colors.dim(divider(52))}</Text>
153
+ </Box>
154
+
155
+ <Box marginTop={0}>
156
+ <Text color={allReady ? "#00ff41" : "#00ffff"}>
157
+ {" "}
158
+ {allReady
159
+ ? `${icons.ok} All ${total} services running`
160
+ : `${icons.scan} ${readyCount}/${total} ready`}
161
+ </Text>
162
+ <Text color="gray"> {icons.dot} q quit {icons.dot} l logs</Text>
163
+ </Box>
164
+
165
+ {recentLogs.length > 0 && (
166
+ <>
167
+ <Box marginTop={1} marginBottom={0}>
168
+ <Text>{colors.dim(divider(52))}</Text>
169
+ </Box>
170
+ <Box flexDirection="column" marginTop={0}>
171
+ {recentLogs.map((entry, i) => (
172
+ <LogLine key={`${entry.timestamp}-${i}`} entry={entry} />
173
+ ))}
174
+ </Box>
175
+ </>
176
+ )}
177
+ </Box>
178
+ );
179
+ }
180
+
181
+ export interface DevViewHandle {
182
+ updateProcess: (
183
+ name: string,
184
+ status: ProcessStatus,
185
+ message?: string,
186
+ ) => void;
187
+ addLog: (source: string, line: string, isError?: boolean) => void;
188
+ unmount: () => void;
189
+ }
190
+
191
+ export function renderDevView(
192
+ initialProcesses: ProcessState[],
193
+ description: string,
194
+ env: Record<string, string>,
195
+ onExit?: () => void,
196
+ onExportLogs?: () => void,
197
+ ): DevViewHandle {
198
+ let processes = [...initialProcesses];
199
+ let logs: LogEntry[] = [];
200
+ let rerender: (() => void) | null = null;
201
+
202
+ const updateProcess = (
203
+ name: string,
204
+ status: ProcessStatus,
205
+ message?: string,
206
+ ) => {
207
+ processes = processes.map((p) =>
208
+ p.name === name ? { ...p, status, message } : p,
209
+ );
210
+ rerender?.();
211
+ };
212
+
213
+ const addLog = (source: string, line: string, isError = false) => {
214
+ logs = [...logs, { source, line, timestamp: Date.now(), isError }];
215
+ if (logs.length > 100) logs = logs.slice(-100);
216
+ rerender?.();
217
+ };
218
+
219
+ function DevViewWrapper() {
220
+ const [, forceUpdate] = useState(0);
221
+
222
+ useEffect(() => {
223
+ rerender = () => forceUpdate((n) => n + 1);
224
+ return () => {
225
+ rerender = null;
226
+ };
227
+ }, []);
228
+
229
+ return (
230
+ <DevView
231
+ processes={processes}
232
+ logs={logs}
233
+ description={description}
234
+ onExit={onExit}
235
+ onExportLogs={onExportLogs}
236
+ />
237
+ );
238
+ }
239
+
240
+ const { unmount } = render(<DevViewWrapper />);
241
+
242
+ return { updateProcess, addLog, unmount };
243
+ }
@@ -0,0 +1,173 @@
1
+ import React, { useState, useEffect } from "react";
2
+ import { render, Text, Box } from "ink";
3
+ import { colors, icons, gradients, divider } from "../utils/theme";
4
+
5
+ interface Endpoint {
6
+ name: string;
7
+ url: string;
8
+ type: "host" | "remote" | "ssr";
9
+ }
10
+
11
+ interface StatusResult {
12
+ ok: boolean;
13
+ ms: number;
14
+ }
15
+
16
+ interface StatusViewProps {
17
+ endpoints: Endpoint[];
18
+ env: string;
19
+ onComplete?: () => void;
20
+ }
21
+
22
+ async function checkHealth(url: string): Promise<StatusResult> {
23
+ const start = Date.now();
24
+ try {
25
+ const response = await fetch(url, { method: "HEAD" });
26
+ return { ok: response.ok, ms: Date.now() - start };
27
+ } catch {
28
+ return { ok: false, ms: Date.now() - start };
29
+ }
30
+ }
31
+
32
+ function StatusRow({
33
+ name,
34
+ url,
35
+ status,
36
+ }: {
37
+ name: string;
38
+ url: string;
39
+ status: StatusResult | "checking" | "pending";
40
+ }) {
41
+ const icon =
42
+ status === "checking"
43
+ ? colors.cyan("[~]")
44
+ : status === "pending"
45
+ ? colors.dim("[ ]")
46
+ : status.ok
47
+ ? colors.green("[-]")
48
+ : colors.magenta("[!]");
49
+
50
+ const timing =
51
+ status === "checking"
52
+ ? colors.cyan("checking...")
53
+ : status === "pending"
54
+ ? colors.dim("--")
55
+ : status.ok
56
+ ? colors.green(`${status.ms}ms`)
57
+ : colors.magenta(`${status.ms}ms`);
58
+
59
+ return (
60
+ <Box>
61
+ <Text>
62
+ {" "}
63
+ {icon} {name.padEnd(12)} {timing.padEnd(14)} {colors.dim(url)}
64
+ </Text>
65
+ </Box>
66
+ );
67
+ }
68
+
69
+ function StatusView({ endpoints, env, onComplete }: StatusViewProps) {
70
+ const [results, setResults] = useState<
71
+ Record<string, StatusResult | "checking" | "pending">
72
+ >(() => {
73
+ const initial: Record<string, "pending"> = {};
74
+ for (const ep of endpoints) {
75
+ initial[ep.name] = "pending";
76
+ }
77
+ return initial;
78
+ });
79
+
80
+ const [done, setDone] = useState(false);
81
+
82
+ useEffect(() => {
83
+ let mounted = true;
84
+
85
+ async function runChecks() {
86
+ for (const ep of endpoints) {
87
+ if (!mounted) return;
88
+ setResults((prev) => ({ ...prev, [ep.name]: "checking" }));
89
+
90
+ const result = await checkHealth(ep.url);
91
+
92
+ if (!mounted) return;
93
+ setResults((prev) => ({ ...prev, [ep.name]: result }));
94
+ }
95
+ setDone(true);
96
+ onComplete?.();
97
+ }
98
+
99
+ runChecks();
100
+
101
+ return () => {
102
+ mounted = false;
103
+ };
104
+ }, [endpoints, onComplete]);
105
+
106
+ const healthy = Object.values(results).filter(
107
+ (r) => typeof r === "object" && r.ok
108
+ ).length;
109
+ const total = endpoints.length;
110
+ const checking = Object.values(results).filter(
111
+ (r) => r === "checking"
112
+ ).length;
113
+
114
+ return (
115
+ <Box flexDirection="column">
116
+ <Box marginBottom={1}>
117
+ <Text>
118
+ {colors.cyan(`+${"-".repeat(46)}+`)}
119
+ </Text>
120
+ </Box>
121
+ <Box marginBottom={1}>
122
+ <Text>
123
+ {" "}
124
+ {icons.scan} {gradients.cyber(`SCANNING ${env} endpoints`)}
125
+ </Text>
126
+ </Box>
127
+ <Box marginBottom={1}>
128
+ <Text>{colors.cyan(`+${"-".repeat(46)}+`)}</Text>
129
+ </Box>
130
+
131
+ {endpoints.map((ep) => (
132
+ <StatusRow
133
+ key={ep.name}
134
+ name={ep.name}
135
+ url={ep.url}
136
+ status={results[ep.name]}
137
+ />
138
+ ))}
139
+
140
+ <Box marginTop={1}>
141
+ <Text>{colors.dim(divider(48))}</Text>
142
+ </Box>
143
+ <Box>
144
+ <Text>
145
+ {" "}
146
+ {done
147
+ ? colors.green(`${healthy}/${total} healthy`)
148
+ : colors.cyan(`${checking} checking...`)}
149
+ </Text>
150
+ </Box>
151
+ </Box>
152
+ );
153
+ }
154
+
155
+ export function renderStatusView(
156
+ endpoints: Endpoint[],
157
+ env: string
158
+ ): Promise<void> {
159
+ return new Promise((resolve) => {
160
+ const { unmount } = render(
161
+ <StatusView
162
+ endpoints={endpoints}
163
+ env={env}
164
+ onComplete={() => {
165
+ setTimeout(() => {
166
+ unmount();
167
+ resolve();
168
+ }, 100);
169
+ }}
170
+ />
171
+ );
172
+ });
173
+ }
@@ -0,0 +1,110 @@
1
+ import { colors, icons } from "../utils/theme";
2
+ import type { ProcessState, ProcessStatus } from "./dev-view";
3
+
4
+ export interface StreamingViewHandle {
5
+ updateProcess: (name: string, status: ProcessStatus, message?: string) => void;
6
+ addLog: (source: string, line: string, isError?: boolean) => void;
7
+ unmount: () => void;
8
+ }
9
+
10
+ const getTimestamp = (): string => {
11
+ const now = new Date();
12
+ return `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}`;
13
+ };
14
+
15
+ const write = (text: string) => process.stdout.write(text + "\n");
16
+
17
+ const getServiceColor = (name: string): (s: string) => string => {
18
+ if (name === "host") return colors.cyan;
19
+ if (name === "ui" || name === "ui-ssr") return colors.magenta;
20
+ if (name === "api") return colors.blue;
21
+ return colors.white;
22
+ };
23
+
24
+ const getStatusIcon = (status: ProcessStatus): string => {
25
+ switch (status) {
26
+ case "pending": return icons.pending;
27
+ case "starting": return icons.scan;
28
+ case "ready": return icons.ok;
29
+ case "error": return icons.err;
30
+ }
31
+ };
32
+
33
+ export function renderStreamingView(
34
+ initialProcesses: ProcessState[],
35
+ description: string,
36
+ _env: Record<string, string>,
37
+ onExit?: () => void,
38
+ _onExportLogs?: () => void,
39
+ ): StreamingViewHandle {
40
+ const processes = new Map<string, ProcessState>();
41
+ for (const p of initialProcesses) {
42
+ processes.set(p.name, { ...p });
43
+ }
44
+
45
+ let allReadyPrinted = false;
46
+ const hostProcess = initialProcesses.find(p => p.name === "host");
47
+ const hostPort = hostProcess?.port || 3000;
48
+
49
+ console.log();
50
+ console.log(colors.cyan(`${"─".repeat(52)}`));
51
+ console.log(` ${icons.run} ${colors.cyan(description.toUpperCase())}`);
52
+ console.log(colors.cyan(`${"─".repeat(52)}`));
53
+ console.log();
54
+
55
+ for (const proc of initialProcesses) {
56
+ const color = getServiceColor(proc.name);
57
+ const sourceLabel = proc.source ? ` (${proc.source})` : "";
58
+ console.log(`${colors.dim(`[${getTimestamp()}]`)} ${color(`[${proc.name.toUpperCase()}]`)} ${icons.pending} waiting${sourceLabel}`);
59
+ }
60
+
61
+ const checkAllReady = () => {
62
+ if (allReadyPrinted) return;
63
+
64
+ const allReady = Array.from(processes.values()).every(p => p.status === "ready");
65
+ if (allReady) {
66
+ allReadyPrinted = true;
67
+ console.log();
68
+ console.log(colors.dim(`${"─".repeat(52)}`));
69
+ console.log(colors.green(`${icons.ok} All ${processes.size} services ready`));
70
+ console.log(colors.green(`${icons.arrow} http://localhost:${hostPort}`));
71
+ console.log(colors.dim(`${"─".repeat(52)}`));
72
+ console.log();
73
+ }
74
+ };
75
+
76
+ const updateProcess = (name: string, status: ProcessStatus, message?: string) => {
77
+ const proc = processes.get(name);
78
+ if (!proc) return;
79
+
80
+ proc.status = status;
81
+ if (message) proc.message = message;
82
+
83
+ const color = getServiceColor(name);
84
+ const icon = getStatusIcon(status);
85
+ const statusText = status === "ready" ? "ready" : status === "starting" ? "starting" : status === "error" ? "failed" : "waiting";
86
+ const portStr = proc.port > 0 && status === "ready" ? ` :${proc.port}` : "";
87
+
88
+ write(`${colors.dim(`[${getTimestamp()}]`)} ${color(`[${name.toUpperCase()}]`)} ${status === "ready" ? colors.green(icon) : status === "error" ? colors.error(icon) : icon} ${statusText}${portStr}`);
89
+
90
+ checkAllReady();
91
+ };
92
+
93
+ const addLog = (source: string, line: string, isError = false) => {
94
+ const color = getServiceColor(source);
95
+ const logColor = isError ? colors.error : colors.dim;
96
+ write(`${colors.dim(`[${getTimestamp()}]`)} ${color(`[${source.toUpperCase()}]`)} ${colors.dim("│")} ${logColor(line)}`);
97
+ };
98
+
99
+ const unmount = () => {
100
+ onExit?.();
101
+ };
102
+
103
+ process.on("SIGINT", () => {
104
+ console.log();
105
+ console.log(colors.dim(`[${getTimestamp()}] Shutting down...`));
106
+ unmount();
107
+ });
108
+
109
+ return { updateProcess, addLog, unmount };
110
+ }