mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
feat: api testing
This commit is contained in:
@@ -135,7 +135,7 @@ bl quota history # View quota-change histor
|
||||
# Asset center — browse, download, and manage model-generated assets (requires console login)
|
||||
bl asset-center list --type IMAGE
|
||||
bl asset-center get <asset-id> --include-download-url
|
||||
bl asset-center download --id <asset-id> --out ./image.png
|
||||
bl asset-center download --id <asset-id>
|
||||
bl asset-center stats
|
||||
bl asset-center storage
|
||||
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ bl quota history # 查看提额历史记录
|
||||
# 资产中心 — 浏览、下载与管理模型生成资产(需控制台登录)
|
||||
bl asset-center list --type IMAGE
|
||||
bl asset-center get <asset-id> --include-download-url
|
||||
bl asset-center download --id <asset-id> --out ./image.png
|
||||
bl asset-center download --id <asset-id>
|
||||
bl asset-center stats
|
||||
bl asset-center storage
|
||||
|
||||
|
||||
@@ -86,7 +86,6 @@ import {
|
||||
assetStats,
|
||||
assetStorage,
|
||||
assetTransferList,
|
||||
assetOssSlrStatus,
|
||||
assetOssSlrAuthorize,
|
||||
assetOssBind,
|
||||
assetOssShow,
|
||||
@@ -186,7 +185,6 @@ export const commands: Record<string, AnyCommand> = {
|
||||
"asset-center stats": assetStats,
|
||||
"asset-center storage": assetStorage,
|
||||
"asset-center transfer list": assetTransferList,
|
||||
"asset-center oss slr status": assetOssSlrStatus,
|
||||
"asset-center oss slr authorize": assetOssSlrAuthorize,
|
||||
"asset-center oss bind": assetOssBind,
|
||||
"asset-center oss show": assetOssShow,
|
||||
|
||||
@@ -40,8 +40,8 @@ describe("e2e: asset-center", () => {
|
||||
test("asset-center download --help 正常退出", async () => {
|
||||
const { stderr, exitCode } = await runCli(["asset-center", "download", "--help"]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
expect(stderr).toContain("--out");
|
||||
expect(stderr).toContain("--id");
|
||||
expect(stderr).not.toContain("--out");
|
||||
});
|
||||
|
||||
test("asset-center stats --help 正常退出", async () => {
|
||||
@@ -83,16 +83,10 @@ describe.skipIf(!isConsoleE2EReady())("e2e: asset-center(Console)", () => {
|
||||
expect(stderr).toMatch(/--id|Missing required argument/i);
|
||||
});
|
||||
|
||||
test("asset-center download 缺少 --out 时退出为用法错误 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCli([
|
||||
"asset-center",
|
||||
"download",
|
||||
"--id",
|
||||
"asset-001",
|
||||
"--quiet",
|
||||
]);
|
||||
test("asset-center download 缺少 --id 时退出为用法错误 (2)", async () => {
|
||||
const { stderr, exitCode } = await runCli(["asset-center", "download", "--quiet"]);
|
||||
expect(exitCode).toBe(2);
|
||||
expect(stderr).toMatch(/--out|Missing required argument/i);
|
||||
expect(stderr).toMatch(/--id|Missing required argument/i);
|
||||
});
|
||||
|
||||
test("asset-center oss bind policy=BEFORE_DAYS 缺 --before-days 报错", async () => {
|
||||
@@ -156,19 +150,19 @@ describe.skipIf(!isConsoleE2EReady())("e2e: asset-center(Console)", () => {
|
||||
expect(data.api).toContain("getStorageQuota");
|
||||
});
|
||||
|
||||
test("asset-center oss slr status --dry-run 输出请求参数", async () => {
|
||||
test("asset-center oss slr authorize --dry-run 输出请求参数", async () => {
|
||||
const { stdout, stderr, exitCode } = await runCli([
|
||||
"asset-center",
|
||||
"oss",
|
||||
"slr",
|
||||
"status",
|
||||
"authorize",
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
]);
|
||||
expect(exitCode, stderr).toBe(0);
|
||||
const data = parseStdoutJson<{ api?: string }>(stdout);
|
||||
expect(data.api).toContain("checkOssSLR");
|
||||
expect(data.api).toContain("createOssSLR");
|
||||
});
|
||||
|
||||
test("【console】asset-center list 真实调用或鉴权失败优雅退出", async () => {
|
||||
|
||||
@@ -43,8 +43,7 @@ bl asset
|
||||
│
|
||||
└── oss # OSS 转存子组
|
||||
├── slr
|
||||
│ ├── status # 检查 SLR 授权状态
|
||||
│ └── authorize # 创建 SLR 授权
|
||||
│ └── authorize # 一键 SLR 授权
|
||||
├── bind # 创建转存策略(绑定 OSS)
|
||||
├── show # 查看当前转存策略
|
||||
├── update # 更新转存策略
|
||||
@@ -79,7 +78,6 @@ bl asset
|
||||
| `"asset service enable"` | `assetServiceEnable` | 3 |
|
||||
| `"asset service disable"` | `assetServiceDisable` | 3 |
|
||||
| `"asset transfer list"` | `assetTransferList` | 3 |
|
||||
| `"asset oss slr status"` | `assetOssSlrStatus` | 2 |
|
||||
| `"asset oss slr authorize"` | `assetOssSlrAuthorize` | 2 |
|
||||
| `"asset oss bind"` | `assetOssBind` | 2 |
|
||||
| `"asset oss show"` | `assetOssShow` | 2 |
|
||||
@@ -370,36 +368,19 @@ Overage: ¥0.12/GB/month
|
||||
|
||||
### 5.2 Phase 2 命令 — OSS 子组
|
||||
|
||||
#### `bl asset oss slr status`
|
||||
|
||||
```
|
||||
Description: Check whether OSS service-linked role is authorized
|
||||
|
||||
Usage: bl asset oss slr status
|
||||
|
||||
Examples:
|
||||
bl asset oss slr status
|
||||
bl asset oss slr status --output json
|
||||
```
|
||||
|
||||
**PRD 映射:** #8 SLR 授权(检查步骤)
|
||||
|
||||
---
|
||||
|
||||
#### `bl asset oss slr authorize`
|
||||
|
||||
```
|
||||
Description: Create OSS service-linked role authorization
|
||||
Description: Authorize OSS service-linked role (one-click)
|
||||
|
||||
Usage: bl asset oss slr authorize
|
||||
|
||||
Examples:
|
||||
bl asset oss slr authorize
|
||||
bl asset oss slr authorize --output json
|
||||
```
|
||||
|
||||
行为:已授权则跳过并提示;未授权则调用 create。
|
||||
|
||||
**PRD 映射:** #8 SLR 授权(执行步骤)
|
||||
**PRD 映射:** #8 SLR 一键授权
|
||||
|
||||
---
|
||||
|
||||
@@ -594,22 +575,22 @@ bl asset stats --sync-failed
|
||||
|
||||
## 6. PRD 覆盖矩阵
|
||||
|
||||
| PRD # | 功能 | CLI 命令 | Phase | 状态 |
|
||||
| ----- | ------------- | ------------------------------------ | ----- | ---------------------------------- |
|
||||
| 1 | 查看资产列表 | `asset list` | 1 | ✅ 可开发 |
|
||||
| 2 | 查看资产详情 | `asset get` | 1 | ✅ 可开发 |
|
||||
| 3 | 收藏/取消收藏 | `asset favorite` / `unfavorite` | 1 | ✅ 可开发 |
|
||||
| 4 | 删除资产 | `asset delete` | 1 | ✅ 可开发 |
|
||||
| 5 | 批量删除 | `asset delete`(多 `--id`) | 1 | ✅ 可开发 |
|
||||
| 6 | 下载资产 | `asset download` | 1 | ✅ 可开发 |
|
||||
| 7 | 查看资产统计 | `asset stats` | 1 | ✅ 可开发(转存失败用 workaround) |
|
||||
| 8 | SLR 授权 | `asset oss slr status` + `authorize` | 2 | ✅ 可开发 |
|
||||
| 9 | 绑定 OSS | `asset oss bind` | 2 | ✅ 可开发 |
|
||||
| 10 | 查看 OSS 配置 | `asset oss show` | 2 | ✅ 可开发 |
|
||||
| 11 | 修改 OSS 配置 | `asset oss update` | 2 | ✅ 可开发 |
|
||||
| 12 | 解绑 OSS | `asset oss unbind` | 2 | ✅ 可开发 |
|
||||
| 13 | 查看转存日志 | `asset transfer list` | 3 | ⛔ API 缺失 |
|
||||
| 14 | 查看容量信息 | `asset storage` | 1 | ✅ 可开发 |
|
||||
| PRD # | 功能 | CLI 命令 | Phase | 状态 |
|
||||
| ----- | ------------- | ------------------------------- | ----- | ---------------------------------- |
|
||||
| 1 | 查看资产列表 | `asset list` | 1 | ✅ 可开发 |
|
||||
| 2 | 查看资产详情 | `asset get` | 1 | ✅ 可开发 |
|
||||
| 3 | 收藏/取消收藏 | `asset favorite` / `unfavorite` | 1 | ✅ 可开发 |
|
||||
| 4 | 删除资产 | `asset delete` | 1 | ✅ 可开发 |
|
||||
| 5 | 批量删除 | `asset delete`(多 `--id`) | 1 | ✅ 可开发 |
|
||||
| 6 | 下载资产 | `asset download` | 1 | ✅ 可开发 |
|
||||
| 7 | 查看资产统计 | `asset stats` | 1 | ✅ 可开发(转存失败用 workaround) |
|
||||
| 8 | SLR 授权 | `asset oss slr authorize` | 2 | ✅ 可开发 |
|
||||
| 9 | 绑定 OSS | `asset oss bind` | 2 | ✅ 可开发 |
|
||||
| 10 | 查看 OSS 配置 | `asset oss show` | 2 | ✅ 可开发 |
|
||||
| 11 | 修改 OSS 配置 | `asset oss update` | 2 | ✅ 可开发 |
|
||||
| 12 | 解绑 OSS | `asset oss unbind` | 2 | ✅ 可开发 |
|
||||
| 13 | 查看转存日志 | `asset transfer list` | 3 | ⛔ API 缺失 |
|
||||
| 14 | 查看容量信息 | `asset storage` | 1 | ✅ 可开发 |
|
||||
|
||||
---
|
||||
|
||||
@@ -650,7 +631,6 @@ bl asset download --id asset-001 --out ./image.png
|
||||
### 7.4 OSS 转存配置
|
||||
|
||||
```bash
|
||||
bl asset oss slr status
|
||||
bl asset oss slr authorize
|
||||
bl asset oss regions list
|
||||
bl asset oss buckets list --region cn-hangzhou
|
||||
|
||||
@@ -67,14 +67,14 @@
|
||||
|
||||
### 2.3 OSS 转存
|
||||
|
||||
| PRD # | 能力 | CLI 命令 | API Action | 备注 |
|
||||
| ----- | ------------- | -------------------------------------------------------- | ------------------------------ | ----------------------------- |
|
||||
| 8 | SLR 授权 | `bl asset oss slr status` / `bl asset oss slr authorize` | `checkOssSLR` / `createOssSLR` | 授权前先查状态 |
|
||||
| 9 | 绑定 OSS | `bl asset oss bind` | `createAssetTransferPolicy` | 配置 bucket / path / 转存策略 |
|
||||
| 10 | 查看 OSS 配置 | `bl asset oss show` | `getAssetTransferPolicy` | |
|
||||
| 11 | 修改 OSS 配置 | `bl asset oss update` | `updateAssetTransferPolicy` | 需 `--policy-id` |
|
||||
| 12 | 解绑 OSS | `bl asset oss unbind` | `deleteAssetTransferPolicy` | |
|
||||
| 13 | 查看转存日志 | `bl asset transfer list`(待定) | **文档缺失** | 见 §6 风险项 |
|
||||
| PRD # | 能力 | CLI 命令 | API Action | 备注 |
|
||||
| ----- | ------------- | -------------------------------- | --------------------------- | ----------------------------- |
|
||||
| 8 | SLR 授权 | `bl asset oss slr authorize` | `createOssSLR` | 一键授权,直接调用 create |
|
||||
| 9 | 绑定 OSS | `bl asset oss bind` | `createAssetTransferPolicy` | 配置 bucket / path / 转存策略 |
|
||||
| 10 | 查看 OSS 配置 | `bl asset oss show` | `getAssetTransferPolicy` | |
|
||||
| 11 | 修改 OSS 配置 | `bl asset oss update` | `updateAssetTransferPolicy` | 需 `--policy-id` |
|
||||
| 12 | 解绑 OSS | `bl asset oss unbind` | `deleteAssetTransferPolicy` | |
|
||||
| 13 | 查看转存日志 | `bl asset transfer list`(待定) | **文档缺失** | 见 §6 风险项 |
|
||||
|
||||
**辅助命令(绑定 UX,API 已有):**
|
||||
|
||||
@@ -141,7 +141,6 @@ asset-center/
|
||||
├── models-list.ts # 可选 P1
|
||||
├── service-status.ts # 可选 P1
|
||||
└── oss/
|
||||
├── slr-status.ts
|
||||
├── slr-authorize.ts
|
||||
├── bind.ts
|
||||
├── show.ts
|
||||
@@ -364,11 +363,7 @@ CLI 用法:`--id asset-001 --id asset-002` 或多次重复。实现时在 `val
|
||||
|
||||
### 5.8 `bl asset oss slr authorize`
|
||||
|
||||
流程:
|
||||
|
||||
1. 调用 `checkOssSLR`
|
||||
2. 若 `authorized === true`,text 输出 "Already authorized." 并 exit 0
|
||||
3. 否则调用 `createOssSLR`
|
||||
直接调用 `createOssSLR`,一键完成 SLR 授权。
|
||||
|
||||
---
|
||||
|
||||
@@ -456,7 +451,7 @@ asset list | get | favorite | unfavorite | delete | restore | download | stats |
|
||||
### Phase 2 — OSS 转存(P1)
|
||||
|
||||
```
|
||||
asset oss slr status | authorize
|
||||
asset oss slr authorize
|
||||
asset oss bind | show | update | unbind
|
||||
asset oss regions | buckets | folders list
|
||||
asset models list | service status
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
# Asset Center 命令测试报告 — Phase 2
|
||||
|
||||
- **测试时间**: 2026-07-10 09:07:09 (UTC)
|
||||
- **Workspace**: `llm-0xvms4kqhbqjlg8s`
|
||||
- **测试 IMAGE**: `asset_98175cbf83294f7b8ada86657623dcf3`
|
||||
- **测试 VIDEO**: `asset_df026105d2274ff9b8c824058fa23d60`
|
||||
- **策略**: 可逆写操作(favorite/unfavorite 往返);download 到 /tmp 后删除;其余只读
|
||||
- **汇总**: 16 通过 / 0 失败 / 16 总计
|
||||
|
||||
> Phase 1 报告见同目录 [TEST-REPORT.md](./TEST-REPORT.md)(24 项 dry-run + 只读基础验证)
|
||||
|
||||
## Phase 2 测试结果
|
||||
|
||||
| # | 分类 | 命令 | 模式 | 状态 | Exit | 耗时 | 结果摘要 |
|
||||
| --- | ---- | ------------------------------------------ | -------- | ------- | ---- | ------- | -------------------------------------------------------------------------------------------------------- |
|
||||
| 1 | 下载 | `asset-center download (IMAGE)` | 真实调用 | ✅ PASS | 0 | 20045ms | saved /tmp/asset-center-test-asset_98175cbf83294f7b8ada86657623dcf3.png (1449847 bytes, reported 1.4 MB) |
|
||||
| 2 | 查询 | `asset-center get --include-download-url` | 真实调用 | ✅ PASS | 0 | 21226ms | download_url present |
|
||||
| 3 | 查询 | `asset-center list --include-download-url` | 真实调用 | ✅ PASS | 0 | 19057ms | items contain download_url |
|
||||
| 4 | 查询 | `asset-center list --next-token` | 真实调用 | ✅ PASS | 0 | 19584ms | page2=3 items, overlap=0, has_pre=true |
|
||||
| 5 | 统计 | `asset-center stats --type IMAGE` | 真实调用 | ✅ PASS | 0 | 18830ms | image=7, total=7 |
|
||||
| 6 | 统计 | `asset-center stats --sync-failed` | 真实调用 | ✅ PASS | 0 | 19084ms | total=27, sync_failed=0 |
|
||||
| 7 | 查询 | `asset-center list --recycle-bin` | 真实调用 | ✅ PASS | 0 | 19314ms | 0 soft-deleted item(s) |
|
||||
| 8 | 输出 | `asset-center list (text)` | 真实调用 | ✅ PASS | 0 | 17840ms | 4 lines table output |
|
||||
| 9 | 收藏 | `asset-center favorite (真实)` | 真实调用 | ✅ PASS | 0 | 22082ms | affected=1 |
|
||||
| 10 | 收藏 | `get 验证 favorited=true` | 真实调用 | ✅ PASS | 0 | 18865ms | favorited=true ✓ |
|
||||
| 11 | 查询 | `list --favorited 含测试资产` | 真实调用 | ✅ PASS | 0 | 22120ms | found in favorited list |
|
||||
| 12 | 收藏 | `asset-center unfavorite (真实)` | 真实调用 | ✅ PASS | 0 | 22133ms | affected=1 |
|
||||
| 13 | 收藏 | `get 验证 favorited=false (恢复)` | 真实调用 | ✅ PASS | 0 | 27797ms | favorited=false ✓ |
|
||||
| 14 | 收藏 | `favorite 批量 (--id x2)` | 真实调用 | ✅ PASS | 0 | 22947ms | affected=2 |
|
||||
| 15 | 收藏 | `unfavorite 批量 (--id x2)` | 真实调用 | ✅ PASS | 0 | 15385ms | affected=2 |
|
||||
| 16 | 边界 | `get 不存在的 asset-id` | 真实调用 | ✅ PASS | 1 | 12207ms | exit 1, 服务端错误原样透传: "资产不存在" |
|
||||
|
||||
## 边界行为说明
|
||||
|
||||
查询不存在的 `asset-id` 时,服务端返回业务错误 **「资产不存在」**,CLI 按约定 **原样透传**(exit code 1),不会替换为本地文案。这与 AGENTS.md 错误处理边界一致。
|
||||
@@ -0,0 +1,44 @@
|
||||
# Asset Center 命令测试报告
|
||||
|
||||
- **测试时间**: 2026-07-10 08:41:06 (UTC)
|
||||
- **Workspace**: `llm-0xvms4kqhbqjlg8s`
|
||||
- **样本 Asset ID**: `asset_df026105d2274ff9b8c824058fa23d60`
|
||||
- **策略**: 只读命令真实调用;写操作/下载/OSS 变更一律 `--dry-run`
|
||||
- **汇总**: 24 通过 / 0 失败 / 24 总计
|
||||
|
||||
## 测试结果
|
||||
|
||||
| # | 分类 | 命令 | 模式 | 状态 | Exit | 耗时 | 结果摘要 |
|
||||
| --- | ---- | ---------------------------------------------------- | -------- | ------- | ---- | ------- | --------------------------------------------------------------- |
|
||||
| 1 | 查询 | `asset-center list` | 真实调用 | ✅ PASS | 0 | 18962ms | 3 item(s), next=94 |
|
||||
| 2 | 查询 | `asset-center list --type IMAGE` | 真实调用 | ✅ PASS | 0 | 16411ms | 2 item(s), next=90 |
|
||||
| 3 | 查询 | `asset-center get` | 真实调用 | ✅ PASS | 0 | 16541ms | {gmtModified, aliyunUid, generateTime, aliyunMainId} |
|
||||
| 4 | 统计 | `asset-center stats` | 真实调用 | ✅ PASS | 0 | 14226ms | total=27 |
|
||||
| 5 | 统计 | `asset-center storage` | 真实调用 | ✅ PASS | 0 | 18622ms | {used_storage_size, free_storage_quota, extra_storage_price} |
|
||||
| 6 | 转存 | `asset-center transfer list` | 真实调用 | ✅ PASS | 0 | 19662ms | 0 item(s) |
|
||||
| 7 | OSS | `asset-center oss slr status` | 真实调用 | ✅ PASS | 0 | 19745ms | authorized=true |
|
||||
| 8 | OSS | `asset-center oss show` | 真实调用 | ✅ PASS | 0 | 21994ms | {success, failed, messageUnmodified} |
|
||||
| 9 | 查询 | `asset-center list --dry-run` | dry-run | ✅ PASS | 0 | 23855ms | dry-run → /zelda/api/v1/bailian/asset/listModelGeneratedAsset |
|
||||
| 10 | 查询 | `asset-center get --dry-run` | dry-run | ✅ PASS | 0 | 17398ms | dry-run → /zelda/api/v1/bailian/asset/getModelGeneratedAsset |
|
||||
| 11 | 收藏 | `asset-center favorite` | dry-run | ✅ PASS | 0 | 16471ms | dry-run → /zelda/api/v1/bailian/asset/batchFavoriteAsset |
|
||||
| 12 | 收藏 | `asset-center unfavorite` | dry-run | ✅ PASS | 0 | 19724ms | dry-run → /zelda/api/v1/bailian/asset/batchUnfavoriteAsset |
|
||||
| 13 | 删除 | `asset-center delete` | dry-run | ✅ PASS | 0 | 20612ms | dry-run → /zelda/api/v1/bailian/asset/batchDeleteAsset |
|
||||
| 14 | 下载 | `asset-center download` | dry-run | ✅ PASS | 0 | 20348ms | dry-run → /zelda/api/v1/bailian/asset/batchGetAssetDownloadUrl |
|
||||
| 15 | 统计 | `asset-center stats --dry-run` | dry-run | ✅ PASS | 0 | 14898ms | dry-run → /zelda/api/v1/bailian/asset/countModelGeneratedAsset |
|
||||
| 16 | 统计 | `asset-center storage --dry-run` | dry-run | ✅ PASS | 0 | 14305ms | dry-run → /zelda/api/v1/bailian/asset/getStorageQuota |
|
||||
| 17 | OSS | `asset-center oss slr authorize` | dry-run | ✅ PASS | 0 | 14005ms | dry-run → /zelda/api/v1/bailian/asset/createOssSLR |
|
||||
| 18 | OSS | `asset-center oss bind` | dry-run | ✅ PASS | 0 | 16737ms | dry-run → /zelda/api/v1/bailian/asset/createAssetTransferPolicy |
|
||||
| 19 | OSS | `asset-center oss update` | dry-run | ✅ PASS | 0 | 18024ms | dry-run → /zelda/api/v1/bailian/asset/updateAssetTransferPolicy |
|
||||
| 20 | OSS | `asset-center oss unbind` | dry-run | ✅ PASS | 0 | 15174ms | dry-run → /zelda/api/v1/bailian/asset/deleteAssetTransferPolicy |
|
||||
| 21 | 校验 | `asset-center get (缺 asset-id)` | 参数校验 | ✅ PASS | 2 | 15202ms | Error: Missing required flag: --asset-id |
|
||||
| 22 | 校验 | `asset-center favorite (缺 --id)` | 参数校验 | ✅ PASS | 2 | 16697ms | Error: Missing required flag: --id |
|
||||
| 23 | 校验 | `asset-center download (缺 --out)` | 参数校验 | ✅ PASS | 2 | 18803ms | Error: Missing required flag: --out |
|
||||
| 24 | 校验 | `asset-center oss bind (BEFORE_DAYS 缺 before-days)` | 参数校验 | ✅ PASS | 2 | 14996ms | Error: --before-days is required when --policy is BEFORE_DAYS. |
|
||||
|
||||
## 模式说明
|
||||
|
||||
| 模式 | 说明 |
|
||||
| -------- | -------------------------------------------------- |
|
||||
| 真实调用 | 只读 API,不修改数据 |
|
||||
| dry-run | 输出 `{ api, data, gateway }` 请求体,不发起写操作 |
|
||||
| 参数校验 | 预期 exit code 2(用法错误) |
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
ExitCode,
|
||||
type FlagsDef,
|
||||
} from "bailian-cli-core";
|
||||
import { downloadFile, emitResult, emitBare, formatBytes } from "bailian-cli-runtime";
|
||||
import { emitResult, emitBare, padEnd } from "bailian-cli-runtime";
|
||||
import type { AssetDownloadResponse } from "./types.ts";
|
||||
import { ASSET_API, callAssetApi, dryRunPayload } from "./utils.ts";
|
||||
|
||||
@@ -13,33 +13,26 @@ const DOWNLOAD_FLAGS = {
|
||||
id: {
|
||||
type: "string",
|
||||
valueHint: "<asset-id>",
|
||||
description: "Asset ID to download",
|
||||
required: true,
|
||||
},
|
||||
out: {
|
||||
type: "string",
|
||||
valueHint: "<path>",
|
||||
description: "Output file path",
|
||||
description: "Asset ID to get download URL for",
|
||||
required: true,
|
||||
},
|
||||
} satisfies FlagsDef;
|
||||
|
||||
/**
|
||||
* `bl asset-center download` — 下载单个资产到本地文件。
|
||||
* `bl asset-center download` — 通过资产 ID 获取签名下载链接。
|
||||
*
|
||||
* 通过 batchGetAssetDownloadUrl 获取签名 URL,再写入 --out 指定路径。
|
||||
* 调用 batchGetAssetDownloadUrl,输出 download URL,不落盘。
|
||||
*/
|
||||
export default defineCommand({
|
||||
description: "Download an asset by ID to a local file",
|
||||
description: "Get a signed download URL for an asset by ID",
|
||||
auth: "console",
|
||||
usageArgs: "--id <asset-id> --out <path>",
|
||||
usageArgs: "--id <asset-id>",
|
||||
flags: DOWNLOAD_FLAGS,
|
||||
exampleArgs: ["--id asset-001 --out ./image.png", "--id asset-001 --out ./image.png --quiet"],
|
||||
exampleArgs: ["--id asset-001", "--id asset-001 --output json", "--id asset-001 --quiet"],
|
||||
async run(ctx) {
|
||||
const { settings, identity, flags } = ctx;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
const assetId = flags.id;
|
||||
const outPath = flags.out;
|
||||
const body = { assetIdList: [assetId] };
|
||||
|
||||
if (settings.dryRun) {
|
||||
@@ -47,7 +40,6 @@ export default defineCommand({
|
||||
{
|
||||
asset_id: assetId,
|
||||
action: "download",
|
||||
out: outPath,
|
||||
...dryRunPayload(settings, identity.binName, ASSET_API.batchGetAssetDownloadUrl, body),
|
||||
},
|
||||
format,
|
||||
@@ -68,13 +60,17 @@ export default defineCommand({
|
||||
throw new BailianError(`No download URL available for ${assetId}.`, ExitCode.GENERAL);
|
||||
}
|
||||
|
||||
const { size } = await downloadFile(url, outPath, { quiet: settings.quiet });
|
||||
|
||||
if (settings.quiet) {
|
||||
emitBare(outPath);
|
||||
emitBare(url);
|
||||
return;
|
||||
}
|
||||
|
||||
emitResult({ saved: outPath, size: formatBytes(size) }, format);
|
||||
if (format === "json") {
|
||||
emitResult({ asset_id: assetId, download_url: url }, format);
|
||||
return;
|
||||
}
|
||||
|
||||
emitBare(`${padEnd("AssetId", 16)} ${assetId}`);
|
||||
emitBare(`${padEnd("DownloadUrl", 16)} ${url}`);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -4,12 +4,10 @@ import type { AssetOssSlrResponse } from "../types.ts";
|
||||
import { ASSET_API, callAssetApi, dryRunPayload } from "../utils.ts";
|
||||
|
||||
/**
|
||||
* `bl asset-center oss slr authorize` — 创建 OSS SLR 授权。
|
||||
*
|
||||
* 已授权时跳过并提示;未授权时调用 createOssSLR。
|
||||
* `bl asset-center oss slr authorize` — 一键授权 OSS 服务关联角色(SLR)。
|
||||
*/
|
||||
export default defineCommand({
|
||||
description: "Create OSS service-linked role authorization",
|
||||
description: "Authorize OSS service-linked role (one-click)",
|
||||
auth: "console",
|
||||
usageArgs: "[flags]",
|
||||
exampleArgs: ["", "--output json"],
|
||||
@@ -22,23 +20,6 @@ export default defineCommand({
|
||||
return;
|
||||
}
|
||||
|
||||
const status = await callAssetApi<AssetOssSlrResponse>(
|
||||
ctx.client,
|
||||
settings,
|
||||
identity.binName,
|
||||
ASSET_API.checkOssSLR,
|
||||
{},
|
||||
);
|
||||
|
||||
if (status.authorized) {
|
||||
if (settings.quiet || format === "text") {
|
||||
emitBare("OSS SLR is already authorized.");
|
||||
} else {
|
||||
emitResult({ authorized: true, created: false }, format);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await callAssetApi<AssetOssSlrResponse>(
|
||||
ctx.client,
|
||||
settings,
|
||||
@@ -50,7 +31,7 @@ export default defineCommand({
|
||||
if (settings.quiet || format === "text") {
|
||||
emitBare(data.success ? "OSS SLR authorized." : "OSS SLR authorization failed.");
|
||||
} else {
|
||||
emitResult({ authorized: true, created: true, success: data.success ?? false }, format);
|
||||
emitResult({ success: data.success ?? false }, format);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import { defineCommand, detectOutputFormat } from "bailian-cli-core";
|
||||
import { emitResult, emitBare } from "bailian-cli-runtime";
|
||||
import type { AssetOssSlrResponse } from "../types.ts";
|
||||
import { ASSET_API, callAssetApi, dryRunPayload } from "../utils.ts";
|
||||
|
||||
/**
|
||||
* `bl asset-center oss slr status` — 检查 OSS 服务关联角色(SLR)是否已授权。
|
||||
*/
|
||||
export default defineCommand({
|
||||
description: "Check whether OSS service-linked role is authorized",
|
||||
auth: "console",
|
||||
usageArgs: "[flags]",
|
||||
exampleArgs: ["", "--output json"],
|
||||
async run(ctx) {
|
||||
const { settings, identity } = ctx;
|
||||
const format = detectOutputFormat(settings.output);
|
||||
|
||||
if (settings.dryRun) {
|
||||
emitResult(dryRunPayload(settings, identity.binName, ASSET_API.checkOssSLR, {}), format);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await callAssetApi<AssetOssSlrResponse>(
|
||||
ctx.client,
|
||||
settings,
|
||||
identity.binName,
|
||||
ASSET_API.checkOssSLR,
|
||||
{},
|
||||
);
|
||||
|
||||
if (format === "json") {
|
||||
emitResult({ authorized: data.authorized ?? false }, format);
|
||||
return;
|
||||
}
|
||||
|
||||
emitBare(data.authorized ? "OSS SLR is authorized." : "OSS SLR is not authorized.");
|
||||
},
|
||||
});
|
||||
@@ -22,7 +22,6 @@ export const ASSET_API = {
|
||||
batchGetAssetDownloadUrl: assetApi("batchGetAssetDownloadUrl"),
|
||||
countModelGeneratedAsset: assetApi("countModelGeneratedAsset"),
|
||||
getStorageQuota: assetApi("getStorageQuota"),
|
||||
checkOssSLR: assetApi("checkOssSLR"),
|
||||
createOssSLR: assetApi("createOssSLR"),
|
||||
createAssetTransferPolicy: assetApi("createAssetTransferPolicy"),
|
||||
getAssetTransferPolicy: assetApi("getAssetTransferPolicy"),
|
||||
|
||||
@@ -93,7 +93,6 @@ export { default as assetDownload } from "./commands/asset-center/download.ts";
|
||||
export { default as assetStats } from "./commands/asset-center/stats.ts";
|
||||
export { default as assetStorage } from "./commands/asset-center/storage.ts";
|
||||
export { default as assetTransferList } from "./commands/asset-center/transfer-list.ts";
|
||||
export { default as assetOssSlrStatus } from "./commands/asset-center/oss/slr-status.ts";
|
||||
export { default as assetOssSlrAuthorize } from "./commands/asset-center/oss/slr-authorize.ts";
|
||||
export { default as assetOssBind } from "./commands/asset-center/oss/bind.ts";
|
||||
export { default as assetOssShow } from "./commands/asset-center/oss/show.ts";
|
||||
|
||||
@@ -10,14 +10,13 @@ Index: [index.md](index.md)
|
||||
| Command | Description |
|
||||
| ----------------------------------- | -------------------------------------------------------------- |
|
||||
| `bl asset-center delete` | Delete assets (soft delete to recycle bin by default) |
|
||||
| `bl asset-center download` | Download an asset by ID to a local file |
|
||||
| `bl asset-center download` | Get a signed download URL for an asset by ID |
|
||||
| `bl asset-center favorite` | Add assets to favorites |
|
||||
| `bl asset-center get` | Get full details of a model-generated asset |
|
||||
| `bl asset-center list` | List model-generated assets with filters and cursor pagination |
|
||||
| `bl asset-center oss bind` | Create OSS transfer policy (bind bucket for asset transfer) |
|
||||
| `bl asset-center oss show` | View current OSS transfer policy |
|
||||
| `bl asset-center oss slr authorize` | Create OSS service-linked role authorization |
|
||||
| `bl asset-center oss slr status` | Check whether OSS service-linked role is authorized |
|
||||
| `bl asset-center oss slr authorize` | Authorize OSS service-linked role (one-click) |
|
||||
| `bl asset-center oss unbind` | Delete OSS transfer policy (unbind) |
|
||||
| `bl asset-center oss update` | Update an existing OSS transfer policy |
|
||||
| `bl asset-center stats` | Count model-generated assets by type |
|
||||
@@ -62,18 +61,17 @@ bl asset-center delete --id asset-001 --permanent
|
||||
|
||||
### `bl asset-center download`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | ------------------------------------------------------- |
|
||||
| **Name** | `asset-center download` |
|
||||
| **Description** | Download an asset by ID to a local file |
|
||||
| **Usage** | `bl asset-center download --id <asset-id> --out <path>` |
|
||||
| Field | Value |
|
||||
| --------------- | -------------------------------------------- |
|
||||
| **Name** | `asset-center download` |
|
||||
| **Description** | Get a signed download URL for an asset by ID |
|
||||
| **Usage** | `bl asset-center download --id <asset-id>` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | -------------------------------------------------------- |
|
||||
| `--id <asset-id>` | string | yes | Asset ID to download |
|
||||
| `--out <path>` | string | yes | Output file path |
|
||||
| `--id <asset-id>` | string | yes | Asset ID to get download URL for |
|
||||
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
|
||||
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
|
||||
@@ -82,11 +80,15 @@ bl asset-center delete --id asset-001 --permanent
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl asset-center download --id asset-001 --out ./image.png
|
||||
bl asset-center download --id asset-001
|
||||
```
|
||||
|
||||
```bash
|
||||
bl asset-center download --id asset-001 --out ./image.png --quiet
|
||||
bl asset-center download --id asset-001 --output json
|
||||
```
|
||||
|
||||
```bash
|
||||
bl asset-center download --id asset-001 --quiet
|
||||
```
|
||||
|
||||
### `bl asset-center favorite`
|
||||
@@ -266,11 +268,11 @@ bl asset-center oss show --policy-id policy-abc123 --output json
|
||||
|
||||
### `bl asset-center oss slr authorize`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | -------------------------------------------- |
|
||||
| **Name** | `asset-center oss slr authorize` |
|
||||
| **Description** | Create OSS service-linked role authorization |
|
||||
| **Usage** | `bl asset-center oss slr authorize [flags]` |
|
||||
| Field | Value |
|
||||
| --------------- | --------------------------------------------- |
|
||||
| **Name** | `asset-center oss slr authorize` |
|
||||
| **Description** | Authorize OSS service-linked role (one-click) |
|
||||
| **Usage** | `bl asset-center oss slr authorize [flags]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
@@ -291,33 +293,6 @@ bl asset-center oss slr authorize
|
||||
bl asset-center oss slr authorize --output json
|
||||
```
|
||||
|
||||
### `bl asset-center oss slr status`
|
||||
|
||||
| Field | Value |
|
||||
| --------------- | --------------------------------------------------- |
|
||||
| **Name** | `asset-center oss slr status` |
|
||||
| **Description** | Check whether OSS service-linked role is authorized |
|
||||
| **Usage** | `bl asset-center oss slr status [flags]` |
|
||||
|
||||
#### Flags
|
||||
|
||||
| Flag | Type | Required | Description |
|
||||
| ------------------------------ | ------ | -------- | -------------------------------------------------------- |
|
||||
| `--console-region <region>` | string | no | Console gateway region (e.g. cn-beijing, ap-southeast-1) |
|
||||
| `--console-site <site>` | string | no | Console site: domestic, international |
|
||||
| `--console-switch-agent <uid>` | number | no | Switch agent UID for delegated access |
|
||||
| `--workspace-id <id>` | string | no | Workspace ID (env: BAILIAN_WORKSPACE_ID) |
|
||||
|
||||
#### Examples
|
||||
|
||||
```bash
|
||||
bl asset-center oss slr status
|
||||
```
|
||||
|
||||
```bash
|
||||
bl asset-center oss slr status --output json
|
||||
```
|
||||
|
||||
### `bl asset-center oss unbind`
|
||||
|
||||
| Field | Value |
|
||||
|
||||
@@ -8,115 +8,130 @@ Use this index for the full quick index and global flags.
|
||||
|
||||
## Quick index
|
||||
|
||||
| Command | Description | Detail |
|
||||
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
|
||||
| `bl advisor recommend` | Recommend the best models for your use case (intent analysis → candidate recall → LLM ranking) | [advisor.md](advisor.md) |
|
||||
| `bl app call` | Call a Bailian application (agent or workflow) | [app.md](app.md) |
|
||||
| `bl app list` | List Bailian applications | [app.md](app.md) |
|
||||
| `bl auth login` | Authenticate with API key, console browser login, or OpenAPI AK/SK (credentials can coexist) | [auth.md](auth.md) |
|
||||
| `bl auth logout` | Clear stored credentials | [auth.md](auth.md) |
|
||||
| `bl auth status` | Show current authentication state | [auth.md](auth.md) |
|
||||
| `bl config set` | Set a config value | [config.md](config.md) |
|
||||
| `bl config show` | Display current configuration | [config.md](config.md) |
|
||||
| `bl console call` | Call a Bailian console API via the CLI gateway | [console.md](console.md) |
|
||||
| `bl dataset delete` | Delete a dataset file by ID | [dataset.md](dataset.md) |
|
||||
| `bl dataset get` | Get details of a single dataset file | [dataset.md](dataset.md) |
|
||||
| `bl dataset list` | List uploaded dataset files | [dataset.md](dataset.md) |
|
||||
| `bl dataset upload` | Upload a dataset file (.jsonl or .zip) to Bailian | [dataset.md](dataset.md) |
|
||||
| `bl dataset validate` | Locally validate a dataset file (.jsonl or .zip) without uploading | [dataset.md](dataset.md) |
|
||||
| `bl deploy audio create` | Create an audio (TTS) model deployment | [deploy.md](deploy.md) |
|
||||
| `bl deploy delete` | Delete a model deployment (must be STOPPED or FAILED) | [deploy.md](deploy.md) |
|
||||
| `bl deploy get` | Get details of a single model deployment | [deploy.md](deploy.md) |
|
||||
| `bl deploy image create` | Create an image generation model deployment | [deploy.md](deploy.md) |
|
||||
| `bl deploy list` | List model deployments | [deploy.md](deploy.md) |
|
||||
| `bl deploy models` | List models available for deployment | [deploy.md](deploy.md) |
|
||||
| `bl deploy scale` | Scale a deployment's capacity | [deploy.md](deploy.md) |
|
||||
| `bl deploy text create` | Create a text model deployment | [deploy.md](deploy.md) |
|
||||
| `bl deploy update` | Update a deployment's rate limits (rpm_limit / tpm_limit) | [deploy.md](deploy.md) |
|
||||
| `bl file upload` | Upload a local file to DashScope temporary storage (48h) | [file.md](file.md) |
|
||||
| `bl finetune audio create` | Create an audio TTS model fine-tune job (sft-lora) | [finetune.md](finetune.md) |
|
||||
| `bl finetune cancel` | Cancel a running fine-tune job | [finetune.md](finetune.md) |
|
||||
| `bl finetune capability` | Query fine-tune training capability — by model (which training types it supports) or by training type (which models support it) | [finetune.md](finetune.md) |
|
||||
| `bl finetune checkpoints` | List checkpoints produced by a fine-tune job | [finetune.md](finetune.md) |
|
||||
| `bl finetune delete` | Delete a fine-tune job record | [finetune.md](finetune.md) |
|
||||
| `bl finetune export` | Publish a checkpoint as a deployable model | [finetune.md](finetune.md) |
|
||||
| `bl finetune get` | Get details of a single fine-tune job | [finetune.md](finetune.md) |
|
||||
| `bl finetune image create` | Create an image generation model fine-tune job (sft-lora) | [finetune.md](finetune.md) |
|
||||
| `bl finetune list` | List fine-tune jobs | [finetune.md](finetune.md) |
|
||||
| `bl finetune logs` | Fetch training logs for a fine-tune job | [finetune.md](finetune.md) |
|
||||
| `bl finetune text create` | Create a text model fine-tune job (sft \| sft-lora \| dpo \| dpo-lora \| cpt) | [finetune.md](finetune.md) |
|
||||
| `bl finetune watch` | Probe a fine-tune job's status (default: single non-blocking fetch). Pass --follow to poll until terminal. | [finetune.md](finetune.md) |
|
||||
| `bl image edit` | Edit an existing image with text instructions (Qwen-Image) | [image.md](image.md) |
|
||||
| `bl image generate` | Generate images (Qwen-Image / wan2.x) | [image.md](image.md) |
|
||||
| `bl knowledge chat` | Chat with a Bailian knowledge base (RAG Q&A with streaming) | [knowledge.md](knowledge.md) |
|
||||
| `bl knowledge retrieve` | Retrieve from a Bailian knowledge base (deprecated, use `search` instead) | [knowledge.md](knowledge.md) |
|
||||
| `bl knowledge search` | Search a Bailian knowledge base (RAG semantic retrieval) | [knowledge.md](knowledge.md) |
|
||||
| `bl mcp call` | Call a tool on an MCP server (tools/call) | [mcp.md](mcp.md) |
|
||||
| `bl mcp list` | List MCP servers activated under your Bailian account | [mcp.md](mcp.md) |
|
||||
| `bl mcp tools` | List tools exposed by an MCP server (tools/list) | [mcp.md](mcp.md) |
|
||||
| `bl memory add` | Add memory from messages or custom content | [memory.md](memory.md) |
|
||||
| `bl memory delete` | Delete a memory node | [memory.md](memory.md) |
|
||||
| `bl memory list` | List memory nodes for a user | [memory.md](memory.md) |
|
||||
| `bl memory profile create` | Create a user profile schema for memory profiling | [memory.md](memory.md) |
|
||||
| `bl memory profile get` | Get user profile by schema ID and user ID | [memory.md](memory.md) |
|
||||
| `bl memory search` | Search memory nodes by query or messages | [memory.md](memory.md) |
|
||||
| `bl memory update` | Update a memory node content | [memory.md](memory.md) |
|
||||
| `bl omni` | Multimodal chat with text + audio output (Qwen-Omni) | [omni.md](omni.md) |
|
||||
| `bl pipeline run` | Run a pipeline workflow definition | [pipeline.md](pipeline.md) |
|
||||
| `bl pipeline validate` | Validate a pipeline definition without executing | [pipeline.md](pipeline.md) |
|
||||
| `bl quota check` | Check current usage against rate limits | [quota.md](quota.md) |
|
||||
| `bl quota history` | View quota change history | [quota.md](quota.md) |
|
||||
| `bl quota list` | View model RPM/TPM rate limits | [quota.md](quota.md) |
|
||||
| `bl quota request` | Request a temporary quota increase | [quota.md](quota.md) |
|
||||
| `bl search web` | Search the web using DashScope MCP WebSearch service | [search.md](search.md) |
|
||||
| `bl speech recognize` | Recognize speech from audio files (FunAudio-ASR) | [speech.md](speech.md) |
|
||||
| `bl speech synthesize` | Synthesize speech from text (CosyVoice TTS) | [speech.md](speech.md) |
|
||||
| `bl text chat` | Send a chat completion (OpenAI compatible, DashScope) | [text.md](text.md) |
|
||||
| `bl token-plan add-member` | Add a member to a Token Plan organization | [token-plan.md](token-plan.md) |
|
||||
| `bl token-plan assign-seats` | Batch assign Token Plan seats to members | [token-plan.md](token-plan.md) |
|
||||
| `bl token-plan create-key` | Create a Token Plan API key for a seat | [token-plan.md](token-plan.md) |
|
||||
| `bl token-plan list-seats` | List Token Plan subscription seat details | [token-plan.md](token-plan.md) |
|
||||
| `bl update` | Update the CLI to the latest version | [update.md](update.md) |
|
||||
| `bl usage free` | Query free-tier quota for models (all models if --model is omitted) | [usage.md](usage.md) |
|
||||
| `bl usage freetier` | Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable | [usage.md](usage.md) |
|
||||
| `bl usage stats` | Query model usage statistics | [usage.md](usage.md) |
|
||||
| `bl video download` | Download a completed video by task ID | [video.md](video.md) |
|
||||
| `bl video edit` | Edit a video with happyhorse-1.0-video-edit (style transfer, object replacement, etc.) | [video.md](video.md) |
|
||||
| `bl video generate` | Generate a video from text or image (happyhorse-1.1-t2v / happyhorse-1.1-i2v / wan2.6-t2v) | [video.md](video.md) |
|
||||
| `bl video ref` | Reference-to-video generation (happyhorse-1.1-r2v / wan2.6-r2v): multi-subject, multi-shot with voice | [video.md](video.md) |
|
||||
| `bl video task get` | Query async task status | [video.md](video.md) |
|
||||
| `bl vision describe` | Describe an image or video using Qwen-VL | [vision.md](vision.md) |
|
||||
| `bl workspace list` | List all workspaces | [workspace.md](workspace.md) |
|
||||
| Command | Description | Detail |
|
||||
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
|
||||
| `bl advisor recommend` | Recommend the best models for your use case (intent analysis → candidate recall → LLM ranking) | [advisor.md](advisor.md) |
|
||||
| `bl app call` | Call a Bailian application (agent or workflow) | [app.md](app.md) |
|
||||
| `bl app list` | List Bailian applications | [app.md](app.md) |
|
||||
| `bl asset-center delete` | Delete assets (soft delete to recycle bin by default) | [asset-center.md](asset-center.md) |
|
||||
| `bl asset-center download` | Get a signed download URL for an asset by ID | [asset-center.md](asset-center.md) |
|
||||
| `bl asset-center favorite` | Add assets to favorites | [asset-center.md](asset-center.md) |
|
||||
| `bl asset-center get` | Get full details of a model-generated asset | [asset-center.md](asset-center.md) |
|
||||
| `bl asset-center list` | List model-generated assets with filters and cursor pagination | [asset-center.md](asset-center.md) |
|
||||
| `bl asset-center oss bind` | Create OSS transfer policy (bind bucket for asset transfer) | [asset-center.md](asset-center.md) |
|
||||
| `bl asset-center oss show` | View current OSS transfer policy | [asset-center.md](asset-center.md) |
|
||||
| `bl asset-center oss slr authorize` | Authorize OSS service-linked role (one-click) | [asset-center.md](asset-center.md) |
|
||||
| `bl asset-center oss unbind` | Delete OSS transfer policy (unbind) | [asset-center.md](asset-center.md) |
|
||||
| `bl asset-center oss update` | Update an existing OSS transfer policy | [asset-center.md](asset-center.md) |
|
||||
| `bl asset-center stats` | Count model-generated assets by type | [asset-center.md](asset-center.md) |
|
||||
| `bl asset-center storage` | View storage quota, usage, and overage pricing | [asset-center.md](asset-center.md) |
|
||||
| `bl asset-center transfer list` | List asset OSS transfer records (filtered by sync status) | [asset-center.md](asset-center.md) |
|
||||
| `bl asset-center unfavorite` | Remove assets from favorites | [asset-center.md](asset-center.md) |
|
||||
| `bl auth login` | Authenticate with API key, console browser login, or OpenAPI AK/SK (credentials can coexist) | [auth.md](auth.md) |
|
||||
| `bl auth logout` | Clear stored credentials | [auth.md](auth.md) |
|
||||
| `bl auth status` | Show current authentication state | [auth.md](auth.md) |
|
||||
| `bl config set` | Set a config value | [config.md](config.md) |
|
||||
| `bl config show` | Display current configuration | [config.md](config.md) |
|
||||
| `bl console call` | Call a Bailian console API via the CLI gateway | [console.md](console.md) |
|
||||
| `bl dataset delete` | Delete a dataset file by ID | [dataset.md](dataset.md) |
|
||||
| `bl dataset get` | Get details of a single dataset file | [dataset.md](dataset.md) |
|
||||
| `bl dataset list` | List uploaded dataset files | [dataset.md](dataset.md) |
|
||||
| `bl dataset upload` | Upload a dataset file (.jsonl or .zip) to Bailian | [dataset.md](dataset.md) |
|
||||
| `bl dataset validate` | Locally validate a dataset file (.jsonl or .zip) without uploading | [dataset.md](dataset.md) |
|
||||
| `bl deploy audio create` | Create an audio (TTS) model deployment | [deploy.md](deploy.md) |
|
||||
| `bl deploy delete` | Delete a model deployment (must be STOPPED or FAILED) | [deploy.md](deploy.md) |
|
||||
| `bl deploy get` | Get details of a single model deployment | [deploy.md](deploy.md) |
|
||||
| `bl deploy image create` | Create an image generation model deployment | [deploy.md](deploy.md) |
|
||||
| `bl deploy list` | List model deployments | [deploy.md](deploy.md) |
|
||||
| `bl deploy models` | List models available for deployment | [deploy.md](deploy.md) |
|
||||
| `bl deploy scale` | Scale a deployment's capacity | [deploy.md](deploy.md) |
|
||||
| `bl deploy text create` | Create a text model deployment | [deploy.md](deploy.md) |
|
||||
| `bl deploy update` | Update a deployment's rate limits (rpm_limit / tpm_limit) | [deploy.md](deploy.md) |
|
||||
| `bl file upload` | Upload a local file to DashScope temporary storage (48h) | [file.md](file.md) |
|
||||
| `bl finetune audio create` | Create an audio TTS model fine-tune job (sft-lora) | [finetune.md](finetune.md) |
|
||||
| `bl finetune cancel` | Cancel a running fine-tune job | [finetune.md](finetune.md) |
|
||||
| `bl finetune capability` | Query fine-tune training capability — by model (which training types it supports) or by training type (which models support it) | [finetune.md](finetune.md) |
|
||||
| `bl finetune checkpoints` | List checkpoints produced by a fine-tune job | [finetune.md](finetune.md) |
|
||||
| `bl finetune delete` | Delete a fine-tune job record | [finetune.md](finetune.md) |
|
||||
| `bl finetune export` | Publish a checkpoint as a deployable model | [finetune.md](finetune.md) |
|
||||
| `bl finetune get` | Get details of a single fine-tune job | [finetune.md](finetune.md) |
|
||||
| `bl finetune image create` | Create an image generation model fine-tune job (sft-lora) | [finetune.md](finetune.md) |
|
||||
| `bl finetune list` | List fine-tune jobs | [finetune.md](finetune.md) |
|
||||
| `bl finetune logs` | Fetch training logs for a fine-tune job | [finetune.md](finetune.md) |
|
||||
| `bl finetune text create` | Create a text model fine-tune job (sft \| sft-lora \| dpo \| dpo-lora \| cpt) | [finetune.md](finetune.md) |
|
||||
| `bl finetune watch` | Probe a fine-tune job's status (default: single non-blocking fetch). Pass --follow to poll until terminal. | [finetune.md](finetune.md) |
|
||||
| `bl image edit` | Edit an existing image with text instructions (Qwen-Image) | [image.md](image.md) |
|
||||
| `bl image generate` | Generate images (Qwen-Image / wan2.x) | [image.md](image.md) |
|
||||
| `bl knowledge chat` | Chat with a Bailian knowledge base (RAG Q&A with streaming) | [knowledge.md](knowledge.md) |
|
||||
| `bl knowledge retrieve` | Retrieve from a Bailian knowledge base (deprecated, use `search` instead) | [knowledge.md](knowledge.md) |
|
||||
| `bl knowledge search` | Search a Bailian knowledge base (RAG semantic retrieval) | [knowledge.md](knowledge.md) |
|
||||
| `bl mcp call` | Call a tool on an MCP server (tools/call) | [mcp.md](mcp.md) |
|
||||
| `bl mcp list` | List MCP servers activated under your Bailian account | [mcp.md](mcp.md) |
|
||||
| `bl mcp tools` | List tools exposed by an MCP server (tools/list) | [mcp.md](mcp.md) |
|
||||
| `bl memory add` | Add memory from messages or custom content | [memory.md](memory.md) |
|
||||
| `bl memory delete` | Delete a memory node | [memory.md](memory.md) |
|
||||
| `bl memory list` | List memory nodes for a user | [memory.md](memory.md) |
|
||||
| `bl memory profile create` | Create a user profile schema for memory profiling | [memory.md](memory.md) |
|
||||
| `bl memory profile get` | Get user profile by schema ID and user ID | [memory.md](memory.md) |
|
||||
| `bl memory search` | Search memory nodes by query or messages | [memory.md](memory.md) |
|
||||
| `bl memory update` | Update a memory node content | [memory.md](memory.md) |
|
||||
| `bl omni` | Multimodal chat with text + audio output (Qwen-Omni) | [omni.md](omni.md) |
|
||||
| `bl pipeline run` | Run a pipeline workflow definition | [pipeline.md](pipeline.md) |
|
||||
| `bl pipeline validate` | Validate a pipeline definition without executing | [pipeline.md](pipeline.md) |
|
||||
| `bl quota check` | Check current usage against rate limits | [quota.md](quota.md) |
|
||||
| `bl quota history` | View quota change history | [quota.md](quota.md) |
|
||||
| `bl quota list` | View model RPM/TPM rate limits | [quota.md](quota.md) |
|
||||
| `bl quota request` | Request a temporary quota increase | [quota.md](quota.md) |
|
||||
| `bl search web` | Search the web using DashScope MCP WebSearch service | [search.md](search.md) |
|
||||
| `bl speech recognize` | Recognize speech from audio files (FunAudio-ASR) | [speech.md](speech.md) |
|
||||
| `bl speech synthesize` | Synthesize speech from text (CosyVoice TTS) | [speech.md](speech.md) |
|
||||
| `bl text chat` | Send a chat completion (OpenAI compatible, DashScope) | [text.md](text.md) |
|
||||
| `bl token-plan add-member` | Add a member to a Token Plan organization | [token-plan.md](token-plan.md) |
|
||||
| `bl token-plan assign-seats` | Batch assign Token Plan seats to members | [token-plan.md](token-plan.md) |
|
||||
| `bl token-plan create-key` | Create a Token Plan API key for a seat | [token-plan.md](token-plan.md) |
|
||||
| `bl token-plan list-seats` | List Token Plan subscription seat details | [token-plan.md](token-plan.md) |
|
||||
| `bl update` | Update the CLI to the latest version | [update.md](update.md) |
|
||||
| `bl usage free` | Query free-tier quota for models (all models if --model is omitted) | [usage.md](usage.md) |
|
||||
| `bl usage freetier` | Enable or disable auto-stop for free-tier models. Enables by default; use --off to disable | [usage.md](usage.md) |
|
||||
| `bl usage stats` | Query model usage statistics | [usage.md](usage.md) |
|
||||
| `bl video download` | Download a completed video by task ID | [video.md](video.md) |
|
||||
| `bl video edit` | Edit a video with happyhorse-1.0-video-edit (style transfer, object replacement, etc.) | [video.md](video.md) |
|
||||
| `bl video generate` | Generate a video from text or image (happyhorse-1.1-t2v / happyhorse-1.1-i2v / wan2.6-t2v) | [video.md](video.md) |
|
||||
| `bl video ref` | Reference-to-video generation (happyhorse-1.1-r2v / wan2.6-r2v): multi-subject, multi-shot with voice | [video.md](video.md) |
|
||||
| `bl video task get` | Query async task status | [video.md](video.md) |
|
||||
| `bl vision describe` | Describe an image or video using Qwen-VL | [vision.md](vision.md) |
|
||||
| `bl workspace list` | List all workspaces | [workspace.md](workspace.md) |
|
||||
|
||||
## By group
|
||||
|
||||
| Group | Commands | Reference |
|
||||
| ------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ |
|
||||
| `advisor` | `recommend` | [advisor.md](advisor.md) |
|
||||
| `app` | `call`, `list` | [app.md](app.md) |
|
||||
| `auth` | `login`, `logout`, `status` | [auth.md](auth.md) |
|
||||
| `config` | `set`, `show` | [config.md](config.md) |
|
||||
| `console` | `call` | [console.md](console.md) |
|
||||
| `dataset` | `delete`, `get`, `list`, `upload`, `validate` | [dataset.md](dataset.md) |
|
||||
| `deploy` | `audio create`, `delete`, `get`, `image create`, `list`, `models`, `scale`, `text create`, `update` | [deploy.md](deploy.md) |
|
||||
| `file` | `upload` | [file.md](file.md) |
|
||||
| `finetune` | `audio create`, `cancel`, `capability`, `checkpoints`, `delete`, `export`, `get`, `image create`, `list`, `logs`, `text create`, `watch` | [finetune.md](finetune.md) |
|
||||
| `image` | `edit`, `generate` | [image.md](image.md) |
|
||||
| `knowledge` | `chat`, `retrieve`, `search` | [knowledge.md](knowledge.md) |
|
||||
| `mcp` | `call`, `list`, `tools` | [mcp.md](mcp.md) |
|
||||
| `memory` | `add`, `delete`, `list`, `profile create`, `profile get`, `search`, `update` | [memory.md](memory.md) |
|
||||
| `omni` | `(root)` | [omni.md](omni.md) |
|
||||
| `pipeline` | `run`, `validate` | [pipeline.md](pipeline.md) |
|
||||
| `quota` | `check`, `history`, `list`, `request` | [quota.md](quota.md) |
|
||||
| `search` | `web` | [search.md](search.md) |
|
||||
| `speech` | `recognize`, `synthesize` | [speech.md](speech.md) |
|
||||
| `text` | `chat` | [text.md](text.md) |
|
||||
| `token-plan` | `add-member`, `assign-seats`, `create-key`, `list-seats` | [token-plan.md](token-plan.md) |
|
||||
| `update` | `(root)` | [update.md](update.md) |
|
||||
| `usage` | `free`, `freetier`, `stats` | [usage.md](usage.md) |
|
||||
| `video` | `download`, `edit`, `generate`, `ref`, `task get` | [video.md](video.md) |
|
||||
| `vision` | `describe` | [vision.md](vision.md) |
|
||||
| `workspace` | `list` | [workspace.md](workspace.md) |
|
||||
| Group | Commands | Reference |
|
||||
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- |
|
||||
| `advisor` | `recommend` | [advisor.md](advisor.md) |
|
||||
| `app` | `call`, `list` | [app.md](app.md) |
|
||||
| `asset-center` | `delete`, `download`, `favorite`, `get`, `list`, `oss bind`, `oss show`, `oss slr authorize`, `oss unbind`, `oss update`, `stats`, `storage`, `transfer list`, `unfavorite` | [asset-center.md](asset-center.md) |
|
||||
| `auth` | `login`, `logout`, `status` | [auth.md](auth.md) |
|
||||
| `config` | `set`, `show` | [config.md](config.md) |
|
||||
| `console` | `call` | [console.md](console.md) |
|
||||
| `dataset` | `delete`, `get`, `list`, `upload`, `validate` | [dataset.md](dataset.md) |
|
||||
| `deploy` | `audio create`, `delete`, `get`, `image create`, `list`, `models`, `scale`, `text create`, `update` | [deploy.md](deploy.md) |
|
||||
| `file` | `upload` | [file.md](file.md) |
|
||||
| `finetune` | `audio create`, `cancel`, `capability`, `checkpoints`, `delete`, `export`, `get`, `image create`, `list`, `logs`, `text create`, `watch` | [finetune.md](finetune.md) |
|
||||
| `image` | `edit`, `generate` | [image.md](image.md) |
|
||||
| `knowledge` | `chat`, `retrieve`, `search` | [knowledge.md](knowledge.md) |
|
||||
| `mcp` | `call`, `list`, `tools` | [mcp.md](mcp.md) |
|
||||
| `memory` | `add`, `delete`, `list`, `profile create`, `profile get`, `search`, `update` | [memory.md](memory.md) |
|
||||
| `omni` | `(root)` | [omni.md](omni.md) |
|
||||
| `pipeline` | `run`, `validate` | [pipeline.md](pipeline.md) |
|
||||
| `quota` | `check`, `history`, `list`, `request` | [quota.md](quota.md) |
|
||||
| `search` | `web` | [search.md](search.md) |
|
||||
| `speech` | `recognize`, `synthesize` | [speech.md](speech.md) |
|
||||
| `text` | `chat` | [text.md](text.md) |
|
||||
| `token-plan` | `add-member`, `assign-seats`, `create-key`, `list-seats` | [token-plan.md](token-plan.md) |
|
||||
| `update` | `(root)` | [update.md](update.md) |
|
||||
| `usage` | `free`, `freetier`, `stats` | [usage.md](usage.md) |
|
||||
| `video` | `download`, `edit`, `generate`, `ref`, `task get` | [video.md](video.md) |
|
||||
| `vision` | `describe` | [vision.md](vision.md) |
|
||||
| `workspace` | `list` | [workspace.md](workspace.md) |
|
||||
|
||||
## Global flags
|
||||
|
||||
|
||||
@@ -0,0 +1,523 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Asset-center Phase 2: reversible write ops + advanced read scenarios.
|
||||
*/
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { existsSync, unlinkSync, statSync } from "node:fs";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
import { writeFileSync } from "node:fs";
|
||||
|
||||
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const cliDir = join(root, "packages/cli");
|
||||
const mainTs = join(cliDir, "src/main.ts");
|
||||
const workspaceId = process.env.BAILIAN_WORKSPACE_ID ?? "llm-0xvms4kqhbqjlg8s";
|
||||
|
||||
function runBl(args, { timeout = 180_000 } = {}) {
|
||||
const start = Date.now();
|
||||
try {
|
||||
const stdout = execFileSync("pnpm", ["exec", "tsx", mainTs, ...args], {
|
||||
cwd: cliDir,
|
||||
encoding: "utf8",
|
||||
timeout,
|
||||
env: { ...process.env, BAILIAN_WORKSPACE_ID: workspaceId },
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
return { exitCode: 0, stdout: stdout.trim(), stderr: "", durationMs: Date.now() - start };
|
||||
} catch (error) {
|
||||
return {
|
||||
exitCode: error.status ?? 1,
|
||||
stdout: (error.stdout ?? "").trim(),
|
||||
stderr: (error.stderr ?? "").trim(),
|
||||
durationMs: Date.now() - start,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function parseJson(text) {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function status(exitCode, expected = 0) {
|
||||
return exitCode === expected ? "✅ PASS" : "❌ FAIL";
|
||||
}
|
||||
|
||||
function pickUnfavoritedImage(listJson) {
|
||||
return listJson?.items?.find((item) => item.asset_type === "IMAGE" && item.favorited === false);
|
||||
}
|
||||
|
||||
const results = [];
|
||||
|
||||
function record(category, command, mode, result, expect, summary) {
|
||||
const pass = result.exitCode === expect;
|
||||
results.push({ category, command, mode, ...result, expect, pass, summary });
|
||||
console.log(` ${pass ? "✅" : "❌"} ${command} — ${summary}`);
|
||||
return pass;
|
||||
}
|
||||
|
||||
console.log(`\n🔍 Asset Center Phase 2 — workspace: ${workspaceId}\n`);
|
||||
|
||||
// --- Setup: find test assets ---
|
||||
const imageList = runBl([
|
||||
"asset-center",
|
||||
"list",
|
||||
"--type",
|
||||
"IMAGE",
|
||||
"--page-size",
|
||||
"10",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
const imageJson = parseJson(imageList.stdout);
|
||||
const testImage = pickUnfavoritedImage(imageJson);
|
||||
const testImageId = testImage?.asset_id ?? imageJson?.items?.[0]?.asset_id;
|
||||
|
||||
const videoList = runBl([
|
||||
"asset-center",
|
||||
"list",
|
||||
"--type",
|
||||
"VIDEO",
|
||||
"--page-size",
|
||||
"1",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
const testVideoId = parseJson(videoList.stdout)?.items?.[0]?.asset_id;
|
||||
|
||||
console.log(`📦 测试 IMAGE: ${testImageId}`);
|
||||
console.log(`📦 测试 VIDEO: ${testVideoId}\n`);
|
||||
|
||||
// 1. Download IMAGE to /tmp
|
||||
const downloadPath = `/tmp/asset-center-test-${testImageId}.png`;
|
||||
if (existsSync(downloadPath)) unlinkSync(downloadPath);
|
||||
|
||||
const dlResult = runBl(
|
||||
[
|
||||
"asset-center",
|
||||
"download",
|
||||
"--id",
|
||||
testImageId,
|
||||
"--out",
|
||||
downloadPath,
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
{ timeout: 300_000 },
|
||||
);
|
||||
|
||||
let dlSummary = "download failed";
|
||||
if (dlResult.exitCode === 0) {
|
||||
const dlJson = parseJson(dlResult.stdout);
|
||||
const fileExists = existsSync(downloadPath);
|
||||
const fileSize = fileExists ? statSync(downloadPath).size : 0;
|
||||
dlSummary = fileExists
|
||||
? `saved ${downloadPath} (${fileSize} bytes, reported ${dlJson?.size ?? "?"})`
|
||||
: `exit 0 but file missing`;
|
||||
if (fileExists) unlinkSync(downloadPath);
|
||||
}
|
||||
record("下载", "asset-center download (IMAGE)", "真实调用", dlResult, 0, dlSummary);
|
||||
|
||||
// 2. Get with --include-download-url
|
||||
const getUrlResult = runBl([
|
||||
"asset-center",
|
||||
"get",
|
||||
"--asset-id",
|
||||
testImageId,
|
||||
"--include-download-url",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
const getUrlJson = parseJson(getUrlResult.stdout);
|
||||
const hasDownloadUrl = Boolean(getUrlJson?.download_url ?? getUrlJson?.downloadUrl);
|
||||
record(
|
||||
"查询",
|
||||
"asset-center get --include-download-url",
|
||||
"真实调用",
|
||||
getUrlResult,
|
||||
0,
|
||||
hasDownloadUrl ? "download_url present" : `no download_url in response`,
|
||||
);
|
||||
|
||||
// 3. List with --include-download-url
|
||||
const listUrlResult = runBl([
|
||||
"asset-center",
|
||||
"list",
|
||||
"--type",
|
||||
"IMAGE",
|
||||
"--page-size",
|
||||
"2",
|
||||
"--include-download-url",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
const listUrlJson = parseJson(listUrlResult.stdout);
|
||||
const listHasUrl = listUrlJson?.items?.some((item) => item.download_url);
|
||||
record(
|
||||
"查询",
|
||||
"asset-center list --include-download-url",
|
||||
"真实调用",
|
||||
listUrlResult,
|
||||
0,
|
||||
listHasUrl ? "items contain download_url" : "no download_url in items",
|
||||
);
|
||||
|
||||
// 4. Pagination: page 1 → next-token → page 2
|
||||
const page1 = parseJson(
|
||||
runBl([
|
||||
"asset-center",
|
||||
"list",
|
||||
"--page-size",
|
||||
"3",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]).stdout,
|
||||
);
|
||||
const nextToken = page1?.next_token;
|
||||
let pageSummary = "no next_token (single page)";
|
||||
if (nextToken != null) {
|
||||
const page2Result = runBl([
|
||||
"asset-center",
|
||||
"list",
|
||||
"--page-size",
|
||||
"3",
|
||||
"--next-token",
|
||||
String(nextToken),
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
const page2 = parseJson(page2Result.stdout);
|
||||
const ids1 = new Set(page1.items.map((item) => item.asset_id));
|
||||
const overlap = page2?.items?.filter((item) => ids1.has(item.asset_id)).length ?? 0;
|
||||
pageSummary =
|
||||
page2Result.exitCode === 0
|
||||
? `page2=${page2?.items?.length ?? 0} items, overlap=${overlap}, has_pre=${page2?.has_pre}`
|
||||
: `page2 failed exit ${page2Result.exitCode}`;
|
||||
record("查询", "asset-center list --next-token", "真实调用", page2Result, 0, pageSummary);
|
||||
} else {
|
||||
record(
|
||||
"查询",
|
||||
"asset-center list --next-token",
|
||||
"跳过",
|
||||
{ exitCode: 0, durationMs: 0, stdout: "", stderr: "" },
|
||||
0,
|
||||
pageSummary,
|
||||
);
|
||||
}
|
||||
|
||||
// 5. Stats filters
|
||||
const statsImage = runBl([
|
||||
"asset-center",
|
||||
"stats",
|
||||
"--type",
|
||||
"IMAGE",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
const statsImageJson = parseJson(statsImage.stdout);
|
||||
record(
|
||||
"统计",
|
||||
"asset-center stats --type IMAGE",
|
||||
"真实调用",
|
||||
statsImage,
|
||||
0,
|
||||
`image=${statsImageJson?.image_count ?? "?"}, total=${statsImageJson?.total_count ?? "?"}`,
|
||||
);
|
||||
|
||||
const statsSyncFailed = runBl([
|
||||
"asset-center",
|
||||
"stats",
|
||||
"--sync-failed",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
const statsSyncJson = parseJson(statsSyncFailed.stdout);
|
||||
record(
|
||||
"统计",
|
||||
"asset-center stats --sync-failed",
|
||||
"真实调用",
|
||||
statsSyncFailed,
|
||||
0,
|
||||
`total=${statsSyncJson?.total_count ?? "?"}, sync_failed=${statsSyncJson?.sync_failed_count ?? "?"}`,
|
||||
);
|
||||
|
||||
// 6. Recycle bin (read-only)
|
||||
const recycleResult = runBl([
|
||||
"asset-center",
|
||||
"list",
|
||||
"--recycle-bin",
|
||||
"--page-size",
|
||||
"5",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
const recycleJson = parseJson(recycleResult.stdout);
|
||||
record(
|
||||
"查询",
|
||||
"asset-center list --recycle-bin",
|
||||
"真实调用",
|
||||
recycleResult,
|
||||
0,
|
||||
`${recycleJson?.items?.length ?? 0} soft-deleted item(s)`,
|
||||
);
|
||||
|
||||
// 7. Text output format
|
||||
const textResult = runBl([
|
||||
"asset-center",
|
||||
"list",
|
||||
"--page-size",
|
||||
"2",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
const textHasTable = textResult.stdout.includes("ASSET_ID") || textResult.stdout.includes("asset_");
|
||||
record(
|
||||
"输出",
|
||||
"asset-center list (text)",
|
||||
"真实调用",
|
||||
textResult,
|
||||
0,
|
||||
textHasTable
|
||||
? `${textResult.stdout.split("\n").filter(Boolean).length} lines table output`
|
||||
: "unexpected text format",
|
||||
);
|
||||
|
||||
// 8. Favorite → verify → unfavorite → verify (round-trip)
|
||||
if (testImageId) {
|
||||
const favResult = runBl([
|
||||
"asset-center",
|
||||
"favorite",
|
||||
"--id",
|
||||
testImageId,
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
const favJson = parseJson(favResult.stdout);
|
||||
record(
|
||||
"收藏",
|
||||
"asset-center favorite (真实)",
|
||||
"真实调用",
|
||||
favResult,
|
||||
0,
|
||||
`affected=${favJson?.affected_count ?? "?"}`,
|
||||
);
|
||||
|
||||
const verifyFav = runBl([
|
||||
"asset-center",
|
||||
"get",
|
||||
"--asset-id",
|
||||
testImageId,
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
const verifyFavJson = parseJson(verifyFav.stdout);
|
||||
const isFavorited = verifyFavJson?.favorited === true;
|
||||
record(
|
||||
"收藏",
|
||||
"get 验证 favorited=true",
|
||||
"真实调用",
|
||||
verifyFav,
|
||||
0,
|
||||
isFavorited ? "favorited=true ✓" : `favorited=${verifyFavJson?.favorited}`,
|
||||
);
|
||||
|
||||
const favListResult = runBl([
|
||||
"asset-center",
|
||||
"list",
|
||||
"--favorited",
|
||||
"--page-size",
|
||||
"20",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
const favListJson = parseJson(favListResult.stdout);
|
||||
const inFavList = favListJson?.items?.some((item) => item.asset_id === testImageId);
|
||||
record(
|
||||
"查询",
|
||||
"list --favorited 含测试资产",
|
||||
"真实调用",
|
||||
favListResult,
|
||||
0,
|
||||
inFavList ? "found in favorited list" : "NOT in favorited list",
|
||||
);
|
||||
|
||||
const unfavResult = runBl([
|
||||
"asset-center",
|
||||
"unfavorite",
|
||||
"--id",
|
||||
testImageId,
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
const unfavJson = parseJson(unfavResult.stdout);
|
||||
record(
|
||||
"收藏",
|
||||
"asset-center unfavorite (真实)",
|
||||
"真实调用",
|
||||
unfavResult,
|
||||
0,
|
||||
`affected=${unfavJson?.affected_count ?? "?"}`,
|
||||
);
|
||||
|
||||
const verifyUnfav = runBl([
|
||||
"asset-center",
|
||||
"get",
|
||||
"--asset-id",
|
||||
testImageId,
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
const verifyUnfavJson = parseJson(verifyUnfav.stdout);
|
||||
const isUnfavorited = verifyUnfavJson?.favorited === false;
|
||||
record(
|
||||
"收藏",
|
||||
"get 验证 favorited=false (恢复)",
|
||||
"真实调用",
|
||||
verifyUnfav,
|
||||
0,
|
||||
isUnfavorited ? "favorited=false ✓" : `favorited=${verifyUnfavJson?.favorited}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 9. Batch favorite (2 ids) round-trip
|
||||
const twoImages = imageJson?.items?.filter((item) => !item.favorited).slice(0, 2) ?? [];
|
||||
if (twoImages.length === 2) {
|
||||
const [first, second] = twoImages;
|
||||
const batchFav = runBl([
|
||||
"asset-center",
|
||||
"favorite",
|
||||
"--id",
|
||||
first.asset_id,
|
||||
"--id",
|
||||
second.asset_id,
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
record("收藏", "favorite 批量 (--id x2)", "真实调用", batchFav, 0, summarizeBatch(batchFav));
|
||||
const batchUnfav = runBl([
|
||||
"asset-center",
|
||||
"unfavorite",
|
||||
"--id",
|
||||
first.asset_id,
|
||||
"--id",
|
||||
second.asset_id,
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
record(
|
||||
"收藏",
|
||||
"unfavorite 批量 (--id x2)",
|
||||
"真实调用",
|
||||
batchUnfav,
|
||||
0,
|
||||
summarizeBatch(batchUnfav),
|
||||
);
|
||||
}
|
||||
|
||||
// 10. Get non-existent asset — server error passthrough (exit 1)
|
||||
const badGet = runBl([
|
||||
"asset-center",
|
||||
"get",
|
||||
"--asset-id",
|
||||
"asset_nonexistent_00000000",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
const badGetErr = parseJson(badGet.stderr);
|
||||
record(
|
||||
"边界",
|
||||
"get 不存在的 asset-id",
|
||||
"真实调用",
|
||||
badGet,
|
||||
1,
|
||||
badGetErr?.error?.message === "资产不存在"
|
||||
? 'exit 1, 服务端错误原样透传: "资产不存在"'
|
||||
: `exit ${badGet.exitCode}, msg=${badGetErr?.error?.message ?? badGet.stderr.slice(0, 60)}`,
|
||||
);
|
||||
|
||||
function summarizeBatch(result) {
|
||||
const json = parseJson(result.stdout);
|
||||
return json ? `affected=${json.affected_count ?? "?"}` : result.stdout.slice(0, 60);
|
||||
}
|
||||
|
||||
// --- Report ---
|
||||
const passed = results.filter((row) => row.pass).length;
|
||||
const failed = results.filter((row) => !row.pass).length;
|
||||
|
||||
const lines = [
|
||||
`# Asset Center 命令测试报告 — Phase 2`,
|
||||
``,
|
||||
`- **测试时间**: ${new Date().toISOString().replace("T", " ").slice(0, 19)} (UTC)`,
|
||||
`- **Workspace**: \`${workspaceId}\``,
|
||||
`- **测试 IMAGE**: \`${testImageId ?? "n/a"}\``,
|
||||
`- **测试 VIDEO**: \`${testVideoId ?? "n/a"}\``,
|
||||
`- **策略**: 可逆写操作(favorite/unfavorite 往返);download 到 /tmp 后删除;其余只读`,
|
||||
`- **汇总**: ${passed} 通过 / ${failed} 失败 / ${results.length} 总计`,
|
||||
``,
|
||||
`> Phase 1 报告见同目录 [TEST-REPORT.md](./TEST-REPORT.md)(24 项 dry-run + 只读基础验证)`,
|
||||
``,
|
||||
`## Phase 2 测试结果`,
|
||||
``,
|
||||
`| # | 分类 | 命令 | 模式 | 状态 | Exit | 耗时 | 结果摘要 |`,
|
||||
`|---|------|------|------|------|------|------|----------|`,
|
||||
];
|
||||
|
||||
results.forEach((row, index) => {
|
||||
lines.push(
|
||||
`| ${index + 1} | ${row.category} | \`${row.command}\` | ${row.mode} | ${status(row.exitCode, row.expect)} | ${row.exitCode} | ${row.durationMs}ms | ${(row.summary ?? "").replace(/\|/g, "\\|")} |`,
|
||||
);
|
||||
});
|
||||
|
||||
if (failed > 0) {
|
||||
lines.push(``, `## 失败详情`, ``);
|
||||
for (const row of results.filter((row) => !row.pass)) {
|
||||
lines.push(`### ${row.command}`, ``);
|
||||
lines.push(`**stderr:**`, `\`\`\``, row.stderr || "(empty)", `\`\`\``, ``);
|
||||
lines.push(`**stdout:**`, `\`\`\``, row.stdout?.slice(0, 800) || "(empty)", `\`\`\``, ``);
|
||||
}
|
||||
}
|
||||
|
||||
const reportPath = join(root, "packages/commands/src/commands/asset-center/TEST-REPORT-PHASE2.md");
|
||||
writeFileSync(reportPath, lines.join("\n"));
|
||||
console.log(`\n📄 Report: ${reportPath}`);
|
||||
console.log(`✅ ${passed} passed, ❌ ${failed} failed\n`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
@@ -0,0 +1,507 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Asset-center command integration test runner.
|
||||
* Read-only commands: real API. Write commands: --dry-run only.
|
||||
*/
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
|
||||
const root = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const cliDir = join(root, "packages/cli");
|
||||
const mainTs = join(cliDir, "src/main.ts");
|
||||
const workspaceId = process.env.BAILIAN_WORKSPACE_ID ?? "llm-0xvms4kqhbqjlg8s";
|
||||
|
||||
function runBl(args, { timeout = 120_000 } = {}) {
|
||||
const start = Date.now();
|
||||
try {
|
||||
const stdout = execFileSync("pnpm", ["exec", "tsx", mainTs, ...args], {
|
||||
cwd: cliDir,
|
||||
encoding: "utf8",
|
||||
timeout,
|
||||
env: { ...process.env, BAILIAN_WORKSPACE_ID: workspaceId },
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
return {
|
||||
exitCode: 0,
|
||||
stdout: stdout.trim(),
|
||||
stderr: "",
|
||||
durationMs: Date.now() - start,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
exitCode: error.status ?? 1,
|
||||
stdout: (error.stdout ?? "").trim(),
|
||||
stderr: (error.stderr ?? "").trim(),
|
||||
durationMs: Date.now() - start,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function parseJson(text) {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function summarizeOutput(result) {
|
||||
const json = parseJson(result.stdout);
|
||||
if (json) {
|
||||
if (json.api) return `dry-run → ${json.api.split(".").pop()}`;
|
||||
if (json.items?.length !== undefined)
|
||||
return `${json.items.length} item(s)${json.next_token != null ? `, next=${json.next_token}` : ""}`;
|
||||
if (json.item) return `asset=${json.item.asset_id ?? json.item.assetId ?? "?"}`;
|
||||
if (json.total_count != null) return `total=${json.total_count}`;
|
||||
if (json.used_bytes != null)
|
||||
return `used=${json.used_bytes} / quota=${json.quota_bytes ?? "?"}`;
|
||||
if (json.policy_id ?? json.policyId) return `policy=${json.policy_id ?? json.policyId}`;
|
||||
if (json.authorized != null) return `authorized=${json.authorized}`;
|
||||
if (json.saved) return `saved=${json.saved}`;
|
||||
const keys = Object.keys(json).slice(0, 4).join(", ");
|
||||
return keys ? `{${keys}}` : "json ok";
|
||||
}
|
||||
const lines = result.stdout.split("\n").filter(Boolean);
|
||||
if (lines.length === 0 && result.stderr) {
|
||||
const errLine = result.stderr
|
||||
.split("\n")
|
||||
.find((line) => line.includes("Error") || line.includes("error"));
|
||||
return errLine?.slice(0, 80) ?? result.stderr.slice(0, 80);
|
||||
}
|
||||
if (lines.length <= 3) return lines.join(" | ").slice(0, 100);
|
||||
return `${lines.length} lines, first: ${lines[0].slice(0, 60)}`;
|
||||
}
|
||||
|
||||
function status(exitCode, expected = 0) {
|
||||
if (exitCode === expected) return "✅ PASS";
|
||||
return "❌ FAIL";
|
||||
}
|
||||
|
||||
// Fetch sample asset ID
|
||||
const listResult = runBl([
|
||||
"asset-center",
|
||||
"list",
|
||||
"--page-size",
|
||||
"1",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
]);
|
||||
const listJson = parseJson(listResult.stdout);
|
||||
const sampleAssetId = listJson?.items?.[0]?.asset_id ?? "asset_df026105d2274ff9b8c824058fa23d60";
|
||||
|
||||
const cases = [
|
||||
// --- Read-only (real API) ---
|
||||
{
|
||||
category: "查询",
|
||||
command: "asset-center list",
|
||||
mode: "真实调用",
|
||||
args: [
|
||||
"asset-center",
|
||||
"list",
|
||||
"--page-size",
|
||||
"3",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "查询",
|
||||
command: "asset-center list --type IMAGE",
|
||||
mode: "真实调用",
|
||||
args: [
|
||||
"asset-center",
|
||||
"list",
|
||||
"--type",
|
||||
"IMAGE",
|
||||
"--page-size",
|
||||
"2",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "查询",
|
||||
command: "asset-center get",
|
||||
mode: "真实调用",
|
||||
args: [
|
||||
"asset-center",
|
||||
"get",
|
||||
"--asset-id",
|
||||
sampleAssetId,
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "统计",
|
||||
command: "asset-center stats",
|
||||
mode: "真实调用",
|
||||
args: ["asset-center", "stats", "--output", "json", "--workspace-id", workspaceId],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "统计",
|
||||
command: "asset-center storage",
|
||||
mode: "真实调用",
|
||||
args: ["asset-center", "storage", "--output", "json", "--workspace-id", workspaceId],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "转存",
|
||||
command: "asset-center transfer list",
|
||||
mode: "真实调用",
|
||||
args: [
|
||||
"asset-center",
|
||||
"transfer",
|
||||
"list",
|
||||
"--page-size",
|
||||
"5",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "OSS",
|
||||
command: "asset-center oss show",
|
||||
mode: "真实调用",
|
||||
args: ["asset-center", "oss", "show", "--output", "json", "--workspace-id", workspaceId],
|
||||
expect: 0,
|
||||
},
|
||||
|
||||
// --- Dry-run (write / side-effect) ---
|
||||
{
|
||||
category: "查询",
|
||||
command: "asset-center list --dry-run",
|
||||
mode: "dry-run",
|
||||
args: ["asset-center", "list", "--dry-run", "--output", "json", "--workspace-id", workspaceId],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "查询",
|
||||
command: "asset-center get --dry-run",
|
||||
mode: "dry-run",
|
||||
args: [
|
||||
"asset-center",
|
||||
"get",
|
||||
"--asset-id",
|
||||
sampleAssetId,
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "收藏",
|
||||
command: "asset-center favorite",
|
||||
mode: "dry-run",
|
||||
args: [
|
||||
"asset-center",
|
||||
"favorite",
|
||||
"--id",
|
||||
sampleAssetId,
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "收藏",
|
||||
command: "asset-center unfavorite",
|
||||
mode: "dry-run",
|
||||
args: [
|
||||
"asset-center",
|
||||
"unfavorite",
|
||||
"--id",
|
||||
sampleAssetId,
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "删除",
|
||||
command: "asset-center delete",
|
||||
mode: "dry-run",
|
||||
args: [
|
||||
"asset-center",
|
||||
"delete",
|
||||
"--id",
|
||||
sampleAssetId,
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "下载",
|
||||
command: "asset-center download",
|
||||
mode: "dry-run",
|
||||
args: [
|
||||
"asset-center",
|
||||
"download",
|
||||
"--id",
|
||||
sampleAssetId,
|
||||
"--out",
|
||||
"/tmp/asset-test-download.mp4",
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "统计",
|
||||
command: "asset-center stats --dry-run",
|
||||
mode: "dry-run",
|
||||
args: ["asset-center", "stats", "--dry-run", "--output", "json", "--workspace-id", workspaceId],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "统计",
|
||||
command: "asset-center storage --dry-run",
|
||||
mode: "dry-run",
|
||||
args: [
|
||||
"asset-center",
|
||||
"storage",
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "OSS",
|
||||
command: "asset-center oss slr authorize",
|
||||
mode: "dry-run",
|
||||
args: [
|
||||
"asset-center",
|
||||
"oss",
|
||||
"slr",
|
||||
"authorize",
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "OSS",
|
||||
command: "asset-center oss bind",
|
||||
mode: "dry-run",
|
||||
args: [
|
||||
"asset-center",
|
||||
"oss",
|
||||
"bind",
|
||||
"--bucket",
|
||||
"test-bucket",
|
||||
"--region",
|
||||
"cn-hangzhou",
|
||||
"--path-prefix",
|
||||
"assets/",
|
||||
"--policy",
|
||||
"ALL",
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "OSS",
|
||||
command: "asset-center oss update",
|
||||
mode: "dry-run",
|
||||
args: [
|
||||
"asset-center",
|
||||
"oss",
|
||||
"update",
|
||||
"--policy-id",
|
||||
"policy-test-001",
|
||||
"--bucket",
|
||||
"test-bucket",
|
||||
"--region",
|
||||
"cn-hangzhou",
|
||||
"--path-prefix",
|
||||
"assets/",
|
||||
"--policy",
|
||||
"BEFORE_DAYS",
|
||||
"--before-days",
|
||||
"30",
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
expect: 0,
|
||||
},
|
||||
{
|
||||
category: "OSS",
|
||||
command: "asset-center oss unbind",
|
||||
mode: "dry-run",
|
||||
args: [
|
||||
"asset-center",
|
||||
"oss",
|
||||
"unbind",
|
||||
"--policy-id",
|
||||
"policy-test-001",
|
||||
"--dry-run",
|
||||
"--output",
|
||||
"json",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
expect: 0,
|
||||
},
|
||||
|
||||
// --- Validation (expect usage error) ---
|
||||
{
|
||||
category: "校验",
|
||||
command: "asset-center get (缺 asset-id)",
|
||||
mode: "参数校验",
|
||||
args: ["asset-center", "get", "--quiet", "--workspace-id", workspaceId],
|
||||
expect: 2,
|
||||
},
|
||||
{
|
||||
category: "校验",
|
||||
command: "asset-center favorite (缺 --id)",
|
||||
mode: "参数校验",
|
||||
args: ["asset-center", "favorite", "--quiet", "--workspace-id", workspaceId],
|
||||
expect: 2,
|
||||
},
|
||||
{
|
||||
category: "校验",
|
||||
command: "asset-center download (缺 --out)",
|
||||
mode: "参数校验",
|
||||
args: [
|
||||
"asset-center",
|
||||
"download",
|
||||
"--id",
|
||||
sampleAssetId,
|
||||
"--quiet",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
expect: 2,
|
||||
},
|
||||
{
|
||||
category: "校验",
|
||||
command: "asset-center oss bind (BEFORE_DAYS 缺 before-days)",
|
||||
mode: "参数校验",
|
||||
args: [
|
||||
"asset-center",
|
||||
"oss",
|
||||
"bind",
|
||||
"--bucket",
|
||||
"b",
|
||||
"--region",
|
||||
"cn-hangzhou",
|
||||
"--path-prefix",
|
||||
"p/",
|
||||
"--policy",
|
||||
"BEFORE_DAYS",
|
||||
"--workspace-id",
|
||||
workspaceId,
|
||||
],
|
||||
expect: 2,
|
||||
},
|
||||
];
|
||||
|
||||
console.log(`\n🔍 Asset Center 命令调试 — workspace: ${workspaceId}`);
|
||||
console.log(`📦 样本 asset-id: ${sampleAssetId}\n`);
|
||||
|
||||
const results = [];
|
||||
for (const testCase of cases) {
|
||||
process.stdout.write(` running: ${testCase.command} ... `);
|
||||
const result = runBl(testCase.args);
|
||||
const pass = result.exitCode === testCase.expect;
|
||||
console.log(pass ? "ok" : `FAIL (exit ${result.exitCode})`);
|
||||
results.push({ ...testCase, ...result, pass });
|
||||
}
|
||||
|
||||
const passed = results.filter((row) => row.pass).length;
|
||||
const failed = results.filter((row) => !row.pass).length;
|
||||
|
||||
// Markdown report
|
||||
const lines = [
|
||||
`# Asset Center 命令测试报告`,
|
||||
``,
|
||||
`- **测试时间**: ${new Date().toISOString().replace("T", " ").slice(0, 19)} (UTC)`,
|
||||
`- **Workspace**: \`${workspaceId}\``,
|
||||
`- **样本 Asset ID**: \`${sampleAssetId}\``,
|
||||
`- **策略**: 只读命令真实调用;写操作/下载/OSS 变更一律 \`--dry-run\``,
|
||||
`- **汇总**: ${passed} 通过 / ${failed} 失败 / ${results.length} 总计`,
|
||||
``,
|
||||
`## 测试结果`,
|
||||
``,
|
||||
`| # | 分类 | 命令 | 模式 | 状态 | Exit | 耗时 | 结果摘要 |`,
|
||||
`|---|------|------|------|------|------|------|----------|`,
|
||||
];
|
||||
|
||||
results.forEach((row, index) => {
|
||||
lines.push(
|
||||
`| ${index + 1} | ${row.category} | \`${row.command}\` | ${row.mode} | ${status(row.exitCode, row.expect)} | ${row.exitCode} | ${row.durationMs}ms | ${summarizeOutput(row).replace(/\|/g, "\\|")} |`,
|
||||
);
|
||||
});
|
||||
|
||||
if (failed > 0) {
|
||||
lines.push(``, `## 失败详情`, ``);
|
||||
for (const row of results.filter((row) => !row.pass)) {
|
||||
lines.push(`### ${row.command}`, ``);
|
||||
lines.push(`**stderr:**`, `\`\`\``, row.stderr || "(empty)", `\`\`\``, ``);
|
||||
lines.push(`**stdout:**`, `\`\`\``, row.stdout.slice(0, 500) || "(empty)", `\`\`\``, ``);
|
||||
}
|
||||
}
|
||||
|
||||
lines.push(
|
||||
``,
|
||||
`## 模式说明`,
|
||||
``,
|
||||
`| 模式 | 说明 |`,
|
||||
`|------|------|`,
|
||||
`| 真实调用 | 只读 API,不修改数据 |`,
|
||||
`| dry-run | 输出 \`{ api, data, gateway }\` 请求体,不发起写操作 |`,
|
||||
`| 参数校验 | 预期 exit code 2(用法错误) |`,
|
||||
);
|
||||
|
||||
const reportPath = join(root, "packages/commands/src/commands/asset-center/TEST-REPORT.md");
|
||||
writeFileSync(reportPath, lines.join("\n"));
|
||||
console.log(`\n📄 Report written: ${reportPath}`);
|
||||
console.log(`✅ ${passed} passed, ❌ ${failed} failed\n`);
|
||||
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
Reference in New Issue
Block a user