@medflyt/test-db-service
Version:
Developer tool to instantly provision test PostgreSQL databases from a template
172 lines • 6.76 kB
JavaScript
;
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