everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
262 lines (260 loc) • 9.82 kB
JavaScript
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