@cdwr/core
Version:
A set of core utilities for the Codeware ecosystem.
369 lines (356 loc) • 10.2 kB
JavaScript
// 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/find-down.ts
import { promises } from "fs";
import { basename, join as join2 } from "path";
import { cwd } from "process";
var findDown = async (filename, { startDir = cwd() } = {}) => {
const files = await promises.readdir(startDir, { recursive: true });
const found = files.find((file) => basename(file.toString()) === filename);
return found ? join2(startDir, found.toString()) : null;
};
// 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 cwd2 = 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 child = exec2(command, {
cwd: cwd2,
encoding: "utf-8",
env,
signal
});
return new Promise((resolve, reject) => {
let output = "";
let complete = false;
const terminate = (result, status) => {
if (complete) {
return;
}
complete = true;
if (!signal.aborted) {
controller.abort();
}
if (status === "success") {
resolve(result);
} else {
reject(result);
}
};
const checkLog = (chunk) => {
output += chunk;
if (errorDetector && chunk.match(errorDetector)) {
logDebug(
"Error detector found a match, terminate command with failure",
output
);
return terminate(output, "fail");
}
if (doneFn && doneFn(output)) {
logDebug(
"Predicate function met, terminate command successfully",
output
);
return terminate(output, "success");
}
};
child.stdout?.on("data", checkLog);
child.stderr?.on("data", checkLog);
child.on("error", (err) => {
if (signal.aborted) {
return;
}
logError("Received error event");
terminate(err.message, "fail");
});
child.on("exit", (code) => {
if (signal.aborted) {
return;
}
if (doneFn && !complete) {
logError(
"Command output:",
output.split("\n").map((l) => ` ${l}`).join("\n")
);
return terminate(`Exited with ${code}`, "fail");
}
if (code === 0) {
terminate(output, "success");
} else {
terminate(`Exited with ${code}:
${output}`, "fail");
}
});
});
}
// 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,
findDown,
getPackageVersion,
isDebugEnabled,
killPort,
killProcessAndPorts,
killProcessTree,
logDebug,
logError,
logInfo,
logSuccess,
logWarning,
runCommand,
spawn,
spawnPty,
whoami
};