everything-dev 1.27.0 → 1.28.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.
Files changed (114) hide show
  1. package/dist/cli/infra.cjs +1 -1
  2. package/dist/cli/infra.mjs +1 -1
  3. package/dist/cli/init.cjs +34 -9
  4. package/dist/cli/init.cjs.map +1 -1
  5. package/dist/cli/init.d.cts +2 -1
  6. package/dist/cli/init.d.cts.map +1 -1
  7. package/dist/cli/init.d.mts +2 -1
  8. package/dist/cli/init.d.mts.map +1 -1
  9. package/dist/cli/init.mjs +34 -9
  10. package/dist/cli/init.mjs.map +1 -1
  11. package/dist/cli/prompts.cjs +28 -24
  12. package/dist/cli/prompts.cjs.map +1 -1
  13. package/dist/cli/prompts.mjs +27 -24
  14. package/dist/cli/prompts.mjs.map +1 -1
  15. package/dist/cli/sync.cjs +40 -3
  16. package/dist/cli/sync.cjs.map +1 -1
  17. package/dist/cli/sync.mjs +40 -3
  18. package/dist/cli/sync.mjs.map +1 -1
  19. package/dist/cli.cjs +187 -12
  20. package/dist/cli.cjs.map +1 -1
  21. package/dist/cli.mjs +186 -11
  22. package/dist/cli.mjs.map +1 -1
  23. package/dist/config.cjs +1 -0
  24. package/dist/config.cjs.map +1 -1
  25. package/dist/config.d.cts.map +1 -1
  26. package/dist/config.d.mts.map +1 -1
  27. package/dist/config.mjs +1 -0
  28. package/dist/config.mjs.map +1 -1
  29. package/dist/contract.cjs +1 -1
  30. package/dist/contract.cjs.map +1 -1
  31. package/dist/contract.d.cts +38 -34
  32. package/dist/contract.d.cts.map +1 -1
  33. package/dist/contract.d.mts +38 -34
  34. package/dist/contract.d.mts.map +1 -1
  35. package/dist/contract.mjs +1 -0
  36. package/dist/contract.mjs.map +1 -1
  37. package/dist/dev-session.cjs +0 -1
  38. package/dist/dev-session.mjs +1 -1
  39. package/dist/index.cjs +0 -2
  40. package/dist/index.d.cts +2 -2
  41. package/dist/index.d.mts +2 -2
  42. package/dist/index.mjs +0 -1
  43. package/dist/near-cli.cjs +1 -1
  44. package/dist/near-cli.mjs +1 -1
  45. package/dist/orchestrator.cjs +1 -1
  46. package/dist/orchestrator.mjs +1 -1
  47. package/dist/plugin.cjs +183 -151
  48. package/dist/plugin.cjs.map +1 -1
  49. package/dist/plugin.d.cts +67 -34
  50. package/dist/plugin.d.cts.map +1 -1
  51. package/dist/plugin.d.mts +66 -34
  52. package/dist/plugin.d.mts.map +1 -1
  53. package/dist/plugin.mjs +173 -142
  54. package/dist/plugin.mjs.map +1 -1
  55. package/dist/service-descriptor.d.cts +34 -0
  56. package/dist/service-descriptor.d.cts.map +1 -0
  57. package/dist/service-descriptor.d.mts +36 -0
  58. package/dist/service-descriptor.d.mts.map +1 -0
  59. package/dist/types.d.cts +2 -2
  60. package/dist/types.d.mts +2 -2
  61. package/dist/utils/run.cjs +9 -20
  62. package/dist/utils/run.cjs.map +1 -1
  63. package/dist/utils/run.mjs +9 -20
  64. package/dist/utils/run.mjs.map +1 -1
  65. package/package.json +2 -2
  66. package/src/api-contract.ts +0 -623
  67. package/src/app.ts +0 -193
  68. package/src/cli/catalog.ts +0 -49
  69. package/src/cli/framework-version.ts +0 -61
  70. package/src/cli/help.ts +0 -13
  71. package/src/cli/infra.ts +0 -190
  72. package/src/cli/init.ts +0 -1145
  73. package/src/cli/parse.ts +0 -147
  74. package/src/cli/prompts.ts +0 -135
  75. package/src/cli/snapshot.ts +0 -46
  76. package/src/cli/status.ts +0 -99
  77. package/src/cli/sync.ts +0 -429
  78. package/src/cli/timing.ts +0 -63
  79. package/src/cli/upgrade.ts +0 -869
  80. package/src/cli.ts +0 -516
  81. package/src/components/dev-view.tsx +0 -352
  82. package/src/components/streaming-view.ts +0 -177
  83. package/src/config.ts +0 -893
  84. package/src/contract.meta.ts +0 -140
  85. package/src/contract.ts +0 -326
  86. package/src/dev-logs.ts +0 -92
  87. package/src/dev-session.ts +0 -283
  88. package/src/fastkv.ts +0 -181
  89. package/src/index.ts +0 -8
  90. package/src/integrity.ts +0 -138
  91. package/src/internal/manifest-normalizer.ts +0 -290
  92. package/src/merge.ts +0 -187
  93. package/src/mf.ts +0 -147
  94. package/src/near-cli.ts +0 -259
  95. package/src/network.ts +0 -3
  96. package/src/orchestrator.ts +0 -493
  97. package/src/plugin.ts +0 -1799
  98. package/src/sdk.ts +0 -14
  99. package/src/service-descriptor.ts +0 -281
  100. package/src/shared.ts +0 -249
  101. package/src/sidebar.ts +0 -140
  102. package/src/types.ts +0 -330
  103. package/src/ui/head.ts +0 -83
  104. package/src/ui/index.ts +0 -5
  105. package/src/ui/metadata.ts +0 -95
  106. package/src/ui/router.ts +0 -88
  107. package/src/ui/runtime.ts +0 -42
  108. package/src/ui/types.ts +0 -65
  109. package/src/utils/banner.ts +0 -21
  110. package/src/utils/linkify.ts +0 -11
  111. package/src/utils/path-match.ts +0 -16
  112. package/src/utils/run.ts +0 -31
  113. package/src/utils/save-config.ts +0 -20
  114. package/src/utils/theme.ts +0 -39
@@ -1,352 +0,0 @@
1
- import { Box, render, Text, useApp, useInput } from "ink";
2
- import { useEffect, useState } from "react";
3
- import type { SourceMode } from "../types";
4
- import { linkify } from "../utils/linkify";
5
- import { colors, divider, frames, gradients, icons } from "../utils/theme";
6
-
7
- const PLUGIN_PREFIX = "plugin:";
8
-
9
- export type ProcessStatus = "pending" | "starting" | "ready" | "error";
10
-
11
- export interface ProcessState {
12
- name: string;
13
- status: ProcessStatus;
14
- port: number;
15
- message?: string;
16
- source?: SourceMode;
17
- }
18
-
19
- export interface LogEntry {
20
- id: string;
21
- source: string;
22
- line: string;
23
- timestamp: number;
24
- isError?: boolean;
25
- }
26
-
27
- interface DevViewProps {
28
- processes: ProcessState[];
29
- logs: LogEntry[];
30
- description: string;
31
- proxyTarget?: string;
32
- onExit?: () => Promise<void> | void;
33
- onExportLogs?: () => Promise<void> | void;
34
- }
35
-
36
- function StatusIcon({ status }: { status: ProcessStatus }) {
37
- switch (status) {
38
- case "pending":
39
- return <Text color="gray">{icons.pending}</Text>;
40
- case "starting":
41
- return <Text color="#00ffff">{icons.scan}</Text>;
42
- case "ready":
43
- return <Text color="#00ff41">{icons.ok}</Text>;
44
- case "error":
45
- return <Text color="#ff3366">{icons.err}</Text>;
46
- }
47
- }
48
-
49
- function getServiceColor(name: string): string {
50
- if (name.startsWith(PLUGIN_PREFIX)) return "#ffaa00";
51
- return name === "host" ? "#00ffff" : name === "ui" ? "#ff00ff" : "#0080ff";
52
- }
53
-
54
- function getDisplayName(name: string): string {
55
- return name.startsWith(PLUGIN_PREFIX)
56
- ? name.slice(PLUGIN_PREFIX.length).toUpperCase()
57
- : name.toUpperCase();
58
- }
59
-
60
- function isPlugin(name: string): boolean {
61
- return name.startsWith(PLUGIN_PREFIX);
62
- }
63
-
64
- function getSectionedProcesses(processes: ProcessState[]): Array<{
65
- key: string;
66
- title: string;
67
- processes: ProcessState[];
68
- }> {
69
- const plugins = processes.filter((p) => isPlugin(p.name));
70
- const services = processes.filter((p) => !isPlugin(p.name));
71
- const sections: Array<{ key: string; title: string; processes: ProcessState[] }> = [];
72
- if (plugins.length > 0) sections.push({ key: "plugins", title: "PLUGINS", processes: plugins });
73
- if (services.length > 0)
74
- sections.push({ key: "services", title: "SERVICES", processes: services });
75
- return sections;
76
- }
77
-
78
- function getColumnWidths(processes: ProcessState[]): { name: number; source: number } {
79
- const name = Math.max(6, ...processes.map((p) => getDisplayName(p.name).length));
80
- const source = Math.max(10, ...processes.map((p) => (p.source ? `(${p.source})`.length : 0)));
81
- return { name, source };
82
- }
83
-
84
- function ProcessRow({
85
- proc,
86
- nameWidth,
87
- sourceWidth,
88
- }: {
89
- proc: ProcessState;
90
- nameWidth: number;
91
- sourceWidth: number;
92
- }) {
93
- const color = getServiceColor(proc.name);
94
- const isRemote = proc.source === "remote";
95
- const isHost = proc.name === "host";
96
- const showPort = proc.port > 0 && (isHost || !isRemote);
97
- const portStr = showPort ? `:${proc.port}` : "";
98
- const sourceLabel = proc.source ? ` (${proc.source})` : "";
99
-
100
- const statusText =
101
- proc.status === "pending"
102
- ? "waiting"
103
- : proc.status === "starting"
104
- ? "starting"
105
- : proc.status === "ready"
106
- ? isRemote && !isHost
107
- ? "loaded"
108
- : "running"
109
- : "failed";
110
-
111
- return (
112
- <Box>
113
- <Text>{" "}</Text>
114
- <StatusIcon status={proc.status} />
115
- <Text> </Text>
116
- <Text color={color} bold>
117
- {getDisplayName(proc.name).padEnd(nameWidth)}
118
- </Text>
119
- <Text color="gray">{sourceLabel.padEnd(sourceWidth)}</Text>
120
- <Text color={proc.status === "ready" ? "#00ff41" : "gray"}>{statusText}</Text>
121
- {showPort && <Text color="#00ffff"> {portStr}</Text>}
122
- </Box>
123
- );
124
- }
125
-
126
- function SectionHeader({ title }: { title: string }) {
127
- return (
128
- <Box marginBottom={0} marginTop={1}>
129
- <Text color="#00ffff" bold>
130
- {title}
131
- </Text>
132
- </Box>
133
- );
134
- }
135
-
136
- function LogLine({ entry }: { entry: LogEntry }) {
137
- const color = getServiceColor(entry.source);
138
-
139
- return (
140
- <Box>
141
- <Text color={color}>[{entry.source}]</Text>
142
- <Text color={entry.isError ? "#ff3366" : undefined}> {linkify(entry.line)}</Text>
143
- </Box>
144
- );
145
- }
146
-
147
- function truncateUrl(url: string, maxLen: number): string {
148
- if (url.length <= maxLen) return url;
149
- try {
150
- const parsed = new URL(url);
151
- const host = parsed.host;
152
- if (host.length > maxLen - 10) {
153
- return `${host.slice(0, maxLen - 13)}...`;
154
- }
155
- return host;
156
- } catch {
157
- return `${url.slice(0, maxLen - 3)}...`;
158
- }
159
- }
160
-
161
- function DevView({
162
- processes,
163
- logs,
164
- description,
165
- proxyTarget,
166
- onExit,
167
- onExportLogs,
168
- }: DevViewProps) {
169
- const { exit } = useApp();
170
- const [isShuttingDown, setIsShuttingDown] = useState(false);
171
-
172
- useInput((input, key) => {
173
- if (isShuttingDown) return;
174
-
175
- if (input === "q" || (key.ctrl && input === "c")) {
176
- setIsShuttingDown(true);
177
- Promise.resolve(onExit?.()).then(() => {
178
- exit();
179
- });
180
- }
181
- if (input === "l") {
182
- setIsShuttingDown(true);
183
- Promise.resolve(onExportLogs?.()).then(() => {
184
- exit();
185
- });
186
- }
187
- });
188
-
189
- const readyCount = processes.filter((p) => p.status === "ready").length;
190
- const total = processes.length;
191
- const allReady = readyCount === total;
192
- const hostProcess = processes.find((p) => p.name === "host");
193
- const hostPort = hostProcess?.port || 3000;
194
- const recentLogs = logs.slice(-12);
195
- const sectionedProcesses = getSectionedProcesses(processes);
196
- const columnWidths = getColumnWidths(processes);
197
-
198
- return (
199
- <Box flexDirection="column">
200
- <Box marginBottom={0}>
201
- <Text color="#00ffff">{frames.top(52)}</Text>
202
- </Box>
203
- <Box>
204
- <Text>
205
- {" "}
206
- {icons.run} {gradients.cyber(description.toUpperCase())}
207
- </Text>
208
- </Box>
209
- <Box marginBottom={1}>
210
- <Text color="#00ffff">{frames.bottom(52)}</Text>
211
- </Box>
212
-
213
- {allReady && (
214
- <Box marginBottom={1} flexDirection="column">
215
- <Box>
216
- <Text color="#00ff41">
217
- {" "}
218
- {icons.app} APP READY
219
- </Text>
220
- </Box>
221
- <Box>
222
- <Text color="#00ff41" bold>
223
- {" "}
224
- {icons.arrow} http://localhost:{hostPort}
225
- </Text>
226
- </Box>
227
- </Box>
228
- )}
229
-
230
- {proxyTarget && (
231
- <Box marginBottom={1}>
232
- <Text color="#ffaa00">
233
- {" "}
234
- {icons.arrow} API PROXY → {truncateUrl(proxyTarget, 38)}
235
- </Text>
236
- </Box>
237
- )}
238
-
239
- <Box marginTop={0} marginBottom={0}>
240
- <Text>{colors.dim(divider(52))}</Text>
241
- </Box>
242
-
243
- {sectionedProcesses.map((section) => (
244
- <Box key={section.key} flexDirection="column">
245
- <SectionHeader title={section.title} />
246
- {section.processes.map((proc) => (
247
- <ProcessRow
248
- key={proc.name}
249
- proc={proc}
250
- nameWidth={columnWidths.name}
251
- sourceWidth={columnWidths.source}
252
- />
253
- ))}
254
- </Box>
255
- ))}
256
-
257
- <Box marginTop={1} marginBottom={0}>
258
- <Text>{colors.dim(divider(52))}</Text>
259
- </Box>
260
-
261
- <Box marginTop={0}>
262
- <Text color={allReady ? "#00ff41" : "#00ffff"}>
263
- {" "}
264
- {allReady
265
- ? `${icons.ok} All ${total} services running`
266
- : `${icons.scan} ${readyCount}/${total} ready`}
267
- </Text>
268
- <Text color="gray">
269
- {" "}
270
- {icons.dot} q quit {icons.dot} l logs
271
- </Text>
272
- </Box>
273
-
274
- {recentLogs.length > 0 && (
275
- <>
276
- <Box marginTop={1} marginBottom={0}>
277
- <Text>{colors.dim(divider(52))}</Text>
278
- </Box>
279
- <Box flexDirection="column" marginTop={0}>
280
- {recentLogs.map((entry) => (
281
- <LogLine key={entry.id} entry={entry} />
282
- ))}
283
- </Box>
284
- </>
285
- )}
286
- </Box>
287
- );
288
- }
289
-
290
- export interface DevViewHandle {
291
- updateProcess: (name: string, status: ProcessStatus, message?: string) => void;
292
- addLog: (source: string, line: string, isError?: boolean) => void;
293
- unmount: () => void;
294
- }
295
-
296
- export function renderDevView(
297
- initialProcesses: ProcessState[],
298
- description: string,
299
- env: Record<string, string>,
300
- onExit?: () => Promise<void> | void,
301
- onExportLogs?: () => Promise<void> | void,
302
- ): DevViewHandle {
303
- let processes = [...initialProcesses];
304
- let logs: LogEntry[] = [];
305
- let rerender: (() => void) | null = null;
306
- const proxyTarget = env.API_PROXY;
307
- let logSeq = 0;
308
- let lastLogKey: string | null = null;
309
-
310
- const updateProcess = (name: string, status: ProcessStatus, message?: string) => {
311
- processes = processes.map((p) => (p.name === name ? { ...p, status, message } : p));
312
- rerender?.();
313
- };
314
-
315
- const addLog = (source: string, line: string, isError = false) => {
316
- const nextKey = `${source}:${isError ? "1" : "0"}:${line}`;
317
- if (nextKey === lastLogKey) return;
318
- lastLogKey = nextKey;
319
-
320
- logs = [
321
- ...logs,
322
- { id: `${Date.now()}-${++logSeq}`, source, line, timestamp: Date.now(), isError },
323
- ];
324
- if (logs.length > 100) logs = logs.slice(-100);
325
- rerender?.();
326
- };
327
-
328
- function DevViewWrapper() {
329
- const [, forceUpdate] = useState(0);
330
-
331
- useEffect(() => {
332
- rerender = () => forceUpdate((n: number) => n + 1);
333
- return () => {
334
- rerender = null;
335
- };
336
- }, []);
337
-
338
- return (
339
- <DevView
340
- processes={processes}
341
- logs={logs}
342
- description={description}
343
- proxyTarget={proxyTarget}
344
- onExit={onExit}
345
- onExportLogs={onExportLogs}
346
- />
347
- );
348
- }
349
-
350
- const { unmount } = render(<DevViewWrapper />);
351
- return { updateProcess, addLog, unmount };
352
- }
@@ -1,177 +0,0 @@
1
- import chalk from "chalk";
2
- import { linkify } from "../utils/linkify";
3
- import { colors, icons } from "../utils/theme";
4
- import type { ProcessState, ProcessStatus } from "./dev-view";
5
-
6
- const orange = chalk.hex("#ffaa00");
7
- const PLUGIN_PREFIX = "plugin:";
8
-
9
- export interface StreamingViewHandle {
10
- updateProcess: (name: string, status: ProcessStatus, message?: string) => void;
11
- addLog: (source: string, line: string, isError?: boolean) => void;
12
- unmount: () => Promise<void> | void;
13
- }
14
-
15
- const getTimestamp = (): string => {
16
- const now = new Date();
17
- return `${now.getHours().toString().padStart(2, "0")}:${now.getMinutes().toString().padStart(2, "0")}:${now.getSeconds().toString().padStart(2, "0")}`;
18
- };
19
-
20
- const write = (text: string) => process.stdout.write(`${text}\n`);
21
-
22
- const getServiceColor = (name: string): ((s: string) => string) => {
23
- if (name.startsWith(PLUGIN_PREFIX)) return orange;
24
- if (name === "host") return colors.cyan;
25
- if (name === "ui" || name === "ui-ssr") return colors.magenta;
26
- if (name === "api") return colors.blue;
27
- return colors.white;
28
- };
29
-
30
- const getDisplayName = (name: string): string => {
31
- return name.startsWith(PLUGIN_PREFIX)
32
- ? name.slice(PLUGIN_PREFIX.length).toUpperCase()
33
- : name.toUpperCase();
34
- };
35
-
36
- const isPlugin = (name: string): boolean => name.startsWith(PLUGIN_PREFIX);
37
-
38
- const getSectionedProcesses = (processes: ProcessState[]) => {
39
- const plugins = processes.filter((p) => isPlugin(p.name));
40
- const services = processes.filter((p) => !isPlugin(p.name));
41
- const sections: Array<{ key: string; title: string; processes: ProcessState[] }> = [];
42
- if (plugins.length > 0) sections.push({ key: "plugins", title: "PLUGINS", processes: plugins });
43
- if (services.length > 0)
44
- sections.push({ key: "services", title: "SERVICES", processes: services });
45
- return sections;
46
- };
47
-
48
- const getColumnWidths = (processes: ProcessState[]) => {
49
- const name = Math.max(6, ...processes.map((p) => getDisplayName(p.name).length));
50
- const source = Math.max(10, ...processes.map((p) => (p.source ? ` (${p.source})`.length : 0)));
51
- return { name, source };
52
- };
53
-
54
- const getStatusIcon = (status: ProcessStatus): string => {
55
- switch (status) {
56
- case "pending":
57
- return icons.pending;
58
- case "starting":
59
- return icons.scan;
60
- case "ready":
61
- return icons.ok;
62
- case "error":
63
- return icons.err;
64
- }
65
- };
66
-
67
- export function renderStreamingView(
68
- initialProcesses: ProcessState[],
69
- description: string,
70
- env: Record<string, string>,
71
- onExit?: () => Promise<void> | void,
72
- ): StreamingViewHandle {
73
- const processes = new Map<string, ProcessState>();
74
- for (const p of initialProcesses) {
75
- processes.set(p.name, { ...p });
76
- }
77
-
78
- let allReadyPrinted = false;
79
- const hostProcess = initialProcesses.find((p) => p.name === "host");
80
- const hostPort = hostProcess?.port || 3000;
81
- const proxyTarget = env.API_PROXY;
82
- const sectionedProcesses = getSectionedProcesses(initialProcesses);
83
- const columnWidths = getColumnWidths(initialProcesses);
84
- const lastLogBySource = new Map<string, string>();
85
-
86
- const headerLines: string[] = [
87
- "",
88
- colors.cyan(`${"─".repeat(52)}`),
89
- ` ${icons.run} ${colors.cyan(description.toUpperCase())}`,
90
- colors.cyan(`${"─".repeat(52)}`),
91
- "",
92
- ];
93
-
94
- if (proxyTarget) {
95
- headerLines.push(orange(` ${icons.arrow} API PROXY → ${proxyTarget}`), "");
96
- }
97
-
98
- for (const section of sectionedProcesses) {
99
- headerLines.push(colors.cyan(` ${section.title}`));
100
- for (const proc of section.processes) {
101
- const color = getServiceColor(proc.name);
102
- const sourceLabel = proc.source ? ` (${proc.source})` : "";
103
- headerLines.push(
104
- `${colors.dim(`[${getTimestamp()}]`)} ${color(`[${getDisplayName(proc.name).padEnd(columnWidths.name)}]`)} ${icons.pending} waiting${sourceLabel.padEnd(columnWidths.source)}`,
105
- );
106
- }
107
- headerLines.push("");
108
- }
109
- console.log(headerLines.join("\n"));
110
-
111
- const checkAllReady = () => {
112
- if (allReadyPrinted) return;
113
- const allReady = Array.from(processes.values()).every((p) => p.status === "ready");
114
- if (allReady) {
115
- allReadyPrinted = true;
116
- const readyLines = [
117
- "",
118
- colors.dim(`${"─".repeat(52)}`),
119
- colors.green(`${icons.ok} All ${processes.size} services ready`),
120
- colors.green(`${icons.arrow} http://localhost:${hostPort}`),
121
- colors.dim(`${"─".repeat(52)}`),
122
- "",
123
- ];
124
- console.log(readyLines.join("\n"));
125
- }
126
- };
127
-
128
- const updateProcess = (name: string, status: ProcessStatus, message?: string) => {
129
- const proc = processes.get(name);
130
- if (!proc) return;
131
-
132
- proc.status = status;
133
- if (message) proc.message = message;
134
-
135
- const color = getServiceColor(name);
136
- const icon = getStatusIcon(status);
137
- const displayName = getDisplayName(name).padEnd(columnWidths.name);
138
- const sourceLabel = proc?.source ? ` (${proc.source})` : "";
139
- const isRemote = proc?.source === "remote";
140
- const isHost = name === "host";
141
- const showPort = proc.port > 0 && (isHost || !isRemote) && status === "ready";
142
- const statusText =
143
- status === "ready"
144
- ? isRemote && !isHost
145
- ? "loaded"
146
- : "running"
147
- : status === "starting"
148
- ? "starting"
149
- : status === "error"
150
- ? "failed"
151
- : "waiting";
152
- const portStr = showPort ? ` :${proc.port}` : "";
153
-
154
- write(
155
- `${colors.dim(`[${getTimestamp()}]`)} ${color(`[${displayName}]`)} ${status === "ready" ? colors.green(icon) : status === "error" ? colors.error(icon) : icon} ${statusText}${sourceLabel.padEnd(columnWidths.source)}${portStr}`,
156
- );
157
-
158
- checkAllReady();
159
- };
160
-
161
- const addLog = (source: string, line: string, isError = false) => {
162
- const lastLine = lastLogBySource.get(source);
163
- const nextLine = `${isError ? "ERR" : "OUT"}:${line}`;
164
- if (lastLine === nextLine) return;
165
- lastLogBySource.set(source, nextLine);
166
-
167
- const color = getServiceColor(source);
168
- const logColor = isError ? colors.error : colors.dim;
169
- write(
170
- `${colors.dim(`[${getTimestamp()}]`)} ${color(`[${source.toUpperCase()}]`)} ${colors.dim("│")} ${logColor(linkify(line))}`,
171
- );
172
- };
173
-
174
- const unmount = () => onExit?.();
175
-
176
- return { updateProcess, addLog, unmount };
177
- }