zentao-api 0.6.6 → 0.6.8
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.
- package/README.md +41 -2
- package/dist/browser/zentao-api.global.js +21 -21
- package/dist/client/index.d.ts +45 -5
- package/dist/client/index.js +131 -18
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/misc/errors.d.ts +6 -1
- package/dist/misc/errors.js +9 -1
- package/dist/misc/zentao-version.d.ts +11 -0
- package/dist/misc/zentao-version.js +35 -0
- package/dist/modules/define.d.ts +4 -3
- package/dist/modules/define.js +4 -3
- package/dist/modules/generated.d.ts +228 -0
- package/dist/modules/generated.js +228 -0
- package/dist/modules/override.d.ts +2 -0
- package/dist/modules/override.js +3 -0
- package/dist/modules/query.d.ts +13 -8
- package/dist/modules/query.js +25 -11
- package/dist/modules/registry-store.js +6 -0
- package/dist/profiles/file-lock.d.ts +6 -0
- package/dist/profiles/file-lock.js +127 -0
- package/dist/profiles/index.d.ts +11 -4
- package/dist/profiles/index.js +134 -79
- package/dist/request/index.d.ts +3 -0
- package/dist/request/index.js +47 -11
- package/dist/types/client.d.ts +10 -0
- package/dist/types/module.d.ts +32 -2
- package/dist/types/options.d.ts +8 -0
- package/dist/types/profile.d.ts +14 -8
- package/dist/types/response.d.ts +9 -0
- package/dist/version.js +2 -2
- package/package.json +1 -1
package/dist/client/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ClientRequestOptions, ZentaoClientOptions } from '../types/index.js';
|
|
1
|
+
import type { ClientRequestOptions, FromProfileOptions, GetZentaoConfigOptions, ServerConfig, ZentaoClientOptions } from '../types/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* 禅道 API 客户端,封装一次次原始 HTTP 调用。
|
|
4
4
|
*
|
|
@@ -20,6 +20,10 @@ export declare class ZentaoClient {
|
|
|
20
20
|
private token?;
|
|
21
21
|
private readonly timeout?;
|
|
22
22
|
private readonly insecure?;
|
|
23
|
+
private profileKey?;
|
|
24
|
+
private serverConfig?;
|
|
25
|
+
private serverConfigFetchedAt?;
|
|
26
|
+
private configRequest?;
|
|
23
27
|
/**
|
|
24
28
|
* 使用完整配置创建客户端。
|
|
25
29
|
*
|
|
@@ -65,6 +69,35 @@ export declare class ZentaoClient {
|
|
|
65
69
|
responseType: 'blob';
|
|
66
70
|
}): Promise<Blob>;
|
|
67
71
|
request<T = unknown>(path: string, options?: ClientRequestOptions): Promise<T>;
|
|
72
|
+
/**
|
|
73
|
+
* 使用完整 URL 发起请求,复用 API 与站点配置的传输层。
|
|
74
|
+
*
|
|
75
|
+
* @param url - 完整请求 URL,包含所需的查询参数。
|
|
76
|
+
* @param options - 单次请求选项;其中 `query` 需由调用方预先拼入 `url`。
|
|
77
|
+
* @param token - 显式注入的 Token;省略时不自动使用实例保存的 Token。
|
|
78
|
+
* @param fetchOptions - 原生 fetch 的缓存与凭据选项。
|
|
79
|
+
* @returns 按 `options.responseType` 解析的响应体,默认优先 JSON,失败后返回文本。
|
|
80
|
+
* @throws {ZentaoError} 传输层失败时抛出,详见 {@link ZentaoClient.request}。
|
|
81
|
+
*/
|
|
82
|
+
fetch(url: string, options: ClientRequestOptions, token?: string, fetchOptions?: Pick<RequestInit, 'cache' | 'credentials'>): Promise<unknown>;
|
|
83
|
+
/**
|
|
84
|
+
* 匿名获取禅道站点 `/?mode=getconfig` 配置,只需站点地址,无需登录或 profile。
|
|
85
|
+
*
|
|
86
|
+
* 不发送 API Token,浏览器请求显式省略 Cookie 等凭据,不受 API Token 过期影响。
|
|
87
|
+
*
|
|
88
|
+
* 默认复用不超过 24 小时的缓存;缺失、过期或时间异常时重新获取。
|
|
89
|
+
* `forceRefresh: true` 忽略缓存。同一客户端的并发刷新共用首次调用的传输选项;
|
|
90
|
+
* 后加入的调用仍可通过自己的 signal 取消等待。
|
|
91
|
+
* 成功后更新实例缓存;启用 `persistProfiles` 且绑定了 profile 时仅更新其配置和获取时间。
|
|
92
|
+
* 返回独立副本,修改返回值不会改变缓存。此方法不会忽略配置获取错误。
|
|
93
|
+
*
|
|
94
|
+
* @param options - 缓存、超时、TLS 与取消选项。
|
|
95
|
+
* @returns 服务器配置。
|
|
96
|
+
* @throws {ZentaoError} 传输错误、`E_INVALID_ZENTAO_CONFIG`、`E_INVALID_ZENTAO_VERSION` 或 profile 存储错误。
|
|
97
|
+
*/
|
|
98
|
+
getZentaoConfig(options?: GetZentaoConfigOptions): Promise<ServerConfig>;
|
|
99
|
+
private loadZentaoConfig;
|
|
100
|
+
private waitForConfig;
|
|
68
101
|
/**
|
|
69
102
|
* 发起 `GET` 请求。
|
|
70
103
|
*
|
|
@@ -106,10 +139,14 @@ export declare class ZentaoClient {
|
|
|
106
139
|
/**
|
|
107
140
|
* 使用账号密码登录禅道。
|
|
108
141
|
*
|
|
109
|
-
*
|
|
142
|
+
* 验证成功后强制获取一次站点配置,再把返回的 Token 写入当前客户端实例;
|
|
143
|
+
* 配置获取失败默认阻断登录,只有全局 `skipVersionCheckOnConfigError` 可允许继续。
|
|
144
|
+
* 全局 `version` 不跳过登录时的配置获取。
|
|
110
145
|
* 当全局 `persistProfiles` 为真时,会同时把账号、Token、用户信息、服务端配置和
|
|
111
146
|
* 客户端偏好(仅在显式设置过 `timeout` / `insecure` 时)持久化为本地 profile,
|
|
112
147
|
* 并切换为当前 profile,方便下次通过 {@link ZentaoClient.fromProfile} 直接登录态恢复。
|
|
148
|
+
* 重新登录同一账号时保留已有自定义字段,以及未被显式覆盖的客户端偏好。
|
|
149
|
+
* 保存的 `timeout` / `insecure` 与请求一致:全局显式值优先于实例默认值。
|
|
113
150
|
*
|
|
114
151
|
* @param account - 禅道用户账号。
|
|
115
152
|
* @param password - 禅道用户密码(明文,仅在传输层 TLS 内使用)。
|
|
@@ -140,13 +177,16 @@ export declare class ZentaoClient {
|
|
|
140
177
|
/**
|
|
141
178
|
* 根据本地持久化 profile 创建客户端。
|
|
142
179
|
*
|
|
143
|
-
*
|
|
180
|
+
* 默认调用 {@link switchProfile}:若 `profileKey` 存在则刷新其 `lastUsedTime` 并设为当前 profile;
|
|
144
181
|
* 不传 `profileKey` 时使用当前 profile。Profile 中保存的 `timeout` / `insecure` 偏好也会被带回到客户端实例。
|
|
182
|
+
* `activate: false` 时只读存储,不切换账号、不更新时间,支持可读但不可写的存储。
|
|
183
|
+
* 两种模式均不替换全局客户端;后续配置刷新是否写回仍由全局 `persistProfiles` 控制。
|
|
145
184
|
*
|
|
146
185
|
* @param profileKey - 可选的 profile key,格式为 `account@server`;不传时使用当前 profile。
|
|
186
|
+
* @param options - 恢复选项;默认保持切换当前 profile 的行为。
|
|
147
187
|
* @returns 用 profile 还原后的客户端实例。
|
|
148
188
|
* @throws {ZentaoError} `E_NO_PROFILE`(无任何 profile 且未传 key)、`E_PROFILE_NOT_FOUND`(指定 key 不存在)、
|
|
149
|
-
* `E_PROFILE_STORAGE_UNAVAILABLE`(运行时无法访问持久化存储)。
|
|
189
|
+
* `E_PROFILE_STORAGE_INVALID`(存储内容不合法)、`E_PROFILE_STORAGE_UNAVAILABLE`(运行时无法访问持久化存储)。
|
|
150
190
|
*/
|
|
151
|
-
static fromProfile(profileKey?: string): Promise<ZentaoClient>;
|
|
191
|
+
static fromProfile(profileKey?: string, options?: FromProfileOptions): Promise<ZentaoClient>;
|
|
152
192
|
}
|
package/dist/client/index.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import { ZentaoError } from '../misc/errors.js';
|
|
1
|
+
import { isZentaoConfigFetchError, ZentaoError } from '../misc/errors.js';
|
|
2
|
+
import { parseZentaoVersion } from '../misc/zentao-version.js';
|
|
2
3
|
import { assertInsecureSupported, fetchWithInsecureTls } from '../misc/environment.js';
|
|
3
4
|
import { getGlobalOptions, setGlobalOptions } from '../misc/global-options.js';
|
|
4
|
-
import {
|
|
5
|
+
import { getProfileOrThrow, saveLoginProfile, switchProfile, updateProfileServerConfig } from '../profiles/index.js';
|
|
5
6
|
import { isRecord, normalizeSiteUrl } from '../utils/index.js';
|
|
6
7
|
const DEFAULT_TIMEOUT = 10000;
|
|
8
|
+
const CONFIG_MAX_AGE = 24 * 60 * 60 * 1000;
|
|
9
|
+
function isServerConfig(value) {
|
|
10
|
+
return isRecord(value) && typeof value.version === 'string' && value.version.trim().length > 0;
|
|
11
|
+
}
|
|
7
12
|
function appendQueryValue(search, key, value) {
|
|
8
13
|
if (value === undefined)
|
|
9
14
|
return;
|
|
@@ -298,6 +303,10 @@ export class ZentaoClient {
|
|
|
298
303
|
token;
|
|
299
304
|
timeout;
|
|
300
305
|
insecure;
|
|
306
|
+
profileKey;
|
|
307
|
+
serverConfig;
|
|
308
|
+
serverConfigFetchedAt;
|
|
309
|
+
configRequest;
|
|
301
310
|
constructor(input) {
|
|
302
311
|
const options = typeof input === 'string' ? { baseUrl: input } : input;
|
|
303
312
|
this.siteUrl = normalizeSiteUrl(options.baseUrl);
|
|
@@ -307,20 +316,33 @@ export class ZentaoClient {
|
|
|
307
316
|
this.insecure = options.insecure;
|
|
308
317
|
}
|
|
309
318
|
async request(path, options = {}) {
|
|
319
|
+
return this.fetch(buildUrl(this.baseUrl, path, options.query), options, this.token);
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* 使用完整 URL 发起请求,复用 API 与站点配置的传输层。
|
|
323
|
+
*
|
|
324
|
+
* @param url - 完整请求 URL,包含所需的查询参数。
|
|
325
|
+
* @param options - 单次请求选项;其中 `query` 需由调用方预先拼入 `url`。
|
|
326
|
+
* @param token - 显式注入的 Token;省略时不自动使用实例保存的 Token。
|
|
327
|
+
* @param fetchOptions - 原生 fetch 的缓存与凭据选项。
|
|
328
|
+
* @returns 按 `options.responseType` 解析的响应体,默认优先 JSON,失败后返回文本。
|
|
329
|
+
* @throws {ZentaoError} 传输层失败时抛出,详见 {@link ZentaoClient.request}。
|
|
330
|
+
*/
|
|
331
|
+
async fetch(url, options, token, fetchOptions = {}) {
|
|
310
332
|
const globals = getGlobalOptions();
|
|
311
333
|
const method = options.method ?? 'GET';
|
|
312
334
|
const timeout = options.timeout ?? globals.timeout ?? this.timeout ?? DEFAULT_TIMEOUT;
|
|
313
335
|
const insecure = options.insecure ?? globals.insecure ?? this.insecure;
|
|
314
336
|
assertInsecureSupported(insecure);
|
|
315
|
-
const url = buildUrl(this.baseUrl, path, options.query);
|
|
316
337
|
const headers = new Headers(options.headers);
|
|
317
|
-
if (
|
|
318
|
-
headers.set('Token',
|
|
338
|
+
if (token) {
|
|
339
|
+
headers.set('Token', token);
|
|
319
340
|
}
|
|
320
341
|
const init = {
|
|
321
342
|
method,
|
|
322
343
|
headers,
|
|
323
344
|
redirect: 'manual',
|
|
345
|
+
...fetchOptions,
|
|
324
346
|
};
|
|
325
347
|
// GET 请求不携带 body,避免浏览器和部分代理拒绝请求。
|
|
326
348
|
if (options.body !== undefined && method !== 'GET') {
|
|
@@ -365,6 +387,75 @@ export class ZentaoClient {
|
|
|
365
387
|
cleanup();
|
|
366
388
|
}
|
|
367
389
|
}
|
|
390
|
+
/**
|
|
391
|
+
* 匿名获取禅道站点 `/?mode=getconfig` 配置,只需站点地址,无需登录或 profile。
|
|
392
|
+
*
|
|
393
|
+
* 不发送 API Token,浏览器请求显式省略 Cookie 等凭据,不受 API Token 过期影响。
|
|
394
|
+
*
|
|
395
|
+
* 默认复用不超过 24 小时的缓存;缺失、过期或时间异常时重新获取。
|
|
396
|
+
* `forceRefresh: true` 忽略缓存。同一客户端的并发刷新共用首次调用的传输选项;
|
|
397
|
+
* 后加入的调用仍可通过自己的 signal 取消等待。
|
|
398
|
+
* 成功后更新实例缓存;启用 `persistProfiles` 且绑定了 profile 时仅更新其配置和获取时间。
|
|
399
|
+
* 返回独立副本,修改返回值不会改变缓存。此方法不会忽略配置获取错误。
|
|
400
|
+
*
|
|
401
|
+
* @param options - 缓存、超时、TLS 与取消选项。
|
|
402
|
+
* @returns 服务器配置。
|
|
403
|
+
* @throws {ZentaoError} 传输错误、`E_INVALID_ZENTAO_CONFIG`、`E_INVALID_ZENTAO_VERSION` 或 profile 存储错误。
|
|
404
|
+
*/
|
|
405
|
+
async getZentaoConfig(options = {}) {
|
|
406
|
+
const config = await this.loadZentaoConfig(options, getGlobalOptions().persistProfiles ? this.profileKey : undefined);
|
|
407
|
+
return structuredClone(config);
|
|
408
|
+
}
|
|
409
|
+
async loadZentaoConfig(options, profileKey) {
|
|
410
|
+
if (options.signal?.aborted)
|
|
411
|
+
throw new ZentaoError('E_ABORTED');
|
|
412
|
+
// 刷新进行中时等待其结果,避免同一批调用混用旧缓存和新配置。
|
|
413
|
+
if (this.configRequest)
|
|
414
|
+
return this.waitForConfig(this.configRequest, options.signal);
|
|
415
|
+
const fetchedAt = typeof this.serverConfigFetchedAt === 'string' ? Date.parse(this.serverConfigFetchedAt) : NaN;
|
|
416
|
+
const age = Date.now() - fetchedAt;
|
|
417
|
+
if (!options.forceRefresh && isServerConfig(this.serverConfig) && age >= 0 && age <= CONFIG_MAX_AGE) {
|
|
418
|
+
parseZentaoVersion(this.serverConfig.version);
|
|
419
|
+
return this.serverConfig;
|
|
420
|
+
}
|
|
421
|
+
const pending = this.fetch(buildUrl(this.siteUrl, '/', { mode: 'getconfig' }), {
|
|
422
|
+
method: 'GET', timeout: options.timeout, insecure: options.insecure, signal: options.signal,
|
|
423
|
+
}, undefined, { cache: 'no-store', credentials: 'omit' }).then(async (config) => {
|
|
424
|
+
if (!isServerConfig(config))
|
|
425
|
+
throw new ZentaoError('E_INVALID_ZENTAO_CONFIG');
|
|
426
|
+
parseZentaoVersion(config.version);
|
|
427
|
+
const timestamp = new Date().toISOString();
|
|
428
|
+
if (profileKey)
|
|
429
|
+
await updateProfileServerConfig(profileKey, config, timestamp);
|
|
430
|
+
this.serverConfig = config;
|
|
431
|
+
this.serverConfigFetchedAt = timestamp;
|
|
432
|
+
return config;
|
|
433
|
+
});
|
|
434
|
+
this.configRequest = pending;
|
|
435
|
+
try {
|
|
436
|
+
return await pending;
|
|
437
|
+
}
|
|
438
|
+
finally {
|
|
439
|
+
this.configRequest = undefined;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
async waitForConfig(pending, signal) {
|
|
443
|
+
if (!signal)
|
|
444
|
+
return pending;
|
|
445
|
+
let onAbort;
|
|
446
|
+
const aborted = new Promise((_, reject) => {
|
|
447
|
+
onAbort = () => reject(new ZentaoError('E_ABORTED'));
|
|
448
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
449
|
+
if (signal.aborted)
|
|
450
|
+
onAbort();
|
|
451
|
+
});
|
|
452
|
+
try {
|
|
453
|
+
return await Promise.race([pending, aborted]);
|
|
454
|
+
}
|
|
455
|
+
finally {
|
|
456
|
+
signal.removeEventListener('abort', onAbort);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
368
459
|
/**
|
|
369
460
|
* 发起 `GET` 请求。
|
|
370
461
|
*
|
|
@@ -414,10 +505,14 @@ export class ZentaoClient {
|
|
|
414
505
|
/**
|
|
415
506
|
* 使用账号密码登录禅道。
|
|
416
507
|
*
|
|
417
|
-
*
|
|
508
|
+
* 验证成功后强制获取一次站点配置,再把返回的 Token 写入当前客户端实例;
|
|
509
|
+
* 配置获取失败默认阻断登录,只有全局 `skipVersionCheckOnConfigError` 可允许继续。
|
|
510
|
+
* 全局 `version` 不跳过登录时的配置获取。
|
|
418
511
|
* 当全局 `persistProfiles` 为真时,会同时把账号、Token、用户信息、服务端配置和
|
|
419
512
|
* 客户端偏好(仅在显式设置过 `timeout` / `insecure` 时)持久化为本地 profile,
|
|
420
513
|
* 并切换为当前 profile,方便下次通过 {@link ZentaoClient.fromProfile} 直接登录态恢复。
|
|
514
|
+
* 重新登录同一账号时保留已有自定义字段,以及未被显式覆盖的客户端偏好。
|
|
515
|
+
* 保存的 `timeout` / `insecure` 与请求一致:全局显式值优先于实例默认值。
|
|
421
516
|
*
|
|
422
517
|
* @param account - 禅道用户账号。
|
|
423
518
|
* @param password - 禅道用户密码(明文,仅在传输层 TLS 内使用)。
|
|
@@ -430,25 +525,38 @@ export class ZentaoClient {
|
|
|
430
525
|
if (response.status !== 'success' || !response.token) {
|
|
431
526
|
throw new ZentaoError('E_LOGIN_FAILED');
|
|
432
527
|
}
|
|
433
|
-
this.token = response.token;
|
|
434
528
|
const globals = getGlobalOptions();
|
|
529
|
+
let serverConfig;
|
|
530
|
+
try {
|
|
531
|
+
// 共用 getZentaoConfig 的获取流程,但登录确认前不能刷新之前绑定的账号。
|
|
532
|
+
serverConfig = await this.loadZentaoConfig({ forceRefresh: true });
|
|
533
|
+
}
|
|
534
|
+
catch (error) {
|
|
535
|
+
if (!globals.skipVersionCheckOnConfigError || !isZentaoConfigFetchError(error))
|
|
536
|
+
throw error;
|
|
537
|
+
}
|
|
538
|
+
let profileKey;
|
|
435
539
|
if (globals.persistProfiles) {
|
|
436
540
|
const config = {};
|
|
437
|
-
const timeout =
|
|
438
|
-
const insecure =
|
|
541
|
+
const timeout = globals.timeout ?? this.timeout;
|
|
542
|
+
const insecure = globals.insecure ?? this.insecure;
|
|
439
543
|
if (timeout !== undefined)
|
|
440
544
|
config.timeout = timeout;
|
|
441
545
|
if (insecure !== undefined)
|
|
442
546
|
config.insecure = insecure;
|
|
443
|
-
await
|
|
547
|
+
const profile = await saveLoginProfile({
|
|
444
548
|
server: this.siteUrl,
|
|
445
549
|
account,
|
|
446
550
|
token: response.token,
|
|
447
551
|
user: isRecord(response.user) ? response.user : undefined,
|
|
448
|
-
serverConfig:
|
|
552
|
+
serverConfig: serverConfig ? structuredClone(serverConfig) : undefined,
|
|
553
|
+
serverConfigFetchedAt: serverConfig ? this.serverConfigFetchedAt : undefined,
|
|
449
554
|
config: Object.keys(config).length > 0 ? config : undefined,
|
|
450
555
|
});
|
|
556
|
+
profileKey = profile.key;
|
|
451
557
|
}
|
|
558
|
+
this.token = response.token;
|
|
559
|
+
this.profileKey = profileKey;
|
|
452
560
|
return response.token;
|
|
453
561
|
}
|
|
454
562
|
/**
|
|
@@ -479,23 +587,28 @@ export class ZentaoClient {
|
|
|
479
587
|
/**
|
|
480
588
|
* 根据本地持久化 profile 创建客户端。
|
|
481
589
|
*
|
|
482
|
-
*
|
|
590
|
+
* 默认调用 {@link switchProfile}:若 `profileKey` 存在则刷新其 `lastUsedTime` 并设为当前 profile;
|
|
483
591
|
* 不传 `profileKey` 时使用当前 profile。Profile 中保存的 `timeout` / `insecure` 偏好也会被带回到客户端实例。
|
|
592
|
+
* `activate: false` 时只读存储,不切换账号、不更新时间,支持可读但不可写的存储。
|
|
593
|
+
* 两种模式均不替换全局客户端;后续配置刷新是否写回仍由全局 `persistProfiles` 控制。
|
|
484
594
|
*
|
|
485
595
|
* @param profileKey - 可选的 profile key,格式为 `account@server`;不传时使用当前 profile。
|
|
596
|
+
* @param options - 恢复选项;默认保持切换当前 profile 的行为。
|
|
486
597
|
* @returns 用 profile 还原后的客户端实例。
|
|
487
598
|
* @throws {ZentaoError} `E_NO_PROFILE`(无任何 profile 且未传 key)、`E_PROFILE_NOT_FOUND`(指定 key 不存在)、
|
|
488
|
-
* `E_PROFILE_STORAGE_UNAVAILABLE`(运行时无法访问持久化存储)。
|
|
599
|
+
* `E_PROFILE_STORAGE_INVALID`(存储内容不合法)、`E_PROFILE_STORAGE_UNAVAILABLE`(运行时无法访问持久化存储)。
|
|
489
600
|
*/
|
|
490
|
-
static async fromProfile(profileKey) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
const activeProfile = await switchProfile(profileKey);
|
|
494
|
-
return new ZentaoClient({
|
|
601
|
+
static async fromProfile(profileKey, options = {}) {
|
|
602
|
+
const activeProfile = options.activate === false ? await getProfileOrThrow(profileKey) : await switchProfile(profileKey);
|
|
603
|
+
const client = new ZentaoClient({
|
|
495
604
|
baseUrl: activeProfile.server,
|
|
496
605
|
token: activeProfile.token,
|
|
497
606
|
timeout: typeof activeProfile.config?.timeout === 'number' ? activeProfile.config.timeout : undefined,
|
|
498
607
|
insecure: typeof activeProfile.config?.insecure === 'boolean' ? activeProfile.config.insecure : undefined,
|
|
499
608
|
});
|
|
609
|
+
client.profileKey = activeProfile.key;
|
|
610
|
+
client.serverConfig = activeProfile.serverConfig;
|
|
611
|
+
client.serverConfigFetchedAt = activeProfile.serverConfigFetchedAt;
|
|
612
|
+
return client;
|
|
500
613
|
}
|
|
501
614
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { ZentaoClient } from './client/index.js';
|
|
|
2
2
|
export { ERRORS, ZentaoError, type ErrorCode } from './misc/errors.js';
|
|
3
3
|
export { getGlobalOptions, setGlobalOptions } from './misc/global-options.js';
|
|
4
4
|
export { ZENTAO_PROFILES_STORAGE_KEY, addProfile, deleteProfile, getAllProfiles, getProfile, getProfileKey, switchProfile, } from './profiles/index.js';
|
|
5
|
-
export { defineModuleActions, defineModules, type DefineModulesOptions, type ExportRegistryOptions, type ExportedModuleAction, type ExportedModuleDefinition, exportRegistry, extendModuleAction, getModuleNames, getModule, getModuleAction, getModuleActionParams, getObjectProps, } from './modules/registry.js';
|
|
5
|
+
export { defineModuleActions, defineModules, type DefineModulesOptions, type ExportRegistryOptions, type ExportedModuleAction, type ExportedModuleDefinition, exportRegistry, extendModuleAction, getModuleNames, isModuleName, getModule, getModuleAction, getModuleActionParams, getObjectProps, } from './modules/registry.js';
|
|
6
6
|
export { request, type BuiltinRequestName, type RequestParamsFor, type RequestResultFor, } from './request/index.js';
|
|
7
7
|
export { pickFields, pickFieldsSingle, filterData, searchData, sortData, processData, } from './utils/index.js';
|
|
8
8
|
export { BUILD, VERSION } from './version.js';
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { ZentaoClient } from './client/index.js';
|
|
|
2
2
|
export { ERRORS, ZentaoError } from './misc/errors.js';
|
|
3
3
|
export { getGlobalOptions, setGlobalOptions } from './misc/global-options.js';
|
|
4
4
|
export { ZENTAO_PROFILES_STORAGE_KEY, addProfile, deleteProfile, getAllProfiles, getProfile, getProfileKey, switchProfile, } from './profiles/index.js';
|
|
5
|
-
export { defineModuleActions, defineModules, exportRegistry, extendModuleAction, getModuleNames, getModule, getModuleAction, getModuleActionParams, getObjectProps, } from './modules/registry.js';
|
|
5
|
+
export { defineModuleActions, defineModules, exportRegistry, extendModuleAction, getModuleNames, isModuleName, getModule, getModuleAction, getModuleActionParams, getObjectProps, } from './modules/registry.js';
|
|
6
6
|
export { request, } from './request/index.js';
|
|
7
7
|
export { pickFields, pickFieldsSingle, filterData, searchData, sortData, processData, } from './utils/index.js';
|
|
8
8
|
export { BUILD, VERSION } from './version.js';
|
package/dist/misc/errors.d.ts
CHANGED
|
@@ -13,10 +13,13 @@ export declare const ERRORS: {
|
|
|
13
13
|
readonly E_ABORTED: "Request was aborted.";
|
|
14
14
|
readonly E_INSECURE_BROWSER: "The insecure option is only supported in Node.js runtimes.";
|
|
15
15
|
readonly E_LOGIN_FAILED: "ZenTao login failed.";
|
|
16
|
+
readonly E_INVALID_ZENTAO_CONFIG: "ZenTao configuration must be an object with a non-empty version.";
|
|
17
|
+
readonly E_INVALID_ZENTAO_VERSION: "Invalid ZenTao version: {version}";
|
|
18
|
+
readonly E_UNSUPPORTED_ZENTAO_VERSION: "Action {action} does not support ZenTao {version}; minimum versions: {minVersion}";
|
|
16
19
|
readonly E_INVALID_PROFILE: "Invalid ZenTao profile.";
|
|
17
20
|
readonly E_NO_PROFILE: "No ZenTao profile is configured.";
|
|
18
21
|
readonly E_PROFILE_NOT_FOUND: "ZenTao profile not found: {profileKey}";
|
|
19
|
-
readonly E_PROFILE_STORAGE_INVALID: "ZenTao profile storage
|
|
22
|
+
readonly E_PROFILE_STORAGE_INVALID: "ZenTao profile storage must be a JSON object with a profiles array and an optional currentProfile string.";
|
|
20
23
|
readonly E_PROFILE_STORAGE_UNAVAILABLE: "ZenTao profile storage is unavailable in this runtime.";
|
|
21
24
|
readonly E_INVALID_MODULE: "Unknown module: {module}";
|
|
22
25
|
readonly E_INVALID_ACTION: "Unknown action: {module}-{action}";
|
|
@@ -36,6 +39,8 @@ export declare const ERRORS: {
|
|
|
36
39
|
};
|
|
37
40
|
/** SDK 已知错误码,对应 {@link ERRORS} 的 key。 */
|
|
38
41
|
export type ErrorCode = keyof typeof ERRORS;
|
|
42
|
+
/** 仅配置获取失败可选择跳过;取消、版本错误和持久化错误仍须抛出。 @internal */
|
|
43
|
+
export declare function isZentaoConfigFetchError(error: unknown): boolean;
|
|
39
44
|
/**
|
|
40
45
|
* SDK 统一错误类型。
|
|
41
46
|
*
|
package/dist/misc/errors.js
CHANGED
|
@@ -13,10 +13,13 @@ export const ERRORS = {
|
|
|
13
13
|
E_ABORTED: 'Request was aborted.',
|
|
14
14
|
E_INSECURE_BROWSER: 'The insecure option is only supported in Node.js runtimes.',
|
|
15
15
|
E_LOGIN_FAILED: 'ZenTao login failed.',
|
|
16
|
+
E_INVALID_ZENTAO_CONFIG: 'ZenTao configuration must be an object with a non-empty version.',
|
|
17
|
+
E_INVALID_ZENTAO_VERSION: 'Invalid ZenTao version: {version}',
|
|
18
|
+
E_UNSUPPORTED_ZENTAO_VERSION: 'Action {action} does not support ZenTao {version}; minimum versions: {minVersion}',
|
|
16
19
|
E_INVALID_PROFILE: 'Invalid ZenTao profile.',
|
|
17
20
|
E_NO_PROFILE: 'No ZenTao profile is configured.',
|
|
18
21
|
E_PROFILE_NOT_FOUND: 'ZenTao profile not found: {profileKey}',
|
|
19
|
-
E_PROFILE_STORAGE_INVALID: 'ZenTao profile storage
|
|
22
|
+
E_PROFILE_STORAGE_INVALID: 'ZenTao profile storage must be a JSON object with a profiles array and an optional currentProfile string.',
|
|
20
23
|
E_PROFILE_STORAGE_UNAVAILABLE: 'ZenTao profile storage is unavailable in this runtime.',
|
|
21
24
|
E_INVALID_MODULE: 'Unknown module: {module}',
|
|
22
25
|
E_INVALID_ACTION: 'Unknown action: {module}-{action}',
|
|
@@ -34,6 +37,11 @@ export const ERRORS = {
|
|
|
34
37
|
E_UPLOAD_FILE_TOO_LARGE: 'Upload file is {size} bytes and exceeds the {limit} byte limit.',
|
|
35
38
|
E_INVALID_UPLOAD_SOURCE: 'Invalid upload source for field: {field}',
|
|
36
39
|
};
|
|
40
|
+
/** 仅配置获取失败可选择跳过;取消、版本错误和持久化错误仍须抛出。 @internal */
|
|
41
|
+
export function isZentaoConfigFetchError(error) {
|
|
42
|
+
return error instanceof ZentaoError && (error.code === 'E_HTTP_ERROR' || error.code === 'E_NETWORK_ERROR'
|
|
43
|
+
|| error.code === 'E_TIMEOUT' || error.code === 'E_INVALID_ZENTAO_CONFIG');
|
|
44
|
+
}
|
|
37
45
|
/**
|
|
38
46
|
* SDK 统一错误类型。
|
|
39
47
|
*
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface ZentaoVersion {
|
|
2
|
+
edition: string;
|
|
3
|
+
parts: number[];
|
|
4
|
+
}
|
|
5
|
+
/** 只接受开源版、biz、max、ipd 系列的点分数字正式版本。 */
|
|
6
|
+
export declare function parseZentaoVersion(version: string): ZentaoVersion;
|
|
7
|
+
/** 注册和生成时共用,缺少版本、格式非法或同系列重复均拒绝。 */
|
|
8
|
+
export declare function validateMinVersion(value: unknown): asserts value is readonly string[];
|
|
9
|
+
/** 同系列逐段比较,缺少的数字段视为零;未列出的系列不支持。 */
|
|
10
|
+
export declare function supportsZentaoVersion(version: ZentaoVersion, minimums: readonly string[]): boolean;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ZentaoError } from './errors.js';
|
|
2
|
+
/** 只接受开源版、biz、max、ipd 系列的点分数字正式版本。 */
|
|
3
|
+
export function parseZentaoVersion(version) {
|
|
4
|
+
const match = typeof version === 'string' ? version.trim().match(/^(biz|max|ipd)?(\d+(?:\.\d+)*)$/i) : null;
|
|
5
|
+
const parts = match?.[2].split('.').map(Number);
|
|
6
|
+
if (!match || !parts?.every(Number.isSafeInteger)) {
|
|
7
|
+
throw new ZentaoError('E_INVALID_ZENTAO_VERSION', { version: String(version) });
|
|
8
|
+
}
|
|
9
|
+
return { edition: (match[1] ?? '').toLowerCase(), parts };
|
|
10
|
+
}
|
|
11
|
+
/** 注册和生成时共用,缺少版本、格式非法或同系列重复均拒绝。 */
|
|
12
|
+
export function validateMinVersion(value) {
|
|
13
|
+
try {
|
|
14
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
15
|
+
throw new Error('minVersion must be a non-empty array.');
|
|
16
|
+
const editions = Array.from(value, version => parseZentaoVersion(version).edition);
|
|
17
|
+
if (new Set(editions).size !== editions.length)
|
|
18
|
+
throw new Error('Duplicate editions in minVersion.');
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw new ZentaoError('E_INVALID_ACTION_DEFINITION', undefined, error);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** 同系列逐段比较,缺少的数字段视为零;未列出的系列不支持。 */
|
|
25
|
+
export function supportsZentaoVersion(version, minimums) {
|
|
26
|
+
const minimum = minimums.map(parseZentaoVersion).find(item => item.edition === version.edition);
|
|
27
|
+
if (!minimum)
|
|
28
|
+
return false;
|
|
29
|
+
for (let index = 0; index < Math.max(version.parts.length, minimum.parts.length); index++) {
|
|
30
|
+
const difference = (version.parts[index] ?? 0) - (minimum.parts[index] ?? 0);
|
|
31
|
+
if (difference !== 0)
|
|
32
|
+
return difference > 0;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
package/dist/modules/define.d.ts
CHANGED
|
@@ -21,7 +21,8 @@ export interface DefineModulesOptions {
|
|
|
21
21
|
*
|
|
22
22
|
* @param input - 单个或一组模块定义。
|
|
23
23
|
* @param options - 写入策略,参见 {@link DefineModulesOptions}。
|
|
24
|
-
* @throws {ZentaoError} `E_INVALID_MODULE_DEFINITION` —— 缺少 `name` 或 `actions`
|
|
24
|
+
* @throws {ZentaoError} `E_INVALID_MODULE_DEFINITION` —— 缺少 `name` 或 `actions` 字段;
|
|
25
|
+
* `E_INVALID_ACTION_DEFINITION` —— 动作字段非法或 `minVersion` 缺失、为空、格式错误、系列重复。
|
|
25
26
|
*/
|
|
26
27
|
export declare function defineModules(input: ModuleDefinition | ModuleDefinition[], options?: DefineModulesOptions): void;
|
|
27
28
|
/**
|
|
@@ -34,7 +35,7 @@ export declare function defineModules(input: ModuleDefinition | ModuleDefinition
|
|
|
34
35
|
* @param moduleName - 目标模块名(大小写不敏感)。
|
|
35
36
|
* @param input - 单个或一组动作定义。
|
|
36
37
|
* @throws {ZentaoError} `E_INVALID_MODULE`(模块未注册)、`E_INVALID_ACTION_DEFINITION`
|
|
37
|
-
* (动作缺少 `name` / `path
|
|
38
|
+
* (动作缺少 `name` / `path`、`minVersion` 无效,或 `method` / `resultType` 类型非法),或
|
|
38
39
|
* `E_INDETERMINATE_ACTION_METHOD` / `E_INDETERMINATE_ACTION_RESULT_TYPE`(省略字段且无法按 `type` 推导)。
|
|
39
40
|
*/
|
|
40
41
|
export declare function defineModuleActions(moduleName: string, input: ModuleAction | ModuleAction[]): void;
|
|
@@ -52,7 +53,7 @@ export declare function defineModuleActions(moduleName: string, input: ModuleAct
|
|
|
52
53
|
* @param actionName - 目标动作名(大小写不敏感)。
|
|
53
54
|
* @param action - 深度合并的补丁对象,或接收当前动作深克隆并返回完整动作定义的函数。
|
|
54
55
|
* @throws {ZentaoError} `E_INVALID_MODULE`(模块未注册)、`E_INVALID_ACTION`(动作不存在)、
|
|
55
|
-
* `E_INVALID_ACTION_DEFINITION`(合并结果缺少 `name` / `path
|
|
56
|
+
* `E_INVALID_ACTION_DEFINITION`(合并结果缺少 `name` / `path`、`minVersion` 无效,或 `method` / `resultType` 类型非法),
|
|
56
57
|
* 或 `E_INDETERMINATE_ACTION_METHOD` / `E_INDETERMINATE_ACTION_RESULT_TYPE`(省略字段且无法按 `type` 推导)。
|
|
57
58
|
*/
|
|
58
59
|
export declare function extendModuleAction(moduleName: string, actionName: string, action: Partial<ModuleAction> | ((action: ModuleAction) => ModuleAction)): void;
|
package/dist/modules/define.js
CHANGED
|
@@ -13,7 +13,8 @@ import { deepClone, deepMerge, findActionIndex, freezeAction, freezeModule, getM
|
|
|
13
13
|
*
|
|
14
14
|
* @param input - 单个或一组模块定义。
|
|
15
15
|
* @param options - 写入策略,参见 {@link DefineModulesOptions}。
|
|
16
|
-
* @throws {ZentaoError} `E_INVALID_MODULE_DEFINITION` —— 缺少 `name` 或 `actions`
|
|
16
|
+
* @throws {ZentaoError} `E_INVALID_MODULE_DEFINITION` —— 缺少 `name` 或 `actions` 字段;
|
|
17
|
+
* `E_INVALID_ACTION_DEFINITION` —— 动作字段非法或 `minVersion` 缺失、为空、格式错误、系列重复。
|
|
17
18
|
*/
|
|
18
19
|
export function defineModules(input, options = {}) {
|
|
19
20
|
const modules = getModulesState();
|
|
@@ -42,7 +43,7 @@ export function defineModules(input, options = {}) {
|
|
|
42
43
|
* @param moduleName - 目标模块名(大小写不敏感)。
|
|
43
44
|
* @param input - 单个或一组动作定义。
|
|
44
45
|
* @throws {ZentaoError} `E_INVALID_MODULE`(模块未注册)、`E_INVALID_ACTION_DEFINITION`
|
|
45
|
-
* (动作缺少 `name` / `path
|
|
46
|
+
* (动作缺少 `name` / `path`、`minVersion` 无效,或 `method` / `resultType` 类型非法),或
|
|
46
47
|
* `E_INDETERMINATE_ACTION_METHOD` / `E_INDETERMINATE_ACTION_RESULT_TYPE`(省略字段且无法按 `type` 推导)。
|
|
47
48
|
*/
|
|
48
49
|
export function defineModuleActions(moduleName, input) {
|
|
@@ -84,7 +85,7 @@ export function defineModuleActions(moduleName, input) {
|
|
|
84
85
|
* @param actionName - 目标动作名(大小写不敏感)。
|
|
85
86
|
* @param action - 深度合并的补丁对象,或接收当前动作深克隆并返回完整动作定义的函数。
|
|
86
87
|
* @throws {ZentaoError} `E_INVALID_MODULE`(模块未注册)、`E_INVALID_ACTION`(动作不存在)、
|
|
87
|
-
* `E_INVALID_ACTION_DEFINITION`(合并结果缺少 `name` / `path
|
|
88
|
+
* `E_INVALID_ACTION_DEFINITION`(合并结果缺少 `name` / `path`、`minVersion` 无效,或 `method` / `resultType` 类型非法),
|
|
88
89
|
* 或 `E_INDETERMINATE_ACTION_METHOD` / `E_INDETERMINATE_ACTION_RESULT_TYPE`(省略字段且无法按 `type` 推导)。
|
|
89
90
|
*/
|
|
90
91
|
export function extendModuleAction(moduleName, actionName, action) {
|