feat(佈署): 以 npm 裝出 tea-sdlc 指令並產生各平台轉接檔
單一入口 bin/tea-sdlc.js 認四個子指令。第一個位置參數是子指令,其餘 argv 原樣 交出去——既有的 flag 解析拒絕位置參數,所以子指令必須在那之前就被取走。 轉接檔裡沒有路徑,只有一句 tea-sdlc prompt --name <指令名>。正本在哪由 PATH 上 的 tea-sdlc 自己回推:fnm 把 Node 版號寫進全域安裝路徑,寫死路徑的話升一次 Node,七個平台的轉接檔會同時指向不存在的檔案,而且不會有任何錯誤訊息。 prompt 是全專案唯一輸出非 JSON 的路徑,理由只有一個:它的輸出要餵給模型讀。 失敗仍走 envelope——成功是內容,失敗才需要結構。 status 的 ok 不兼差表達環境好壞,健康與否放在 data.healthy:呼叫端要分得出 「status 掛了」與「status 成功查到你環境有問題」。 install 只寫進偵測得到的平台;缺 git/tea 只警告不中止,因為那兩個完全不影響 轉接檔產生,硬擋等於逼使用者為了裝 plugin 先去裝 tea。uninstall 只刪帶產生標記 的檔案,使用者自己寫的同名檔案一律留著並在輸出裡交代。裝哪些指令以 prompts/ 裡 實際存在的正本為準,不是寫死的六個名字——裝出指向不存在正本的轉接檔,使用者只會 看到 PROMPT_NOT_FOUND。 流程正本的 description 前綴在抄進轉接檔之前就檢查:有三個平台關不掉自動觸發, 全靠那句話把 description 窄到不會被誤判,不能等使用者發現誤觸才知道漏了。 議題 #26 #27 #28 #29 #17 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* status 子指令:在出事之前先知道自己是什麼狀態。
|
||||
*
|
||||
* 這裡最要緊的一條是 ok 與健康狀態分離:ok 的意思一直是「這支指令跑成功了」,
|
||||
* 不兼差表達環境好壞。混用的話呼叫端就分不出「status 掛了」與「status 成功查到
|
||||
* 你環境有問題」,而這兩件事的下一步完全不同。
|
||||
*/
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { join } from 'node:path';
|
||||
import { manifest, pathWithOnly, repoRoot, runBin } from './helpers/run-script.js';
|
||||
import { healthyRoutes, stubEnv, withStubGitea } from './helpers/stub-gitea.js';
|
||||
|
||||
const REPO = 'plugins/tea-sdlc';
|
||||
const version = () => manifest().version;
|
||||
|
||||
|
||||
test('回報版本、正本位置、是不是 link 模式、健康旗標與環境四項', async (t) => {
|
||||
const stub = await withStubGitea(t, healthyRoutes(REPO));
|
||||
|
||||
const { code, json } = await runBin(['status'], { env: stubEnv(stub) });
|
||||
|
||||
assert.equal(code, 0);
|
||||
assert.equal(json.ok, true);
|
||||
assert.equal(json.data.version, version());
|
||||
assert.equal(json.data.root, repoRoot);
|
||||
assert.equal(json.data.healthy, true);
|
||||
assert.deepEqual(Object.keys(json.data.environment).sort(), ['git', 'login', 'node', 'tea']);
|
||||
});
|
||||
|
||||
test('從 working tree 跑的時候是 link 模式,root 指向 working tree', async (t) => {
|
||||
const stub = await withStubGitea(t, healthyRoutes(REPO));
|
||||
|
||||
const { json } = await runBin(['status'], { env: stubEnv(stub) });
|
||||
|
||||
assert.equal(json.data.linked, true);
|
||||
assert.equal(json.data.root.includes('node_modules'), false);
|
||||
});
|
||||
|
||||
test('缺少 git 或 tea 時對應欄位為 false,健康旗標跟著倒下', async (t) => {
|
||||
const stub = await withStubGitea(t, healthyRoutes(REPO));
|
||||
|
||||
const { code, json } = await runBin(['status'], {
|
||||
env: stubEnv(stub),
|
||||
path: pathWithOnly(['node']),
|
||||
});
|
||||
|
||||
// 查詢本身仍然成功——它成功查到你的環境有問題
|
||||
assert.equal(code, 0);
|
||||
assert.equal(json.ok, true);
|
||||
assert.equal(json.data.environment.node, true);
|
||||
assert.equal(json.data.environment.git, false);
|
||||
assert.equal(json.data.environment.tea, false);
|
||||
assert.equal(json.data.healthy, false);
|
||||
});
|
||||
|
||||
test('Gitea 登入失效時 ok 仍為 true,healthy 為 false', async (t) => {
|
||||
const stub = await withStubGitea(t, {
|
||||
...healthyRoutes(REPO),
|
||||
'GET /api/v1/user': { status: 401, body: { message: 'token expired' } },
|
||||
});
|
||||
|
||||
const { code, json } = await runBin(['status'], { env: stubEnv(stub) });
|
||||
|
||||
assert.equal(code, 0);
|
||||
assert.equal(json.ok, true);
|
||||
assert.equal(json.data.environment.login, false);
|
||||
assert.equal(json.data.healthy, false);
|
||||
});
|
||||
|
||||
test('登入檢查走既有的單一 HTTP 出口,打的是 /user', async (t) => {
|
||||
const stub = await withStubGitea(t, healthyRoutes(REPO));
|
||||
|
||||
await runBin(['status'], { env: stubEnv(stub) });
|
||||
|
||||
assert.deepEqual(
|
||||
stub.requests.map((request) => `${request.method} ${request.path}`),
|
||||
['GET /api/v1/user'],
|
||||
);
|
||||
assert.equal(stub.requests[0].authorization, 'token stub-token');
|
||||
});
|
||||
|
||||
test('根本還沒登入時也只是 login 為 false,不會讓整支指令失敗', async (t) => {
|
||||
const { code, json } = await runBin(['status'], {
|
||||
env: { TEA_SDLC_CONFIG: join(repoRoot, '.tmp', 'no-such-tea-config.yml') },
|
||||
});
|
||||
|
||||
assert.equal(code, 0);
|
||||
assert.equal(json.ok, true);
|
||||
assert.equal(json.data.environment.login, false);
|
||||
assert.equal(json.data.healthy, false);
|
||||
});
|
||||
Reference in New Issue
Block a user