From f524f17375b79904c576a03975a221e4105beef7 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Thu, 17 Sep 2026 07:06:59 +0000 Subject: [PATCH] =?UTF-8?q?fix(lib):=20git=20=E7=9A=84=E9=80=B2=E5=BA=A6?= =?UTF-8?q?=E8=A8=8A=E6=81=AF=E4=B8=8D=E5=86=8D=E6=BC=8F=E5=88=B0=20stderr?= =?UTF-8?q?=EF=BC=8C=E4=B8=A6=E8=AE=93=E5=89=8D=E7=BD=AE=E6=AA=A2=E6=9F=A5?= =?UTF-8?q?=E4=BA=A4=E5=9B=9E=E5=B8=B3=E8=99=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 兩件都是 branch-prep 與 claim 落地時才浮現的既有缺口: git 把 checkout/fetch 的進度訊息全寫在 stderr,而 execFileSync 預設讓 stderr 直接 繼承給父行程。腳本的輸出契約是「stdout 一行 JSON、stderr 乾淨」,不收的話呼叫端還得 自己分辨哪幾行是雜訊。改成收進來;失敗時這些內容仍讀得到,錯誤訊息不會因此變模糊。 preflight 的第二層本來就打過 /user 問「我是誰」,卻只把答案丟掉,害呼叫端要再問一次。 改為連同 repo 資訊一起交回去。原本沒有任何呼叫端在用它的回傳值,形狀改變是安全的。 Co-Authored-By: Claude Opus 5 (1M context) --- scripts/lib.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/scripts/lib.js b/scripts/lib.js index 3ecca47..9380f47 100644 --- a/scripts/lib.js +++ b/scripts/lib.js @@ -365,7 +365,14 @@ function safeJson(text) { */ export function runGit(args, { cwd } = {}) { try { - return execFileSync('git', args, { cwd, encoding: 'utf8' }).trim(); + // stderr 要收進來而不是繼承:git 把 checkout/fetch 的進度訊息全寫在 stderr, + // 不收的話它們會混進腳本的輸出,而腳本的契約是「stdout 一行 JSON、stderr 乾淨」。 + // 失敗時這些內容仍讀得到(error.stderr),錯誤訊息不會因此變模糊。 + return execFileSync('git', args, { + cwd, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + }).trim(); } catch (error) { const detail = (error.stderr || error.message || '').toString().trim(); throw new ScriptError('GIT_FAILED', `git ${args.join(' ')} 失敗:${detail}`); @@ -379,15 +386,16 @@ export function runGit(args, { cwd } = {}) { * 前一層沒過就不往下打,避免把一個設定問題報成四個。 * @param {{base: string, token: string}} login * @param {string} repo owner/name - * @returns {Promise} 通過時回傳 repo 資訊,省下呼叫端再查一次 + * @returns {Promise<{repo: object, user: object}>} 通過時把查到的 repo 與帳號一起交回去, + * 省下呼叫端再查一次——第二層本來就已經問過「我是誰」了 */ export async function preflight(login, repo) { checkEnvironment(); - await checkLogin(login); + const user = await checkLogin(login); const info = await fetchRepo(login, repo); await checkIssueWrite(login, repo, info); checkTimeTracker(info); - return info; + return { repo: info, user }; } /** 第一層:執行環境。node 由「正在執行」本身證明,git 與 tea 則實際到 PATH 上找。 */ @@ -458,7 +466,7 @@ export function onPath(binary) { return null; } -/** 第二層:Gitea 登入是否有效 */ +/** 第二層:Gitea 登入是否有效。順手把帳號交出去,呼叫端常要知道「我是誰」。 */ async function checkLogin(login) { const response = await giteaRequest(login, 'GET', '/user'); if (response.status === 401 || response.status === 403) { @@ -467,7 +475,7 @@ async function checkLogin(login) { `Gitea 不接受目前的 token(HTTP ${response.status});請重新執行 tea login add 更新登入`, ); } - expectOk(response, 'GET /user'); + return expectOk(response, 'GET /user'); } async function fetchRepo(login, repo) {