From 6c2d54079d3ae4a7f689ccf2be5604e65a0ed4c8 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 31 Jul 2026 13:53:05 +0100 Subject: [PATCH] Move subprocess helpers from `update-release-branch.ts` to `command.ts` --- pr-checks/command.ts | 81 +++++++++++++++++++++++++++ pr-checks/update-release-branch.ts | 88 +----------------------------- 2 files changed, 84 insertions(+), 85 deletions(-) create mode 100644 pr-checks/command.ts diff --git a/pr-checks/command.ts b/pr-checks/command.ts new file mode 100644 index 000000000..459265c0d --- /dev/null +++ b/pr-checks/command.ts @@ -0,0 +1,81 @@ +import { execFileSync, ExecFileSyncOptions } from "node:child_process"; + +import { DryRunOption, REPO_ROOT } from "./config"; + +/** Options for {@link runCommand}. */ +export interface RunCommandOptions extends DryRunOption { + /** Options for `execFileSync`. */ + execOptions?: ExecFileSyncOptions; +} + +/** + * Runs a command, streaming output to the console by default. + * + * @param command The name of the command to run. + * @param args The arguments for the command. + * @throws When the process exits with a non-zero exit code. + * @param options How to run the command. + */ +export function runCommand( + command: string, + args: string[], + options?: RunCommandOptions, +) { + if (!options?.dryRun) { + console.log(`Running \`${command} ${args.join(" ")}\`.`); + return execFileSync(command, args, { + stdio: "inherit", + cwd: REPO_ROOT, + ...options?.execOptions, + }); + } else { + console.info( + `[DRY RUN] Would have executed '${command} ${args.join(" ")}'`, + ); + return ""; + } +} + +/** Options for {@link runGit}. */ +export interface RunGitOptions extends DryRunOption { + /** When true, non-zero exit codes will not throw. */ + allowNonZeroExitCode?: boolean; +} + +/** + * Runs `git` with the given `args` and returns the stdout. + * + * @param args - Arguments to pass to `git`. + * @param options - Optional settings. + * @throws If `git` does not exit successfully, unless + * `options.allowNonZeroExitCode` is `true`. + * @returns The trimmed stdout output. + */ +export function runGit(args: string[], options?: RunGitOptions): string { + const execOptions: ExecFileSyncOptions = { + encoding: "utf8", + stdio: ["pipe", "pipe", "pipe"], + }; + + try { + const result = runCommand("git", args, { + dryRun: options?.dryRun, + execOptions, + }) as string; + return result.trimEnd(); + } catch (error: unknown) { + if (options?.allowNonZeroExitCode) { + // execFileSync throws an object with `stdout` when the process exits + // with a non-zero code. + const execError = error as { stdout?: Buffer | string }; + if (typeof execError.stdout === "string") { + return execError.stdout.trimEnd(); + } + if (Buffer.isBuffer(execError.stdout)) { + return execError.stdout.toString("utf8").trimEnd(); + } + return ""; + } + throw error; + } +} diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 9d46fd3ec..ca76603a3 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -18,18 +18,14 @@ * [--dry-run] */ -import { execFileSync, type ExecFileSyncOptions } from "node:child_process"; +import { execFileSync } from "node:child_process"; import { parseArgs } from "node:util"; import { type ApiClient, getApiClient } from "./api-client"; import * as changelog from "./changelog"; import { transferChangenotesToChangelog } from "./changenotes"; -import { - CHANGELOG_FILE, - CHANGENOTES_DIR, - DryRunOption, - REPO_ROOT, -} from "./config"; +import { runCommand, runGit } from "./command"; +import { CHANGELOG_FILE, CHANGENOTES_DIR } from "./config"; import { getCurrentVersion, replaceVersionInPackageJson, @@ -71,84 +67,6 @@ export function getGitHubToken(): string { throw new Error("Missing GitHub token. Set GITHUB_TOKEN or GH_TOKEN."); } -/** Options for {@link runCommand}. */ -export interface RunCommandOptions extends DryRunOption { - /** Options for `execFileSync`. */ - execOptions?: ExecFileSyncOptions; -} - -/** - * Runs a command, streaming output to the console by default. - * - * @param command The name of the command to run. - * @param args The arguments for the command. - * @throws When the process exits with a non-zero exit code. - * @param options How to run the command. - */ -export function runCommand( - command: string, - args: string[], - options?: RunCommandOptions, -) { - if (!options?.dryRun) { - console.log(`Running \`${command} ${args.join(" ")}\`.`); - return execFileSync(command, args, { - stdio: "inherit", - cwd: REPO_ROOT, - ...options?.execOptions, - }); - } else { - console.info( - `[DRY RUN] Would have executed '${command} ${args.join(" ")}'`, - ); - return ""; - } -} - -/** Options for {@link runGit}. */ -export interface RunGitOptions extends DryRunOption { - /** When true, non-zero exit codes will not throw. */ - allowNonZeroExitCode?: boolean; -} - -/** - * Runs `git` with the given `args` and returns the stdout. - * - * @param args - Arguments to pass to `git`. - * @param options - Optional settings. - * @throws If `git` does not exit successfully, unless - * `options.allowNonZeroExitCode` is `true`. - * @returns The trimmed stdout output. - */ -export function runGit(args: string[], options?: RunGitOptions): string { - const execOptions: ExecFileSyncOptions = { - encoding: "utf8", - stdio: ["pipe", "pipe", "pipe"], - }; - - try { - const result = runCommand("git", args, { - dryRun: options?.dryRun, - execOptions, - }) as string; - return result.trimEnd(); - } catch (error: unknown) { - if (options?.allowNonZeroExitCode) { - // execFileSync throws an object with `stdout` when the process exits - // with a non-zero code. - const execError = error as { stdout?: Buffer | string }; - if (typeof execError.stdout === "string") { - return execError.stdout.trimEnd(); - } - if (Buffer.isBuffer(execError.stdout)) { - return execError.stdout.toString("utf8").trimEnd(); - } - return ""; - } - throw error; - } -} - /** Returns true if the given branch exists on the origin remote. */ export function branchExistsOnRemote(branchName: string): boolean { const result = runGit(["ls-remote", "--heads", ORIGIN, branchName]);