From 49ac11b82922ded49874edcf83234523e485a5c4 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 2 Jun 2026 09:27:31 +0000 Subject: [PATCH] fix: tee artifact download progress live --- ...fact-registry-ssh-timeout-contract-test.ts | 2 +- scripts/src/artifact-registry.ts | 19 ++++++++++++------- scripts/src/command.ts | 6 ++---- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/scripts/artifact-registry-ssh-timeout-contract-test.ts b/scripts/artifact-registry-ssh-timeout-contract-test.ts index 48bc1d77..640da462 100644 --- a/scripts/artifact-registry-ssh-timeout-contract-test.ts +++ b/scripts/artifact-registry-ssh-timeout-contract-test.ts @@ -17,7 +17,7 @@ assertCondition(source.includes("runRemoteScriptBackground(options, remoteScript assertCondition(source.includes('runRemoteScriptBackground(options, deployScript, Math.max(options.timeoutMs, 420_000), "d601-k3s-deploy")'), "D601 k3s deploy must use background polling"); assertCondition(source.includes('"ssh",\n options.providerId,\n "download"'), "download helper must route through UniDesk ssh download"); assertCondition(downloadRemoteFileSource.includes('"--chunk-bytes",\n "64000"'), "artifact ssh download must use a mid-size bounded chunk, not the largest chunk"); -assertCondition(downloadRemoteFileSource.includes("teeStderrFile: process.env.UNIDESK_JOB_STDERR_FILE"), "artifact ssh download must stream progress stderr into async job logs"); +assertCondition(downloadRemoteFileSource.includes("tee -a") && downloadRemoteFileSource.includes("UNIDESK_JOB_STDERR_FILE"), "artifact ssh download must stream progress stderr into async job logs"); assertCondition(source.includes("UNIDESK_SSH_CLIENT_TOKEN") && source.includes("UNIDESK_SSH_CLIENT_ROUTE_ALLOWLIST"), "dev frontend artifact deploy must sync scoped ssh runtime keys"); console.log(JSON.stringify({ diff --git a/scripts/src/artifact-registry.ts b/scripts/src/artifact-registry.ts index 3fb3fe34..ebcde892 100644 --- a/scripts/src/artifact-registry.ts +++ b/scripts/src/artifact-registry.ts @@ -1671,7 +1671,7 @@ function combineCommandResults(command: string[], parts: CommandResult[]): Comma } function downloadRemoteFile(options: ArtifactRegistryOptions, remotePath: string, localPath: string, timeoutMs = options.timeoutMs): CommandResult { - const result = runCommand([ + const command = [ process.execPath, "scripts/cli.ts", "ssh", @@ -1681,12 +1681,17 @@ function downloadRemoteFile(options: ArtifactRegistryOptions, remotePath: string "64000", remotePath, localPath, - ], repoRoot, { - timeoutMs, - teeStdoutFile: process.env.UNIDESK_JOB_STDOUT_FILE, - teeStderrFile: process.env.UNIDESK_JOB_STDERR_FILE, - }); - return result; + ]; + const stdoutFile = process.env.UNIDESK_JOB_STDOUT_FILE; + const stderrFile = process.env.UNIDESK_JOB_STDERR_FILE; + if (!stdoutFile || !stderrFile) return runCommand(command, repoRoot, { timeoutMs }); + return runCommand([ + "bash", + "-lc", + `set -o pipefail; "$@" > >(tee -a ${shellQuote(stdoutFile)}) 2> >(tee -a ${shellQuote(stderrFile)} >&2)`, + "unidesk-artifact-download", + ...command, + ], repoRoot, { timeoutMs }); } async function runRemoteScriptBackground( diff --git a/scripts/src/command.ts b/scripts/src/command.ts index 331c67a5..92f5b8c0 100644 --- a/scripts/src/command.ts +++ b/scripts/src/command.ts @@ -1,5 +1,5 @@ import { spawn, spawnSync } from "node:child_process"; -import { appendFileSync, closeSync, createWriteStream, existsSync, openSync, readSync, statSync } from "node:fs"; +import { closeSync, createWriteStream, existsSync, openSync, readSync, statSync } from "node:fs"; export interface CommandResult { command: string[]; @@ -11,7 +11,7 @@ export interface CommandResult { timedOut: boolean; } -export function runCommand(command: string[], cwd: string, options: { timeoutMs?: number; env?: NodeJS.ProcessEnv; teeStdoutFile?: string; teeStderrFile?: string } = {}): CommandResult { +export function runCommand(command: string[], cwd: string, options: { timeoutMs?: number; env?: NodeJS.ProcessEnv } = {}): CommandResult { const result = spawnSync(command[0], command.slice(1), { cwd, encoding: "utf8", @@ -20,9 +20,7 @@ export function runCommand(command: string[], cwd: string, options: { timeoutMs? timeout: options.timeoutMs, }); const error = result.error as (Error & { code?: string }) | undefined; - if (options.teeStdoutFile !== undefined && result.stdout !== undefined && result.stdout.length > 0) appendFileSync(options.teeStdoutFile, result.stdout, "utf8"); const stderr = result.stderr ?? error?.message ?? ""; - if (options.teeStderrFile !== undefined && stderr.length > 0) appendFileSync(options.teeStderrFile, stderr, "utf8"); return { command, cwd,