everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
175 lines (173 loc) • 6.65 kB
JavaScript
const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
let node_fs = require("node:fs");
let node_path = require("node:path");
let node_crypto = require("node:crypto");
let _clack_prompts = require("@clack/prompts");
_clack_prompts = require_runtime.__toESM(_clack_prompts, 1);
let dotenv = require("dotenv");
//#region src/cli/infra.ts
const POSTGRES_USER = "everythingdev";
const POSTGRES_PASSWORD = "everythingdev";
const API_DATABASE_SECRET = "API_DATABASE_URL";
const AUTH_DATABASE_SECRET = "AUTH_DATABASE_URL";
const HOST_SECRET = "CORS_ORIGIN";
const BASE_DATABASE_PORT = 5434;
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 getSecretGroups(runtimeConfig) {
const groups = [];
const seen = /* @__PURE__ */ new Set();
const addGroup = (section, secrets) => {
const filtered = secrets.filter((s) => {
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 buildGeneratedInfraSpec(runtimeConfig) {
const groups = getSecretGroups(runtimeConfig);
return {
groups,
databases: buildDatabaseConfigs(groups.flatMap((group) => group.secrets))
};
}
function normalizeDatabaseSlug(secret) {
return secret.replace(/_DATABASE_URL$/, "").toLowerCase();
}
function buildDatabaseConfigs(secrets) {
return [
API_DATABASE_SECRET,
AUTH_DATABASE_SECRET,
...uniqueSecrets(secrets.filter((secret) => secret.endsWith("_DATABASE_URL"))).filter((secret) => secret !== API_DATABASE_SECRET && secret !== AUTH_DATABASE_SECRET).sort((a, b) => a.localeCompare(b))
].map((secret, index) => {
const slug = normalizeDatabaseSlug(secret);
const port = secret === API_DATABASE_SECRET ? 5432 : secret === AUTH_DATABASE_SECRET ? 5433 : BASE_DATABASE_PORT + index - 2;
return {
secret,
slug,
port,
serviceName: `postgres-${slug.replace(/_/g, "-")}`,
databaseName: `${slug}_db`,
volumeName: `postgres_${slug}_data`,
url: `postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@localhost:${port}/${slug}_db`
};
});
}
function defaultSecretValue(secret, databases, options) {
if (secret === "BETTER_AUTH_SECRET") return options.forExample ? "" : (0, node_crypto.randomBytes)(32).toString("base64url");
if (secret === "CORS_ORIGIN") return "http://localhost:3000";
return databases.get(secret)?.url ?? "";
}
function renderEnvFile(groups, databases, options) {
const databaseMap = new Map(databases.map((entry) => [entry.secret, entry]));
const lines = [
"# Generated from configured bos secrets",
"# Update values as needed for your local environment",
""
];
for (const group of groups) {
lines.push(`# ${group.section}`);
for (const secret of group.secrets) lines.push(`${secret}=${defaultSecretValue(secret, databaseMap, options)}`);
lines.push("");
}
return `${lines.join("\n")}\n`;
}
function renderDockerCompose(databases) {
const lines = [
"x-pg-common: &pg-common",
" image: postgres:17-alpine",
" environment: &pg-env",
` POSTGRES_USER: ${POSTGRES_USER}`,
` POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}`,
" healthcheck:",
" test: [\"CMD-SHELL\", \"pg_isready -U everythingdev\"]",
" interval: 3s",
" timeout: 3s",
" retries: 5",
"",
"services:"
];
for (const database of databases) {
lines.push(` ${database.serviceName}:`);
lines.push(" <<: *pg-common");
lines.push(" environment:");
lines.push(" <<: *pg-env");
lines.push(` POSTGRES_DB: ${database.databaseName}`);
lines.push(" ports:");
lines.push(` - "${database.port}:5432"`);
lines.push(" volumes:");
lines.push(` - ${database.volumeName}:/var/lib/postgresql/data`);
lines.push("");
}
lines.push("volumes:");
for (const database of databases) lines.push(` ${database.volumeName}:`);
return `${lines.join("\n")}\n`;
}
function syncTextFile(filePath, nextContent) {
if ((0, node_fs.existsSync)(filePath) && (0, node_fs.readFileSync)(filePath, "utf-8") === nextContent) return false;
(0, node_fs.writeFileSync)(filePath, nextContent);
return true;
}
function writeGeneratedInfra(configDir, runtimeConfig) {
return syncGeneratedInfra(configDir, runtimeConfig).secrets;
}
function syncGeneratedInfra(configDir, runtimeConfig) {
const spec = buildGeneratedInfraSpec(runtimeConfig);
const secrets = spec.groups.flatMap((group) => group.secrets);
const newEnvContent = renderEnvFile(spec.groups, spec.databases, { forExample: true });
const newDockerContent = renderDockerCompose(spec.databases);
const envExamplePath = (0, node_path.join)(configDir, ".env.example");
const dockerComposePath = (0, node_path.join)(configDir, "docker-compose.yml");
return {
secrets,
envExampleChanged: syncTextFile(envExamplePath, newEnvContent),
dockerComposeChanged: syncTextFile(dockerComposePath, newDockerContent)
};
}
function ensureEnvFile(configDir) {
const envPath = (0, node_path.join)(configDir, ".env");
const examplePath = (0, node_path.join)(configDir, ".env.example");
if ((0, node_fs.existsSync)(envPath) || !(0, node_fs.existsSync)(examplePath)) return;
const lines = (0, node_fs.readFileSync)(examplePath, "utf-8").split("\n");
const secret = (0, node_crypto.randomBytes)(32).toString("base64url");
(0, node_fs.writeFileSync)(envPath, lines.map((line) => {
if (/^BETTER_AUTH_SECRET=/.test(line)) return `BETTER_AUTH_SECRET=${secret}`;
return line;
}).join("\n"));
_clack_prompts.log.info("Created .env from generated .env.example with generated BETTER_AUTH_SECRET");
}
function loadProjectEnv(configDir) {
const envPath = (0, node_path.join)(configDir, ".env");
if (!(0, node_fs.existsSync)(envPath)) return;
(0, dotenv.config)({
path: envPath,
processEnv: process.env,
quiet: true
});
}
//#endregion
exports.ensureEnvFile = ensureEnvFile;
exports.loadProjectEnv = loadProjectEnv;
exports.syncGeneratedInfra = syncGeneratedInfra;
exports.writeGeneratedInfra = writeGeneratedInfra;
//# sourceMappingURL=infra.cjs.map