UNPKG

everything-dev

Version:

A consolidated product package for building Module Federation apps with oRPC APIs.

175 lines (172 loc) • 5.28 kB
import { bindingEnv } from "./binding.mjs"; import { existsSync, mkdirSync, writeFileSync } from "node:fs"; import { join, resolve } from "node:path"; import { Context, Data, Effect, Layer } from "effect"; import { spawn } from "node:child_process"; //#region src/db/drizzle-kit.ts var DrizzleKitError = class extends Data.TaggedError("DrizzleKitError") {}; var DrizzleKit = class extends Context.Service()("everything-dev/DrizzleKit") {}; /** * The single choke point for invoking drizzle-kit. Every child receives the * composition root's materialized environment (`bindingEnv`) so the config it * evaluates never has to re-derive credentials from ambient state. */ function spawnDrizzleKit(spec) { return Effect.callback((resume) => { let stderr = ""; let settled = false; const settle = (effect) => { if (settled) return; settled = true; resume(effect); }; const child = spawn(process.platform === "win32" ? "npx.cmd" : "npx", [...spec.args], { cwd: spec.cwd, env: spec.env, stdio: spec.stdio }); if (spec.stdio === "pipe") child.stderr?.on("data", (data) => { stderr += data.toString(); }); child.on("error", (err) => { const hint = err.code === "ENOENT" ? "\"npx\" not found. Ensure Node.js is installed." : err.message; settle(Effect.fail(new DrizzleKitError({ command: spec.command, message: hint, exitCode: void 0 }))); }); child.on("exit", (code) => { if (code === 0 || code === null) { settle(Effect.void); return; } settle(Effect.fail(new DrizzleKitError({ command: spec.command, message: `drizzle-kit ${spec.command} exited with code ${code}${stderr.trim() ? `: ${stderr.trim()}` : ""}`, exitCode: code }))); }); return Effect.sync(() => { if (child.exitCode === null && !child.killed) child.kill(); }); }); } function childEnv(binding) { return { ...process.env, ...bindingEnv({ secretName: binding.identity.secretName, url: binding.url }) }; } function workspaceConfigPath(binding) { const workspaceDir = binding.identity.workspaceDir; if (!workspaceDir) return void 0; const configPath = join(workspaceDir, "drizzle.config.ts"); return existsSync(configPath) ? configPath : void 0; } function writeGeneratedConfig(binding, projectDir) { const dbDir = resolve(projectDir, ".bos", "db", binding.key); mkdirSync(dbDir, { recursive: true }); const configPath = join(dbDir, "drizzle.config.ts"); const configContent = `import { defineConfig } from "drizzle-kit"; export default defineConfig({ schema: "./drizzle/schema.ts", out: "./drizzle", dialect: "postgresql", dbCredentials: { url: "${binding.url}", }, migrations: { schema: "${binding.identity.journal.schema}", table: "${binding.identity.journal.table}", }, verbose: true, strict: true, }); `; writeFileSync(configPath, configContent); return dbDir; } const makeDrizzleKitLive = (options) => Layer.succeed(DrizzleKit, { studio: (binding) => Effect.gen(function* () { const localConfig = workspaceConfigPath(binding); if (localConfig && binding.identity.workspaceDir) { options.onLog?.(`Starting Drizzle Studio for ${binding.key} (local)...`); yield* spawnDrizzleKit({ args: [ "drizzle-kit", "studio", "--config", localConfig ], cwd: binding.identity.workspaceDir, env: childEnv(binding), command: "studio", stdio: "inherit" }); return; } if (binding.source === "local") return yield* new DrizzleKitError({ command: "studio", message: `No drizzle.config.ts found in ${binding.identity.workspaceDir ?? binding.key}. Run 'drizzle-kit init' first in the plugin workspace.`, exitCode: void 0 }); options.onLog?.(`Introspecting database schema for ${binding.key}...`); const dbDir = writeGeneratedConfig(binding, options.projectDir); yield* spawnDrizzleKit({ args: [ "drizzle-kit", "pull", "--config", join(dbDir, "drizzle.config.ts") ], cwd: dbDir, env: childEnv(binding), command: "pull", stdio: "inherit" }).pipe(Effect.mapError((error) => new DrizzleKitError({ command: "pull", exitCode: error.exitCode, message: `Failed to introspect database for "${binding.key}". Check that ${binding.identity.secretName} is correct and the database is reachable. ` + error.message }))); options.onLog?.(`Starting Drizzle Studio for ${binding.key}...`); yield* spawnDrizzleKit({ args: [ "drizzle-kit", "studio", "--config", join(dbDir, "drizzle.config.ts") ], cwd: dbDir, env: childEnv(binding), command: "studio", stdio: "inherit" }); }), migrate: (binding) => { const configPath = workspaceConfigPath(binding); if (!configPath || !binding.identity.workspaceDir) return Effect.fail(new DrizzleKitError({ command: "migrate", message: `No drizzle.config.ts found in ${binding.identity.workspaceDir ?? binding.key}.`, exitCode: void 0 })); return spawnDrizzleKit({ args: [ "drizzle-kit", "migrate", "--config", configPath ], cwd: binding.identity.workspaceDir, env: childEnv(binding), command: "migrate", stdio: "pipe" }); } }); //#endregion export { DrizzleKit, DrizzleKitError, makeDrizzleKitLive }; //# sourceMappingURL=drizzle-kit.mjs.map