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