UNPKG

everything-dev

Version:

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

262 lines (260 loc) • 9.82 kB
let node_fs = require("node:fs"); let node_path = require("node:path"); let node_crypto = require("node:crypto"); //#region src/cli/infra.ts const POSTGRES_USER = "everythingdev"; const POSTGRES_PASSWORD = "everythingdev"; const AUTH_DATABASE_SECRET = "AUTH_DATABASE_URL"; const HOST_SECRET = "CORS_ORIGIN"; const REDIS_PORT = 6379; const TEST_AUTH_SECRET = "regression-test-secret-do-not-use-in-production"; const VESTIGIAL_SECRETS = /* @__PURE__ */ new Set(["NEAR_RELAYER_PRIVATE_KEY_MAINNET", "NEAR_RELAYER_PRIVATE_KEY_TESTNET"]); /** * Fixed local-development database conventions. The committed * docker-compose.yml provisions exactly these services/ports — nothing is * derived from runtime config. Every non-auth `*_DATABASE_URL` secret shares * the api database (schema-isolated per plugin); auth gets its own. */ const DEV_API_DB_PORT = 5432; const DEV_AUTH_DB_PORT = 5433; const TEST_API_DB_PORT = 5434; const TEST_AUTH_DB_PORT = 5435; function uniqueSecrets(values) { const secrets = []; const seen = /* @__PURE__ */ new Set(); for (const value of values) { if (!value || seen.has(value)) continue; seen.add(value); secrets.push(value); } return secrets; } function loadPortState(configDir) { if (!configDir) return {}; const statePath = (0, node_path.join)(configDir, ".bos", "infra-state.json"); if (!(0, node_fs.existsSync)(statePath)) return {}; try { return { devPorts: JSON.parse((0, node_fs.readFileSync)(statePath, "utf-8")).devPorts }; } catch { return {}; } } function savePortState(configDir, state) { const statePath = (0, node_path.join)(configDir, ".bos", "infra-state.json"); (0, node_fs.mkdirSync)((0, node_path.dirname)(statePath), { recursive: true }); (0, node_fs.writeFileSync)(statePath, `${JSON.stringify(state, null, 2)}\n`); } function normalizeDatabaseSlug(secret) { return secret.replace(/_DATABASE_URL$/, "").toLowerCase(); } function normalizeRedisSlug(secret) { return secret.replace(/_REDIS_URL$/, "").toLowerCase(); } function databaseUrl(port, databaseName) { return `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${port}/${databaseName}`; } function buildConventionalDatabases(secrets) { return uniqueSecrets(secrets.filter((secret) => secret.endsWith("_DATABASE_URL"))).sort((a, b) => normalizeDatabaseSlug(a).localeCompare(normalizeDatabaseSlug(b))).map((secret) => { const slug = normalizeDatabaseSlug(secret); const isAuth = secret === AUTH_DATABASE_SECRET; const port = isAuth ? DEV_AUTH_DB_PORT : DEV_API_DB_PORT; const databaseName = isAuth ? "auth_db" : "api_db"; return { secret, slug, port, databaseName, url: databaseUrl(port, databaseName) }; }); } function buildConventionalRedis(secrets) { return uniqueSecrets(secrets.filter((secret) => secret.endsWith("_REDIS_URL"))).sort((a, b) => normalizeRedisSlug(a).localeCompare(normalizeRedisSlug(b))).map((secret) => ({ secret, slug: normalizeRedisSlug(secret), port: REDIS_PORT, url: `redis://localhost:${REDIS_PORT}` })); } function testDatabaseUrl(secret) { const isAuth = secret === AUTH_DATABASE_SECRET; return databaseUrl(isAuth ? TEST_AUTH_DB_PORT : TEST_API_DB_PORT, isAuth ? "auth_test_db" : "api_test_db"); } function getSecretGroups(runtimeConfig) { const groups = []; const seen = /* @__PURE__ */ new Set(); const addGroup = (section, secrets) => { const filtered = secrets.filter((s) => { if (VESTIGIAL_SECRETS.has(s)) return false; if (seen.has(s)) return false; seen.add(s); return true; }); if (filtered.length > 0) groups.push({ section, secrets: filtered }); }; addGroup("app.host", uniqueSecrets([...runtimeConfig.host.secrets ?? [], HOST_SECRET])); addGroup("app.api", uniqueSecrets(runtimeConfig.api.secrets ?? [])); if (runtimeConfig.auth) addGroup("app.auth", uniqueSecrets(runtimeConfig.auth.secrets ?? [])); if (runtimeConfig.plugins) { for (const [pluginKey, plugin] of Object.entries(runtimeConfig.plugins)) if (plugin.secrets && plugin.secrets.length > 0) addGroup(`plugins.${pluginKey}`, plugin.secrets); } return groups; } function extractPortFromUrl(url) { return url.match(/:(\d{4,5})(?:\/|$)/)?.[1] ?? null; } function resolveDevHostPort(runtimeConfig) { if (typeof runtimeConfig.host?.port === "number") return runtimeConfig.host.port; const fromUrl = runtimeConfig.host?.url ? extractPortFromUrl(runtimeConfig.host.url) : null; if (fromUrl) { const parsed = Number.parseInt(fromUrl, 10); if (!Number.isNaN(parsed)) return parsed; } return 3e3; } function defaultSecretValue(secret, databases, redisConfigs, options) { if (secret === "BETTER_AUTH_SECRET") return options.forExample ? "" : (0, node_crypto.randomBytes)(32).toString("base64url"); if (secret === "CORS_ORIGIN") { if (options.forExample) return "http://localhost:3000"; if (typeof options.devHostPort === "number") return `http://localhost:${options.devHostPort}`; return "http://localhost:3000"; } return databases.get(secret)?.url ?? redisConfigs.get(secret)?.url ?? ""; } /** * Plugin `*_DATABASE_URL` secrets share the API database (the platform's * two-database contract), so they are not rendered as template entries — * the host falls back to `API_DATABASE_URL` when they are unset. */ function isFallbackDatabaseSecret(secret) { return secret.endsWith("_DATABASE_URL") && secret !== "API_DATABASE_URL" && secret !== "AUTH_DATABASE_URL"; } function renderEnvFile(groups, options) { const rendered = groups.map((group) => ({ section: group.section, secrets: group.secrets.filter((secret) => !isFallbackDatabaseSecret(secret)) })).filter((group) => group.secrets.length > 0); const allSecrets = rendered.flatMap((group) => group.secrets); const databaseMap = new Map(buildConventionalDatabases(allSecrets).map((entry) => [entry.secret, entry])); const redisMap = new Map(buildConventionalRedis(allSecrets).map((entry) => [entry.secret, entry])); const lines = [ "# Generated from configured bos secrets", "# Update values as needed for your local environment", "" ]; for (const group of rendered) { lines.push(`# ${group.section}`); for (const secret of group.secrets) lines.push(`${secret}=${defaultSecretValue(secret, databaseMap, redisMap, options)}`); lines.push(""); } return `${lines.join("\n")}\n`; } function renderEnvTestFile(groups) { const lines = [ "# Generated test environment — loaded by test suites instead of .env", "# Test databases run on dedicated *-test docker-compose services", "" ]; for (const group of groups) { const entries = []; for (const secret of group.secrets) if (secret === "BETTER_AUTH_SECRET") entries.push(`${secret}=${TEST_AUTH_SECRET}`); else if (secret.endsWith("_DATABASE_URL")) entries.push(`${secret}=${testDatabaseUrl(secret)}`); if (entries.length > 0) { lines.push(`# ${group.section}`); lines.push(...entries); lines.push(""); } } return `${lines.join("\n")}\n`; } function buildCiInfraPlan(runtimeConfig, options = {}) { const allSecrets = collectAllSecrets(runtimeConfig); const databases = buildConventionalDatabases(allSecrets); const redisConfigs = buildConventionalRedis(allSecrets); const hostPort = options.hostPortOverride ?? (Number.isFinite(Number(process.env.BOS_CI_HOST_PORT)) ? Number(process.env.BOS_CI_HOST_PORT) : void 0) ?? resolveDevHostPort(runtimeConfig); const env = {}; for (const db of databases) env[db.secret] = db.url; for (const r of redisConfigs) env[r.secret] = r.url; env.CORS_ORIGIN = `http://127.0.0.1:${hostPort}`; if (!env.BETTER_AUTH_SECRET) env.BETTER_AUTH_SECRET = ""; const services = []; const seenPorts = /* @__PURE__ */ new Set(); for (const db of databases) { if (seenPorts.has(db.port)) continue; seenPorts.add(db.port); services.push({ key: db.slug, slug: db.slug, image: "public.ecr.aws/docker/library/postgres:17-alpine", env: { POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB: db.databaseName }, ports: [`${db.port}:5432`], healthcheck: { test: ["CMD-SHELL", `pg_isready -U ${POSTGRES_USER} -d ${db.databaseName}`], interval: "3s", timeout: "3s", retries: 10 }, volumes: [`${db.slug}_data:/var/lib/postgresql/data`], database: { user: POSTGRES_USER, password: POSTGRES_PASSWORD, name: db.databaseName } }); } for (const r of redisConfigs) services.push({ key: r.slug, slug: r.slug, image: "public.ecr.aws/docker/library/redis:7-alpine", env: {}, ports: [`${r.port}:6379`], healthcheck: { test: [ "CMD", "redis-cli", "ping" ], interval: "3s", timeout: "3s", retries: 10 }, volumes: [`${r.slug}_data:/data`] }); return { account: runtimeConfig.account, gateway: runtimeConfig.domain ?? runtimeConfig.account, project: runtimeConfig.account, env, services, generatedAt: (/* @__PURE__ */ new Date()).toISOString() }; } function collectAllSecrets(runtimeConfig) { const all = []; all.push(...runtimeConfig.host.secrets ?? []); all.push(...runtimeConfig.api.secrets ?? []); if (runtimeConfig.auth) all.push(...runtimeConfig.auth.secrets ?? []); if (runtimeConfig.plugins) { for (const plugin of Object.values(runtimeConfig.plugins)) if (plugin.secrets) all.push(...plugin.secrets); } return all.filter((s) => !VESTIGIAL_SECRETS.has(s)); } //#endregion exports.buildCiInfraPlan = buildCiInfraPlan; exports.buildConventionalDatabases = buildConventionalDatabases; exports.buildConventionalRedis = buildConventionalRedis; exports.getSecretGroups = getSecretGroups; exports.loadPortState = loadPortState; exports.normalizeDatabaseSlug = normalizeDatabaseSlug; exports.renderEnvFile = renderEnvFile; exports.renderEnvTestFile = renderEnvTestFile; exports.resolveDevHostPort = resolveDevHostPort; exports.savePortState = savePortState; //# sourceMappingURL=infra.cjs.map