everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
318 lines (316 loc) • 13.1 kB
JavaScript
const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
const require_mf = require('./mf.cjs');
const require_service_descriptor = require('./service-descriptor.cjs');
let effect = require("effect");
let _effect_platform = require("@effect/platform");
//#region src/orchestrator.ts
process.on("unhandledRejection", (reason) => {
console.error("[Orchestrator] Unhandled rejection:", reason);
});
process.on("uncaughtException", (err) => {
console.error("[Orchestrator] Uncaught exception:", err);
});
const stripAnsi = (input) => {
const ESC = String.fromCharCode(27);
const BEL = String.fromCharCode(7);
return input.replace(new RegExp(`${ESC}\\][^${BEL}]*${BEL}`, "g"), "").replace(new RegExp(`${ESC}\\[[0-?]*[ -/]*[@-~]`, "g"), "");
};
const probeHttpOk = (url, timeoutMs = 400) => effect.Effect.tryPromise({
try: async () => {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
return (await fetch(url, { signal: controller.signal })).ok;
} catch {
return false;
} finally {
clearTimeout(timer);
}
},
catch: () => false
});
const detectStatus = (line, descriptor) => {
const cleanLine = stripAnsi(line);
const errorPatterns = descriptor.errorPatterns ?? [];
const readyPatterns = descriptor.readyPatterns ?? [];
for (const pattern of errorPatterns) if (pattern.test(cleanLine)) return {
status: "error",
isError: true
};
for (const pattern of readyPatterns) if (pattern.test(cleanLine)) return {
status: "ready",
isError: false
};
return null;
};
const patchConsole = (name, callbacks) => {
const originalLog = console.log;
const originalError = console.error;
const originalWarn = console.warn;
const originalInfo = console.info;
const formatArgs = (args, isError = false) => {
return args.map((arg) => {
if (arg instanceof Error) {
const parts = [`${arg.name}: ${arg.message}`];
if (arg.cause instanceof Error) parts.push(`(cause: ${arg.cause.name}: ${arg.cause.message})`);
else if (arg.cause) parts.push(`(cause: ${String(arg.cause)})`);
if (isError && arg.stack) parts.push(arg.stack);
return parts.join("\n");
}
return typeof arg === "object" ? JSON.stringify(arg, null, 2) : String(arg);
}).join(" ");
};
console.log = (...args) => {
callbacks.onLog(name, formatArgs(args), false);
};
console.error = (...args) => {
callbacks.onLog(name, formatArgs(args, true), true);
};
console.warn = (...args) => {
callbacks.onLog(name, formatArgs(args), false);
};
console.info = (...args) => {
callbacks.onLog(name, formatArgs(args), false);
};
return () => {
console.log = originalLog;
console.error = originalError;
console.warn = originalWarn;
console.info = originalInfo;
};
};
const spawnRemoteHost = (descriptor, callbacks) => effect.Effect.gen(function* () {
const runtimeConfig = yield* require_service_descriptor.DevRuntimeConfig;
const remoteUrl = descriptor.remoteUrl;
if (!remoteUrl) return yield* effect.Effect.fail(/* @__PURE__ */ new Error("remoteUrl not provided on host descriptor"));
callbacks.onStatus(descriptor.key, "starting");
callbacks.onLog(descriptor.key, `Remote: ${remoteUrl}`);
const restoreConsole = patchConsole(descriptor.key, callbacks);
callbacks.onLog(descriptor.key, "Loading Module Federation runtime...");
const mfRuntime = yield* effect.Effect.tryPromise({
try: () => import("@module-federation/enhanced/runtime"),
catch: (e) => /* @__PURE__ */ new Error(`Failed to load MF runtime: ${e}`)
});
const mfCore = yield* effect.Effect.tryPromise({
try: () => import("@module-federation/runtime-core"),
catch: (e) => /* @__PURE__ */ new Error(`Failed to load MF core: ${e}`)
});
let mf = mfRuntime.getInstance();
if (!mf) {
mf = mfRuntime.createInstance({
name: "cli-host",
remotes: []
});
mfCore.setGlobalFederationInstance(mf);
}
require_mf.patchManifestFetchForSsrPublicPath(mf);
const baseUrl = remoteUrl.replace(/\/remoteEntry\.js$/, "").replace(/\/mf-manifest\.json$/, "").replace(/\/$/, "");
const remoteEntryUrl = `${baseUrl}/remoteEntry.js`;
const manifestUrl = `${baseUrl}/mf-manifest.json`;
const entryUrl = yield* effect.Effect.tryPromise({
try: async () => {
try {
const res = await fetch(manifestUrl);
if (!res.ok) return remoteEntryUrl;
const json = await res.json();
if (json && typeof json === "object" && "metaData" in json && "exposes" in json && "shared" in json) return manifestUrl;
} catch {}
return remoteEntryUrl;
},
catch: () => remoteEntryUrl
});
mf.registerRemotes([{
name: "host",
entry: entryUrl
}]);
callbacks.onLog(descriptor.key, `Loading host from ${entryUrl}...`);
const hostModule = yield* effect.Effect.tryPromise({
try: () => mf.loadRemote("host/Server"),
catch: (e) => /* @__PURE__ */ new Error(`Failed to load host module: ${e}`)
});
if (!hostModule?.runServer) return yield* effect.Effect.fail(/* @__PURE__ */ new Error("Host module does not export runServer function"));
callbacks.onLog(descriptor.key, "Starting server...");
const serverHandle = hostModule.runServer({ config: runtimeConfig });
yield* effect.Effect.tryPromise({
try: () => serverHandle.ready,
catch: (e) => /* @__PURE__ */ new Error(`Server failed to start: ${e}`)
});
callbacks.onStatus(descriptor.key, "ready");
return {
name: descriptor.key,
pid: process.pid,
kill: effect.Effect.gen(function* () {
callbacks.onLog(descriptor.key, "Shutting down remote host...");
restoreConsole();
yield* effect.Effect.tryPromise({
try: () => serverHandle.shutdown(),
catch: () => {}
}).pipe(effect.Effect.ignore);
}),
waitForReady: effect.Effect.succeed(void 0),
waitForExit: effect.Effect.never
};
});
const spawnDevProcess = (descriptor, callbacks) => effect.Effect.gen(function* () {
const runtimeConfig = yield* require_service_descriptor.DevRuntimeConfig;
if (!descriptor.localPath) return yield* effect.Effect.fail(/* @__PURE__ */ new Error(`No localPath for local service: ${descriptor.key}`));
const fullCwd = descriptor.localPath;
const command = descriptor.command ?? "bun";
const args = descriptor.args ?? ["run", "dev"];
const port = descriptor.port ?? descriptor.defaultPort;
const name = descriptor.key;
const readyDeferred = yield* effect.Deferred.make();
const statusRef = yield* effect.Ref.make("starting");
callbacks.onStatus(name, "starting");
const envVars = {
...process.env,
FORCE_COLOR: "1",
...port > 0 ? { PORT: String(port) } : {}
};
if (name === "host") envVars.BOS_RUNTIME_CONFIG = JSON.stringify(runtimeConfig);
const cmd = _effect_platform.Command.make(command, ...args).pipe(_effect_platform.Command.workingDirectory(fullCwd), _effect_platform.Command.env(envVars));
const proc = yield* _effect_platform.Command.start(cmd);
const markReady = effect.Effect.gen(function* () {
const currentStatus = yield* effect.Ref.get(statusRef);
if (currentStatus === "ready" || currentStatus === "error") return;
yield* effect.Ref.set(statusRef, "ready");
callbacks.onStatus(name, "ready");
yield* effect.Deferred.succeed(readyDeferred, void 0).pipe(effect.Effect.ignore);
});
if (port > 0) {
const url = `http://127.0.0.1:${port}${descriptor.readinessPath}`;
yield* effect.Effect.forkScoped(effect.Effect.gen(function* () {
const deadline = Date.now() + 9e4;
while (Date.now() < deadline) {
const status = yield* effect.Ref.get(statusRef);
if (status === "ready" || status === "error") return;
if (yield* probeHttpOk(url)) {
yield* markReady;
return;
}
yield* effect.Effect.sleep("200 millis");
}
}));
}
const pid = Number(proc.pid);
yield* effect.Effect.forkScoped(effect.Effect.gen(function* () {
const exitCode = yield* proc.exitCode;
const currentStatus = yield* effect.Ref.get(statusRef);
if (currentStatus === "ready" || currentStatus === "error") return;
callbacks.onLog(name, `Process exited before ready (exit code: ${exitCode})`, true);
yield* effect.Ref.set(statusRef, "error");
callbacks.onStatus(name, "error");
yield* effect.Deferred.fail(readyDeferred, /* @__PURE__ */ new Error(`Process exited before ready: ${name}`)).pipe(effect.Effect.ignore);
}));
const handleLine = (line, isStderr) => effect.Effect.gen(function* () {
if (!line.trim()) return;
const cleanLine = stripAnsi(line);
const looksLikeError = isStderr && /^(error|fail|fatal|exception|unhandled|reject)/i.test(cleanLine) && !/^\$/.test(cleanLine);
callbacks.onLog(name, line, looksLikeError);
const currentStatus = yield* effect.Ref.get(statusRef);
if (currentStatus === "ready" || currentStatus === "error") return;
const detected = detectStatus(line, descriptor);
if (detected) {
yield* effect.Ref.set(statusRef, detected.status);
callbacks.onStatus(name, detected.status);
if (detected.status === "ready" || detected.status === "error") if (detected.status === "ready") yield* effect.Deferred.succeed(readyDeferred, void 0).pipe(effect.Effect.ignore);
else yield* effect.Deferred.fail(readyDeferred, /* @__PURE__ */ new Error(`Process failed: ${name}`)).pipe(effect.Effect.ignore);
}
});
yield* effect.Effect.forkScoped(effect.Stream.runForEach((line) => handleLine(line, false))(effect.Stream.splitLines(effect.Stream.decodeText(proc.stdout, "utf-8"))));
yield* effect.Effect.forkScoped(effect.Stream.runForEach((line) => handleLine(line, true))(effect.Stream.splitLines(effect.Stream.decodeText(proc.stderr, "utf-8"))));
return {
name,
pid,
kill: effect.Effect.gen(function* () {
const result = yield* proc.kill("SIGTERM").pipe(effect.Effect.timeout("3 seconds"), effect.Effect.option);
if (effect.Option.isNone(result)) {
const pid = Number(proc.pid);
yield* effect.Effect.try(() => process.kill(-pid, "SIGKILL")).pipe(effect.Effect.ignore);
yield* effect.Effect.sleep("250 millis");
}
}).pipe(effect.Effect.ignore),
waitForReady: effect.Deferred.await(readyDeferred),
waitForExit: proc.exitCode
};
});
const spawnRemoteProbe = (pkg, descriptor, callbacks) => effect.Effect.gen(function* () {
callbacks.onStatus(pkg, "starting");
const readyDeferred = yield* effect.Deferred.make();
const statusRef = yield* effect.Ref.make("starting");
const markReady = effect.Effect.gen(function* () {
yield* effect.Ref.set(statusRef, "ready");
yield* effect.Deferred.succeed(readyDeferred, void 0);
callbacks.onStatus(pkg, "ready", "loaded");
});
const markError = effect.Effect.gen(function* () {
yield* effect.Ref.set(statusRef, "error");
yield* effect.Deferred.fail(readyDeferred, /* @__PURE__ */ new Error(`Remote ${pkg} unreachable`));
callbacks.onStatus(pkg, "error", "unreachable");
});
const baseUrl = descriptor.url.replace(/\/$/, "");
const manifestUrl = `${baseUrl}/mf-manifest.json`;
const entryUrl = `${baseUrl}${descriptor.readinessPath}`;
const probeUrl = descriptor.readinessPath === "/health" ? `${baseUrl}/health` : manifestUrl;
yield* effect.Effect.forkScoped(effect.Effect.gen(function* () {
const deadline = Date.now() + 6e4;
while (Date.now() < deadline) {
const status = yield* effect.Ref.get(statusRef);
if (status === "ready" || status === "error") return;
if (yield* probeHttpOk(probeUrl, 400)) {
yield* markReady;
return;
}
if (yield* probeHttpOk(entryUrl, 400)) {
yield* markReady;
return;
}
yield* effect.Effect.sleep("500 millis");
}
if ((yield* effect.Ref.get(statusRef)) !== "ready") yield* markError;
}));
return {
name: pkg,
pid: void 0,
kill: effect.Effect.gen(function* () {
yield* effect.Ref.set(statusRef, "error");
yield* effect.Deferred.fail(readyDeferred, /* @__PURE__ */ new Error("Killed")).pipe(effect.Effect.ignore);
}),
waitForReady: effect.Deferred.await(readyDeferred),
waitForExit: effect.Effect.never
};
});
const makeDevProcess = (pkg, callbacks, portOverride) => effect.Effect.gen(function* () {
const descriptor = (yield* require_service_descriptor.ServiceDescriptorMap).get(pkg);
if (!descriptor) {
callbacks.onStatus(pkg, "ready", "Remote");
return {
name: pkg,
pid: void 0,
kill: effect.Effect.void,
waitForReady: effect.Effect.void,
waitForExit: effect.Effect.never
};
}
if (pkg === "host" && descriptor.source === "remote") return yield* spawnRemoteHost(descriptor, callbacks);
if (descriptor.source === "remote" || !descriptor.localPath) return yield* spawnRemoteProbe(pkg, descriptor, callbacks);
return yield* spawnDevProcess(portOverride ? {
...descriptor,
port: portOverride
} : descriptor, callbacks);
});
function getProcessStates(packages, services, portOverride) {
return packages.map((pkg) => {
const descriptor = services.get(pkg);
return {
name: pkg,
status: "pending",
port: portOverride && pkg === "host" ? portOverride : descriptor?.port ?? descriptor?.defaultPort ?? 0,
source: descriptor?.source
};
});
}
//#endregion
exports.getProcessStates = getProcessStates;
exports.makeDevProcess = makeDevProcess;
//# sourceMappingURL=orchestrator.cjs.map