@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.
@@ -40,11 +40,14 @@ exports.configCommand = configCommand;
40
40
  exports.updateProjectConfigCommand = updateProjectConfigCommand;
41
41
  exports.getAvailableProjectConfigs = getAvailableProjectConfigs;
42
42
  exports.showConfigCommand = showConfigCommand;
43
- const inquirer_1 = __importDefault(require("inquirer"));
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 roles = await businessService.getProjectRoles(projectId);
65
- if (roles.length === 0) {
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
- const { manualRoleId } = await inquirer_1.default.prompt([
113
- {
114
- type: 'input',
115
- name: 'manualRoleId',
116
- message: '角色 ID(支持逗号分隔,如: 1,2,3):',
117
- default: existingValue || '',
118
- validate: (input) => {
119
- if (!input.trim()) {
120
- return '角色 ID 不能为空';
121
- }
122
- const ids = input.split(',').map((id) => id.trim());
123
- const allValid = ids.every((id) => /^\d+$/.test(id));
124
- return allValid ? true : '角色 ID 必须是数字或逗号分隔的数字列表';
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
- return manualRoleId;
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('\n欢迎使用 Hecom CodeArts 配置向导');
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 { overwrite } = await inquirer_1.default.prompt([
160
- {
161
- type: 'confirm',
162
- name: 'overwrite',
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 = await inquirer_1.default.prompt([
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: (input) => (input.trim() ? true : 'IAM 认证端点不能为空'),
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: (input) => (input.trim() ? true : '华为云区域不能为空'),
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: (input) => (input.trim() ? true : 'CodeArts API 地址不能为空'),
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: (input) => (input.trim() ? true : '华为云账号名不能为空'),
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: (input) => (input.trim() ? true : 'IAM 用户名不能为空'),
221
- },
222
- {
223
- type: 'password',
224
- name: 'password',
225
- message: 'IAM 密码:',
226
- mask: '*',
227
- default: existingConfig[types_1.ConfigKey.HUAWEI_CLOUD_PASSWORD] || '',
228
- validate: (input) => (input.trim() ? true : 'IAM 密码不能为空'),
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 { retry } = await inquirer_1.default.prompt([
250
- {
251
- type: 'confirm',
252
- name: 'retry',
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 projects = await businessService.getProjects(100);
272
- if (projects.length === 0) {
273
- const { manualProjectId } = await inquirer_1.default.prompt([
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
- const { manualProjectId } = await inquirer_1.default.prompt([
306
- {
307
- type: 'input',
308
- name: 'manualProjectId',
309
- message: '项目 ID:',
310
- default: existingConfig[types_1.ConfigKey.PROJECT_ID] || '',
311
- validate: (input) => (input.trim() ? true : '项目 ID 不能为空'),
312
- },
313
- ]);
314
- projectId = manualProjectId;
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('\n【华为云 IAM 凭证】');
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('\n【CodeArts 配置】');
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,