UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

110 lines (108 loc) 4.76 kB
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