Skip to content

Commit da6e131

Browse files
committed
feat(commands): migrate delete commands to runtime risk confirmation
1 parent 2d2ec1c commit da6e131

12 files changed

Lines changed: 174 additions & 179 deletions

File tree

packages/commands/src/commands/dataset/delete.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { defineCommand, deleteDataset, type FlagsDef } from "bailian-cli-core";
2-
import { emitResult, emitBare, confirmDangerousAction } from "bailian-cli-runtime";
2+
import { emitResult, emitBare } from "bailian-cli-runtime";
33

44
const DELETE_FLAGS = {
55
fileId: {
@@ -8,28 +8,25 @@ const DELETE_FLAGS = {
88
description: { "en-US": "Dataset file ID (required)", "zh-CN": "数据集文件 ID(必填)" },
99
required: true,
1010
},
11-
yes: {
12-
type: "switch",
13-
description: { "en-US": "Skip the confirmation prompt", "zh-CN": "跳过确认提示" },
14-
},
1511
} satisfies FlagsDef;
1612

1713
export default defineCommand({
1814
description: { "en-US": "Delete a dataset file by ID", "zh-CN": "通过 ID 删除数据集文件" },
1915
auth: "apiKey",
20-
usageArgs: "--file-id <id> [--yes]",
16+
risk: {
17+
level: "high",
18+
message: {
19+
"en-US": "This permanently deletes the specified dataset file and cannot be undone.",
20+
"zh-CN": "该操作会永久删除指定的数据集文件,且无法撤销。",
21+
},
22+
},
23+
usageArgs: "--file-id <id>",
2124
flags: DELETE_FLAGS,
2225
exampleArgs: [
2326
"--file-id file-id-xxx",
2427
"--file-id file-id-xxx --dry-run",
2528
"--file-id file-id-xxx --yes",
2629
],
27-
notes: [
28-
{
29-
"en-US": "Irreversible — the dataset file is permanently removed.",
30-
"zh-CN": "该操作不可撤销——数据集文件将被永久删除。",
31-
},
32-
],
3330
async run(ctx) {
3431
const { settings, flags } = ctx;
3532
const fileId = flags.fileId;
@@ -39,11 +36,6 @@ export default defineCommand({
3936
return;
4037
}
4138

42-
await confirmDangerousAction(
43-
`Delete dataset file ${fileId}.\nThe file is permanently removed. This cannot be undone.`,
44-
flags.yes ?? false,
45-
);
46-
4739
const response = await deleteDataset(ctx.client, fileId);
4840

4941
if (settings.quiet) {

packages/commands/src/commands/deploy/delete.ts

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
ExitCode,
77
type FlagsDef,
88
} from "bailian-cli-core";
9-
import { emitResult, emitBare, confirmDangerousAction } from "bailian-cli-runtime";
9+
import { emitResult, emitBare } from "bailian-cli-runtime";
1010

1111
const DELETE_FLAGS = {
1212
deployedModel: {
@@ -25,10 +25,6 @@ const DELETE_FLAGS = {
2525
"zh-CN": "跳过本地 STOPPED/FAILED 状态预检查",
2626
},
2727
},
28-
yes: {
29-
type: "switch",
30-
description: { "en-US": "Skip the confirmation prompt", "zh-CN": "跳过确认提示" },
31-
},
3228
} satisfies FlagsDef;
3329

3430
/**
@@ -44,14 +40,15 @@ export default defineCommand({
4440
"zh-CN": "删除模型部署(状态必须为 STOPPED 或 FAILED)",
4541
},
4642
auth: "apiKey",
47-
usageArgs: "--deployed-model <id> [--skip-precheck] [--yes]",
48-
flags: DELETE_FLAGS,
49-
notes: [
50-
{
51-
"en-US": "Irreversible — the deployment is permanently destroyed.",
52-
"zh-CN": "该操作不可撤销——部署将被永久销毁。",
43+
risk: {
44+
level: "high",
45+
message: {
46+
"en-US": "This permanently deletes the specified model deployment and cannot be undone.",
47+
"zh-CN": "该操作会永久删除指定的模型部署,且无法撤销。",
5348
},
54-
],
49+
},
50+
usageArgs: "--deployed-model <id> [--skip-precheck]",
51+
flags: DELETE_FLAGS,
5552
exampleArgs: [
5653
"--deployed-model dep-...",
5754
"--deployed-model dep-... --dry-run",
@@ -87,11 +84,6 @@ export default defineCommand({
8784
}
8885
}
8986

90-
await confirmDangerousAction(
91-
`Delete deployment ${deployedModel}.\nThe deployment is permanently destroyed. This cannot be undone.`,
92-
flags.yes ?? false,
93-
);
94-
9587
const response = await deleteDeployment(ctx.client, deployedModel);
9688

9789
if (settings.quiet) {

packages/commands/src/commands/finetune/delete.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { defineCommand, deleteFineTune, type FlagsDef } from "bailian-cli-core";
2-
import { emitResult, emitBare, confirmDangerousAction } from "bailian-cli-runtime";
2+
import { emitResult, emitBare } from "bailian-cli-runtime";
33

44
const DELETE_FLAGS = {
55
jobId: {
@@ -8,23 +8,22 @@ const DELETE_FLAGS = {
88
description: { "en-US": "Fine-tune job ID (required)", "zh-CN": "微调任务 ID(必填)" },
99
required: true,
1010
},
11-
yes: {
12-
type: "switch",
13-
description: { "en-US": "Skip the confirmation prompt", "zh-CN": "跳过确认提示" },
14-
},
1511
} satisfies FlagsDef;
1612

1713
export default defineCommand({
1814
description: { "en-US": "Delete a fine-tune job record", "zh-CN": "删除微调任务记录" },
1915
auth: "apiKey",
20-
usageArgs: "--job-id <id> [--yes]",
16+
risk: {
17+
level: "high",
18+
message: {
19+
"en-US": "This permanently deletes the specified fine-tune job record and cannot be undone.",
20+
"zh-CN": "该操作会永久删除指定的微调任务记录,且无法撤销。",
21+
},
22+
},
23+
usageArgs: "--job-id <id>",
2124
flags: DELETE_FLAGS,
2225
exampleArgs: ["--job-id ft-xxx", "--job-id ft-xxx --dry-run", "--job-id ft-xxx --yes"],
2326
notes: [
24-
{
25-
"en-US": "Irreversible — the job record is permanently removed.",
26-
"zh-CN": "该操作不可撤销——任务记录将被永久删除。",
27-
},
2827
{
2928
"en-US":
3029
"Cancel a RUNNING job first via `finetune cancel` — the platform refuses to delete jobs that are still in flight.",
@@ -40,11 +39,6 @@ export default defineCommand({
4039
return;
4140
}
4241

43-
await confirmDangerousAction(
44-
`Delete fine-tune job record ${jobId}.\nThe job record is permanently removed. This cannot be undone.`,
45-
flags.yes ?? false,
46-
);
47-
4842
const response = await deleteFineTune(ctx.client, jobId);
4943

5044
if (settings.quiet) {

packages/commands/src/commands/quota/delete.ts

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,33 @@
11
import { defineCommand, detectOutputFormat, modelsLimitsPath } from "bailian-cli-core";
2-
import { emitResult, confirmDangerousAction } from "bailian-cli-runtime";
2+
import { emitResult } from "bailian-cli-runtime";
33

44
export default defineCommand({
55
description: {
66
"en-US": "Clear all custom rate limits (QPM/TPM) for a model",
77
"zh-CN": "清除模型的所有自定义限流配置(QPM/TPM)",
88
},
99
auth: "apiKey",
10-
usageArgs: "--model <model> [--yes]",
10+
risk: {
11+
level: "high",
12+
message: {
13+
"en-US":
14+
"This permanently clears all custom QPM/TPM rate limits for the specified model and cannot be undone.",
15+
"zh-CN": "该操作会永久清除指定模型的所有自定义 QPM/TPM 限流配置,且无法撤销。",
16+
},
17+
},
18+
usageArgs: "--model <model>",
1119
flags: {
1220
model: {
1321
type: "string",
1422
valueHint: "<model>",
1523
description: { "en-US": "Model name (required)", "zh-CN": "模型名称(必填)" },
1624
required: true,
1725
},
18-
yes: {
19-
type: "switch",
20-
description: {
21-
"en-US": "Skip the confirmation prompt",
22-
"zh-CN": "跳过确认提示",
23-
},
24-
},
2526
},
26-
exampleArgs: ["--model qwen-plus", "--model qwen-plus --yes", "--model qwen-plus --output json"],
27-
notes: [
28-
{
29-
"en-US":
30-
"Irreversible — the server-side OVERLAY is reset to defaults, so your custom QPM/TPM configuration is permanently removed.",
31-
"zh-CN":
32-
"该操作不可撤销——服务端 OVERLAY 会重置为默认值,你的自定义 QPM/TPM 配置将被永久删除。",
33-
},
34-
{
35-
"en-US": "Requires confirmation; pass --yes to skip the prompt in scripts.",
36-
"zh-CN": "需要确认;脚本中可加 --yes 跳过交互提示。",
37-
},
27+
exampleArgs: [
28+
"--model qwen-plus",
29+
"--model qwen-plus --dry-run --output json",
30+
"--model qwen-plus --yes",
3831
],
3932
async run(ctx) {
4033
const { settings, flags } = ctx;
@@ -51,11 +44,6 @@ export default defineCommand({
5144
return;
5245
}
5346

54-
await confirmDangerousAction(
55-
`Clear all custom rate limits for model ${modelName}.\nYour custom QPM/TPM configuration will be removed.`,
56-
flags.yes ?? false,
57-
);
58-
5947
const result = await ctx.client.requestJson<{ request_id?: string }>({
6048
path: modelsLimitsPath(),
6149
method: "POST",

packages/commands/tests/e2e/dataset.e2e.test.ts

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -293,16 +293,6 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: dataset (offline)", () => {
293293
expect(`${stdout}\n${stderr}`).toMatch(/--schema video is not supported/);
294294
});
295295

296-
test("dataset delete --help 展示 --yes", async () => {
297-
const { stderr, exitCode } = await runCommandE2e(DATASET_ROUTES, [
298-
"dataset",
299-
"delete",
300-
"--help",
301-
]);
302-
expect(exitCode, stderr).toBe(0);
303-
expect(stderr).toMatch(/--yes/i);
304-
});
305-
306296
test("dataset delete --dry-run 发出结构化动作", async () => {
307297
const { stdout, stderr, exitCode } = await runCommandE2e(DATASET_ROUTES, [
308298
"dataset",
@@ -317,16 +307,34 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: dataset (offline)", () => {
317307
const data = parseStdoutJson<{ action: string }>(stdout);
318308
expect(data.action).toBe("dataset.delete");
319309
});
310+
});
320311

321-
test("dataset delete 非 TTY 无 --yes 报 USAGE (2)", async () => {
312+
describe("e2e: dataset high-risk confirmation", () => {
313+
test("dataset delete --help 展示 runtime 注入的 --yes", async () => {
314+
const { stderr, exitCode } = await runCommandE2e(DATASET_ROUTES, [
315+
"dataset",
316+
"delete",
317+
"--help",
318+
]);
319+
expect(exitCode, stderr).toBe(0);
320+
expect(stderr).toMatch(/--yes/i);
321+
});
322+
323+
test("dataset delete 无 --yes 返回确认请求 (7)", async () => {
322324
const { stderr, exitCode } = await runCommandE2e(DATASET_ROUTES, [
323325
"dataset",
324326
"delete",
325327
"--file-id",
326328
"file-id-xxx",
329+
"--api-key",
330+
"e2e-dummy-key",
331+
"--output",
332+
"json",
327333
]);
328-
expect(exitCode).toBe(2);
329-
expect(stderr).toMatch(/--yes/);
334+
expect(exitCode).toBe(7);
335+
expect(JSON.parse(stderr)).toMatchObject({
336+
error: { code: 7, type: "requires_confirmation" },
337+
});
330338
});
331339
});
332340

packages/commands/tests/e2e/deploy.e2e.test.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,24 +201,30 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: deploy (offline)", () => {
201201
const data = parseStdoutJson<{ action: string }>(stdout);
202202
expect(data.action).toBe(`deploy.${sub}`);
203203
});
204+
});
204205

205-
test("deploy delete --help 展示 --yes", async () => {
206+
describe("e2e: deploy high-risk confirmation", () => {
207+
test("deploy delete --help 展示 runtime 注入的 --yes", async () => {
206208
const { stderr, exitCode } = await runCommandE2e(DEPLOY_ROUTES, ["deploy", "delete", "--help"]);
207209
expect(exitCode, stderr).toBe(0);
208210
expect(stderr).toMatch(/--yes/i);
209211
});
210212

211-
test("deploy delete 非 TTY 无 --yes 报 USAGE (2)", async () => {
212-
// --skip-precheck 保证确认门在发任何网络请求前触发
213+
test("deploy delete 无 --yes 返回确认请求 (7)", async () => {
213214
const { stderr, exitCode } = await runCommandE2e(DEPLOY_ROUTES, [
214215
"deploy",
215216
"delete",
216217
"--deployed-model",
217218
"dep-xxx",
218-
"--skip-precheck",
219+
"--api-key",
220+
"e2e-dummy-key",
221+
"--output",
222+
"json",
219223
]);
220-
expect(exitCode).toBe(2);
221-
expect(stderr).toMatch(/--yes/);
224+
expect(exitCode).toBe(7);
225+
expect(JSON.parse(stderr)).toMatchObject({
226+
error: { code: 7, type: "requires_confirmation" },
227+
});
222228
});
223229
});
224230

packages/commands/tests/e2e/finetune.e2e.test.ts

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -294,27 +294,6 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
294294
expect(data.action).toBe(`finetune.${sub}`);
295295
});
296296

297-
test("finetune delete --help 展示 --yes", async () => {
298-
const { stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
299-
"finetune",
300-
"delete",
301-
"--help",
302-
]);
303-
expect(exitCode, stderr).toBe(0);
304-
expect(stderr).toMatch(/--yes/i);
305-
});
306-
307-
test("finetune delete 非 TTY 无 --yes 报 USAGE (2)", async () => {
308-
const { stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
309-
"finetune",
310-
"delete",
311-
"--job-id",
312-
"ft-xxx",
313-
]);
314-
expect(exitCode).toBe(2);
315-
expect(stderr).toMatch(/--yes/);
316-
});
317-
318297
test("finetune create --dry-run 解析多 datasets 中的空白", async () => {
319298
const { stdout, stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
320299
"finetune",
@@ -493,6 +472,35 @@ describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (offline)", () => {
493472
});
494473
});
495474

475+
describe("e2e: finetune high-risk confirmation", () => {
476+
test("finetune delete --help 展示 runtime 注入的 --yes", async () => {
477+
const { stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
478+
"finetune",
479+
"delete",
480+
"--help",
481+
]);
482+
expect(exitCode, stderr).toBe(0);
483+
expect(stderr).toMatch(/--yes/i);
484+
});
485+
486+
test("finetune delete 无 --yes 返回确认请求 (7)", async () => {
487+
const { stderr, exitCode } = await runCommandE2e(FINETUNE_ROUTES, [
488+
"finetune",
489+
"delete",
490+
"--job-id",
491+
"ft-xxx",
492+
"--api-key",
493+
"e2e-dummy-key",
494+
"--output",
495+
"json",
496+
]);
497+
expect(exitCode).toBe(7);
498+
expect(JSON.parse(stderr)).toMatchObject({
499+
error: { code: 7, type: "requires_confirmation" },
500+
});
501+
});
502+
});
503+
496504
describe.skipIf(!isDashScopeE2EReady())("e2e: finetune (DashScope)", () => {
497505
/**
498506
* 不同开发者的 key 状态不一:可能鉴权失败、可能账号下没有任何微调记录、

0 commit comments

Comments
 (0)