Skip to content

Repository files navigation

zentao-api

npm version Node.js license

Browser & Node.js SDK for ZenTao (禅道) API v2.

zentao-api 是一个零运行时依赖的 JavaScript/TypeScript SDK,提供底层 REST 客户端和基于模块注册表的高阶请求接口,可运行在 Node.js 18+、Bun、浏览器打包工具及 CDN/script 标签环境中。

快速开始 · 调用方式 · 浏览器 · 完整文档

特性

  • 两层 API:使用 ZentaoClient 直接调用 REST 路径,或使用 request("module/action") 自动组装路径、查询参数和请求体。
  • 完整类型提示:内置请求名、参数和 data 返回值可由 TypeScript 自动推导。
  • 统一响应结构:自动提取业务数据和分页信息,稳定返回 ResponseData<T>
  • 覆盖禅道常用模块:产品、项目、执行、需求、任务、Bug、测试、版本、发布等。
  • 内置本地数据处理:支持转换、过滤、搜索、排序、限制数量和字段摘取。
  • 可扩展模块注册表、持久化 Profile、稳定错误码及 Node.js 自签名证书支持。

安装

npm install zentao-api

使用 Bun:

bun add zentao-api

包采用 ESM,并自带 TypeScript 类型定义。Node.js 需要 18 或更高版本。

快速开始

推荐通过 ZentaoClient.init() 配置全局客户端,再使用高阶 request() 调用内置模块:

import { ZentaoClient, request } from 'zentao-api';

ZentaoClient.init({
  baseUrl: 'https://zentao.example.com',
  token: 'your-token',
});

const result = await request('product/list', {
  browseType: 'all',
  recPerPage: 20,
  pageID: 1,
});

console.log(result.data);         // 产品列表
console.log(result.pager?.total); // 总记录数

baseUrl 填写禅道站点根地址;SDK 会自动拼接 /api.php/v2,并在后续请求中注入 Token 请求头。

使用账号密码登录

没有 token 时,可以先登录。ZentaoClient.init() 返回的实例同时也是 request() 使用的全局客户端:

import { ZentaoClient, request } from 'zentao-api';

const client = ZentaoClient.init({
  baseUrl: 'https://zentao.example.com',
});

await client.login('admin', 'password');

const products = await request('product/list');

请从环境变量或安全配置中读取账号、密码和 token,不要将凭据提交到代码仓库。

两种调用方式

调用方式 适合场景 返回值
request("module/action") 调用注册表中的常用禅道 API,自动处理参数与分页 统一的 ResponseData<T>
ZentaoClient 调用尚未注册的路径、上传文件或读取二进制响应 禅道原始响应体

高阶模块请求

请求名支持三种写法:

await request('product');      // product/list
await request('product/list'); // 显式动作名
await request('product/1');    // product/get,且对象 ID 为 1

带作用域的列表可以传产品、项目或执行 ID,SDK 会自动选择实际路径:

const bugs = await request('bug/list', {
  productID: 1,
  browseType: 'unclosed',
});

// 也可以显式指定作用域:
const projectBugs = await request('bug/list', {
  scope: 'projects',
  scopeID: 8,
});

单次调用选项会覆盖全局选项。下面的处理只作用于 SDK 返回的 data,不会改变服务端数据:

const bugs = await request(
  'bug/list',
  { productID: 1, recPerPage: 100 },
  {
    filter: ['status=active,pri>=2'],
    search: ['登录'],
    sort: 'pri:desc,id:asc',
    limit: '10',
    pick: ['id', 'title', 'pri'],
  },
);

更多过滤语法和处理顺序见本地数据处理指南

统一返回结构

除非启用 rawrequest() 始终返回以下结构:

interface ResponseData<T> {
  status: 'success' | 'fail';
  message?: string;
  data?: T;
  pager?: {
    total: number;
    page: number;
    recPerPage: number;
  };
}

内置请求会自动推导参数和数据类型;自定义调用也可以显式收窄 data

interface ProductSummary {
  id: number;
  name: string;
}

const result = await request<ProductSummary[]>('product/list', {});
result.data?.forEach((product) => console.log(product.name));

需要完整服务端响应时,传入 { raw: true }。此时会跳过响应归一化、本地数据处理和 throwOnFail

const raw = await request('product/list', {}, { raw: true });

底层 REST 客户端

ZentaoClient 适合直接调用 API v2 路径:

import { ZentaoClient } from 'zentao-api';

const client = new ZentaoClient({
  baseUrl: 'https://zentao.example.com',
  token: 'your-token',
  timeout: 10_000,
});

const products = await client.get('/products');
const product = await client.get('/products/1');
const created = await client.post('/products', { name: '新产品' });

通用 client.request() 还支持自定义请求头、查询参数、AbortSignalFormData,以及 textarrayBufferblobresponse 等响应类型。

配置

客户端选项

选项 类型 说明
baseUrl string 禅道站点根地址;SDK 自动处理 /api.php/v2
token string 禅道 API Token;也可稍后通过 login() 获取。
timeout number 默认请求超时时间,单位为毫秒,默认 10000
insecure boolean 跳过 TLS 证书校验,仅支持 Node.js 运行时。

全局选项

import { setGlobalOptions } from 'zentao-api';

setGlobalOptions({
  recPerPage: '50',
  limit: '20',
  timeout: 30_000,
  throwOnFail: true,
  autoFill: false,
});

常用全局选项包括 clientrecPerPagelimittimeoutinsecurepersistProfilesthrowOnFailautoFill。优先级通常为:单次调用选项 > 全局选项 > 客户端默认值。

持久化 Profile

Profile 默认不会写入。先启用 persistProfiles,登录成功后才会保存站点、账号、token 和客户端配置:

import { ZentaoClient, setGlobalOptions } from 'zentao-api';

setGlobalOptions({ persistProfiles: true });

const client = ZentaoClient.init({
  baseUrl: 'https://zentao.example.com',
});

await client.login('admin', 'password');

后续可以恢复当前 Profile;如需继续调用高阶 request(),再把恢复的客户端设为全局客户端:

const client = await ZentaoClient.fromProfile();
setGlobalOptions({ client });

// 或指定 profile key
const another = await ZentaoClient.fromProfile(
  'admin@https://zentao.example.com',
);
环境 存储位置
Node.js / Bun ~/.config/zentao/zentao.json
浏览器 localStorage

Profile 包含可直接调用 API 的 token,请按敏感凭据保护其存储位置。

错误处理

HTTP、网络、超时、参数、模块解析和 Profile 错误会统一抛出带稳定错误码的 ZentaoError

import { request, ZentaoError } from 'zentao-api';

try {
  await request('bug/resolve', {
    bugID: 1001,
    resolution: 'fixed',
  }, {
    throwOnFail: true,
  });
} catch (error) {
  if (error instanceof ZentaoError) {
    console.error(error.code);    // 例如 E_HTTP_ERROR、E_TIMEOUT
    console.error(error.message);
    console.error(error.details);
  }
}

禅道返回 { status: "fail" } 属于业务失败,默认仍作为 ResponseData 返回;只有启用单次或全局 throwOnFail 后,才会抛出 E_API_FAILED。HTTP、网络和超时等传输层错误始终抛出异常。

浏览器

Vite、Webpack、Rspack 等打包工具可以从包根导入;需要显式选择浏览器入口时使用 zentao-api/browser

import { ZentaoClient, request } from 'zentao-api/browser';

使用 script 标签时,UMD 构建会将公共 API 暴露到 window.ZentaoAPI

<script src="https://cdn.jsdelivr.net/npm/zentao-api@latest/dist/browser/zentao-api.global.js"></script>
<script>
  const client = new window.ZentaoAPI.ZentaoClient({
    baseUrl: 'https://zentao.example.com',
    token: 'your-token',
  });

  console.log(window.ZentaoAPI.VERSION);
</script>

浏览器直连要求禅道服务器允许 CORS,并会向前端暴露 token;敏感场景请通过后端代理。insecure 仅适用于 Node.js,在浏览器中使用会抛出 E_INSECURE_BROWSER

模块注册表与扩展

可以在运行时查看 SDK 当前支持的模块、动作和参数:

import {
  getModuleAction,
  getModuleActionParams,
  getModuleNames,
  getObjectProps,
} from 'zentao-api';

const modules = getModuleNames();
const action = getModuleAction('bug', 'create');
const params = getModuleActionParams('bug', 'create');
const labels = getObjectProps('bug');

未注册的 API 可以新增为自定义模块:

import { defineModules } from 'zentao-api';

defineModules({
  name: 'custom',
  actions: [
    {
      name: 'list',
      type: 'list',
      path: '/custom',
      resultGetter: 'items',
    },
  ],
});

只需修改已有动作的个别字段时,使用 extendModuleAction();补丁对象会深度合并,数组会整体替换:

import { extendModuleAction } from 'zentao-api';

extendModuleAction('task', 'list', {
  path: '/executions/{executionID}/tasks',
  pathParams: { executionID: '执行 ID' },
});

defineModuleActions() 可追加或整体替换单个动作,defineModules(module, { replace: true }) 可整体替换同名模块。请确保扩展代码在第一次调用 request() 前执行。

文档

开发与贡献

本仓库只使用 Bun 管理开发依赖,请勿使用 npm、pnpm 或 yarn 安装仓库依赖,以免生成额外 lockfile。

bun install
bun test                  # 单元测试
bun run test:real         # 真实禅道环境集成测试
bun run docs:dev          # 生成并预览文档站
bun run check             # 完整 CI:测试、类型检查、注册表、构建、冒烟测试

bun run test:real 会依次读取 .env.localenv.local,需要配置 ZENTAO_URL(或 ZENTAO_BASE_URL),以及 ZENTAO_TOKENZENTAO_ACCOUNT / ZENTAO_PASSWORD。使用 bun run test:real -- --keep-test-data 可保留测试创建的数据。

模块注册表由 data/zentao-openapi.json 生成。请勿手动编辑 src/modules/generated.ts;更新规范后运行:

bun run scripts/update-registry.ts
bun run docs:generate

提交代码前请确保 bun run check 通过。欢迎提交 Issue 和 Pull Request。

License

MIT

About

A small JavaScript/TypeScript SDK for ZenTao API v2. It works in Node.js 18+, browser bundlers, and CDN/script-tag usage.

Topics

Resources

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages