76 lines · 2.9 KB · JavaScript
RawDownload
// Helpers shared by the build, release, and maintenance scripts.import { execFileSync } from "node:child_process";import { createHash } from "node:crypto";import { existsSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";import { createServer } from "node:net";import { dirname, join, resolve } from "node:path";import { fileURLToPath, pathToFileURL } from "node:url"; export const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../.."); /** True when `moduleUrl` (pass `import.meta.url`) is the script Node was asked to run. */export function isMain(moduleUrl) {  return Boolean(process.argv[1]) && moduleUrl === pathToFileURL(resolve(process.argv[1])).href;} /** A TCP port on 127.0.0.1 that was free a moment ago. */export function freePort() {  return new Promise((resolve, reject) => {    const server = createServer();    server.once("error", reject);    server.listen(0, "127.0.0.1", () => {      const { port } = server.address();      server.close(() => resolve(port));    });  });} export const readJson = (path) => JSON.parse(readFileSync(path, "utf8")); /** Pretty-printed with a trailing newline, the shape every tracked JSON file uses. */export function writeJson(path, value) {  writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);} export const sha256 = (data) => createHash("sha256").update(data).digest("hex"); /** Run a command with inherited stdio. */export function run(command, args, options = {}) {  return execFileSync(command, args, { stdio: "inherit", ...options });} /** Run a command and return its stdout as text; stderr is captured into the error. */export function capture(command, args, options = {}) {  return execFileSync(command, args, {    encoding: "utf8",    stdio: ["ignore", "pipe", "pipe"],    // Binary Git patches easily exceed Node's 1 MiB default.    maxBuffer: 64 * 1024 * 1024,    ...options,  });} /** * Every regular file below `root`; [] when it is missing. Symbolic links are * skipped, not followed (unlike `readdirSync`'s `recursive` option): in a pnpm * or npm tree they point back at files that are already being visited. */export function walkFiles(root, files = []) {  if (!existsSync(root)) return files;  for (const entry of readdirSync(root, { withFileTypes: true })) {    const path = join(root, entry.name);    if (entry.isDirectory()) walkFiles(path, files);    else if (entry.isFile()) files.push(path);  }  return files;} export const treeBytes = (root) => walkFiles(root).reduce((total, path) => total + statSync(path).size, 0); /** Delete a file or directory tree and return the bytes it held (0 when it is missing). */export function removePath(path) {  if (!existsSync(path)) return 0;  const bytes = statSync(path).isDirectory() ? treeBytes(path) : statSync(path).size;  rmSync(path, { recursive: true, force: true });  return bytes;}