UNPKG

everything-dev

Version:

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

376 lines (374 loc) • 13.9 kB
import { buildDescription, isAuthMirrorPluginEntry } from "../service-descriptor.mjs"; import { PortAllocator } from "../app.mjs"; import { buildConventionalDatabases, buildConventionalRedis, getSecretGroups, loadPortState, savePortState } from "../cli/infra.mjs"; import { InfraError } from "./types.mjs"; import { shouldPersistPortState } from "./materializer.mjs"; import { ownerOfPort } from "./port-ownership.mjs"; import { resolve } from "node:path"; import { Clock, Effect } from "effect"; import { createHash } from "node:crypto"; //#region src/infra/planner.ts const DEFAULT_HOST_PORT = 3e3; const POSTGRES_USER = "everythingdev"; const POSTGRES_PASSWORD = "everythingdev"; function workspaceKey(configDir) { return createHash("sha256").update(resolve(configDir)).digest("hex").slice(0, 12); } function normalizeCliPorts(input) { return { host: input.port, api: input.apiPort, auth: input.authPort, ui: input.uiPort, pluginsStart: input.pluginPortStart, plugins: input.plugins }; } const PLUGIN_START_OFFSET = 10; const BLOCK_STEP = 100; function allocateServices(cliPorts, plugins, configDir, auth, sources) { return Effect.gen(function* () { const wKey = workspaceKey(configDir); const persisted = loadPortState(configDir).devPorts; const allocator = yield* PortAllocator; const base = cliPorts.host ?? persisted?.host ?? DEFAULT_HOST_PORT; const pluginStartPreferred = cliPorts.pluginsStart ?? persisted?.pluginPortStart ?? base + PLUGIN_START_OFFSET; const pluginStartPinned = cliPorts.pluginsStart !== void 0 || persisted?.pluginPortStart !== void 0; const entries = []; const addEntry = (key, flag, persistedValue, derived) => { if (flag !== void 0) entries.push({ key, preferred: flag, pinned: true }); else if (persistedValue !== void 0) entries.push({ key, preferred: persistedValue, pinned: true }); else entries.push({ key, preferred: derived, pinned: false }); }; addEntry("host", cliPorts.host, void 0, base); if (sources?.api !== "remote") addEntry("api", cliPorts.api, persisted?.api, base + 1); if (sources?.auth !== "remote") addEntry("auth", cliPorts.auth, persisted?.auth, base + 2); if (sources?.ui !== "remote") addEntry("ui", cliPorts.ui, persisted?.ui, base + 3); const pluginKeys = Object.keys(plugins).sort(); const isAuthMirrorFor = (pluginId, pluginCfg) => pluginCfg !== void 0 && isAuthMirrorPluginEntry(auth, pluginId, pluginCfg); let pluginSlot = 0; for (const pluginId of pluginKeys) { const pluginCfg = plugins[pluginId]; const pluginIsLocal = pluginCfg?.source === "local"; const slotBase = pluginStartPinned ? pluginStartPreferred : base + PLUGIN_START_OFFSET; if (!isAuthMirrorFor(pluginId, pluginCfg) && pluginIsLocal) { addEntry(`plugin:${pluginId}`, cliPorts.plugins?.[pluginId]?.api, void 0, slotBase + pluginSlot); pluginSlot += 1; } if (pluginCfg?.source === "local" && pluginCfg?.localPath && pluginCfg?.ui?.source === "local") { addEntry(`plugin-ui:${pluginId}`, cliPorts.plugins?.[pluginId]?.ui, void 0, slotBase + pluginSlot); pluginSlot += 1; } } const allocation = yield* allocator.acquireBlock({ base, step: BLOCK_STEP, entries }); if (allocation.base !== base) { yield* Effect.logError(`[Dev] Preferred port block starting at ${base} is occupied — using ${allocation.base} (this run only; restarts will retry ${base})`); for (const conflict of allocation.conflicts.slice(0, 6)) if (conflict.claimed) yield* Effect.logError(`[Dev] port ${conflict.port} (${conflict.key}) — live bos session (registry claim)`); else { const owner = yield* ownerOfPort(conflict.port); yield* Effect.logError(`[Dev] port ${conflict.port} (${conflict.key}) — ${owner ? `pid ${owner.pid} (${owner.command})` : "foreign holder (lsof unavailable)"}`); } } const pluginApiPorts = {}; const pluginUiPorts = {}; for (const pluginId of pluginKeys) { const api = allocation.ports[`plugin:${pluginId}`]; if (api !== void 0) pluginApiPorts[pluginId] = api; const ui = allocation.ports[`plugin-ui:${pluginId}`]; if (ui !== void 0) pluginUiPorts[pluginId] = ui; } const resolved = { host: allocation.ports.host, api: allocation.ports.api, auth: allocation.ports.auth, ui: allocation.ports.ui, plugins: Object.fromEntries(pluginKeys.map((k) => [k, { api: pluginApiPorts[k], ui: pluginUiPorts[k] }])) }; const devPortsState = { host: cliPorts.host, api: cliPorts.api, auth: cliPorts.auth, ui: cliPorts.ui, pluginPortStart: cliPorts.pluginsStart }; const claimPorts = {}; for (const [key, port] of Object.entries(allocation.ports)) claimPorts[key] = port; return { ports: resolved, claims: [{ resourceKey: `workspace:${wKey}`, pid: process.pid, configDir, ports: claimPorts, startedAt: yield* Clock.currentTimeMillis }], devPortsState }; }).pipe(Effect.mapError((portErr) => new InfraError({ phase: "allocate-services", message: `Port allocation failed: ${String(portErr)}`, cause: portErr }))); } function allocateDatabases(runtimeConfig) { return Effect.gen(function* () { const allSecrets = getSecretGroups(runtimeConfig).flatMap((group) => group.secrets); const allocator = yield* PortAllocator; const conventionalDatabases = buildConventionalDatabases(allSecrets); const conventionalRedis = buildConventionalRedis(allSecrets); const dbs = []; const allocatedByDesired = /* @__PURE__ */ new Map(); for (const db of conventionalDatabases) { let port; if (allocatedByDesired.has(db.port)) port = allocatedByDesired.get(db.port); else { port = yield* allocator.pickAvailable(db.port); allocatedByDesired.set(db.port, port); } dbs.push({ secret: db.secret, slug: db.slug, port, dbName: db.databaseName, url: `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${port}/${db.databaseName}` }); } const redisPlans = []; for (const r of conventionalRedis) { const port = yield* allocator.pickAvailable(r.port); redisPlans.push({ secret: r.secret, slug: r.slug, port, url: `redis://localhost:${port}` }); } return { dbs, redisPlans }; }).pipe(Effect.mapError((portErr) => new InfraError({ phase: "allocate-databases", message: `Database port allocation failed: ${String(portErr)}`, cause: portErr }))); } function buildServiceDescriptors(runtimeConfig, resolvedPorts) { const descriptors = []; if (runtimeConfig.host) { const isLocal = runtimeConfig.host.source === "local"; descriptors.push({ key: "host", source: runtimeConfig.host.source, url: isLocal ? `http://localhost:${resolvedPorts.host}` : runtimeConfig.host.url, port: isLocal ? resolvedPorts.host : void 0, localPath: isLocal ? runtimeConfig.host.localPath : void 0 }); } if (runtimeConfig.api) { const isLocal = runtimeConfig.api.source === "local"; descriptors.push({ key: "api", source: runtimeConfig.api.source, url: isLocal ? `http://localhost:${resolvedPorts.api}` : runtimeConfig.api.url, port: isLocal ? resolvedPorts.api : void 0, localPath: isLocal ? runtimeConfig.api.localPath : void 0 }); } if (runtimeConfig.auth) { const isLocal = runtimeConfig.auth.source === "local"; descriptors.push({ key: "auth", source: runtimeConfig.auth.source, url: isLocal ? `http://localhost:${resolvedPorts.auth}` : runtimeConfig.auth.url, port: isLocal ? resolvedPorts.auth : void 0, localPath: isLocal ? runtimeConfig.auth.localPath : void 0 }); } if (runtimeConfig.ui) { const isLocal = runtimeConfig.ui.source === "local"; descriptors.push({ key: "ui", source: runtimeConfig.ui.source, url: isLocal ? `http://localhost:${resolvedPorts.ui}` : runtimeConfig.ui.url, port: isLocal ? resolvedPorts.ui : void 0, localPath: isLocal ? runtimeConfig.ui.localPath : void 0 }); } if (runtimeConfig.plugins) for (const [pluginId, pluginCfg] of Object.entries(runtimeConfig.plugins)) { const pluginIsLocal = pluginCfg.source === "local"; const p = resolvedPorts.plugins[pluginId]; const isAuthMirror = isAuthMirrorPluginEntry(runtimeConfig.auth, pluginId, pluginCfg); if (pluginIsLocal && p?.api && !isAuthMirror) descriptors.push({ key: `plugin:${pluginId}`, source: "local", url: `http://localhost:${p.api}`, port: p.api, localPath: pluginCfg.localPath }); if (!pluginIsLocal && pluginCfg.url && !isAuthMirror) descriptors.push({ key: `plugin:${pluginId}`, source: "remote", url: pluginCfg.url, port: void 0, localPath: void 0 }); if (pluginIsLocal && p?.ui && pluginCfg.ui?.source === "local") descriptors.push({ key: `plugin-ui:${pluginId}`, source: "local", url: `http://localhost:${p.ui}`, port: p.ui, localPath: pluginCfg.ui?.localPath }); } return descriptors; } function buildLaunchSpec(runtimeConfig, resolvedPorts) { const hostPort = runtimeConfig.host?.source === "local" ? resolvedPorts.host : runtimeConfig.host?.port; const corsOrigin = hostPort ? `http://localhost:${hostPort}` : `http://localhost:3000`; return { port: resolvedPorts.host, hostUrl: runtimeConfig.host?.url, corsOrigin, env: { ...resolvedPorts.host ? { PORT: String(resolvedPorts.host) } : {}, ...resolvedPorts.api ? { API_PORT: String(resolvedPorts.api) } : {}, ...resolvedPorts.ui ? { UI_PORT: String(resolvedPorts.ui) } : {}, ...resolvedPorts.auth ? { AUTH_PORT: String(resolvedPorts.auth) } : {} }, runtimeConfig }; } function buildEnvGenerated(resolvedPorts, dbs, redisPlans) { const env = {}; if (resolvedPorts.host) { env.CORS_ORIGIN = `http://localhost:${resolvedPorts.host}`; env.BASE_URL = `http://localhost:${resolvedPorts.host}`; } for (const db of dbs) env[db.secret] = db.url; for (const r of redisPlans) env[r.secret] = r.url; return env; } function planInfra(input) { return Effect.gen(function* () { const cliPorts = normalizeCliPorts(input.cli); const wKey = workspaceKey(input.configDir); const { ports: svcPorts, claims, devPortsState } = yield* allocateServices(cliPorts, input.bosConfig.plugins ?? {}, input.configDir, input.bosConfig.auth, { host: input.bosConfig.host?.source, api: input.bosConfig.api?.source, auth: input.bosConfig.auth?.source, ui: input.bosConfig.ui?.source }); const { dbs, redisPlans } = yield* allocateDatabases(input.bosConfig); const resolvedPorts = { ...svcPorts }; const previousDevPorts = loadPortState(input.configDir).devPorts ?? {}; const devPorts = { host: devPortsState.host ?? previousDevPorts.host, api: devPortsState.api ?? previousDevPorts.api, auth: devPortsState.auth ?? previousDevPorts.auth, ui: devPortsState.ui ?? previousDevPorts.ui, pluginPortStart: devPortsState.pluginPortStart ?? previousDevPorts.pluginPortStart }; if (shouldPersistPortState()) savePortState(input.configDir, { devPorts }); const hostIsLocal = input.bosConfig.host?.source === "local"; const apiIsLocal = input.bosConfig.api?.source === "local"; const uiIsLocal = input.bosConfig.ui?.source === "local"; const authIsLocal = input.bosConfig.auth?.source === "local"; const assignedRuntimeConfig = { ...input.bosConfig, host: resolvedPorts.host ? { ...input.bosConfig.host, port: resolvedPorts.host, url: `http://localhost:${resolvedPorts.host}`, remoteUrl: !hostIsLocal ? input.bosConfig.host.remoteUrl ?? input.bosConfig.host.url : void 0 } : input.bosConfig.host, api: apiIsLocal && resolvedPorts.api ? { ...input.bosConfig.api, port: resolvedPorts.api, url: `http://localhost:${resolvedPorts.api}` } : input.bosConfig.api, ui: uiIsLocal && resolvedPorts.ui ? { ...input.bosConfig.ui, port: resolvedPorts.ui, url: `http://localhost:${resolvedPorts.ui}`, ssrUrl: void 0 } : input.bosConfig.ui, auth: authIsLocal && resolvedPorts.auth && input.bosConfig.auth ? { ...input.bosConfig.auth, port: resolvedPorts.auth, url: `http://localhost:${resolvedPorts.auth}` } : input.bosConfig.auth, plugins: input.bosConfig.plugins ? Object.fromEntries(Object.entries(input.bosConfig.plugins).map(([id, p]) => { const isAuthMirror = isAuthMirrorPluginEntry(input.bosConfig.auth, id, p); const pluginPort = resolvedPorts.plugins[id]; const patchedUi = (() => { if (p.ui?.source !== "local" || !p.ui?.localPath || !pluginPort?.ui) return p.ui; return { ...p.ui, port: pluginPort.ui, url: `http://localhost:${pluginPort.ui}`, ssrUrl: void 0 }; })(); if (isAuthMirror) return [id, { ...p, ui: patchedUi }]; if (p.source === "local" && pluginPort?.api) return [id, { ...p, port: pluginPort.api, url: `http://localhost:${pluginPort.api}`, ui: patchedUi }]; return [id, p]; })) : void 0 }; const serviceDescriptors = buildServiceDescriptors(assignedRuntimeConfig, resolvedPorts); const launch = buildLaunchSpec(input.bosConfig, resolvedPorts); const envGenerated = buildEnvGenerated(resolvedPorts, dbs, redisPlans); const packages = serviceDescriptors.map((d) => d.key); const descriptionMap = new Map(serviceDescriptors.map((d) => [d.key, d])); const description = buildDescription(descriptionMap); const orchestrator = { packages, env: {}, description, port: resolvedPorts.host, interactive: input.cli.interactive }; return { workspaceKey: wKey, cliPorts, resolvedPorts, runtimeConfig: assignedRuntimeConfig, launch, description, serviceDescriptors: new Map(serviceDescriptors.map((d) => [d.key, d])), envGenerated, claims, orchestrator }; }); } //#endregion export { buildEnvGenerated, buildLaunchSpec, buildServiceDescriptors, planInfra, workspaceKey }; //# sourceMappingURL=planner.mjs.map