@hangox/pm-cli 0.0.1 → 0.2.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.
- package/dist/index.js +428 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -334,17 +334,174 @@ function validateCredentials(creds, requiredFields = ["token", "host", "project"
|
|
|
334
334
|
|
|
335
335
|
// src/utils/output.ts
|
|
336
336
|
import { writeFileSync as writeFileSync2 } from "fs";
|
|
337
|
+
|
|
338
|
+
// src/utils/key-translator.ts
|
|
339
|
+
var KEY_MAP = {
|
|
340
|
+
// 问题 (Issue) 相关字段
|
|
341
|
+
id: "\u7F16\u53F7",
|
|
342
|
+
subject: "\u6807\u9898",
|
|
343
|
+
description: "\u63CF\u8FF0",
|
|
344
|
+
status: "\u72B6\u6001",
|
|
345
|
+
tracker: "\u8DDF\u8E2A\u5668",
|
|
346
|
+
priority: "\u4F18\u5148\u7EA7",
|
|
347
|
+
author: "\u521B\u5EFA\u8005",
|
|
348
|
+
assigned_to: "\u8D1F\u8D23\u4EBA",
|
|
349
|
+
project: "\u9879\u76EE",
|
|
350
|
+
parent: "\u7236\u5355",
|
|
351
|
+
parent_id: "\u7236\u5355\u7F16\u53F7",
|
|
352
|
+
parent_issue_id: "\u7236\u5355\u7F16\u53F7",
|
|
353
|
+
root_id: "\u6839\u5355\u7F16\u53F7",
|
|
354
|
+
children: "\u5B50\u5355\u5217\u8868",
|
|
355
|
+
level: "\u5C42\u7EA7",
|
|
356
|
+
fixed_version: "\u76EE\u6807\u7248\u672C",
|
|
357
|
+
version: "\u7248\u672C",
|
|
358
|
+
start_date: "\u5F00\u59CB\u65E5\u671F",
|
|
359
|
+
due_date: "\u622A\u6B62\u65E5\u671F",
|
|
360
|
+
estimated_hours: "\u9884\u4F30\u5DE5\u65F6",
|
|
361
|
+
spent_hours: "\u5DF2\u7528\u5DE5\u65F6",
|
|
362
|
+
done_ratio: "\u5B8C\u6210\u5EA6",
|
|
363
|
+
created_on: "\u521B\u5EFA\u65F6\u95F4",
|
|
364
|
+
updated_on: "\u66F4\u65B0\u65F6\u95F4",
|
|
365
|
+
closed_on: "\u5173\u95ED\u65F6\u95F4",
|
|
366
|
+
relations: "\u5173\u8054\u95EE\u9898",
|
|
367
|
+
custom_fields: "\u81EA\u5B9A\u4E49\u5B57\u6BB5",
|
|
368
|
+
attachments: "\u9644\u4EF6",
|
|
369
|
+
journals: "\u5386\u53F2\u8BB0\u5F55",
|
|
370
|
+
watchers: "\u8DDF\u8E2A\u8005",
|
|
371
|
+
watcher_user_ids: "\u8DDF\u8E2A\u8005\u7F16\u53F7",
|
|
372
|
+
// 通用对象字段
|
|
373
|
+
name: "\u540D\u79F0",
|
|
374
|
+
value: "\u503C",
|
|
375
|
+
mail: "\u90AE\u7BB1",
|
|
376
|
+
login: "\u767B\u5F55\u540D",
|
|
377
|
+
firstname: "\u540D",
|
|
378
|
+
lastname: "\u59D3",
|
|
379
|
+
identifier: "\u6807\u8BC6\u7B26",
|
|
380
|
+
is_public: "\u662F\u5426\u516C\u5F00",
|
|
381
|
+
// 工时 (TimeEntry) 相关字段
|
|
382
|
+
hours: "\u5DE5\u65F6",
|
|
383
|
+
spent_on: "\u5DE5\u65F6\u65E5\u671F",
|
|
384
|
+
activity: "\u6D3B\u52A8\u7C7B\u578B",
|
|
385
|
+
activity_id: "\u6D3B\u52A8\u7C7B\u578B\u7F16\u53F7",
|
|
386
|
+
comments: "\u5907\u6CE8",
|
|
387
|
+
time_entries: "\u5DE5\u65F6\u6761\u76EE",
|
|
388
|
+
time_entry: "\u5DE5\u65F6\u6761\u76EE",
|
|
389
|
+
total_count: "\u603B\u6570",
|
|
390
|
+
total_hours: "\u603B\u5DE5\u65F6",
|
|
391
|
+
// 用户 (User) 相关字段
|
|
392
|
+
user: "\u7528\u6237",
|
|
393
|
+
user_id: "\u7528\u6237\u7F16\u53F7",
|
|
394
|
+
author_mail: "\u4F5C\u8005\u90AE\u7BB1",
|
|
395
|
+
assigned_to_mail: "\u8D1F\u8D23\u4EBA\u90AE\u7BB1",
|
|
396
|
+
assigned_to_id: "\u8D1F\u8D23\u4EBA\u7F16\u53F7",
|
|
397
|
+
follows: "\u8DDF\u8FDBQA",
|
|
398
|
+
last_login_on: "\u6700\u540E\u767B\u5F55\u65F6\u95F4",
|
|
399
|
+
// API 响应字段
|
|
400
|
+
success: "\u6210\u529F",
|
|
401
|
+
data: "\u6570\u636E",
|
|
402
|
+
error: "\u9519\u8BEF",
|
|
403
|
+
message: "\u6D88\u606F",
|
|
404
|
+
msg: "\u6D88\u606F",
|
|
405
|
+
api_error_msg: "\u63A5\u53E3\u9519\u8BEF",
|
|
406
|
+
post_error_msg: "\u8BF7\u6C42\u9519\u8BEF",
|
|
407
|
+
output: "\u8F93\u51FA\u8DEF\u5F84",
|
|
408
|
+
issues: "\u95EE\u9898\u5217\u8868",
|
|
409
|
+
list: "\u5217\u8868",
|
|
410
|
+
total: "\u603B\u6570",
|
|
411
|
+
// 查询/统计相关字段
|
|
412
|
+
offset: "\u504F\u79FB\u91CF",
|
|
413
|
+
limit: "\u9650\u5236\u6570",
|
|
414
|
+
page: "\u9875\u7801",
|
|
415
|
+
per_page: "\u6BCF\u9875\u6570\u91CF",
|
|
416
|
+
period: "\u65F6\u95F4\u6BB5",
|
|
417
|
+
targetHours: "\u76EE\u6807\u5DE5\u65F6",
|
|
418
|
+
remainingHours: "\u5269\u4F59\u5DE5\u65F6",
|
|
419
|
+
isFilled: "\u662F\u5426\u586B\u6EE1",
|
|
420
|
+
projectBreakdown: "\u9879\u76EE\u5206\u5E03",
|
|
421
|
+
project_summary: "\u9879\u76EE\u6C47\u603B",
|
|
422
|
+
// 版本 (Version) 相关字段
|
|
423
|
+
effective_date: "\u6709\u6548\u65E5\u671F",
|
|
424
|
+
sharing: "\u5171\u4EAB\u8303\u56F4",
|
|
425
|
+
wiki_page_title: "Wiki\u9875\u9762",
|
|
426
|
+
// 自定义字段相关
|
|
427
|
+
custom_field: "\u81EA\u5B9A\u4E49\u5B57\u6BB5",
|
|
428
|
+
identify: "\u6807\u8BC6",
|
|
429
|
+
// 选项/配置相关
|
|
430
|
+
options: "\u9009\u9879",
|
|
431
|
+
activities: "\u6D3B\u52A8\u7C7B\u578B\u5217\u8868",
|
|
432
|
+
trackers: "\u8DDF\u8E2A\u5668\u5217\u8868",
|
|
433
|
+
statuses: "\u72B6\u6001\u5217\u8868",
|
|
434
|
+
priorities: "\u4F18\u5148\u7EA7\u5217\u8868",
|
|
435
|
+
versions: "\u7248\u672C\u5217\u8868",
|
|
436
|
+
users: "\u7528\u6237\u5217\u8868",
|
|
437
|
+
projects: "\u9879\u76EE\u5217\u8868",
|
|
438
|
+
// 其他字段
|
|
439
|
+
notes: "\u8BF4\u660E",
|
|
440
|
+
is_pending_version: "\u662F\u5426\u5F85\u5B9A\u7248\u672C",
|
|
441
|
+
count: "\u6570\u91CF",
|
|
442
|
+
category: "\u5206\u7C7B",
|
|
443
|
+
category_id: "\u5206\u7C7B\u7F16\u53F7"
|
|
444
|
+
};
|
|
445
|
+
var keyMappingEnabled = true;
|
|
446
|
+
function setKeyMappingEnabled(enabled) {
|
|
447
|
+
keyMappingEnabled = enabled;
|
|
448
|
+
}
|
|
449
|
+
function isKeyMappingEnabled() {
|
|
450
|
+
return keyMappingEnabled;
|
|
451
|
+
}
|
|
452
|
+
function collectKeysRecursive(obj, keys) {
|
|
453
|
+
if (obj === null || obj === void 0) return;
|
|
454
|
+
if (typeof obj !== "object") return;
|
|
455
|
+
if (Array.isArray(obj)) {
|
|
456
|
+
for (const item of obj) {
|
|
457
|
+
collectKeysRecursive(item, keys);
|
|
458
|
+
}
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
462
|
+
keys.add(key);
|
|
463
|
+
collectKeysRecursive(value, keys);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function collectUsedKeys(obj) {
|
|
467
|
+
const keys = /* @__PURE__ */ new Set();
|
|
468
|
+
collectKeysRecursive(obj, keys);
|
|
469
|
+
return Array.from(keys);
|
|
470
|
+
}
|
|
471
|
+
function generateKeyMapping(obj) {
|
|
472
|
+
const usedKeys = collectUsedKeys(obj);
|
|
473
|
+
const mapping = {};
|
|
474
|
+
for (const key of usedKeys) {
|
|
475
|
+
if (KEY_MAP[key]) {
|
|
476
|
+
mapping[key] = KEY_MAP[key];
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
return mapping;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/utils/output.ts
|
|
337
483
|
function generateOutputPath(command, identifier) {
|
|
338
484
|
const now = /* @__PURE__ */ new Date();
|
|
339
485
|
const timestamp = now.getFullYear().toString() + (now.getMonth() + 1).toString().padStart(2, "0") + now.getDate().toString().padStart(2, "0") + now.getHours().toString().padStart(2, "0") + now.getMinutes().toString().padStart(2, "0") + now.getSeconds().toString().padStart(2, "0");
|
|
340
486
|
return `/tmp/pm-cli_${command}_${identifier}_${timestamp}.json`;
|
|
341
487
|
}
|
|
488
|
+
function addKeyMappingToResult(result) {
|
|
489
|
+
if (!isKeyMappingEnabled()) {
|
|
490
|
+
return result;
|
|
491
|
+
}
|
|
492
|
+
const mapping = generateKeyMapping(result);
|
|
493
|
+
return {
|
|
494
|
+
...result,
|
|
495
|
+
_key_mapping: mapping
|
|
496
|
+
};
|
|
497
|
+
}
|
|
342
498
|
function outputToFile(data, filePath) {
|
|
343
499
|
const result = {
|
|
344
500
|
success: true,
|
|
345
501
|
data
|
|
346
502
|
};
|
|
347
|
-
|
|
503
|
+
const finalResult = addKeyMappingToResult(result);
|
|
504
|
+
writeFileSync2(filePath, JSON.stringify(finalResult, null, 2), "utf-8");
|
|
348
505
|
return filePath;
|
|
349
506
|
}
|
|
350
507
|
function smartOutput(data, options) {
|
|
@@ -362,14 +519,16 @@ function outputSuccess(data) {
|
|
|
362
519
|
success: true,
|
|
363
520
|
data
|
|
364
521
|
};
|
|
365
|
-
|
|
522
|
+
const finalResult = addKeyMappingToResult(result);
|
|
523
|
+
console.log(JSON.stringify(finalResult, null, 2));
|
|
366
524
|
}
|
|
367
525
|
function outputError(error) {
|
|
368
526
|
const result = {
|
|
369
527
|
success: false,
|
|
370
528
|
error
|
|
371
529
|
};
|
|
372
|
-
|
|
530
|
+
const finalResult = addKeyMappingToResult(result);
|
|
531
|
+
console.log(JSON.stringify(finalResult, null, 2));
|
|
373
532
|
}
|
|
374
533
|
|
|
375
534
|
// src/commands/test.ts
|
|
@@ -560,9 +719,15 @@ var IssueService = class {
|
|
|
560
719
|
return await apiClient.post("filter_query_v6", params);
|
|
561
720
|
}
|
|
562
721
|
/**
|
|
563
|
-
*
|
|
722
|
+
* 递归获取问题及其子单(包含完整详情)
|
|
564
723
|
* @param depth 递归深度,默认 10
|
|
565
724
|
* @param currentLevel 当前层级(内部使用)
|
|
725
|
+
*
|
|
726
|
+
* 实现逻辑:
|
|
727
|
+
* 1. 调用 getIssue(includeChildren=true) 获取当前问题详情
|
|
728
|
+
* 2. API 返回的 children 字段只包含简略信息(status 是字符串,缺少 assigned_to 等)
|
|
729
|
+
* 3. 从 children 中提取子单 ID,递归调用 getIssue 获取每个子单的完整详情
|
|
730
|
+
* 4. 用完整详情替换原始的简略 children
|
|
566
731
|
*/
|
|
567
732
|
async getIssueWithChildren(token, host, project, issueId, depth = 10, currentLevel2 = 0) {
|
|
568
733
|
logger_default.info("\u9012\u5F52\u83B7\u53D6\u95EE\u9898\u8BE6\u60C5", { host, project, issueId, depth, currentLevel: currentLevel2 });
|
|
@@ -575,15 +740,16 @@ var IssueService = class {
|
|
|
575
740
|
if (currentLevel2 >= depth) {
|
|
576
741
|
return { success: true, data: issue };
|
|
577
742
|
}
|
|
578
|
-
const
|
|
579
|
-
if (!
|
|
743
|
+
const rawChildren = issue.children;
|
|
744
|
+
if (!rawChildren || rawChildren.length === 0) {
|
|
580
745
|
return { success: true, data: issue };
|
|
581
746
|
}
|
|
582
|
-
const
|
|
583
|
-
if (
|
|
747
|
+
const directChildrenIds = rawChildren.filter((child) => child.id).map((child) => child.id);
|
|
748
|
+
if (directChildrenIds.length === 0) {
|
|
584
749
|
return { success: true, data: issue };
|
|
585
750
|
}
|
|
586
|
-
|
|
751
|
+
logger_default.info("\u83B7\u53D6\u5B50\u5355\u5B8C\u6574\u8BE6\u60C5", { parentId: issueId, childCount: directChildrenIds.length, level: currentLevel2 });
|
|
752
|
+
const childrenPromises = directChildrenIds.map(
|
|
587
753
|
(childId) => this.getIssueWithChildren(token, host, project, childId, depth, currentLevel2 + 1)
|
|
588
754
|
);
|
|
589
755
|
const childrenResults = await Promise.all(childrenPromises);
|
|
@@ -672,6 +838,50 @@ var IssueService = class {
|
|
|
672
838
|
}
|
|
673
839
|
return result;
|
|
674
840
|
}
|
|
841
|
+
/**
|
|
842
|
+
* 批量获取多个问题详情
|
|
843
|
+
* @param issueIds 问题 ID 数组
|
|
844
|
+
* @param options 可选参数
|
|
845
|
+
* @returns 返回所有问题的详情数组(包含成功和失败的结果)
|
|
846
|
+
*/
|
|
847
|
+
async getMultipleIssues(token, host, project, issueIds, options) {
|
|
848
|
+
const { includeChildren = false, includeRelations = false, depth = 0 } = options || {};
|
|
849
|
+
logger_default.info("\u6279\u91CF\u83B7\u53D6\u95EE\u9898\u8BE6\u60C5", { host, project, count: issueIds.length, issueIds });
|
|
850
|
+
const results = await Promise.all(
|
|
851
|
+
issueIds.map(async (issueId) => {
|
|
852
|
+
try {
|
|
853
|
+
let result;
|
|
854
|
+
if (includeChildren && depth > 0) {
|
|
855
|
+
result = await this.getIssueWithChildren(token, host, project, issueId, depth);
|
|
856
|
+
} else {
|
|
857
|
+
result = await this.getIssue(token, host, project, issueId, includeChildren, includeRelations);
|
|
858
|
+
}
|
|
859
|
+
if (result.success && result.data) {
|
|
860
|
+
return { id: issueId, success: true, data: result.data };
|
|
861
|
+
} else {
|
|
862
|
+
return {
|
|
863
|
+
id: issueId,
|
|
864
|
+
success: false,
|
|
865
|
+
error: result.message || result.msg || result.api_error_msg || "\u83B7\u53D6\u5931\u8D25"
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
} catch (error) {
|
|
869
|
+
return {
|
|
870
|
+
id: issueId,
|
|
871
|
+
success: false,
|
|
872
|
+
error: error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
})
|
|
876
|
+
);
|
|
877
|
+
const successCount = results.filter((r) => r.success).length;
|
|
878
|
+
const failCount = results.filter((r) => !r.success).length;
|
|
879
|
+
logger_default.info("\u6279\u91CF\u83B7\u53D6\u5B8C\u6210", { total: issueIds.length, success: successCount, fail: failCount });
|
|
880
|
+
return {
|
|
881
|
+
success: true,
|
|
882
|
+
data: results
|
|
883
|
+
};
|
|
884
|
+
}
|
|
675
885
|
};
|
|
676
886
|
var issueService = new IssueService();
|
|
677
887
|
|
|
@@ -776,7 +986,7 @@ function createIssueCommand() {
|
|
|
776
986
|
}
|
|
777
987
|
}
|
|
778
988
|
});
|
|
779
|
-
issueCmd.command("create").description("\u521B\u5EFA\u95EE\u9898").requiredOption("--subject <subject>", "\u95EE\u9898\u6807\u9898").option("--description <description>", "\u95EE\u9898\u63CF\u8FF0").option("--tracker-id <id>", "\u8DDF\u8E2A\u5668 ID").option("--priority-id <id>", "\u4F18\u5148\u7EA7 ID").option("--assigned-to-id <id>", "\u6307\u6D3E\u4EBA ID").option("--parent-id <id>", "\u7236\u95EE\u9898 ID").option("--start-date <date>", "\u5F00\u59CB\u65E5\u671F").option("--due-date <date>", "\u622A\u6B62\u65E5\u671F").option("--estimated-hours <hours>", "\u9884\u4F30\u5DE5\u65F6").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
989
|
+
issueCmd.command("create").description("\u521B\u5EFA\u95EE\u9898").requiredOption("--subject <subject>", "\u95EE\u9898\u6807\u9898").option("--description <description>", "\u95EE\u9898\u63CF\u8FF0").option("--tracker <tracker>", "\u8DDF\u8E2A\u5668\u540D\u79F0").option("--tracker-id <id>", "\u8DDF\u8E2A\u5668 ID").option("--priority-id <id>", "\u4F18\u5148\u7EA7 ID").option("--assigned-to-mail <email>", "\u6307\u6D3E\u4EBA\u90AE\u7BB1").option("--assigned-to-id <id>", "\u6307\u6D3E\u4EBA ID").option("--parent-id <id>", "\u7236\u95EE\u9898 ID\uFF08\u5B50\u5355\u4F1A\u7EE7\u627F\u7236\u5355\u7684 tracker\u3001version\u3001assigned_to \u7B49\u4FE1\u606F\uFF09").option("--version <version>", "\u76EE\u6807\u7248\u672C\u540D\u79F0").option("--start-date <date>", "\u5F00\u59CB\u65E5\u671F").option("--due-date <date>", "\u622A\u6B62\u65E5\u671F").option("--estimated-hours <hours>", "\u9884\u4F30\u5DE5\u65F6").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
780
990
|
const creds = resolveCredentials(options);
|
|
781
991
|
const validation = validateCredentials(creds);
|
|
782
992
|
if (!validation.valid) {
|
|
@@ -789,11 +999,71 @@ function createIssueCommand() {
|
|
|
789
999
|
project: creds.project,
|
|
790
1000
|
subject: options.subject
|
|
791
1001
|
};
|
|
1002
|
+
if (options.parentId) {
|
|
1003
|
+
const parentId = parseInt(options.parentId, 10);
|
|
1004
|
+
const parentResult = await issueService.getIssue(
|
|
1005
|
+
creds.token,
|
|
1006
|
+
creds.host,
|
|
1007
|
+
creds.project || "",
|
|
1008
|
+
parentId,
|
|
1009
|
+
false,
|
|
1010
|
+
false
|
|
1011
|
+
);
|
|
1012
|
+
if (parentResult.success && parentResult.data) {
|
|
1013
|
+
const parent = parentResult.data;
|
|
1014
|
+
if (!options.tracker && !options.trackerId && parent.tracker?.name) {
|
|
1015
|
+
params.tracker = parent.tracker.name;
|
|
1016
|
+
}
|
|
1017
|
+
if (!options.assignedToMail && !options.assignedToId && parent.assigned_to?.mail) {
|
|
1018
|
+
params.assigned_to_mail = parent.assigned_to.mail;
|
|
1019
|
+
}
|
|
1020
|
+
if (!options.version && parent.fixed_version?.name) {
|
|
1021
|
+
params.version = parent.fixed_version.name;
|
|
1022
|
+
}
|
|
1023
|
+
if (!options.priorityId && parent.priority?.id) {
|
|
1024
|
+
params.priority_id = parent.priority.id;
|
|
1025
|
+
}
|
|
1026
|
+
params.status = "\u65B0\u5EFA";
|
|
1027
|
+
if (parent.custom_fields && parent.custom_fields.length > 0) {
|
|
1028
|
+
const customFieldMap = {};
|
|
1029
|
+
const followsMails = [];
|
|
1030
|
+
for (const field of parent.custom_fields) {
|
|
1031
|
+
if (field.value !== null && field.value !== void 0 && field.value !== "") {
|
|
1032
|
+
const fieldWithIdentify = field;
|
|
1033
|
+
if (fieldWithIdentify.identify === "IssuesQCFollow") {
|
|
1034
|
+
const followsValue = field.value;
|
|
1035
|
+
if (Array.isArray(followsValue)) {
|
|
1036
|
+
for (const item of followsValue) {
|
|
1037
|
+
if (item.user?.mail) {
|
|
1038
|
+
followsMails.push(item.user.mail);
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
} else {
|
|
1043
|
+
customFieldMap[field.id] = field.value;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
if (Object.keys(customFieldMap).length > 0) {
|
|
1048
|
+
params.custom_field = JSON.stringify(customFieldMap);
|
|
1049
|
+
}
|
|
1050
|
+
if (followsMails.length > 0) {
|
|
1051
|
+
params.follows = followsMails;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
} else {
|
|
1055
|
+
outputError(`\u65E0\u6CD5\u83B7\u53D6\u7236\u5355 #${parentId} \u7684\u4FE1\u606F: ${parentResult.message || "\u672A\u77E5\u9519\u8BEF"}`);
|
|
1056
|
+
process.exit(1);
|
|
1057
|
+
}
|
|
1058
|
+
params.parent_issue_id = parentId;
|
|
1059
|
+
}
|
|
792
1060
|
if (options.description) params.description = options.description;
|
|
1061
|
+
if (options.tracker) params.tracker = options.tracker;
|
|
793
1062
|
if (options.trackerId) params.tracker_id = parseInt(options.trackerId, 10);
|
|
794
1063
|
if (options.priorityId) params.priority_id = parseInt(options.priorityId, 10);
|
|
1064
|
+
if (options.assignedToMail) params.assigned_to_mail = options.assignedToMail;
|
|
795
1065
|
if (options.assignedToId) params.assigned_to_id = parseInt(options.assignedToId, 10);
|
|
796
|
-
if (options.
|
|
1066
|
+
if (options.version) params.version = options.version;
|
|
797
1067
|
if (options.startDate) params.start_date = options.startDate;
|
|
798
1068
|
if (options.dueDate) params.due_date = options.dueDate;
|
|
799
1069
|
if (options.estimatedHours) params.estimated_hours = parseFloat(options.estimatedHours);
|
|
@@ -1006,6 +1276,61 @@ function createIssueCommand() {
|
|
|
1006
1276
|
process.exit(1);
|
|
1007
1277
|
}
|
|
1008
1278
|
});
|
|
1279
|
+
issueCmd.command("mget [ids...]").description("\u6279\u91CF\u83B7\u53D6\u591A\u4E2A\u95EE\u9898\u8BE6\u60C5").option("--ids <ids>", "\u9017\u53F7\u5206\u9694\u7684\u95EE\u9898 ID \u5217\u8868").option("--depth <depth>", "\u9012\u5F52\u83B7\u53D6\u5B50\u5355\u7684\u6DF1\u5EA6\uFF080 \u8868\u793A\u4E0D\u83B7\u53D6\u5B50\u5355\uFF09", "0").option("--include-relations", "\u5305\u542B\u5173\u8054\u95EE\u9898").option("-o, --output <path>", "\u8F93\u51FA JSON \u5230\u6307\u5B9A\u6587\u4EF6\uFF08\u9ED8\u8BA4\u8F93\u51FA\u5230 /tmp\uFF09").option("--stdout", "\u5F3A\u5236\u8F93\u51FA\u5230\u63A7\u5236\u53F0\u800C\u975E\u6587\u4EF6").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (ids, options) => {
|
|
1280
|
+
let issueIds = [];
|
|
1281
|
+
if (ids && ids.length > 0) {
|
|
1282
|
+
const parsedIds = ids.map((id) => parseInt(id.replace(/^#/, ""), 10)).filter((id) => !isNaN(id));
|
|
1283
|
+
issueIds.push(...parsedIds);
|
|
1284
|
+
}
|
|
1285
|
+
if (options.ids) {
|
|
1286
|
+
const idsFromOption = options.ids.split(",").map((id) => parseInt(id.trim().replace(/^#/, ""), 10)).filter((id) => !isNaN(id));
|
|
1287
|
+
issueIds.push(...idsFromOption);
|
|
1288
|
+
}
|
|
1289
|
+
issueIds = [...new Set(issueIds)];
|
|
1290
|
+
if (issueIds.length === 0) {
|
|
1291
|
+
outputError("\u8BF7\u63D0\u4F9B\u81F3\u5C11\u4E00\u4E2A\u95EE\u9898 ID");
|
|
1292
|
+
process.exit(1);
|
|
1293
|
+
}
|
|
1294
|
+
const creds = resolveCredentials(options);
|
|
1295
|
+
const validation = validateCredentials(creds, ["token", "host"]);
|
|
1296
|
+
if (!validation.valid) {
|
|
1297
|
+
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
1298
|
+
process.exit(1);
|
|
1299
|
+
}
|
|
1300
|
+
const depth = parseInt(options.depth, 10);
|
|
1301
|
+
const includeChildren = depth > 0;
|
|
1302
|
+
const includeRelations = options.includeRelations;
|
|
1303
|
+
const result = await issueService.getMultipleIssues(
|
|
1304
|
+
creds.token,
|
|
1305
|
+
creds.host,
|
|
1306
|
+
creds.project || "",
|
|
1307
|
+
issueIds,
|
|
1308
|
+
{ includeChildren, includeRelations, depth }
|
|
1309
|
+
);
|
|
1310
|
+
if (result.success && result.data) {
|
|
1311
|
+
const successItems = result.data.filter((item) => item.success);
|
|
1312
|
+
const failedItems = result.data.filter((item) => !item.success);
|
|
1313
|
+
const output = {
|
|
1314
|
+
success: true,
|
|
1315
|
+
summary: {
|
|
1316
|
+
total: issueIds.length,
|
|
1317
|
+
success: successItems.length,
|
|
1318
|
+
failed: failedItems.length
|
|
1319
|
+
},
|
|
1320
|
+
issues: successItems.map((item) => item.data),
|
|
1321
|
+
errors: failedItems.length > 0 ? failedItems.map((item) => ({ id: item.id, error: item.error })) : void 0
|
|
1322
|
+
};
|
|
1323
|
+
smartOutput(output, {
|
|
1324
|
+
stdout: options.stdout,
|
|
1325
|
+
output: options.output,
|
|
1326
|
+
command: "issue-mget",
|
|
1327
|
+
identifier: issueIds.join("_")
|
|
1328
|
+
});
|
|
1329
|
+
} else {
|
|
1330
|
+
outputError(result.message || result.msg || result.api_error_msg || "\u6279\u91CF\u83B7\u53D6\u95EE\u9898\u5931\u8D25");
|
|
1331
|
+
process.exit(1);
|
|
1332
|
+
}
|
|
1333
|
+
});
|
|
1009
1334
|
return issueCmd;
|
|
1010
1335
|
}
|
|
1011
1336
|
|
|
@@ -1036,18 +1361,22 @@ var TimeEntryService = class {
|
|
|
1036
1361
|
return await apiClient.get("query_time_entries", requestParams);
|
|
1037
1362
|
}
|
|
1038
1363
|
/**
|
|
1039
|
-
*
|
|
1364
|
+
* 获取工时条目选项(活动类型列表等)
|
|
1365
|
+
* 使用 time_entry GET API
|
|
1040
1366
|
*/
|
|
1041
|
-
async getTimeEntryOptions(token, host, project) {
|
|
1042
|
-
logger_default.info("\u83B7\u53D6\u5DE5\u65F6\u6761\u76EE\u9009\u9879", { host, project });
|
|
1043
|
-
|
|
1367
|
+
async getTimeEntryOptions(token, host, project, issueId) {
|
|
1368
|
+
logger_default.info("\u83B7\u53D6\u5DE5\u65F6\u6761\u76EE\u9009\u9879", { host, project, issueId });
|
|
1369
|
+
const params = { token, host, project };
|
|
1370
|
+
if (issueId) params.issue_id = issueId;
|
|
1371
|
+
return await apiClient.get("time_entry", params);
|
|
1044
1372
|
}
|
|
1045
1373
|
/**
|
|
1046
1374
|
* 创建工时条目
|
|
1375
|
+
* 使用 time_entry API(非 save_time_entry)
|
|
1047
1376
|
*/
|
|
1048
1377
|
async createTimeEntry(params) {
|
|
1049
1378
|
logger_default.info("\u521B\u5EFA\u5DE5\u65F6\u6761\u76EE", { params });
|
|
1050
|
-
return await apiClient.post("
|
|
1379
|
+
return await apiClient.post("time_entry", params);
|
|
1051
1380
|
}
|
|
1052
1381
|
/**
|
|
1053
1382
|
* 更新工时条目
|
|
@@ -1059,13 +1388,13 @@ var TimeEntryService = class {
|
|
|
1059
1388
|
/**
|
|
1060
1389
|
* 删除工时条目
|
|
1061
1390
|
*/
|
|
1062
|
-
async deleteTimeEntry(token, host,
|
|
1063
|
-
logger_default.info("\u5220\u9664\u5DE5\u65F6\u6761\u76EE", { host,
|
|
1391
|
+
async deleteTimeEntry(token, host, timeEntryId) {
|
|
1392
|
+
logger_default.info("\u5220\u9664\u5DE5\u65F6\u6761\u76EE", { host, timeEntryId });
|
|
1064
1393
|
return await apiClient.get("delete_time_entry", {
|
|
1065
1394
|
token,
|
|
1066
1395
|
host,
|
|
1067
|
-
|
|
1068
|
-
time_entry_id
|
|
1396
|
+
id: timeEntryId
|
|
1397
|
+
// 使用 id 参数,非 time_entry_id
|
|
1069
1398
|
});
|
|
1070
1399
|
}
|
|
1071
1400
|
};
|
|
@@ -1179,13 +1508,22 @@ function createTimeCommand() {
|
|
|
1179
1508
|
}
|
|
1180
1509
|
}
|
|
1181
1510
|
});
|
|
1182
|
-
timeCmd.command("create").description("\u521B\u5EFA\u5DE5\u65F6\u6761\u76EE").requiredOption("--issue <id>", "\u95EE\u9898 ID").requiredOption("--days <days>", "\u5DE5\u65F6\uFF08\u5929\uFF09").
|
|
1511
|
+
timeCmd.command("create").description("\u521B\u5EFA\u5DE5\u65F6\u6761\u76EE").requiredOption("--issue <id>", "\u95EE\u9898 ID").requiredOption("--days <days>", "\u5DE5\u65F6\uFF08\u5929\uFF09").requiredOption("--activity <id>", "\u6D3B\u52A8\u7C7B\u578B ID\uFF08\u4F7F\u7528 pm-cli time options \u83B7\u53D6\u53EF\u7528\u503C\uFF09").option("--date <date>", "\u65E5\u671F (YYYY-MM-DD)\uFF0C\u9ED8\u8BA4\u4ECA\u5929").option("--comments <comments>", "\u5907\u6CE8").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
1183
1512
|
const creds = resolveCredentials(options);
|
|
1184
1513
|
const validation = validateCredentials(creds);
|
|
1185
1514
|
if (!validation.valid) {
|
|
1186
1515
|
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
1187
1516
|
process.exit(1);
|
|
1188
1517
|
}
|
|
1518
|
+
let userId;
|
|
1519
|
+
const configUserId = getConfigValue("userId", options.config);
|
|
1520
|
+
if (configUserId) {
|
|
1521
|
+
userId = parseInt(configUserId, 10);
|
|
1522
|
+
}
|
|
1523
|
+
if (!userId) {
|
|
1524
|
+
outputError("\u7F3A\u5C11\u7528\u6237 ID\uFF0C\u8BF7\u5148\u8BBE\u7F6E: pm-cli config set user-id <\u60A8\u7684\u7528\u6237ID>");
|
|
1525
|
+
process.exit(1);
|
|
1526
|
+
}
|
|
1189
1527
|
const issueId = parseInt(options.issue.replace(/^#/, ""), 10);
|
|
1190
1528
|
if (isNaN(issueId)) {
|
|
1191
1529
|
outputError("\u65E0\u6548\u7684\u95EE\u9898 ID");
|
|
@@ -1196,6 +1534,11 @@ function createTimeCommand() {
|
|
|
1196
1534
|
outputError("\u65E0\u6548\u7684\u5DE5\u65F6\u6570");
|
|
1197
1535
|
process.exit(1);
|
|
1198
1536
|
}
|
|
1537
|
+
const activityId = parseInt(options.activity, 10);
|
|
1538
|
+
if (isNaN(activityId)) {
|
|
1539
|
+
outputError("\u65E0\u6548\u7684\u6D3B\u52A8\u7C7B\u578B ID\uFF0C\u4F7F\u7528 pm-cli time options \u83B7\u53D6\u53EF\u7528\u503C");
|
|
1540
|
+
process.exit(1);
|
|
1541
|
+
}
|
|
1199
1542
|
const params = {
|
|
1200
1543
|
token: creds.token,
|
|
1201
1544
|
host: creds.host,
|
|
@@ -1203,9 +1546,12 @@ function createTimeCommand() {
|
|
|
1203
1546
|
issue_id: issueId,
|
|
1204
1547
|
hours: days,
|
|
1205
1548
|
// API 参数名为 hours,但单位是天
|
|
1206
|
-
spent_on: options.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
|
|
1549
|
+
spent_on: options.date || (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
1550
|
+
user_id: userId,
|
|
1551
|
+
// 用户 ID(必填)
|
|
1552
|
+
activity_id: activityId
|
|
1553
|
+
// 活动类型 ID(必填)
|
|
1207
1554
|
};
|
|
1208
|
-
if (options.activity) params.activity_id = parseInt(options.activity, 10);
|
|
1209
1555
|
if (options.comments) params.comments = options.comments;
|
|
1210
1556
|
const result = await timeEntryService.createTimeEntry(params);
|
|
1211
1557
|
if (result.success && result.data) {
|
|
@@ -1215,27 +1561,51 @@ function createTimeCommand() {
|
|
|
1215
1561
|
process.exit(1);
|
|
1216
1562
|
}
|
|
1217
1563
|
});
|
|
1218
|
-
timeCmd.command("update <id>").description("\u66F4\u65B0\u5DE5\u65F6\u6761\u76EE").
|
|
1564
|
+
timeCmd.command("update <id>").description("\u66F4\u65B0\u5DE5\u65F6\u6761\u76EE").requiredOption("--issue <id>", "\u95EE\u9898 ID\uFF08\u5FC5\u586B\uFF09").requiredOption("--days <days>", "\u5DE5\u65F6\uFF08\u5929\uFF09\uFF08\u5FC5\u586B\uFF09").requiredOption("--activity <id>", "\u6D3B\u52A8\u7C7B\u578B ID\uFF08\u5FC5\u586B\uFF09").requiredOption("--date <date>", "\u65E5\u671F (YYYY-MM-DD)\uFF08\u5FC5\u586B\uFF09").option("--comments <comments>", "\u5907\u6CE8").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (id, options) => {
|
|
1219
1565
|
const creds = resolveCredentials(options);
|
|
1220
1566
|
const validation = validateCredentials(creds);
|
|
1221
1567
|
if (!validation.valid) {
|
|
1222
1568
|
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
1223
1569
|
process.exit(1);
|
|
1224
1570
|
}
|
|
1571
|
+
let userId;
|
|
1572
|
+
const configUserId = getConfigValue("userId", options.config);
|
|
1573
|
+
if (configUserId) {
|
|
1574
|
+
userId = parseInt(configUserId, 10);
|
|
1575
|
+
}
|
|
1576
|
+
if (!userId) {
|
|
1577
|
+
outputError("\u7F3A\u5C11\u7528\u6237 ID\uFF0C\u8BF7\u5148\u8BBE\u7F6E: pm-cli config set user-id <\u60A8\u7684\u7528\u6237ID>");
|
|
1578
|
+
process.exit(1);
|
|
1579
|
+
}
|
|
1225
1580
|
const timeEntryId = parseInt(id, 10);
|
|
1226
1581
|
if (isNaN(timeEntryId)) {
|
|
1227
1582
|
outputError("\u65E0\u6548\u7684\u5DE5\u65F6\u6761\u76EE ID");
|
|
1228
1583
|
process.exit(1);
|
|
1229
1584
|
}
|
|
1585
|
+
const issueId = parseInt(options.issue.replace(/^#/, ""), 10);
|
|
1586
|
+
if (isNaN(issueId)) {
|
|
1587
|
+
outputError("\u65E0\u6548\u7684\u95EE\u9898 ID");
|
|
1588
|
+
process.exit(1);
|
|
1589
|
+
}
|
|
1230
1590
|
const params = {
|
|
1231
1591
|
token: creds.token,
|
|
1232
1592
|
host: creds.host,
|
|
1233
1593
|
project: creds.project,
|
|
1234
|
-
|
|
1594
|
+
id: timeEntryId,
|
|
1595
|
+
// 工时 ID
|
|
1596
|
+
issue_id: issueId,
|
|
1597
|
+
// 问题 ID(必填)
|
|
1598
|
+
hours: parseFloat(options.days),
|
|
1599
|
+
// 工时(必填)
|
|
1600
|
+
activity_id: parseInt(options.activity, 10),
|
|
1601
|
+
// 活动类型(必填)
|
|
1602
|
+
spent_on: options.date,
|
|
1603
|
+
// 日期(必填)
|
|
1604
|
+
user_id: userId,
|
|
1605
|
+
// 用户 ID(必填)
|
|
1606
|
+
updated_by: userId
|
|
1607
|
+
// 操作用户 ID(必填)
|
|
1235
1608
|
};
|
|
1236
|
-
if (options.days) params.hours = parseFloat(options.days);
|
|
1237
|
-
if (options.activity) params.activity_id = parseInt(options.activity, 10);
|
|
1238
|
-
if (options.date) params.spent_on = options.date;
|
|
1239
1609
|
if (options.comments) params.comments = options.comments;
|
|
1240
1610
|
const result = await timeEntryService.updateTimeEntry(params);
|
|
1241
1611
|
if (result.success && result.data) {
|
|
@@ -1247,7 +1617,7 @@ function createTimeCommand() {
|
|
|
1247
1617
|
});
|
|
1248
1618
|
timeCmd.command("delete <id>").description("\u5220\u9664\u5DE5\u65F6\u6761\u76EE").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (id, options) => {
|
|
1249
1619
|
const creds = resolveCredentials(options);
|
|
1250
|
-
const validation = validateCredentials(creds);
|
|
1620
|
+
const validation = validateCredentials(creds, ["token", "host"]);
|
|
1251
1621
|
if (!validation.valid) {
|
|
1252
1622
|
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
1253
1623
|
process.exit(1);
|
|
@@ -1260,7 +1630,6 @@ function createTimeCommand() {
|
|
|
1260
1630
|
const result = await timeEntryService.deleteTimeEntry(
|
|
1261
1631
|
creds.token,
|
|
1262
1632
|
creds.host,
|
|
1263
|
-
creds.project,
|
|
1264
1633
|
timeEntryId
|
|
1265
1634
|
);
|
|
1266
1635
|
if (result.success) {
|
|
@@ -1270,26 +1639,34 @@ function createTimeCommand() {
|
|
|
1270
1639
|
process.exit(1);
|
|
1271
1640
|
}
|
|
1272
1641
|
});
|
|
1273
|
-
timeCmd.command("options").description("\u83B7\u53D6\u5DE5\u65F6\u6761\u76EE\u9009\u9879").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
1642
|
+
timeCmd.command("options").description("\u83B7\u53D6\u5DE5\u65F6\u6761\u76EE\u9009\u9879\uFF08\u6D3B\u52A8\u7C7B\u578B\u5217\u8868\u7B49\uFF09").option("--issue <id>", "\u95EE\u9898 ID\uFF08\u53EF\u9009\uFF0C\u7528\u4E8E\u83B7\u53D6\u7279\u5B9A\u95EE\u9898\u7684\u5DE5\u65F6\u9009\u9879\uFF09").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
1274
1643
|
const creds = resolveCredentials(options);
|
|
1275
1644
|
const validation = validateCredentials(creds);
|
|
1276
1645
|
if (!validation.valid) {
|
|
1277
1646
|
outputError(`\u7F3A\u5C11\u5FC5\u8981\u53C2\u6570: ${validation.missing.join(", ")}`);
|
|
1278
1647
|
process.exit(1);
|
|
1279
1648
|
}
|
|
1649
|
+
const issueId = options.issue ? parseInt(options.issue.replace(/^#/, ""), 10) : void 0;
|
|
1280
1650
|
const result = await timeEntryService.getTimeEntryOptions(
|
|
1281
1651
|
creds.token,
|
|
1282
1652
|
creds.host,
|
|
1283
|
-
creds.project
|
|
1653
|
+
creds.project,
|
|
1654
|
+
issueId
|
|
1284
1655
|
);
|
|
1285
|
-
if (result.success) {
|
|
1286
|
-
|
|
1656
|
+
if (result.success && result.data) {
|
|
1657
|
+
const data = result.data;
|
|
1658
|
+
if (data.options?.activities) {
|
|
1659
|
+
const activities = data.options.activities.map(([name, id]) => ({ id, name }));
|
|
1660
|
+
outputSuccess({ activities });
|
|
1661
|
+
} else {
|
|
1662
|
+
outputSuccess(result.data);
|
|
1663
|
+
}
|
|
1287
1664
|
} else {
|
|
1288
1665
|
outputError(result.message || result.msg || result.api_error_msg || "\u83B7\u53D6\u9009\u9879\u5931\u8D25");
|
|
1289
1666
|
process.exit(1);
|
|
1290
1667
|
}
|
|
1291
1668
|
});
|
|
1292
|
-
timeCmd.command("summary").description("\u5DE5\u65F6\u7EDF\u8BA1\u6C47\u603B\uFF08\u67E5\u8BE2\u6307\u5B9A\u65F6\u95F4\u6BB5\u7684\u5DE5\u65F6\u5E76\u8BA1\u7B97\u7F3A\u53E3\uFF09").option("--week <week>", "\u5468\u9009\u62E9: current (\u672C\u5468), last (\u4E0A\u5468), \u6216\u6570\u5B57\u504F\u79FB", "current").option("--from <date>", "\u5F00\u59CB\u65E5\u671F (YYYY-MM-DD)\uFF0C\u4E0E --week \u4E92\u65A5").option("--to <date>", "\u7ED3\u675F\u65E5\u671F (YYYY-MM-DD)\uFF0C\u4E0E --week \u4E92\u65A5").option("--target <days>", "\u76EE\u6807\u5DE5\u65F6\uFF08\u5929\uFF09\uFF0C\u9ED8\u8BA4 5", "5").option("--user-id <id>", "\u7528\u6237 ID\uFF08\u9ED8\u8BA4\u4F7F\u7528\u914D\u7F6E\u4E2D\u7684 user-id\uFF09").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
1669
|
+
timeCmd.command("summary").description("\u5DE5\u65F6\u7EDF\u8BA1\u6C47\u603B\uFF08\u67E5\u8BE2\u6307\u5B9A\u65F6\u95F4\u6BB5\u7684\u5DE5\u65F6\u5E76\u8BA1\u7B97\u7F3A\u53E3\uFF09").option("--week <week>", "\u5468\u9009\u62E9: current (\u672C\u5468), last (\u4E0A\u5468), \u6216\u6570\u5B57\u504F\u79FB", "current").option("--from <date>", "\u5F00\u59CB\u65E5\u671F (YYYY-MM-DD)\uFF0C\u4E0E --week \u4E92\u65A5").option("--to <date>", "\u7ED3\u675F\u65E5\u671F (YYYY-MM-DD)\uFF0C\u4E0E --week \u4E92\u65A5").option("--target <days>", "\u76EE\u6807\u5DE5\u65F6\uFF08\u5929\uFF09\uFF0C\u9ED8\u8BA4 5", "5").option("--detail", "\u663E\u793A\u5B8C\u6574\u5DE5\u65F6\u6761\u76EE\u8BE6\u60C5").option("--user-id <id>", "\u7528\u6237 ID\uFF08\u9ED8\u8BA4\u4F7F\u7528\u914D\u7F6E\u4E2D\u7684 user-id\uFF09").option("--token <token>", "API Token").option("--host <host>", "PM \u4E3B\u673A\u5730\u5740").option("--project <project>", "\u9879\u76EE\u540D\u79F0").option("--profile <name>", "\u4F7F\u7528\u914D\u7F6E profile").option("--config <path>", "\u81EA\u5B9A\u4E49\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").action(async (options) => {
|
|
1293
1670
|
const creds = resolveCredentials(options);
|
|
1294
1671
|
const validation = validateCredentials(creds, ["token", "host"]);
|
|
1295
1672
|
if (!validation.valid) {
|
|
@@ -1331,6 +1708,7 @@ function createTimeCommand() {
|
|
|
1331
1708
|
const projectNames = Object.values(projects).map((p) => p.name);
|
|
1332
1709
|
logger_default.info(`\u67E5\u8BE2 ${projectNames.length} \u4E2A\u9879\u76EE...`);
|
|
1333
1710
|
const projectSummary = [];
|
|
1711
|
+
const allTimeEntries = [];
|
|
1334
1712
|
let totalHours = 0;
|
|
1335
1713
|
for (const projectName of projectNames) {
|
|
1336
1714
|
const result = await timeEntryService.queryTimeEntries({
|
|
@@ -1353,13 +1731,14 @@ function createTimeCommand() {
|
|
|
1353
1731
|
});
|
|
1354
1732
|
totalHours += projectHours;
|
|
1355
1733
|
}
|
|
1734
|
+
allTimeEntries.push(...data.time_entries);
|
|
1356
1735
|
}
|
|
1357
1736
|
}
|
|
1358
1737
|
}
|
|
1359
1738
|
const targetHours = parseFloat(options.target) || 5;
|
|
1360
1739
|
const remainingHours = Math.max(0, targetHours - totalHours);
|
|
1361
1740
|
totalHours = Math.round(totalHours * 100) / 100;
|
|
1362
|
-
|
|
1741
|
+
const output = {
|
|
1363
1742
|
period: periodLabel,
|
|
1364
1743
|
userId: userId || "\u672A\u6307\u5B9A",
|
|
1365
1744
|
totalHours,
|
|
@@ -1367,7 +1746,15 @@ function createTimeCommand() {
|
|
|
1367
1746
|
remainingHours: Math.round(remainingHours * 100) / 100,
|
|
1368
1747
|
isFilled: remainingHours === 0,
|
|
1369
1748
|
projectBreakdown: projectSummary.sort((a, b) => b.hours - a.hours)
|
|
1370
|
-
}
|
|
1749
|
+
};
|
|
1750
|
+
if (options.detail) {
|
|
1751
|
+
output.timeEntries = allTimeEntries.sort((a, b) => {
|
|
1752
|
+
const dateA = a.spent_on || "";
|
|
1753
|
+
const dateB = b.spent_on || "";
|
|
1754
|
+
return dateA.localeCompare(dateB);
|
|
1755
|
+
});
|
|
1756
|
+
}
|
|
1757
|
+
outputSuccess(output);
|
|
1371
1758
|
});
|
|
1372
1759
|
return timeCmd;
|
|
1373
1760
|
}
|
|
@@ -1430,7 +1817,7 @@ function createProjectCommand() {
|
|
|
1430
1817
|
|
|
1431
1818
|
// src/index.ts
|
|
1432
1819
|
var program = new Command6();
|
|
1433
|
-
program.name("pm-cli").description("\u7F51\u6613\u6613\u534F\u4F5C (PM NetEase) \u547D\u4EE4\u884C\u5DE5\u5177").version("0.1.0").option("--verbose", "\u8BE6\u7EC6\u8F93\u51FA").option("--debug", "\u8C03\u8BD5\u6A21\u5F0F").hook("preAction", (thisCommand) => {
|
|
1820
|
+
program.name("pm-cli").description("\u7F51\u6613\u6613\u534F\u4F5C (PM NetEase) \u547D\u4EE4\u884C\u5DE5\u5177").version("0.1.0").option("--verbose", "\u8BE6\u7EC6\u8F93\u51FA").option("--debug", "\u8C03\u8BD5\u6A21\u5F0F").option("--no-mapping", "\u4E0D\u8F93\u51FA key \u6620\u5C04\u8868\uFF08\u9ED8\u8BA4\u8F93\u51FA\uFF09").hook("preAction", (thisCommand) => {
|
|
1434
1821
|
const opts = thisCommand.opts();
|
|
1435
1822
|
if (opts.debug) {
|
|
1436
1823
|
logger_default.setLevel("debug");
|
|
@@ -1439,6 +1826,9 @@ program.name("pm-cli").description("\u7F51\u6613\u6613\u534F\u4F5C (PM NetEase)
|
|
|
1439
1826
|
} else {
|
|
1440
1827
|
logger_default.setLevel("silent");
|
|
1441
1828
|
}
|
|
1829
|
+
if (opts.mapping === false) {
|
|
1830
|
+
setKeyMappingEnabled(false);
|
|
1831
|
+
}
|
|
1442
1832
|
});
|
|
1443
1833
|
program.addCommand(createTestCommand());
|
|
1444
1834
|
program.addCommand(createConfigCommand());
|