Spaces:
Running
Running
| import { spawnSync } from 'node:child_process'; | |
| import { resolve } from 'node:path'; | |
| import { pathToFileURL } from 'node:url'; | |
| const DEFAULT_MAX_BUFFER = 20 * 1024 * 1024; | |
| const DEFAULT_FETCH_TIMEOUT_MS = 5000; | |
| export function isMainModule(moduleUrl, argvPath) { | |
| return Boolean(argvPath) && moduleUrl === pathToFileURL(resolve(argvPath)).href; | |
| } | |
| export function runCommand(command, args = [], options = {}) { | |
| const startedAt = Date.now(); | |
| const result = spawnSync(command, args, { | |
| cwd: options.cwd, | |
| encoding: options.encoding || 'utf8', | |
| env: options.env, | |
| input: options.input, | |
| maxBuffer: options.maxBuffer || DEFAULT_MAX_BUFFER, | |
| stdio: ['pipe', 'pipe', 'pipe'], | |
| timeout: options.timeoutMs | |
| }); | |
| const elapsedMs = Date.now() - startedAt; | |
| if (result.error) { | |
| return { | |
| ok: false, | |
| command, | |
| args, | |
| status: result.status, | |
| stdout: String(result.stdout || ''), | |
| stderr: String(result.stderr || ''), | |
| elapsed_ms: elapsedMs, | |
| error: result.error.message, | |
| signal: result.signal | |
| }; | |
| } | |
| return { | |
| ok: result.status === 0, | |
| command, | |
| args, | |
| status: result.status, | |
| stdout: String(result.stdout || ''), | |
| stderr: String(result.stderr || ''), | |
| elapsed_ms: elapsedMs, | |
| signal: result.signal | |
| }; | |
| } | |
| export function runCommandStrict(command, args = [], options = {}) { | |
| const result = runCommand(command, args, options); | |
| if (!result.ok) throw new Error(commandFailureMessage(result)); | |
| return result.stdout; | |
| } | |
| export function commandFailureMessage(result) { | |
| return pickFailureOutput(result) || `${result.command} ${result.args.join(' ')} failed`; | |
| } | |
| export function printJson(payload) { | |
| console.log(JSON.stringify(payload, null, 2)); | |
| } | |
| export function parseJsonPayload(output, label = 'command') { | |
| const lines = output.split(/\r?\n/); | |
| let lastError; | |
| let sawCandidate = false; | |
| for (let start = 0; start < lines.length; start += 1) { | |
| const trimmed = lines[start].trimStart(); | |
| if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) continue; | |
| sawCandidate = true; | |
| for (let end = lines.length; end > start; end -= 1) { | |
| try { | |
| return JSON.parse(lines.slice(start, end).join('\n')); | |
| } catch (error) { | |
| lastError = error; | |
| } | |
| } | |
| } | |
| if (!sawCandidate) throw new Error(`${label} did not return JSON output.`); | |
| throw new Error(`${label} returned invalid JSON: ${lastError instanceof Error ? lastError.message : String(lastError)}`); | |
| } | |
| export function redactBaseUrl(value) { | |
| const parsed = new URL(value); | |
| const path = parsed.pathname === '/' ? '' : parsed.pathname.replace(/\/+$/, ''); | |
| return `${parsed.protocol}//${parsed.host}${path}`; | |
| } | |
| export async function fetchJsonWithTimeout(url, options = {}) { | |
| const timeoutMs = options.timeoutMs || DEFAULT_FETCH_TIMEOUT_MS; | |
| const pathname = safeUrlPathname(url); | |
| const controller = new AbortController(); | |
| const timer = setTimeout(() => controller.abort(), timeoutMs); | |
| try { | |
| const response = await fetch(url, { signal: controller.signal }); | |
| const text = await response.text(); | |
| const bodySnippet = formatResponseBodySnippet(text); | |
| if (!response.ok) throw new Error(`${pathname} failed with HTTP ${response.status}${bodySnippet}`); | |
| try { | |
| return JSON.parse(text); | |
| } catch (error) { | |
| if (error instanceof SyntaxError) throw new Error(`${pathname} did not return JSON${bodySnippet}`); | |
| throw error; | |
| } | |
| } catch (error) { | |
| if (error?.name === 'AbortError') throw new Error(`${pathname} timed out after ${timeoutMs}ms.`); | |
| throw error; | |
| } finally { | |
| clearTimeout(timer); | |
| } | |
| } | |
| function formatResponseBodySnippet(text) { | |
| if (!text || process.env.NODE_ENV === 'production') return ''; | |
| return `: ${text.slice(0, 100)}`; | |
| } | |
| function safeUrlPathname(url) { | |
| try { | |
| return new URL(url).pathname; | |
| } catch { | |
| return String(url); | |
| } | |
| } | |
| export function pickFailureOutput(result, maxLength = 4000) { | |
| const output = [ | |
| result.stdout, | |
| result.stderr, | |
| result.status !== undefined && result.status !== null ? `status: ${result.status}` : '', | |
| result.error ? `error: ${result.error}` : '', | |
| result.signal ? `signal: ${result.signal}` : '' | |
| ] | |
| .filter(Boolean) | |
| .join('\n') | |
| .trim(); | |
| return output.length > maxLength ? `${output.slice(0, maxLength)}...` : output; | |
| } | |