UNPKG

@cdwr/core

Version:

A set of core utilities for the Codeware ecosystem.

348 lines (336 loc) 9.57 kB
// packages/core/src/lib/utils/docker-build.ts import { existsSync } from "fs"; import { join } from "path"; import { dockerCommand } from "docker-cli-js"; import invariant from "tiny-invariant"; // packages/core/src/lib/utils/log-utils.ts import chalk from "chalk"; function isDebugEnabled() { return process.env["CDWR_DEBUG_LOGGING"] === "true" || process.env["NX_VERBOSE_LOGGING"] === "true"; } function logDebug(title, body) { if (!isDebugEnabled()) { return; } const message = `${chalk.reset.inverse.bold.cyan( " DEBUG " )} ${chalk.bold.cyan(title)}`; return consoleLogger(message, body); } function logInfo(title, body) { const message = `${chalk.reset.inverse.bold.blue( " INFO " )} ${chalk.bold.blue(title)}`; return consoleLogger(message, body); } function logError(title, body) { const message = `${chalk.reset.inverse.bold.red(" ERROR ")} ${chalk.bold.red( title )}`; return consoleLogger(message, body); } function logSuccess(title, body) { const message = `${chalk.reset.inverse.bold.green( " SUCCESS " )} ${chalk.bold.green(title)}`; return consoleLogger(message, body); } function logWarning(title, body) { const message = `${chalk.reset.inverse.bold.yellow( " WARNING " )} ${chalk.bold.yellow(title)}`; return consoleLogger(message, body); } function consoleLogger(message, body) { process.stdout.write("\n"); process.stdout.write(`${getLogPrefix()} ${message} `); if (body) { process.stdout.write(`${body} `); } process.stdout.write("\n"); } function getLogPrefix() { const prefix = process.env["CDWR_LOG_PREFIX"] ?? "E2E"; return `${chalk.reset.inverse.bold.yellow(` ${prefix.trim()} `)}`; } // packages/core/src/lib/utils/docker-build.ts var dockerBuild = async (image, quiet) => { const { context, dockerfile, name, tag, args } = image; const dockerfilePath = join(context, dockerfile); invariant(!!context?.length, "Context path must be provided"); invariant(!!dockerfile?.length, "Doockerfile path must be provided"); invariant(!!name?.length, "Image name must be provided"); invariant( existsSync(join(dockerfilePath)), `Dockerfile not found: ${dockerfilePath}` ); const buildTag = `${name}:${tag}`; if (!quiet) { logInfo(`[${buildTag}] Context: '${image.context}'`); logInfo(`[${buildTag}] Dockerfile: '${image.dockerfile}'`); logInfo( `[${buildTag}] Args: ${image?.args ? JSON.stringify(image.args) : "{}"}` ); } const buildArgs = args && Object.keys(args).map((key) => `--build-arg ${key}=${args[key]}`).join(" ") || ""; const cmd = `build -f ${dockerfilePath} -t ${buildTag} ${buildArgs} ${quiet ? "-q" : ""} ${context}`; if (!quiet) { logInfo(`[${buildTag}] Building image...`); } try { const data = await dockerCommand(cmd, { echo: !quiet }); if (!quiet) { logInfo(`[${buildTag}] Build success: ${data.response[0]}`); } } catch (error) { logError(`[${buildTag}] Build failed`); logError(`[${buildTag}] Command: '${cmd}'`); throw error; } }; // packages/core/src/lib/utils/promisified-exec.ts import { exec as cpExec, execFile as cpExecFile } from "child_process"; import { promisify } from "util"; var exec = promisify(cpExec); var execFile = promisify(cpExecFile); // packages/core/src/lib/utils/get-package-version.ts function getNpmExecutable() { if (process.platform === "win32") { return process.env["npm_execpath"] || "npm.cmd"; } return "npm"; } async function getPackageVersion(packageName) { let version; try { const { stdout } = await execFile(getNpmExecutable(), [ "list", packageName, "--depth=0", "--json" ]); const { dependencies } = JSON.parse(stdout); const pkg = dependencies && dependencies[packageName]; version = pkg?.version; } catch (error) { logDebug( `Failed to get version for package ${packageName}`, error.message ); } return version ?? ""; } // packages/core/src/lib/utils/kill-port.ts import kill from "kill-port"; import { check as portCheck } from "tcp-port-used"; var KILL_PORT_DELAY = 5e3; async function killPort(port, options) { const delay = options?.delay ?? KILL_PORT_DELAY; const verbose = options?.verbose; if (await portCheck(port)) { let killPortResult; try { if (verbose) { logInfo(`Attempting to close port ${port}`); } killPortResult = await kill(port); await new Promise((resolve) => setTimeout(() => resolve(), delay)); if (await portCheck(port)) { logError(`Port ${port} still open`, JSON.stringify(killPortResult)); } else { if (verbose) { logSuccess(`Port ${port} successfully closed`); } return true; } } catch { logError(`Port ${port} closing failed`); } return false; } else { return true; } } // packages/core/src/lib/utils/kill-process-tree.ts import { promisify as promisify2 } from "util"; import treeKill from "tree-kill"; var killProcessTree = promisify2(treeKill); // packages/core/src/lib/utils/kill-process-and-ports.ts async function killProcessAndPorts(pid, ports) { if (pid) { await killProcessTree(pid, "SIGKILL"); } for (const port of ports ?? []) { await killPort(port); } } // packages/core/src/lib/utils/promisified-spawn.ts import { spawn as cpSpawn } from "child_process"; function spawn(command, args, options) { return new Promise((resolve, reject) => { const process2 = cpSpawn(command, args, { ...options, stdio: "pipe" }); const stdoutChunks = []; const stderrChunks = []; process2.stdout.on("data", (data) => { stdoutChunks.push(Buffer.from(data)); }); process2.stderr.on("data", (data) => { stderrChunks.push(Buffer.from(data)); }); process2.on("close", (code) => { const stdout = Buffer.concat(stdoutChunks).toString(); const stderr = Buffer.concat(stderrChunks).toString(); if (code === 0) { resolve({ stdout, stderr }); } else { reject(new Error(`Process exited with code ${code} Error: ${stderr}`)); } }); process2.on("error", reject); }); } // packages/core/src/lib/utils/run-command.ts import { exec as exec2 } from "child_process"; import { tmpProjPath } from "@nx/plugin/testing"; function runCommand(command, options) { const cwd = options?.cwd ?? tmpProjPath(); const doneFn = options?.doneFn; const env = options?.env ?? process.env; const errorDetector = options?.errorDetector; const verbose = options?.verbose; if (verbose) { logDebug("Running command...", command); } const controller = new AbortController(); const { signal } = controller; const p = exec2(command, { cwd, encoding: "utf-8", env, signal }); return new Promise((resolve, reject) => { let output = ""; let complete = false; const checkLog = (log) => { if (verbose) { logDebug(log); } output += log; if (errorDetector && log.match(errorDetector)) { logDebug( "Error detector found a match, terminate command with failure", log ); return terminate(output, "fail"); } if (doneFn && doneFn(log) && !complete) { complete = true; logDebug("Predicate function met, terminate command successfully", log); terminate(output, "success"); } }; const terminate = (result, status) => { if (!signal.aborted) { controller.abort(); } if (status === "success") { resolve(result); } else { reject(result); } }; p.stdout?.on("data", checkLog); p.stderr?.on("data", checkLog); p.on("error", (err) => { if (signal.aborted) { return; } logError("Received error event"); terminate(err.message, "fail"); }); p.on("exit", (code) => { if (doneFn && !complete) { logError( "Command output:", output.split("\n").map((l) => ` ${l}`).join("\n") ); reject(`Exited with ${code}`); } else { terminate(output, "success"); } }); }); } // packages/core/src/lib/utils/spawn-pty.ts import { spawn as spawn2 } from "@homebridge/node-pty-prebuilt-multiarch"; function spawnPty(command, args, options) { return new Promise((resolve, reject) => { const ptyData = []; const ptyProcess = spawn2(command, args, { cwd: process.cwd(), encoding: "utf-8", env: process.env }); ptyProcess.onData((data) => { ptyData.push(data); if (options?.prompt) { const answer = options.prompt(data); if (answer) { ptyProcess.write(`${answer} `); } } }); ptyProcess.onExit(({ exitCode }) => { const output = ptyData.join(""); if (exitCode === 0) { resolve(output); } else { reject( new Error(`Process exited with code ${exitCode} Output: ${output}`) ); } }); }); } // packages/core/src/lib/utils/whoami.ts import npmWhoami from "npm-whoami"; async function whoami() { return new Promise((resolve) => { npmWhoami((err, user) => { if (err || !user) { resolve(""); } else { resolve(user); } }); }); } export { dockerBuild, exec, getPackageVersion, isDebugEnabled, killPort, killProcessAndPorts, killProcessTree, logDebug, logError, logInfo, logSuccess, logWarning, runCommand, spawn, spawnPty, whoami };