diff --git a/scripts/release/process.ts b/scripts/release/process.ts index 746f24ac36..acec98feae 100644 --- a/scripts/release/process.ts +++ b/scripts/release/process.ts @@ -38,6 +38,32 @@ export function attempt(command: string, args: readonly string[], options: RunOp return { status: result.status, stdout: result.stdout, stderr: result.stderr } } +/** + * Run a command, letting its output reach the log while also returning it. + * + * A step that both shows progress and classifies its own failure needs both: the + * output has to appear in the workflow log as the command produces it, and the + * caller has to read it to decide whether a failure is worth retrying. + * @param command - executable name. + * @param args - command arguments. + * @param options - working directory and environment. + * @returns The exit status and captured streams. + */ +export function attemptStreaming(command: string, args: readonly string[], options: RunOptions = {}): CommandResult { + const result = spawnSync(command, [...args], { + cwd: options.cwd, + env: options.env, + encoding: 'utf8', + // 'inherit' would leave nothing to capture, so the streams are piped and + // echoed instead. + stdio: ['inherit', 'pipe', 'pipe'], + }) + if (result.error !== undefined) throw result.error + if (result.stdout !== '') process.stdout.write(result.stdout) + if (result.stderr !== '') process.stderr.write(result.stderr) + return { status: result.status, stdout: result.stdout, stderr: result.stderr } +} + /** * Run a command, capture its standard output, and fail on a non-zero exit. * @param command - executable name. diff --git a/scripts/release/publish.ts b/scripts/release/publish.ts index 11ad01173c..f861da18c2 100644 --- a/scripts/release/publish.ts +++ b/scripts/release/publish.ts @@ -18,7 +18,7 @@ import { join, resolve } from 'node:path' import { setTimeout as sleep } from 'node:timers/promises' import { parseArgs } from 'node:util' import { releaseFamily } from './families.ts' -import { attempt, isEntry } from './process.ts' +import { attempt, attemptStreaming, isEntry } from './process.ts' import { packedIdentity, readPublishOrder } from './tarball.ts' /** @@ -102,7 +102,7 @@ async function publishTarball(tarball: string, name: string, version: string): P // command-line flag could not serve both and would override the manifest // that does. Each packed manifest decides, and // check-workspace-constraints holds every manifest to its sequence's level. - const result = attempt('npm', ['publish', tarball, ...tagArgs]) + const result = attemptStreaming('npm', ['publish', tarball, ...tagArgs]) const output = `${result.stdout}${result.stderr}` if (result.status === 0) return