import { type SpawnSyncReturns, spawnSync } from "node:child_process"; import { readFileSync, writeFileSync } from "node:fs"; import { dirname, relative, resolve } from "node:path"; import { fileURLToPath } from "node:url"; export const ROOT: string = resolve( dirname(fileURLToPath(import.meta.url)), "../..", ); export function readJson(path: string): unknown { return JSON.parse(readFileSync(path, "utf8")); } export function writeJson(path: string, data: unknown): void { writeFileSync(path, `${JSON.stringify(data, null, "\t")}\n`, "utf8"); } export interface RunOptions { cwd?: string; capture?: boolean; env?: NodeJS.ProcessEnv; timeout?: number; check?: boolean; } export type RunResult = SpawnSyncReturns; interface RunError extends Error { code?: string; result?: RunResult; } export function run( cmd: string, args: string[] = [], options: RunOptions = {}, ): RunResult { const result = spawnSync(cmd, args, { cwd: options.cwd ?? ROOT, encoding: "utf8", stdio: options.capture ? ["ignore", "pipe", "pipe"] : "inherit", env: options.env ?? process.env, timeout: options.timeout, }); if (result.error && (result.error as RunError).code !== "ENOENT") throw result.error; if (options.check !== false && result.status !== 0) { const err = new Error( `${cmd} ${args.join(" ")} exited ${result.status ?? result.signal ?? (result.error as RunError | undefined)?.code}`, ) as RunError; err.result = result; throw err; } return result; } export function git(args: string[], options: RunOptions = {}): RunResult { return run("git", args, { ...options, cwd: ROOT }); } export function rel(path: string, root: string = ROOT): string { return relative(root, path).replaceAll("\\", "/"); } export function stripAnsi(text: string): string { return text .replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "") .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "") .trim(); }