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.
Files changed (134) hide show
  1. package/dist/api.cjs +1 -1
  2. package/dist/api.mjs +1 -1
  3. package/dist/app.cjs +82 -51
  4. package/dist/app.cjs.map +1 -1
  5. package/dist/app.mjs +82 -51
  6. package/dist/app.mjs.map +1 -1
  7. package/dist/cli/upgrade.cjs.map +1 -1
  8. package/dist/cli/upgrade.mjs.map +1 -1
  9. package/dist/components/dev-view.cjs +6 -3
  10. package/dist/components/dev-view.cjs.map +1 -1
  11. package/dist/components/dev-view.mjs +6 -3
  12. package/dist/components/dev-view.mjs.map +1 -1
  13. package/dist/components/streaming-view.cjs +5 -2
  14. package/dist/components/streaming-view.cjs.map +1 -1
  15. package/dist/components/streaming-view.mjs +5 -2
  16. package/dist/components/streaming-view.mjs.map +1 -1
  17. package/dist/config.cjs +28 -5
  18. package/dist/config.cjs.map +1 -1
  19. package/dist/config.d.cts.map +1 -1
  20. package/dist/config.d.mts.map +1 -1
  21. package/dist/config.mjs +28 -5
  22. package/dist/config.mjs.map +1 -1
  23. package/dist/contract.cjs +1 -0
  24. package/dist/contract.cjs.map +1 -1
  25. package/dist/contract.d.cts +14 -6
  26. package/dist/contract.d.cts.map +1 -1
  27. package/dist/contract.d.mts +14 -6
  28. package/dist/contract.d.mts.map +1 -1
  29. package/dist/contract.mjs +1 -0
  30. package/dist/contract.mjs.map +1 -1
  31. package/dist/dev-logs.cjs +6 -2
  32. package/dist/dev-logs.cjs.map +1 -1
  33. package/dist/dev-logs.mjs +7 -2
  34. package/dist/dev-logs.mjs.map +1 -1
  35. package/dist/dev-session.cjs +27 -23
  36. package/dist/dev-session.cjs.map +1 -1
  37. package/dist/dev-session.mjs +27 -24
  38. package/dist/dev-session.mjs.map +1 -1
  39. package/dist/federation.server.cjs +1 -1
  40. package/dist/federation.server.mjs +1 -1
  41. package/dist/host.cjs +4 -3
  42. package/dist/host.cjs.map +1 -1
  43. package/dist/host.d.cts.map +1 -1
  44. package/dist/host.d.mts.map +1 -1
  45. package/dist/host.mjs +4 -3
  46. package/dist/host.mjs.map +1 -1
  47. package/dist/integrity.cjs +68 -2
  48. package/dist/integrity.cjs.map +1 -1
  49. package/dist/integrity.d.cts +14 -1
  50. package/dist/integrity.d.cts.map +1 -1
  51. package/dist/integrity.d.mts +14 -1
  52. package/dist/integrity.d.mts.map +1 -1
  53. package/dist/integrity.mjs +66 -3
  54. package/dist/integrity.mjs.map +1 -1
  55. package/dist/mf.cjs +32 -0
  56. package/dist/mf.cjs.map +1 -1
  57. package/dist/mf.d.cts +3 -1
  58. package/dist/mf.d.cts.map +1 -1
  59. package/dist/mf.d.mts +3 -1
  60. package/dist/mf.d.mts.map +1 -1
  61. package/dist/mf.mjs +32 -1
  62. package/dist/mf.mjs.map +1 -1
  63. package/dist/orchestrator.cjs +167 -317
  64. package/dist/orchestrator.cjs.map +1 -1
  65. package/dist/orchestrator.d.cts +24 -21
  66. package/dist/orchestrator.d.cts.map +1 -1
  67. package/dist/orchestrator.d.mts +24 -21
  68. package/dist/orchestrator.d.mts.map +1 -1
  69. package/dist/orchestrator.mjs +168 -316
  70. package/dist/orchestrator.mjs.map +1 -1
  71. package/dist/plugin.cjs +38 -107
  72. package/dist/plugin.cjs.map +1 -1
  73. package/dist/plugin.d.cts +19 -5
  74. package/dist/plugin.d.cts.map +1 -1
  75. package/dist/plugin.d.mts +19 -5
  76. package/dist/plugin.d.mts.map +1 -1
  77. package/dist/plugin.mjs +39 -108
  78. package/dist/plugin.mjs.map +1 -1
  79. package/dist/service-descriptor.cjs +188 -0
  80. package/dist/service-descriptor.cjs.map +1 -0
  81. package/dist/service-descriptor.d.cts +107 -0
  82. package/dist/service-descriptor.d.cts.map +1 -0
  83. package/dist/service-descriptor.d.mts +107 -0
  84. package/dist/service-descriptor.d.mts.map +1 -0
  85. package/dist/service-descriptor.mjs +182 -0
  86. package/dist/service-descriptor.mjs.map +1 -0
  87. package/dist/types.cjs +8 -1
  88. package/dist/types.cjs.map +1 -1
  89. package/dist/types.d.cts +18 -3
  90. package/dist/types.d.cts.map +1 -1
  91. package/dist/types.d.mts +18 -3
  92. package/dist/types.d.mts.map +1 -1
  93. package/dist/types.mjs +8 -1
  94. package/dist/types.mjs.map +1 -1
  95. package/dist/ui/index.cjs +1 -0
  96. package/dist/ui/index.d.cts +2 -2
  97. package/dist/ui/index.d.mts +2 -2
  98. package/dist/ui/index.mjs +2 -2
  99. package/dist/ui/runtime.cjs +4 -0
  100. package/dist/ui/runtime.cjs.map +1 -1
  101. package/dist/ui/runtime.d.cts +2 -1
  102. package/dist/ui/runtime.d.cts.map +1 -1
  103. package/dist/ui/runtime.d.mts +2 -1
  104. package/dist/ui/runtime.d.mts.map +1 -1
  105. package/dist/ui/runtime.mjs +4 -1
  106. package/dist/ui/runtime.mjs.map +1 -1
  107. package/package.json +12 -4
  108. package/skills/dev-workflow/SKILL.md +105 -0
  109. package/skills/publish-sync/SKILL.md +130 -0
  110. package/src/app.ts +98 -204
  111. package/src/cli/upgrade.ts +20 -4
  112. package/src/components/dev-view.tsx +8 -3
  113. package/src/components/streaming-view.ts +7 -2
  114. package/src/config.ts +40 -8
  115. package/src/contract.ts +1 -0
  116. package/src/dev-logs.ts +8 -1
  117. package/src/dev-session.ts +56 -79
  118. package/src/host.ts +4 -3
  119. package/src/integrity.ts +96 -10
  120. package/src/mf.ts +42 -0
  121. package/src/orchestrator.ts +232 -411
  122. package/src/plugin.ts +48 -136
  123. package/src/service-descriptor.ts +258 -0
  124. package/src/types.ts +8 -1
  125. package/src/ui/runtime.ts +5 -0
  126. package/dist/process-registry.cjs +0 -120
  127. package/dist/process-registry.cjs.map +0 -1
  128. package/dist/process-registry.d.cts +0 -25
  129. package/dist/process-registry.d.cts.map +0 -1
  130. package/dist/process-registry.d.mts +0 -25
  131. package/dist/process-registry.d.mts.map +0 -1
  132. package/dist/process-registry.mjs +0 -119
  133. package/dist/process-registry.mjs.map +0 -1
  134. package/src/process-registry.ts +0 -154
@@ -1,204 +1,62 @@
1
- import { getHostDevelopmentPort, getProjectRoot, parsePort } from "./config.mjs";
2
1
  import { patchManifestFetchForSsrPublicPath } from "./mf.mjs";
3
- import { Deferred, Effect, Fiber, Ref } from "effect";
2
+ import { DevRuntimeConfig, ServiceDescriptorMap } from "./service-descriptor.mjs";
3
+ import { Deferred, Effect, Option, Ref, Stream } from "effect";
4
4
  import { createConnection } from "node:net";
5
+ import { Command } from "@effect/platform";
5
6
 
6
7
  //#region src/orchestrator.ts
7
- const processConfigBases = {
8
- "host-build": {
9
- name: "host-build",
10
- command: "bun",
11
- args: ["run", "build"],
12
- cwd: "host",
13
- readyPatterns: [/built in/i, /compiled.*successfully/i],
14
- errorPatterns: [
15
- /error:/i,
16
- /failed/i,
17
- /exception/i
18
- ]
19
- },
20
- host: {
21
- name: "host",
22
- command: "bun",
23
- args: ["run", "dev"],
24
- cwd: "host",
25
- readyPatterns: [/Host (dev|production) server running at/i, /Server running at/i],
26
- errorPatterns: [
27
- /error:/i,
28
- /failed/i,
29
- /exception/i
30
- ]
31
- },
32
- ui: {
33
- name: "ui",
34
- command: "bun",
35
- args: ["run", "dev"],
36
- cwd: "ui",
37
- readyPatterns: [
38
- /\bready\s+built in\b/i,
39
- /\bLocal:\b/i,
40
- /\bcompiled\b.*successfully/i
41
- ],
42
- errorPatterns: [/error/i, /failed to compile/i]
43
- },
44
- "ui-ssr": {
45
- name: "ui-ssr",
46
- command: "bun",
47
- args: ["run", "dev:ssr"],
48
- cwd: "ui",
49
- readyPatterns: [/\bready\s+built in\b/i, /\bcompiled\b.*successfully/i],
50
- errorPatterns: [/error/i, /failed/i]
51
- },
52
- api: {
53
- name: "api",
54
- command: "bun",
55
- args: ["run", "dev"],
56
- cwd: "api",
57
- readyPatterns: [
58
- /ready in/i,
59
- /compiled.*successfully/i,
60
- /listening/i,
61
- /started/i
62
- ],
63
- errorPatterns: [/error/i, /failed/i]
64
- }
65
- };
66
- function getProcessConfig(pkg, env, portOverride, bosConfig, runtimeConfig) {
67
- if (pkg === "auth") {
68
- const authConfig = runtimeConfig?.auth;
69
- if (!authConfig?.localPath || authConfig.source !== "local") return null;
70
- const port = portOverride ?? authConfig.port ?? (authConfig.url ? parsePort(authConfig.url) : 3020);
71
- return {
72
- name: "auth",
73
- command: "bun",
74
- args: ["run", "dev"],
75
- cwd: authConfig.localPath,
76
- port,
77
- readyPatterns: [
78
- /ready in/i,
79
- /compiled.*successfully/i,
80
- /listening/i,
81
- /started/i
82
- ],
83
- errorPatterns: [/error/i, /failed/i],
84
- env
85
- };
86
- }
87
- if (pkg.startsWith("plugin:")) {
88
- const pluginId = pkg.slice(7);
89
- const pluginConfig = runtimeConfig?.plugins?.[pluginId] ?? null;
90
- const localPath = pluginConfig?.localPath;
91
- if (!localPath || pluginConfig?.source !== "local") return null;
92
- return {
93
- name: pkg,
94
- command: "bun",
95
- args: ["run", "dev"],
96
- cwd: localPath,
97
- port: portOverride ?? pluginConfig?.port ?? (pluginConfig?.url ? parsePort(pluginConfig.url) : 0),
98
- readyPatterns: [
99
- /ready in/i,
100
- /compiled.*successfully/i,
101
- /listening/i,
102
- /started/i
103
- ],
104
- errorPatterns: [/error/i, /failed/i],
105
- env
106
- };
107
- }
108
- const base = processConfigBases[pkg];
109
- if (!base) return null;
110
- let port;
111
- if (pkg === "host") port = portOverride ?? (runtimeConfig?.hostUrl ? parsePort(runtimeConfig.hostUrl) : bosConfig ? getHostDevelopmentPort(bosConfig.app.host.development) : 3e3);
112
- else if (pkg === "ui") port = runtimeConfig?.ui.port ?? (runtimeConfig?.ui.url ? parsePort(runtimeConfig.ui.url) : 3002);
113
- else if (pkg === "ui-ssr") port = runtimeConfig?.ui.ssrUrl ? parsePort(runtimeConfig.ui.ssrUrl) : runtimeConfig?.ui.port ? runtimeConfig.ui.port + 1 : 3003;
114
- else if (pkg === "api") port = runtimeConfig?.api.port ?? (runtimeConfig?.api.url ? parsePort(runtimeConfig.api.url) : 3014);
115
- else port = 0;
116
- const cwd = pkg === "ui" ? runtimeConfig?.ui.localPath ?? base.cwd : pkg === "api" ? runtimeConfig?.api.localPath ?? base.cwd : base.cwd;
117
- return {
118
- ...base,
119
- cwd,
120
- port,
121
- env
122
- };
123
- }
124
8
  const stripAnsi = (input) => {
125
9
  const ESC = String.fromCharCode(27);
126
10
  const BEL = String.fromCharCode(7);
127
11
  return input.replace(new RegExp(`${ESC}\\][^${BEL}]*${BEL}`, "g"), "").replace(new RegExp(`${ESC}\\[[0-?]*[ -/]*[@-~]`, "g"), "");
128
12
  };
129
- const probeHttpOk = async (url, timeoutMs = 400) => {
130
- const controller = new AbortController();
131
- const timer = setTimeout(() => controller.abort(), timeoutMs);
132
- try {
133
- return (await fetch(url, { signal: controller.signal })).ok;
134
- } catch {
135
- return false;
136
- } finally {
137
- clearTimeout(timer);
138
- }
139
- };
140
- const probeTcpOpen = async (port, timeoutMs = 250) => {
141
- return new Promise((resolve) => {
142
- const socket = createConnection({
143
- host: "127.0.0.1",
144
- port
145
- });
146
- const timer = setTimeout(() => {
147
- socket.destroy();
148
- resolve(false);
149
- }, timeoutMs);
150
- socket.once("connect", () => {
13
+ const probeHttpOk = (url, timeoutMs = 400) => Effect.tryPromise({
14
+ try: async () => {
15
+ const controller = new AbortController();
16
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
17
+ try {
18
+ return (await fetch(url, { signal: controller.signal })).ok;
19
+ } catch {
20
+ return false;
21
+ } finally {
151
22
  clearTimeout(timer);
152
- socket.destroy();
153
- resolve(true);
154
- });
155
- socket.once("error", () => {
156
- clearTimeout(timer);
157
- resolve(false);
158
- });
23
+ }
24
+ },
25
+ catch: () => false
26
+ });
27
+ const probeTcpOpen = (port, timeoutMs = 250) => Effect.async((resume) => {
28
+ const socket = createConnection({
29
+ host: "127.0.0.1",
30
+ port
159
31
  });
160
- };
161
- const detectStatus = (line, config) => {
32
+ const timer = setTimeout(() => {
33
+ socket.destroy();
34
+ resume(Effect.succeed(false));
35
+ }, timeoutMs);
36
+ socket.once("connect", () => {
37
+ clearTimeout(timer);
38
+ socket.destroy();
39
+ resume(Effect.succeed(true));
40
+ });
41
+ socket.once("error", () => {
42
+ clearTimeout(timer);
43
+ resume(Effect.succeed(false));
44
+ });
45
+ });
46
+ const detectStatus = (line, descriptor) => {
162
47
  const cleanLine = stripAnsi(line);
163
- for (const pattern of config.errorPatterns) if (pattern.test(cleanLine)) return {
48
+ const errorPatterns = descriptor.errorPatterns ?? [];
49
+ const readyPatterns = descriptor.readyPatterns ?? [];
50
+ for (const pattern of errorPatterns) if (pattern.test(cleanLine)) return {
164
51
  status: "error",
165
52
  isError: true
166
53
  };
167
- for (const pattern of config.readyPatterns) if (pattern.test(cleanLine)) return {
54
+ for (const pattern of readyPatterns) if (pattern.test(cleanLine)) return {
168
55
  status: "ready",
169
56
  isError: false
170
57
  };
171
58
  return null;
172
59
  };
173
- const killProcessTree = (pid) => Effect.gen(function* () {
174
- const killSignal = (signal) => Effect.try({
175
- try: () => {
176
- process.kill(-pid, signal);
177
- },
178
- catch: () => null
179
- }).pipe(Effect.ignore);
180
- const killDirect = (signal) => Effect.try({
181
- try: () => {
182
- process.kill(pid, signal);
183
- },
184
- catch: () => null
185
- }).pipe(Effect.ignore);
186
- const isRunning = () => Effect.try({
187
- try: () => {
188
- process.kill(pid, 0);
189
- return true;
190
- },
191
- catch: () => false
192
- });
193
- yield* killSignal("SIGTERM");
194
- yield* killDirect("SIGTERM");
195
- yield* Effect.sleep("200 millis");
196
- if (yield* isRunning()) {
197
- yield* killSignal("SIGKILL");
198
- yield* killDirect("SIGKILL");
199
- yield* Effect.sleep("100 millis");
200
- }
201
- });
202
60
  const patchConsole = (name, callbacks) => {
203
61
  const originalLog = console.log;
204
62
  const originalError = console.error;
@@ -226,14 +84,14 @@ const patchConsole = (name, callbacks) => {
226
84
  console.info = originalInfo;
227
85
  };
228
86
  };
229
- const spawnRemoteHost = (config, callbacks, runtimeConfig) => Effect.gen(function* () {
230
- const remoteUrl = config.env?.HOST_REMOTE_URL;
231
- if (!remoteUrl) return yield* Effect.fail(/* @__PURE__ */ new Error("HOST_REMOTE_URL not provided for remote host"));
232
- if (config.env) for (const [key, value] of Object.entries(config.env)) process.env[key] = value;
233
- callbacks.onStatus(config.name, "starting");
234
- callbacks.onLog(config.name, `Remote: ${remoteUrl}`);
235
- const restoreConsole = patchConsole(config.name, callbacks);
236
- callbacks.onLog(config.name, "Loading Module Federation runtime...");
87
+ const spawnRemoteHost = (descriptor, callbacks) => Effect.gen(function* () {
88
+ const runtimeConfig = yield* DevRuntimeConfig;
89
+ const remoteUrl = descriptor.remoteUrl;
90
+ if (!remoteUrl) return yield* Effect.fail(/* @__PURE__ */ new Error("remoteUrl not provided on host descriptor"));
91
+ callbacks.onStatus(descriptor.key, "starting");
92
+ callbacks.onLog(descriptor.key, `Remote: ${remoteUrl}`);
93
+ const restoreConsole = patchConsole(descriptor.key, callbacks);
94
+ callbacks.onLog(descriptor.key, "Loading Module Federation runtime...");
237
95
  const mfRuntime = yield* Effect.tryPromise({
238
96
  try: () => import("@module-federation/enhanced/runtime"),
239
97
  catch: (e) => /* @__PURE__ */ new Error(`Failed to load MF runtime: ${e}`)
@@ -270,24 +128,24 @@ const spawnRemoteHost = (config, callbacks, runtimeConfig) => Effect.gen(functio
270
128
  name: "host",
271
129
  entry: entryUrl
272
130
  }]);
273
- callbacks.onLog(config.name, `Loading host from ${entryUrl}...`);
131
+ callbacks.onLog(descriptor.key, `Loading host from ${entryUrl}...`);
274
132
  const hostModule = yield* Effect.tryPromise({
275
133
  try: () => mf.loadRemote("host/Server"),
276
134
  catch: (e) => /* @__PURE__ */ new Error(`Failed to load host module: ${e}`)
277
135
  });
278
136
  if (!hostModule?.runServer) return yield* Effect.fail(/* @__PURE__ */ new Error("Host module does not export runServer function"));
279
- callbacks.onLog(config.name, "Starting server...");
137
+ callbacks.onLog(descriptor.key, "Starting server...");
280
138
  const serverHandle = hostModule.runServer({ config: runtimeConfig });
281
139
  yield* Effect.tryPromise({
282
140
  try: () => serverHandle.ready,
283
141
  catch: (e) => /* @__PURE__ */ new Error(`Server failed to start: ${e}`)
284
142
  });
285
- callbacks.onStatus(config.name, "ready");
143
+ callbacks.onStatus(descriptor.key, "ready");
286
144
  return {
287
- name: config.name,
145
+ name: descriptor.key,
288
146
  pid: process.pid,
289
147
  kill: Effect.gen(function* () {
290
- callbacks.onLog(config.name, "Shutting down remote host...");
148
+ callbacks.onLog(descriptor.key, "Shutting down remote host...");
291
149
  restoreConsole();
292
150
  yield* Effect.tryPromise({
293
151
  try: () => serverHandle.shutdown(),
@@ -298,56 +156,44 @@ const spawnRemoteHost = (config, callbacks, runtimeConfig) => Effect.gen(functio
298
156
  waitForExit: Effect.never
299
157
  };
300
158
  });
301
- const spawnDevProcess = (config, callbacks, runtimeConfig, registry) => Effect.gen(function* () {
302
- let configDir;
303
- try {
304
- configDir = getProjectRoot();
305
- } catch {
306
- configDir = process.cwd();
307
- }
308
- const fullCwd = config.cwd.startsWith("/") ? config.cwd : `${configDir}/${config.cwd}`;
159
+ const spawnDevProcess = (descriptor, callbacks) => Effect.gen(function* () {
160
+ const runtimeConfig = yield* DevRuntimeConfig;
161
+ if (!descriptor.localPath) return yield* Effect.fail(/* @__PURE__ */ new Error(`No localPath for local service: ${descriptor.key}`));
162
+ const fullCwd = descriptor.localPath;
163
+ const command = descriptor.command ?? "bun";
164
+ const args = descriptor.args ?? ["run", "dev"];
165
+ const port = descriptor.port ?? descriptor.defaultPort;
166
+ const name = descriptor.key;
309
167
  const readyDeferred = yield* Deferred.make();
310
168
  const statusRef = yield* Ref.make("starting");
311
- callbacks.onStatus(config.name, "starting");
169
+ callbacks.onStatus(name, "starting");
312
170
  const envVars = {
313
171
  ...process.env,
314
- ...config.env,
315
172
  FORCE_COLOR: "1",
316
- ...config.port > 0 ? { PORT: String(config.port) } : {}
173
+ ...port > 0 ? { PORT: String(port) } : {}
317
174
  };
318
- if (runtimeConfig && config.name === "host") envVars.BOS_RUNTIME_CONFIG = JSON.stringify(runtimeConfig);
319
- const proc = Bun.spawn({
320
- cmd: [config.command, ...config.args],
321
- cwd: fullCwd,
322
- env: envVars,
323
- stdio: [
324
- "inherit",
325
- "pipe",
326
- "pipe"
327
- ]
328
- });
175
+ if (name === "host") envVars.BOS_RUNTIME_CONFIG = JSON.stringify(runtimeConfig);
176
+ const cmd = Command.make(command, ...args).pipe(Command.workingDirectory(fullCwd), Command.env(envVars));
177
+ const proc = yield* Command.start(cmd);
329
178
  const markReady = Effect.gen(function* () {
330
179
  const currentStatus = yield* Ref.get(statusRef);
331
180
  if (currentStatus === "ready" || currentStatus === "error") return;
332
181
  yield* Ref.set(statusRef, "ready");
333
- callbacks.onStatus(config.name, "ready");
182
+ callbacks.onStatus(name, "ready");
334
183
  yield* Deferred.succeed(readyDeferred, void 0).pipe(Effect.ignore);
335
184
  });
336
- if (config.port > 0) {
337
- const readinessPath = config.name === "host" ? "/health" : config.name === "ui-ssr" ? "/" : "/remoteEntry.js";
338
- const url = `http://127.0.0.1:${config.port}${readinessPath}`;
185
+ if (port > 0) {
186
+ const url = `http://127.0.0.1:${port}${descriptor.readinessPath}`;
339
187
  yield* Effect.forkScoped(Effect.gen(function* () {
340
188
  const deadline = Date.now() + 9e4;
341
189
  while (Date.now() < deadline) {
342
190
  const status = yield* Ref.get(statusRef);
343
191
  if (status === "ready" || status === "error") return;
344
- if (url ? yield* Effect.tryPromise({
345
- try: () => probeHttpOk(url),
346
- catch: () => false
347
- }) : yield* Effect.tryPromise({
348
- try: () => probeTcpOpen(config.port),
349
- catch: () => false
350
- })) {
192
+ if (yield* probeHttpOk(url)) {
193
+ yield* markReady;
194
+ return;
195
+ }
196
+ if (yield* probeTcpOpen(port)) {
351
197
  yield* markReady;
352
198
  return;
353
199
  }
@@ -355,117 +201,123 @@ const spawnDevProcess = (config, callbacks, runtimeConfig, registry) => Effect.g
355
201
  }
356
202
  }));
357
203
  }
358
- if (registry && proc.pid) yield* registry.track({
359
- pid: proc.pid,
360
- name: config.name,
361
- port: config.port,
362
- startedAt: Date.now(),
363
- command: [config.command, ...config.args].join(" ")
364
- });
365
- yield* Effect.forkScoped(Effect.promise(() => proc.exited).pipe(Effect.andThen((code) => Effect.gen(function* () {
366
- if (registry && proc.pid) yield* registry.untrack(proc.pid).pipe(Effect.ignore);
204
+ const pid = Number(proc.pid);
205
+ yield* Effect.forkScoped(Effect.gen(function* () {
206
+ const exitCode = yield* proc.exitCode;
367
207
  if ((yield* Ref.get(statusRef)) === "ready") return;
368
- callbacks.onLog(config.name, `Process exited before ready (exit code: ${code})`, true);
208
+ callbacks.onLog(name, `Process exited before ready (exit code: ${exitCode})`, true);
369
209
  yield* Ref.set(statusRef, "error");
370
- callbacks.onStatus(config.name, "error");
371
- yield* Deferred.fail(readyDeferred, /* @__PURE__ */ new Error(`Process exited before ready: ${config.name}`)).pipe(Effect.ignore);
372
- }))));
210
+ callbacks.onStatus(name, "error");
211
+ yield* Deferred.fail(readyDeferred, /* @__PURE__ */ new Error(`Process exited before ready: ${name}`)).pipe(Effect.ignore);
212
+ }));
373
213
  const handleLine = (line, isStderr) => Effect.gen(function* () {
374
214
  if (!line.trim()) return;
375
- callbacks.onLog(config.name, line, isStderr);
215
+ const cleanLine = stripAnsi(line);
216
+ const looksLikeError = isStderr && /^(error|fail|fatal|exception|unhandled|reject)/i.test(cleanLine) && !/^\$/.test(cleanLine);
217
+ callbacks.onLog(name, line, looksLikeError);
376
218
  if ((yield* Ref.get(statusRef)) === "ready") return;
377
- const detected = detectStatus(line, config);
219
+ const detected = detectStatus(line, descriptor);
378
220
  if (detected) {
379
221
  yield* Ref.set(statusRef, detected.status);
380
- callbacks.onStatus(config.name, detected.status);
222
+ callbacks.onStatus(name, detected.status);
381
223
  if (detected.status === "ready" || detected.status === "error") if (detected.status === "ready") yield* Deferred.succeed(readyDeferred, void 0).pipe(Effect.ignore);
382
- else yield* Deferred.fail(readyDeferred, /* @__PURE__ */ new Error(`Process failed: ${config.name}`)).pipe(Effect.ignore);
224
+ else yield* Deferred.fail(readyDeferred, /* @__PURE__ */ new Error(`Process failed: ${name}`)).pipe(Effect.ignore);
383
225
  }
384
226
  });
385
- const decoder = new TextDecoder();
386
- const stdoutFiber = yield* Effect.forkScoped(Effect.async((resume) => {
387
- if (!proc.stdout) {
388
- resume(Effect.void);
389
- return;
390
- }
391
- const reader = proc.stdout.getReader();
392
- let buffer = "";
393
- let active = true;
394
- const pump = () => reader.read().then(({ done, value }) => {
395
- if (!active) return;
396
- if (done) {
397
- if (buffer) Effect.runSync(handleLine(buffer, false));
227
+ yield* Effect.forkScoped(Stream.runForEach((line) => handleLine(line, false))(Stream.splitLines(Stream.decodeText(proc.stdout, "utf-8"))));
228
+ yield* Effect.forkScoped(Stream.runForEach((line) => handleLine(line, true))(Stream.splitLines(Stream.decodeText(proc.stderr, "utf-8"))));
229
+ return {
230
+ name,
231
+ pid,
232
+ kill: Effect.gen(function* () {
233
+ const result = yield* proc.kill("SIGTERM").pipe(Effect.timeout("3 seconds"), Effect.option);
234
+ if (Option.isNone(result)) {
235
+ const pid = Number(proc.pid);
236
+ yield* Effect.try(() => process.kill(-pid, "SIGKILL")).pipe(Effect.ignore);
237
+ yield* Effect.sleep("250 millis");
238
+ }
239
+ }).pipe(Effect.ignore),
240
+ waitForReady: Deferred.await(readyDeferred),
241
+ waitForExit: proc.exitCode
242
+ };
243
+ });
244
+ const spawnRemoteProbe = (pkg, descriptor, callbacks) => Effect.gen(function* () {
245
+ callbacks.onStatus(pkg, "starting");
246
+ const readyDeferred = yield* Deferred.make();
247
+ const statusRef = yield* Ref.make("starting");
248
+ const markReady = Effect.gen(function* () {
249
+ yield* Ref.set(statusRef, "ready");
250
+ yield* Deferred.succeed(readyDeferred, void 0);
251
+ callbacks.onStatus(pkg, "ready", "loaded");
252
+ });
253
+ const markError = Effect.gen(function* () {
254
+ yield* Ref.set(statusRef, "error");
255
+ yield* Deferred.fail(readyDeferred, /* @__PURE__ */ new Error(`Remote ${pkg} unreachable`));
256
+ callbacks.onStatus(pkg, "error", "unreachable");
257
+ });
258
+ const baseUrl = descriptor.url.replace(/\/$/, "");
259
+ const manifestUrl = `${baseUrl}/mf-manifest.json`;
260
+ const entryUrl = `${baseUrl}${descriptor.readinessPath}`;
261
+ const probeUrl = descriptor.readinessPath === "/health" ? `${baseUrl}/health` : manifestUrl;
262
+ yield* Effect.forkScoped(Effect.gen(function* () {
263
+ const deadline = Date.now() + 6e4;
264
+ while (Date.now() < deadline) {
265
+ const status = yield* Ref.get(statusRef);
266
+ if (status === "ready" || status === "error") return;
267
+ if (yield* probeHttpOk(probeUrl, 400)) {
268
+ yield* markReady;
398
269
  return;
399
270
  }
400
- buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
401
- const lines = buffer.split("\n");
402
- buffer = lines.pop() ?? "";
403
- for (const line of lines) Effect.runSync(handleLine(line, false));
404
- return pump();
405
- });
406
- pump().then(() => {
407
- if (active) resume(Effect.void);
408
- });
409
- return Effect.sync(() => {
410
- active = false;
411
- reader.cancel();
412
- });
413
- }));
414
- const stderrFiber = yield* Effect.forkScoped(Effect.async((resume) => {
415
- if (!proc.stderr) {
416
- resume(Effect.void);
417
- return;
418
- }
419
- const reader = proc.stderr.getReader();
420
- let buffer = "";
421
- let active = true;
422
- const pump = () => reader.read().then(({ done, value }) => {
423
- if (!active) return;
424
- if (done) {
425
- if (buffer) Effect.runSync(handleLine(buffer, true));
271
+ if (yield* probeHttpOk(entryUrl, 400)) {
272
+ yield* markReady;
426
273
  return;
427
274
  }
428
- buffer += decoder.decode(value, { stream: true }).replace(/\r\n/g, "\n").replace(/\r/g, "\n");
429
- const lines = buffer.split("\n");
430
- buffer = lines.pop() ?? "";
431
- for (const line of lines) Effect.runSync(handleLine(line, true));
432
- return pump();
433
- });
434
- pump().then(() => {
435
- if (active) resume(Effect.void);
436
- });
437
- return Effect.sync(() => {
438
- active = false;
439
- reader.cancel();
440
- });
275
+ yield* Effect.sleep("500 millis");
276
+ }
277
+ if ((yield* Ref.get(statusRef)) !== "ready") yield* markError;
441
278
  }));
442
279
  return {
443
- name: config.name,
444
- pid: proc.pid,
445
- kill: proc.pid ? killProcessTree(proc.pid) : Effect.gen(function* () {
446
- proc.kill("SIGTERM");
447
- yield* Effect.sleep("100 millis");
448
- try {
449
- proc.kill("SIGKILL");
450
- } catch {}
280
+ name: pkg,
281
+ pid: void 0,
282
+ kill: Effect.gen(function* () {
283
+ yield* Ref.set(statusRef, "error");
284
+ yield* Deferred.fail(readyDeferred, /* @__PURE__ */ new Error("Killed")).pipe(Effect.ignore);
451
285
  }),
452
286
  waitForReady: Deferred.await(readyDeferred),
453
- waitForExit: Effect.gen(function* () {
454
- yield* Fiber.joinAll([stdoutFiber, stderrFiber]);
455
- return yield* Effect.promise(() => proc.exited);
456
- })
287
+ waitForExit: Effect.never
457
288
  };
458
289
  });
459
- const makeDevProcess = (pkg, env, callbacks, portOverride, bosConfig, runtimeConfig, registry) => Effect.gen(function* () {
460
- const config = getProcessConfig(pkg, env, portOverride, bosConfig, runtimeConfig);
461
- if (!config) return yield* Effect.fail(/* @__PURE__ */ new Error(`Unknown package: ${pkg}`));
462
- if (pkg === "host" && runtimeConfig) {
463
- if (env?.HOST_SOURCE === "remote") return yield* spawnRemoteHost(config, callbacks, runtimeConfig);
464
- return yield* spawnDevProcess(config, callbacks, runtimeConfig, registry);
290
+ const makeDevProcess = (pkg, callbacks, portOverride) => Effect.gen(function* () {
291
+ const descriptor = (yield* ServiceDescriptorMap).get(pkg);
292
+ if (!descriptor) {
293
+ callbacks.onStatus(pkg, "ready", "Remote");
294
+ return {
295
+ name: pkg,
296
+ pid: void 0,
297
+ kill: Effect.void,
298
+ waitForReady: Effect.void,
299
+ waitForExit: Effect.never
300
+ };
465
301
  }
466
- return yield* spawnDevProcess(config, callbacks, runtimeConfig, registry);
302
+ if (pkg === "host" && descriptor.source === "remote") return yield* spawnRemoteHost(descriptor, callbacks);
303
+ if (descriptor.source === "remote" || !descriptor.localPath) return yield* spawnRemoteProbe(pkg, descriptor, callbacks);
304
+ return yield* spawnDevProcess(portOverride ? {
305
+ ...descriptor,
306
+ port: portOverride
307
+ } : descriptor, callbacks);
467
308
  });
309
+ function getProcessStates(packages, services, portOverride) {
310
+ return packages.map((pkg) => {
311
+ const descriptor = services.get(pkg);
312
+ return {
313
+ name: pkg,
314
+ status: "pending",
315
+ port: portOverride && pkg === "host" ? portOverride : descriptor?.port ?? descriptor?.defaultPort ?? 0,
316
+ source: descriptor?.source
317
+ };
318
+ });
319
+ }
468
320
 
469
321
  //#endregion
470
- export { getProcessConfig, makeDevProcess, spawnDevProcess, spawnRemoteHost };
322
+ export { getProcessStates, makeDevProcess };
471
323
  //# sourceMappingURL=orchestrator.mjs.map