UNPKG

@medflyt/test-db-service

Version:

Developer tool to instantly provision test PostgreSQL databases from a template

172 lines 6.76 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.test = exports.PostgresServer = exports.randomPort = exports.getPostgresBinaryPath = exports.checkPostgresInstalled = void 0; const childProcess = require("child_process"); const envPaths = require("env-paths"); const fs = require("fs"); const path = require("path"); const promisify_child_process_1 = require("promisify-child-process"); const readline = require("readline"); const TargetPlatforms_1 = require("./DownloadUtils/TargetPlatforms"); const download_postgres_1 = require("./download_postgres"); const tempDir_1 = require("./DownloadUtils/tempDir"); const APP_NAME = "launch-postgres"; const appEnvPaths = envPaths(APP_NAME); function getCurrentPlatform() { return { platform: TargetPlatforms_1.getCurrentPlatformForDownload(), arch: TargetPlatforms_1.getCurrentArchForDownload() }; } function postgresDirectory(platform, postgresVersion) { return path.join(appEnvPaths.cache, postgresVersion + "-" + platform.platform + "-" + platform.arch); } function checkPostgresInstalled(platform, postgresVersion) { return new Promise((resolve) => { fs.stat(postgresDirectory(platform, postgresVersion), (err, stats) => { if (err) { resolve(false); return; } resolve(stats.isDirectory()); }); }); } exports.checkPostgresInstalled = checkPostgresInstalled; async function getPostgresBinaryPath(platform, postgresVersion, binaryName) { const isInstalled = await checkPostgresInstalled(platform, postgresVersion); if (!isInstalled) { await download_postgres_1.runBinariesJarFileProcess(APP_NAME, platform.platform, platform.arch, postgresVersion, postgresDirectory(platform, postgresVersion)); } const ext = platform.platform === "windows" ? ".exe" : ""; return path.join(postgresDirectory(platform, postgresVersion), "bin", binaryName + ext); } exports.getPostgresBinaryPath = getPostgresBinaryPath; // See: <https://en.wikipedia.org/wiki/Ephemeral_port> const MIN_PORT = 49152; const MAX_PORT = 65534; function randomPort() { return MIN_PORT + Math.floor(Math.random() * (MAX_PORT - MIN_PORT)); } exports.randomPort = randomPort; /** * @returns null if the port is not available */ function tryLaunchPostgres(postgres, dataDir, port) { return new Promise((resolve, reject) => { const postgresChild = childProcess.spawn(postgres, ["-F", "-D", dataDir, "-p", `${port}`], { detached: true, env: {} }); process.on("exit", () => { postgresChild.kill(); }); let handled = false; readline.createInterface({ input: postgresChild.stderr }).on("line", line => { if (handled) { return; } if (/database system is ready to accept connections/.test(line)) { handled = true; resolve(postgresChild); } else if (/could not bind/.test(line)) { handled = true; postgresChild.kill(); resolve(null); } }); postgresChild.on("error", (err) => { if (handled) { return; } reject(err); }); }); } const USERNAME = "test"; const PASSWORD = "test"; class PostgresServer { constructor(tempDir, postgresProc, port) { this.tempDir = tempDir; this.postgresProc = postgresProc; this.port = port; this.url = `postgres://${USERNAME}:${PASSWORD}@127.0.0.1:${this.port}/postgres`; } static async start(postgresVersion) { const platform = getCurrentPlatform(); const initDb = await getPostgresBinaryPath(platform, postgresVersion, "initdb"); const postgres = await getPostgresBinaryPath(platform, postgresVersion, "postgres"); const tempDir = await tempDir_1.createTempDir(APP_NAME); try { await tempDir_1.withTempDir(APP_NAME, async (pwTmpDir) => { const pwFile = path.join(pwTmpDir, "password.txt"); await new Promise((resolve, reject) => { fs.writeFile(pwFile, PASSWORD, { encoding: "utf8" }, (err) => { if (err) { reject(err); return; } resolve(); }); }); let initDbChild; try { initDbChild = promisify_child_process_1.spawn(initDb, ["-D", tempDir.directory, "-N", "-U", USERNAME, "--pwfile", pwFile], { encoding: "utf8", env: {} }); } catch (err) { throw new Error(`initdb failed, error running command "${initDb}": ${err.message}`); } if (initDbChild.stdout === null) { throw new Error("initdb stdout is null"); } if (initDbChild.stderr === null) { throw new Error("initdb stdout is null"); } let output = ""; initDbChild.stdout.on("data", (msg) => { output += msg.toString(); }); initDbChild.stderr.on("data", (msg) => { output += msg.toString(); }); try { await initDbChild; } catch (err) { throw new Error(`initdb failed:\n${output}`); } }); let postgresProc = null; let port = 0; while (postgresProc === null) { port = randomPort(); postgresProc = await tryLaunchPostgres(postgres, tempDir.directory, port); } return new PostgresServer(tempDir, postgresProc, port); } catch (err) { await tempDir.close(); throw err; } } async close() { // <https://www.postgresql.org/docs/current/server-shutdown.html> const SIGQUIT = 3; this.postgresProc.kill(SIGQUIT); await new Promise((resolve) => { this.postgresProc.on("close", resolve); }); await this.tempDir.close(); } } exports.PostgresServer = PostgresServer; async function test() { process.on("SIGINT", () => process.exit()); const pgServer = await PostgresServer.start("10.10"); try { console.log("Running", pgServer.port, pgServer.url); await new Promise((resolve) => { setTimeout(resolve, 10000); }); } finally { await pgServer.close(); } } exports.test = test; // test(); //# sourceMappingURL=launch_postgres.js.map