@hecom/codearts 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/bin/cli.js +25 -4
- package/dist/charts/chart.interface.d.ts +5 -0
- package/dist/charts/chart.interface.js +2 -0
- package/dist/charts/index.d.ts +2 -0
- package/dist/charts/index.js +17 -0
- package/dist/charts/modules/__tests__/bug-by-developer-hours.test.d.ts +1 -0
- package/dist/charts/modules/__tests__/bug-by-developer-hours.test.js +54 -0
- package/dist/charts/modules/__tests__/bug-by-fix-duration.test.d.ts +1 -0
- package/dist/charts/modules/__tests__/bug-by-fix-duration.test.js +89 -0
- package/dist/charts/modules/__tests__/bug-open-priority-heatmap.test.d.ts +1 -0
- package/dist/charts/modules/__tests__/bug-open-priority-heatmap.test.js +61 -0
- package/dist/charts/modules/bug-by-assignee.d.ts +2 -0
- package/dist/charts/modules/bug-by-assignee.js +30 -0
- package/dist/charts/modules/bug-by-defect-analysis.d.ts +2 -0
- package/dist/charts/modules/bug-by-defect-analysis.js +30 -0
- package/dist/charts/modules/bug-by-developer-hours.d.ts +2 -0
- package/dist/charts/modules/bug-by-developer-hours.js +32 -0
- package/dist/charts/modules/bug-by-fix-duration.d.ts +2 -0
- package/dist/charts/modules/bug-by-fix-duration.js +46 -0
- package/dist/charts/modules/bug-by-module.d.ts +2 -0
- package/dist/charts/modules/bug-by-module.js +34 -0
- package/dist/charts/modules/bug-open-priority-heatmap.d.ts +2 -0
- package/dist/charts/modules/bug-open-priority-heatmap.js +74 -0
- package/dist/charts/renderer.d.ts +21 -0
- package/dist/charts/renderer.js +163 -0
- package/dist/commands/config.command.d.ts +1 -1
- package/dist/commands/config.command.js +6 -6
- package/dist/commands/fix.command.d.ts +6 -0
- package/dist/commands/fix.command.js +220 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.js +5 -1
- package/dist/commands/rebug.command.d.ts +5 -0
- package/dist/commands/rebug.command.js +113 -0
- package/dist/services/api.service.d.ts +10 -2
- package/dist/services/api.service.js +19 -0
- package/dist/services/business.service.d.ts +40 -1
- package/dist/services/business.service.js +226 -5
- package/dist/types/index.d.ts +107 -1
- package/dist/types/index.js +60 -1
- package/dist/utils/config-loader.d.ts +7 -6
- package/dist/utils/config-loader.js +13 -13
- package/package.json +5 -2
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.renderReport = renderReport;
|
|
37
|
+
exports.openInBrowser = openInBrowser;
|
|
38
|
+
const child_process_1 = require("child_process");
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const logger_1 = require("../utils/logger");
|
|
42
|
+
/**
|
|
43
|
+
* 生成 HTML 报告并写入文件,返回文件路径。
|
|
44
|
+
* @param bugs Bug 列表
|
|
45
|
+
* @param charts 图表模块列表
|
|
46
|
+
* @param meta 报告元数据
|
|
47
|
+
* @param outputDir 输出目录,若不指定则使用系统 cache 目录
|
|
48
|
+
* 若文件写入失败,将 HTML 内容通过 logger 输出作为兜底,然后抛出错误。
|
|
49
|
+
*/
|
|
50
|
+
function renderReport(bugs, charts, meta, outputDir) {
|
|
51
|
+
const d = new Date(meta.generatedAt);
|
|
52
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
53
|
+
const timestamp = `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}-${pad(d.getHours())}${pad(d.getMinutes())}${pad(d.getSeconds())}`;
|
|
54
|
+
const filename = `rebug-report-${timestamp}.html`;
|
|
55
|
+
// 如果未指定输出目录,使用系统 cache 目录
|
|
56
|
+
let targetDir = outputDir;
|
|
57
|
+
if (!targetDir) {
|
|
58
|
+
const os = require('os');
|
|
59
|
+
const platform = process.platform;
|
|
60
|
+
if (platform === 'darwin') {
|
|
61
|
+
targetDir = path.join(os.homedir(), 'Library', 'Caches', 'hecom-codearts');
|
|
62
|
+
}
|
|
63
|
+
else if (platform === 'linux') {
|
|
64
|
+
targetDir = path.join(os.homedir(), '.cache', 'hecom-codearts');
|
|
65
|
+
}
|
|
66
|
+
else if (platform === 'win32') {
|
|
67
|
+
targetDir = path.join(os.homedir(), 'AppData', 'Local', 'Temp', 'hecom-codearts');
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
targetDir = path.join(os.tmpdir(), 'hecom-codearts');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// 确保目录存在
|
|
74
|
+
if (!fs.existsSync(targetDir)) {
|
|
75
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
const outputPath = path.join(targetDir, filename);
|
|
78
|
+
const html = buildHtml(bugs, charts, meta);
|
|
79
|
+
try {
|
|
80
|
+
fs.writeFileSync(outputPath, html, 'utf-8');
|
|
81
|
+
}
|
|
82
|
+
catch (writeError) {
|
|
83
|
+
logger_1.logger.warn('文件写入失败,请手动保存以下 HTML 内容:');
|
|
84
|
+
logger_1.logger.warn(html);
|
|
85
|
+
throw writeError;
|
|
86
|
+
}
|
|
87
|
+
return outputPath;
|
|
88
|
+
}
|
|
89
|
+
function buildHtml(bugs, charts, meta) {
|
|
90
|
+
const chartContainers = charts
|
|
91
|
+
.map((chart, i) => `<div class="chart-card">
|
|
92
|
+
<div class="chart-title">${chart.title}</div>
|
|
93
|
+
<div id="chart-${i}" style="height:400px;"></div>
|
|
94
|
+
</div>`)
|
|
95
|
+
.join('\n');
|
|
96
|
+
const chartScripts = charts
|
|
97
|
+
.map((chart, i) => {
|
|
98
|
+
const option = JSON.stringify(chart.buildOption(bugs));
|
|
99
|
+
return `echarts.init(document.getElementById('chart-${i}')).setOption(${option});`;
|
|
100
|
+
})
|
|
101
|
+
.join('\n');
|
|
102
|
+
const iterationsText = meta.iterationNames.join(', ') || '全部';
|
|
103
|
+
const terminalText = meta.terminalTypes.join(', ') || '全部';
|
|
104
|
+
return `<!DOCTYPE html>
|
|
105
|
+
<html lang="zh-CN">
|
|
106
|
+
<head>
|
|
107
|
+
<meta charset="UTF-8">
|
|
108
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
109
|
+
<title>Bug 分析报告</title>
|
|
110
|
+
<style>
|
|
111
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 0; padding: 20px; background: #f5f5f5; color: #333; }
|
|
112
|
+
.header { background: #fff; border-radius: 8px; padding: 24px; margin-bottom: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
113
|
+
.header h1 { margin: 0 0 16px; font-size: 22px; color: #1a1a1a; }
|
|
114
|
+
.meta-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
|
|
115
|
+
.meta-item { background: #f8f9fa; border-radius: 6px; padding: 12px; }
|
|
116
|
+
.meta-label { font-size: 12px; color: #888; margin-bottom: 4px; }
|
|
117
|
+
.meta-value { font-size: 14px; font-weight: 600; color: #333; }
|
|
118
|
+
.charts-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
|
|
119
|
+
.chart-card { background: #fff; border-radius: 8px; padding: 20px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
|
120
|
+
.chart-title { font-size: 15px; font-weight: 600; color: #1a1a1a; margin-bottom: 12px; }
|
|
121
|
+
@media (max-width: 900px) { .charts-grid { grid-template-columns: 1fr; } }
|
|
122
|
+
</style>
|
|
123
|
+
</head>
|
|
124
|
+
<body>
|
|
125
|
+
<div class="header">
|
|
126
|
+
<h1>Bug 分析报告</h1>
|
|
127
|
+
<div class="meta-grid">
|
|
128
|
+
<div class="meta-item"><div class="meta-label">总 Bug 数</div><div class="meta-value">${meta.totalCount}</div></div>
|
|
129
|
+
<div class="meta-item"><div class="meta-label">迭代</div><div class="meta-value">${iterationsText}</div></div>
|
|
130
|
+
<div class="meta-item"><div class="meta-label">终端类型</div><div class="meta-value">${terminalText}</div></div>
|
|
131
|
+
<div class="meta-item"><div class="meta-label">生成时间</div><div class="meta-value">${new Date(meta.generatedAt).toLocaleString('zh-CN')}</div></div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
<div class="charts-grid">
|
|
135
|
+
${chartContainers}
|
|
136
|
+
</div>
|
|
137
|
+
<script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
|
|
138
|
+
<script>
|
|
139
|
+
${chartScripts}
|
|
140
|
+
</script>
|
|
141
|
+
</body>
|
|
142
|
+
</html>`;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* 自动在系统默认浏览器中打开文件
|
|
146
|
+
*/
|
|
147
|
+
function openInBrowser(filePath) {
|
|
148
|
+
const platform = process.platform;
|
|
149
|
+
try {
|
|
150
|
+
if (platform === 'darwin') {
|
|
151
|
+
(0, child_process_1.execSync)(`open "${filePath}"`);
|
|
152
|
+
}
|
|
153
|
+
else if (platform === 'linux') {
|
|
154
|
+
(0, child_process_1.execSync)(`xdg-open "${filePath}"`);
|
|
155
|
+
}
|
|
156
|
+
else if (platform === 'win32') {
|
|
157
|
+
(0, child_process_1.execSync)(`start "" "${filePath}"`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
// 打开失败不中断流程,路径已打印
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -139,7 +139,7 @@ async function inputPassword() {
|
|
|
139
139
|
}
|
|
140
140
|
/**
|
|
141
141
|
* 交互式配置向导命令
|
|
142
|
-
*
|
|
142
|
+
* 引导用户创建或更新配置文件
|
|
143
143
|
*/
|
|
144
144
|
async function configCommand() {
|
|
145
145
|
logger_1.logger.info('欢迎使用 Hecom CodeArts 配置向导');
|
|
@@ -149,7 +149,7 @@ async function configCommand() {
|
|
|
149
149
|
const existingConfig = (0, config_loader_1.configExists)() ? (0, config_loader_1.readConfig)() : {};
|
|
150
150
|
if ((0, config_loader_1.configExists)()) {
|
|
151
151
|
const overwrite = await (0, prompts_1.confirm)({
|
|
152
|
-
message: '
|
|
152
|
+
message: '检测到已存在配置文件,是否覆盖?',
|
|
153
153
|
default: true,
|
|
154
154
|
});
|
|
155
155
|
if (!overwrite) {
|
|
@@ -188,9 +188,9 @@ async function configCommand() {
|
|
|
188
188
|
validate: (inputValue) => (inputValue.trim() ? true : 'CodeArts API 地址不能为空'),
|
|
189
189
|
}),
|
|
190
190
|
domain: await (0, prompts_1.input)({
|
|
191
|
-
message: '
|
|
191
|
+
message: '租户名/原华为云账号:',
|
|
192
192
|
default: existingConfig[types_1.ConfigKey.HUAWEI_CLOUD_DOMAIN] || '',
|
|
193
|
-
validate: (inputValue) =>
|
|
193
|
+
validate: (inputValue) => inputValue.trim() ? true : '租户名/原华为云账号不能为空',
|
|
194
194
|
}),
|
|
195
195
|
username: await (0, prompts_1.input)({
|
|
196
196
|
message: 'IAM 用户名:',
|
|
@@ -308,7 +308,7 @@ async function configCommand() {
|
|
|
308
308
|
};
|
|
309
309
|
try {
|
|
310
310
|
(0, config_loader_1.writeConfig)(finalConfig);
|
|
311
|
-
logger_1.logger.success('\n✅
|
|
311
|
+
logger_1.logger.success('\n✅ 配置完成!');
|
|
312
312
|
logger_1.logger.info(`配置文件位置: ${(0, config_loader_1.getConfigPath)()}`);
|
|
313
313
|
logger_1.logger.info('\n提示:配置文件包含敏感信息,请妥善保管。');
|
|
314
314
|
}
|
|
@@ -324,7 +324,7 @@ async function configCommand() {
|
|
|
324
324
|
async function updateProjectConfigCommand(configKey) {
|
|
325
325
|
// 检查配置文件是否存在
|
|
326
326
|
if (!(0, config_loader_1.configExists)()) {
|
|
327
|
-
logger_1.logger.error('\n❌
|
|
327
|
+
logger_1.logger.error('\n❌ 配置文件不存在,请先运行 `npx @hecom/codearts config` 创建配置。');
|
|
328
328
|
process.exit(1);
|
|
329
329
|
}
|
|
330
330
|
// 查找对应的配置项
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.fixCommand = fixCommand;
|
|
7
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const business_service_1 = require("../services/business.service");
|
|
10
|
+
const types_1 = require("../types");
|
|
11
|
+
const config_loader_1 = require("../utils/config-loader");
|
|
12
|
+
const console_1 = require("../utils/console");
|
|
13
|
+
const logger_1 = require("../utils/logger");
|
|
14
|
+
/**
|
|
15
|
+
* Fix 命令:交互式修复 bug,填写缺陷分析信息
|
|
16
|
+
* @param cliOptions 命令行选项
|
|
17
|
+
*/
|
|
18
|
+
async function fixCommand(cliOptions) {
|
|
19
|
+
try {
|
|
20
|
+
// Step 1: 加载配置
|
|
21
|
+
const { projectId, config } = (0, config_loader_1.loadConfig)(cliOptions);
|
|
22
|
+
const businessService = new business_service_1.BusinessService(config);
|
|
23
|
+
if (!projectId) {
|
|
24
|
+
logger_1.logger.error('项目 ID 未配置,请先执行 codearts config');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Step 2: 加载 bug 列表和自定义字段选项
|
|
28
|
+
const spinner = (0, ora_1.default)('正在加载数据...').start();
|
|
29
|
+
let bugList;
|
|
30
|
+
let customFieldOptions;
|
|
31
|
+
try {
|
|
32
|
+
bugList = await businessService.getCurrentUserBugs(projectId);
|
|
33
|
+
customFieldOptions = await businessService.getCustomFieldOptions(projectId, [
|
|
34
|
+
types_1.CustomFieldId.DEFECT_TECHNICAL_ANALYSIS,
|
|
35
|
+
types_1.CustomFieldId.INTRODUCTION_PHASE,
|
|
36
|
+
]);
|
|
37
|
+
spinner.succeed('数据加载完成');
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
spinner.fail('数据加载失败');
|
|
41
|
+
logger_1.logger.error(`${String(error)}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Step 3: 检查 bug 列表是否为空
|
|
45
|
+
if (bugList.length === 0) {
|
|
46
|
+
logger_1.logger.warn('当前用户没有分配的 bug');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
// Step 4: 让用户选择 bug
|
|
50
|
+
const selectedBug = await selectBug(bugList);
|
|
51
|
+
if (!selectedBug) {
|
|
52
|
+
logger_1.logger.info('操作取消');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Step 5: 填写缺陷分析信息
|
|
56
|
+
const bugFixData = {};
|
|
57
|
+
// 5.1: 选择缺陷技术分析(必填)
|
|
58
|
+
const defectAnalysisOptions = customFieldOptions[types_1.CustomFieldId.DEFECT_TECHNICAL_ANALYSIS] || [];
|
|
59
|
+
if (defectAnalysisOptions.length === 0) {
|
|
60
|
+
logger_1.logger.error('无法获取缺陷技术分析选项');
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
bugFixData.defectAnalysis = await (0, prompts_1.select)({
|
|
64
|
+
message: '请选择缺陷技术分析(必填)',
|
|
65
|
+
choices: defectAnalysisOptions.map((option) => ({
|
|
66
|
+
name: option,
|
|
67
|
+
value: option,
|
|
68
|
+
})),
|
|
69
|
+
});
|
|
70
|
+
// 5.2: 填写问题原因及解决办法(可选)
|
|
71
|
+
const problemReason = await (0, prompts_1.input)({
|
|
72
|
+
message: '请输入问题原因及解决办法(可选,留空跳过)',
|
|
73
|
+
});
|
|
74
|
+
if (problemReason.trim()) {
|
|
75
|
+
bugFixData.problemReason = problemReason;
|
|
76
|
+
}
|
|
77
|
+
// 5.3: 填写影响范围(可选)
|
|
78
|
+
const impactScope = await (0, prompts_1.input)({
|
|
79
|
+
message: '请输入影响范围(可选,留空跳过)',
|
|
80
|
+
});
|
|
81
|
+
if (impactScope.trim()) {
|
|
82
|
+
bugFixData.impactScope = impactScope;
|
|
83
|
+
}
|
|
84
|
+
// Step 6: 检查是否为客户反馈 bug
|
|
85
|
+
const isCustomerFeedback = checkIsCustomerFeedback(selectedBug);
|
|
86
|
+
if (isCustomerFeedback) {
|
|
87
|
+
// 6.1: 选择引入阶段(必填)
|
|
88
|
+
const introductionPhaseOptions = customFieldOptions[types_1.CustomFieldId.INTRODUCTION_PHASE] || [];
|
|
89
|
+
if (introductionPhaseOptions.length === 0) {
|
|
90
|
+
logger_1.logger.error('无法获取引入阶段选项');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
bugFixData.introductionStage = await (0, prompts_1.select)({
|
|
94
|
+
message: '请选择引入阶段(必填)',
|
|
95
|
+
choices: introductionPhaseOptions
|
|
96
|
+
.sort()
|
|
97
|
+
.reverse()
|
|
98
|
+
.map((option) => ({
|
|
99
|
+
name: option,
|
|
100
|
+
value: option,
|
|
101
|
+
})),
|
|
102
|
+
});
|
|
103
|
+
// 6.2: 输入发布时间(必填)
|
|
104
|
+
let releaseDate;
|
|
105
|
+
let validDate = false;
|
|
106
|
+
while (!validDate) {
|
|
107
|
+
releaseDate = await (0, prompts_1.input)({
|
|
108
|
+
message: '请输入发布时间(必填,格式:YYYY/M/D)',
|
|
109
|
+
validate: (value) => {
|
|
110
|
+
if (!value) {
|
|
111
|
+
return '发布时间不能为空';
|
|
112
|
+
}
|
|
113
|
+
const dateRegex = /^\d{4}\/\d{1,2}\/\d{1,2}$/;
|
|
114
|
+
if (!dateRegex.test(value)) {
|
|
115
|
+
return '日期格式不正确,请使用 YYYY/M/D 格式';
|
|
116
|
+
}
|
|
117
|
+
const date = new Date(value);
|
|
118
|
+
if (isNaN(date.getTime())) {
|
|
119
|
+
return '请输入有效的日期';
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
validDate = true;
|
|
125
|
+
bugFixData.releaseDate = releaseDate;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Step 7: 确认并提交
|
|
129
|
+
const confirmed = await showSummaryAndConfirm(selectedBug, bugFixData, isCustomerFeedback);
|
|
130
|
+
if (!confirmed) {
|
|
131
|
+
logger_1.logger.info('操作已取消');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Step 8: 调用 API 更新 bug
|
|
135
|
+
const updateSpinner = (0, ora_1.default)('正在保存缺陷信息...').start();
|
|
136
|
+
try {
|
|
137
|
+
await businessService.fixBug(projectId, selectedBug, bugFixData);
|
|
138
|
+
updateSpinner.succeed('缺陷信息保存成功!');
|
|
139
|
+
logger_1.logger.info(`\nBug 链接:${(0, console_1.issueLink)(projectId, String(selectedBug.id))}`);
|
|
140
|
+
// Step 9: 询问是否继续修复
|
|
141
|
+
const continueIterate = await (0, prompts_1.confirm)({
|
|
142
|
+
message: '是否继续修复?',
|
|
143
|
+
default: false,
|
|
144
|
+
});
|
|
145
|
+
if (continueIterate) {
|
|
146
|
+
await fixCommand(cliOptions);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
updateSpinner.fail('保存失败');
|
|
151
|
+
logger_1.logger.error(`${String(error)}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
if (error instanceof Error && error.name === 'ExitPromptError') {
|
|
156
|
+
logger_1.logger.info('操作取消');
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
logger_1.logger.error(`执行 fix 命令失败: ${String(error)}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* 让用户选择一个 bug
|
|
165
|
+
* @param bugList bug 列表
|
|
166
|
+
* @returns 选中的 bug,或 null 如果用户取消
|
|
167
|
+
*/
|
|
168
|
+
async function selectBug(bugList) {
|
|
169
|
+
const bugChoices = bugList.map((bug) => {
|
|
170
|
+
const statusName = bug.status?.name || '未知状态';
|
|
171
|
+
const name = bug.name.length > 40 ? `${bug.name.slice(0, 47)}...` : bug.name;
|
|
172
|
+
const label = `[${statusName}] ${name}`;
|
|
173
|
+
return {
|
|
174
|
+
name: label,
|
|
175
|
+
value: bug,
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
const selectedBug = await (0, prompts_1.select)({
|
|
179
|
+
message: '请选择要修复的 bug',
|
|
180
|
+
choices: bugChoices,
|
|
181
|
+
pageSize: 10,
|
|
182
|
+
});
|
|
183
|
+
return selectedBug || null;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 检查 bug 是否为客户反馈类型
|
|
187
|
+
* @param bug bug 工作项
|
|
188
|
+
* @returns 是否为客户反馈
|
|
189
|
+
*/
|
|
190
|
+
function checkIsCustomerFeedback(bug) {
|
|
191
|
+
const customFields = bug.new_custom_fields || [];
|
|
192
|
+
const defectTypeField = customFields.find((field) => field?.custom_field === types_1.CustomFieldId.DEFECT_TYPE || field?.field_name === '缺陷类型');
|
|
193
|
+
const defectType = defectTypeField?.value || '';
|
|
194
|
+
return defectType === '客户反馈';
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* 显示总结信息并确认提交
|
|
198
|
+
* @param bug 选中的 bug
|
|
199
|
+
* @param bugFixData 填写的缺陷信息
|
|
200
|
+
* @param isCustomerFeedback 是否为客户反馈
|
|
201
|
+
* @returns 用户是否确认提交
|
|
202
|
+
*/
|
|
203
|
+
async function showSummaryAndConfirm(bug, bugFixData, isCustomerFeedback) {
|
|
204
|
+
logger_1.logger.info('\n========== 填写信息总结 ==========\n');
|
|
205
|
+
logger_1.logger.info(`Bug ID: #${bug.id}`);
|
|
206
|
+
logger_1.logger.info(`Bug 标题: ${bug.name}`);
|
|
207
|
+
logger_1.logger.info(`缺陷技术分析: ${bugFixData.defectAnalysis || '未填写'}`);
|
|
208
|
+
logger_1.logger.info(`问题原因: ${bugFixData.problemReason || '未填写'}`);
|
|
209
|
+
logger_1.logger.info(`影响范围: ${bugFixData.impactScope || '未填写'}`);
|
|
210
|
+
if (isCustomerFeedback) {
|
|
211
|
+
logger_1.logger.info(`引入阶段: ${bugFixData.introductionStage || '未填写'}`);
|
|
212
|
+
logger_1.logger.info(`发布时间: ${bugFixData.releaseDate || '未填写'}`);
|
|
213
|
+
}
|
|
214
|
+
logger_1.logger.info('\n==================================\n');
|
|
215
|
+
const confirmed = await (0, prompts_1.confirm)({
|
|
216
|
+
message: '确认提交以上信息?',
|
|
217
|
+
default: true,
|
|
218
|
+
});
|
|
219
|
+
return confirmed;
|
|
220
|
+
}
|
package/dist/commands/index.d.ts
CHANGED
|
@@ -2,3 +2,5 @@ export { dailyCommand } from './daily.command';
|
|
|
2
2
|
export { workHourCommand } from './work-hour.command';
|
|
3
3
|
export { configCommand } from './config.command';
|
|
4
4
|
export { bugCommand } from './bug.command';
|
|
5
|
+
export { fixCommand } from './fix.command';
|
|
6
|
+
export { rebugCommand } from './rebug.command';
|
package/dist/commands/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.bugCommand = exports.configCommand = exports.workHourCommand = exports.dailyCommand = void 0;
|
|
3
|
+
exports.rebugCommand = exports.fixCommand = exports.bugCommand = exports.configCommand = exports.workHourCommand = exports.dailyCommand = void 0;
|
|
4
4
|
var daily_command_1 = require("./daily.command");
|
|
5
5
|
Object.defineProperty(exports, "dailyCommand", { enumerable: true, get: function () { return daily_command_1.dailyCommand; } });
|
|
6
6
|
var work_hour_command_1 = require("./work-hour.command");
|
|
@@ -9,3 +9,7 @@ var config_command_1 = require("./config.command");
|
|
|
9
9
|
Object.defineProperty(exports, "configCommand", { enumerable: true, get: function () { return config_command_1.configCommand; } });
|
|
10
10
|
var bug_command_1 = require("./bug.command");
|
|
11
11
|
Object.defineProperty(exports, "bugCommand", { enumerable: true, get: function () { return bug_command_1.bugCommand; } });
|
|
12
|
+
var fix_command_1 = require("./fix.command");
|
|
13
|
+
Object.defineProperty(exports, "fixCommand", { enumerable: true, get: function () { return fix_command_1.fixCommand; } });
|
|
14
|
+
var rebug_command_1 = require("./rebug.command");
|
|
15
|
+
Object.defineProperty(exports, "rebugCommand", { enumerable: true, get: function () { return rebug_command_1.rebugCommand; } });
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.rebugCommand = rebugCommand;
|
|
7
|
+
const prompts_1 = require("@inquirer/prompts");
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const charts_1 = require("../charts");
|
|
10
|
+
const renderer_1 = require("../charts/renderer");
|
|
11
|
+
const business_service_1 = require("../services/business.service");
|
|
12
|
+
const types_1 = require("../types");
|
|
13
|
+
const config_loader_1 = require("../utils/config-loader");
|
|
14
|
+
const inquirer_theme_1 = require("../utils/inquirer-theme");
|
|
15
|
+
const logger_1 = require("../utils/logger");
|
|
16
|
+
/**
|
|
17
|
+
* rebug 命令:交互式查询 Bug 列表并生成 ECharts 可视化报告
|
|
18
|
+
*/
|
|
19
|
+
async function rebugCommand(cliOptions = {}) {
|
|
20
|
+
try {
|
|
21
|
+
const { projectId, config } = (0, config_loader_1.loadConfig)(cliOptions);
|
|
22
|
+
const businessService = new business_service_1.BusinessService(config);
|
|
23
|
+
// Step 1: 加载迭代列表(前12个)
|
|
24
|
+
const loadSpinner = (0, ora_1.default)('正在加载迭代列表...').start();
|
|
25
|
+
let iterations;
|
|
26
|
+
try {
|
|
27
|
+
iterations = await businessService.getIterations(projectId, { limit: 12 });
|
|
28
|
+
loadSpinner.succeed('迭代列表加载完成');
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
loadSpinner.fail('加载迭代列表失败');
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
if (iterations.length === 0) {
|
|
35
|
+
throw new Error('未获取到任何迭代信息');
|
|
36
|
+
}
|
|
37
|
+
// Step 2: 用户选择迭代
|
|
38
|
+
const selectedIterations = await (0, prompts_1.checkbox)({
|
|
39
|
+
message: '请选择要查询的迭代:',
|
|
40
|
+
choices: iterations.map((it) => ({
|
|
41
|
+
name: `${it.name} (${it.begin_time} ~ ${it.end_time})`,
|
|
42
|
+
value: it,
|
|
43
|
+
checked: false,
|
|
44
|
+
})),
|
|
45
|
+
validate: (answer) => (answer.length === 0 ? '至少需要选择一个迭代' : true),
|
|
46
|
+
theme: inquirer_theme_1.globalTheme,
|
|
47
|
+
});
|
|
48
|
+
// Step 3: 加载终端类型选项
|
|
49
|
+
let terminalTypeOptions = [];
|
|
50
|
+
try {
|
|
51
|
+
const customFieldOptions = await businessService.getCustomFieldOptions(projectId, [
|
|
52
|
+
types_1.CustomFieldId.TERMINAL_TYPE,
|
|
53
|
+
]);
|
|
54
|
+
terminalTypeOptions = customFieldOptions[types_1.CustomFieldId.TERMINAL_TYPE] || [];
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
logger_1.logger.warn('获取终端类型选项失败,将跳过终端类型筛选');
|
|
58
|
+
}
|
|
59
|
+
// Step 4: 用户选择终端类型(如果有选项)
|
|
60
|
+
let selectedTerminalTypes = [];
|
|
61
|
+
if (terminalTypeOptions.length > 0) {
|
|
62
|
+
selectedTerminalTypes = await (0, prompts_1.checkbox)({
|
|
63
|
+
message: '请选择终端类型(不选则查询全部):',
|
|
64
|
+
choices: terminalTypeOptions.map((t) => ({ name: t, value: t, checked: false })),
|
|
65
|
+
theme: inquirer_theme_1.globalTheme,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
// Step 5: 查询 Bug 列表
|
|
69
|
+
const querySpinner = (0, ora_1.default)('正在查询 Bug 列表...').start();
|
|
70
|
+
const iterationIds = selectedIterations.map((it) => it.id);
|
|
71
|
+
let allBugs;
|
|
72
|
+
try {
|
|
73
|
+
allBugs = await businessService.getBugsByIterationsAndTerminals(projectId, iterationIds, selectedTerminalTypes);
|
|
74
|
+
querySpinner.succeed(`查询完成:共找到 ${allBugs.length} 个 Bug`);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
querySpinner.fail('Bug 查询失败');
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
// Step 6: 控制台摘要
|
|
81
|
+
logger_1.logger.info(`迭代:${selectedIterations.map((it) => it.name).join(', ')}`);
|
|
82
|
+
if (selectedTerminalTypes.length > 0) {
|
|
83
|
+
logger_1.logger.info(`终端类型:${selectedTerminalTypes.join(', ')}`);
|
|
84
|
+
}
|
|
85
|
+
logger_1.logger.info('正在生成分析报告...');
|
|
86
|
+
// Step 7: 生成报告
|
|
87
|
+
const meta = {
|
|
88
|
+
iterationNames: selectedIterations.map((it) => it.name),
|
|
89
|
+
terminalTypes: selectedTerminalTypes,
|
|
90
|
+
totalCount: allBugs.length,
|
|
91
|
+
generatedAt: new Date().toISOString(),
|
|
92
|
+
};
|
|
93
|
+
let reportPath;
|
|
94
|
+
try {
|
|
95
|
+
const outputDir = cliOptions.outputDir || undefined;
|
|
96
|
+
reportPath = (0, renderer_1.renderReport)(allBugs, charts_1.allCharts, meta, outputDir);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
logger_1.logger.error(`生成报告文件失败: ${String(error)}`);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
logger_1.logger.info(`报告已生成:${reportPath}`);
|
|
103
|
+
(0, renderer_1.openInBrowser)(reportPath);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
if (error instanceof Error && error.name === 'ExitPromptError') {
|
|
107
|
+
logger_1.logger.info('操作取消');
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
logger_1.logger.error(`执行 rebug 命令失败: ${String(error)}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AddIssueNotesRequest, AddIssueNotesResponse, ApiResponse, CachedToken, HuaweiCloudConfig, ListChildIssuesV2Response, ListChildIssuesV4Response, ListIssuesV4Request, ListIssuesV4Response, ListProjectIterationsV4Request, ListProjectIterationsV4Response, ProjectListResponse, ProjectMemberListResponse, ProjectMemberQueryParams, ProjectQueryParams, ShowProjectWorkHoursRequest, ShowProjectWorkHoursResponse } from '../types';
|
|
1
|
+
import { AddIssueNotesRequest, AddIssueNotesResponse, ApiResponse, CachedToken, CurrentUserInfo, GetCustomFieldsResponse, HuaweiCloudConfig, ListChildIssuesV2Response, ListChildIssuesV4Response, ListIssuesV4Request, ListIssuesV4Response, ListProjectIterationsV4Request, ListProjectIterationsV4Response, ProjectListResponse, ProjectMemberListResponse, ProjectMemberQueryParams, ProjectQueryParams, ShowProjectWorkHoursRequest, ShowProjectWorkHoursResponse, UpdateIssueRequest } from '../types';
|
|
2
2
|
/**
|
|
3
3
|
* 华为云CodeArts API服务类
|
|
4
4
|
* 支持IAM Token认证和CodeArts API调用
|
|
@@ -78,7 +78,7 @@ export declare class ApiService {
|
|
|
78
78
|
/**
|
|
79
79
|
* 更新工作项
|
|
80
80
|
*/
|
|
81
|
-
updateIssue(projectId: string, issueId: string, issueData:
|
|
81
|
+
updateIssue(projectId: string, issueId: string, issueData: UpdateIssueRequest): Promise<ApiResponse<unknown>>;
|
|
82
82
|
/**
|
|
83
83
|
* 删除工作项
|
|
84
84
|
*/
|
|
@@ -95,10 +95,18 @@ export declare class ApiService {
|
|
|
95
95
|
* 获取项目成员列表
|
|
96
96
|
*/
|
|
97
97
|
getMembers(projectId: string, params?: ProjectMemberQueryParams): Promise<ApiResponse<ProjectMemberListResponse>>;
|
|
98
|
+
/**
|
|
99
|
+
* 获取当前用户信息
|
|
100
|
+
*/
|
|
101
|
+
showCurUserInfo(): Promise<ApiResponse<CurrentUserInfo>>;
|
|
98
102
|
/**
|
|
99
103
|
* 按用户查询工时(单项目)
|
|
100
104
|
*/
|
|
101
105
|
showProjectWorkHours(projectId: string, params?: ShowProjectWorkHoursRequest): Promise<ApiResponse<ShowProjectWorkHoursResponse>>;
|
|
106
|
+
/**
|
|
107
|
+
* 获取自定义字段信息
|
|
108
|
+
*/
|
|
109
|
+
getCustomFields(projectId: string, customFieldIds: string[]): Promise<ApiResponse<GetCustomFieldsResponse>>;
|
|
102
110
|
/**
|
|
103
111
|
* 获取当前Token信息(用于调试)
|
|
104
112
|
*/
|
|
@@ -403,6 +403,14 @@ class ApiService {
|
|
|
403
403
|
},
|
|
404
404
|
});
|
|
405
405
|
}
|
|
406
|
+
/**
|
|
407
|
+
* 获取当前用户信息
|
|
408
|
+
*/
|
|
409
|
+
async showCurUserInfo() {
|
|
410
|
+
return this.request('/v4/user', {
|
|
411
|
+
method: 'GET',
|
|
412
|
+
});
|
|
413
|
+
}
|
|
406
414
|
/**
|
|
407
415
|
* 按用户查询工时(单项目)
|
|
408
416
|
*/
|
|
@@ -416,6 +424,17 @@ class ApiService {
|
|
|
416
424
|
},
|
|
417
425
|
});
|
|
418
426
|
}
|
|
427
|
+
/**
|
|
428
|
+
* 获取自定义字段信息
|
|
429
|
+
*/
|
|
430
|
+
async getCustomFields(projectId, customFieldIds) {
|
|
431
|
+
return this.request(`/v4/projects/${projectId}/issues/custom-fields`, {
|
|
432
|
+
method: 'POST',
|
|
433
|
+
data: {
|
|
434
|
+
custom_fields: customFieldIds,
|
|
435
|
+
},
|
|
436
|
+
});
|
|
437
|
+
}
|
|
419
438
|
/**
|
|
420
439
|
* 获取当前Token信息(用于调试)
|
|
421
440
|
*/
|