everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
125 lines (123 loc) • 4.92 kB
JavaScript
import { PGLITE_DEV_DATABASE_URL, PGLITE_TEST_DATABASE_URL } from "../services/infra.mjs";
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { Data, Effect } from "effect";
import { config } from "dotenv";
//#region src/env/legacy-databases.ts
var LegacyMigrationError = class extends 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.try({
try: () => {
const envPath = join(configDir, ".env");
const databaseUrlDrift = [];
if (existsSync(envPath)) {
const raw = readFileSync(envPath, "utf-8");
const parsed = 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}`;
});
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.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") ? PGLITE_TEST_DATABASE_URL : 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 = join(configDir, "docker-compose.yml");
if (!existsSync(composePath)) return false;
let raw;
try {
raw = readFileSync(composePath, "utf-8");
} catch {
return false;
}
if (!isScaffoldComposeFile(raw)) return false;
const retiredDir = join(configDir, ".bos", "compose-retired");
mkdirSync(retiredDir, { recursive: true });
renameSync(composePath, join(retiredDir, "docker-compose.yml"));
return true;
}
//#endregion
export { LegacyMigrationError, migrateLegacyComposeState, rewrittenLegacyDatabaseValues };
//# sourceMappingURL=legacy-databases.mjs.map