Skip to content

Commit 91e6c6f

Browse files
committed
refactor(runtime): resolve/middleware kernel + declarative arg validation
把 main 从一堆 if + process.exit 重构为「argv 解析成数据 → 交给统一管线执行」。 内核 - resolve(argv) → Resolution(version/help/run/usageError):路由即数据,dispatch 只 switch - compose 洋葱中间件 (versionCheck/telemetry/auth/runCommand),命令仍收 (config, flags) - registry.locate() 统一 leaf/group/unknown,取代 isGroupPath + 抛异常的 resolve - 删除 command-help.ts 全局可变单例:help 渲染收口到错误边界 错误模型 - 新增 UsageError(写错了 → exit 2) 与 IncompleteCommandError(没写完 → 打 help、exit 0) - version / help / onboarding / 组帮助统一由 resolve 产出、dispatch 分派 参数与校验 - parseFlags 重写:无 positional、新增 switch 类型、值/类型/重复校验 - 无条件必填 → 解析器声明式强制 (OptionDef.required) - 跨 flag / 条件约束 → 新增 command.validate(flags) 钩子 (text-chat / search-web / speech / vision / video-ref) - 移除全部交互式输入 (promptText/Select/Confirm),缺输入直接打 help 输出 - detectOutputFormat 默认 text,不再按 TTY 切 json 测试 - 删除 3 个 stale cli 测试,runtime 单测重写 (29 passed),e2e 适配新行为
1 parent 7a0a083 commit 91e6c6f

69 files changed

Lines changed: 843 additions & 1255 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/cli/tests/args.test.ts

Lines changed: 0 additions & 95 deletions
This file was deleted.

packages/cli/tests/e2e/config.e2e.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@ describe("e2e: config", () => {
6363
expect(stdout).toMatch(/config_file|timeout|base_url/i);
6464
});
6565

66-
test("config set 缺少 --key / --value 时退出为用法错误 (2)", async () => {
66+
test("config set 缺少 --key / --value 时打印子命令帮助并退出 (0)", async () => {
6767
const { stderr, exitCode } = await runCli(["config", "set", "--non-interactive"]);
68-
expect(exitCode).toBe(2);
69-
expect(stderr).toMatch(/--key|--value|required/i);
68+
expect(exitCode, stderr).toBe(0);
69+
expect(stderr).toMatch(/--key|--value|Usage:/i);
7070
});
7171

7272
test("config set 非法 key 时退出为用法错误", async () => {

packages/cli/tests/e2e/knowledge.e2e.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ describe("e2e: knowledge retrieve", () => {
6666
// ---- Error scenarios (no real credentials needed) ----
6767

6868
describe("e2e: knowledge retrieve errors", () => {
69-
test("无任何凭证时提示 No credentials found 并非零退出", async () => {
69+
test("无任何凭证时提示缺少密钥并非零退出", async () => {
7070
const { stderr, exitCode } = await runCli(
7171
[
7272
"knowledge",
@@ -88,7 +88,7 @@ describe("e2e: knowledge retrieve errors", () => {
8888
},
8989
);
9090
expect(exitCode).not.toBe(0);
91-
expect(stderr).toMatch(/no credentials found/i);
91+
expect(stderr).toMatch(/no api key found|no credentials found/i);
9292
});
9393
});
9494

packages/cli/tests/e2e/mcp.e2e.test.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@ describe("e2e: mcp", () => {
3333
test("mcp tools --help 正常退出", async () => {
3434
const { stderr, exitCode } = await runCli(["mcp", "tools", "--help"]);
3535
expect(exitCode, stderr).toBe(0);
36-
expect(stderr).toMatch(/tools|server-code|--url/i);
36+
expect(stderr).toMatch(/tools|--server|--url/i);
3737
});
3838

3939
test("mcp call --help 正常退出", async () => {
4040
const { stderr, exitCode } = await runCli(["mcp", "call", "--help"]);
4141
expect(exitCode, stderr).toBe(0);
42-
expect(stderr).toMatch(/call|server-code|tool|--arg|--json/i);
42+
expect(stderr).toMatch(/call|--target|--arg|--json/i);
4343
});
4444

4545
test("mcp list --help 不暴露 --all 入口(市场全量已下线)", async () => {
@@ -103,10 +103,11 @@ describe("e2e: mcp", () => {
103103
expect(data.consoleRegion).toBe("cn-hangzhou");
104104
});
105105

106-
test("mcp tools <server-code> --dry-run 输出 /api/v1/mcps/<code>/mcp 形态 URL", async () => {
106+
test("mcp tools --server <code> --dry-run 输出 /api/v1/mcps/<code>/mcp 形态 URL", async () => {
107107
const { stdout, stderr, exitCode } = await runCli([
108108
"mcp",
109109
"tools",
110+
"--server",
110111
"market-cmapi00073529",
111112
"--dry-run",
112113
"--non-interactive",
@@ -126,6 +127,7 @@ describe("e2e: mcp", () => {
126127
const { stdout, stderr, exitCode } = await runCli([
127128
"mcp",
128129
"tools",
130+
"--server",
129131
"my-server",
130132
"--url",
131133
"https://example.com/custom/mcp",
@@ -140,16 +142,17 @@ describe("e2e: mcp", () => {
140142
expect(data.url).toBe("https://example.com/custom/mcp");
141143
});
142144

143-
test("mcp tools 缺少 server-code 时打印子命令帮助并退出 (0)", async () => {
145+
test("mcp tools 缺少 --server 时打印子命令帮助并退出 (0)", async () => {
144146
const { stderr, exitCode } = await runCli(["mcp", "tools", "--non-interactive"]);
145147
expect(exitCode, stderr).toBe(0);
146-
expect(stderr).toMatch(/server-code|Usage:/i);
148+
expect(stderr).toMatch(/--server|Usage:/i);
147149
});
148150

149-
test("mcp call <server-code>.<tool> --dry-run 输出工具调用计划", async () => {
151+
test("mcp call --target <server.tool> --dry-run 输出工具调用计划", async () => {
150152
const { stdout, stderr, exitCode } = await runCli([
151153
"mcp",
152154
"call",
155+
"--target",
153156
"market-cmapi00073529.SmartStockSelection",
154157
"--query",
155158
"筛选ROE>15%的消费股",
@@ -176,6 +179,7 @@ describe("e2e: mcp", () => {
176179
const { stdout, stderr, exitCode } = await runCli([
177180
"mcp",
178181
"call",
182+
"--target",
179183
"market-cmapi00073529.FinQuery",
180184
"--json",
181185
'{"q":"贵州茅台","limit":5,"riskLevel":"R2"}',
@@ -208,10 +212,11 @@ describe("e2e: mcp", () => {
208212
expect(data.arguments?.query).toBe("招商银行");
209213
});
210214

211-
test("mcp call 目标缺少 . 时报错且非零退出", async () => {
215+
test("mcp call --target 缺少 . 时报错且非零退出", async () => {
212216
const { stderr, exitCode } = await runCli([
213217
"mcp",
214218
"call",
219+
"--target",
215220
"no-dot-target",
216221
"--non-interactive",
217222
"--output",
@@ -225,6 +230,7 @@ describe("e2e: mcp", () => {
225230
const { stderr, exitCode } = await runCli([
226231
"mcp",
227232
"call",
233+
"--target",
228234
"srv.tool",
229235
"--arg",
230236
"no-equals-sign",
@@ -240,6 +246,7 @@ describe("e2e: mcp", () => {
240246
const { stderr, exitCode } = await runCli([
241247
"mcp",
242248
"call",
249+
"--target",
243250
"srv.tool",
244251
"--json",
245252
"{not-json",
@@ -251,10 +258,10 @@ describe("e2e: mcp", () => {
251258
expect(stderr).toMatch(/--json is not valid JSON|--json must decode/);
252259
});
253260

254-
test("mcp call 缺少 positional 时打印子命令帮助并退出 (0)", async () => {
261+
test("mcp call 缺少 --target 时打印子命令帮助并退出 (0)", async () => {
255262
const { stderr, exitCode } = await runCli(["mcp", "call", "--non-interactive"]);
256263
expect(exitCode, stderr).toBe(0);
257-
expect(stderr).toMatch(/server-code|Usage:/i);
264+
expect(stderr).toMatch(/--target|Usage:/i);
258265
});
259266
});
260267

@@ -266,6 +273,7 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: mcp (live)", () => {
266273
const { stdout, stderr, exitCode } = await runCli([
267274
"mcp",
268275
"tools",
276+
"--server",
269277
"WebSearch",
270278
"--non-interactive",
271279
"--output",

packages/cli/tests/e2e/pipeline.e2e.test.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ describe("e2e: pipeline", () => {
8989
const { stdout, stderr, exitCode } = await runCli([
9090
"pipeline",
9191
"validate",
92+
"--file",
9293
chatBasicPath,
9394
"--output",
9495
"json",
@@ -100,9 +101,12 @@ describe("e2e: pipeline", () => {
100101
});
101102

102103
test("pipeline validate 使用 config 输出格式", async () => {
103-
const { stdout, stderr, exitCode } = await runCli(["pipeline", "validate", chatBasicPath], {
104-
DASHSCOPE_OUTPUT: "text",
105-
});
104+
const { stdout, stderr, exitCode } = await runCli(
105+
["pipeline", "validate", "--file", chatBasicPath],
106+
{
107+
DASHSCOPE_OUTPUT: "text",
108+
},
109+
);
106110
expect(exitCode, stderr).toBe(0);
107111
expect(stdout).toBe("Pipeline definition is valid.\n");
108112
});
@@ -111,6 +115,7 @@ describe("e2e: pipeline", () => {
111115
const { stdout, stderr, exitCode } = await runCli([
112116
"pipeline",
113117
"validate",
118+
"--file",
114119
invalidPipelinePath,
115120
"--output",
116121
"json",
@@ -122,16 +127,17 @@ describe("e2e: pipeline", () => {
122127
expect(data.issues?.join("\n")).toMatch(/pipeline graph contains cycle/i);
123128
});
124129

125-
test("pipeline run 缺少 file 时退出为用法错误 (2)", async () => {
130+
test("pipeline run 缺少 --file 时打印子命令帮助并退出 (0)", async () => {
126131
const { stderr, exitCode } = await runCli(["pipeline", "run", "--non-interactive"]);
127-
expect(exitCode).toBe(2);
128-
expect(stderr).toMatch(/pipeline file is required|Usage: bl pipeline run <file>/i);
132+
expect(exitCode, stderr).toBe(0);
133+
expect(stderr).toMatch(/Usage: bl pipeline run --file <path>|--file/i);
129134
});
130135

131136
test("pipeline run --dry-run --output json 仅输出计划", async () => {
132137
const { stdout, stderr, exitCode } = await runCli([
133138
"pipeline",
134139
"run",
140+
"--file",
135141
chatBasicPath,
136142
"--input",
137143
'{"message":"hello"}',
@@ -166,6 +172,7 @@ describe("e2e: pipeline", () => {
166172
[
167173
"pipeline",
168174
"run",
175+
"--file",
169176
chatBasicPath,
170177
"--input",
171178
'{"message":"hello"}',
@@ -183,6 +190,7 @@ describe("e2e: pipeline", () => {
183190
const { stderr, exitCode } = await runCli([
184191
"pipeline",
185192
"run",
193+
"--file",
186194
chatBasicPath,
187195
"--input",
188196
'{"message":"hello"}',
@@ -199,6 +207,7 @@ describe("e2e: pipeline", () => {
199207
const { stdout, stderr, exitCode } = await runCli([
200208
"pipeline",
201209
"run",
210+
"--file",
202211
chatBasicPath,
203212
"--input",
204213
'{"message":"hello"}',
@@ -227,6 +236,7 @@ describe("e2e: pipeline", () => {
227236
const { stdout, stderr, exitCode } = await runCli([
228237
"pipeline",
229238
"run",
239+
"--file",
230240
chatBasicPath,
231241
"--dry-run",
232242
"--events",

packages/cli/tests/e2e/search-web.e2e.test.ts

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@ describe("e2e: search web", () => {
2727
expect(exitCode, stderr).toBe(0);
2828
expect(stderr).toMatch(/web|--query|list-tools|count/i);
2929
});
30+
31+
test("search web --dry-run --list-tools 无需 --query 也无需凭证即可干跑", async () => {
32+
const { stdout, stderr, exitCode } = await runCli(
33+
["search", "web", "--dry-run", "--list-tools", "--non-interactive", "--output", "json"],
34+
{
35+
DASHSCOPE_API_KEY: undefined,
36+
DASHSCOPE_ACCESS_TOKEN: undefined,
37+
},
38+
);
39+
expect(exitCode, stderr).toBe(0);
40+
const data = parseStdoutJson<{ action?: string }>(stdout);
41+
expect(data.action).toBe("tools/list");
42+
});
3043
});
3144

3245
describe.skipIf(!isDashScopeE2EReady())("e2e: search web", () => {
@@ -61,21 +74,6 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: search web", () => {
6174
expect(data.arguments?.count).toBe(5);
6275
});
6376

64-
test("search web --dry-run --list-tools 仅描述 tools/list", async () => {
65-
const { stdout, stderr, exitCode } = await runCli([
66-
"search",
67-
"web",
68-
"--dry-run",
69-
"--list-tools",
70-
"--non-interactive",
71-
"--output",
72-
"json",
73-
]);
74-
expect(exitCode, stderr).toBe(0);
75-
const data = parseStdoutJson<{ action?: string }>(stdout);
76-
expect(data.action).toBe("tools/list");
77-
});
78-
7977
test("联网搜索返回 JSON 且含搜索结果", async () => {
8078
const { stdout, stderr, exitCode } = await runCli([
8179
"search",

packages/cli/tests/e2e/video-download.e2e.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
8787
const genMp4 = join(outDir, "e2e-gen-for-download.mp4");
8888

8989
const gen = await runCli([
90-
...cliTimeoutPrefix(),
9190
"video",
9291
"generate",
92+
...cliTimeoutPrefix(),
9393
"--model",
9494
"happyhorse-1.0-t2v",
9595
"--duration",
@@ -116,9 +116,9 @@ describe.skipIf(!isBailianE2EVideoEnabled() || !isDashScopeE2EReady())(
116116

117117
const downloadMp4 = join(outDir, "e2e-download.mp4");
118118
const dl = await runCli([
119-
...cliTimeoutPrefix(),
120119
"video",
121120
"download",
121+
...cliTimeoutPrefix(),
122122
"--task-id",
123123
genData.task_id!,
124124
"--out",

0 commit comments

Comments
 (0)