execaSync 启动失败后 stdout 是 undefined,错误处理又报错
Execa10.0.0同步启动失败时可能返回undefined输出,掩盖原来的ENOENT。采用含修正的正式版本,并按文本、字节或行数组读取错误输出。
Execa 同步执行外部程序失败后,错误处理又在 error.stdout.includes(...) 上抛出 TypeError;异步执行相同目标却能得到空字符串。先区分“子进程启动失败”和“子进程运行后没有输出”,再核对安装版本与输出选项。
Execa 10.0.0 的同步路径中,Node 的 spawnSync 启动失败会给出 output 为 null,Execa 把它变成若干 undefined。捕获原来的 ENOENT 以后直接把 stdout 当字符串,因而发生第二次错误,掩盖真正的启动原因。
先处理原错误,不从空输出来猜启动状态
程序路径不存在和 cwd 不存在,都可能导致启动阶段 ENOENT。与之不同,程序已经启动、随后返回非零退出码,可能有可用的 stdout、stderr。输出为空本身不能证明进程是否启动。
若错误指向路径或工作目录,应按Node 的 execFile 路径与 cwd 检查核对实际运行环境;不要为了让 includes 不报错,就忽略原来的启动失败。
当前正式 Execa 10.2.1 已在同步输出处理里为 spawn 失败构造空的管道输出,再依照 encoding、lines、buffer 等选项转换。10.2.0 的正式实现也已包含该处理;已使用这一版本的项目应继续检查实际加载的副本与输出选项。
错误输出要按约定的类型读取
在默认文本捕获、reject 为默认值的调用中,可以把原始启动错误与可用文本分别处理。以下函数接收受控程序路径、参数和已有 options,保留其他选项并显式关闭 shell:
import { execaSync } from 'execa';
export function runTool(file, args, options) {
try {
return execaSync(file, args, { ...options, shell: false });
} catch (error) {
if (error && typeof error === 'object' && error.code === 'ENOENT') {
throw error;
}
const stderr = typeof error?.stderr === 'string' ? error.stderr : undefined;
if (stderr !== undefined && stderr.includes('known diagnostic')) {
// 进入应用原有的这类诊断处理,不把命令失败改成成功。
reportKnownDiagnostic(stderr);
}
throw error;
}
}
reportKnownDiagnostic 代表应用已有的记录入口,诊断文字应按数据规则脱敏。示例不会为 ENOENT 生成假的命令输出,也不会自动重试。
使用 encoding: 'buffer' 时,捕获输出是字节数组;lines 模式可能是数组。stdout: 'ignore'、继承输出或关闭 buffer 时,undefined 仍可能是正常约定。不要为了统一处理把这些值全部 String() 化:数组会变成逗号分隔文字,undefined 会变成一个看似真实的单词。
核对实际副本和失败入口
升级后确认应用实际解析到的 Execa 版本,以及调用是否经过另一个包装工具。用受控环境分别检查不存在的程序、不存在的 cwd、正常空输出和非零退出码。再覆盖本项目真正使用的文本、字节或行数组配置,按各自类型验收。
选项本身非法、导致启动函数立即抛错的路径,与进程无法生成的路径也可能有不同结果,不能把这次输出修正扩大成“任何错误都有字符串 stdout”。
同步调用仍会阻塞当前线程;输出修复不改变这个特性。对可能有副作用的命令,运行失败或输出缺失都不表示没有外部修改,重试前仍要核对业务完成状态。


