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/process.ts
CHANGED
|
@@ -1,459 +1,549 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Deferred, Effect, Fiber, Ref, Stream } from "every-plugin/effect";
|
|
1
|
+
import { Deferred, Effect, Fiber, Ref } from "every-plugin/effect";
|
|
3
2
|
import type { ProcessStatus } from "../components/dev-view";
|
|
4
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
type BosConfig,
|
|
5
|
+
getProjectRoot,
|
|
6
|
+
parsePort,
|
|
7
|
+
type RemoteConfig,
|
|
8
|
+
type SourceMode,
|
|
9
|
+
} from "../config";
|
|
5
10
|
import type { RuntimeConfig } from "../types";
|
|
6
11
|
|
|
7
12
|
export interface DevProcess {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
name: string;
|
|
14
|
+
command: string;
|
|
15
|
+
args: string[];
|
|
16
|
+
cwd: string;
|
|
17
|
+
env?: Record<string, string>;
|
|
18
|
+
port: number;
|
|
19
|
+
readyPatterns: RegExp[];
|
|
20
|
+
errorPatterns: RegExp[];
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
interface ProcessConfigBase {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
name: string;
|
|
25
|
+
command: string;
|
|
26
|
+
args: string[];
|
|
27
|
+
cwd: string;
|
|
28
|
+
readyPatterns: RegExp[];
|
|
29
|
+
errorPatterns: RegExp[];
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
const processConfigBases: Record<string, ProcessConfigBase> = {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
33
|
+
host: {
|
|
34
|
+
name: "host",
|
|
35
|
+
command: "bun",
|
|
36
|
+
args: ["run", "dev"],
|
|
37
|
+
cwd: "host",
|
|
38
|
+
readyPatterns: [/Host (dev|production) server running at/i],
|
|
39
|
+
errorPatterns: [/error:/i, /failed/i, /exception/i],
|
|
40
|
+
},
|
|
41
|
+
ui: {
|
|
42
|
+
name: "ui",
|
|
43
|
+
command: "bun",
|
|
44
|
+
args: ["run", "dev"],
|
|
45
|
+
cwd: "ui",
|
|
46
|
+
readyPatterns: [/ready in/i, /compiled.*successfully/i, /➜.*local:/i],
|
|
47
|
+
errorPatterns: [/error/i, /failed to compile/i],
|
|
48
|
+
},
|
|
49
|
+
"ui-ssr": {
|
|
50
|
+
name: "ui-ssr",
|
|
51
|
+
command: "bun",
|
|
52
|
+
args: ["run", "build:ssr", "--watch"],
|
|
53
|
+
cwd: "ui",
|
|
54
|
+
readyPatterns: [/compiled.*successfully/i, /built in/i],
|
|
55
|
+
errorPatterns: [/error/i, /failed/i],
|
|
56
|
+
},
|
|
57
|
+
api: {
|
|
58
|
+
name: "api",
|
|
59
|
+
command: "bun",
|
|
60
|
+
args: ["run", "dev"],
|
|
61
|
+
cwd: "api",
|
|
62
|
+
readyPatterns: [/Plugin dev server ready/i, /listening/i, /started/i],
|
|
63
|
+
errorPatterns: [/error/i, /failed/i],
|
|
64
|
+
},
|
|
60
65
|
};
|
|
61
66
|
|
|
62
67
|
export const getProcessConfig = (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
pkg: string,
|
|
69
|
+
env?: Record<string, string>,
|
|
70
|
+
portOverride?: number,
|
|
71
|
+
bosConfig?: BosConfig,
|
|
66
72
|
): DevProcess | null => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
73
|
+
const base = processConfigBases[pkg];
|
|
74
|
+
if (!base) return null;
|
|
75
|
+
|
|
76
|
+
// Use config if provided, otherwise defaults
|
|
77
|
+
let port: number;
|
|
78
|
+
if (pkg === "host") {
|
|
79
|
+
port =
|
|
80
|
+
portOverride ??
|
|
81
|
+
(bosConfig ? parsePort(bosConfig.app.host.development) : 3000);
|
|
82
|
+
} else if (pkg === "ui") {
|
|
83
|
+
port = bosConfig?.app?.ui
|
|
84
|
+
? parsePort((bosConfig.app.ui as RemoteConfig).development)
|
|
85
|
+
: 3002;
|
|
86
|
+
} else if (pkg === "ui-ssr") {
|
|
87
|
+
// SSR build watcher doesn't need a port (build process, not server)
|
|
88
|
+
port = 0;
|
|
89
|
+
} else if (pkg === "api") {
|
|
90
|
+
port = bosConfig?.app?.api
|
|
91
|
+
? parsePort((bosConfig.app.api as RemoteConfig).development)
|
|
92
|
+
: 3014;
|
|
93
|
+
} else {
|
|
94
|
+
port = 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Set BUILD_TARGET for SSR build process
|
|
98
|
+
const processEnv =
|
|
99
|
+
pkg === "ui-ssr" ? { ...env, BUILD_TARGET: "server" } : env;
|
|
100
|
+
|
|
101
|
+
return { ...base, port, env: processEnv };
|
|
88
102
|
};
|
|
89
103
|
|
|
90
104
|
export interface ProcessCallbacks {
|
|
91
|
-
|
|
92
|
-
|
|
105
|
+
onStatus: (name: string, status: ProcessStatus, message?: string) => void;
|
|
106
|
+
onLog: (name: string, line: string, isError?: boolean) => void;
|
|
93
107
|
}
|
|
94
108
|
|
|
95
109
|
export interface ProcessHandle {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
110
|
+
name: string;
|
|
111
|
+
pid: number | undefined;
|
|
112
|
+
kill: () => Promise<void>;
|
|
113
|
+
waitForReady: Effect.Effect<void>;
|
|
114
|
+
waitForExit: Effect.Effect<unknown, unknown>;
|
|
101
115
|
}
|
|
102
116
|
|
|
103
117
|
const detectStatus = (
|
|
104
|
-
|
|
105
|
-
|
|
118
|
+
line: string,
|
|
119
|
+
config: DevProcess,
|
|
106
120
|
): { status: ProcessStatus; isError: boolean } | null => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
121
|
+
for (const pattern of config.errorPatterns) {
|
|
122
|
+
if (pattern.test(line)) {
|
|
123
|
+
return { status: "error", isError: true };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (const pattern of config.readyPatterns) {
|
|
127
|
+
if (pattern.test(line)) {
|
|
128
|
+
return { status: "ready", isError: false };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
118
132
|
};
|
|
119
133
|
|
|
120
134
|
const killProcessTree = (pid: number) =>
|
|
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
|
-
|
|
135
|
+
Effect.gen(function* () {
|
|
136
|
+
const killSignal = (signal: NodeJS.Signals) =>
|
|
137
|
+
Effect.try({
|
|
138
|
+
try: () => {
|
|
139
|
+
process.kill(-pid, signal);
|
|
140
|
+
},
|
|
141
|
+
catch: () => null,
|
|
142
|
+
}).pipe(Effect.ignore);
|
|
143
|
+
|
|
144
|
+
const killDirect = (signal: NodeJS.Signals) =>
|
|
145
|
+
Effect.try({
|
|
146
|
+
try: () => {
|
|
147
|
+
process.kill(pid, signal);
|
|
148
|
+
},
|
|
149
|
+
catch: () => null,
|
|
150
|
+
}).pipe(Effect.ignore);
|
|
151
|
+
|
|
152
|
+
const isRunning = () =>
|
|
153
|
+
Effect.try({
|
|
154
|
+
try: () => {
|
|
155
|
+
process.kill(pid, 0);
|
|
156
|
+
return true;
|
|
157
|
+
},
|
|
158
|
+
catch: () => false,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
yield* killSignal("SIGTERM");
|
|
162
|
+
yield* killDirect("SIGTERM");
|
|
163
|
+
|
|
164
|
+
yield* Effect.sleep("200 millis");
|
|
165
|
+
|
|
166
|
+
const stillRunning = yield* isRunning();
|
|
167
|
+
if (stillRunning) {
|
|
168
|
+
yield* killSignal("SIGKILL");
|
|
169
|
+
yield* killDirect("SIGKILL");
|
|
170
|
+
yield* Effect.sleep("100 millis");
|
|
171
|
+
}
|
|
172
|
+
});
|
|
159
173
|
|
|
160
174
|
export function buildRuntimeConfig(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
175
|
+
bosConfig: BosConfig,
|
|
176
|
+
options: {
|
|
177
|
+
uiSource: SourceMode;
|
|
178
|
+
apiSource: SourceMode;
|
|
179
|
+
hostUrl: string;
|
|
180
|
+
proxy?: string;
|
|
181
|
+
env?: "development" | "production";
|
|
182
|
+
},
|
|
169
183
|
): RuntimeConfig {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
184
|
+
const uiConfig = bosConfig.app.ui as RemoteConfig;
|
|
185
|
+
const apiConfig = bosConfig.app.api as RemoteConfig;
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
env: options.env ?? "development",
|
|
189
|
+
account: bosConfig.account,
|
|
190
|
+
hostUrl: options.hostUrl,
|
|
191
|
+
shared: (bosConfig as { shared?: { ui?: Record<string, unknown> } })
|
|
192
|
+
.shared as RuntimeConfig["shared"],
|
|
193
|
+
ui: {
|
|
194
|
+
name: uiConfig.name,
|
|
195
|
+
url:
|
|
196
|
+
options.uiSource === "remote"
|
|
197
|
+
? uiConfig.production
|
|
198
|
+
: uiConfig.development,
|
|
199
|
+
entry: `${
|
|
200
|
+
options.uiSource === "remote"
|
|
201
|
+
? uiConfig.production
|
|
202
|
+
: uiConfig.development
|
|
203
|
+
}/remoteEntry.js`,
|
|
204
|
+
source: options.uiSource,
|
|
205
|
+
},
|
|
206
|
+
api: {
|
|
207
|
+
name: apiConfig.name,
|
|
208
|
+
url:
|
|
209
|
+
options.apiSource === "remote"
|
|
210
|
+
? apiConfig.production
|
|
211
|
+
: apiConfig.development,
|
|
212
|
+
entry: `${
|
|
213
|
+
options.apiSource === "remote"
|
|
214
|
+
? apiConfig.production
|
|
215
|
+
: apiConfig.development
|
|
216
|
+
}/remoteEntry.js`,
|
|
217
|
+
source: options.apiSource,
|
|
218
|
+
proxy: options.proxy,
|
|
219
|
+
variables: apiConfig.variables,
|
|
220
|
+
secrets: apiConfig.secrets,
|
|
221
|
+
},
|
|
222
|
+
};
|
|
193
223
|
}
|
|
194
224
|
|
|
195
225
|
export const spawnDevProcess = (
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
226
|
+
config: DevProcess,
|
|
227
|
+
callbacks: ProcessCallbacks,
|
|
228
|
+
runtimeConfig?: RuntimeConfig,
|
|
199
229
|
) =>
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
230
|
+
Effect.gen(function* () {
|
|
231
|
+
let configDir: string;
|
|
232
|
+
try {
|
|
233
|
+
configDir = getProjectRoot();
|
|
234
|
+
} catch {
|
|
235
|
+
configDir = process.cwd();
|
|
236
|
+
}
|
|
237
|
+
const fullCwd = `${configDir}/${config.cwd}`;
|
|
238
|
+
const readyDeferred = yield* Deferred.make<void>();
|
|
239
|
+
const statusRef = yield* Ref.make<ProcessStatus>("starting");
|
|
240
|
+
|
|
241
|
+
callbacks.onStatus(config.name, "starting");
|
|
242
|
+
|
|
243
|
+
const envVars: Record<string, string> = {
|
|
244
|
+
...(process.env as Record<string, string>),
|
|
245
|
+
...config.env,
|
|
246
|
+
FORCE_COLOR: "1",
|
|
247
|
+
...(config.port > 0 ? { PORT: String(config.port) } : {}),
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
if (runtimeConfig && config.name === "host") {
|
|
251
|
+
envVars.BOS_RUNTIME_CONFIG = JSON.stringify(runtimeConfig);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Spawn process using Bun.spawn (replaces @effect/platform Command)
|
|
255
|
+
const proc = Bun.spawn({
|
|
256
|
+
cmd: [config.command, ...config.args],
|
|
257
|
+
cwd: fullCwd,
|
|
258
|
+
env: envVars,
|
|
259
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const handleLine = (line: string, isStderr: boolean) =>
|
|
263
|
+
Effect.gen(function* () {
|
|
264
|
+
if (!line.trim()) return;
|
|
265
|
+
|
|
266
|
+
callbacks.onLog(config.name, line, isStderr);
|
|
267
|
+
|
|
268
|
+
const currentStatus = yield* Ref.get(statusRef);
|
|
269
|
+
if (currentStatus === "ready") return;
|
|
270
|
+
|
|
271
|
+
const detected = detectStatus(line, config);
|
|
272
|
+
if (detected) {
|
|
273
|
+
yield* Ref.set(statusRef, detected.status);
|
|
274
|
+
callbacks.onStatus(config.name, detected.status);
|
|
275
|
+
if (detected.status === "ready") {
|
|
276
|
+
yield* Deferred.succeed(readyDeferred, undefined);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Read stdout and stderr using raw stream readers (replaces Effect Stream)
|
|
282
|
+
const decoder = new TextDecoder();
|
|
283
|
+
|
|
284
|
+
const stdoutFiber = yield* Effect.fork(
|
|
285
|
+
Effect.async<void>((resume) => {
|
|
286
|
+
if (!proc.stdout) {
|
|
287
|
+
resume(Effect.void);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
const reader = proc.stdout.getReader();
|
|
291
|
+
let buffer = "";
|
|
292
|
+
|
|
293
|
+
const pump = (): Promise<void> =>
|
|
294
|
+
reader.read().then(({ done, value }) => {
|
|
295
|
+
if (done) {
|
|
296
|
+
if (buffer) {
|
|
297
|
+
Effect.runSync(handleLine(buffer, false));
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
buffer += decoder.decode(value, { stream: true });
|
|
302
|
+
const lines = buffer.split("\n");
|
|
303
|
+
buffer = lines.pop() ?? "";
|
|
304
|
+
for (const line of lines) {
|
|
305
|
+
Effect.runSync(handleLine(line, false));
|
|
306
|
+
}
|
|
307
|
+
return pump();
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
pump().then(() => resume(Effect.void));
|
|
311
|
+
}),
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const stderrFiber = yield* Effect.fork(
|
|
315
|
+
Effect.async<void>((resume) => {
|
|
316
|
+
if (!proc.stderr) {
|
|
317
|
+
resume(Effect.void);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const reader = proc.stderr.getReader();
|
|
321
|
+
let buffer = "";
|
|
322
|
+
|
|
323
|
+
const pump = (): Promise<void> =>
|
|
324
|
+
reader.read().then(({ done, value }) => {
|
|
325
|
+
if (done) {
|
|
326
|
+
if (buffer) {
|
|
327
|
+
Effect.runSync(handleLine(buffer, true));
|
|
328
|
+
}
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
buffer += decoder.decode(value, { stream: true });
|
|
332
|
+
const lines = buffer.split("\n");
|
|
333
|
+
buffer = lines.pop() ?? "";
|
|
334
|
+
for (const line of lines) {
|
|
335
|
+
Effect.runSync(handleLine(line, true));
|
|
336
|
+
}
|
|
337
|
+
return pump();
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
pump().then(() => resume(Effect.void));
|
|
341
|
+
}),
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
const handle: ProcessHandle = {
|
|
345
|
+
name: config.name,
|
|
346
|
+
pid: proc.pid,
|
|
347
|
+
kill: async () => {
|
|
348
|
+
const pid = proc.pid;
|
|
349
|
+
if (pid) {
|
|
350
|
+
await Effect.runPromise(killProcessTree(pid));
|
|
351
|
+
} else {
|
|
352
|
+
proc.kill("SIGTERM");
|
|
353
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
354
|
+
try {
|
|
355
|
+
proc.kill("SIGKILL");
|
|
356
|
+
} catch {}
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
waitForReady: Deferred.await(readyDeferred),
|
|
360
|
+
waitForExit: Effect.gen(function* () {
|
|
361
|
+
yield* Fiber.joinAll([stdoutFiber, stderrFiber]);
|
|
362
|
+
return yield* Effect.promise(() => proc.exited);
|
|
363
|
+
}),
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
return handle;
|
|
367
|
+
});
|
|
285
368
|
|
|
286
369
|
interface ServerHandle {
|
|
287
|
-
|
|
288
|
-
|
|
370
|
+
ready: Promise<void>;
|
|
371
|
+
shutdown: () => Promise<void>;
|
|
289
372
|
}
|
|
290
373
|
|
|
291
374
|
interface ServerInput {
|
|
292
|
-
|
|
375
|
+
config: RuntimeConfig;
|
|
293
376
|
}
|
|
294
377
|
|
|
295
378
|
const patchConsole = (
|
|
296
|
-
|
|
297
|
-
|
|
379
|
+
name: string,
|
|
380
|
+
callbacks: ProcessCallbacks,
|
|
298
381
|
): (() => void) => {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
382
|
+
const originalLog = console.log;
|
|
383
|
+
const originalError = console.error;
|
|
384
|
+
const originalWarn = console.warn;
|
|
385
|
+
const originalInfo = console.info;
|
|
386
|
+
|
|
387
|
+
const formatArgs = (args: unknown[]): string => {
|
|
388
|
+
return args
|
|
389
|
+
.map((arg) =>
|
|
390
|
+
typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg),
|
|
391
|
+
)
|
|
392
|
+
.join(" ");
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
console.log = (...args: unknown[]) => {
|
|
396
|
+
callbacks.onLog(name, formatArgs(args), false);
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
console.error = (...args: unknown[]) => {
|
|
400
|
+
callbacks.onLog(name, formatArgs(args), true);
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
console.warn = (...args: unknown[]) => {
|
|
404
|
+
callbacks.onLog(name, formatArgs(args), false);
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
console.info = (...args: unknown[]) => {
|
|
408
|
+
callbacks.onLog(name, formatArgs(args), false);
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
return () => {
|
|
412
|
+
console.log = originalLog;
|
|
413
|
+
console.error = originalError;
|
|
414
|
+
console.warn = originalWarn;
|
|
415
|
+
console.info = originalInfo;
|
|
416
|
+
};
|
|
334
417
|
};
|
|
335
418
|
|
|
336
419
|
export const spawnRemoteHost = (
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
420
|
+
config: DevProcess,
|
|
421
|
+
callbacks: ProcessCallbacks,
|
|
422
|
+
runtimeConfig: RuntimeConfig,
|
|
340
423
|
) =>
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
424
|
+
Effect.gen(function* () {
|
|
425
|
+
const remoteUrl = config.env?.HOST_REMOTE_URL;
|
|
426
|
+
|
|
427
|
+
if (!remoteUrl) {
|
|
428
|
+
return yield* Effect.fail(
|
|
429
|
+
new Error("HOST_REMOTE_URL not provided for remote host"),
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (config.env) {
|
|
434
|
+
for (const [key, value] of Object.entries(config.env)) {
|
|
435
|
+
process.env[key] = value;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
callbacks.onStatus(config.name, "starting");
|
|
440
|
+
|
|
441
|
+
callbacks.onLog(config.name, `Remote: ${remoteUrl}`);
|
|
442
|
+
|
|
443
|
+
const restoreConsole = patchConsole(config.name, callbacks);
|
|
444
|
+
|
|
445
|
+
callbacks.onLog(config.name, "Loading Module Federation runtime...");
|
|
446
|
+
|
|
447
|
+
const mfRuntime = yield* Effect.tryPromise({
|
|
448
|
+
try: () => import("@module-federation/enhanced/runtime"),
|
|
449
|
+
catch: (e) => new Error(`Failed to load MF runtime: ${e}`),
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
const mfCore = yield* Effect.tryPromise({
|
|
453
|
+
try: () => import("@module-federation/runtime-core"),
|
|
454
|
+
catch: (e) => new Error(`Failed to load MF core: ${e}`),
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
let mf = mfRuntime.getInstance();
|
|
458
|
+
if (!mf) {
|
|
459
|
+
mf = mfRuntime.createInstance({ name: "cli-host", remotes: [] });
|
|
460
|
+
mfCore.setGlobalFederationInstance(mf);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const remoteEntryUrl = remoteUrl.endsWith("/remoteEntry.js")
|
|
464
|
+
? remoteUrl
|
|
465
|
+
: `${remoteUrl}/remoteEntry.js`;
|
|
466
|
+
|
|
467
|
+
mf.registerRemotes([{ name: "host", entry: remoteEntryUrl }]);
|
|
468
|
+
|
|
469
|
+
callbacks.onLog(config.name, `Loading host from ${remoteEntryUrl}...`);
|
|
470
|
+
|
|
471
|
+
const hostModule = yield* Effect.tryPromise({
|
|
472
|
+
try: () =>
|
|
473
|
+
mf.loadRemote<{ runServer: (input: ServerInput) => ServerHandle }>(
|
|
474
|
+
"host/Server",
|
|
475
|
+
),
|
|
476
|
+
catch: (e) => new Error(`Failed to load host module: ${e}`),
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
if (!hostModule?.runServer) {
|
|
480
|
+
return yield* Effect.fail(
|
|
481
|
+
new Error("Host module does not export runServer function"),
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
callbacks.onLog(config.name, "Starting server...");
|
|
486
|
+
const serverHandle = hostModule.runServer({ config: runtimeConfig });
|
|
487
|
+
|
|
488
|
+
yield* Effect.tryPromise({
|
|
489
|
+
try: () => serverHandle.ready,
|
|
490
|
+
catch: (e) => new Error(`Server failed to start: ${e}`),
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
callbacks.onStatus(config.name, "ready");
|
|
494
|
+
|
|
495
|
+
const handle: ProcessHandle = {
|
|
496
|
+
name: config.name,
|
|
497
|
+
pid: process.pid,
|
|
498
|
+
kill: async () => {
|
|
499
|
+
callbacks.onLog(config.name, "Shutting down remote host...");
|
|
500
|
+
restoreConsole();
|
|
501
|
+
await serverHandle.shutdown();
|
|
502
|
+
},
|
|
503
|
+
waitForReady: Effect.void,
|
|
504
|
+
waitForExit: Effect.never,
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
return handle;
|
|
508
|
+
});
|
|
419
509
|
|
|
420
510
|
export const makeDevProcess = (
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
511
|
+
pkg: string,
|
|
512
|
+
env: Record<string, string> | undefined,
|
|
513
|
+
callbacks: ProcessCallbacks,
|
|
514
|
+
portOverride?: number,
|
|
515
|
+
bosConfig?: BosConfig,
|
|
426
516
|
) =>
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
517
|
+
Effect.gen(function* () {
|
|
518
|
+
const config = getProcessConfig(pkg, env, portOverride);
|
|
519
|
+
if (!config) {
|
|
520
|
+
return yield* Effect.fail(new Error(`Unknown package: ${pkg}`));
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (pkg === "host" && bosConfig) {
|
|
524
|
+
const uiSource = (env?.UI_SOURCE as SourceMode) ?? "local";
|
|
525
|
+
const apiSource = (env?.API_SOURCE as SourceMode) ?? "local";
|
|
526
|
+
const apiProxy = env?.API_PROXY;
|
|
527
|
+
|
|
528
|
+
let hostUrl = `http://localhost:${config.port}`;
|
|
529
|
+
if (process.env.HOST_URL) {
|
|
530
|
+
hostUrl = process.env.HOST_URL;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const runtimeConfig = buildRuntimeConfig(bosConfig, {
|
|
534
|
+
uiSource,
|
|
535
|
+
apiSource,
|
|
536
|
+
hostUrl,
|
|
537
|
+
proxy: apiProxy,
|
|
538
|
+
env: "development",
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
if (env?.HOST_SOURCE === "remote") {
|
|
542
|
+
return yield* spawnRemoteHost(config, callbacks, runtimeConfig);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return yield* spawnDevProcess(config, callbacks, runtimeConfig);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
return yield* spawnDevProcess(config, callbacks);
|
|
549
|
+
});
|