@hecom/codearts 0.2.2 → 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.
- package/README.md +1 -1
- package/dist/bin/cli.js +20 -5
- package/dist/commands/bug.command.d.ts +1 -1
- package/dist/commands/bug.command.js +78 -96
- package/dist/commands/config.command.js +133 -166
- package/dist/commands/daily.command.js +166 -158
- package/dist/commands/work-hour.command.js +53 -120
- package/dist/services/business.service.d.ts +14 -19
- package/dist/services/business.service.js +28 -37
- package/dist/types/index.d.ts +13 -13
- package/dist/types/index.js +8 -17
- package/dist/utils/config-loader.d.ts +5 -5
- package/dist/utils/config-loader.js +1 -1
- package/dist/utils/console.d.ts +4 -0
- package/dist/utils/console.js +47 -0
- package/dist/utils/csv-writer.d.ts +11 -2
- package/dist/utils/csv-writer.js +37 -5
- package/dist/utils/inquirer-theme.d.ts +13 -0
- package/dist/utils/inquirer-theme.js +29 -0
- package/dist/utils/logger.d.ts +1 -2
- package/dist/utils/logger.js +6 -1
- package/package.json +8 -5
- package/dist/index.d.ts +0 -3
- package/dist/index.js +0 -23
|
@@ -40,11 +40,14 @@ exports.configCommand = configCommand;
|
|
|
40
40
|
exports.updateProjectConfigCommand = updateProjectConfigCommand;
|
|
41
41
|
exports.getAvailableProjectConfigs = getAvailableProjectConfigs;
|
|
42
42
|
exports.showConfigCommand = showConfigCommand;
|
|
43
|
-
const
|
|
43
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
44
|
+
const ora_1 = __importDefault(require("ora"));
|
|
45
|
+
const picocolors_1 = __importDefault(require("picocolors"));
|
|
44
46
|
const readline = __importStar(require("readline"));
|
|
45
47
|
const business_service_1 = require("../services/business.service");
|
|
46
48
|
const types_1 = require("../types");
|
|
47
49
|
const config_loader_1 = require("../utils/config-loader");
|
|
50
|
+
const inquirer_theme_1 = require("../utils/inquirer-theme");
|
|
48
51
|
const logger_1 = require("../utils/logger");
|
|
49
52
|
/**
|
|
50
53
|
* 清除终端上指定行数的内容
|
|
@@ -56,76 +59,58 @@ function clearLines(lines) {
|
|
|
56
59
|
readline.clearLine(process.stdout, 0); // 清除当前行
|
|
57
60
|
}
|
|
58
61
|
}
|
|
62
|
+
async function inputRoleIds(existingValue) {
|
|
63
|
+
return await (0, prompts_1.input)({
|
|
64
|
+
message: '角色 ID(支持逗号分隔,如: 1,2,3):',
|
|
65
|
+
default: existingValue || '',
|
|
66
|
+
validate: (inputValue) => {
|
|
67
|
+
if (!inputValue.trim()) {
|
|
68
|
+
return '角色 ID 不能为空';
|
|
69
|
+
}
|
|
70
|
+
const ids = inputValue.split(',').map((id) => id.trim());
|
|
71
|
+
const allValid = ids.every((id) => /^\d+$/.test(id));
|
|
72
|
+
return allValid ? true : '角色 ID 必须是数字或逗号分隔的数字列表';
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
}
|
|
59
76
|
/**
|
|
60
77
|
* 配置角色 ID
|
|
61
78
|
*/
|
|
62
79
|
async function configureRoleIds(businessService, projectId, existingValue) {
|
|
80
|
+
let roles = [];
|
|
63
81
|
try {
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const { manualRoleId } = await inquirer_1.default.prompt([
|
|
68
|
-
{
|
|
69
|
-
type: 'input',
|
|
70
|
-
name: 'manualRoleId',
|
|
71
|
-
message: '角色 ID(支持逗号分隔,如: 1,2,3):',
|
|
72
|
-
default: existingValue || '',
|
|
73
|
-
validate: (input) => {
|
|
74
|
-
if (!input.trim()) {
|
|
75
|
-
return '角色 ID 不能为空';
|
|
76
|
-
}
|
|
77
|
-
const ids = input.split(',').map((id) => id.trim());
|
|
78
|
-
const allValid = ids.every((id) => /^\d+$/.test(id));
|
|
79
|
-
return allValid ? true : '角色 ID 必须是数字或逗号分隔的数字列表';
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
]);
|
|
83
|
-
return manualRoleId;
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
// 使用多选框选择角色
|
|
87
|
-
const roleChoices = roles.map((role) => ({
|
|
88
|
-
name: `${role.role_name} (${role.role_id})`,
|
|
89
|
-
value: role.role_id.toString(),
|
|
90
|
-
checked: existingValue ? existingValue.split(',').includes(role.role_id.toString()) : false,
|
|
91
|
-
}));
|
|
92
|
-
const { selectedRoleIds } = await inquirer_1.default.prompt([
|
|
93
|
-
{
|
|
94
|
-
type: 'checkbox',
|
|
95
|
-
name: 'selectedRoleIds',
|
|
96
|
-
message: '请选择角色:',
|
|
97
|
-
choices: roleChoices,
|
|
98
|
-
validate: (input) => {
|
|
99
|
-
if (input.length === 0) {
|
|
100
|
-
return '至少选择一个角色';
|
|
101
|
-
}
|
|
102
|
-
return true;
|
|
103
|
-
},
|
|
104
|
-
},
|
|
105
|
-
]);
|
|
106
|
-
return selectedRoleIds.join(',');
|
|
107
|
-
}
|
|
82
|
+
const spinner = (0, ora_1.default)('正在获取角色列表...').start();
|
|
83
|
+
roles = await businessService.getProjectRoles(projectId);
|
|
84
|
+
spinner.stop();
|
|
108
85
|
}
|
|
109
86
|
catch (error) {
|
|
110
87
|
logger_1.logger.error('❌ 获取角色列表失败:', error);
|
|
111
88
|
// 如果获取失败,使用手动输入
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
89
|
+
return await inputRoleIds(existingValue);
|
|
90
|
+
}
|
|
91
|
+
if (roles.length === 0) {
|
|
92
|
+
// 如果没有获取到角色,使用手动输入
|
|
93
|
+
return await inputRoleIds(existingValue);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
// 使用多选框选择角色
|
|
97
|
+
const roleChoices = roles.map((role) => ({
|
|
98
|
+
name: `${role.role_name} (${role.role_id})`,
|
|
99
|
+
value: role.role_id.toString(),
|
|
100
|
+
checked: existingValue ? existingValue.split(',').includes(role.role_id.toString()) : false,
|
|
101
|
+
}));
|
|
102
|
+
const selectedRoleIds = await (0, prompts_1.checkbox)({
|
|
103
|
+
message: '请选择角色:',
|
|
104
|
+
choices: roleChoices,
|
|
105
|
+
validate: (answer) => {
|
|
106
|
+
if (answer.length === 0) {
|
|
107
|
+
return '至少需要选择一个角色';
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
126
110
|
},
|
|
127
|
-
|
|
128
|
-
|
|
111
|
+
theme: inquirer_theme_1.globalTheme,
|
|
112
|
+
});
|
|
113
|
+
return selectedRoleIds.join(',');
|
|
129
114
|
}
|
|
130
115
|
}
|
|
131
116
|
/**
|
|
@@ -145,25 +130,28 @@ const PROJECT_CONFIG_ITEMS = [
|
|
|
145
130
|
// configure: configureCustomField,
|
|
146
131
|
// },
|
|
147
132
|
];
|
|
133
|
+
async function inputPassword() {
|
|
134
|
+
return await (0, prompts_1.password)({
|
|
135
|
+
message: 'IAM 密码:',
|
|
136
|
+
mask: '*',
|
|
137
|
+
validate: (inputValue) => (inputValue.trim() ? true : 'IAM 密码不能为空'),
|
|
138
|
+
});
|
|
139
|
+
}
|
|
148
140
|
/**
|
|
149
141
|
* 交互式配置向导命令
|
|
150
142
|
* 引导用户创建或更新全局配置文件
|
|
151
143
|
*/
|
|
152
144
|
async function configCommand() {
|
|
153
|
-
logger_1.logger.info('
|
|
145
|
+
logger_1.logger.info('欢迎使用 Hecom CodeArts 配置向导');
|
|
154
146
|
logger_1.logger.info('='.repeat(60));
|
|
155
|
-
logger_1.logger.info('此向导将帮助您配置华为云 CodeArts API
|
|
147
|
+
logger_1.logger.info('此向导将帮助您配置华为云 CodeArts API 访问凭证以及项目相关设置');
|
|
156
148
|
logger_1.logger.info(`配置将保存到: ${(0, config_loader_1.getConfigPath)()}\n`);
|
|
157
149
|
const existingConfig = (0, config_loader_1.configExists)() ? (0, config_loader_1.readConfig)() : {};
|
|
158
150
|
if ((0, config_loader_1.configExists)()) {
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
message: '检测到已存在全局配置,是否覆盖?',
|
|
164
|
-
default: false,
|
|
165
|
-
},
|
|
166
|
-
]);
|
|
151
|
+
const overwrite = await (0, prompts_1.confirm)({
|
|
152
|
+
message: '检测到已存在全局配置,是否覆盖?',
|
|
153
|
+
default: true,
|
|
154
|
+
});
|
|
167
155
|
if (!overwrite) {
|
|
168
156
|
logger_1.logger.info('\n已取消配置。');
|
|
169
157
|
return;
|
|
@@ -181,53 +169,52 @@ async function configCommand() {
|
|
|
181
169
|
password: '',
|
|
182
170
|
};
|
|
183
171
|
while (!credentialsValid) {
|
|
184
|
-
iamAnswers =
|
|
185
|
-
{
|
|
186
|
-
type: 'input',
|
|
187
|
-
name: 'iamEndpoint',
|
|
172
|
+
iamAnswers = {
|
|
173
|
+
iamEndpoint: await (0, prompts_1.input)({
|
|
188
174
|
message: 'IAM 认证端点:',
|
|
189
175
|
default: existingConfig[types_1.ConfigKey.HUAWEI_CLOUD_IAM_ENDPOINT] ||
|
|
190
176
|
'https://iam.cn-north-4.myhuaweicloud.com',
|
|
191
|
-
validate: (
|
|
192
|
-
},
|
|
193
|
-
{
|
|
194
|
-
type: 'input',
|
|
195
|
-
name: 'region',
|
|
177
|
+
validate: (inputValue) => (inputValue.trim() ? true : 'IAM 认证端点不能为空'),
|
|
178
|
+
}),
|
|
179
|
+
region: await (0, prompts_1.input)({
|
|
196
180
|
message: '华为云区域:',
|
|
197
181
|
default: existingConfig[types_1.ConfigKey.HUAWEI_CLOUD_REGION] || 'cn-north-4',
|
|
198
|
-
validate: (
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
type: 'input',
|
|
202
|
-
name: 'codeartsUrl',
|
|
182
|
+
validate: (inputValue) => (inputValue.trim() ? true : '华为云区域不能为空'),
|
|
183
|
+
}),
|
|
184
|
+
codeartsUrl: await (0, prompts_1.input)({
|
|
203
185
|
message: 'CodeArts API 地址:',
|
|
204
186
|
default: existingConfig[types_1.ConfigKey.CODEARTS_BASE_URL] ||
|
|
205
187
|
'https://projectman-ext.cn-north-4.myhuaweicloud.cn',
|
|
206
|
-
validate: (
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
type: 'input',
|
|
210
|
-
name: 'domain',
|
|
188
|
+
validate: (inputValue) => (inputValue.trim() ? true : 'CodeArts API 地址不能为空'),
|
|
189
|
+
}),
|
|
190
|
+
domain: await (0, prompts_1.input)({
|
|
211
191
|
message: '华为云账号名:',
|
|
212
192
|
default: existingConfig[types_1.ConfigKey.HUAWEI_CLOUD_DOMAIN] || '',
|
|
213
|
-
validate: (
|
|
214
|
-
},
|
|
215
|
-
{
|
|
216
|
-
type: 'input',
|
|
217
|
-
name: 'username',
|
|
193
|
+
validate: (inputValue) => (inputValue.trim() ? true : '华为云账号名不能为空'),
|
|
194
|
+
}),
|
|
195
|
+
username: await (0, prompts_1.input)({
|
|
218
196
|
message: 'IAM 用户名:',
|
|
219
197
|
default: existingConfig[types_1.ConfigKey.HUAWEI_CLOUD_USERNAME] || '',
|
|
220
|
-
validate: (
|
|
221
|
-
},
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
198
|
+
validate: (inputValue) => (inputValue.trim() ? true : 'IAM 用户名不能为空'),
|
|
199
|
+
}),
|
|
200
|
+
password: '',
|
|
201
|
+
};
|
|
202
|
+
// 处理密码:如果存在旧密码,询问是否重用
|
|
203
|
+
if (existingConfig[types_1.ConfigKey.HUAWEI_CLOUD_PASSWORD]) {
|
|
204
|
+
const useExistingPassword = await (0, prompts_1.confirm)({
|
|
205
|
+
message: 'IAM 密码: 是否使用已保存的密码?',
|
|
206
|
+
default: true,
|
|
207
|
+
});
|
|
208
|
+
if (useExistingPassword) {
|
|
209
|
+
iamAnswers.password = existingConfig[types_1.ConfigKey.HUAWEI_CLOUD_PASSWORD];
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
iamAnswers.password = await inputPassword();
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
iamAnswers.password = await inputPassword();
|
|
217
|
+
}
|
|
231
218
|
// 创建 BusinessService 实例
|
|
232
219
|
businessService = new business_service_1.BusinessService({
|
|
233
220
|
iamEndpoint: iamAnswers.iamEndpoint,
|
|
@@ -239,21 +226,19 @@ async function configCommand() {
|
|
|
239
226
|
enableLogging: false,
|
|
240
227
|
});
|
|
241
228
|
// 验证凭证
|
|
229
|
+
const spinner = (0, ora_1.default)('正在验证 IAM 凭证...').start();
|
|
242
230
|
const validationResult = await businessService.validateCredentials();
|
|
231
|
+
spinner.stop();
|
|
243
232
|
if (validationResult.success) {
|
|
244
233
|
credentialsValid = true;
|
|
245
234
|
}
|
|
246
235
|
else {
|
|
247
236
|
const errorMessage = `❌ IAM 凭证验证失败: ${validationResult.error}\n`;
|
|
248
237
|
logger_1.logger.error(errorMessage);
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
message: '是否重新配置 IAM 凭证?',
|
|
254
|
-
default: true,
|
|
255
|
-
},
|
|
256
|
-
]);
|
|
238
|
+
const retry = await (0, prompts_1.confirm)({
|
|
239
|
+
message: '是否重新配置 IAM 凭证?',
|
|
240
|
+
default: true,
|
|
241
|
+
});
|
|
257
242
|
if (!retry) {
|
|
258
243
|
logger_1.logger.info('\n已取消配置。');
|
|
259
244
|
return;
|
|
@@ -267,51 +252,42 @@ async function configCommand() {
|
|
|
267
252
|
}
|
|
268
253
|
// 第二阶段:获取项目列表并选择项目
|
|
269
254
|
let projectId;
|
|
255
|
+
let projects = [];
|
|
256
|
+
async function inputProjectId() {
|
|
257
|
+
return await (0, prompts_1.input)({
|
|
258
|
+
message: '项目 ID:',
|
|
259
|
+
default: existingConfig[types_1.ConfigKey.PROJECT_ID] || '',
|
|
260
|
+
validate: (inputValue) => (inputValue.trim() ? true : '项目 ID 不能为空'),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
270
263
|
try {
|
|
271
|
-
const
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
{
|
|
275
|
-
type: 'input',
|
|
276
|
-
name: 'manualProjectId',
|
|
277
|
-
message: '项目 ID:',
|
|
278
|
-
default: existingConfig[types_1.ConfigKey.PROJECT_ID] || '',
|
|
279
|
-
validate: (input) => (input.trim() ? true : '项目 ID 不能为空'),
|
|
280
|
-
},
|
|
281
|
-
]);
|
|
282
|
-
projectId = manualProjectId;
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
const projectChoices = projects.map((p) => ({
|
|
286
|
-
name: `${p.project_name} (${p.project_id})`,
|
|
287
|
-
value: p.project_id,
|
|
288
|
-
}));
|
|
289
|
-
const { selectedProjectId } = await inquirer_1.default.prompt([
|
|
290
|
-
{
|
|
291
|
-
type: 'list',
|
|
292
|
-
name: 'selectedProjectId',
|
|
293
|
-
message: '请选择项目:',
|
|
294
|
-
choices: projectChoices,
|
|
295
|
-
default: existingConfig[types_1.ConfigKey.PROJECT_ID] || projectChoices[0]?.value,
|
|
296
|
-
},
|
|
297
|
-
]);
|
|
298
|
-
projectId = selectedProjectId;
|
|
299
|
-
}
|
|
264
|
+
const spinner = (0, ora_1.default)('正在获取项目列表...').start();
|
|
265
|
+
projects = await businessService.getProjects(100);
|
|
266
|
+
spinner.stop();
|
|
300
267
|
}
|
|
301
268
|
catch (error) {
|
|
302
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
303
269
|
logger_1.logger.error(`❌ 获取项目列表失败: `, error);
|
|
304
270
|
// 获取失败,使用手动输入
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
|
|
271
|
+
projectId = await inputProjectId();
|
|
272
|
+
}
|
|
273
|
+
if (projects.length === 0) {
|
|
274
|
+
projectId = await inputProjectId();
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
const projectChoices = projects.map((p) => ({
|
|
278
|
+
name: `${p.project_name} (${p.project_id})`,
|
|
279
|
+
value: p.project_id,
|
|
280
|
+
}));
|
|
281
|
+
// 确定默认项目 ID
|
|
282
|
+
const existingProjectId = existingConfig[types_1.ConfigKey.PROJECT_ID];
|
|
283
|
+
const isExistingProjectValid = existingProjectId && projects.some((p) => p.project_id === existingProjectId);
|
|
284
|
+
const defaultProjectId = isExistingProjectValid ? existingProjectId : projectChoices[0]?.value;
|
|
285
|
+
projectId = await (0, prompts_1.select)({
|
|
286
|
+
message: '请选择项目:',
|
|
287
|
+
choices: projectChoices,
|
|
288
|
+
default: defaultProjectId,
|
|
289
|
+
theme: inquirer_theme_1.globalTheme,
|
|
290
|
+
});
|
|
315
291
|
}
|
|
316
292
|
// 第三阶段:配置项目相关配置
|
|
317
293
|
const projectConfigs = {};
|
|
@@ -363,16 +339,6 @@ async function updateProjectConfigCommand(configKey) {
|
|
|
363
339
|
}
|
|
364
340
|
// 读取现有配置
|
|
365
341
|
const existingConfig = (0, config_loader_1.readConfig)();
|
|
366
|
-
// 检查必要的配置是否存在
|
|
367
|
-
if (!existingConfig[types_1.ConfigKey.HUAWEI_CLOUD_USERNAME] ||
|
|
368
|
-
!existingConfig[types_1.ConfigKey.HUAWEI_CLOUD_PASSWORD]) {
|
|
369
|
-
logger_1.logger.error('\n❌ 全局配置不完整,请先运行 `npx @hecom/codearts config` 完成配置。');
|
|
370
|
-
process.exit(1);
|
|
371
|
-
}
|
|
372
|
-
if (!existingConfig[types_1.ConfigKey.PROJECT_ID]) {
|
|
373
|
-
logger_1.logger.error('\n❌ 项目 ID 未配置,请先运行 `npx @hecom/codearts config` 完成配置。');
|
|
374
|
-
process.exit(1);
|
|
375
|
-
}
|
|
376
342
|
// 创建 BusinessService 实例
|
|
377
343
|
const businessService = new business_service_1.BusinessService({
|
|
378
344
|
iamEndpoint: existingConfig[types_1.ConfigKey.HUAWEI_CLOUD_IAM_ENDPOINT],
|
|
@@ -414,7 +380,7 @@ async function showConfigCommand() {
|
|
|
414
380
|
// 获取最终合并后的配置
|
|
415
381
|
const config = (0, config_loader_1.getConfig)();
|
|
416
382
|
// 按类别显示配置
|
|
417
|
-
logger_1.logger.info('
|
|
383
|
+
logger_1.logger.info(picocolors_1.default.cyan('【华为云 IAM 凭证】'));
|
|
418
384
|
const iamKeys = [
|
|
419
385
|
types_1.ConfigKey.HUAWEI_CLOUD_IAM_ENDPOINT,
|
|
420
386
|
types_1.ConfigKey.HUAWEI_CLOUD_REGION,
|
|
@@ -427,7 +393,8 @@ async function showConfigCommand() {
|
|
|
427
393
|
const displayValue = key.includes('PASSWORD') && value !== '(未配置)' ? '********' : value;
|
|
428
394
|
logger_1.logger.info(` ${formatKeyName(key)}: ${displayValue}`);
|
|
429
395
|
}
|
|
430
|
-
logger_1.logger.info(
|
|
396
|
+
logger_1.logger.info();
|
|
397
|
+
logger_1.logger.info(picocolors_1.default.cyanBright('【CodeArts 配置】'));
|
|
431
398
|
const codeartsKeys = [
|
|
432
399
|
types_1.ConfigKey.CODEARTS_BASE_URL,
|
|
433
400
|
types_1.ConfigKey.PROJECT_ID,
|