everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
110 lines (108 loc) • 4.76 kB
JavaScript
import { buildRuntimeConfig as buildRuntimeConfig$1, getProjectRoot, resolveLocalDevelopmentPath } from "./config.mjs";
import { claimedPorts } from "./process-registry.mjs";
import { existsSync } from "node:fs";
import { join } from "node:path";
import { Context, Data, Effect, Layer } from "effect";
import { createServer } from "node:net";
//#region src/app.ts
const PROBE_TIMEOUT_MS = 250;
const MAX_PORT_SCAN_STEPS = 1e3;
const PARALLEL_PROBE_WINDOW = 8;
var PortAllocationError = class extends Data.TaggedError("PortAllocationError") {};
var PortAllocator = class extends Context.Tag("PortAllocator")() {};
function detectLocalPackages(bosConfig, runtimeConfig) {
const packages = [];
const configDir = getProjectRoot();
const uiLocalPath = runtimeConfig?.ui.localPath ?? resolveLocalDevelopmentPath(bosConfig?.app.ui.development, configDir);
if (uiLocalPath && existsSync(join(uiLocalPath, "package.json"))) packages.push("ui");
const apiLocalPath = runtimeConfig?.api.localPath ?? resolveLocalDevelopmentPath(bosConfig?.app.api.development, configDir);
if (apiLocalPath && existsSync(join(apiLocalPath, "package.json"))) packages.push("api");
const hostLocalPath = runtimeConfig?.host?.localPath ?? resolveLocalDevelopmentPath(bosConfig?.app.host.development, configDir);
if (hostLocalPath && existsSync(join(hostLocalPath, "package.json"))) packages.push("host");
else if (existsSync(join(configDir, "host", "package.json"))) packages.push("host");
for (const [pluginId, pluginConfig] of Object.entries(runtimeConfig?.plugins ?? {})) {
if (pluginConfig.localPath && existsSync(join(pluginConfig.localPath, "package.json"))) packages.push(`plugin:${pluginId}`);
if (pluginConfig.ui?.localPath && existsSync(join(pluginConfig.ui.localPath, "package.json"))) packages.push(`plugin-ui:${pluginId}`);
}
const authLocalPath = runtimeConfig?.auth?.localPath ?? resolveLocalDevelopmentPath(bosConfig?.app.auth?.development, configDir);
if (authLocalPath && existsSync(join(authLocalPath, "package.json"))) packages.push("auth");
return packages;
}
function buildRuntimeConfig(bosConfig, options) {
return buildRuntimeConfig$1(bosConfig, getProjectRoot(), options.env ?? "development", {
hostSource: options.hostSource,
uiSource: options.uiSource,
apiSource: options.apiSource,
authSource: options.authSource,
proxy: options.proxy,
plugins: options.plugins
});
}
function probePortBindable(port) {
return Effect.async((resume) => {
const server = createServer();
server.once("listening", () => {
server.close(() => {
resume(Effect.succeed(true));
});
});
server.once("error", () => {
server.removeAllListeners();
resume(Effect.succeed(false));
});
server.listen(port, "127.0.0.1");
const timer = setTimeout(() => {
server.removeAllListeners();
try {
server.close();
} catch {}
resume(Effect.succeed(false));
}, PROBE_TIMEOUT_MS);
server.once("listening", () => clearTimeout(timer));
server.once("error", () => clearTimeout(timer));
});
}
function pickAvailablePort(preferred, usedPorts, budget) {
return Effect.gen(function* () {
const within = (candidate) => !budget || candidate >= budget.min && candidate <= budget.max;
let port = preferred;
if (!within(port)) port = budget ? budget.min : port;
const ceiling = budget ? budget.max + 1 : Number.MAX_SAFE_INTEGER;
let steps = 0;
const fail = () => Effect.fail(new PortAllocationError({
preferred,
budget,
cause: budget ? `No free port in budget [${budget.min}, ${budget.max}] starting from ${preferred}` : `No free port found starting from ${preferred} within ${MAX_PORT_SCAN_STEPS} steps`
}));
while (true) {
if (port >= ceiling || steps > MAX_PORT_SCAN_STEPS) yield* fail();
const candidates = [];
for (let i = 0; i < PARALLEL_PROBE_WINDOW && port + i < ceiling; i++) {
const candidate = port + i;
if (!usedPorts.has(candidate)) candidates.push(candidate);
}
if (candidates.length === 0) {
port += PARALLEL_PROBE_WINDOW;
steps += PARALLEL_PROBE_WINDOW;
continue;
}
const firstFree = (yield* Effect.forEach(candidates, (c) => probePortBindable(c).pipe(Effect.map((free) => ({
port: c,
free
}))), { concurrency: "unbounded" })).find((r) => r.free);
if (firstFree) {
usedPorts.add(firstFree.port);
return firstFree.port;
}
port += PARALLEL_PROBE_WINDOW;
steps += PARALLEL_PROBE_WINDOW;
}
});
}
const PortAllocatorLive = Layer.sync(PortAllocator, () => {
const usedPorts = claimedPorts();
return { pickAvailable: (preferred, budget) => pickAvailablePort(preferred, usedPorts, budget) };
});
//#endregion
export { PortAllocator, PortAllocatorLive, buildRuntimeConfig, detectLocalPackages };
//# sourceMappingURL=app.mjs.map