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 });

上传附件

Node.js/Bun 可以直接把本地文件路径交给高阶 request();SDK 会根据模块注册表构造 multipart/form-data。浏览器中请传 FileBlob,不能传本地路径。

const uploaded = await request('file/create', {
  file: '/tmp/zentao-api-upload.txt',
  objectType: 'story',
  objectID: 1,
});

console.log(uploaded.data?.id, uploaded.data?.url);

单个本地文件或 Blob 默认限制为 50 MiB;可通过单次调用选项 maxUploadBytes 调整。

底层 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,
});

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

禅道版本检查

高阶 request() 会在发送业务请求前检查 Action 的必填 minVersion。四个系列分别比较,例如 22.5biz13.5max8.5ipd5.5;支持点分数字正式版本,暂不支持 alpha、beta、rc 等后缀。底层 client.get/post/request 不检查 Action 版本。

setGlobalOptions({ version: 'biz13.5' });
await request('story/getGrades'); // 直接使用全局版本,无需获取配置
await request('story/getGrades', {}, { forceRefreshConfig: true }); // 用服务器实际版本校验本次请求

const config = await client.getZentaoConfig(); // 复用有效缓存,缺失或过期时获取
const freshConfig = await client.getZentaoConfig({ forceRefresh: true });

getZentaoConfig() 只需站点地址,无需登录或 profile;未提供 Token 或 Token 已过期时都可调用。请求不发送 API Token,浏览器中还会显式省略 Cookie 等凭据,因此可以在登录前查询版本:

const siteClient = new ZentaoClient('https://zentao.example.com');
const { version } = await siteClient.getZentaoConfig();

强制刷新不会改写全局 version。未指定全局版本时,使用不超过 24 小时的缓存;缓存缺失、过期或时间异常时访问站点根地址的 /?mode=getconfig。登录验证成功后也会强制获取一次,即使已设置全局版本。

配置获取失败默认停止调用;可以通过全局或单次 skipVersionCheckOnConfigError: true 跳过本次检查。该选项不忽略版本不匹配、版本格式错误、取消或 profile 存储错误。版本不匹配抛出 E_UNSUPPORTED_ZENTAO_VERSION

持久化 Profile

login() 默认不会自动写入 Profile。先启用 persistProfiles,登录成功后才会保存站点、账号、token、客户端配置,以及 serverConfig 和获取时间 serverConfigFetchedAt

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',
);

// 只读取并恢复,不切换当前 Profile、不更新使用时间、不写存储。
const isolated = await ZentaoClient.fromProfile(
  'admin@https://zentao.example.com', { activate: false },
);
环境 存储位置
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 {
  getModule,
  getModuleAction,
  getModuleActionParams,
  getModuleNames,
  getObjectProps,
} from 'zentao-api';

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

const supported = getModule('story', { version: 'biz13.0' });
const unavailable = getModuleAction('story', 'getGrades', { version: 'biz13.0' }); // undefined
const supportedParams = getModuleActionParams('story', 'create', { version: 'biz13.0', roles: ['body'] });

模块查询只使用显式传入的 version,省略时返回当前完整定义,不受全局版本影响。过滤后没有动作的模块不返回。旧 Action 后续新增的参数仍按当前定义返回,不做参数级版本过滤。

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

import { defineModules } from 'zentao-api';

defineModules({
  name: 'custom',
  actions: [
    {
      name: 'list',
      minVersion: ['22.0', 'biz13.0', 'max8.0', 'ipd5.0'],
      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() 前执行。

所有完整 Action 定义都必须包含非空的 minVersion 数组,同一系列不能重复;未列出的系列视为不支持。SDK 0.5.5 及之前已有的 Action 最低版本为 22.0 / biz13.0 / max8.0 / ipd5.0,之后新增的 Action 为 22.5 / biz13.5 / max8.5 / ipd5.5

文档

开发与贡献

本仓库只使用 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 可保留测试创建的数据。

测试创建和更新项目时会复用现有 Scrum 项目的有效项目流程,也可通过 ZENTAO_WORKFLOW_GROUP 显式指定流程 ID。没有可复用流程时默认使用开源版的 0;付费版需要指定有效的流程 ID。

真实环境测试按场景执行,覆盖以下流程:

  • 产品、项目集、项目、执行、计划,以及用户和团队成员维护。
  • 研发需求、业务需求、用户需求、任务、Bug、测试用例和模块目录的增删改查、状态流转,以及产品/项目/执行范围的列表。
  • 应用查询和修改、构建、测试单、发布、反馈、工单、转化操作和待办。
  • 个人、团队、产品、项目四类文档空间中的文档库、目录、文档,以及个人工作列表;可用时还会测试会议和自定义合同工作流。

测试使用唯一名称创建临时数据,按依赖顺序清理;使用 Token 时仍需设置 ZENTAO_ACCOUNTZENTAO_REVIEWER,供负责人、成员和评审人字段使用。--keep-test-data 会保留新增场景的数据;原有生命周期场景中的“删除第二个任务”仍会执行。

每次运行会输出接口统计并写入 coverage/real-env.json(已被 Git 忽略),列出已执行、成功、失败、排除原因、尚未测试的动作及实际访问的不同路径,同时记录退出码、耗时和清理错误。动作只要出现一次失败就归入失败,即使另一组参数调用成功;动作统计不能替代场景断言结果。只有探测到明确缺失的可选服务端模块才跳过对应测试;权限、SQL、缺失控制器方法、HTML 错误页和断言失败仍会让命令以非零状态退出。没有删除 API 的问题/风险写入和额外应用创建不自动执行;应用修改只使用临时产品自动生成的应用。

模块注册表由 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

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages