feat(install): 安裝完成等於驗過能用
install 寫完轉接檔後,把叫用鏈真的走一遍:轉接檔 → PATH 上的 tea-sdlc → 流程正本。
最脆弱的是中間那一環。套件裝在某個 Node 版本底下,換個版本就找不到了,而轉接檔本身
看起來完全正常——沒有這道驗證,使用者要到第一次打 /sdlc-plan 才發現,那時他已經離開
安裝的心智狀態很久了。所以不是查檔案在不在,而是真的到 PATH 上把 tea-sdlc 找出來執行
一次,再把取回的正本跟套件裡的那一份逐字比對:找不到、叫不動、或叫到的是另一份安裝,
三種都驗得出來。轉接檔則逐一回磁碟讀,比對存在且內容含正確的叫用行。
驗證不碰網路,也與 Gitea 登入、時間追蹤無關,所以無條件執行。
驗不過回 ok:false,但已經寫好的轉接檔一份都不刪。回滾在升級情境下是淨損失:原本有一組
能用的舊轉接檔,覆蓋後驗證失敗再刪掉,使用者就從「有點舊但能用」變成什麼都沒有;何況
最可能的病灶是「PATH 上找不到 tea-sdlc」,那不是轉接檔的問題。
為此 lib 多一個 Failure:有一種失敗是事情做完了、檔案也寫出去了,只是驗不過,那時最該
交出去的正是「已經寫了哪些、哪一段不通」。envelope 形狀不變,只是 {ok:false, error}
旁邊多一個 data,只讀 error.code 的呼叫端照常運作。
--dry-run 不寫入,也就沒有東西可驗,verify 標成 skipped。
Closes #59
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+31
-2
@@ -217,11 +217,35 @@ export class RawText {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 失敗,但手上的東西還是要交出去。
|
||||
*
|
||||
* 丟 ScriptError 的失敗只剩 code 與 message,因為那種失敗通常是「什麼都還沒做」。
|
||||
* 有一種失敗不是這樣:事情做完了、檔案也寫出去了,只是驗不過。那時使用者最需要
|
||||
* 知道的正是「已經寫了哪些、哪一個平台不通」,把 data 丟掉等於逼他自己去翻。
|
||||
*
|
||||
* envelope 形狀不變,只是 {ok:false, error} 旁邊多一個 data:只讀 error.code 的
|
||||
* 呼叫端照常運作。
|
||||
*/
|
||||
export class Failure {
|
||||
/**
|
||||
* @param {string} code 可區分的錯誤碼
|
||||
* @param {string} message 給人看的訊息,要說得出病灶與修復方式
|
||||
* @param {object} data 已經做完的部分,原樣放進 envelope
|
||||
*/
|
||||
constructor(code, message, data) {
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 每支腳本與指令入口的進入點:跑完印一行 JSON 就結束,例外一律收斂成 {ok:false}。
|
||||
* stderr 永遠保持乾淨,呼叫端只需要讀 stdout。
|
||||
* 回傳 RawText 時改印原樣內容,不包 envelope,其餘行為不變。
|
||||
* @param {() => Promise<object|RawText>|object|RawText} run 回傳要放進 data 的物件
|
||||
* 回傳 RawText 時改印原樣內容,不包 envelope;回傳 Failure 時印 {ok:false} 並退出碼 1,
|
||||
* 但把 data 一起帶出去。其餘行為不變。
|
||||
* @param {() => Promise<object|RawText|Failure>|object|RawText|Failure} run 回傳要放進 data 的物件
|
||||
*/
|
||||
export async function main(run) {
|
||||
try {
|
||||
@@ -230,6 +254,11 @@ export async function main(run) {
|
||||
write(data.text, 0);
|
||||
return;
|
||||
}
|
||||
if (data instanceof Failure) {
|
||||
const { code, message } = data;
|
||||
write(`${JSON.stringify({ ok: false, error: { code, message }, data: data.data })}\n`, 1);
|
||||
return;
|
||||
}
|
||||
write(`${JSON.stringify({ ok: true, data })}\n`, 0);
|
||||
} catch (error) {
|
||||
const code = error instanceof ScriptError ? error.code : 'UNEXPECTED';
|
||||
|
||||
Reference in New Issue
Block a user