everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
344 lines (342 loc) • 12.2 kB
JavaScript
const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
const require_app = require('../app.cjs');
const require_infra = require('../cli/infra.cjs');
const require_service_descriptor = require('../service-descriptor.cjs');
const require_types = require('./types.cjs');
let node_path = require("node:path");
let effect = require("effect");
let node_crypto = require("node:crypto");
//#region src/infra/planner.ts
const DEFAULT_HOST_PORT = 3e3;
const DEFAULT_API_PORT = 3001;
const DEFAULT_AUTH_PORT = 3002;
const DEFAULT_UI_PORT = 3003;
const DEFAULT_PLUGIN_PORT_START = 3010;
const POSTGRES_USER = "everythingdev";
const POSTGRES_PASSWORD = "everythingdev";
function workspaceKey(configDir) {
return (0, node_crypto.createHash)("sha256").update((0, node_path.resolve)(configDir)).digest("hex").slice(0, 12);
}
function normalizeCliPorts(input) {
return {
host: input.port,
api: input.apiPort,
auth: input.authPort,
ui: input.uiPort,
uiSsr: void 0,
pluginsStart: input.pluginPortStart,
plugins: input.plugins
};
}
function allocateServices(cliPorts, plugins, configDir) {
return effect.Effect.gen(function* () {
const wKey = workspaceKey(configDir);
const persisted = require_infra.loadPortState(configDir).devPorts;
const allocator = yield* require_app.PortAllocator;
const hostPort = yield* allocator.pickAvailable(cliPorts.host ?? persisted?.host ?? DEFAULT_HOST_PORT);
const apiPort = yield* allocator.pickAvailable(cliPorts.api ?? persisted?.api ?? DEFAULT_API_PORT);
const authPort = yield* allocator.pickAvailable(cliPorts.auth ?? persisted?.auth ?? DEFAULT_AUTH_PORT);
const uiPort = yield* allocator.pickAvailable(cliPorts.ui ?? persisted?.ui ?? DEFAULT_UI_PORT);
const uiSsrPort = yield* allocator.pickAvailable(cliPorts.uiSsr ?? uiPort + 1);
const pluginApiPorts = {};
const pluginUiPorts = {};
const pluginKeys = Object.keys(plugins).sort();
const pluginStart = cliPorts.pluginsStart ?? persisted?.pluginPortStart ?? DEFAULT_PLUGIN_PORT_START;
let nextPluginPort = pluginStart;
for (const pluginId of pluginKeys) {
const pluginCfg = plugins[pluginId];
const preferred = cliPorts.plugins?.[pluginId]?.api ?? nextPluginPort;
const pluginPort = yield* allocator.pickAvailable(preferred);
pluginApiPorts[pluginId] = pluginPort;
nextPluginPort = pluginPort + 1;
if (pluginCfg?.source === "local" && pluginCfg?.localPath && pluginCfg?.ui?.source === "local") {
const uiPreferred = cliPorts.plugins?.[pluginId]?.ui ?? nextPluginPort;
const pluginUiPort = yield* allocator.pickAvailable(uiPreferred);
pluginUiPorts[pluginId] = pluginUiPort;
nextPluginPort = pluginUiPort + 1;
}
}
const resolved = {
host: hostPort,
api: apiPort,
auth: authPort,
ui: uiPort,
uiSsr: uiSsrPort,
plugins: Object.fromEntries(Object.entries(pluginApiPorts).map(([k, v]) => [k, {
api: v,
ui: pluginUiPorts[k]
}])),
postgres: {},
redis: {}
};
const devPortsState = {
host: hostPort,
api: apiPort,
auth: authPort,
ui: uiPort,
pluginPortStart: pluginStart
};
const claimPorts = {
host: hostPort,
api: apiPort,
auth: authPort,
ui: uiPort,
uiSsr: uiSsrPort
};
for (const [id, port] of Object.entries(pluginApiPorts)) claimPorts[`plugin:${id}`] = port;
for (const [id, port] of Object.entries(pluginUiPorts)) claimPorts[`plugin-ui:${id}`] = port;
return {
ports: resolved,
claims: [{
resourceKey: `workspace:${wKey}`,
pid: process.pid,
configDir,
ports: claimPorts,
startedAt: Date.now()
}],
devPortsState
};
}).pipe(effect.Effect.mapError((portErr) => new require_types.InfraError({
phase: "allocate-services",
message: `Port allocation failed: ${String(portErr)}`,
cause: portErr
})));
}
function allocateDatabases(runtimeConfig, configDir) {
return effect.Effect.gen(function* () {
const persisted = require_infra.loadPortState(configDir);
const allSecrets = require_infra.getSecretGroups(runtimeConfig).flatMap((group) => group.secrets);
const originMap = require_infra.buildOriginMap(configDir, runtimeConfig);
const allocator = yield* require_app.PortAllocator;
const infraDatabases = yield* effect.Effect.sync(() => require_infra.buildDatabaseConfigs(allSecrets, originMap, { ...persisted.postgresPorts }));
const infraRedis = yield* effect.Effect.sync(() => require_infra.buildRedisConfigs(allSecrets, originMap, { ...persisted.redisPorts }));
const postgres = {};
const dbs = [];
for (const db of infraDatabases) {
const port = yield* allocator.pickAvailable(db.port);
postgres[db.slug] = port;
dbs.push({
secret: db.secret,
slug: db.slug,
port,
dbName: db.databaseName,
containerName: db.containerName,
volumeName: db.volumeName,
url: `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${port}/${db.slug}_db`
});
}
const redis = {};
const redisPlans = [];
for (const r of infraRedis) {
const port = yield* allocator.pickAvailable(r.port);
redis[r.slug] = port;
redisPlans.push({
secret: r.secret,
slug: r.slug,
port,
containerName: r.containerName,
volumeName: r.volumeName,
url: `redis://localhost:${port}`
});
}
return {
postgres,
redis,
dbs,
redisPlans
};
}).pipe(effect.Effect.mapError((portErr) => new require_types.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 (isLocal && resolvedPorts.uiSsr) descriptors.push({
key: "ui-ssr",
source: "local",
url: `http://localhost:${resolvedPorts.uiSsr}`,
port: resolvedPorts.uiSsr,
localPath: runtimeConfig.ui.localPath
});
}
if (runtimeConfig.plugins) for (const [pluginId, pluginCfg] of Object.entries(runtimeConfig.plugins)) {
const pluginIsLocal = pluginCfg.source === "local";
const p = resolvedPorts.plugins[pluginId];
if (pluginIsLocal && p?.api) descriptors.push({
key: `plugin:${pluginId}`,
source: "local",
url: `http://localhost:${p.api}`,
port: p.api,
localPath: pluginCfg.localPath
});
if (!pluginIsLocal && pluginCfg.url) 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 buildComposeModel(dbs, redisPlans) {
return {
databases: dbs,
redis: redisPlans
};
}
function buildEnvGenerated(resolvedPorts, dbs, redisPlans) {
const env = {};
if (resolvedPorts.host) env.CORS_ORIGIN = `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.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);
const { dbs, redisPlans, postgres: pgPorts, redis: rdPorts } = yield* allocateDatabases(input.bosConfig, input.configDir);
const resolvedPorts = {
...svcPorts,
postgres: pgPorts,
redis: rdPorts
};
require_infra.savePortState(input.configDir, {
postgresPorts: pgPorts,
redisPorts: rdPorts,
devPorts: devPortsState
});
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}`
} : 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 pluginPort = resolvedPorts.plugins[id];
if (p.source === "local" && pluginPort?.api) return [id, {
...p,
port: pluginPort.api,
url: `http://localhost:${pluginPort.api}`
}];
return [id, p];
})) : void 0
};
const serviceDescriptors = buildServiceDescriptors(input.bosConfig, resolvedPorts);
const launch = buildLaunchSpec(input.bosConfig, resolvedPorts);
const composeModel = buildComposeModel(dbs, redisPlans);
const envGenerated = buildEnvGenerated(resolvedPorts, dbs, redisPlans);
const packages = serviceDescriptors.map((d) => d.key);
const description = require_service_descriptor.buildDescription(new Map(serviceDescriptors.map((d) => [d.key, d])));
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,
composeModel,
claims,
orchestrator
};
});
}
//#endregion
exports.planInfra = planInfra;
//# sourceMappingURL=planner.cjs.map