zentao-api 0.1.0 → 0.2.0-beta.2

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 (53) hide show
  1. package/LICENSE +2 -2
  2. package/README.md +221 -131
  3. package/dist/browser/zentao-api.global.js +2 -0
  4. package/dist/browser.d.ts +1 -0
  5. package/dist/browser.js +1 -0
  6. package/dist/client/index.d.ts +39 -0
  7. package/dist/client/index.js +172 -0
  8. package/dist/index.d.ts +8 -4
  9. package/dist/index.js +7 -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 +8 -0
  13. package/dist/misc/environment.js +124 -0
  14. package/dist/misc/errors.d.ts +30 -0
  15. package/dist/misc/errors.js +40 -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 +142 -0
  22. package/dist/modules/resolve.d.ts +7 -0
  23. package/dist/modules/resolve.js +206 -0
  24. package/dist/profiles/index.d.ts +14 -0
  25. package/dist/profiles/index.js +205 -0
  26. package/dist/request/index.d.ts +7 -0
  27. package/dist/request/index.js +65 -0
  28. package/dist/types/index.d.ts +296 -0
  29. package/dist/types/index.js +1 -0
  30. package/dist/utils/index.d.ts +6 -0
  31. package/dist/utils/index.js +26 -0
  32. package/dist/version.d.ts +2 -0
  33. package/dist/version.js +4 -0
  34. package/package.json +52 -76
  35. package/dist/types.d.ts +0 -70
  36. package/dist/utils.d.ts +0 -93
  37. package/dist/zentao-api.cjs.development.js +0 -3619
  38. package/dist/zentao-api.cjs.development.js.map +0 -1
  39. package/dist/zentao-api.cjs.production.min.js +0 -2
  40. package/dist/zentao-api.cjs.production.min.js.map +0 -1
  41. package/dist/zentao-api.esm.js +0 -3611
  42. package/dist/zentao-api.esm.js.map +0 -1
  43. package/dist/zentao-config.d.ts +0 -93
  44. package/dist/zentao-request-builder.d.ts +0 -120
  45. package/dist/zentao.d.ts +0 -175
  46. package/dist/zentao12.d.ts +0 -676
  47. package/src/index.ts +0 -5
  48. package/src/types.ts +0 -88
  49. package/src/utils.ts +0 -216
  50. package/src/zentao-config.ts +0 -150
  51. package/src/zentao-request-builder.ts +0 -227
  52. package/src/zentao.ts +0 -596
  53. package/src/zentao12.ts +0 -1272
package/src/zentao.ts DELETED
@@ -1,596 +0,0 @@
1
- import axios, {AxiosResponse} from 'axios';
2
- import Configstore from 'configstore';
3
- import kleur from 'kleur';
4
- import querystring from 'querystring';
5
- import {
6
- ZentaoApiResult,
7
- ZentaoOptions,
8
- ZentaoRequestMethod,
9
- ZentaoRequestParamPair,
10
- ZentaoRequestParams,
11
- ZentaoRequestType,
12
- } from './types';
13
- import {formatZentaoUrl, normalizeRequestParams, slimmingObject} from './utils';
14
- import ZentaoConfig from './zentao-config';
15
- import ZentaoRequestBuilder from './zentao-request-builder';
16
-
17
- /**
18
- * 禅道请求类
19
- *
20
- * @example
21
- * import { Zentao } from 'zentao-api';
22
- * const zentao = new Zentao({
23
- * url: 'https://pro.demo.zentao.net/', account: 'demo', password: '123456'
24
- * });
25
- * // TODO: 使用 zentao 调用其他 API
26
- */
27
- export default class Zentao {
28
- /**
29
- * 当前服务器和登录账号标识字符串
30
- */
31
- private readonly _identifier: string;
32
-
33
- /**
34
- * 是否将 token 存储到本地,如果设置为 `false`,则每次创建新的 `Zentao` 实例都会在首次调用 API 之前重新获取 Token
35
- */
36
- private readonly _preserveToken: boolean;
37
-
38
- /**
39
- * 当前实例名称
40
- */
41
- private readonly _sessionName: string;
42
-
43
- /**
44
- * 禅道服务器地址
45
- */
46
- private readonly _url: string;
47
-
48
- /**
49
- * 登录账号
50
- */
51
- private readonly _account: string;
52
-
53
- /**
54
- * 登录密码
55
- */
56
- private readonly _password: string;
57
-
58
- /**
59
- * 如果设置为 `true`,则会在控制台输出详细日志
60
- */
61
- private _debug: boolean;
62
-
63
- /**
64
- * 禅道配置
65
- */
66
- private _config?: ZentaoConfig;
67
-
68
- /**
69
- * 本地存储管理对象
70
- */
71
- private _store?: Configstore;
72
-
73
- /**
74
- * 用户指定的请求方式
75
- */
76
- private _userRequestType?: ZentaoRequestType;
77
-
78
- /**
79
- * 构造一个禅道 API 请求对象
80
- * @param {ZentaoOptions} [options] 选项,用于指定服务器地址、账号和密码以及 API 调用相关设置
81
- * @example
82
- * import { Zentao } from 'zentao-api';
83
- * const zentao = new Zentao({
84
- * url: 'https://pro.demo.zentao.net/', // 禅道服务器地址
85
- * account: 'demo', // 用户账号
86
- * password: '123456', // 用户密码
87
- * accessMode: 'GET', // 请求方式
88
- * preserveToken: true, // 是否将 token 存储到本地,如果设置为 `false`,则每次创建新的 `Zentao` 实例都会在首次调用 API 之前重新获取 Token
89
- * debug: true, // 如果设置为 `true`,则会在控制台输出详细日志
90
- * });
91
- * // TODO: 使用 zentao 调用其他 API
92
- */
93
- constructor(options: ZentaoOptions) {
94
- this._debug = options.debug ?? false;
95
-
96
- this._url = formatZentaoUrl(options.url);
97
- this._account = options.account;
98
- this._password = options.password;
99
-
100
- // 创建账号标识
101
- this._identifier = `${this.account}@${this._url}`;
102
-
103
- // Zentao 实例名称
104
- this._sessionName = `ZENTAO-API::${options.sessionName ??
105
- this._identifier}`;
106
-
107
- this._userRequestType = options.accessMode;
108
- this._preserveToken = options.preserveToken ?? true;
109
-
110
- if (this._debug) {
111
- console.log(
112
- [
113
- `${kleur.yellow('▶︎')} ${kleur
114
- .bold()
115
- .blue(this._sessionName)} ${kleur.yellow('◀︎')}`,
116
- ` url: ${kleur.magenta(this.url)}`,
117
- ` account: ${kleur.magenta(this.account)}`,
118
- ` password: ${kleur.magenta(this.password)}`,
119
- ` preserveToken: ${kleur.magenta(
120
- `${this._preserveToken}`
121
- )}`,
122
- ` sessionName: ${kleur.magenta(this._sessionName)}`,
123
- ` identifier: ${kleur.magenta(this._identifier)}`,
124
- ` requestType: ${kleur.magenta(this.requestType)}`,
125
- ].join('\n')
126
- );
127
- }
128
-
129
- // 从本地存储加载禅道配置
130
- if (this._preserveToken) {
131
- this._store = new Configstore(this._sessionName, {});
132
- const configFromStore = this._store.get('config');
133
- if (configFromStore) {
134
- this._config = new ZentaoConfig(configFromStore);
135
-
136
- if (this._debug) {
137
- console.log(
138
- [
139
- kleur.bold(
140
- `\n${kleur.gray('➡︎')} ${kleur
141
- .bold()
142
- .blue(
143
- 'Load zentao config from local storage'
144
- )}`
145
- ),
146
- ` ${JSON.stringify(configFromStore)}`,
147
- ].join('\n')
148
- );
149
- }
150
- }
151
- }
152
- }
153
-
154
- /**
155
- * 禅道服务器地址
156
- */
157
- get url(): string {
158
- return this._url;
159
- }
160
-
161
- /**
162
- * 当前实例名称
163
- */
164
- get sessionName(): string {
165
- return this._sessionName;
166
- }
167
-
168
- /**
169
- * 当前服务器和账号标识
170
- */
171
- get identifier(): string {
172
- return this._identifier;
173
- }
174
-
175
- /**
176
- * 当前用户账号
177
- */
178
- get account(): string {
179
- return this._account;
180
- }
181
-
182
- /**
183
- * 登录密码
184
- */
185
- get password(): string {
186
- return this._password;
187
- }
188
-
189
- /**
190
- * 请求方式
191
- */
192
- get requestType(): ZentaoRequestType {
193
- return this._userRequestType ?? this._config?.requestType ?? 'GET';
194
- }
195
-
196
- /**
197
- * 当前用于验证的 Token 字符串
198
- */
199
- get token(): string {
200
- return this._config?.token ?? '';
201
- }
202
-
203
- /**
204
- * 登录到禅道并更新用于请求 API 的 token,通常不需要手动调用此方法,在调用 API 时会自动判断 token 是否可用,如果不可用会自动调用此方法
205
- *
206
- * @returns 返回请求结果,当登录成功时,其中 `result` 字段为所登录的用户信息对象
207
- * @example
208
- * import { Zentao } from 'zentao-api';
209
- * const zentao = new Zentao({
210
- * url: 'https://pro.demo.zentao.net/', account: 'demo', password: '123456'
211
- * });
212
- * const result = await zentao.login();
213
- * const user = result.result; // 获取当前登录的用户对象
214
- * console.log('当前登录的用户名称为:', user.realname);
215
- */
216
- async login(): Promise<ZentaoApiResult> {
217
- await this.fetchConfig();
218
-
219
- const res = await this.m('user')
220
- .f('login')
221
- .useConverter((remoteData, result) => {
222
- if (remoteData.user) {
223
- result.result = remoteData.user;
224
- }
225
- return result;
226
- })
227
- .post({account: this.account, password: this.password});
228
-
229
- return res;
230
- }
231
-
232
- /**
233
- * 获取禅道服务器配置
234
- * @returns 禅道服务器配置
235
- */
236
- async fetchConfig(): Promise<ZentaoConfig> {
237
- const url = `${this._url}/?mode=getconfig`;
238
- try {
239
- const resp = await axios.get(url);
240
-
241
- const config = new ZentaoConfig(resp.data);
242
- this._config = config;
243
-
244
- this._log('fetchConfig', {resp});
245
- } catch (error) {
246
- this._log('fetchConfig', {url, error});
247
- throw error;
248
- }
249
-
250
- return this._config;
251
- }
252
-
253
- /**
254
- * 根据模块名创建一个禅道请求构建实例
255
- * @param moduleName 模块名
256
- * @param methodName 方法名
257
- * @param params 请求参数
258
- * @returns 禅道请求构建实例
259
- */
260
- module(
261
- moduleName: string,
262
- methodName?: string,
263
- params?: ZentaoRequestParams
264
- ): ZentaoRequestBuilder {
265
- return new ZentaoRequestBuilder(this, moduleName, methodName, params);
266
- }
267
-
268
- /**
269
- * 根据模块名创建一个禅道请求构建实例
270
- * @param moduleName 模块名
271
- * @param methodName 方法名
272
- * @param params 请求参数
273
- * @returns 禅道请求构建实例
274
- * @alias module
275
- */
276
- m(
277
- moduleName: string,
278
- methodName?: string,
279
- params?: ZentaoRequestParams
280
- ): ZentaoRequestBuilder {
281
- return this.module(moduleName, methodName, params);
282
- }
283
-
284
- /**
285
- * 向禅道服务器发起请求
286
- * @param moduleName 模块名
287
- * @param methodName 方法名
288
- * @param options 其他请求选项
289
- * @returns 请求结果
290
- */
291
- async request(
292
- moduleName: string,
293
- methodName: string = 'index',
294
- options: {
295
- params?: ZentaoRequestParams;
296
- data?: string | Record<string, any>;
297
- name?: string;
298
- method?: ZentaoRequestMethod;
299
- url?: string;
300
- resultConvertor?: (
301
- remoteData: any,
302
- result: ZentaoApiResult
303
- ) => ZentaoApiResult;
304
- fields?: string[];
305
- } = {}
306
- ): Promise<ZentaoApiResult> {
307
- if (
308
- (!this._config || this._config?.isTokenExpired) &&
309
- `${moduleName}/${methodName}`.toLowerCase() !== 'user/login'
310
- ) {
311
- await this.login();
312
- }
313
-
314
- if (!this._config) {
315
- throw new Error(
316
- `Zentao config is empty, makesure to fetch config before request from ${moduleName}-${methodName}.`
317
- );
318
- }
319
-
320
- const params = normalizeRequestParams(options.params);
321
- const url =
322
- options.url ?? this.createUrl(moduleName, methodName, params);
323
- const name =
324
- options.name ??
325
- `${moduleName}${methodName[0].toUpperCase()}${methodName.substr(
326
- 1
327
- )}`;
328
- const method = options.method ?? 'GET';
329
- const headers = {
330
- Cookie: this._config.tokenAuth,
331
- };
332
-
333
- let {data} = options;
334
- if (data && typeof data === 'object') {
335
- const formData: Record<string, any> = {};
336
- for (const key of Object.keys(data)) {
337
- const value = data[key];
338
- if (Array.isArray(value)) {
339
- value.forEach((item, index) => {
340
- formData[`${key}[${index}]`] = item;
341
- });
342
- delete data[key];
343
- } else {
344
- formData[key] = value;
345
- }
346
- }
347
- data = querystring.stringify(formData);
348
- }
349
-
350
- try {
351
- const resp = await axios.request({
352
- method,
353
- url,
354
- data,
355
- headers,
356
- });
357
-
358
- let result: ZentaoApiResult;
359
- const remoteData = resp.data;
360
- if (typeof remoteData === 'object' && remoteData !== null) {
361
- if (
362
- typeof remoteData.data === 'string' &&
363
- (remoteData.data[0] === '[' || remoteData.data[0] === '{')
364
- ) {
365
- remoteData.data = JSON.parse(remoteData.data);
366
- }
367
-
368
- const success =
369
- remoteData.status === 'success' ||
370
- remoteData.result === 'success';
371
- result = {
372
- status: success ? 1 : 0,
373
- msg: remoteData.message ?? (success ? 'success' : 'error'),
374
- result: remoteData.data ?? remoteData.result,
375
- };
376
- } else {
377
- result = {status: 0, msg: 'error', result: resp.data};
378
- }
379
-
380
- if (options.resultConvertor) {
381
- result = options.resultConvertor(remoteData, result);
382
- }
383
-
384
- if (
385
- options.fields &&
386
- typeof result.result === 'object' &&
387
- result.result
388
- ) {
389
- if (Array.isArray(result.result)) {
390
- result.result = result.result.map(x =>
391
- slimmingObject(x, options.fields!)
392
- );
393
- } else {
394
- result.result = slimmingObject(
395
- result.result,
396
- options.fields!
397
- );
398
- }
399
- }
400
-
401
- if (
402
- `${moduleName}/${methodName}` === 'user/login' &&
403
- result.status === 1
404
- ) {
405
- this._config?.renewToken();
406
- if (this._preserveToken) {
407
- this._store?.set('config', this._config);
408
- }
409
- }
410
-
411
- this._log(name, {url, result, params, data, resp});
412
- return result;
413
- } catch (error) {
414
- this._log(name, {url, error, params, data});
415
- throw error;
416
- }
417
- }
418
-
419
- /**
420
- * 生成请求地址
421
- * @param moduleName 模块名
422
- * @param methodName 方法名
423
- * @param params 其他参数
424
- * @returns 请求地址
425
- */
426
- createUrl(
427
- moduleName: string,
428
- methodName: string = 'index',
429
- params?: ZentaoRequestParamPair[]
430
- ): string {
431
- const config = this._config;
432
- if (!config) {
433
- throw new Error(
434
- 'Zentao config is empty, makesure fetch config before call others api.'
435
- );
436
- }
437
-
438
- const urlParts: string[] = [this.url];
439
- if (this.requestType === 'PATH_INFO') {
440
- urlParts.push(moduleName, config.requestFix, methodName);
441
- if (params) {
442
- for (const paramPair of params) {
443
- urlParts.push(config.requestFix, paramPair[1]);
444
- }
445
- }
446
- urlParts.push('.json');
447
- } else {
448
- urlParts.push(
449
- `?${config.moduleVar}=${moduleName}&${config.methodVar}=${methodName}`
450
- );
451
- if (params) {
452
- for (const paramPair of params) {
453
- urlParts.push(
454
- `&${paramPair[0]}=${encodeURIComponent(paramPair[1])}`
455
- );
456
- }
457
- }
458
- urlParts.push(`&${config.viewVar}=json`);
459
- }
460
- return urlParts.join('');
461
- }
462
-
463
- /**
464
- * 输出 API 请求日志
465
- * @param name 名称
466
- * @param atrributes 日志属性对象
467
- * @param others 其他日志内容
468
- */
469
- protected _log(
470
- name: string,
471
- atrributes: {
472
- url?: string;
473
- params?: ZentaoRequestParamPair[];
474
- data?: string | Record<string, any>;
475
- method?: string;
476
- resp?: AxiosResponse;
477
- result?: ZentaoApiResult;
478
- error?: any;
479
- },
480
- ...others: any[]
481
- ) {
482
- if (!this._debug) {
483
- return;
484
- }
485
-
486
- const logLines = ['\n\n'];
487
- const {resp, result} = atrributes;
488
- const status = resp?.status ?? '';
489
- const success = result ? result.status === 1 : status === 200;
490
- const url = (resp ? resp.config.url : atrributes.url) ?? '';
491
- const method = (resp ? resp.config.method : atrributes.method) ?? 'GET';
492
-
493
- logLines.push(
494
- kleur.bold(
495
- `${kleur.gray('➡︎')} ${kleur[
496
- success ? 'green' : 'red'
497
- ]().inverse(` ${name} ${success ? '✓' : '𐄂'} `)}`
498
- )
499
- );
500
-
501
- logLines.push(
502
- `\n ${kleur
503
- .bold()
504
- .blue(method.toUpperCase())} ${kleur.blue().underline(url)}`
505
- );
506
- logLines.push(
507
- ` status: ${kleur[status === 200 ? 'green' : 'red'](
508
- `● ${status}`
509
- )} ${kleur.gray(resp?.statusText ?? '')}`
510
- );
511
-
512
- if (atrributes.params) {
513
- logLines.push(`\n ${kleur.bold().blue('Request Parameters')}`);
514
- for (const pair of atrributes.params) {
515
- const pairValue =
516
- typeof pair[1] === 'string'
517
- ? pair[1]
518
- : JSON.stringify(pair[1]);
519
- logLines.push(` ${pair[0]}: ${kleur.magenta(pairValue)}`);
520
- }
521
- }
522
-
523
- if (resp?.config?.headers) {
524
- logLines.push(`\n ${kleur.bold().blue('Request Headers')}`);
525
- const headers = resp.config.headers;
526
- for (const key of Object.keys(headers)) {
527
- const value =
528
- typeof headers[key] === 'string'
529
- ? headers[key]
530
- : JSON.stringify(headers[key]);
531
- logLines.push(` ${key}: ${kleur.magenta(value)}`);
532
- }
533
- }
534
-
535
- if (atrributes.data) {
536
- let {data} = atrributes;
537
- logLines.push(`\n ${kleur.bold().blue('Request Data')}`);
538
- if (typeof data === 'string') {
539
- data = querystring.parse(data);
540
- }
541
- for (const key of Object.keys(data)) {
542
- const value =
543
- typeof data[key] === 'string'
544
- ? data[key]
545
- : JSON.stringify(data[key]);
546
- logLines.push(` ${key}: ${kleur.magenta(value)}`);
547
- }
548
- }
549
-
550
- if (result) {
551
- logLines.push(`\n ${kleur.bold().cyan('Response Data')}`);
552
- logLines.push(
553
- ` status: ${kleur[result.status === 1 ? 'green' : 'red'](
554
- result.status
555
- )},`
556
- );
557
- if (result.msg !== undefined) {
558
- logLines.push(
559
- ` msg: ${kleur[result.status === 1 ? 'green' : 'red'](
560
- typeof result.msg === 'string'
561
- ? result.msg
562
- : JSON.stringify(result.msg)
563
- )},`
564
- );
565
- }
566
- if (result.result !== undefined) {
567
- logLines.push(
568
- ` result: ${kleur.magenta(
569
- JSON.stringify(result.result)
570
- )}`
571
- );
572
- }
573
- }
574
- if (resp && (!result || !success || !result.result)) {
575
- logLines.push(`\n ${kleur.bold().cyan('Response Text')}`);
576
- logLines.push(
577
- ` ${
578
- typeof resp.data === 'string'
579
- ? resp.data
580
- : JSON.stringify(resp.data)
581
- }`
582
- );
583
- }
584
-
585
- if (atrributes.error) {
586
- logLines.push(`\n ${kleur.bold().red('Error')}`);
587
- logLines.push(` ${kleur.red(atrributes.error)}`);
588
- }
589
-
590
- if (others && others.length) {
591
- logLines.push(...others);
592
- }
593
-
594
- console.log(logLines.join('\n'));
595
- }
596
- }