everything-dev 0.2.1 → 0.3.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 +80 -79
- package/src/cli.ts +1491 -1198
- package/src/components/monitor-view.tsx +423 -419
- package/src/config.ts +529 -241
- package/src/contract.ts +381 -364
- package/src/lib/env.ts +83 -65
- package/src/lib/nova.ts +207 -195
- package/src/lib/orchestrator.ts +232 -199
- package/src/lib/process-registry.ts +141 -132
- package/src/lib/process.ts +499 -409
- package/src/lib/resource-monitor/diff.ts +27 -9
- package/src/lib/resource-monitor/platform/darwin.ts +31 -18
- package/src/lib/resource-monitor/snapshot.ts +164 -151
- package/src/plugin.ts +2281 -1841
- package/src/types.ts +182 -83
- package/src/ui/head.ts +37 -26
- package/src/utils/banner.ts +7 -9
- package/src/utils/run.ts +27 -16
- package/src/lib/secrets.ts +0 -29
package/src/lib/orchestrator.ts
CHANGED
|
@@ -1,243 +1,276 @@
|
|
|
1
1
|
import { appendFile } from "node:fs/promises";
|
|
2
|
-
import { BunContext, BunRuntime } from "@effect/platform-bun";
|
|
3
2
|
import { Effect } from "every-plugin/effect";
|
|
4
3
|
import path from "path";
|
|
5
4
|
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
type DevViewHandle,
|
|
6
|
+
type LogEntry,
|
|
7
|
+
type ProcessState,
|
|
8
|
+
renderDevView,
|
|
10
9
|
} from "../components/dev-view";
|
|
11
10
|
import { renderStreamingView } from "../components/streaming-view";
|
|
12
11
|
import type { AppConfig, BosConfig } from "../config";
|
|
13
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
getProcessConfig,
|
|
14
|
+
makeDevProcess,
|
|
15
|
+
type ProcessCallbacks,
|
|
16
|
+
type ProcessHandle,
|
|
17
|
+
} from "./process";
|
|
14
18
|
|
|
15
19
|
let activeCleanup: (() => Promise<void>) | null = null;
|
|
16
20
|
|
|
17
21
|
const LOG_NOISE_PATTERNS = [
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
/\[ Federation Runtime \] Version .* from (host|ui) of shared singleton module/,
|
|
23
|
+
/Executing an Effect versioned \d+\.\d+\.\d+ with a Runtime of version/,
|
|
24
|
+
/you may want to dedupe the effect dependencies/,
|
|
21
25
|
];
|
|
22
26
|
|
|
23
27
|
const isDebugMode = (): boolean => {
|
|
24
|
-
|
|
28
|
+
return process.env.DEBUG === "true" || process.env.DEBUG === "1";
|
|
25
29
|
};
|
|
26
30
|
|
|
27
31
|
const shouldDisplayLog = (line: string): boolean => {
|
|
28
|
-
|
|
29
|
-
|
|
32
|
+
if (isDebugMode()) return true;
|
|
33
|
+
return !LOG_NOISE_PATTERNS.some((pattern) => pattern.test(line));
|
|
30
34
|
};
|
|
31
35
|
|
|
32
36
|
export interface AppOrchestrator {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
packages: string[];
|
|
38
|
+
env: Record<string, string>;
|
|
39
|
+
description: string;
|
|
40
|
+
appConfig: AppConfig;
|
|
41
|
+
bosConfig?: BosConfig;
|
|
42
|
+
port?: number;
|
|
43
|
+
interactive?: boolean;
|
|
44
|
+
noLogs?: boolean;
|
|
41
45
|
}
|
|
42
46
|
|
|
43
47
|
const isInteractiveSupported = (): boolean => {
|
|
44
|
-
|
|
48
|
+
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
45
49
|
};
|
|
46
50
|
|
|
47
51
|
const STARTUP_ORDER = ["ui-ssr", "ui", "api", "host"];
|
|
48
52
|
|
|
49
53
|
const sortByOrder = (packages: string[]): string[] => {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
return [...packages].sort((a, b) => {
|
|
55
|
+
const aIdx = STARTUP_ORDER.indexOf(a);
|
|
56
|
+
const bIdx = STARTUP_ORDER.indexOf(b);
|
|
57
|
+
if (aIdx === -1 && bIdx === -1) return 0;
|
|
58
|
+
if (aIdx === -1) return 1;
|
|
59
|
+
if (bIdx === -1) return -1;
|
|
60
|
+
return aIdx - bIdx;
|
|
61
|
+
});
|
|
58
62
|
};
|
|
59
63
|
|
|
60
64
|
const getLogDir = () => path.join(process.cwd(), ".bos", "logs");
|
|
61
65
|
const getLogFile = () => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
const now = new Date();
|
|
67
|
+
const ts = now.toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
68
|
+
return path.join(getLogDir(), `dev-${ts}.log`);
|
|
65
69
|
};
|
|
66
70
|
|
|
67
71
|
const ensureLogDir = async () => {
|
|
68
|
-
|
|
69
|
-
|
|
72
|
+
const dir = getLogDir();
|
|
73
|
+
await Bun.spawn(["mkdir", "-p", dir]).exited;
|
|
70
74
|
};
|
|
71
75
|
|
|
72
76
|
const formatLogLine = (entry: LogEntry): string => {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
const ts = new Date(entry.timestamp).toISOString();
|
|
78
|
+
const prefix = entry.isError ? "ERR" : "OUT";
|
|
79
|
+
return `[${ts}] [${entry.source}] [${prefix}] ${entry.line}`;
|
|
76
80
|
};
|
|
77
81
|
|
|
78
82
|
export const runDevServers = (orchestrator: AppOrchestrator) =>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
83
|
+
Effect.gen(function* () {
|
|
84
|
+
const orderedPackages = sortByOrder(orchestrator.packages);
|
|
85
|
+
|
|
86
|
+
const initialProcesses: ProcessState[] = orderedPackages.map((pkg) => {
|
|
87
|
+
const portOverride = pkg === "host" ? orchestrator.port : undefined;
|
|
88
|
+
const config = getProcessConfig(pkg, undefined, portOverride);
|
|
89
|
+
const source =
|
|
90
|
+
pkg === "host"
|
|
91
|
+
? orchestrator.appConfig.host
|
|
92
|
+
: pkg === "ui"
|
|
93
|
+
? orchestrator.appConfig.ui
|
|
94
|
+
: pkg === "api"
|
|
95
|
+
? orchestrator.appConfig.api
|
|
96
|
+
: undefined;
|
|
97
|
+
return {
|
|
98
|
+
name: pkg,
|
|
99
|
+
status: "pending" as const,
|
|
100
|
+
port: config?.port ?? 0,
|
|
101
|
+
source,
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const handles: ProcessHandle[] = [];
|
|
106
|
+
const allLogs: LogEntry[] = [];
|
|
107
|
+
let logFile: string | null = null;
|
|
108
|
+
let view: DevViewHandle | null = null;
|
|
109
|
+
let shuttingDown = false;
|
|
110
|
+
|
|
111
|
+
if (!orchestrator.noLogs) {
|
|
112
|
+
yield* Effect.promise(async () => {
|
|
113
|
+
await ensureLogDir();
|
|
114
|
+
logFile = getLogFile();
|
|
115
|
+
await Bun.write(
|
|
116
|
+
logFile,
|
|
117
|
+
`# BOS Dev Session: ${orchestrator.description}\n# Started: ${new Date().toISOString()}\n\n`,
|
|
118
|
+
);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const killAll = async () => {
|
|
123
|
+
const reversed = [...handles].reverse();
|
|
124
|
+
for (const handle of reversed) {
|
|
125
|
+
try {
|
|
126
|
+
await handle.kill();
|
|
127
|
+
} catch {}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const exportLogs = async () => {
|
|
132
|
+
console.log("\n");
|
|
133
|
+
console.log("═".repeat(70));
|
|
134
|
+
console.log(` SESSION LOGS: ${orchestrator.description}`);
|
|
135
|
+
console.log(
|
|
136
|
+
` Started: ${new Date(allLogs[0]?.timestamp || Date.now()).toISOString()}`,
|
|
137
|
+
);
|
|
138
|
+
console.log(` Total entries: ${allLogs.length}`);
|
|
139
|
+
console.log("═".repeat(70));
|
|
140
|
+
console.log("");
|
|
141
|
+
|
|
142
|
+
for (const entry of allLogs) {
|
|
143
|
+
console.log(formatLogLine(entry));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
console.log("");
|
|
147
|
+
console.log("═".repeat(70));
|
|
148
|
+
console.log(" END OF LOGS");
|
|
149
|
+
if (logFile) {
|
|
150
|
+
console.log(` Full logs saved to: ${logFile}`);
|
|
151
|
+
}
|
|
152
|
+
console.log("═".repeat(70));
|
|
153
|
+
console.log("");
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const cleanup = async (showLogs = false) => {
|
|
157
|
+
if (shuttingDown) return;
|
|
158
|
+
shuttingDown = true;
|
|
159
|
+
view?.unmount();
|
|
160
|
+
await killAll();
|
|
161
|
+
if (showLogs) {
|
|
162
|
+
await exportLogs();
|
|
163
|
+
}
|
|
164
|
+
activeCleanup = null;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
activeCleanup = cleanup;
|
|
168
|
+
|
|
169
|
+
const useInteractive = orchestrator.interactive ?? isInteractiveSupported();
|
|
170
|
+
|
|
171
|
+
view = useInteractive
|
|
172
|
+
? renderDevView(
|
|
173
|
+
initialProcesses,
|
|
174
|
+
orchestrator.description,
|
|
175
|
+
orchestrator.env,
|
|
176
|
+
() => cleanup(false),
|
|
177
|
+
() => cleanup(true),
|
|
178
|
+
)
|
|
179
|
+
: renderStreamingView(
|
|
180
|
+
initialProcesses,
|
|
181
|
+
orchestrator.description,
|
|
182
|
+
orchestrator.env,
|
|
183
|
+
() => cleanup(false),
|
|
184
|
+
() => cleanup(true),
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const callbacks: ProcessCallbacks = {
|
|
188
|
+
onStatus: (name, status, message) => {
|
|
189
|
+
view?.updateProcess(name, status, message);
|
|
190
|
+
},
|
|
191
|
+
onLog: (name, line, isError) => {
|
|
192
|
+
const entry: LogEntry = {
|
|
193
|
+
source: name,
|
|
194
|
+
line,
|
|
195
|
+
timestamp: Date.now(),
|
|
196
|
+
isError,
|
|
197
|
+
};
|
|
198
|
+
allLogs.push(entry);
|
|
199
|
+
|
|
200
|
+
if (shouldDisplayLog(line)) {
|
|
201
|
+
view?.addLog(name, line, isError);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (logFile) {
|
|
205
|
+
const logLine = formatLogLine(entry) + "\n";
|
|
206
|
+
appendFile(logFile, logLine).catch(() => {});
|
|
207
|
+
}
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
for (const pkg of orderedPackages) {
|
|
212
|
+
const portOverride = pkg === "host" ? orchestrator.port : undefined;
|
|
213
|
+
const handle = yield* makeDevProcess(
|
|
214
|
+
pkg,
|
|
215
|
+
orchestrator.env,
|
|
216
|
+
callbacks,
|
|
217
|
+
portOverride,
|
|
218
|
+
orchestrator.bosConfig,
|
|
219
|
+
);
|
|
220
|
+
handles.push(handle);
|
|
221
|
+
|
|
222
|
+
yield* Effect.race(
|
|
223
|
+
handle.waitForReady,
|
|
224
|
+
Effect.sleep("30 seconds").pipe(
|
|
225
|
+
Effect.andThen(
|
|
226
|
+
Effect.sync(() => {
|
|
227
|
+
callbacks.onLog(
|
|
228
|
+
pkg,
|
|
229
|
+
"Timeout waiting for ready, continuing...",
|
|
230
|
+
true,
|
|
231
|
+
);
|
|
232
|
+
}),
|
|
233
|
+
),
|
|
234
|
+
),
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
yield* Effect.addFinalizer(() => Effect.promise(() => cleanup(false)));
|
|
239
|
+
|
|
240
|
+
yield* Effect.never;
|
|
241
|
+
});
|
|
210
242
|
|
|
211
243
|
export const startApp = (orchestrator: AppOrchestrator) => {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
244
|
+
const program = Effect.scoped(runDevServers(orchestrator)).pipe(
|
|
245
|
+
Effect.catchAll((e) =>
|
|
246
|
+
Effect.sync(() => {
|
|
247
|
+
if (e instanceof Error) {
|
|
248
|
+
console.error("App server error:", e.message);
|
|
249
|
+
if (e.stack) {
|
|
250
|
+
console.error(e.stack);
|
|
251
|
+
}
|
|
252
|
+
} else if (typeof e === "object" && e !== null) {
|
|
253
|
+
console.error("App server error:", JSON.stringify(e, null, 2));
|
|
254
|
+
} else {
|
|
255
|
+
console.error("App server error:", e);
|
|
256
|
+
}
|
|
257
|
+
}),
|
|
258
|
+
),
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const handleSignal = async () => {
|
|
262
|
+
if (activeCleanup) {
|
|
263
|
+
await activeCleanup();
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
process.on("SIGINT", () => {
|
|
268
|
+
handleSignal().finally(() => process.exit(0));
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
process.on("SIGTERM", () => {
|
|
272
|
+
handleSignal().finally(() => process.exit(0));
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
Effect.runPromise(program);
|
|
243
276
|
};
|