everything-dev 1.7.2 → 1.8.0
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/dist/api.cjs +1 -1
- package/dist/api.mjs +1 -1
- package/dist/app.cjs +82 -51
- package/dist/app.cjs.map +1 -1
- package/dist/app.mjs +82 -51
- package/dist/app.mjs.map +1 -1
- package/dist/cli/upgrade.cjs.map +1 -1
- package/dist/cli/upgrade.mjs.map +1 -1
- package/dist/components/dev-view.cjs +6 -3
- package/dist/components/dev-view.cjs.map +1 -1
- package/dist/components/dev-view.mjs +6 -3
- package/dist/components/dev-view.mjs.map +1 -1
- package/dist/components/streaming-view.cjs +5 -2
- package/dist/components/streaming-view.cjs.map +1 -1
- package/dist/components/streaming-view.mjs +5 -2
- package/dist/components/streaming-view.mjs.map +1 -1
- package/dist/config.cjs +28 -5
- package/dist/config.cjs.map +1 -1
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +28 -5
- package/dist/config.mjs.map +1 -1
- package/dist/contract.cjs +1 -0
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +14 -6
- package/dist/contract.d.cts.map +1 -1
- package/dist/contract.d.mts +14 -6
- package/dist/contract.d.mts.map +1 -1
- package/dist/contract.mjs +1 -0
- package/dist/contract.mjs.map +1 -1
- package/dist/dev-logs.cjs +6 -2
- package/dist/dev-logs.cjs.map +1 -1
- package/dist/dev-logs.mjs +7 -2
- package/dist/dev-logs.mjs.map +1 -1
- package/dist/dev-session.cjs +27 -23
- package/dist/dev-session.cjs.map +1 -1
- package/dist/dev-session.mjs +27 -24
- package/dist/dev-session.mjs.map +1 -1
- package/dist/federation.server.cjs +1 -1
- package/dist/federation.server.mjs +1 -1
- package/dist/host.cjs +4 -3
- package/dist/host.cjs.map +1 -1
- package/dist/host.d.cts.map +1 -1
- package/dist/host.d.mts.map +1 -1
- package/dist/host.mjs +4 -3
- package/dist/host.mjs.map +1 -1
- package/dist/integrity.cjs +68 -2
- package/dist/integrity.cjs.map +1 -1
- package/dist/integrity.d.cts +14 -1
- package/dist/integrity.d.cts.map +1 -1
- package/dist/integrity.d.mts +14 -1
- package/dist/integrity.d.mts.map +1 -1
- package/dist/integrity.mjs +66 -3
- package/dist/integrity.mjs.map +1 -1
- package/dist/mf.cjs +32 -0
- package/dist/mf.cjs.map +1 -1
- package/dist/mf.d.cts +3 -1
- package/dist/mf.d.cts.map +1 -1
- package/dist/mf.d.mts +3 -1
- package/dist/mf.d.mts.map +1 -1
- package/dist/mf.mjs +32 -1
- package/dist/mf.mjs.map +1 -1
- package/dist/orchestrator.cjs +167 -317
- package/dist/orchestrator.cjs.map +1 -1
- package/dist/orchestrator.d.cts +24 -21
- package/dist/orchestrator.d.cts.map +1 -1
- package/dist/orchestrator.d.mts +24 -21
- package/dist/orchestrator.d.mts.map +1 -1
- package/dist/orchestrator.mjs +168 -316
- package/dist/orchestrator.mjs.map +1 -1
- package/dist/plugin.cjs +38 -107
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +19 -5
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts +19 -5
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +39 -108
- package/dist/plugin.mjs.map +1 -1
- package/dist/service-descriptor.cjs +188 -0
- package/dist/service-descriptor.cjs.map +1 -0
- package/dist/service-descriptor.d.cts +107 -0
- package/dist/service-descriptor.d.cts.map +1 -0
- package/dist/service-descriptor.d.mts +107 -0
- package/dist/service-descriptor.d.mts.map +1 -0
- package/dist/service-descriptor.mjs +182 -0
- package/dist/service-descriptor.mjs.map +1 -0
- package/dist/types.cjs +8 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +18 -3
- package/dist/types.d.cts.map +1 -1
- package/dist/types.d.mts +18 -3
- package/dist/types.d.mts.map +1 -1
- package/dist/types.mjs +8 -1
- package/dist/types.mjs.map +1 -1
- package/dist/ui/index.cjs +1 -0
- package/dist/ui/index.d.cts +2 -2
- package/dist/ui/index.d.mts +2 -2
- package/dist/ui/index.mjs +2 -2
- package/dist/ui/runtime.cjs +4 -0
- package/dist/ui/runtime.cjs.map +1 -1
- package/dist/ui/runtime.d.cts +2 -1
- package/dist/ui/runtime.d.cts.map +1 -1
- package/dist/ui/runtime.d.mts +2 -1
- package/dist/ui/runtime.d.mts.map +1 -1
- package/dist/ui/runtime.mjs +4 -1
- package/dist/ui/runtime.mjs.map +1 -1
- package/package.json +12 -4
- package/skills/dev-workflow/SKILL.md +105 -0
- package/skills/publish-sync/SKILL.md +130 -0
- package/src/app.ts +98 -204
- package/src/cli/upgrade.ts +20 -4
- package/src/components/dev-view.tsx +8 -3
- package/src/components/streaming-view.ts +7 -2
- package/src/config.ts +40 -8
- package/src/contract.ts +1 -0
- package/src/dev-logs.ts +8 -1
- package/src/dev-session.ts +56 -79
- package/src/host.ts +4 -3
- package/src/integrity.ts +96 -10
- package/src/mf.ts +42 -0
- package/src/orchestrator.ts +232 -411
- package/src/plugin.ts +48 -136
- package/src/service-descriptor.ts +258 -0
- package/src/types.ts +8 -1
- package/src/ui/runtime.ts +5 -0
- package/dist/process-registry.cjs +0 -120
- package/dist/process-registry.cjs.map +0 -1
- package/dist/process-registry.d.cts +0 -25
- package/dist/process-registry.d.cts.map +0 -1
- package/dist/process-registry.d.mts +0 -25
- package/dist/process-registry.d.mts.map +0 -1
- package/dist/process-registry.mjs +0 -119
- package/dist/process-registry.mjs.map +0 -1
- package/src/process-registry.ts +0 -154
package/src/orchestrator.ts
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
import { createConnection } from "node:net";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { Command } from "@effect/platform";
|
|
3
|
+
import type { ExitCode } from "@effect/platform/CommandExecutor";
|
|
4
|
+
import { Deferred, Effect, Option, Ref, Stream } from "effect";
|
|
4
5
|
import { patchManifestFetchForSsrPublicPath } from "./mf";
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
args: string[];
|
|
12
|
-
cwd: string;
|
|
13
|
-
env?: Record<string, string>;
|
|
14
|
-
port: number;
|
|
15
|
-
readyPatterns: RegExp[];
|
|
16
|
-
errorPatterns: RegExp[];
|
|
17
|
-
}
|
|
6
|
+
import {
|
|
7
|
+
DevRuntimeConfig,
|
|
8
|
+
type ServiceDescriptor,
|
|
9
|
+
ServiceDescriptorMap,
|
|
10
|
+
} from "./service-descriptor";
|
|
11
|
+
import type { RuntimeConfig } from "./types";
|
|
18
12
|
|
|
19
13
|
export interface ProcessCallbacks {
|
|
20
14
|
onStatus: (name: string, status: ProcessStatus, message?: string) => void;
|
|
@@ -26,148 +20,17 @@ export interface ProcessHandle {
|
|
|
26
20
|
pid: number | undefined;
|
|
27
21
|
kill: Effect.Effect<void, unknown>;
|
|
28
22
|
waitForReady: Effect.Effect<void, Error>;
|
|
29
|
-
waitForExit: Effect.Effect<unknown>;
|
|
23
|
+
waitForExit: Effect.Effect<ExitCode, unknown>;
|
|
30
24
|
}
|
|
31
25
|
|
|
32
26
|
export type ProcessStatus = "pending" | "starting" | "ready" | "error";
|
|
33
27
|
|
|
34
|
-
interface
|
|
28
|
+
export interface ProcessState {
|
|
35
29
|
name: string;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
errorPatterns: RegExp[];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const processConfigBases: Record<string, ProcessConfigBase> = {
|
|
44
|
-
"host-build": {
|
|
45
|
-
name: "host-build",
|
|
46
|
-
command: "bun",
|
|
47
|
-
args: ["run", "build"],
|
|
48
|
-
cwd: "host",
|
|
49
|
-
readyPatterns: [/built in/i, /compiled.*successfully/i],
|
|
50
|
-
errorPatterns: [/error:/i, /failed/i, /exception/i],
|
|
51
|
-
},
|
|
52
|
-
host: {
|
|
53
|
-
name: "host",
|
|
54
|
-
command: "bun",
|
|
55
|
-
args: ["run", "dev"],
|
|
56
|
-
cwd: "host",
|
|
57
|
-
readyPatterns: [/Host (dev|production) server running at/i, /Server running at/i],
|
|
58
|
-
errorPatterns: [/error:/i, /failed/i, /exception/i],
|
|
59
|
-
},
|
|
60
|
-
ui: {
|
|
61
|
-
name: "ui",
|
|
62
|
-
command: "bun",
|
|
63
|
-
args: ["run", "dev"],
|
|
64
|
-
cwd: "ui",
|
|
65
|
-
readyPatterns: [/\bready\s+built in\b/i, /\bLocal:\b/i, /\bcompiled\b.*successfully/i],
|
|
66
|
-
errorPatterns: [/error/i, /failed to compile/i],
|
|
67
|
-
},
|
|
68
|
-
"ui-ssr": {
|
|
69
|
-
name: "ui-ssr",
|
|
70
|
-
command: "bun",
|
|
71
|
-
args: ["run", "dev:ssr"],
|
|
72
|
-
cwd: "ui",
|
|
73
|
-
readyPatterns: [/\bready\s+built in\b/i, /\bcompiled\b.*successfully/i],
|
|
74
|
-
errorPatterns: [/error/i, /failed/i],
|
|
75
|
-
},
|
|
76
|
-
api: {
|
|
77
|
-
name: "api",
|
|
78
|
-
command: "bun",
|
|
79
|
-
args: ["run", "dev"],
|
|
80
|
-
cwd: "api",
|
|
81
|
-
readyPatterns: [/ready in/i, /compiled.*successfully/i, /listening/i, /started/i],
|
|
82
|
-
errorPatterns: [/error/i, /failed/i],
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
export function getProcessConfig(
|
|
87
|
-
pkg: string,
|
|
88
|
-
env?: Record<string, string>,
|
|
89
|
-
portOverride?: number,
|
|
90
|
-
bosConfig?: BosConfig,
|
|
91
|
-
runtimeConfig?: RuntimeConfig,
|
|
92
|
-
): DevProcess | null {
|
|
93
|
-
if (pkg === "auth") {
|
|
94
|
-
const authConfig = runtimeConfig?.auth;
|
|
95
|
-
if (!authConfig?.localPath || authConfig.source !== "local") return null;
|
|
96
|
-
|
|
97
|
-
const port =
|
|
98
|
-
portOverride ?? authConfig.port ?? (authConfig.url ? parsePort(authConfig.url) : 3020);
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
name: "auth",
|
|
102
|
-
command: "bun",
|
|
103
|
-
args: ["run", "dev"],
|
|
104
|
-
cwd: authConfig.localPath,
|
|
105
|
-
port,
|
|
106
|
-
readyPatterns: [/ready in/i, /compiled.*successfully/i, /listening/i, /started/i],
|
|
107
|
-
errorPatterns: [/error/i, /failed/i],
|
|
108
|
-
env,
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (pkg.startsWith("plugin:")) {
|
|
113
|
-
const pluginId = pkg.slice("plugin:".length);
|
|
114
|
-
const pluginConfig = runtimeConfig?.plugins?.[pluginId] ?? null;
|
|
115
|
-
const localPath = pluginConfig?.localPath;
|
|
116
|
-
|
|
117
|
-
if (!localPath || pluginConfig?.source !== "local") return null;
|
|
118
|
-
|
|
119
|
-
const port =
|
|
120
|
-
portOverride ?? pluginConfig?.port ?? (pluginConfig?.url ? parsePort(pluginConfig.url) : 0);
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
name: pkg,
|
|
124
|
-
command: "bun",
|
|
125
|
-
args: ["run", "dev"],
|
|
126
|
-
cwd: localPath,
|
|
127
|
-
port,
|
|
128
|
-
readyPatterns: [/ready in/i, /compiled.*successfully/i, /listening/i, /started/i],
|
|
129
|
-
errorPatterns: [/error/i, /failed/i],
|
|
130
|
-
env,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const base = processConfigBases[pkg];
|
|
135
|
-
if (!base) return null;
|
|
136
|
-
|
|
137
|
-
let port: number;
|
|
138
|
-
if (pkg === "host") {
|
|
139
|
-
port =
|
|
140
|
-
portOverride ??
|
|
141
|
-
(runtimeConfig?.hostUrl
|
|
142
|
-
? parsePort(runtimeConfig.hostUrl)
|
|
143
|
-
: bosConfig
|
|
144
|
-
? getHostDevelopmentPort(bosConfig.app.host.development)
|
|
145
|
-
: 3000);
|
|
146
|
-
} else if (pkg === "ui") {
|
|
147
|
-
port =
|
|
148
|
-
runtimeConfig?.ui.port ?? (runtimeConfig?.ui.url ? parsePort(runtimeConfig.ui.url) : 3002);
|
|
149
|
-
} else if (pkg === "ui-ssr") {
|
|
150
|
-
const uiPort = runtimeConfig?.ui.ssrUrl
|
|
151
|
-
? parsePort(runtimeConfig.ui.ssrUrl)
|
|
152
|
-
: runtimeConfig?.ui.port
|
|
153
|
-
? runtimeConfig.ui.port + 1
|
|
154
|
-
: 3003;
|
|
155
|
-
port = uiPort;
|
|
156
|
-
} else if (pkg === "api") {
|
|
157
|
-
port =
|
|
158
|
-
runtimeConfig?.api.port ?? (runtimeConfig?.api.url ? parsePort(runtimeConfig.api.url) : 3014);
|
|
159
|
-
} else {
|
|
160
|
-
port = 0;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const cwd =
|
|
164
|
-
pkg === "ui"
|
|
165
|
-
? (runtimeConfig?.ui.localPath ?? base.cwd)
|
|
166
|
-
: pkg === "api"
|
|
167
|
-
? (runtimeConfig?.api.localPath ?? base.cwd)
|
|
168
|
-
: base.cwd;
|
|
169
|
-
|
|
170
|
-
return { ...base, cwd, port, env };
|
|
30
|
+
status: ProcessStatus;
|
|
31
|
+
port: number;
|
|
32
|
+
message?: string;
|
|
33
|
+
source?: "local" | "remote";
|
|
171
34
|
}
|
|
172
35
|
|
|
173
36
|
const stripAnsi = (input: string): string => {
|
|
@@ -178,49 +41,54 @@ const stripAnsi = (input: string): string => {
|
|
|
178
41
|
.replace(new RegExp(`${ESC}\\[[0-?]*[ -/]*[@-~]`, "g"), "");
|
|
179
42
|
};
|
|
180
43
|
|
|
181
|
-
const probeHttpOk =
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
44
|
+
const probeHttpOk = (url: string, timeoutMs = 400) =>
|
|
45
|
+
Effect.tryPromise({
|
|
46
|
+
try: async () => {
|
|
47
|
+
const controller = new AbortController();
|
|
48
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
49
|
+
try {
|
|
50
|
+
const res = await fetch(url, { signal: controller.signal });
|
|
51
|
+
return res.ok;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
} finally {
|
|
55
|
+
clearTimeout(timer);
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
catch: () => false,
|
|
59
|
+
});
|
|
193
60
|
|
|
194
|
-
const probeTcpOpen =
|
|
195
|
-
|
|
61
|
+
const probeTcpOpen = (port: number, timeoutMs = 250) =>
|
|
62
|
+
Effect.async<boolean>((resume) => {
|
|
196
63
|
const socket = createConnection({ host: "127.0.0.1", port });
|
|
197
64
|
const timer = setTimeout(() => {
|
|
198
65
|
socket.destroy();
|
|
199
|
-
|
|
66
|
+
resume(Effect.succeed(false));
|
|
200
67
|
}, timeoutMs);
|
|
201
68
|
socket.once("connect", () => {
|
|
202
69
|
clearTimeout(timer);
|
|
203
70
|
socket.destroy();
|
|
204
|
-
|
|
71
|
+
resume(Effect.succeed(true));
|
|
205
72
|
});
|
|
206
73
|
socket.once("error", () => {
|
|
207
74
|
clearTimeout(timer);
|
|
208
|
-
|
|
75
|
+
resume(Effect.succeed(false));
|
|
209
76
|
});
|
|
210
77
|
});
|
|
211
|
-
};
|
|
212
78
|
|
|
213
79
|
const detectStatus = (
|
|
214
80
|
line: string,
|
|
215
|
-
|
|
81
|
+
descriptor: ServiceDescriptor,
|
|
216
82
|
): { status: ProcessStatus; isError: boolean } | null => {
|
|
217
83
|
const cleanLine = stripAnsi(line);
|
|
218
|
-
|
|
84
|
+
const errorPatterns = descriptor.errorPatterns ?? [];
|
|
85
|
+
const readyPatterns = descriptor.readyPatterns ?? [];
|
|
86
|
+
for (const pattern of errorPatterns) {
|
|
219
87
|
if (pattern.test(cleanLine)) {
|
|
220
88
|
return { status: "error", isError: true };
|
|
221
89
|
}
|
|
222
90
|
}
|
|
223
|
-
for (const pattern of
|
|
91
|
+
for (const pattern of readyPatterns) {
|
|
224
92
|
if (pattern.test(cleanLine)) {
|
|
225
93
|
return { status: "ready", isError: false };
|
|
226
94
|
}
|
|
@@ -228,46 +96,6 @@ const detectStatus = (
|
|
|
228
96
|
return null;
|
|
229
97
|
};
|
|
230
98
|
|
|
231
|
-
const killProcessTree = (pid: number) =>
|
|
232
|
-
Effect.gen(function* () {
|
|
233
|
-
const killSignal = (signal: NodeJS.Signals) =>
|
|
234
|
-
Effect.try({
|
|
235
|
-
try: () => {
|
|
236
|
-
process.kill(-pid, signal);
|
|
237
|
-
},
|
|
238
|
-
catch: () => null,
|
|
239
|
-
}).pipe(Effect.ignore);
|
|
240
|
-
|
|
241
|
-
const killDirect = (signal: NodeJS.Signals) =>
|
|
242
|
-
Effect.try({
|
|
243
|
-
try: () => {
|
|
244
|
-
process.kill(pid, signal);
|
|
245
|
-
},
|
|
246
|
-
catch: () => null,
|
|
247
|
-
}).pipe(Effect.ignore);
|
|
248
|
-
|
|
249
|
-
const isRunning = () =>
|
|
250
|
-
Effect.try({
|
|
251
|
-
try: () => {
|
|
252
|
-
process.kill(pid, 0);
|
|
253
|
-
return true;
|
|
254
|
-
},
|
|
255
|
-
catch: () => false,
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
yield* killSignal("SIGTERM");
|
|
259
|
-
yield* killDirect("SIGTERM");
|
|
260
|
-
|
|
261
|
-
yield* Effect.sleep("200 millis");
|
|
262
|
-
|
|
263
|
-
const stillRunning = yield* isRunning();
|
|
264
|
-
if (stillRunning) {
|
|
265
|
-
yield* killSignal("SIGKILL");
|
|
266
|
-
yield* killDirect("SIGKILL");
|
|
267
|
-
yield* Effect.sleep("100 millis");
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
|
|
271
99
|
interface ServerHandle {
|
|
272
100
|
ready: Promise<void>;
|
|
273
101
|
shutdown: () => Promise<void>;
|
|
@@ -310,27 +138,18 @@ const patchConsole = (name: string, callbacks: ProcessCallbacks): (() => void) =
|
|
|
310
138
|
};
|
|
311
139
|
};
|
|
312
140
|
|
|
313
|
-
|
|
314
|
-
config: DevProcess,
|
|
315
|
-
callbacks: ProcessCallbacks,
|
|
316
|
-
runtimeConfig: RuntimeConfig,
|
|
317
|
-
) =>
|
|
141
|
+
const spawnRemoteHost = (descriptor: ServiceDescriptor, callbacks: ProcessCallbacks) =>
|
|
318
142
|
Effect.gen(function* () {
|
|
319
|
-
const
|
|
143
|
+
const runtimeConfig = yield* DevRuntimeConfig;
|
|
144
|
+
const remoteUrl = descriptor.remoteUrl;
|
|
320
145
|
if (!remoteUrl) {
|
|
321
|
-
return yield* Effect.fail(new Error("
|
|
146
|
+
return yield* Effect.fail(new Error("remoteUrl not provided on host descriptor"));
|
|
322
147
|
}
|
|
323
148
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
callbacks.onStatus(config.name, "starting");
|
|
331
|
-
callbacks.onLog(config.name, `Remote: ${remoteUrl}`);
|
|
332
|
-
const restoreConsole = patchConsole(config.name, callbacks);
|
|
333
|
-
callbacks.onLog(config.name, "Loading Module Federation runtime...");
|
|
149
|
+
callbacks.onStatus(descriptor.key, "starting");
|
|
150
|
+
callbacks.onLog(descriptor.key, `Remote: ${remoteUrl}`);
|
|
151
|
+
const restoreConsole = patchConsole(descriptor.key, callbacks);
|
|
152
|
+
callbacks.onLog(descriptor.key, "Loading Module Federation runtime...");
|
|
334
153
|
|
|
335
154
|
const mfRuntime = yield* Effect.tryPromise({
|
|
336
155
|
try: () => import("@module-federation/enhanced/runtime"),
|
|
@@ -378,7 +197,7 @@ export const spawnRemoteHost = (
|
|
|
378
197
|
});
|
|
379
198
|
|
|
380
199
|
(mf as any).registerRemotes([{ name: "host", entry: entryUrl }]);
|
|
381
|
-
callbacks.onLog(
|
|
200
|
+
callbacks.onLog(descriptor.key, `Loading host from ${entryUrl}...`);
|
|
382
201
|
|
|
383
202
|
const hostModule = yield* Effect.tryPromise({
|
|
384
203
|
try: () =>
|
|
@@ -392,20 +211,20 @@ export const spawnRemoteHost = (
|
|
|
392
211
|
return yield* Effect.fail(new Error("Host module does not export runServer function"));
|
|
393
212
|
}
|
|
394
213
|
|
|
395
|
-
callbacks.onLog(
|
|
214
|
+
callbacks.onLog(descriptor.key, "Starting server...");
|
|
396
215
|
const serverHandle = hostModule.runServer({ config: runtimeConfig });
|
|
397
216
|
yield* Effect.tryPromise({
|
|
398
217
|
try: () => serverHandle.ready,
|
|
399
218
|
catch: (e) => new Error(`Server failed to start: ${e}`),
|
|
400
219
|
});
|
|
401
220
|
|
|
402
|
-
callbacks.onStatus(
|
|
221
|
+
callbacks.onStatus(descriptor.key, "ready");
|
|
403
222
|
|
|
404
223
|
return {
|
|
405
|
-
name:
|
|
224
|
+
name: descriptor.key,
|
|
406
225
|
pid: process.pid,
|
|
407
226
|
kill: Effect.gen(function* () {
|
|
408
|
-
callbacks.onLog(
|
|
227
|
+
callbacks.onLog(descriptor.key, "Shutting down remote host...");
|
|
409
228
|
restoreConsole();
|
|
410
229
|
yield* Effect.tryPromise({
|
|
411
230
|
try: () => serverHandle.shutdown(),
|
|
@@ -417,55 +236,53 @@ export const spawnRemoteHost = (
|
|
|
417
236
|
} satisfies ProcessHandle;
|
|
418
237
|
});
|
|
419
238
|
|
|
420
|
-
|
|
421
|
-
config: DevProcess,
|
|
422
|
-
callbacks: ProcessCallbacks,
|
|
423
|
-
runtimeConfig?: RuntimeConfig,
|
|
424
|
-
registry?: ProcessRegistry,
|
|
425
|
-
) =>
|
|
239
|
+
const spawnDevProcess = (descriptor: ServiceDescriptor, callbacks: ProcessCallbacks) =>
|
|
426
240
|
Effect.gen(function* () {
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
configDir = process.cwd();
|
|
241
|
+
const runtimeConfig = yield* DevRuntimeConfig;
|
|
242
|
+
|
|
243
|
+
if (!descriptor.localPath) {
|
|
244
|
+
return yield* Effect.fail(new Error(`No localPath for local service: ${descriptor.key}`));
|
|
432
245
|
}
|
|
433
|
-
|
|
246
|
+
|
|
247
|
+
const fullCwd = descriptor.localPath;
|
|
248
|
+
const command = descriptor.command ?? "bun";
|
|
249
|
+
const args = descriptor.args ?? ["run", "dev"];
|
|
250
|
+
const port = descriptor.port ?? descriptor.defaultPort;
|
|
251
|
+
const name = descriptor.key;
|
|
252
|
+
|
|
434
253
|
const readyDeferred = yield* Deferred.make<void, Error>();
|
|
435
254
|
const statusRef = yield* Ref.make<ProcessStatus>("starting");
|
|
436
255
|
|
|
437
|
-
callbacks.onStatus(
|
|
256
|
+
callbacks.onStatus(name, "starting");
|
|
438
257
|
|
|
439
258
|
const envVars: Record<string, string> = {
|
|
440
259
|
...(process.env as Record<string, string>),
|
|
441
|
-
...config.env,
|
|
442
260
|
FORCE_COLOR: "1",
|
|
443
|
-
...(
|
|
261
|
+
...(port > 0 ? { PORT: String(port) } : {}),
|
|
444
262
|
};
|
|
445
263
|
|
|
446
|
-
if (
|
|
264
|
+
if (name === "host") {
|
|
447
265
|
envVars.BOS_RUNTIME_CONFIG = JSON.stringify(runtimeConfig);
|
|
448
266
|
}
|
|
449
267
|
|
|
450
|
-
const
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
268
|
+
const cmd = Command.make(command, ...args).pipe(
|
|
269
|
+
Command.workingDirectory(fullCwd),
|
|
270
|
+
Command.env(envVars),
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const proc = yield* Command.start(cmd);
|
|
456
274
|
|
|
457
275
|
const markReady = Effect.gen(function* () {
|
|
458
276
|
const currentStatus = yield* Ref.get(statusRef);
|
|
459
277
|
if (currentStatus === "ready" || currentStatus === "error") return;
|
|
460
278
|
yield* Ref.set(statusRef, "ready");
|
|
461
|
-
callbacks.onStatus(
|
|
279
|
+
callbacks.onStatus(name, "ready");
|
|
462
280
|
yield* Deferred.succeed(readyDeferred, undefined).pipe(Effect.ignore);
|
|
463
281
|
});
|
|
464
282
|
|
|
465
|
-
if (
|
|
466
|
-
const readinessPath =
|
|
467
|
-
|
|
468
|
-
const url = `http://127.0.0.1:${config.port}${readinessPath}`;
|
|
283
|
+
if (port > 0) {
|
|
284
|
+
const readinessPath = descriptor.readinessPath;
|
|
285
|
+
const url = `http://127.0.0.1:${port}${readinessPath}`;
|
|
469
286
|
|
|
470
287
|
yield* Effect.forkScoped(
|
|
471
288
|
Effect.gen(function* () {
|
|
@@ -473,74 +290,61 @@ export const spawnDevProcess = (
|
|
|
473
290
|
while (Date.now() < deadline) {
|
|
474
291
|
const status = yield* Ref.get(statusRef);
|
|
475
292
|
if (status === "ready" || status === "error") return;
|
|
476
|
-
const ok = url
|
|
477
|
-
? yield* Effect.tryPromise({
|
|
478
|
-
try: () => probeHttpOk(url),
|
|
479
|
-
catch: () => false,
|
|
480
|
-
})
|
|
481
|
-
: yield* Effect.tryPromise({
|
|
482
|
-
try: () => probeTcpOpen(config.port),
|
|
483
|
-
catch: () => false,
|
|
484
|
-
});
|
|
293
|
+
const ok = yield* probeHttpOk(url);
|
|
485
294
|
if (ok) {
|
|
486
295
|
yield* markReady;
|
|
487
296
|
return;
|
|
488
297
|
}
|
|
298
|
+
const tcpOk = yield* probeTcpOpen(port);
|
|
299
|
+
if (tcpOk) {
|
|
300
|
+
yield* markReady;
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
489
303
|
yield* Effect.sleep("200 millis");
|
|
490
304
|
}
|
|
491
305
|
}),
|
|
492
306
|
);
|
|
493
307
|
}
|
|
494
308
|
|
|
495
|
-
|
|
496
|
-
yield* registry.track({
|
|
497
|
-
pid: proc.pid,
|
|
498
|
-
name: config.name,
|
|
499
|
-
port: config.port,
|
|
500
|
-
startedAt: Date.now(),
|
|
501
|
-
command: [config.command, ...config.args].join(" "),
|
|
502
|
-
});
|
|
503
|
-
}
|
|
309
|
+
const pid = Number(proc.pid);
|
|
504
310
|
|
|
505
311
|
yield* Effect.forkScoped(
|
|
506
|
-
Effect.
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
yield* Deferred.fail(
|
|
518
|
-
readyDeferred,
|
|
519
|
-
new Error(`Process exited before ready: ${config.name}`),
|
|
520
|
-
).pipe(Effect.ignore);
|
|
521
|
-
}),
|
|
522
|
-
),
|
|
523
|
-
),
|
|
312
|
+
Effect.gen(function* () {
|
|
313
|
+
const exitCode = yield* proc.exitCode;
|
|
314
|
+
const currentStatus = yield* Ref.get(statusRef);
|
|
315
|
+
if (currentStatus === "ready") return;
|
|
316
|
+
callbacks.onLog(name, `Process exited before ready (exit code: ${exitCode})`, true);
|
|
317
|
+
yield* Ref.set(statusRef, "error");
|
|
318
|
+
callbacks.onStatus(name, "error");
|
|
319
|
+
yield* Deferred.fail(readyDeferred, new Error(`Process exited before ready: ${name}`)).pipe(
|
|
320
|
+
Effect.ignore,
|
|
321
|
+
);
|
|
322
|
+
}),
|
|
524
323
|
);
|
|
525
324
|
|
|
526
325
|
const handleLine = (line: string, isStderr: boolean) =>
|
|
527
326
|
Effect.gen(function* () {
|
|
528
327
|
if (!line.trim()) return;
|
|
529
328
|
|
|
530
|
-
|
|
329
|
+
const cleanLine = stripAnsi(line);
|
|
330
|
+
const looksLikeError =
|
|
331
|
+
isStderr &&
|
|
332
|
+
/^(error|fail|fatal|exception|unhandled|reject)/i.test(cleanLine) &&
|
|
333
|
+
!/^\$/.test(cleanLine);
|
|
334
|
+
callbacks.onLog(name, line, looksLikeError);
|
|
531
335
|
|
|
532
336
|
const currentStatus = yield* Ref.get(statusRef);
|
|
533
337
|
if (currentStatus === "ready") return;
|
|
534
338
|
|
|
535
|
-
const detected = detectStatus(line,
|
|
339
|
+
const detected = detectStatus(line, descriptor);
|
|
536
340
|
if (detected) {
|
|
537
341
|
yield* Ref.set(statusRef, detected.status);
|
|
538
|
-
callbacks.onStatus(
|
|
342
|
+
callbacks.onStatus(name, detected.status);
|
|
539
343
|
if (detected.status === "ready" || detected.status === "error") {
|
|
540
344
|
if (detected.status === "ready") {
|
|
541
345
|
yield* Deferred.succeed(readyDeferred, undefined).pipe(Effect.ignore);
|
|
542
346
|
} else {
|
|
543
|
-
yield* Deferred.fail(readyDeferred, new Error(`Process failed: ${
|
|
347
|
+
yield* Deferred.fail(readyDeferred, new Error(`Process failed: ${name}`)).pipe(
|
|
544
348
|
Effect.ignore,
|
|
545
349
|
);
|
|
546
350
|
}
|
|
@@ -548,131 +352,148 @@ export const spawnDevProcess = (
|
|
|
548
352
|
}
|
|
549
353
|
});
|
|
550
354
|
|
|
551
|
-
|
|
355
|
+
yield* Effect.forkScoped(
|
|
356
|
+
Stream.runForEach((line: string) => handleLine(line, false))(
|
|
357
|
+
Stream.splitLines(Stream.decodeText(proc.stdout, "utf-8")),
|
|
358
|
+
),
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
yield* Effect.forkScoped(
|
|
362
|
+
Stream.runForEach((line: string) => handleLine(line, true))(
|
|
363
|
+
Stream.splitLines(Stream.decodeText(proc.stderr, "utf-8")),
|
|
364
|
+
),
|
|
365
|
+
);
|
|
552
366
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
367
|
+
return {
|
|
368
|
+
name,
|
|
369
|
+
pid,
|
|
370
|
+
kill: Effect.gen(function* () {
|
|
371
|
+
const result = yield* proc.kill("SIGTERM").pipe(Effect.timeout("3 seconds"), Effect.option);
|
|
372
|
+
if (Option.isNone(result)) {
|
|
373
|
+
const pid = Number(proc.pid);
|
|
374
|
+
yield* Effect.try(() => process.kill(-pid, "SIGKILL")).pipe(Effect.ignore);
|
|
375
|
+
yield* Effect.sleep("250 millis");
|
|
558
376
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
reader.read().then(({ done, value }) => {
|
|
565
|
-
if (!active) return;
|
|
566
|
-
if (done) {
|
|
567
|
-
if (buffer) Effect.runSync(handleLine(buffer, false));
|
|
568
|
-
return;
|
|
569
|
-
}
|
|
570
|
-
buffer += decoder
|
|
571
|
-
.decode(value, { stream: true })
|
|
572
|
-
.replace(/\r\n/g, "\n")
|
|
573
|
-
.replace(/\r/g, "\n");
|
|
574
|
-
const lines = buffer.split("\n");
|
|
575
|
-
buffer = lines.pop() ?? "";
|
|
576
|
-
for (const line of lines) {
|
|
577
|
-
Effect.runSync(handleLine(line, false));
|
|
578
|
-
}
|
|
579
|
-
return pump();
|
|
580
|
-
});
|
|
377
|
+
}).pipe(Effect.ignore),
|
|
378
|
+
waitForReady: Deferred.await(readyDeferred),
|
|
379
|
+
waitForExit: proc.exitCode,
|
|
380
|
+
} satisfies ProcessHandle;
|
|
381
|
+
});
|
|
581
382
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
383
|
+
const spawnRemoteProbe = (
|
|
384
|
+
pkg: string,
|
|
385
|
+
descriptor: ServiceDescriptor,
|
|
386
|
+
callbacks: ProcessCallbacks,
|
|
387
|
+
) =>
|
|
388
|
+
Effect.gen(function* () {
|
|
389
|
+
callbacks.onStatus(pkg, "starting");
|
|
390
|
+
const readyDeferred = yield* Deferred.make<void, Error>();
|
|
391
|
+
const statusRef = yield* Ref.make<ProcessStatus>("starting");
|
|
585
392
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
);
|
|
393
|
+
const markReady = Effect.gen(function* () {
|
|
394
|
+
yield* Ref.set(statusRef, "ready");
|
|
395
|
+
yield* Deferred.succeed(readyDeferred, undefined);
|
|
396
|
+
callbacks.onStatus(pkg, "ready", "loaded");
|
|
397
|
+
});
|
|
592
398
|
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
.replace(/\r\n/g, "\n")
|
|
613
|
-
.replace(/\r/g, "\n");
|
|
614
|
-
const lines = buffer.split("\n");
|
|
615
|
-
buffer = lines.pop() ?? "";
|
|
616
|
-
for (const line of lines) {
|
|
617
|
-
Effect.runSync(handleLine(line, true));
|
|
618
|
-
}
|
|
619
|
-
return pump();
|
|
620
|
-
});
|
|
399
|
+
const markError = Effect.gen(function* () {
|
|
400
|
+
yield* Ref.set(statusRef, "error");
|
|
401
|
+
yield* Deferred.fail(readyDeferred, new Error(`Remote ${pkg} unreachable`));
|
|
402
|
+
callbacks.onStatus(pkg, "error", "unreachable");
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
const baseUrl = descriptor.url.replace(/\/$/, "");
|
|
406
|
+
const manifestUrl = `${baseUrl}/mf-manifest.json`;
|
|
407
|
+
const entryUrl = `${baseUrl}${descriptor.readinessPath}`;
|
|
408
|
+
const probeUrl = descriptor.readinessPath === "/health" ? `${baseUrl}/health` : manifestUrl;
|
|
409
|
+
|
|
410
|
+
yield* Effect.forkScoped(
|
|
411
|
+
Effect.gen(function* () {
|
|
412
|
+
const deadline = Date.now() + 60_000;
|
|
413
|
+
while (Date.now() < deadline) {
|
|
414
|
+
const status = yield* Ref.get(statusRef);
|
|
415
|
+
if (status === "ready" || status === "error") return;
|
|
416
|
+
|
|
417
|
+
const ok = yield* probeHttpOk(probeUrl, 400);
|
|
621
418
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
419
|
+
if (ok) {
|
|
420
|
+
yield* markReady;
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
625
423
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
424
|
+
const fallbackOk = yield* probeHttpOk(entryUrl, 400);
|
|
425
|
+
|
|
426
|
+
if (fallbackOk) {
|
|
427
|
+
yield* markReady;
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
yield* Effect.sleep("500 millis");
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const status = yield* Ref.get(statusRef);
|
|
435
|
+
if (status !== "ready") {
|
|
436
|
+
yield* markError;
|
|
437
|
+
}
|
|
630
438
|
}),
|
|
631
439
|
);
|
|
632
440
|
|
|
633
|
-
|
|
634
|
-
name:
|
|
635
|
-
pid:
|
|
636
|
-
kill:
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
proc.kill("SIGTERM");
|
|
640
|
-
yield* Effect.sleep("100 millis");
|
|
641
|
-
try {
|
|
642
|
-
proc.kill("SIGKILL");
|
|
643
|
-
} catch {}
|
|
644
|
-
}),
|
|
645
|
-
waitForReady: Deferred.await(readyDeferred),
|
|
646
|
-
waitForExit: Effect.gen(function* () {
|
|
647
|
-
yield* Fiber.joinAll([stdoutFiber, stderrFiber]);
|
|
648
|
-
return yield* Effect.promise(() => proc.exited);
|
|
441
|
+
return {
|
|
442
|
+
name: pkg,
|
|
443
|
+
pid: undefined,
|
|
444
|
+
kill: Effect.gen(function* () {
|
|
445
|
+
yield* Ref.set(statusRef, "error");
|
|
446
|
+
yield* Deferred.fail(readyDeferred, new Error("Killed")).pipe(Effect.ignore);
|
|
649
447
|
}),
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
448
|
+
waitForReady: Deferred.await(readyDeferred),
|
|
449
|
+
waitForExit: Effect.never,
|
|
450
|
+
} satisfies ProcessHandle;
|
|
653
451
|
});
|
|
654
452
|
|
|
655
|
-
export const makeDevProcess = (
|
|
656
|
-
pkg: string,
|
|
657
|
-
env: Record<string, string> | undefined,
|
|
658
|
-
callbacks: ProcessCallbacks,
|
|
659
|
-
portOverride?: number,
|
|
660
|
-
bosConfig?: BosConfig,
|
|
661
|
-
runtimeConfig?: RuntimeConfig,
|
|
662
|
-
registry?: ProcessRegistry,
|
|
663
|
-
) =>
|
|
453
|
+
export const makeDevProcess = (pkg: string, callbacks: ProcessCallbacks, portOverride?: number) =>
|
|
664
454
|
Effect.gen(function* () {
|
|
665
|
-
const
|
|
666
|
-
|
|
667
|
-
|
|
455
|
+
const services = yield* ServiceDescriptorMap;
|
|
456
|
+
const descriptor = services.get(pkg);
|
|
457
|
+
|
|
458
|
+
if (!descriptor) {
|
|
459
|
+
callbacks.onStatus(pkg, "ready", "Remote");
|
|
460
|
+
return {
|
|
461
|
+
name: pkg,
|
|
462
|
+
pid: undefined,
|
|
463
|
+
kill: Effect.void,
|
|
464
|
+
waitForReady: Effect.void,
|
|
465
|
+
waitForExit: Effect.never,
|
|
466
|
+
} satisfies ProcessHandle;
|
|
668
467
|
}
|
|
669
468
|
|
|
670
|
-
if (pkg === "host" &&
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
469
|
+
if (pkg === "host" && descriptor.source === "remote") {
|
|
470
|
+
return yield* spawnRemoteHost(descriptor, callbacks);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (descriptor.source === "remote" || !descriptor.localPath) {
|
|
474
|
+
return yield* spawnRemoteProbe(pkg, descriptor, callbacks);
|
|
675
475
|
}
|
|
676
476
|
|
|
677
|
-
|
|
477
|
+
const resolvedDescriptor = portOverride ? { ...descriptor, port: portOverride } : descriptor;
|
|
478
|
+
|
|
479
|
+
return yield* spawnDevProcess(resolvedDescriptor, callbacks);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
export function getProcessStates(
|
|
483
|
+
packages: string[],
|
|
484
|
+
services: Map<string, ServiceDescriptor>,
|
|
485
|
+
portOverride?: number,
|
|
486
|
+
): ProcessState[] {
|
|
487
|
+
return packages.map((pkg) => {
|
|
488
|
+
const descriptor = services.get(pkg);
|
|
489
|
+
return {
|
|
490
|
+
name: pkg,
|
|
491
|
+
status: "pending" as const,
|
|
492
|
+
port:
|
|
493
|
+
portOverride && pkg === "host"
|
|
494
|
+
? portOverride
|
|
495
|
+
: (descriptor?.port ?? descriptor?.defaultPort ?? 0),
|
|
496
|
+
source: descriptor?.source,
|
|
497
|
+
};
|
|
678
498
|
});
|
|
499
|
+
}
|