@hangox/pm-cli 1.1.6 → 1.2.0

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/api.js ADDED
@@ -0,0 +1,1218 @@
1
+ // src/utils/logger.ts
2
+ var currentLevel = "silent";
3
+ var levels = {
4
+ debug: 0,
5
+ info: 1,
6
+ warn: 2,
7
+ error: 3,
8
+ silent: 4
9
+ };
10
+ function shouldLog(level) {
11
+ return levels[level] >= levels[currentLevel];
12
+ }
13
+ function formatMessage(level, message, data) {
14
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
15
+ const dataStr = data ? ` ${JSON.stringify(data)}` : "";
16
+ return `[${timestamp}] [${level.toUpperCase()}] ${message}${dataStr}`;
17
+ }
18
+ var logger = {
19
+ setLevel(level) {
20
+ currentLevel = level;
21
+ },
22
+ getLevel() {
23
+ return currentLevel;
24
+ },
25
+ debug(message, data) {
26
+ if (shouldLog("debug")) {
27
+ console.error(formatMessage("debug", message, data));
28
+ }
29
+ },
30
+ info(message, data) {
31
+ if (shouldLog("info")) {
32
+ console.error(formatMessage("info", message, data));
33
+ }
34
+ },
35
+ warn(message, data) {
36
+ if (shouldLog("warn")) {
37
+ console.error(formatMessage("warn", message, data));
38
+ }
39
+ },
40
+ error(message, data) {
41
+ if (shouldLog("error")) {
42
+ console.error(formatMessage("error", message, data));
43
+ }
44
+ }
45
+ };
46
+ function setLogLevel(level) {
47
+ currentLevel = level;
48
+ }
49
+ var logger_default = logger;
50
+
51
+ // src/client/api-client.ts
52
+ var ApiClient = class _ApiClient {
53
+ static BASE_URL = "http://redmineapi.nie.netease.com/api";
54
+ static DEFAULT_TIMEOUT = 3e4;
55
+ // 30秒
56
+ /**
57
+ * 发送 GET 请求
58
+ */
59
+ async get(endpoint, params = {}) {
60
+ try {
61
+ logger_default.debug(`API GET \u8BF7\u6C42: ${endpoint}`, { params });
62
+ const queryString = this.buildQueryString(params);
63
+ const url = `${_ApiClient.BASE_URL}/${endpoint}${queryString ? `?${queryString}` : ""}`;
64
+ const controller = new AbortController();
65
+ const timeoutId = setTimeout(() => controller.abort(), _ApiClient.DEFAULT_TIMEOUT);
66
+ try {
67
+ const response = await fetch(url, {
68
+ method: "GET",
69
+ signal: controller.signal
70
+ });
71
+ clearTimeout(timeoutId);
72
+ if (!response.ok) {
73
+ logger_default.error(`HTTP \u8BF7\u6C42\u5931\u8D25: ${response.status} ${response.statusText}`);
74
+ return {
75
+ success: false,
76
+ message: `HTTP\u8BF7\u6C42\u5931\u8D25,\u72B6\u6001\u7801:${response.status}`
77
+ };
78
+ }
79
+ const data = await response.json();
80
+ logger_default.debug("API \u54CD\u5E94:", data);
81
+ return data;
82
+ } finally {
83
+ clearTimeout(timeoutId);
84
+ }
85
+ } catch (error) {
86
+ return this.handleError(error);
87
+ }
88
+ }
89
+ /**
90
+ * 发送 POST 请求
91
+ */
92
+ async post(endpoint, params = {}) {
93
+ try {
94
+ logger_default.debug(`API POST \u8BF7\u6C42: ${endpoint}`, { params });
95
+ const url = `${_ApiClient.BASE_URL}/${endpoint}`;
96
+ const formData = this.buildFormData(params);
97
+ const controller = new AbortController();
98
+ const timeoutId = setTimeout(() => controller.abort(), _ApiClient.DEFAULT_TIMEOUT);
99
+ try {
100
+ const response = await fetch(url, {
101
+ method: "POST",
102
+ headers: {
103
+ "Content-Type": "application/x-www-form-urlencoded"
104
+ },
105
+ body: formData,
106
+ signal: controller.signal
107
+ });
108
+ clearTimeout(timeoutId);
109
+ if (!response.ok) {
110
+ logger_default.error(`HTTP \u8BF7\u6C42\u5931\u8D25: ${response.status} ${response.statusText}`);
111
+ return {
112
+ success: false,
113
+ message: `HTTP\u8BF7\u6C42\u5931\u8D25,\u72B6\u6001\u7801:${response.status}`
114
+ };
115
+ }
116
+ const data = await response.json();
117
+ logger_default.debug("API \u54CD\u5E94:", data);
118
+ return data;
119
+ } finally {
120
+ clearTimeout(timeoutId);
121
+ }
122
+ } catch (error) {
123
+ return this.handleError(error);
124
+ }
125
+ }
126
+ /**
127
+ * 处理错误
128
+ */
129
+ handleError(error) {
130
+ if (error instanceof Error) {
131
+ if (error.name === "AbortError") {
132
+ logger_default.error("\u8BF7\u6C42\u8D85\u65F6");
133
+ return {
134
+ success: false,
135
+ message: "\u8BF7\u6C42\u8D85\u65F6"
136
+ };
137
+ }
138
+ logger_default.error("\u8BF7\u6C42\u5F02\u5E38:", error);
139
+ return {
140
+ success: false,
141
+ message: `\u8BF7\u6C42\u5F02\u5E38:${error.message}`
142
+ };
143
+ }
144
+ return {
145
+ success: false,
146
+ message: "\u672A\u77E5\u9519\u8BEF"
147
+ };
148
+ }
149
+ /**
150
+ * 构建查询字符串
151
+ */
152
+ buildQueryString(params) {
153
+ return Object.entries(params).filter(([, value]) => value !== null && value !== void 0).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`).join("&");
154
+ }
155
+ /**
156
+ * 构建表单数据
157
+ */
158
+ buildFormData(params) {
159
+ return Object.entries(params).filter(([, value]) => value !== null && value !== void 0).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`).join("&");
160
+ }
161
+ };
162
+ var apiClient = new ApiClient();
163
+
164
+ // src/utils/markdown-parser.ts
165
+ function extractHours(text) {
166
+ const daysRegex = /[((][^))]*?([\d.]+)d[^))]*[))]/;
167
+ const daysMatch = text.match(daysRegex);
168
+ if (daysMatch) {
169
+ const value = parseFloat(daysMatch[1]);
170
+ return isNaN(value) ? void 0 : value;
171
+ }
172
+ const chineseDaysRegex = /[((]([\d.]+)天[))]/;
173
+ const chineseDaysMatch = text.match(chineseDaysRegex);
174
+ if (chineseDaysMatch) {
175
+ const value = parseFloat(chineseDaysMatch[1]);
176
+ return isNaN(value) ? void 0 : value;
177
+ }
178
+ const hoursRegex = /[((]([\d.]+)[))]/;
179
+ const hoursMatch = text.match(hoursRegex);
180
+ if (hoursMatch) {
181
+ const value = parseFloat(hoursMatch[1]);
182
+ return isNaN(value) ? void 0 : value;
183
+ }
184
+ return void 0;
185
+ }
186
+ function parseMarkdownLine(line) {
187
+ const leadingSpaces = line.match(/^(\s*)/)?.[1].length || 0;
188
+ const level = Math.floor(leadingSpaces / 2);
189
+ const cleanLine = line.trimStart().replace(/^[*-]\s*/, "").trim();
190
+ const estimatedHours = extractHours(cleanLine);
191
+ const subject = cleanLine.replace(/\s*[((][^))]*[))]/g, "").trim();
192
+ return { level, subject, estimatedHours };
193
+ }
194
+ function parseMarkdownToNodes(markdown) {
195
+ const lines = markdown.trim().split("\n").filter((line) => line.trim().length > 0).filter((line) => /^\s*[*-]/.test(line));
196
+ const rootNodes = [];
197
+ const nodeStack = [];
198
+ for (const line of lines) {
199
+ const { level, subject, estimatedHours } = parseMarkdownLine(line);
200
+ if (!subject) {
201
+ continue;
202
+ }
203
+ const node = {
204
+ subject,
205
+ estimatedHours,
206
+ children: []
207
+ };
208
+ while (nodeStack.length > 0 && nodeStack[nodeStack.length - 1].level >= level) {
209
+ nodeStack.pop();
210
+ }
211
+ if (nodeStack.length === 0) {
212
+ rootNodes.push(node);
213
+ } else {
214
+ nodeStack[nodeStack.length - 1].node.children.push(node);
215
+ }
216
+ nodeStack.push({ level, node });
217
+ }
218
+ return rootNodes;
219
+ }
220
+ function countTasks(nodes) {
221
+ let count = 0;
222
+ for (const node of nodes) {
223
+ count += 1;
224
+ count += countTasks(node.children);
225
+ }
226
+ return count;
227
+ }
228
+ function sumEstimatedHours(nodes) {
229
+ let total = 0;
230
+ for (const node of nodes) {
231
+ if (node.estimatedHours) {
232
+ total += node.estimatedHours;
233
+ }
234
+ total += sumEstimatedHours(node.children);
235
+ }
236
+ return total;
237
+ }
238
+
239
+ // src/services/issue-service.ts
240
+ function sleep(ms) {
241
+ return new Promise((resolve) => setTimeout(resolve, ms));
242
+ }
243
+ var IssueService = class {
244
+ /**
245
+ * 获取问题详情
246
+ */
247
+ async getIssue(token, host, project, issueId, includeChildren, includeRelations) {
248
+ const params = {
249
+ token,
250
+ host,
251
+ issue_id: issueId
252
+ };
253
+ if (project) params.project = project;
254
+ const includes = [];
255
+ if (includeChildren) includes.push("children");
256
+ if (includeRelations) includes.push("relations");
257
+ if (includes.length > 0) {
258
+ params.include = JSON.stringify(includes);
259
+ }
260
+ logger_default.info("\u83B7\u53D6\u95EE\u9898\u8BE6\u60C5", { host, project, issueId });
261
+ return await apiClient.get("issue", params);
262
+ }
263
+ /**
264
+ * 创建问题
265
+ */
266
+ async createIssue(params) {
267
+ logger_default.info("\u521B\u5EFA\u95EE\u9898", { params });
268
+ return await apiClient.post("create_issue", params);
269
+ }
270
+ /**
271
+ * 更新问题
272
+ */
273
+ async updateIssue(params) {
274
+ logger_default.info("\u66F4\u65B0\u95EE\u9898", { params });
275
+ return await apiClient.post("update_issue", params);
276
+ }
277
+ /**
278
+ * 获取问题附件
279
+ */
280
+ async getIssueAttachments(token, host, project, issueId) {
281
+ logger_default.info("\u83B7\u53D6\u95EE\u9898\u9644\u4EF6", { host, project, issueId });
282
+ return await apiClient.get("get_issue_attachments", {
283
+ token,
284
+ host,
285
+ project,
286
+ issue_id: issueId
287
+ });
288
+ }
289
+ /**
290
+ * 获取问题字段选项
291
+ */
292
+ async getIssueFieldOptions(token, host, project) {
293
+ logger_default.info("\u83B7\u53D6\u95EE\u9898\u5B57\u6BB5\u9009\u9879", { host, project });
294
+ return await apiClient.post("get_issue_field_options", { token, host, project });
295
+ }
296
+ /**
297
+ * 自定义查询
298
+ */
299
+ async customQuery(token, host, project, queryId, limit, offset) {
300
+ const params = {
301
+ token,
302
+ host,
303
+ project,
304
+ query_id: queryId
305
+ };
306
+ if (limit !== void 0) params.limit = limit;
307
+ if (offset !== void 0) params.offset = offset;
308
+ logger_default.info("\u81EA\u5B9A\u4E49\u67E5\u8BE2", { host, project, queryId });
309
+ return await apiClient.post("custom_query", params);
310
+ }
311
+ /**
312
+ * V6 过滤器查询
313
+ */
314
+ async filterQueryV6(token, host, project, mode, filterParams) {
315
+ const params = {
316
+ token,
317
+ host,
318
+ project,
319
+ mode,
320
+ ...filterParams
321
+ };
322
+ logger_default.info("V6 \u8FC7\u6EE4\u5668\u67E5\u8BE2", { host, project, mode });
323
+ return await apiClient.post("filter_query_v6", params);
324
+ }
325
+ /**
326
+ * 递归获取问题及其子单(包含完整详情)
327
+ * @param depth 递归深度,默认 10
328
+ * @param currentLevel 当前层级(内部使用)
329
+ *
330
+ * 实现逻辑:
331
+ * 1. 调用 getIssue(includeChildren=true) 获取当前问题详情
332
+ * 2. API 返回的 children 字段只包含简略信息(status 是字符串,缺少 assigned_to 等)
333
+ * 3. 从 children 中提取子单 ID,递归调用 getIssue 获取每个子单的完整详情
334
+ * 4. 用完整详情替换原始的简略 children
335
+ */
336
+ async getIssueWithChildren(token, host, project, issueId, depth = 10, currentLevel2 = 0) {
337
+ logger_default.info("\u9012\u5F52\u83B7\u53D6\u95EE\u9898\u8BE6\u60C5", { host, project, issueId, depth, currentLevel: currentLevel2 });
338
+ const issueResult = await this.getIssue(token, host, project, issueId, true);
339
+ if (!issueResult.success || !issueResult.data) {
340
+ return issueResult;
341
+ }
342
+ const issue = issueResult.data;
343
+ issue.level = currentLevel2;
344
+ if (currentLevel2 >= depth) {
345
+ return { success: true, data: issue };
346
+ }
347
+ const rawChildren = issue.children;
348
+ if (!rawChildren || rawChildren.length === 0) {
349
+ return { success: true, data: issue };
350
+ }
351
+ const directChildrenIds = rawChildren.filter((child) => child.id).map((child) => child.id);
352
+ if (directChildrenIds.length === 0) {
353
+ return { success: true, data: issue };
354
+ }
355
+ logger_default.info("\u83B7\u53D6\u5B50\u5355\u5B8C\u6574\u8BE6\u60C5", { parentId: issueId, childCount: directChildrenIds.length, level: currentLevel2 });
356
+ const childrenPromises = directChildrenIds.map(
357
+ (childId) => this.getIssueWithChildren(token, host, project, childId, depth, currentLevel2 + 1)
358
+ );
359
+ const childrenResults = await Promise.all(childrenPromises);
360
+ issue.children = childrenResults.filter((r) => r.success && r.data).map((r) => r.data);
361
+ return { success: true, data: issue };
362
+ }
363
+ /**
364
+ * 获取直接子单的 ID 列表
365
+ */
366
+ async getDirectChildren(token, host, project, parentId) {
367
+ const filters = {
368
+ parent_id: { operator: "=", values: [parentId.toString()] }
369
+ };
370
+ const params = {
371
+ token,
372
+ host,
373
+ project,
374
+ filter_mode: "simple",
375
+ filters: JSON.stringify(filters),
376
+ c: JSON.stringify(["id"]),
377
+ per_page: 200
378
+ };
379
+ logger_default.info("\u67E5\u8BE2\u76F4\u63A5\u5B50\u5355", { host, project, parentId });
380
+ const result = await apiClient.get("filter_query_v6", params);
381
+ if (result.success && result.data) {
382
+ const data = result.data;
383
+ if (data.data?.list) {
384
+ const ids = data.data.list.map((item) => item.id);
385
+ return { success: true, data: ids };
386
+ }
387
+ }
388
+ return { success: true, data: [] };
389
+ }
390
+ /**
391
+ * 查询子任务(按根任务和负责人过滤)
392
+ * 使用两步查询:先过滤获取 ID 列表,再批量获取详情
393
+ */
394
+ async queryChildren(token, host, project, rootId, assignedToId, perPage = 100) {
395
+ const filters = {
396
+ root_id: { operator: "=", values: [rootId.toString()] }
397
+ };
398
+ if (assignedToId) {
399
+ filters.assigned_to_id = { operator: "=", values: [assignedToId.toString()] };
400
+ }
401
+ const params = {
402
+ token,
403
+ host,
404
+ project,
405
+ filter_mode: "simple",
406
+ filters: JSON.stringify(filters),
407
+ c: JSON.stringify(["id", "subject", "status", "tracker", "estimated_hours", "done_ratio", "assigned_to", "parent"]),
408
+ per_page: perPage
409
+ };
410
+ logger_default.info("\u67E5\u8BE2\u5B50\u4EFB\u52A1", { host, project, rootId, assignedToId });
411
+ const result = await apiClient.get("filter_query_v6", params);
412
+ if (result.success && result.data) {
413
+ const data = result.data;
414
+ if (data.data?.list && data.data.list.length > 0) {
415
+ const issueIds = data.data.list.map((item) => item.id);
416
+ const detailedIssues = await Promise.all(
417
+ issueIds.map(async (id) => {
418
+ const issueResult = await this.getIssue(token, host, project, id);
419
+ if (issueResult.success && issueResult.data) {
420
+ return {
421
+ id: issueResult.data.id,
422
+ subject: issueResult.data.subject,
423
+ status: issueResult.data.status?.name,
424
+ tracker: issueResult.data.tracker?.name,
425
+ estimated_hours: issueResult.data.estimated_hours,
426
+ done_ratio: issueResult.data.done_ratio,
427
+ assigned_to: issueResult.data.assigned_to?.name,
428
+ parent_id: issueResult.data.parent_id
429
+ };
430
+ }
431
+ return null;
432
+ })
433
+ );
434
+ return {
435
+ success: true,
436
+ data: {
437
+ total: detailedIssues.filter(Boolean).length,
438
+ issues: detailedIssues.filter(Boolean)
439
+ }
440
+ };
441
+ }
442
+ }
443
+ return result;
444
+ }
445
+ /**
446
+ * 批量获取多个问题详情
447
+ * @param issueIds 问题 ID 数组
448
+ * @param options 可选参数
449
+ * @returns 返回所有问题的详情数组(包含成功和失败的结果)
450
+ */
451
+ async getMultipleIssues(token, host, project, issueIds, options) {
452
+ const { includeChildren = false, includeRelations = false, depth = 0 } = options || {};
453
+ logger_default.info("\u6279\u91CF\u83B7\u53D6\u95EE\u9898\u8BE6\u60C5", { host, project, count: issueIds.length, issueIds });
454
+ const results = await Promise.all(
455
+ issueIds.map(async (issueId) => {
456
+ try {
457
+ let result;
458
+ if (includeChildren && depth > 0) {
459
+ result = await this.getIssueWithChildren(token, host, project, issueId, depth);
460
+ } else {
461
+ result = await this.getIssue(token, host, project, issueId, includeChildren, includeRelations);
462
+ }
463
+ if (result.success && result.data) {
464
+ return { id: issueId, success: true, data: result.data };
465
+ } else {
466
+ return {
467
+ id: issueId,
468
+ success: false,
469
+ error: result.post_error_msg || result.api_error_msg || result.message || result.msg || "\u83B7\u53D6\u5931\u8D25"
470
+ };
471
+ }
472
+ } catch (error) {
473
+ return {
474
+ id: issueId,
475
+ success: false,
476
+ error: error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"
477
+ };
478
+ }
479
+ })
480
+ );
481
+ const successCount = results.filter((r) => r.success).length;
482
+ const failCount = results.filter((r) => !r.success).length;
483
+ logger_default.info("\u6279\u91CF\u83B7\u53D6\u5B8C\u6210", { total: issueIds.length, success: successCount, fail: failCount });
484
+ return {
485
+ success: true,
486
+ data: results
487
+ };
488
+ }
489
+ /**
490
+ * 同步子单:从源父单复制子单到目标父单
491
+ * @param sourceParentId 源父单 ID
492
+ * @param targetParentId 目标父单 ID
493
+ * @param assignedToMail 新子单的指派人邮箱
494
+ * @param options 选项
495
+ */
496
+ async syncChildIssues(token, host, project, sourceParentId, targetParentId, assignedToMail, options) {
497
+ const { dryRun = false, depth = 10, skipExisting = true } = options || {};
498
+ logger_default.info("\u5F00\u59CB\u540C\u6B65\u5B50\u5355", {
499
+ sourceParentId,
500
+ targetParentId,
501
+ assignedToMail,
502
+ dryRun,
503
+ depth,
504
+ skipExisting
505
+ });
506
+ const result = {
507
+ totalTasks: 0,
508
+ totalCreated: 0,
509
+ totalSkipped: 0,
510
+ totalFailed: 0,
511
+ created: [],
512
+ skipped: [],
513
+ failed: []
514
+ };
515
+ try {
516
+ logger_default.info("\u6B63\u5728\u83B7\u53D6\u6E90\u7236\u5355\u7684\u5B50\u5355\u6811\u5F62\u7ED3\u6784...");
517
+ const sourceResult = await this.getIssueWithChildren(token, host, project, sourceParentId, depth);
518
+ if (!sourceResult.success || !sourceResult.data) {
519
+ return {
520
+ success: false,
521
+ message: `\u83B7\u53D6\u6E90\u7236\u5355 #${sourceParentId} \u5931\u8D25: ${sourceResult.message || "\u672A\u77E5\u9519\u8BEF"}`
522
+ };
523
+ }
524
+ const sourceIssue = sourceResult.data;
525
+ result.totalTasks = this.countAllChildren(sourceIssue);
526
+ logger_default.info(`\u6E90\u7236\u5355\u5171\u6709 ${result.totalTasks} \u4E2A\u5B50\u4EFB\u52A1`);
527
+ logger_default.info("\u6B63\u5728\u83B7\u53D6\u76EE\u6807\u7236\u5355\u4FE1\u606F...");
528
+ const targetResult = await this.getIssue(token, host, project, targetParentId, false, false);
529
+ if (!targetResult.success || !targetResult.data) {
530
+ return {
531
+ success: false,
532
+ message: `\u83B7\u53D6\u76EE\u6807\u7236\u5355 #${targetParentId} \u5931\u8D25: ${targetResult.message || "\u672A\u77E5\u9519\u8BEF"}`
533
+ };
534
+ }
535
+ const targetParentInfo = targetResult.data;
536
+ let existingTaskNames = /* @__PURE__ */ new Set();
537
+ if (skipExisting) {
538
+ logger_default.info("\u6B63\u5728\u83B7\u53D6\u76EE\u6807\u7236\u5355\u5DF2\u6709\u7684\u5B50\u5355...");
539
+ const existingResult = await this.getIssueWithChildren(token, host, project, targetParentId, depth);
540
+ if (existingResult.success && existingResult.data) {
541
+ existingTaskNames = this.collectAllTaskNames(existingResult.data);
542
+ logger_default.info(`\u76EE\u6807\u7236\u5355\u5DF2\u6709 ${existingTaskNames.size} \u4E2A\u5B50\u5355`);
543
+ }
544
+ }
545
+ logger_default.info("\u5F00\u59CB\u9012\u5F52\u540C\u6B65\u5B50\u5355...");
546
+ await this.syncChildrenRecursive(
547
+ token,
548
+ host,
549
+ project,
550
+ sourceIssue,
551
+ targetParentId,
552
+ targetParentInfo,
553
+ existingTaskNames,
554
+ assignedToMail,
555
+ dryRun,
556
+ result
557
+ );
558
+ logger_default.info("\u540C\u6B65\u5B8C\u6210", {
559
+ totalCreated: result.totalCreated,
560
+ totalSkipped: result.totalSkipped,
561
+ totalFailed: result.totalFailed
562
+ });
563
+ return { success: true, data: result };
564
+ } catch (error) {
565
+ logger_default.error("\u540C\u6B65\u5B50\u5355\u65F6\u53D1\u751F\u9519\u8BEF", error);
566
+ return {
567
+ success: false,
568
+ message: `\u540C\u6B65\u5B50\u5355\u65F6\u53D1\u751F\u9519\u8BEF: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
569
+ };
570
+ }
571
+ }
572
+ /**
573
+ * 计算所有子任务数量(递归)
574
+ */
575
+ countAllChildren(issue) {
576
+ let count = 0;
577
+ for (const child of issue.children || []) {
578
+ count++;
579
+ count += this.countAllChildren(child);
580
+ }
581
+ return count;
582
+ }
583
+ /**
584
+ * 收集所有任务名称(递归)
585
+ */
586
+ collectAllTaskNames(issue) {
587
+ const names = /* @__PURE__ */ new Set();
588
+ if (issue.subject?.trim()) {
589
+ names.add(issue.subject.trim());
590
+ }
591
+ for (const child of issue.children || []) {
592
+ const childNames = this.collectAllTaskNames(child);
593
+ childNames.forEach((name) => names.add(name));
594
+ }
595
+ return names;
596
+ }
597
+ /**
598
+ * 递归同步子单
599
+ */
600
+ async syncChildrenRecursive(token, host, project, sourceNode, targetParentId, targetParentInfo, existingTaskNames, assignedToMail, dryRun, result) {
601
+ for (const child of sourceNode.children || []) {
602
+ const taskName = child.subject?.trim() || "\u672A\u547D\u540D\u4EFB\u52A1";
603
+ const sourceId = child.id;
604
+ if (existingTaskNames.has(taskName)) {
605
+ logger_default.info(`\u23ED\uFE0F \u8DF3\u8FC7\u5DF2\u5B58\u5728\u7684\u4EFB\u52A1: ${taskName}`);
606
+ result.totalSkipped++;
607
+ result.skipped.push({
608
+ sourceId,
609
+ subject: taskName,
610
+ reason: "\u76EE\u6807\u7236\u5355\u4E0B\u5DF2\u5B58\u5728\u540C\u540D\u4EFB\u52A1"
611
+ });
612
+ const progress = result.totalCreated + result.totalSkipped + result.totalFailed;
613
+ console.error(`[${progress}/${result.totalTasks}] \u23ED\uFE0F \u8DF3\u8FC7: #${sourceId} ${taskName} (\u5DF2\u5B58\u5728)`);
614
+ continue;
615
+ }
616
+ const isLeafNode = !child.children || child.children.length === 0;
617
+ const createParams = {
618
+ token,
619
+ host,
620
+ project,
621
+ parent_issue_id: targetParentId,
622
+ subject: taskName,
623
+ // 继承自目标父单
624
+ tracker: targetParentInfo.tracker?.name,
625
+ status: "\u65B0\u5EFA",
626
+ // 覆盖指派人为"我"
627
+ assigned_to_mail: assignedToMail,
628
+ // 只有叶子节点才设置工时
629
+ estimated_hours: isLeafNode ? child.estimated_hours : void 0,
630
+ // 保留原任务的优先级
631
+ priority_id: child.priority?.id
632
+ };
633
+ const targetWithVersion = targetParentInfo;
634
+ if (targetWithVersion.fixed_version?.name) {
635
+ createParams.version = targetWithVersion.fixed_version.name;
636
+ }
637
+ const targetWithCustomFields = targetParentInfo;
638
+ if (targetWithCustomFields.custom_fields && targetWithCustomFields.custom_fields.length > 0) {
639
+ const customFieldMap = {};
640
+ const followsMails = [];
641
+ for (const field of targetWithCustomFields.custom_fields) {
642
+ if (field.value !== null && field.value !== void 0 && field.value !== "") {
643
+ if (field.identify === "IssuesQCFollow") {
644
+ const followsValue = field.value;
645
+ if (Array.isArray(followsValue)) {
646
+ for (const item of followsValue) {
647
+ if (item.user?.mail) {
648
+ followsMails.push(item.user.mail);
649
+ }
650
+ }
651
+ }
652
+ } else if (field.field_format === "user") {
653
+ if (field.multiple && Array.isArray(field.value)) {
654
+ const userIds = [];
655
+ for (const item of field.value) {
656
+ if (item.user?.id) {
657
+ userIds.push(item.user.id);
658
+ }
659
+ }
660
+ if (userIds.length > 0) {
661
+ customFieldMap[field.id] = userIds.join(",");
662
+ }
663
+ } else {
664
+ const userValue = field.value;
665
+ if (userValue.user?.id) {
666
+ customFieldMap[field.id] = userValue.user.id;
667
+ }
668
+ }
669
+ } else {
670
+ customFieldMap[field.id] = field.value;
671
+ }
672
+ }
673
+ }
674
+ if (Object.keys(customFieldMap).length > 0) {
675
+ createParams.custom_field = JSON.stringify(customFieldMap);
676
+ }
677
+ if (followsMails.length > 0) {
678
+ createParams.follows = followsMails;
679
+ }
680
+ }
681
+ logger_default.info(`\u6B63\u5728\u521B\u5EFA\u5B50\u4EFB\u52A1: ${taskName}`, { sourceId, targetParentId, isLeafNode });
682
+ if (dryRun) {
683
+ logger_default.info(`[\u6A21\u62DF] \u5C06\u521B\u5EFA\u5B50\u4EFB\u52A1: ${taskName}`);
684
+ result.totalCreated++;
685
+ result.created.push({
686
+ sourceId,
687
+ newId: 0,
688
+ // 模拟模式没有真实 ID
689
+ subject: taskName,
690
+ parentId: targetParentId
691
+ });
692
+ if (child.children && child.children.length > 0) {
693
+ await this.syncChildrenRecursive(
694
+ token,
695
+ host,
696
+ project,
697
+ child,
698
+ 0,
699
+ // 模拟模式下没有真实的新 ID
700
+ targetParentInfo,
701
+ existingTaskNames,
702
+ assignedToMail,
703
+ dryRun,
704
+ result
705
+ );
706
+ }
707
+ } else {
708
+ try {
709
+ const createResult = await this.createIssue(createParams);
710
+ if (createResult.success && createResult.data) {
711
+ const newId = createResult.data.id;
712
+ logger_default.info(`\u2705 \u6210\u529F\u521B\u5EFA\u5B50\u4EFB\u52A1: ID=${newId}, \u6807\u9898=${taskName}`);
713
+ result.totalCreated++;
714
+ result.created.push({
715
+ sourceId,
716
+ newId,
717
+ subject: taskName,
718
+ parentId: targetParentId
719
+ });
720
+ const progress = result.totalCreated + result.totalSkipped + result.totalFailed;
721
+ console.error(`[${progress}/${result.totalTasks}] \u2705 \u540C\u6B65\u6210\u529F: #${newId} \u2190 #${sourceId} ${taskName}`);
722
+ if (child.children && child.children.length > 0) {
723
+ logger_default.info(`\u{1F4C1} \u53D1\u73B0\u5B50\u4EFB\u52A1 ${newId} \u6709 ${child.children.length} \u4E2A\u5B50\u4EFB\u52A1\uFF0C\u7EE7\u7EED\u9012\u5F52\u521B\u5EFA...`);
724
+ await this.syncChildrenRecursive(
725
+ token,
726
+ host,
727
+ project,
728
+ child,
729
+ newId,
730
+ targetParentInfo,
731
+ existingTaskNames,
732
+ assignedToMail,
733
+ dryRun,
734
+ result
735
+ );
736
+ }
737
+ } else {
738
+ const errorMsg = createResult.post_error_msg || createResult.api_error_msg || createResult.message || "\u521B\u5EFA\u5931\u8D25";
739
+ logger_default.error(`\u274C \u521B\u5EFA\u5B50\u4EFB\u52A1\u5931\u8D25: ${taskName}`, errorMsg);
740
+ result.totalFailed++;
741
+ result.failed.push({
742
+ sourceId,
743
+ subject: taskName,
744
+ error: errorMsg
745
+ });
746
+ const progress = result.totalCreated + result.totalSkipped + result.totalFailed;
747
+ console.error(`[${progress}/${result.totalTasks}] \u274C \u540C\u6B65\u5931\u8D25: #${sourceId} ${taskName} - ${errorMsg}`);
748
+ }
749
+ } catch (error) {
750
+ const errorMsg = error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF";
751
+ logger_default.error(`\u274C \u521B\u5EFA\u5B50\u4EFB\u52A1\u65F6\u53D1\u751F\u9519\u8BEF: ${taskName}`, error);
752
+ result.totalFailed++;
753
+ result.failed.push({
754
+ sourceId,
755
+ subject: taskName,
756
+ error: errorMsg
757
+ });
758
+ const progress = result.totalCreated + result.totalSkipped + result.totalFailed;
759
+ console.error(`[${progress}/${result.totalTasks}] \u274C \u540C\u6B65\u5931\u8D25: #${sourceId} ${taskName} - ${errorMsg}`);
760
+ }
761
+ await sleep(500);
762
+ }
763
+ }
764
+ }
765
+ /**
766
+ * 从 Markdown 批量创建子单
767
+ * @param parentId 父单 ID
768
+ * @param markdown Markdown 文本
769
+ * @param assignedToMail 指派人邮箱
770
+ * @param options 选项
771
+ */
772
+ async batchCreateFromMarkdown(token, host, project, parentId, markdown, assignedToMail, options) {
773
+ const { dryRun = false, interval = 5e3, extraCustomFields = {} } = options || {};
774
+ logger_default.info("\u5F00\u59CB\u6279\u91CF\u521B\u5EFA\u5B50\u5355", {
775
+ parentId,
776
+ assignedToMail,
777
+ dryRun,
778
+ interval
779
+ });
780
+ const result = {
781
+ totalCreated: 0,
782
+ totalFailed: 0,
783
+ totalTasks: 0,
784
+ totalEstimatedHours: 0,
785
+ created: [],
786
+ failed: []
787
+ };
788
+ try {
789
+ const taskNodes = parseMarkdownToNodes(markdown);
790
+ if (taskNodes.length === 0) {
791
+ return {
792
+ success: false,
793
+ message: "\u6CA1\u6709\u89E3\u6790\u5230\u4EFB\u4F55\u4EFB\u52A1\uFF0C\u8BF7\u68C0\u67E5 Markdown \u683C\u5F0F"
794
+ };
795
+ }
796
+ result.totalTasks = countTasks(taskNodes);
797
+ result.totalEstimatedHours = sumEstimatedHours(taskNodes);
798
+ logger_default.info("Markdown \u89E3\u6790\u5B8C\u6210", {
799
+ totalTasks: result.totalTasks,
800
+ totalEstimatedHours: result.totalEstimatedHours
801
+ });
802
+ logger_default.info("\u6B63\u5728\u83B7\u53D6\u7236\u5355\u4FE1\u606F...");
803
+ const parentResult = await this.getIssue(token, host, project, parentId, false, false);
804
+ if (!parentResult.success || !parentResult.data) {
805
+ return {
806
+ success: false,
807
+ message: `\u83B7\u53D6\u7236\u5355 #${parentId} \u5931\u8D25: ${parentResult.message || "\u672A\u77E5\u9519\u8BEF"}`
808
+ };
809
+ }
810
+ const parentInfo = parentResult.data;
811
+ logger_default.info("\u5F00\u59CB\u9012\u5F52\u521B\u5EFA\u5B50\u5355...");
812
+ await this.batchCreateRecursive(
813
+ token,
814
+ host,
815
+ project,
816
+ taskNodes,
817
+ parentId,
818
+ parentInfo,
819
+ assignedToMail,
820
+ dryRun,
821
+ interval,
822
+ result,
823
+ extraCustomFields
824
+ );
825
+ logger_default.info("\u6279\u91CF\u521B\u5EFA\u5B8C\u6210", {
826
+ totalCreated: result.totalCreated,
827
+ totalFailed: result.totalFailed
828
+ });
829
+ return { success: true, data: result };
830
+ } catch (error) {
831
+ logger_default.error("\u6279\u91CF\u521B\u5EFA\u5B50\u5355\u65F6\u53D1\u751F\u9519\u8BEF", error);
832
+ return {
833
+ success: false,
834
+ message: `\u6279\u91CF\u521B\u5EFA\u5B50\u5355\u65F6\u53D1\u751F\u9519\u8BEF: ${error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF"}`
835
+ };
836
+ }
837
+ }
838
+ /**
839
+ * 递归批量创建子单
840
+ */
841
+ async batchCreateRecursive(token, host, project, taskNodes, targetParentId, parentInfo, assignedToMail, dryRun, interval, result, extraCustomFields = {}) {
842
+ for (const node of taskNodes) {
843
+ const taskName = node.subject;
844
+ const isLeafNode = node.children.length === 0;
845
+ const createParams = {
846
+ token,
847
+ host,
848
+ project,
849
+ parent_issue_id: targetParentId,
850
+ subject: taskName,
851
+ // 继承自父单
852
+ tracker: parentInfo.tracker?.name,
853
+ status: "\u65B0\u5EFA",
854
+ // 指派人
855
+ assigned_to_mail: assignedToMail,
856
+ // 只有叶子节点才设置工时
857
+ estimated_hours: isLeafNode ? node.estimatedHours : void 0
858
+ };
859
+ const parentWithVersion = parentInfo;
860
+ if (parentWithVersion.fixed_version?.name) {
861
+ createParams.version = parentWithVersion.fixed_version.name;
862
+ }
863
+ const parentWithCustomFields = parentInfo;
864
+ if (parentWithCustomFields.custom_fields && parentWithCustomFields.custom_fields.length > 0) {
865
+ const customFieldMap = {};
866
+ const followsMails = [];
867
+ for (const field of parentWithCustomFields.custom_fields) {
868
+ if (field.value !== null && field.value !== void 0 && field.value !== "") {
869
+ if (field.identify === "IssuesQCFollow") {
870
+ const followsValue = field.value;
871
+ if (Array.isArray(followsValue)) {
872
+ for (const item of followsValue) {
873
+ if (item.user?.mail) {
874
+ followsMails.push(item.user.mail);
875
+ }
876
+ }
877
+ }
878
+ } else if (field.field_format === "user") {
879
+ if (field.multiple && Array.isArray(field.value)) {
880
+ const userIds = [];
881
+ for (const item of field.value) {
882
+ if (item.user?.id) {
883
+ userIds.push(item.user.id);
884
+ }
885
+ }
886
+ if (userIds.length > 0) {
887
+ customFieldMap[field.id] = userIds.join(",");
888
+ }
889
+ } else {
890
+ const userValue = field.value;
891
+ if (userValue.user?.id) {
892
+ customFieldMap[field.id] = userValue.user.id;
893
+ }
894
+ }
895
+ } else {
896
+ customFieldMap[field.id] = field.value;
897
+ }
898
+ }
899
+ }
900
+ const mergedCustomFields = { ...customFieldMap, ...extraCustomFields };
901
+ if (Object.keys(mergedCustomFields).length > 0) {
902
+ createParams.custom_field = JSON.stringify(mergedCustomFields);
903
+ }
904
+ if (followsMails.length > 0) {
905
+ createParams.follows = followsMails;
906
+ }
907
+ } else if (Object.keys(extraCustomFields).length > 0) {
908
+ createParams.custom_field = JSON.stringify(extraCustomFields);
909
+ }
910
+ logger_default.info(`\u6B63\u5728\u521B\u5EFA\u5B50\u4EFB\u52A1: ${taskName}`, { targetParentId, isLeafNode, estimatedHours: node.estimatedHours });
911
+ logger_default.debug("\u521B\u5EFA\u53C2\u6570", { custom_field: createParams.custom_field, follows: createParams.follows });
912
+ if (dryRun) {
913
+ logger_default.info(`[\u6A21\u62DF] \u5C06\u521B\u5EFA\u5B50\u4EFB\u52A1: ${taskName}`);
914
+ result.totalCreated++;
915
+ result.created.push({
916
+ newId: 0,
917
+ // 模拟模式没有真实 ID
918
+ subject: taskName,
919
+ parentId: targetParentId,
920
+ estimatedHours: node.estimatedHours
921
+ });
922
+ if (node.children.length > 0) {
923
+ await this.batchCreateRecursive(
924
+ token,
925
+ host,
926
+ project,
927
+ node.children,
928
+ 0,
929
+ // 模拟模式下没有真实的新 ID
930
+ parentInfo,
931
+ assignedToMail,
932
+ dryRun,
933
+ interval,
934
+ result,
935
+ extraCustomFields
936
+ );
937
+ }
938
+ } else {
939
+ try {
940
+ const createResult = await this.createIssue(createParams);
941
+ if (createResult.success && createResult.data) {
942
+ const newId = createResult.data.id;
943
+ logger_default.info(`\u2705 \u6210\u529F\u521B\u5EFA\u5B50\u4EFB\u52A1: ID=${newId}, \u6807\u9898=${taskName}`);
944
+ result.totalCreated++;
945
+ result.created.push({
946
+ newId,
947
+ subject: taskName,
948
+ parentId: targetParentId,
949
+ estimatedHours: node.estimatedHours
950
+ });
951
+ const progress = result.totalCreated + result.totalFailed;
952
+ console.error(`[${progress}/${result.totalTasks}] \u2705 \u521B\u5EFA\u6210\u529F: #${newId} ${taskName}`);
953
+ if (node.children.length > 0) {
954
+ logger_default.info(`\u{1F4C1} \u53D1\u73B0\u5B50\u4EFB\u52A1 ${newId} \u6709 ${node.children.length} \u4E2A\u5B50\u4EFB\u52A1\uFF0C\u7EE7\u7EED\u9012\u5F52\u521B\u5EFA...`);
955
+ await this.batchCreateRecursive(
956
+ token,
957
+ host,
958
+ project,
959
+ node.children,
960
+ newId,
961
+ parentInfo,
962
+ assignedToMail,
963
+ dryRun,
964
+ interval,
965
+ result,
966
+ extraCustomFields
967
+ );
968
+ }
969
+ } else {
970
+ const errorMsg = createResult.post_error_msg || createResult.api_error_msg || createResult.message || "\u521B\u5EFA\u5931\u8D25";
971
+ logger_default.error(`\u274C \u521B\u5EFA\u5B50\u4EFB\u52A1\u5931\u8D25: ${taskName}`, errorMsg);
972
+ result.totalFailed++;
973
+ result.failed.push({
974
+ subject: taskName,
975
+ parentId: targetParentId,
976
+ error: errorMsg
977
+ });
978
+ const progress = result.totalCreated + result.totalFailed;
979
+ console.error(`[${progress}/${result.totalTasks}] \u274C \u521B\u5EFA\u5931\u8D25: ${taskName} - ${errorMsg}`);
980
+ }
981
+ } catch (error) {
982
+ const errorMsg = error instanceof Error ? error.message : "\u672A\u77E5\u9519\u8BEF";
983
+ logger_default.error(`\u274C \u521B\u5EFA\u5B50\u4EFB\u52A1\u65F6\u53D1\u751F\u9519\u8BEF: ${taskName}`, error);
984
+ result.totalFailed++;
985
+ result.failed.push({
986
+ subject: taskName,
987
+ parentId: targetParentId,
988
+ error: errorMsg
989
+ });
990
+ const progress = result.totalCreated + result.totalFailed;
991
+ console.error(`[${progress}/${result.totalTasks}] \u274C \u521B\u5EFA\u5931\u8D25: ${taskName} - ${errorMsg}`);
992
+ }
993
+ await sleep(interval);
994
+ }
995
+ }
996
+ }
997
+ };
998
+ var issueService = new IssueService();
999
+
1000
+ // src/services/time-entry-service.ts
1001
+ var TimeEntryService = class {
1002
+ /**
1003
+ * 查询工时条目
1004
+ */
1005
+ async queryTimeEntries(params) {
1006
+ const requestParams = {
1007
+ token: params.token,
1008
+ host: params.host,
1009
+ project: params.project
1010
+ };
1011
+ if (params.from_date) requestParams.from_date = params.from_date;
1012
+ if (params.to_date) requestParams.to_date = params.to_date;
1013
+ if (params.user_id) requestParams.user_id = params.user_id;
1014
+ if (params.activity_id) requestParams.activity_id = params.activity_id;
1015
+ if (params.member_of_group_id) requestParams.member_of_group_id = params.member_of_group_id;
1016
+ if (params.tracker_id) requestParams.tracker_id = params.tracker_id;
1017
+ if (params.version_id) requestParams.version_id = params.version_id;
1018
+ if (params.offset !== void 0) requestParams.offset = params.offset;
1019
+ if (params.limit !== void 0) requestParams.limit = params.limit;
1020
+ logger_default.info("\u67E5\u8BE2\u5DE5\u65F6\u6761\u76EE", { host: params.host, project: params.project });
1021
+ return await apiClient.get("query_time_entries", requestParams);
1022
+ }
1023
+ /**
1024
+ * 获取工时条目选项(活动类型列表等)
1025
+ * 使用 time_entry GET API
1026
+ */
1027
+ async getTimeEntryOptions(token, host, project, issueId) {
1028
+ logger_default.info("\u83B7\u53D6\u5DE5\u65F6\u6761\u76EE\u9009\u9879", { host, project, issueId });
1029
+ const params = { token, host, project };
1030
+ if (issueId) params.issue_id = issueId;
1031
+ return await apiClient.get("time_entry", params);
1032
+ }
1033
+ /**
1034
+ * 创建工时条目
1035
+ * 使用 time_entry API(非 save_time_entry)
1036
+ */
1037
+ async createTimeEntry(params) {
1038
+ logger_default.info("\u521B\u5EFA\u5DE5\u65F6\u6761\u76EE", { params });
1039
+ return await apiClient.post("time_entry", params);
1040
+ }
1041
+ /**
1042
+ * 更新工时条目
1043
+ */
1044
+ async updateTimeEntry(params) {
1045
+ logger_default.info("\u66F4\u65B0\u5DE5\u65F6\u6761\u76EE", { params });
1046
+ return await apiClient.post("save_time_entry", params);
1047
+ }
1048
+ /**
1049
+ * 删除工时条目
1050
+ */
1051
+ async deleteTimeEntry(token, host, timeEntryId) {
1052
+ logger_default.info("\u5220\u9664\u5DE5\u65F6\u6761\u76EE", { host, timeEntryId });
1053
+ return await apiClient.get("delete_time_entry", {
1054
+ token,
1055
+ host,
1056
+ id: timeEntryId
1057
+ // 使用 id 参数,非 time_entry_id
1058
+ });
1059
+ }
1060
+ };
1061
+ var timeEntryService = new TimeEntryService();
1062
+
1063
+ // src/services/user-service.ts
1064
+ var UserService = class {
1065
+ /**
1066
+ * 测试连接 (通过获取项目列表来验证)
1067
+ */
1068
+ async testConnection(token, host, project) {
1069
+ logger_default.info("\u6D4B\u8BD5\u8FDE\u63A5", { host, project });
1070
+ return await apiClient.get("project", { token, host });
1071
+ }
1072
+ /**
1073
+ * 获取项目列表
1074
+ */
1075
+ async getProjects(token, host) {
1076
+ logger_default.info("\u83B7\u53D6\u9879\u76EE\u5217\u8868", { host });
1077
+ return await apiClient.get("project", { token, host });
1078
+ }
1079
+ /**
1080
+ * 获取项目用户
1081
+ */
1082
+ async getProjectUsers(token, host, project) {
1083
+ logger_default.info("\u83B7\u53D6\u9879\u76EE\u7528\u6237", { host, project });
1084
+ return await apiClient.get("user", { token, host, project });
1085
+ }
1086
+ /**
1087
+ * 获取主机信息
1088
+ */
1089
+ async getHostInfo(token, host) {
1090
+ logger_default.info("\u83B7\u53D6\u4E3B\u673A\u4FE1\u606F", { host });
1091
+ return await apiClient.get("host", { token, host });
1092
+ }
1093
+ };
1094
+ var userService = new UserService();
1095
+
1096
+ // src/utils/config.ts
1097
+ import * as fs from "fs";
1098
+ import * as path from "path";
1099
+ import * as os from "os";
1100
+ var DEFAULT_CONFIG_DIR = path.join(os.homedir(), ".config", "pm-cli");
1101
+ var DEFAULT_CONFIG_FILE = "config.json";
1102
+ function getConfigPath(customPath) {
1103
+ if (customPath) {
1104
+ return customPath;
1105
+ }
1106
+ if (process.env.PM_CLI_CONFIG) {
1107
+ return process.env.PM_CLI_CONFIG;
1108
+ }
1109
+ return path.join(DEFAULT_CONFIG_DIR, DEFAULT_CONFIG_FILE);
1110
+ }
1111
+ function readConfig(customPath) {
1112
+ const configPath = getConfigPath(customPath);
1113
+ if (!fs.existsSync(configPath)) {
1114
+ return {
1115
+ default: {},
1116
+ profiles: {}
1117
+ };
1118
+ }
1119
+ try {
1120
+ const content = fs.readFileSync(configPath, "utf-8");
1121
+ return JSON.parse(content);
1122
+ } catch {
1123
+ return {
1124
+ default: {},
1125
+ profiles: {}
1126
+ };
1127
+ }
1128
+ }
1129
+ function resolveCredentials(options) {
1130
+ const config = readConfig(options.config);
1131
+ const result = {
1132
+ token: config.default.token,
1133
+ host: config.default.host,
1134
+ project: config.default.project
1135
+ };
1136
+ if (options.profile && config.profiles[options.profile]) {
1137
+ const profile = config.profiles[options.profile];
1138
+ if (profile.token) result.token = profile.token;
1139
+ if (profile.host) result.host = profile.host;
1140
+ if (profile.project) result.project = profile.project;
1141
+ }
1142
+ if (process.env.NETEASE_TOKEN) result.token = process.env.NETEASE_TOKEN;
1143
+ if (process.env.NETEASE_HOST) result.host = process.env.NETEASE_HOST;
1144
+ if (process.env.NETEASE_PROJECT) result.project = process.env.NETEASE_PROJECT;
1145
+ if (options.token) result.token = options.token;
1146
+ if (options.host) result.host = options.host;
1147
+ if (options.project) result.project = options.project;
1148
+ return result;
1149
+ }
1150
+ function validateCredentials(creds, requiredFields = ["token", "host", "project"]) {
1151
+ const missing = [];
1152
+ for (const field of requiredFields) {
1153
+ if (!creds[field]) {
1154
+ missing.push(field);
1155
+ }
1156
+ }
1157
+ return {
1158
+ valid: missing.length === 0,
1159
+ missing
1160
+ };
1161
+ }
1162
+
1163
+ // src/utils/url-parser.ts
1164
+ var PM_URL_PATH_PATTERN = /https?:\/\/([^/]+\.pm\.netease\.com)\/v6\/issues\/(\d+)/;
1165
+ var PM_URL_QUERY_PATTERN = /https?:\/\/([^/]+\.pm\.netease\.com)\/v6\/issues\?/;
1166
+ function parsePmLink(url) {
1167
+ const pathMatch = url.match(PM_URL_PATH_PATTERN);
1168
+ if (pathMatch) {
1169
+ return {
1170
+ host: pathMatch[1],
1171
+ issueId: pathMatch[2]
1172
+ };
1173
+ }
1174
+ const queryMatch = url.match(PM_URL_QUERY_PATTERN);
1175
+ if (queryMatch) {
1176
+ const host = queryMatch[1];
1177
+ const issueIdMatch = url.match(/[?&]issue_id=(\d+)/);
1178
+ if (issueIdMatch) {
1179
+ return {
1180
+ host,
1181
+ issueId: issueIdMatch[1]
1182
+ };
1183
+ }
1184
+ }
1185
+ return null;
1186
+ }
1187
+ function isPmLink(str) {
1188
+ return PM_URL_PATH_PATTERN.test(str) || PM_URL_QUERY_PATTERN.test(str);
1189
+ }
1190
+ function extractPmLinks(text) {
1191
+ const results = [];
1192
+ const urlPattern = /https?:\/\/[^\s]+\.pm\.netease\.com\/v6\/issues[^\s]*/g;
1193
+ const urls = text.match(urlPattern) || [];
1194
+ for (const url of urls) {
1195
+ const info = parsePmLink(url);
1196
+ if (info) {
1197
+ results.push(info);
1198
+ }
1199
+ }
1200
+ return results;
1201
+ }
1202
+ export {
1203
+ ApiClient,
1204
+ IssueService,
1205
+ TimeEntryService,
1206
+ UserService,
1207
+ apiClient,
1208
+ extractPmLinks,
1209
+ isPmLink,
1210
+ issueService,
1211
+ parsePmLink,
1212
+ resolveCredentials,
1213
+ setLogLevel,
1214
+ timeEntryService,
1215
+ userService,
1216
+ validateCredentials
1217
+ };
1218
+ //# sourceMappingURL=api.js.map