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.
- package/package.json +40 -0
- package/src/cli.ts +735 -0
- package/src/components/dev-view.tsx +243 -0
- package/src/components/status-view.tsx +173 -0
- package/src/components/streaming-view.ts +110 -0
- package/src/config.ts +214 -0
- package/src/contract.ts +364 -0
- package/src/index.ts +3 -0
- package/src/lib/env.ts +91 -0
- package/src/lib/near-cli.ts +289 -0
- package/src/lib/nova.ts +254 -0
- package/src/lib/orchestrator.ts +213 -0
- package/src/lib/process.ts +370 -0
- package/src/lib/secrets.ts +28 -0
- package/src/plugin.ts +930 -0
- package/src/utils/banner.ts +19 -0
- package/src/utils/run.ts +21 -0
- package/src/utils/theme.ts +101 -0
- package/tsconfig.json +22 -0
|
@@ -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
|
+
}
|