everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
175 lines (172 loc) • 5.28 kB
JavaScript
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