zentao-api 0.1.0 → 0.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +103 -147
  3. package/dist/browser/zentao-api.global.js +1 -0
  4. package/dist/browser.d.ts +1 -0
  5. package/dist/browser.js +1 -0
  6. package/dist/client/index.d.ts +37 -0
  7. package/dist/client/index.js +149 -0
  8. package/dist/index.d.ts +7 -4
  9. package/dist/index.js +6 -8
  10. package/dist/misc/browser-global.d.ts +1 -0
  11. package/dist/misc/browser-global.js +8 -0
  12. package/dist/misc/environment.d.ts +6 -0
  13. package/dist/misc/environment.js +30 -0
  14. package/dist/misc/errors.d.ts +25 -0
  15. package/dist/misc/errors.js +35 -0
  16. package/dist/misc/global-options.d.ts +5 -0
  17. package/dist/misc/global-options.js +9 -0
  18. package/dist/modules/generated.d.ts +8 -0
  19. package/dist/modules/generated.js +4226 -0
  20. package/dist/modules/registry.d.ts +22 -0
  21. package/dist/modules/registry.js +129 -0
  22. package/dist/modules/resolve.d.ts +7 -0
  23. package/dist/modules/resolve.js +196 -0
  24. package/dist/request/index.d.ts +7 -0
  25. package/dist/request/index.js +65 -0
  26. package/dist/types/index.d.ts +235 -0
  27. package/dist/types/index.js +1 -0
  28. package/dist/utils/index.d.ts +2 -0
  29. package/dist/utils/index.js +14 -0
  30. package/dist/version.d.ts +2 -0
  31. package/dist/version.js +4 -0
  32. package/package.json +43 -77
  33. package/dist/types.d.ts +0 -70
  34. package/dist/utils.d.ts +0 -93
  35. package/dist/zentao-api.cjs.development.js +0 -3619
  36. package/dist/zentao-api.cjs.development.js.map +0 -1
  37. package/dist/zentao-api.cjs.production.min.js +0 -2
  38. package/dist/zentao-api.cjs.production.min.js.map +0 -1
  39. package/dist/zentao-api.esm.js +0 -3611
  40. package/dist/zentao-api.esm.js.map +0 -1
  41. package/dist/zentao-config.d.ts +0 -93
  42. package/dist/zentao-request-builder.d.ts +0 -120
  43. package/dist/zentao.d.ts +0 -175
  44. package/dist/zentao12.d.ts +0 -676
  45. package/src/index.ts +0 -5
  46. package/src/types.ts +0 -88
  47. package/src/utils.ts +0 -216
  48. package/src/zentao-config.ts +0 -150
  49. package/src/zentao-request-builder.ts +0 -227
  50. package/src/zentao.ts +0 -596
  51. package/src/zentao12.ts +0 -1272
@@ -0,0 +1,149 @@
1
+ import { ZentaoError } from '../misc/errors.js';
2
+ import { assertInsecureSupported, withInsecureTls } from '../misc/environment.js';
3
+ import { getGlobalOptions, setGlobalOptions } from '../misc/global-options.js';
4
+ const DEFAULT_TIMEOUT = 10000;
5
+ /** 将用户传入的站点根地址规范化,兼容误传入 `/api.php/v2` 的场景。 */
6
+ function normalizeSiteUrl(baseUrl) {
7
+ const trimmed = baseUrl.trim().replace(/\/+$/, '');
8
+ if (!trimmed)
9
+ throw new ZentaoError('E_INVALID_BASE_URL');
10
+ return trimmed.replace(/\/api\.php\/v2$/i, '');
11
+ }
12
+ /** 拼接 API 路径与查询参数,跳过值为 `undefined` 的查询项。 */
13
+ function buildUrl(baseUrl, path, query) {
14
+ const normalizedPath = path.startsWith('/') ? path : `/${path}`;
15
+ const url = new URL(`${baseUrl}${normalizedPath}`);
16
+ for (const [key, value] of Object.entries(query ?? {})) {
17
+ if (value === undefined)
18
+ continue;
19
+ url.searchParams.set(key, String(value));
20
+ }
21
+ return url.toString();
22
+ }
23
+ /** 优先解析 JSON;非 JSON 响应保留为原始文本。 */
24
+ async function parseResponse(response) {
25
+ const text = await response.text();
26
+ if (text === '')
27
+ return undefined;
28
+ try {
29
+ return JSON.parse(text);
30
+ }
31
+ catch {
32
+ return text;
33
+ }
34
+ }
35
+ /** 禅道 API 客户端,负责 Token 注入、请求超时、TLS 选项和响应解析。 */
36
+ export class ZentaoClient {
37
+ /** 禅道站点根地址,不包含 `/api.php/v2`。 */
38
+ siteUrl;
39
+ /** 禅道 API v2 根地址。 */
40
+ baseUrl;
41
+ token;
42
+ timeout;
43
+ insecure;
44
+ constructor(input) {
45
+ const options = typeof input === 'string' ? { baseUrl: input } : input;
46
+ this.siteUrl = normalizeSiteUrl(options.baseUrl);
47
+ this.baseUrl = `${this.siteUrl}/api.php/v2`;
48
+ this.token = options.token;
49
+ this.timeout = options.timeout;
50
+ this.insecure = options.insecure;
51
+ }
52
+ /**
53
+ * 发起一次原始 API 请求。
54
+ *
55
+ * 默认使用 GET;当服务端返回 `{ status: "fail" }` 时仍按原始内容返回,
56
+ * 只有 HTTP/网络/超时等传输层错误会抛出 {@link ZentaoError}。
57
+ */
58
+ async request(path, options = {}) {
59
+ const globals = getGlobalOptions();
60
+ const method = options.method ?? 'GET';
61
+ const timeout = options.timeout ?? globals.timeout ?? this.timeout ?? DEFAULT_TIMEOUT;
62
+ const insecure = options.insecure ?? globals.insecure ?? this.insecure;
63
+ assertInsecureSupported(insecure);
64
+ const url = buildUrl(this.baseUrl, path, options.query);
65
+ const controller = new AbortController();
66
+ const timer = setTimeout(() => controller.abort(), timeout);
67
+ const headers = {
68
+ 'Content-Type': 'application/json',
69
+ };
70
+ if (this.token) {
71
+ headers.Token = this.token;
72
+ }
73
+ const init = {
74
+ method,
75
+ headers,
76
+ signal: controller.signal,
77
+ };
78
+ // GET 请求不携带 body,避免浏览器和部分代理拒绝请求。
79
+ if (options.body && method !== 'GET') {
80
+ init.body = JSON.stringify(options.body);
81
+ }
82
+ try {
83
+ return await withInsecureTls(insecure, async () => {
84
+ const response = await fetch(url, init);
85
+ if (!response.ok) {
86
+ throw new ZentaoError('E_HTTP_ERROR', {
87
+ status: response.status,
88
+ statusText: response.statusText,
89
+ }, {
90
+ url: response.url,
91
+ status: response.status,
92
+ statusText: response.statusText,
93
+ body: await response.text().catch(() => undefined),
94
+ });
95
+ }
96
+ return parseResponse(response);
97
+ });
98
+ }
99
+ catch (error) {
100
+ if (error instanceof ZentaoError)
101
+ throw error;
102
+ if (error instanceof DOMException && error.name === 'AbortError') {
103
+ throw new ZentaoError('E_TIMEOUT');
104
+ }
105
+ throw new ZentaoError('E_NETWORK_ERROR', { message: error.message ?? String(error) }, error);
106
+ }
107
+ finally {
108
+ clearTimeout(timer);
109
+ }
110
+ }
111
+ /** 发起 GET 请求并按调用方指定类型返回。 */
112
+ async get(path) {
113
+ return this.request(path, { method: 'GET' });
114
+ }
115
+ /** 发起 POST 请求并发送 JSON body。 */
116
+ async post(path, body) {
117
+ return this.request(path, { method: 'POST', body });
118
+ }
119
+ /** 发起 PUT 请求并发送 JSON body。 */
120
+ async put(path, body) {
121
+ return this.request(path, { method: 'PUT', body });
122
+ }
123
+ /** 发起 DELETE 请求。 */
124
+ async delete(path) {
125
+ return this.request(path, { method: 'DELETE' });
126
+ }
127
+ /** 使用账号密码登录,成功后把返回 Token 写入当前客户端实例。 */
128
+ async login(account, password) {
129
+ const response = await this.post('/users/login', { account, password });
130
+ if (response.status !== 'success' || !response.token) {
131
+ throw new ZentaoError('E_LOGIN_FAILED');
132
+ }
133
+ this.token = response.token;
134
+ return response.token;
135
+ }
136
+ /** 创建客户端实例,语义等同于 `new ZentaoClient(options)`。 */
137
+ static create(options) {
138
+ return new ZentaoClient(options);
139
+ }
140
+ /** 创建客户端并写入全局选项,供高阶 `request()` 默认使用。 */
141
+ static init(options) {
142
+ const client = new ZentaoClient(options);
143
+ setGlobalOptions({ client });
144
+ return client;
145
+ }
146
+ }
147
+ export function createClient(options) {
148
+ return ZentaoClient.create(options);
149
+ }
package/dist/index.d.ts CHANGED
@@ -1,4 +1,7 @@
1
- import * as utils from './utils';
2
- import Zentao from './zentao';
3
- import Zentao12 from './zentao12';
4
- export { utils, Zentao, Zentao12 };
1
+ export { ZentaoClient } from './client/index.js';
2
+ export { ERRORS, ZentaoError, type ErrorCode } from './misc/errors.js';
3
+ export { getGlobalOptions, setGlobalOptions } from './misc/global-options.js';
4
+ export { defineModuleActions, defineModules, type DefineModulesOptions, getModule, getModuleAction, } from './modules/registry.js';
5
+ export { request } from './request/index.js';
6
+ export { BUILD, VERSION } from './version.js';
7
+ export type { ApiListResponse, ApiResponse, ClientRequestOptions, GlobalOptions, HttpMethod, ListPagerInfo, LoginResponse, ModuleAction, ModuleActionMethod, ModuleActionName, ModuleActionPagerGetterMap, ModuleActionParam, ModuleActionParamOption, ModuleActionRequestBody, ModuleActionResponse, ModuleActionResultRender, ModuleActionResultRenderType, ModuleActionResultType, ModuleActionType, ModuleDefinition, ModuleName, Pager, RequestOptions, ResolvedModuleCommand, ResponseData, ServerConfig, ZentaoClientOptions, } from './types/index.js';
package/dist/index.js CHANGED
@@ -1,8 +1,6 @@
1
-
2
- 'use strict'
3
-
4
- if (process.env.NODE_ENV === 'production') {
5
- module.exports = require('./zentao-api.cjs.production.min.js')
6
- } else {
7
- module.exports = require('./zentao-api.cjs.development.js')
8
- }
1
+ export { ZentaoClient } from './client/index.js';
2
+ export { ERRORS, ZentaoError } from './misc/errors.js';
3
+ export { getGlobalOptions, setGlobalOptions } from './misc/global-options.js';
4
+ export { defineModuleActions, defineModules, getModule, getModuleAction, } from './modules/registry.js';
5
+ export { request } from './request/index.js';
6
+ export { BUILD, VERSION } from './version.js';
@@ -0,0 +1 @@
1
+ export * from '../index.js';
@@ -0,0 +1,8 @@
1
+ import * as api from '../index.js';
2
+ // Bun 的 IIFE bundle 不总是自动挂载 globalName;这里显式暴露浏览器全局对象。
3
+ const globalTarget = globalThis;
4
+ globalTarget.ZentaoAPI = api;
5
+ if (globalTarget.window) {
6
+ globalTarget.window.ZentaoAPI = api;
7
+ }
8
+ export * from '../index.js';
@@ -0,0 +1,6 @@
1
+ /** 判断当前运行时是否为 Node.js。 */
2
+ export declare function isNodeRuntime(): boolean;
3
+ /** 浏览器无法跳过 TLS 校验,因此在发起请求前提前失败。 */
4
+ export declare function assertInsecureSupported(enabled: boolean | undefined): void;
5
+ /** 在 Node.js 中临时关闭 TLS 校验,并在本次请求结束后恢复原值。 */
6
+ export declare function withInsecureTls<T>(enabled: boolean | undefined, fn: () => Promise<T>): Promise<T>;
@@ -0,0 +1,30 @@
1
+ import { ZentaoError } from './errors.js';
2
+ /** 判断当前运行时是否为 Node.js。 */
3
+ export function isNodeRuntime() {
4
+ return typeof process !== 'undefined' && Boolean(process.versions?.node);
5
+ }
6
+ /** 浏览器无法跳过 TLS 校验,因此在发起请求前提前失败。 */
7
+ export function assertInsecureSupported(enabled) {
8
+ if (enabled && !isNodeRuntime()) {
9
+ throw new ZentaoError('E_INSECURE_BROWSER');
10
+ }
11
+ }
12
+ /** 在 Node.js 中临时关闭 TLS 校验,并在本次请求结束后恢复原值。 */
13
+ export async function withInsecureTls(enabled, fn) {
14
+ if (!enabled)
15
+ return fn();
16
+ assertInsecureSupported(enabled);
17
+ const previous = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
18
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
19
+ try {
20
+ return await fn();
21
+ }
22
+ finally {
23
+ if (previous === undefined) {
24
+ delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
25
+ }
26
+ else {
27
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = previous;
28
+ }
29
+ }
30
+ }
@@ -0,0 +1,25 @@
1
+ export declare const ERRORS: {
2
+ readonly E_INVALID_BASE_URL: "Invalid ZenTao baseUrl.";
3
+ readonly E_NO_GLOBAL_CLIENT: "No global client configured. Call ZentaoClient.init() or pass options.client.";
4
+ readonly E_HTTP_ERROR: "HTTP request failed: {status} {statusText}";
5
+ readonly E_NETWORK_ERROR: "Network request failed: {message}";
6
+ readonly E_TIMEOUT: "Request timed out.";
7
+ readonly E_INSECURE_BROWSER: "The insecure option is only supported in Node.js runtimes.";
8
+ readonly E_LOGIN_FAILED: "ZenTao login failed.";
9
+ readonly E_INVALID_MODULE: "Unknown module: {module}";
10
+ readonly E_INVALID_ACTION: "Unknown action: {module}-{action}";
11
+ readonly E_INVALID_MODULE_DEFINITION: "Invalid module definition.";
12
+ readonly E_INVALID_ACTION_DEFINITION: "Invalid module action definition.";
13
+ readonly E_MISSING_PARAM: "Missing required parameter: {param}";
14
+ readonly E_INVALID_REQUEST_NAME: "Request name must use the form \"moduleName/methodName\".";
15
+ };
16
+ export type ErrorCode = keyof typeof ERRORS;
17
+ /** SDK 统一错误类型,所有可预期错误都会携带稳定错误码。 */
18
+ export declare class ZentaoError extends Error {
19
+ /** 错误码,对应 {@link ERRORS} 的 key。 */
20
+ readonly code: ErrorCode;
21
+ /** 附加上下文,例如 HTTP 响应详情或原始异常。 */
22
+ readonly details?: unknown;
23
+ /** 根据错误码和占位符替换值创建错误。 */
24
+ constructor(code: ErrorCode, replacements?: Record<string, string | number>, details?: unknown);
25
+ }
@@ -0,0 +1,35 @@
1
+ export const ERRORS = {
2
+ E_INVALID_BASE_URL: 'Invalid ZenTao baseUrl.',
3
+ E_NO_GLOBAL_CLIENT: 'No global client configured. Call ZentaoClient.init() or pass options.client.',
4
+ E_HTTP_ERROR: 'HTTP request failed: {status} {statusText}',
5
+ E_NETWORK_ERROR: 'Network request failed: {message}',
6
+ E_TIMEOUT: 'Request timed out.',
7
+ E_INSECURE_BROWSER: 'The insecure option is only supported in Node.js runtimes.',
8
+ E_LOGIN_FAILED: 'ZenTao login failed.',
9
+ E_INVALID_MODULE: 'Unknown module: {module}',
10
+ E_INVALID_ACTION: 'Unknown action: {module}-{action}',
11
+ E_INVALID_MODULE_DEFINITION: 'Invalid module definition.',
12
+ E_INVALID_ACTION_DEFINITION: 'Invalid module action definition.',
13
+ E_MISSING_PARAM: 'Missing required parameter: {param}',
14
+ E_INVALID_REQUEST_NAME: 'Request name must use the form "moduleName/methodName".',
15
+ };
16
+ /** SDK 统一错误类型,所有可预期错误都会携带稳定错误码。 */
17
+ export class ZentaoError extends Error {
18
+ /** 错误码,对应 {@link ERRORS} 的 key。 */
19
+ code;
20
+ /** 附加上下文,例如 HTTP 响应详情或原始异常。 */
21
+ details;
22
+ /** 根据错误码和占位符替换值创建错误。 */
23
+ constructor(code, replacements, details) {
24
+ let message = ERRORS[code];
25
+ if (replacements) {
26
+ for (const [key, value] of Object.entries(replacements)) {
27
+ message = message.replaceAll(`{${key}}`, String(value));
28
+ }
29
+ }
30
+ super(message);
31
+ this.name = 'ZentaoError';
32
+ this.code = code;
33
+ this.details = details;
34
+ }
35
+ }
@@ -0,0 +1,5 @@
1
+ import type { GlobalOptions } from '../types/index.js';
2
+ /** 获取当前全局选项快照;返回副本,避免调用方直接改写内部状态。 */
3
+ export declare function getGlobalOptions(): GlobalOptions;
4
+ /** 合并设置全局选项;传入 `undefined` 可清空对应字段。 */
5
+ export declare function setGlobalOptions(options: Partial<GlobalOptions>): void;
@@ -0,0 +1,9 @@
1
+ let globalOptions = {};
2
+ /** 获取当前全局选项快照;返回副本,避免调用方直接改写内部状态。 */
3
+ export function getGlobalOptions() {
4
+ return { ...globalOptions };
5
+ }
6
+ /** 合并设置全局选项;传入 `undefined` 可清空对应字段。 */
7
+ export function setGlobalOptions(options) {
8
+ globalOptions = { ...globalOptions, ...options };
9
+ }
@@ -0,0 +1,8 @@
1
+ import type { ModuleDefinition } from '../types/index.js';
2
+ /**
3
+ * 内置模块注册表:key 为模块名(小写),value 为对应禅道 REST 资源元数据。
4
+ * 新增模块时优先更新 OpenAPI 数据并重新生成此文件。
5
+ *
6
+ * 此文件由 scripts/update-registry.ts 自动生成,请勿手动编辑。
7
+ */
8
+ export declare const BUILTIN_MODULES: ModuleDefinition[];