@hecom/codearts 0.2.3 → 0.3.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.
@@ -1,9 +1,14 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.workHourCommand = workHourCommand;
7
+ const ora_1 = __importDefault(require("ora"));
4
8
  const holidays_1 = require("../config/holidays");
5
9
  const business_service_1 = require("../services/business.service");
6
10
  const config_loader_1 = require("../utils/config-loader");
11
+ const console_1 = require("../utils/console");
7
12
  const csv_writer_1 = require("../utils/csv-writer");
8
13
  const logger_1 = require("../utils/logger");
9
14
  /**
@@ -16,30 +21,29 @@ function roundToTwo(num) {
16
21
  * 查询年度工时数据
17
22
  */
18
23
  async function queryWorkHourReportData(businessService, projectId, roleIds, targetYear) {
19
- const allMembers = [];
24
+ const members = await businessService.getMembersByRoleIds(projectId, roleIds);
25
+ const userIds = members.map((member) => member.user_id);
26
+ const stats = await businessService.getAllWorkHourStats(projectId, userIds, `${targetYear}-01-01`, `${targetYear}-12-31`);
27
+ // 创建用户ID到成员信息的映射
28
+ const userIdToMember = new Map();
29
+ members.forEach((member) => {
30
+ userIdToMember.set(member.user_id, member);
31
+ });
20
32
  const allUserStats = [];
21
33
  const allTypes = new Set();
22
- for (const roleId of roleIds) {
23
- const roleMembers = await businessService.getMembersByRoleId(projectId, roleId);
24
- if (roleMembers.length === 0) {
25
- logger_1.logger.warn(`角色ID ${roleId} 未找到用户,跳过`);
26
- continue;
27
- }
28
- const roleName = roleMembers[0].role_name;
29
- allMembers.push(...roleMembers);
30
- const roleUserIds = roleMembers.map((member) => member.user_id);
31
- const stats = await businessService.getAllWorkHourStats(projectId, roleUserIds, `${targetYear}-01-01`, `${targetYear}-12-31`);
32
- stats.userStats.forEach((userStat) => {
34
+ stats.userStats.forEach((userStat) => {
35
+ const member = userIdToMember.get(userStat.userId);
36
+ if (member) {
33
37
  allUserStats.push({
34
38
  ...userStat,
35
- roleName,
36
- roleId,
39
+ roleName: member.role_name,
40
+ roleId: member.role_id,
37
41
  });
38
42
  userStat.domainStats.forEach((domainStat) => {
39
43
  allTypes.add(domainStat.type);
40
44
  });
41
- });
42
- }
45
+ }
46
+ });
43
47
  allUserStats.sort((a, b) => {
44
48
  if (a.roleId !== b.roleId) {
45
49
  return a.roleId - b.roleId;
@@ -48,37 +52,10 @@ async function queryWorkHourReportData(businessService, projectId, roleIds, targ
48
52
  });
49
53
  const expectedWorkdays = (0, holidays_1.calculateExpectedWorkdays)(targetYear);
50
54
  const expectedHoursPerPerson = expectedWorkdays * 8;
51
- const totalExpectedHours = expectedHoursPerPerson * allMembers.length;
55
+ const totalExpectedHours = expectedHoursPerPerson * members.length;
52
56
  const totalHours = roundToTwo(allUserStats.reduce((sum, userStat) => sum + userStat.totalHours, 0));
53
- const totalEntries = allUserStats.reduce((sum, userStat) => sum + userStat.domainStats.reduce((s, d) => s + d.workHours.length, 0), 0);
54
57
  const userStatsData = [];
55
- const roleSubtotals = [];
56
- const typeTotals = {};
57
- allTypes.forEach((type) => {
58
- typeTotals[type] = 0;
59
- });
60
- let currentRoleId = null;
61
- let currentRoleName = '';
62
- const roleSubtotalStats = {};
63
- allUserStats.forEach((userStat, index) => {
64
- if (currentRoleId !== null && currentRoleId !== userStat.roleId) {
65
- const subtotalDomainStats = {};
66
- let subtotal = 0;
67
- allTypes.forEach((type) => {
68
- subtotalDomainStats[type] = roleSubtotalStats[type] || 0;
69
- subtotal = roundToTwo(subtotal + (roleSubtotalStats[type] || 0));
70
- });
71
- roleSubtotals.push({
72
- roleName: currentRoleName,
73
- domainStats: subtotalDomainStats,
74
- total: subtotal,
75
- });
76
- Object.keys(roleSubtotalStats).forEach((key) => {
77
- roleSubtotalStats[key] = 0;
78
- });
79
- }
80
- currentRoleId = userStat.roleId;
81
- currentRoleName = userStat.roleName;
58
+ allUserStats.forEach((userStat) => {
82
59
  const domainStats = {};
83
60
  allTypes.forEach((type) => {
84
61
  domainStats[type] = 0;
@@ -87,94 +64,54 @@ async function queryWorkHourReportData(businessService, projectId, roleIds, targ
87
64
  userStat.domainStats.forEach((domainStat) => {
88
65
  domainStats[domainStat.type] = domainStat.totalHours;
89
66
  userTotal = roundToTwo(userTotal + domainStat.totalHours);
90
- typeTotals[domainStat.type] = roundToTwo(typeTotals[domainStat.type] + domainStat.totalHours);
91
- if (!roleSubtotalStats[domainStat.type]) {
92
- roleSubtotalStats[domainStat.type] = 0;
93
- }
94
- roleSubtotalStats[domainStat.type] = roundToTwo(roleSubtotalStats[domainStat.type] + domainStat.totalHours);
95
67
  });
96
68
  userStatsData.push({
97
69
  userName: userStat.userName,
98
70
  roleName: userStat.roleName,
99
- roleId: userStat.roleId,
100
71
  domainStats,
101
72
  total: userTotal,
102
73
  });
103
- if (index === allUserStats.length - 1) {
104
- const subtotalDomainStats = {};
105
- let subtotal = 0;
106
- allTypes.forEach((type) => {
107
- subtotalDomainStats[type] = roleSubtotalStats[type] || 0;
108
- subtotal = roundToTwo(subtotal + (roleSubtotalStats[type] || 0));
109
- });
110
- roleSubtotals.push({
111
- roleName: currentRoleName,
112
- domainStats: subtotalDomainStats,
113
- total: subtotal,
114
- });
115
- }
116
- });
117
- const grandTotalDomainStats = {};
118
- let grandTotal = 0;
119
- allTypes.forEach((type) => {
120
- grandTotalDomainStats[type] = typeTotals[type];
121
- grandTotal = roundToTwo(grandTotal + typeTotals[type]);
122
74
  });
123
75
  return {
124
- year: targetYear,
125
- roleCount: roleIds.length,
126
- memberCount: allMembers.length,
127
- expectedWorkdays,
128
- expectedHours: totalExpectedHours,
129
- actualHours: totalHours,
130
- completionRate: (totalHours / totalExpectedHours) * 100,
131
- entryCount: totalEntries,
132
- userStats: userStatsData,
133
- roleSubtotals,
134
- grandTotal: {
135
- domainStats: grandTotalDomainStats,
136
- total: grandTotal,
137
- },
76
+ title: `${targetYear}年工时统计`,
77
+ roleNames: Array.from(new Set(members.map((m) => m.role_name))),
78
+ totalMap: [
79
+ ['统计人数', `${members.length} 人`],
80
+ ['应计工日', `${expectedWorkdays} 天`],
81
+ ['应计工时', `${totalExpectedHours} 小时`],
82
+ ['实际工时', `${totalHours} 小时`],
83
+ ['工时完成率', `${((totalHours / totalExpectedHours) * 100).toFixed(2)}%`],
84
+ ],
85
+ list: userStatsData,
138
86
  };
139
87
  }
140
88
  /**
141
89
  * 控制台输出工时统计
142
90
  */
143
91
  function outputConsole(data) {
144
- logger_1.logger.info(`\n${data.year}年工时统计 [${data.roleSubtotals.map((r) => r.roleName).join(', ')}]`);
145
- logger_1.logger.info('='.repeat(80));
146
- logger_1.logger.info(`统计人数: ${data.memberCount} 人`);
147
- logger_1.logger.info(`应计工作日: ${data.expectedWorkdays} 天`);
148
- logger_1.logger.info(`应计工时: ${data.expectedHours} 小时`);
149
- logger_1.logger.info(`实际工时: ${data.actualHours} 小时`);
150
- logger_1.logger.info(`工时完成率: ${data.completionRate.toFixed(2)}%`);
151
- logger_1.logger.info('='.repeat(80));
92
+ (0, console_1.consoleTotal)(data);
152
93
  // 构建表格数据
153
94
  const tableData = {};
154
- data.userStats.forEach((userStat) => {
95
+ data.list.forEach((userStat) => {
155
96
  const row = {
156
97
  ...userStat.domainStats,
157
98
  合计: userStat.total,
158
99
  };
159
100
  tableData[userStat.userName] = row;
160
101
  });
161
- // 添加总计行
162
- tableData['总计'] = {
163
- ...data.grandTotal.domainStats,
164
- 合计: data.grandTotal.total,
165
- };
166
102
  logger_1.logger.table(tableData);
167
103
  }
168
104
  /**
169
105
  * CSV 文件输出
170
106
  */
171
- function outputCsv(data, targetYear) {
172
- const domains = Object.keys(data.grandTotal.domainStats);
107
+ function outputCsv(list, targetYear) {
108
+ const domains = Object.keys(list[0].domainStats);
173
109
  const csvLines = [];
174
- csvLines.push(`用户,角色,${domains.join(',')},合计`);
175
- data.userStats.forEach((userStat) => {
110
+ const headerRow = ['用户', '角色', ...domains, '合计'];
111
+ csvLines.push((0, csv_writer_1.buildCsvRow)(headerRow));
112
+ list.forEach((userStat) => {
176
113
  const domainValues = domains.map((domain) => userStat.domainStats[domain] || 0);
177
- csvLines.push(`${userStat.userName},${userStat.roleName},${domainValues.join(',')},${userStat.total}`);
114
+ csvLines.push((0, csv_writer_1.buildCsvRow)([userStat.userName, userStat.roleName, ...domainValues, userStat.total]));
178
115
  });
179
116
  const filename = `work-hour-${targetYear}.csv`;
180
117
  (0, csv_writer_1.writeCsvFile)(filename, csvLines, logger_1.logger);
@@ -189,23 +126,19 @@ function outputJson(data) {
189
126
  * work-hour 命令入口
190
127
  */
191
128
  async function workHourCommand(year, cliOptions = {}) {
192
- try {
193
- const targetYear = year || new Date().getFullYear().toString();
194
- const { projectId, roleIds, config, outputFormat } = (0, config_loader_1.loadConfig)(cliOptions);
195
- const businessService = new business_service_1.BusinessService(config);
196
- const reportData = await queryWorkHourReportData(businessService, projectId, roleIds, targetYear);
197
- if (outputFormat === 'console') {
198
- outputConsole(reportData);
199
- }
200
- else if (outputFormat === 'csv') {
201
- outputCsv(reportData, targetYear);
202
- }
203
- else if (outputFormat === 'json') {
204
- outputJson(reportData);
205
- }
129
+ const spinner = (0, ora_1.default)('正在查询数据...').start();
130
+ const targetYear = year || new Date().getFullYear().toString();
131
+ const { projectId, roleIds, config, outputFormat } = (0, config_loader_1.loadConfig)(cliOptions);
132
+ const businessService = new business_service_1.BusinessService(config);
133
+ const reportData = await queryWorkHourReportData(businessService, projectId, roleIds, targetYear);
134
+ spinner.stop();
135
+ if (outputFormat === 'console') {
136
+ outputConsole(reportData);
137
+ }
138
+ else if (outputFormat === 'csv') {
139
+ outputCsv(reportData.list, targetYear);
206
140
  }
207
- catch (error) {
208
- logger_1.logger.error(`执行过程中发生错误:`, error);
209
- process.exit(1);
141
+ else if (outputFormat === 'json') {
142
+ outputJson(reportData.list);
210
143
  }
211
144
  }
@@ -1,5 +1,4 @@
1
1
  import { AllWorkHourStats, HuaweiCloudConfig, IssueItem, IssueItemV2, IterationInfo, ProjectMember, ProjectRole, WorkHourStats, WorkProgressStats } from '../types';
2
- import { ApiService } from './api.service';
3
2
  /**
4
3
  * 业务服务类
5
4
  * 提供面向业务场景的高级操作,封装ApiService的底层调用
@@ -7,24 +6,28 @@ import { ApiService } from './api.service';
7
6
  export declare class BusinessService {
8
7
  private apiService;
9
8
  constructor(config: HuaweiCloudConfig);
10
- /**
11
- * 获取底层ApiService实例
12
- * 用于需要直接访问API服务的场景
13
- */
14
- getApiService(): ApiService;
15
9
  /**
16
10
  * 通过角色ID获取项目成员
17
11
  * @param projectId 项目ID
18
- * @param roleId 角色ID
12
+ * @param roleIds 角色ID列表
19
13
  * @returns 指定角色的成员列表
20
14
  */
21
- getMembersByRoleId(projectId: string, roleId: number): Promise<ProjectMember[]>;
15
+ getMembersByRoleIds(projectId: string, roleIds: number[]): Promise<ProjectMember[]>;
22
16
  /**
23
17
  * 获取项目中的所有角色列表(去重)
24
18
  * @param projectId 项目ID
25
19
  * @returns 项目中的所有角色列表
26
20
  */
27
21
  getProjectRoles(projectId: string): Promise<ProjectRole[]>;
22
+ /**
23
+ * 获取项目的迭代列表
24
+ * @param projectId 项目ID
25
+ * @param options 可选参数
26
+ * @returns 迭代列表
27
+ */
28
+ getIterations(projectId: string, options?: {
29
+ limit: number;
30
+ }): Promise<IterationInfo[]>;
28
31
  /**
29
32
  * 获取指定日期之后的迭代列表
30
33
  * @param projectId 项目ID
@@ -40,14 +43,6 @@ export declare class BusinessService {
40
43
  * @returns Task类型的工作项列表
41
44
  */
42
45
  getWorkloadByIterationsAndUsers(projectId: string, iterationIds: number[], userIds: string[]): Promise<IssueItem[]>;
43
- /**
44
- * 根据迭代ID和用户ID列表查询工作量列表(仅Task和Story)
45
- * @param projectId 项目ID
46
- * @param iterationId 迭代ID
47
- * @param userIds 用户ID列表
48
- * @returns Task和Story类型的工作项列表
49
- */
50
- getWorkloadByIterationAndUsers(projectId: string, iterationId: number, userIds: string[]): Promise<IssueItem[]>;
51
46
  /**
52
47
  * 统计工作项进度信息
53
48
  * @param issues 工作项列表
@@ -70,12 +65,12 @@ export declare class BusinessService {
70
65
  */
71
66
  getIterationsByTitles(projectId: string, iterationTitles: string[]): Promise<IterationInfo[]>;
72
67
  /**
73
- * 根据迭代标题获取所有 Story
68
+ * 根据迭代信息获取所有 Story
74
69
  * @param projectId 项目ID
75
- * @param iterationTitles 迭代标题列表
70
+ * @param iterations 迭代信息列表
76
71
  * @returns Story 类型的工作项列表
77
72
  */
78
- getStoriesByIterationTitles(projectId: string, iterationTitles: string[]): Promise<IssueItem[]>;
73
+ getStoriesByIterations(projectId: string, iterations: IterationInfo[], userIds?: string[]): Promise<IssueItem[]>;
79
74
  /**
80
75
  * 查询指定用户在指定时间段内的所有工时统计(按人和领域分组)
81
76
  * @param projectId 项目ID
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.BusinessService = void 0;
4
+ const types_1 = require("../types");
4
5
  const api_service_1 = require("./api.service");
5
6
  /**
6
7
  * 业务服务类
@@ -10,27 +11,21 @@ class BusinessService {
10
11
  constructor(config) {
11
12
  this.apiService = new api_service_1.ApiService(config);
12
13
  }
13
- /**
14
- * 获取底层ApiService实例
15
- * 用于需要直接访问API服务的场景
16
- */
17
- getApiService() {
18
- return this.apiService;
19
- }
20
14
  // 业务操作方法将在后续添加
21
15
  /**
22
16
  * 通过角色ID获取项目成员
23
17
  * @param projectId 项目ID
24
- * @param roleId 角色ID
18
+ * @param roleIds 角色ID列表
25
19
  * @returns 指定角色的成员列表
26
20
  */
27
- async getMembersByRoleId(projectId, roleId) {
21
+ async getMembersByRoleIds(projectId, roleIds) {
28
22
  const membersResponse = await this.apiService.getMembers(projectId);
29
23
  if (!membersResponse.success) {
30
24
  throw new Error(`获取成员列表失败: ${membersResponse.error || '未知错误'}`);
31
25
  }
32
26
  const allMembers = membersResponse.data?.members || [];
33
- return allMembers.filter((member) => member.role_id === roleId);
27
+ const roleIdsSet = new Set(roleIds);
28
+ return allMembers.filter((member) => roleIdsSet.has(member.role_id));
34
29
  }
35
30
  /**
36
31
  * 获取项目中的所有角色列表(去重)
@@ -71,6 +66,24 @@ class BusinessService {
71
66
  // 转换为数组并按 role_id 排序
72
67
  return Array.from(rolesMap.values()).sort((a, b) => a.role_id - b.role_id);
73
68
  }
69
+ /**
70
+ * 获取项目的迭代列表
71
+ * @param projectId 项目ID
72
+ * @param options 可选参数
73
+ * @returns 迭代列表
74
+ */
75
+ async getIterations(projectId, options = { limit: 12 }) {
76
+ const iterationsResponse = await this.apiService.getIterations(projectId, {
77
+ include_deleted: false,
78
+ });
79
+ if (!iterationsResponse.success) {
80
+ throw new Error(`获取迭代列表失败: ${iterationsResponse.error || '未知错误'}`);
81
+ }
82
+ const list = (iterationsResponse.data?.iterations?.length || 0) > options.limit
83
+ ? iterationsResponse.data?.iterations?.slice(0, options.limit) || []
84
+ : iterationsResponse.data?.iterations || [];
85
+ return list;
86
+ }
74
87
  /**
75
88
  * 获取指定日期之后的迭代列表
76
89
  * @param projectId 项目ID
@@ -89,7 +102,7 @@ class BusinessService {
89
102
  // 过滤出在目标日期正在进行中的迭代
90
103
  return iterations.filter((iteration) => {
91
104
  // 检查迭代状态是否为进行中 (1)
92
- if (iteration.status !== '1') {
105
+ if (iteration.status !== types_1.IterationStatus.IN_PROGRESS) {
93
106
  return false;
94
107
  }
95
108
  // 检查目标日期是否在迭代时间范围内
@@ -121,27 +134,6 @@ class BusinessService {
121
134
  }
122
135
  return issuesResponse.data?.issues || [];
123
136
  }
124
- /**
125
- * 根据迭代ID和用户ID列表查询工作量列表(仅Task和Story)
126
- * @param projectId 项目ID
127
- * @param iterationId 迭代ID
128
- * @param userIds 用户ID列表
129
- * @returns Task和Story类型的工作项列表
130
- */
131
- async getWorkloadByIterationAndUsers(projectId, iterationId, userIds) {
132
- const issuesResponse = await this.apiService.getIssues(projectId, {
133
- iteration_ids: [iterationId],
134
- tracker_ids: [2, 7], // 2=Task(任务), 7=Story
135
- assigned_ids: userIds,
136
- include_deleted: false,
137
- limit: 100,
138
- offset: 0,
139
- });
140
- if (!issuesResponse.success) {
141
- throw new Error(`获取工作项列表失败: ${issuesResponse.error || '未知错误'}`);
142
- }
143
- return issuesResponse.data?.issues || [];
144
- }
145
137
  /**
146
138
  * 统计工作项进度信息
147
139
  * @param issues 工作项列表
@@ -256,14 +248,12 @@ class BusinessService {
256
248
  return iterations.filter((iteration) => iterationTitles.includes(iteration.name));
257
249
  }
258
250
  /**
259
- * 根据迭代标题获取所有 Story
251
+ * 根据迭代信息获取所有 Story
260
252
  * @param projectId 项目ID
261
- * @param iterationTitles 迭代标题列表
253
+ * @param iterations 迭代信息列表
262
254
  * @returns Story 类型的工作项列表
263
255
  */
264
- async getStoriesByIterationTitles(projectId, iterationTitles) {
265
- // Step 1: 根据标题获取迭代信息
266
- const iterations = await this.getIterationsByTitles(projectId, iterationTitles);
256
+ async getStoriesByIterations(projectId, iterations, userIds) {
267
257
  if (iterations.length === 0) {
268
258
  return [];
269
259
  }
@@ -277,6 +267,7 @@ class BusinessService {
277
267
  while (hasMore) {
278
268
  const issuesResponse = await this.apiService.getIssues(projectId, {
279
269
  iteration_ids: iterationIds,
270
+ assigned_ids: userIds,
280
271
  tracker_ids: [7], // 7=Story
281
272
  include_deleted: false,
282
273
  limit: pageSize,
@@ -305,6 +305,12 @@ export interface ListProjectIterationsV4Request {
305
305
  updated_time_interval?: string;
306
306
  include_deleted?: boolean;
307
307
  }
308
+ export declare enum IterationStatus {
309
+ OPEN = "open",// 老数据默认状态
310
+ NOT_STARTED = "0",// 未启动
311
+ IN_PROGRESS = "1",// 进行中
312
+ COMPLETED = "2"
313
+ }
308
314
  export interface IterationInfo {
309
315
  begin_time: string;
310
316
  deleted: boolean;
@@ -312,7 +318,7 @@ export interface IterationInfo {
312
318
  end_time: string;
313
319
  id: number;
314
320
  name: string;
315
- status: string;
321
+ status: IterationStatus;
316
322
  updated_time: number;
317
323
  }
318
324
  export interface ListProjectIterationsV4Response {
@@ -539,14 +545,6 @@ export declare enum ConfigKey {
539
545
  PROJECT_ID = "PROJECT_ID",
540
546
  ROLE_ID = "ROLE_ID"
541
547
  }
542
- /**
543
- * 不可变配置键(只能通过 config 命令设置)
544
- */
545
- export declare const IMMUTABLE_CONFIG_KEYS: ConfigKey[];
546
- /**
547
- * 可变配置键(可以通过命令行参数覆盖)
548
- */
549
- export declare const MUTABLE_CONFIG_KEYS: ConfigKey[];
550
548
  /**
551
549
  * 类型安全的配置映射
552
550
  */
@@ -560,11 +558,13 @@ export type ConfigMap = {
560
558
  [ConfigKey.PROJECT_ID]: string;
561
559
  [ConfigKey.ROLE_ID]: string;
562
560
  };
563
- /**
564
- * 部分配置映射(用于读取配置文件)
565
- */
566
- export type PartialConfigMap = Partial<ConfigMap>;
567
561
  /**
568
562
  * 输出格式类型
569
563
  */
570
564
  export type OutputFormat = 'console' | 'csv' | 'json';
565
+ export interface ConsoleTotal<T> {
566
+ title: string;
567
+ roleNames: string[];
568
+ totalMap: [string, string | (() => string)][];
569
+ list: T[];
570
+ }
@@ -1,6 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.MUTABLE_CONFIG_KEYS = exports.IMMUTABLE_CONFIG_KEYS = exports.ConfigKey = exports.DefectAnalysisType = void 0;
3
+ exports.ConfigKey = exports.DefectAnalysisType = exports.IterationStatus = void 0;
4
+ var IterationStatus;
5
+ (function (IterationStatus) {
6
+ IterationStatus["OPEN"] = "open";
7
+ IterationStatus["NOT_STARTED"] = "0";
8
+ IterationStatus["IN_PROGRESS"] = "1";
9
+ IterationStatus["COMPLETED"] = "2";
10
+ })(IterationStatus || (exports.IterationStatus = IterationStatus = {}));
4
11
  /**
5
12
  * 缺陷技术分析选项枚举
6
13
  * custom_field32(缺陷技术分析)字段的选项值枚举定义
@@ -40,19 +47,3 @@ var ConfigKey;
40
47
  // 可变配置(可以通过命令行参数覆盖)
41
48
  ConfigKey["ROLE_ID"] = "ROLE_ID";
42
49
  })(ConfigKey || (exports.ConfigKey = ConfigKey = {}));
43
- /**
44
- * 不可变配置键(只能通过 config 命令设置)
45
- */
46
- exports.IMMUTABLE_CONFIG_KEYS = [
47
- ConfigKey.HUAWEI_CLOUD_IAM_ENDPOINT,
48
- ConfigKey.HUAWEI_CLOUD_REGION,
49
- ConfigKey.HUAWEI_CLOUD_USERNAME,
50
- ConfigKey.HUAWEI_CLOUD_PASSWORD,
51
- ConfigKey.HUAWEI_CLOUD_DOMAIN,
52
- ConfigKey.CODEARTS_BASE_URL,
53
- ConfigKey.PROJECT_ID,
54
- ];
55
- /**
56
- * 可变配置键(可以通过命令行参数覆盖)
57
- */
58
- exports.MUTABLE_CONFIG_KEYS = [ConfigKey.ROLE_ID];
@@ -1,4 +1,4 @@
1
- import { HuaweiCloudConfig, OutputFormat, PartialConfigMap } from '../types';
1
+ import { ConfigMap, HuaweiCloudConfig, OutputFormat } from '../types';
2
2
  /**
3
3
  * 获取全局配置文件路径
4
4
  */
@@ -10,18 +10,18 @@ export declare function configExists(): boolean;
10
10
  /**
11
11
  * 读取全局配置
12
12
  */
13
- export declare function readConfig(): PartialConfigMap;
13
+ export declare function readConfig(): Partial<ConfigMap>;
14
14
  /**
15
15
  * 写入全局配置
16
16
  * 支持动态配置项,自动按分组组织配置文件
17
17
  */
18
- export declare function writeConfig(config: PartialConfigMap): void;
18
+ export declare function writeConfig(config: Partial<ConfigMap>): void;
19
19
  /**
20
20
  * 删除全局配置
21
21
  */
22
22
  export declare function deleteConfig(): void;
23
23
  export interface CliOptions {
24
- roleId?: string;
24
+ role?: string;
25
25
  output?: string;
26
26
  report?: boolean;
27
27
  }
@@ -41,4 +41,4 @@ export declare function loadConfig(cliOptions?: CliOptions): LoadedConfig;
41
41
  * 获取最终合并后的配置(用于显示)
42
42
  * @returns 合并后的配置映射
43
43
  */
44
- export declare function getConfig(): PartialConfigMap;
44
+ export declare function getConfig(): Partial<ConfigMap>;
@@ -181,7 +181,7 @@ const globalConfig = configExists() ? readConfig() : {};
181
181
  function loadConfig(cliOptions = {}) {
182
182
  // 命令行参数 > 全局配置
183
183
  const projectId = globalConfig[types_1.ConfigKey.PROJECT_ID];
184
- const roleIdStr = cliOptions.roleId || globalConfig[types_1.ConfigKey.ROLE_ID];
184
+ const roleIdStr = cliOptions.role || globalConfig[types_1.ConfigKey.ROLE_ID];
185
185
  if (!projectId) {
186
186
  throw new Error('缺少项目 ID');
187
187
  }
@@ -0,0 +1,4 @@
1
+ import { ConsoleTotal } from '../types';
2
+ export declare function showLogo(): void;
3
+ export declare function consoleTotal<T>(total: ConsoleTotal<T>): void;
4
+ export declare function issueLink(projectId: string, issueId: string | number): string;
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.showLogo = showLogo;
4
+ exports.consoleTotal = consoleTotal;
5
+ exports.issueLink = issueLink;
4
6
  const logger_1 = require("../utils/logger");
5
7
  const LOGO_LINES = [
6
8
  '██╗ ██╗███████╗ ██████╗ ██████╗ ███╗ ███╗',
@@ -27,3 +29,19 @@ function showLogo() {
27
29
  });
28
30
  logger_1.logger.info();
29
31
  }
32
+ function consoleTotal(total) {
33
+ logger_1.logger.info(`${total.title} [${total.roleNames.join(', ')}]`);
34
+ logger_1.logger.info('='.repeat(80));
35
+ total.totalMap.forEach(([key, value]) => {
36
+ if (typeof value === 'function') {
37
+ logger_1.logger.info(`${key}: ${value()}`);
38
+ }
39
+ else {
40
+ logger_1.logger.info(`${key}: ${value}`);
41
+ }
42
+ });
43
+ logger_1.logger.info('='.repeat(80));
44
+ }
45
+ function issueLink(projectId, issueId) {
46
+ return `https://devcloud.cn-north-4.huaweicloud.com/projectman/scrum/${projectId}/task/detail/${issueId}`;
47
+ }
@@ -1,8 +1,17 @@
1
1
  import { Logger } from './logger';
2
2
  /**
3
- * CSV 转义函数:处理双引号
3
+ * 构建 CSV
4
+ * @param fields CSV 字段数组
5
+ * @returns CSV 行字符串
4
6
  */
5
- export declare function escapeCsv(value: string): string;
7
+ export declare function buildCsvRow(fields: (string | number | null | undefined)[]): string;
8
+ /**
9
+ * 创建 Excel HYPERLINK 公式
10
+ * @param url 链接地址
11
+ * @param displayText 显示文本
12
+ * @returns Excel HYPERLINK 公式字符串
13
+ */
14
+ export declare function createHyperlinkFormula(url: string, displayText: string): string;
6
15
  /**
7
16
  * 写入 CSV 文件到当前工作目录
8
17
  * @param filename 文件名