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:
+14
-6
@@ -365,7 +365,14 @@ function safeJson(text) {
|
|||||||
*/
|
*/
|
||||||
export function runGit(args, { cwd } = {}) {
|
export function runGit(args, { cwd } = {}) {
|
||||||
try {
|
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) {
|
} catch (error) {
|
||||||
const detail = (error.stderr || error.message || '').toString().trim();
|
const detail = (error.stderr || error.message || '').toString().trim();
|
||||||
throw new ScriptError('GIT_FAILED', `git ${args.join(' ')} 失敗:${detail}`);
|
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 {{base: string, token: string}} login
|
||||||
* @param {string} repo owner/name
|
* @param {string} repo owner/name
|
||||||
* @returns {Promise<object>} 通過時回傳 repo 資訊,省下呼叫端再查一次
|
* @returns {Promise<{repo: object, user: object}>} 通過時把查到的 repo 與帳號一起交回去,
|
||||||
|
* 省下呼叫端再查一次——第二層本來就已經問過「我是誰」了
|
||||||
*/
|
*/
|
||||||
export async function preflight(login, repo) {
|
export async function preflight(login, repo) {
|
||||||
checkEnvironment();
|
checkEnvironment();
|
||||||
await checkLogin(login);
|
const user = await checkLogin(login);
|
||||||
const info = await fetchRepo(login, repo);
|
const info = await fetchRepo(login, repo);
|
||||||
await checkIssueWrite(login, repo, info);
|
await checkIssueWrite(login, repo, info);
|
||||||
checkTimeTracker(info);
|
checkTimeTracker(info);
|
||||||
return info;
|
return { repo: info, user };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 第一層:執行環境。node 由「正在執行」本身證明,git 與 tea 則實際到 PATH 上找。 */
|
/** 第一層:執行環境。node 由「正在執行」本身證明,git 與 tea 則實際到 PATH 上找。 */
|
||||||
@@ -458,7 +466,7 @@ export function onPath(binary) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 第二層:Gitea 登入是否有效 */
|
/** 第二層:Gitea 登入是否有效。順手把帳號交出去,呼叫端常要知道「我是誰」。 */
|
||||||
async function checkLogin(login) {
|
async function checkLogin(login) {
|
||||||
const response = await giteaRequest(login, 'GET', '/user');
|
const response = await giteaRequest(login, 'GET', '/user');
|
||||||
if (response.status === 401 || response.status === 403) {
|
if (response.status === 401 || response.status === 403) {
|
||||||
@@ -467,7 +475,7 @@ async function checkLogin(login) {
|
|||||||
`Gitea 不接受目前的 token(HTTP ${response.status});請重新執行 tea login add 更新登入`,
|
`Gitea 不接受目前的 token(HTTP ${response.status});請重新執行 tea login add 更新登入`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
expectOk(response, 'GET /user');
|
return expectOk(response, 'GET /user');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchRepo(login, repo) {
|
async function fetchRepo(login, repo) {
|
||||||
|
|||||||
Reference in New Issue
Block a user