everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
127 lines (125 loc) • 5.14 kB
JavaScript
const require_infra = require("../services/infra.cjs");
let node_fs = require("node:fs");
let node_path = require("node:path");
let effect = require("effect");
let dotenv = require("dotenv");
//#region src/env/legacy-databases.ts
var LegacyMigrationError = class extends effect.Data.TaggedError("LegacyMigrationError") {};
/**
* The compose-era local database convention (`bos dev` provisioned Postgres
* via the committed docker-compose.yml): every `*_DATABASE_URL` pointed at
* `postgres://everythingdev:everythingdev@localhost:<port>/<db>` on the
* hardcoded 5432–5435 block. Those are exactly the opt-ins that made two
* concurrent local projects fight over host ports.
*
* The compose-era convention is retired — local persistence is a
* file-backed PGlite data dir (`.bos/db`), tests run on in-memory pglite.
* `bos dev` repairs leftover state once, idempotently:
* 1. `.env` lines whose URL matches the conventional shape (user, host,
* and database name — port too, but drift ports like 5532 from the old
* +100 allocator also match by name) are rewritten to the new defaults.
* Any other `postgres://` URL is operator-owned and left alone.
* 2. A leftover scaffold-shaped `docker-compose.yml` is moved to
* `.bos/compose-retired/` rather than deleted; operator-authored
* compose files (different service names/images) are untouched.
*/
const migrateLegacyComposeState = (configDir) => effect.Effect.try({
try: () => {
const envPath = (0, node_path.join)(configDir, ".env");
const databaseUrlDrift = [];
if ((0, node_fs.existsSync)(envPath)) {
const raw = (0, node_fs.readFileSync)(envPath, "utf-8");
const parsed = (0, dotenv.config)({
path: envPath,
quiet: true
}).parsed ?? {};
const rewritten = rewrittenLegacyDatabaseValues({
file: parsed,
raw
});
if (rewritten.size > 0) {
const updated = raw.split("\n").map((line) => {
const match = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/.exec(line);
if (!match) return line;
const replacement = rewritten.get(match[1]);
return replacement === void 0 ? line : `${match[1]}=${replacement}`;
});
(0, node_fs.writeFileSync)(envPath, updated.join("\n"));
for (const [key, to] of rewritten) databaseUrlDrift.push({
key,
from: parsed[key] ?? "",
to
});
}
}
return {
databaseUrlDrift,
composeRelocated: relocateLegacyComposeFile(configDir)
};
},
catch: (cause) => new LegacyMigrationError({ cause })
}).pipe(effect.Effect.orElseSucceed(() => ({
databaseUrlDrift: [],
composeRelocated: false
})));
/** Matches the compose-era URL shapes: the scaffolded user + localhost + the convention's database names. */
const LEGACY_DATABASE_NAME = /^(api_db|auth_db|api_test_db|auth_test_db)$/;
function legacyMappingForUrl(rawUrl) {
try {
const url = new URL(rawUrl);
if (url.protocol !== "postgres:" && url.protocol !== "postgresql:") return void 0;
if (url.hostname !== "localhost" && url.hostname !== "127.0.0.1") return void 0;
if (url.username !== "everythingdev" || url.password !== "everythingdev") return void 0;
const databaseName = decodeURIComponent(url.pathname.replace(/^\//, ""));
if (!LEGACY_DATABASE_NAME.test(databaseName)) return void 0;
return databaseName.endsWith("_test_db") ? require_infra.PGLITE_TEST_DATABASE_URL : require_infra.PGLITE_DEV_DATABASE_URL;
} catch {
return;
}
}
function rewrittenLegacyDatabaseValues(input) {
const rewritten = /* @__PURE__ */ new Map();
for (const [key, value] of Object.entries(input.file)) {
const match = value ? legacyMappingForUrl(value) : void 0;
if (match !== void 0) {
if (new RegExp(`^${key}=.+$`, "m").test(input.raw)) rewritten.set(key, match);
}
}
return rewritten;
}
const SCAFFOLD_SERVICE_NAMES = /* @__PURE__ */ new Set([
"postgres-api",
"postgres-auth",
"postgres-api-test",
"postgres-auth-test"
]);
const SCAFFOLD_IMAGE_HINTS = [
"public.ecr.aws/docker/library/postgres",
"docker.io/library/postgres",
"postgres:17-alpine",
"ghcr.io/nearbuilders/mirror-postgres"
];
function isScaffoldComposeFile(raw) {
if (![...raw.matchAll(/^\s+-?\s*([a-z-]+):\s*$/gm)].map((match) => match[1] ?? "").some((name) => SCAFFOLD_SERVICE_NAMES.has(name))) return false;
return SCAFFOLD_IMAGE_HINTS.some((hint) => raw.includes(hint));
}
function relocateLegacyComposeFile(configDir) {
const composePath = (0, node_path.join)(configDir, "docker-compose.yml");
if (!(0, node_fs.existsSync)(composePath)) return false;
let raw;
try {
raw = (0, node_fs.readFileSync)(composePath, "utf-8");
} catch {
return false;
}
if (!isScaffoldComposeFile(raw)) return false;
const retiredDir = (0, node_path.join)(configDir, ".bos", "compose-retired");
(0, node_fs.mkdirSync)(retiredDir, { recursive: true });
(0, node_fs.renameSync)(composePath, (0, node_path.join)(retiredDir, "docker-compose.yml"));
return true;
}
//#endregion
exports.LegacyMigrationError = LegacyMigrationError;
exports.migrateLegacyComposeState = migrateLegacyComposeState;
exports.rewrittenLegacyDatabaseValues = rewrittenLegacyDatabaseValues;
//# sourceMappingURL=legacy-databases.cjs.map