fix(lib): git 的進度訊息不再漏到 stderr,並讓前置檢查交回帳號

兩件都是 branch-prep 與 claim 落地時才浮現的既有缺口:

git 把 checkout/fetch 的進度訊息全寫在 stderr,而 execFileSync 預設讓 stderr 直接
繼承給父行程。腳本的輸出契約是「stdout 一行 JSON、stderr 乾淨」,不收的話呼叫端還得
自己分辨哪幾行是雜訊。改成收進來;失敗時這些內容仍讀得到,錯誤訊息不會因此變模糊。

preflight 的第二層本來就打過 /user 問「我是誰」,卻只把答案丟掉,害呼叫端要再問一次。
改為連同 repo 資訊一起交回去。原本沒有任何呼叫端在用它的回傳值,形狀改變是安全的。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-17 07:06:59 +00:00
co-authored by Claude Opus 5
parent dcbd71c828
commit f524f17375
+14 -6
View File
@@ -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<object>} 通過時回傳 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) {