import process from "process"; import { existsSync } from "fs"; import { exec } from "child_process"; import type { IAppConfig } from "config"; /** * We use graphile-migrate for managing database migrations. * * However we also use convict as the sole source of truth for our app's configuration. We do not want to have to configure * separate env files or config files for graphile-migrate and yet again others for convict. * * So we wrap the graphile-migrate cli tool here. We parse our convict config, set necessary env vars, and then shell out to * graphile-migrate. * * Commander eats all args starting with --, so you must use the -- escape to indicate the arguments have finished * * Example: * ./cli db -- --help // will show graphile migrate help * ./cli db -- watch // will watch the current sql for changes * ./cli db -- watch --once // will apply the current sql once */ export const migrateWrapper = async ( commands: string[], config: IAppConfig, silent = false ): Promise => { const env = { DATABASE_URL: config.db.connection, SHADOW_DATABASE_URL: config.dev.shadowConnection, ROOT_DATABASE_URL: config.dev.rootConnection, DATABASE_NAME: config.db.name, DATABASE_OWNER: config.db.owner, DATABASE_AUTHENTICATOR: config.postgraphile.auth, DATABASE_VISITOR: config.postgraphile.visitor, }; const cmd = `npx --no-install graphile-migrate ${commands.join(" ")}`; const dbDir = `../../db`; const gmrc = `${dbDir}/.gmrc`; if (!existsSync(gmrc)) { throw new Error(`graphile migrate config not found at ${gmrc}`); } if (!silent) console.log("executing:", cmd); return new Promise((resolve, reject) => { const proc = exec(cmd, { env: { ...process.env, ...env }, cwd: dbDir, }); proc.stdout.on("data", (data) => { if (!silent) console.log("MIGRATE:", data); }); proc.stderr.on("data", (data) => { console.error("MIGRATE", data); }); proc.on("close", (code) => { if (code !== 0) { reject(new Error(`graphile-migrate exited with code ${code}`)); return; } resolve(); }); }); };