ai-worktool 1.0.12 → 1.0.14
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/CHANGELOG.md +4 -1
- package/README.md +66 -66
- package/dist/agents/jianguoke.js +2 -2
- package/dist/agents/prompt.js +19 -19
- package/dist/cli.js +0 -0
- package/dist/program.js +43 -3
- package/dist/tools/git.js +12 -12
- package/dist/tools/index.js +1 -1
- package/dist/tools/package.js +9 -8
- package/dist/tools/project.js +19 -10
- package/dist/user.js +181 -162
- package/dist/view.js +20 -11
- package/package.json +4 -2
- package/aicoder-1.0.10.tgz +0 -0
package/CHANGELOG.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
## 1.0.
|
1
|
+
## 1.0.14 (2025-08-05)
|
2
2
|
|
3
3
|
|
4
4
|
### Bug Fixes
|
@@ -142,6 +142,7 @@
|
|
142
142
|
* **jianguoke:** 优化工具调用信息格式化及消息 ID 生成 ([1b240e7](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/1b240e7fbcabb2112789c116b7f483308c184fce))
|
143
143
|
* **log:** 增加日志容器拖动调整功能 ([cfdaf9d](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/cfdaf9d7bec56687a2d49ca92785463dc6713a77))
|
144
144
|
* **plugin:** 更新插件版本和功能描述 ([f3773d6](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/f3773d675fcb274080339cd0274a1a7aff8415a9))
|
145
|
+
* **program:** 增加代码覆盖率报告展示功能 ([f693e27](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/f693e272ef0fe7e58083d2589339af17e45cff95))
|
145
146
|
* **project:** 优化项目类型检测逻辑 ([9bc024d](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/9bc024deeed239c05c9cab810f9f12a6a3306aab))
|
146
147
|
* Refactor CLI and program structure, update commands for testing and fixing code ([8dcbb19](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/8dcbb191fc8a95a40e07647de337452935926b10))
|
147
148
|
* **test/demo:** 添加用户成绩 API 路由 ([6f838c7](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/6f838c7fda9924b1e5a7556d0a429427a652b985))
|
@@ -152,6 +153,8 @@
|
|
152
153
|
* **toolbox:** 重构并增强项目配置检测功能 ([6cc4e33](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/6cc4e33a58c7ebb622d27bc643c1e65d1e8af577))
|
153
154
|
* **tools:** 为简咕客插件生成 zod 工具 ([42dfcd9](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/42dfcd9eb337603ecb0c5fa381ad89784345c9dc))
|
154
155
|
* **tools:** 优化工具插件支持连续多行操作并添加新功能 ([734ca52](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/734ca5287785d84be0f8fc98b074476a3cc643f2))
|
156
|
+
* **user:** 实现登录、登出功能并优化 token 存储 ([c2e046c](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/c2e046ce3068254f9b245d2b39c4c3bf3e7be44b))
|
157
|
+
* **user:** 实现登录、登出功能并优化 token 存储 ([1514408](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/151440887e90d90d0522303009f6940f0507258e))
|
155
158
|
* **user:** 实现登录取消功能并优化登录流程 ([50d124e](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/50d124e720c778e537488ca436855af21dcbcdeb))
|
156
159
|
* **user:** 为认证回调页面添加立即关闭按钮 ([4328b48](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/4328b48e06002fa9d70427b21992f1022ffa73a0))
|
157
160
|
* **views:** 优化代码覆盖率显示 ([ef98b11](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/ef98b115e57650483fae65ddec5ded085839ddd0))
|
package/README.md
CHANGED
@@ -1,67 +1,67 @@
|
|
1
|
-
# 🎮 吃豆豆:单测智能体
|
2
|
-
|
3
|
-
<p align="center">
|
4
|
-
<img src="https://aicoder.jianguoke.cn/assets/images/logo.png" alt="吃豆豆logo" width="120" height="120">
|
5
|
-
</p>
|
6
|
-
|
7
|
-
<p align="center">
|
8
|
-
<strong>自动生成高质量单元测试,让代码覆盖率轻松达到100% 🚀</strong>
|
9
|
-
</p>
|
10
|
-
|
11
|
-
<p align="center">
|
12
|
-
<em>你负责编写源代码,我负责编写测试代码,解放双手,专注核心业务逻辑开发 💻</em>
|
13
|
-
</p>
|
14
|
-
|
15
|
-
---
|
16
|
-
|
17
|
-
## 目录
|
18
|
-
- [🎮 吃豆豆:单测智能体](#-吃豆豆单测智能体)
|
19
|
-
- [目录](#目录)
|
20
|
-
- [🌟 特性亮点](#-特性亮点)
|
21
|
-
- [🚀 核心功能](#-核心功能)
|
22
|
-
- [💎 核心价值](#-核心价值)
|
23
|
-
- [📝 使用指南](#-使用指南)
|
24
|
-
- [🔧 适用范围](#-适用范围)
|
25
|
-
- [🚀 开始使用](#-开始使用)
|
26
|
-
- [💬 联系我们](#-联系我们)
|
27
|
-
|
28
|
-
## 🌟 特性亮点
|
29
|
-
- 🔍 **智能代码分析**:深入理解代码结构和逻辑关系
|
30
|
-
- 📝 **精准测试生成**:针对不同代码场景生成最佳测试策略
|
31
|
-
- 🔄 **持续优化**:根据测试结果不断完善测试用例
|
32
|
-
- 💡 **AI驱动**:利用先进的大语言模型提升测试质量
|
33
|
-
|
34
|
-
## 🚀 核心功能
|
35
|
-
- 🤖 **自动化测试生成**:自动识别未覆盖代码,生成完整测试用例,直到100%代码覆盖通过
|
36
|
-
- 🛠️ **智能错误修复**:检测并修复单元测试中的语法错误、逻辑错误及断言失败
|
37
|
-
- 📊 **覆盖率可视化**:实时展示行/语句/函数/分支覆盖率,通过颜色编码直观呈现测试状态
|
38
|
-
- 🔄 **全流程自动化**:从环境初始化、依赖安装到持续测试优化,全程无需人工干预
|
39
|
-
|
40
|
-
## 💎 核心价值
|
41
|
-
- ⚡ **提升开发效率**:再也**不用手写**单测代码,专注核心业务逻辑开发
|
42
|
-
- 🛡️ **保障代码质量**:确保测试覆盖率达标,降低线上bug风险
|
43
|
-
- 🧩 **简化测试流程**:无需深入掌握测试框架细节,一键启动全自动化测试流程
|
44
|
-
- 👁️ **可视化反馈**:通过直观的覆盖率报表和进度展示,让测试状态一目了然
|
45
|
-
|
46
|
-
## 📝 使用指南
|
47
|
-
1. 📂 打开项目文件夹
|
48
|
-
2. ▶️ 启动吃豆豆智能体
|
49
|
-
3. 📈 查看实时测试进度和覆盖率报告
|
50
|
-
4. 🧠 智能体自动编写代码
|
51
|
-
|
52
|
-
## 🔧 适用范围
|
53
|
-
- 🔌 支持多种调用方式:VSCode插件、CLI命令
|
54
|
-
- 💻 支持多种编程语言:TypeScript、JavaScript等
|
55
|
-
- 🧪 支持主流测试框架:Jest、Vitest等
|
56
|
-
|
57
|
-
---
|
58
|
-
|
59
|
-
## 🚀 开始使用
|
60
|
-
准备好提升你的开发效率了吗?立即安装吃豆豆单测智能体,让测试工作变得轻松简单!
|
61
|
-
|
62
|
-
## 💬 联系我们
|
63
|
-
如有任何问题或建议,可以给我发邮件 9160294@qq.com。
|
64
|
-
|
65
|
-
<p align="center">
|
66
|
-
<strong>© 2025 吃豆豆单测智能体 - 让单元测试自动化、智能化</strong>
|
1
|
+
# 🎮 吃豆豆:单测智能体
|
2
|
+
|
3
|
+
<p align="center">
|
4
|
+
<img src="https://aicoder.jianguoke.cn/assets/images/logo.png" alt="吃豆豆logo" width="120" height="120">
|
5
|
+
</p>
|
6
|
+
|
7
|
+
<p align="center">
|
8
|
+
<strong>自动生成高质量单元测试,让代码覆盖率轻松达到100% 🚀</strong>
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<p align="center">
|
12
|
+
<em>你负责编写源代码,我负责编写测试代码,解放双手,专注核心业务逻辑开发 💻</em>
|
13
|
+
</p>
|
14
|
+
|
15
|
+
---
|
16
|
+
|
17
|
+
## 目录
|
18
|
+
- [🎮 吃豆豆:单测智能体](#-吃豆豆单测智能体)
|
19
|
+
- [目录](#目录)
|
20
|
+
- [🌟 特性亮点](#-特性亮点)
|
21
|
+
- [🚀 核心功能](#-核心功能)
|
22
|
+
- [💎 核心价值](#-核心价值)
|
23
|
+
- [📝 使用指南](#-使用指南)
|
24
|
+
- [🔧 适用范围](#-适用范围)
|
25
|
+
- [🚀 开始使用](#-开始使用)
|
26
|
+
- [💬 联系我们](#-联系我们)
|
27
|
+
|
28
|
+
## 🌟 特性亮点
|
29
|
+
- 🔍 **智能代码分析**:深入理解代码结构和逻辑关系
|
30
|
+
- 📝 **精准测试生成**:针对不同代码场景生成最佳测试策略
|
31
|
+
- 🔄 **持续优化**:根据测试结果不断完善测试用例
|
32
|
+
- 💡 **AI驱动**:利用先进的大语言模型提升测试质量
|
33
|
+
|
34
|
+
## 🚀 核心功能
|
35
|
+
- 🤖 **自动化测试生成**:自动识别未覆盖代码,生成完整测试用例,直到100%代码覆盖通过
|
36
|
+
- 🛠️ **智能错误修复**:检测并修复单元测试中的语法错误、逻辑错误及断言失败
|
37
|
+
- 📊 **覆盖率可视化**:实时展示行/语句/函数/分支覆盖率,通过颜色编码直观呈现测试状态
|
38
|
+
- 🔄 **全流程自动化**:从环境初始化、依赖安装到持续测试优化,全程无需人工干预
|
39
|
+
|
40
|
+
## 💎 核心价值
|
41
|
+
- ⚡ **提升开发效率**:再也**不用手写**单测代码,专注核心业务逻辑开发
|
42
|
+
- 🛡️ **保障代码质量**:确保测试覆盖率达标,降低线上bug风险
|
43
|
+
- 🧩 **简化测试流程**:无需深入掌握测试框架细节,一键启动全自动化测试流程
|
44
|
+
- 👁️ **可视化反馈**:通过直观的覆盖率报表和进度展示,让测试状态一目了然
|
45
|
+
|
46
|
+
## 📝 使用指南
|
47
|
+
1. 📂 打开项目文件夹
|
48
|
+
2. ▶️ 启动吃豆豆智能体
|
49
|
+
3. 📈 查看实时测试进度和覆盖率报告
|
50
|
+
4. 🧠 智能体自动编写代码
|
51
|
+
|
52
|
+
## 🔧 适用范围
|
53
|
+
- 🔌 支持多种调用方式:VSCode插件、CLI命令
|
54
|
+
- 💻 支持多种编程语言:TypeScript、JavaScript等
|
55
|
+
- 🧪 支持主流测试框架:Jest、Vitest等
|
56
|
+
|
57
|
+
---
|
58
|
+
|
59
|
+
## 🚀 开始使用
|
60
|
+
准备好提升你的开发效率了吗?立即安装吃豆豆单测智能体,让测试工作变得轻松简单!
|
61
|
+
|
62
|
+
## 💬 联系我们
|
63
|
+
如有任何问题或建议,可以给我发邮件 9160294@qq.com。
|
64
|
+
|
65
|
+
<p align="center">
|
66
|
+
<strong>© 2025 吃豆豆单测智能体 - 让单元测试自动化、智能化</strong>
|
67
67
|
</p>
|
package/dist/agents/jianguoke.js
CHANGED
@@ -47,7 +47,7 @@ const setConversationId = (id) => {
|
|
47
47
|
};
|
48
48
|
exports.setConversationId = setConversationId;
|
49
49
|
async function postData(url, data) {
|
50
|
-
const token = (0, user_1.getTokenStorage)().get();
|
50
|
+
const token = await (0, user_1.getTokenStorage)().get();
|
51
51
|
try {
|
52
52
|
// 发送POST请求
|
53
53
|
const response = await fetch(url, {
|
@@ -109,7 +109,7 @@ async function startAgent(name, question, vars, withConversation, ctrl, onMessag
|
|
109
109
|
let msg = '';
|
110
110
|
let lastMsgNo = 0;
|
111
111
|
const baseURL = process.env.AI_SERVER || DEFAULT_SERVER;
|
112
|
-
const token = (0, user_1.getTokenStorage)().get();
|
112
|
+
const token = await (0, user_1.getTokenStorage)().get();
|
113
113
|
console.log(`Starting agent: ${baseURL}, token: ${token}`);
|
114
114
|
await (0, fetch_sse_1.fetchEventData)(`${baseURL}/api/agent/exec/aicoder`, {
|
115
115
|
method: 'POST',
|
package/dist/agents/prompt.js
CHANGED
@@ -10,32 +10,32 @@ const path_1 = __importDefault(require("path"));
|
|
10
10
|
const tools_1 = require("../tools");
|
11
11
|
const MAXLEN = 10000;
|
12
12
|
function fixErrorPrompt(error) {
|
13
|
-
return `单测执行失败,日志如下:
|
14
|
-
\`\`\`
|
15
|
-
${error.slice(0, MAXLEN)}
|
16
|
-
\`\`\`
|
13
|
+
return `单测执行失败,日志如下:
|
14
|
+
\`\`\`
|
15
|
+
${error.slice(0, MAXLEN)}
|
16
|
+
\`\`\`
|
17
17
|
`;
|
18
18
|
}
|
19
19
|
// 提示词管理,根据任务合成特定提示词
|
20
20
|
async function fixCodePrompt(failedTest) {
|
21
|
-
return `测试文件"${path_1.default.resolve(failedTest.testFilePath)}"${failedTest.testName ? '里的"' + failedTest.testName + '"' : ''}执行失败.
|
22
|
-
|
23
|
-
错误信息为:
|
24
|
-
\`\`\`
|
25
|
-
${failedTest.errorMessages.join('\n')}
|
26
|
-
\`\`\`
|
27
|
-
|
28
|
-
测试文件内容如下:
|
29
|
-
\`\`\`
|
30
|
-
${await (0, tools_1.readFile)(failedTest.testFilePath, 'utf8', true)}
|
21
|
+
return `测试文件"${path_1.default.resolve(failedTest.testFilePath)}"${failedTest.testName ? '里的"' + failedTest.testName + '"' : ''}执行失败.
|
22
|
+
|
23
|
+
错误信息为:
|
24
|
+
\`\`\`
|
25
|
+
${failedTest.errorMessages.join('\n')}
|
26
|
+
\`\`\`
|
27
|
+
|
28
|
+
测试文件内容如下:
|
29
|
+
\`\`\`
|
30
|
+
${await (0, tools_1.readFile)(failedTest.testFilePath, 'utf8', true)}
|
31
31
|
\`\`\``;
|
32
32
|
}
|
33
33
|
async function addUncoveredLinePrompt(srcPath, report) {
|
34
|
-
return `源文件"${path_1.default.resolve(srcPath)}"的单元测试覆盖率是${report.coverage.lines.pct}%, 还有"${report.uncoveredLines.join(',')}"行未覆盖单测。
|
35
|
-
|
36
|
-
源文件内容如下:
|
37
|
-
\`\`\`
|
38
|
-
${await (0, tools_1.readFile)(srcPath, 'utf8', true)}
|
34
|
+
return `源文件"${path_1.default.resolve(srcPath)}"的单元测试覆盖率是${report.coverage.lines.pct}%, 还有"${report.uncoveredLines.join(',')}"行未覆盖单测。
|
35
|
+
|
36
|
+
源文件内容如下:
|
37
|
+
\`\`\`
|
38
|
+
${await (0, tools_1.readFile)(srcPath, 'utf8', true)}
|
39
39
|
\`\`\``;
|
40
40
|
}
|
41
41
|
//# sourceMappingURL=prompt.js.map
|
package/dist/cli.js
CHANGED
File without changes
|
package/dist/program.js
CHANGED
@@ -6,15 +6,54 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
7
7
|
const commander_1 = require("commander");
|
8
8
|
const path_1 = __importDefault(require("path"));
|
9
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
10
|
+
const os_1 = __importDefault(require("os"));
|
9
11
|
const testAgent_1 = require("./testAgent");
|
10
12
|
const tools_1 = require("./tools");
|
11
13
|
const view_1 = require("./view");
|
12
|
-
|
14
|
+
const user_1 = require("./user");
|
15
|
+
const view_2 = require("./view");
|
13
16
|
const program = new commander_1.Command();
|
14
17
|
program.name('testAgent').description('吃豆豆:单测智能体').version('1.0.1');
|
15
18
|
// 屏蔽调试日志
|
16
19
|
const logHandler = console.log;
|
17
20
|
console.log = () => { };
|
21
|
+
const tokenFile = path_1.default.join(os_1.default.homedir(), '.aiworktools');
|
22
|
+
// token存储在系统用户文件夹的.ai-worktools
|
23
|
+
(0, user_1.setTokenStorage)({
|
24
|
+
get: async () => {
|
25
|
+
let exists = false;
|
26
|
+
try {
|
27
|
+
await promises_1.default.access(tokenFile);
|
28
|
+
exists = true;
|
29
|
+
}
|
30
|
+
catch (err) {
|
31
|
+
return false;
|
32
|
+
}
|
33
|
+
if (exists) {
|
34
|
+
return JSON.parse(await promises_1.default.readFile(tokenFile, 'utf-8')).token;
|
35
|
+
}
|
36
|
+
return null;
|
37
|
+
},
|
38
|
+
set: async (token) => {
|
39
|
+
await promises_1.default.writeFile(tokenFile, JSON.stringify({ token }), 'utf-8');
|
40
|
+
},
|
41
|
+
clear: async () => {
|
42
|
+
await promises_1.default.unlink(tokenFile);
|
43
|
+
}
|
44
|
+
});
|
45
|
+
program
|
46
|
+
.command('login')
|
47
|
+
.description('登录')
|
48
|
+
.action(async () => {
|
49
|
+
await (0, user_1.login)('ai-worktools-cli');
|
50
|
+
});
|
51
|
+
program
|
52
|
+
.command('logout')
|
53
|
+
.description('登出')
|
54
|
+
.action(async () => {
|
55
|
+
await (0, user_1.logout)();
|
56
|
+
});
|
18
57
|
program
|
19
58
|
.command('start [projectRoot]')
|
20
59
|
.description('启动单测智能体,根据源代码文件开始补全单测代码,直到覆盖率100%')
|
@@ -27,7 +66,7 @@ program
|
|
27
66
|
.option('--fullCoverageEnd <boolean>', '是否在达到100%覆盖率后结束', (txt) => txt === 'true', true)
|
28
67
|
.action(async (projectRoot, options) => {
|
29
68
|
let projectConfig;
|
30
|
-
(0, testAgent_1.startTestAgent)({
|
69
|
+
await (0, testAgent_1.startTestAgent)({
|
31
70
|
...options,
|
32
71
|
projectRoot: projectRoot || options.projectRoot,
|
33
72
|
onReady: async (project) => {
|
@@ -40,6 +79,7 @@ program
|
|
40
79
|
}
|
41
80
|
}
|
42
81
|
});
|
82
|
+
(0, view_1.disposeTimer)();
|
43
83
|
});
|
44
84
|
program
|
45
85
|
.command('showCoverage [projectRoot]')
|
@@ -48,7 +88,7 @@ program
|
|
48
88
|
.option('-c, --coverageDir <string>', '覆盖率报告文件夹', 'coverage')
|
49
89
|
.action(async (projectRoot, options) => {
|
50
90
|
await (0, tools_1.runJestTests)(projectRoot || options.projectRoot);
|
51
|
-
|
91
|
+
await (0, view_2.showCoverageView)(path_1.default.join(options.projectRoot, options.coverageDir));
|
52
92
|
});
|
53
93
|
exports.default = program;
|
54
94
|
//# sourceMappingURL=program.js.map
|
package/dist/tools/git.js
CHANGED
@@ -43,7 +43,7 @@ async function clone(repoPath, remoteUrl, localPath, branch) {
|
|
43
43
|
console.log(`成功克隆仓库: ${remoteUrl} 到 ${clonePath}`);
|
44
44
|
}
|
45
45
|
catch (error) {
|
46
|
-
console.
|
46
|
+
console.log(`克隆仓库失败: ${error}`);
|
47
47
|
throw error;
|
48
48
|
}
|
49
49
|
}
|
@@ -59,7 +59,7 @@ async function add(files = '.', repoPath) {
|
|
59
59
|
console.log(`成功添加文件到暂存区: ${Array.isArray(files) ? files.join(', ') : files}`);
|
60
60
|
}
|
61
61
|
catch (error) {
|
62
|
-
console.
|
62
|
+
console.log(`添加文件失败: ${error}`);
|
63
63
|
throw error;
|
64
64
|
}
|
65
65
|
}
|
@@ -73,10 +73,10 @@ async function commit(repoPath, message, files) {
|
|
73
73
|
const git = createGitInstance(repoPath);
|
74
74
|
try {
|
75
75
|
const result = files ? await git.commit(message, files) : await git.commit(message);
|
76
|
-
console.log(`提交成功: ${result
|
76
|
+
console.log(`提交成功: ${result?.summary}`);
|
77
77
|
}
|
78
78
|
catch (error) {
|
79
|
-
console.
|
79
|
+
console.log(`提交失败: ${error}`);
|
80
80
|
throw error;
|
81
81
|
}
|
82
82
|
}
|
@@ -91,10 +91,10 @@ async function pull(repoPath, remote = 'origin', branch) {
|
|
91
91
|
try {
|
92
92
|
const currentBranch = branch || (await git.branchLocal()).current;
|
93
93
|
const result = await git.pull(remote, currentBranch);
|
94
|
-
console.log(`拉取成功: ${result
|
94
|
+
console.log(`拉取成功: ${result?.summary}`);
|
95
95
|
}
|
96
96
|
catch (error) {
|
97
|
-
console.
|
97
|
+
console.log(`拉取失败: ${error}`);
|
98
98
|
throw error;
|
99
99
|
}
|
100
100
|
}
|
@@ -112,7 +112,7 @@ async function push(repoPath, remote = 'origin', branch) {
|
|
112
112
|
console.log(`推送成功: ${remote}/${currentBranch}`);
|
113
113
|
}
|
114
114
|
catch (error) {
|
115
|
-
console.
|
115
|
+
console.log(`推送失败: ${error}`);
|
116
116
|
// 处理常见的推送失败情况(如远程分支有新提交)
|
117
117
|
if (error.message.includes('non-fast-forward')) {
|
118
118
|
console.log('提示: 远程分支有新提交,建议先拉取再推送');
|
@@ -132,7 +132,7 @@ async function createBranch(branchName, repoPath) {
|
|
132
132
|
console.log(`成功创建并切换到分支: ${branchName}`);
|
133
133
|
}
|
134
134
|
catch (error) {
|
135
|
-
console.
|
135
|
+
console.log(`创建分支失败: ${error}`);
|
136
136
|
throw error;
|
137
137
|
}
|
138
138
|
}
|
@@ -148,7 +148,7 @@ async function switchBranch(branchName, repoPath) {
|
|
148
148
|
console.log(`成功切换到分支: ${branchName}`);
|
149
149
|
}
|
150
150
|
catch (error) {
|
151
|
-
console.
|
151
|
+
console.log(`切换分支失败: ${error}`);
|
152
152
|
throw error;
|
153
153
|
}
|
154
154
|
}
|
@@ -162,7 +162,7 @@ async function getStatus(repoPath) {
|
|
162
162
|
return await git.status();
|
163
163
|
}
|
164
164
|
catch (error) {
|
165
|
-
console.
|
165
|
+
console.log(`获取仓库状态失败: ${error}`);
|
166
166
|
throw error;
|
167
167
|
}
|
168
168
|
}
|
@@ -179,7 +179,7 @@ async function addRemote(remoteName, remoteUrl, repoPath) {
|
|
179
179
|
console.log(`成功添加远程仓库: ${remoteName} -> ${remoteUrl}`);
|
180
180
|
}
|
181
181
|
catch (error) {
|
182
|
-
console.
|
182
|
+
console.log(`添加远程仓库失败: ${error}`);
|
183
183
|
throw error;
|
184
184
|
}
|
185
185
|
}
|
@@ -197,7 +197,7 @@ async function setUserInfo(username, email, repoPath) {
|
|
197
197
|
console.log(`已设置用户信息: ${username} <${email}>`);
|
198
198
|
}
|
199
199
|
catch (error) {
|
200
|
-
console.
|
200
|
+
console.log(`设置用户信息失败: ${error}`);
|
201
201
|
throw error;
|
202
202
|
}
|
203
203
|
}
|
package/dist/tools/index.js
CHANGED
@@ -54,7 +54,7 @@ async function wait(timerout = 1000) {
|
|
54
54
|
}, timerout);
|
55
55
|
});
|
56
56
|
}
|
57
|
-
function runTests(projectRoot, testFramework, coverageDir, configPath) {
|
57
|
+
async function runTests(projectRoot, testFramework, coverageDir, configPath) {
|
58
58
|
if (testFramework === 'jest') {
|
59
59
|
return (0, jest_1.runJestTests)(projectRoot, coverageDir, configPath);
|
60
60
|
}
|
package/dist/tools/package.js
CHANGED
@@ -279,7 +279,8 @@ async function checkDependency(projectRoot, packageName, isDev = false) {
|
|
279
279
|
async function setupTestEnvironment(projectRoot, packageManager, devLanguage, testFramework) {
|
280
280
|
// 验证参数
|
281
281
|
validateParameters(packageManager, devLanguage, testFramework);
|
282
|
-
const
|
282
|
+
const resolvedPath = path_1.default.resolve(projectRoot);
|
283
|
+
const packageJsonPath = path_1.default.join(resolvedPath, 'package.json');
|
283
284
|
// 异步检查并初始化package.json
|
284
285
|
try {
|
285
286
|
await promises_1.default.access(packageJsonPath);
|
@@ -287,29 +288,29 @@ async function setupTestEnvironment(projectRoot, packageManager, devLanguage, te
|
|
287
288
|
catch {
|
288
289
|
console.log('未找到package.json,初始化项目...');
|
289
290
|
const initCommand = packageManager === 'yarn' ? 'yarn init -y' : `${packageManager} init -y`;
|
290
|
-
await executeCommand(
|
291
|
+
await executeCommand(resolvedPath, initCommand, '初始化项目');
|
291
292
|
}
|
292
293
|
// 如果是TypeScript项目,检查并安装TypeScript
|
293
294
|
if (devLanguage === 'typescript') {
|
294
295
|
console.log('检查TypeScript是否安装...');
|
295
|
-
const hasTypeScript = await checkDependency(
|
296
|
+
const hasTypeScript = await checkDependency(resolvedPath, 'typescript', true);
|
296
297
|
if (!hasTypeScript) {
|
297
298
|
const installCmd = getInstallCommand(['typescript', '@types/node'], true, packageManager);
|
298
|
-
await executeCommand(
|
299
|
+
await executeCommand(resolvedPath, installCmd, '安装TypeScript及类型定义');
|
299
300
|
// 异步检查并创建tsconfig.json
|
300
|
-
const tsconfigPath = path_1.default.join(
|
301
|
+
const tsconfigPath = path_1.default.join(resolvedPath, 'tsconfig.json');
|
301
302
|
try {
|
302
303
|
await promises_1.default.access(tsconfigPath);
|
303
304
|
}
|
304
305
|
catch {
|
305
|
-
await executeCommand(
|
306
|
+
await executeCommand(resolvedPath, 'npx tsc --init', '创建tsconfig.json');
|
306
307
|
}
|
307
308
|
}
|
308
309
|
}
|
309
310
|
// 检查并安装测试框架
|
310
311
|
console.log(`检查${testFramework}是否安装...`);
|
311
|
-
const hasTestFramework = await checkDependency(
|
312
|
-
const hasTestTypes = devLanguage === 'typescript' ? await checkDependency(
|
312
|
+
const hasTestFramework = await checkDependency(resolvedPath, testFramework, true);
|
313
|
+
const hasTestTypes = devLanguage === 'typescript' ? await checkDependency(resolvedPath, `@types/${testFramework}`, true) : true;
|
313
314
|
const packagesToInstall = [];
|
314
315
|
if (!hasTestFramework) {
|
315
316
|
packagesToInstall.push(testFramework);
|
package/dist/tools/project.js
CHANGED
@@ -4,6 +4,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
6
|
exports.detectProjectConfig = detectProjectConfig;
|
7
|
+
exports.checkProjectInstalled = checkProjectInstalled;
|
8
|
+
exports.detectPackageManager = detectPackageManager;
|
9
|
+
exports.detectLanguages = detectLanguages;
|
10
|
+
exports.detectSrcDir = detectSrcDir;
|
11
|
+
exports.detectTestConfig = detectTestConfig;
|
12
|
+
exports.detectExistingTestPatterns = detectExistingTestPatterns;
|
13
|
+
exports.getDependencies = getDependencies;
|
14
|
+
exports.detectProjectType = detectProjectType;
|
15
|
+
exports.detectFramework = detectFramework;
|
7
16
|
const promises_1 = __importDefault(require("fs/promises"));
|
8
17
|
const path_1 = __importDefault(require("path"));
|
9
18
|
/**
|
@@ -207,13 +216,13 @@ async function detectTestConfig(projectRoot, config) {
|
|
207
216
|
];
|
208
217
|
// 根据项目语言生成对应扩展名的模式
|
209
218
|
const extensions = new Set();
|
210
|
-
if (config.languages
|
219
|
+
if (config.languages?.includes('javascript')) {
|
211
220
|
extensions.add('js');
|
212
221
|
}
|
213
|
-
if (config.languages
|
222
|
+
if (config.languages?.includes('typescript')) {
|
214
223
|
extensions.add('ts');
|
215
224
|
}
|
216
|
-
if (config.languages
|
225
|
+
if (config.languages?.includes('coffeescript')) {
|
217
226
|
extensions.add('coffee');
|
218
227
|
}
|
219
228
|
// 默认为js(如果没有检测到语言)
|
@@ -404,11 +413,11 @@ async function calculateScore(projectRoot, type) {
|
|
404
413
|
}
|
405
414
|
});
|
406
415
|
// 检查配置文件匹配(并行处理)
|
407
|
-
const configFileChecks = features.configFiles.map((file) => fileExists(path_1.default.join(
|
416
|
+
const configFileChecks = features.configFiles.map((file) => fileExists(path_1.default.join(projectRoot, file)));
|
408
417
|
const configFileResults = await Promise.all(configFileChecks);
|
409
418
|
score += configFileResults.filter(Boolean).length * 2; // 配置文件权重中等
|
410
419
|
// 检查目录结构匹配(并行处理)
|
411
|
-
const directoryChecks = features.directories.map((dir) => directoryExists(path_1.default.join(
|
420
|
+
const directoryChecks = features.directories.map((dir) => directoryExists(path_1.default.join(projectRoot, dir)));
|
412
421
|
const directoryResults = await Promise.all(directoryChecks);
|
413
422
|
score += directoryResults.filter(Boolean).length * 1; // 目录权重较低
|
414
423
|
return score;
|
@@ -430,11 +439,6 @@ async function detectProjectType(projectRoot, config) {
|
|
430
439
|
backend: backendScore,
|
431
440
|
cli: cliScore
|
432
441
|
};
|
433
|
-
// 判断是否为全栈项目
|
434
|
-
const isFullstack = frontendScore > 0 && backendScore > 0 && Math.abs(frontendScore - backendScore) < frontendScore * 0.5;
|
435
|
-
if (isFullstack) {
|
436
|
-
config.projectType = 'fullstack';
|
437
|
-
}
|
438
442
|
// 确定最高分数的项目类型
|
439
443
|
const maxScore = Math.max(frontendScore, backendScore, cliScore);
|
440
444
|
if (maxScore === 0) {
|
@@ -449,6 +453,11 @@ async function detectProjectType(projectRoot, config) {
|
|
449
453
|
else {
|
450
454
|
config.projectType = 'cli';
|
451
455
|
}
|
456
|
+
// 判断是否为全栈项目
|
457
|
+
const isFullstack = frontendScore > 0 && backendScore > 0 && Math.abs(frontendScore - backendScore) < frontendScore * 0.5;
|
458
|
+
if (isFullstack) {
|
459
|
+
config.projectType = 'fullstack';
|
460
|
+
}
|
452
461
|
}
|
453
462
|
/**
|
454
463
|
* 检测框架信息
|
package/dist/user.js
CHANGED
@@ -56,8 +56,25 @@ const util_1 = __importDefault(require("util"));
|
|
56
56
|
const child_process_1 = require("child_process");
|
57
57
|
// 转换exec为Promise形式,便于async/await使用
|
58
58
|
const execAsync = util_1.default.promisify(child_process_1.exec);
|
59
|
-
let tokenStorage
|
60
|
-
|
59
|
+
let tokenStorage = {
|
60
|
+
get: async () => {
|
61
|
+
return localStorage.getItem('token');
|
62
|
+
},
|
63
|
+
set: async (token) => {
|
64
|
+
localStorage.setItem('token', token);
|
65
|
+
},
|
66
|
+
clear: async () => {
|
67
|
+
localStorage.removeItem('token');
|
68
|
+
}
|
69
|
+
};
|
70
|
+
let environment = {
|
71
|
+
showInfoMessage: async (message) => {
|
72
|
+
console.log(message);
|
73
|
+
},
|
74
|
+
showErrorMessage: async (message) => {
|
75
|
+
console.error(message);
|
76
|
+
}
|
77
|
+
};
|
61
78
|
function setEnvironment(env) {
|
62
79
|
environment = env;
|
63
80
|
}
|
@@ -98,54 +115,54 @@ function startCallbackServer(state, port) {
|
|
98
115
|
if (callbackState !== state) {
|
99
116
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
100
117
|
res.writeHead(403, { 'Content-Type': 'text/html; charset=utf-8' });
|
101
|
-
res.end(`
|
102
|
-
<html>
|
103
|
-
<head>
|
104
|
-
<style>
|
105
|
-
body {
|
106
|
-
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
107
|
-
display: flex;
|
108
|
-
flex-direction: column;
|
109
|
-
align-items: center;
|
110
|
-
justify-content: center;
|
111
|
-
height: 100vh;
|
112
|
-
margin: 0;
|
113
|
-
background-color: #f8f9fa;
|
114
|
-
color: #333;
|
115
|
-
}
|
116
|
-
.card {
|
117
|
-
background-color: white;
|
118
|
-
border-radius: 12px;
|
119
|
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
120
|
-
padding: 2rem;
|
121
|
-
text-align: center;
|
122
|
-
max-width: 400px;
|
123
|
-
width: 90%;
|
124
|
-
border-left: 4px solid #dc3545;
|
125
|
-
}
|
126
|
-
h1 {
|
127
|
-
color: #dc3545;
|
128
|
-
margin-bottom: 1rem;
|
129
|
-
font-size: 1.8rem;
|
130
|
-
}
|
131
|
-
p {
|
132
|
-
margin: 0.5rem 0;
|
133
|
-
color: #6c757d;
|
134
|
-
}
|
135
|
-
.countdown {
|
136
|
-
font-weight: bold;
|
137
|
-
color: #dc3545;
|
138
|
-
font-size: 1.2rem;
|
139
|
-
}
|
140
|
-
</style>
|
141
|
-
</head>
|
142
|
-
<body>
|
143
|
-
<div class="card">
|
144
|
-
<h1>登录验证失败</h1>
|
145
|
-
<p>无效的状态参数,可能存在安全风险</p>
|
146
|
-
</div>
|
147
|
-
</body>
|
148
|
-
</html>
|
118
|
+
res.end(`
|
119
|
+
<html>
|
120
|
+
<head>
|
121
|
+
<style>
|
122
|
+
body {
|
123
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
124
|
+
display: flex;
|
125
|
+
flex-direction: column;
|
126
|
+
align-items: center;
|
127
|
+
justify-content: center;
|
128
|
+
height: 100vh;
|
129
|
+
margin: 0;
|
130
|
+
background-color: #f8f9fa;
|
131
|
+
color: #333;
|
132
|
+
}
|
133
|
+
.card {
|
134
|
+
background-color: white;
|
135
|
+
border-radius: 12px;
|
136
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
137
|
+
padding: 2rem;
|
138
|
+
text-align: center;
|
139
|
+
max-width: 400px;
|
140
|
+
width: 90%;
|
141
|
+
border-left: 4px solid #dc3545;
|
142
|
+
}
|
143
|
+
h1 {
|
144
|
+
color: #dc3545;
|
145
|
+
margin-bottom: 1rem;
|
146
|
+
font-size: 1.8rem;
|
147
|
+
}
|
148
|
+
p {
|
149
|
+
margin: 0.5rem 0;
|
150
|
+
color: #6c757d;
|
151
|
+
}
|
152
|
+
.countdown {
|
153
|
+
font-weight: bold;
|
154
|
+
color: #dc3545;
|
155
|
+
font-size: 1.2rem;
|
156
|
+
}
|
157
|
+
</style>
|
158
|
+
</head>
|
159
|
+
<body>
|
160
|
+
<div class="card">
|
161
|
+
<h1>登录验证失败</h1>
|
162
|
+
<p>无效的状态参数,可能存在安全风险</p>
|
163
|
+
</div>
|
164
|
+
</body>
|
165
|
+
</html>
|
149
166
|
`);
|
150
167
|
reject(new Error('登录验证失败:无效的状态参数'));
|
151
168
|
server.close();
|
@@ -160,121 +177,121 @@ function startCallbackServer(state, port) {
|
|
160
177
|
// 返回包含自动刷新和关闭逻辑的HTML
|
161
178
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
162
179
|
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
163
|
-
res.end(`
|
164
|
-
<html>
|
165
|
-
<head>
|
166
|
-
<title>登录成功</title>
|
167
|
-
<style>
|
168
|
-
body {
|
169
|
-
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
170
|
-
display: flex;
|
171
|
-
flex-direction: column;
|
172
|
-
align-items: center;
|
173
|
-
justify-content: center;
|
174
|
-
height: 100vh;
|
175
|
-
margin: 0;
|
176
|
-
background-color: #f8f9fa;
|
177
|
-
color: #333;
|
178
|
-
}
|
179
|
-
.card {
|
180
|
-
background-color: white;
|
181
|
-
border-radius: 12px;
|
182
|
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
183
|
-
padding: 2rem;
|
184
|
-
text-align: center;
|
185
|
-
max-width: 400px;
|
186
|
-
width: 90%;
|
187
|
-
border-left: 4px solid #28a745;
|
188
|
-
}
|
189
|
-
h1 {
|
190
|
-
color: #28a745;
|
191
|
-
margin-bottom: 1rem;
|
192
|
-
font-size: 1.8rem;
|
193
|
-
}
|
194
|
-
p {
|
195
|
-
margin: 0.5rem 0;
|
196
|
-
color: #6c757d;
|
197
|
-
}
|
198
|
-
.countdown {
|
199
|
-
font-weight: bold;
|
200
|
-
color: #28a745;
|
201
|
-
font-size: 1.2rem;
|
202
|
-
}
|
203
|
-
.checkmark {
|
204
|
-
font-size: 3rem;
|
205
|
-
color: #28a745;
|
206
|
-
margin-bottom: 1rem;
|
207
|
-
}
|
208
|
-
</style>
|
209
|
-
</head>
|
210
|
-
<body>
|
211
|
-
<div class="card">
|
212
|
-
<div class="checkmark">✓</div>
|
213
|
-
<h1>登录成功!</h1>
|
214
|
-
<p>您可以关闭此窗口返回应用。</p>
|
215
|
-
</div>
|
216
|
-
</body>
|
217
|
-
</html>
|
180
|
+
res.end(`
|
181
|
+
<html>
|
182
|
+
<head>
|
183
|
+
<title>登录成功</title>
|
184
|
+
<style>
|
185
|
+
body {
|
186
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
187
|
+
display: flex;
|
188
|
+
flex-direction: column;
|
189
|
+
align-items: center;
|
190
|
+
justify-content: center;
|
191
|
+
height: 100vh;
|
192
|
+
margin: 0;
|
193
|
+
background-color: #f8f9fa;
|
194
|
+
color: #333;
|
195
|
+
}
|
196
|
+
.card {
|
197
|
+
background-color: white;
|
198
|
+
border-radius: 12px;
|
199
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
200
|
+
padding: 2rem;
|
201
|
+
text-align: center;
|
202
|
+
max-width: 400px;
|
203
|
+
width: 90%;
|
204
|
+
border-left: 4px solid #28a745;
|
205
|
+
}
|
206
|
+
h1 {
|
207
|
+
color: #28a745;
|
208
|
+
margin-bottom: 1rem;
|
209
|
+
font-size: 1.8rem;
|
210
|
+
}
|
211
|
+
p {
|
212
|
+
margin: 0.5rem 0;
|
213
|
+
color: #6c757d;
|
214
|
+
}
|
215
|
+
.countdown {
|
216
|
+
font-weight: bold;
|
217
|
+
color: #28a745;
|
218
|
+
font-size: 1.2rem;
|
219
|
+
}
|
220
|
+
.checkmark {
|
221
|
+
font-size: 3rem;
|
222
|
+
color: #28a745;
|
223
|
+
margin-bottom: 1rem;
|
224
|
+
}
|
225
|
+
</style>
|
226
|
+
</head>
|
227
|
+
<body>
|
228
|
+
<div class="card">
|
229
|
+
<div class="checkmark">✓</div>
|
230
|
+
<h1>登录成功!</h1>
|
231
|
+
<p>您可以关闭此窗口返回应用。</p>
|
232
|
+
</div>
|
233
|
+
</body>
|
234
|
+
</html>
|
218
235
|
`);
|
219
236
|
resolve(token);
|
220
237
|
}
|
221
238
|
else {
|
222
239
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
223
240
|
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
|
224
|
-
res.end(`
|
225
|
-
<html>
|
226
|
-
<head>
|
227
|
-
<style>
|
228
|
-
body {
|
229
|
-
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
230
|
-
display: flex;
|
231
|
-
flex-direction: column;
|
232
|
-
align-items: center;
|
233
|
-
justify-content: center;
|
234
|
-
height: 100vh;
|
235
|
-
margin: 0;
|
236
|
-
background-color: #f8f9fa;
|
237
|
-
color: #333;
|
238
|
-
}
|
239
|
-
.card {
|
240
|
-
background-color: white;
|
241
|
-
border-radius: 12px;
|
242
|
-
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
243
|
-
padding: 2rem;
|
244
|
-
text-align: center;
|
245
|
-
max-width: 400px;
|
246
|
-
width: 90%;
|
247
|
-
border-left: 4px solid #dc3545;
|
248
|
-
}
|
249
|
-
h1 {
|
250
|
-
color: #dc3545;
|
251
|
-
margin-bottom: 1rem;
|
252
|
-
font-size: 1.8rem;
|
253
|
-
}
|
254
|
-
p {
|
255
|
-
margin: 0.5rem 0;
|
256
|
-
color: #6c757d;
|
257
|
-
}
|
258
|
-
.countdown {
|
259
|
-
font-weight: bold;
|
260
|
-
color: #dc3545;
|
261
|
-
font-size: 1.2rem;
|
262
|
-
}
|
263
|
-
.error-icon {
|
264
|
-
font-size: 3rem;
|
265
|
-
color: #dc3545;
|
266
|
-
margin-bottom: 1rem;
|
267
|
-
}
|
268
|
-
</style>
|
269
|
-
</head>
|
270
|
-
<body>
|
271
|
-
<div class="card">
|
272
|
-
<div class="error-icon">✗</div>
|
273
|
-
<h1>登录失败</h1>
|
274
|
-
<p>未收到有效的认证信息</p>
|
275
|
-
</div>
|
276
|
-
</body>
|
277
|
-
</html>
|
241
|
+
res.end(`
|
242
|
+
<html>
|
243
|
+
<head>
|
244
|
+
<style>
|
245
|
+
body {
|
246
|
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
247
|
+
display: flex;
|
248
|
+
flex-direction: column;
|
249
|
+
align-items: center;
|
250
|
+
justify-content: center;
|
251
|
+
height: 100vh;
|
252
|
+
margin: 0;
|
253
|
+
background-color: #f8f9fa;
|
254
|
+
color: #333;
|
255
|
+
}
|
256
|
+
.card {
|
257
|
+
background-color: white;
|
258
|
+
border-radius: 12px;
|
259
|
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
260
|
+
padding: 2rem;
|
261
|
+
text-align: center;
|
262
|
+
max-width: 400px;
|
263
|
+
width: 90%;
|
264
|
+
border-left: 4px solid #dc3545;
|
265
|
+
}
|
266
|
+
h1 {
|
267
|
+
color: #dc3545;
|
268
|
+
margin-bottom: 1rem;
|
269
|
+
font-size: 1.8rem;
|
270
|
+
}
|
271
|
+
p {
|
272
|
+
margin: 0.5rem 0;
|
273
|
+
color: #6c757d;
|
274
|
+
}
|
275
|
+
.countdown {
|
276
|
+
font-weight: bold;
|
277
|
+
color: #dc3545;
|
278
|
+
font-size: 1.2rem;
|
279
|
+
}
|
280
|
+
.error-icon {
|
281
|
+
font-size: 3rem;
|
282
|
+
color: #dc3545;
|
283
|
+
margin-bottom: 1rem;
|
284
|
+
}
|
285
|
+
</style>
|
286
|
+
</head>
|
287
|
+
<body>
|
288
|
+
<div class="card">
|
289
|
+
<div class="error-icon">✗</div>
|
290
|
+
<h1>登录失败</h1>
|
291
|
+
<p>未收到有效的认证信息</p>
|
292
|
+
</div>
|
293
|
+
</body>
|
294
|
+
</html>
|
278
295
|
`);
|
279
296
|
reject(new Error('登录失败:未收到token'));
|
280
297
|
}
|
@@ -335,6 +352,7 @@ async function openExternalUrl(url) {
|
|
335
352
|
return false;
|
336
353
|
}
|
337
354
|
}
|
355
|
+
const AUTH_SERVER_URL = 'https://login.jianguoke.cn/login';
|
338
356
|
/**
|
339
357
|
* 执行登录流程
|
340
358
|
* @param env 环境接口
|
@@ -343,7 +361,8 @@ async function openExternalUrl(url) {
|
|
343
361
|
* @param clientId 客户端ID
|
344
362
|
* @returns 登录成功后的token
|
345
363
|
*/
|
346
|
-
async function login(
|
364
|
+
async function login(clientId) {
|
365
|
+
const authServerUrl = AUTH_SERVER_URL;
|
347
366
|
const env = getEnvironment();
|
348
367
|
const tokenStorage = getTokenStorage();
|
349
368
|
const state = generateState();
|
@@ -407,8 +426,8 @@ function isLoggedIn() {
|
|
407
426
|
}
|
408
427
|
return true;
|
409
428
|
}
|
410
|
-
function getUserData() {
|
411
|
-
const token = getTokenStorage().get();
|
429
|
+
async function getUserData() {
|
430
|
+
const token = await getTokenStorage().get();
|
412
431
|
if (!token) {
|
413
432
|
return null;
|
414
433
|
}
|
package/dist/view.js
CHANGED
@@ -7,10 +7,11 @@ exports.COVERAGE_FILE_PATH = void 0;
|
|
7
7
|
exports.showCoverageView = showCoverageView;
|
8
8
|
exports.stopMonitoring = stopMonitoring;
|
9
9
|
exports.loadAndDisplayCoverage = loadAndDisplayCoverage;
|
10
|
+
exports.disposeTimer = disposeTimer;
|
10
11
|
const fs_1 = __importDefault(require("fs"));
|
11
12
|
const path_1 = __importDefault(require("path"));
|
12
13
|
const table_1 = require("table");
|
13
|
-
const
|
14
|
+
const cli_color_1 = __importDefault(require("cli-color"));
|
14
15
|
let REFRESH_INTERVAL = 3000;
|
15
16
|
// 存储上一次的覆盖率数据
|
16
17
|
let previousCoverage = null;
|
@@ -25,7 +26,7 @@ exports.COVERAGE_FILE_PATH = 'coverage-summary.json';
|
|
25
26
|
// 主函数
|
26
27
|
async function showCoverageView(rootDir, coverageFilePath = exports.COVERAGE_FILE_PATH) {
|
27
28
|
const coveragePath = path_1.default.join(rootDir, coverageFilePath);
|
28
|
-
console.log(
|
29
|
+
console.log(cli_color_1.default.cyan('开始监控覆盖率数据...' + coveragePath));
|
29
30
|
// 初始加载
|
30
31
|
await loadAndDisplayCoverage(coveragePath);
|
31
32
|
// 设置文件监听
|
@@ -50,6 +51,7 @@ function stopMonitoring() {
|
|
50
51
|
refreshTimer = null;
|
51
52
|
}
|
52
53
|
}
|
54
|
+
let timer = null;
|
53
55
|
// 加载并显示覆盖率数据
|
54
56
|
async function loadAndDisplayCoverage(coveragePath, refreshTimeout = REFRESH_INTERVAL) {
|
55
57
|
try {
|
@@ -61,7 +63,8 @@ async function loadAndDisplayCoverage(coveragePath, refreshTimeout = REFRESH_INT
|
|
61
63
|
changedCells = compareCoverage(previousCoverage, coverage);
|
62
64
|
// 如果有变化,3秒后清除标记
|
63
65
|
if (changedCells.length > 0) {
|
64
|
-
setTimeout(() => {
|
66
|
+
timer = setTimeout(() => {
|
67
|
+
timer = null;
|
65
68
|
changedCells = [];
|
66
69
|
loadAndDisplayCoverage(coveragePath);
|
67
70
|
}, refreshTimeout);
|
@@ -73,7 +76,7 @@ async function loadAndDisplayCoverage(coveragePath, refreshTimeout = REFRESH_INT
|
|
73
76
|
renderTable(tableData);
|
74
77
|
}
|
75
78
|
catch (error) {
|
76
|
-
console.error(
|
79
|
+
console.error(cli_color_1.default.red(`加载覆盖率数据时出错: ${error.message}`));
|
77
80
|
}
|
78
81
|
}
|
79
82
|
// 读取覆盖率文件
|
@@ -102,7 +105,7 @@ function generateTableData(coverage) {
|
|
102
105
|
filePaths.sort();
|
103
106
|
// 添加总计行
|
104
107
|
tableData.push([
|
105
|
-
|
108
|
+
cli_color_1.default.bold('总计'),
|
106
109
|
formatPercentage(coverage.total.lines.pct),
|
107
110
|
formatPercentage(coverage.total.statements.pct),
|
108
111
|
formatPercentage(coverage.total.functions.pct),
|
@@ -126,17 +129,17 @@ function generateTableData(coverage) {
|
|
126
129
|
// 格式化百分比并应用颜色
|
127
130
|
function formatPercentage(pct) {
|
128
131
|
if (pct === 'Unknown')
|
129
|
-
return
|
132
|
+
return cli_color_1.default.italic('Unknown');
|
130
133
|
let formatted = pct.toFixed(2) + '%';
|
131
134
|
// 根据覆盖率设置颜色
|
132
135
|
if (pct < 50) {
|
133
|
-
formatted =
|
136
|
+
formatted = cli_color_1.default.red(formatted);
|
134
137
|
}
|
135
138
|
else if (pct < 80) {
|
136
|
-
formatted =
|
139
|
+
formatted = cli_color_1.default.yellow(formatted);
|
137
140
|
}
|
138
141
|
else {
|
139
|
-
formatted =
|
142
|
+
formatted = cli_color_1.default.green(formatted);
|
140
143
|
}
|
141
144
|
return formatted;
|
142
145
|
}
|
@@ -176,7 +179,7 @@ function renderTable(tableData) {
|
|
176
179
|
return row.map((cell, colIndex) => {
|
177
180
|
// 检查是否是变化的单元格
|
178
181
|
if (changedCells.some(([r, c]) => r === rowIndex && c === colIndex)) {
|
179
|
-
return
|
182
|
+
return cli_color_1.default.bgRed.white(cell);
|
180
183
|
}
|
181
184
|
return cell;
|
182
185
|
});
|
@@ -210,7 +213,13 @@ function renderTable(tableData) {
|
|
210
213
|
};
|
211
214
|
// 清除控制台并打印表格
|
212
215
|
console.clear();
|
213
|
-
console.log(
|
216
|
+
console.log(cli_color_1.default.cyan('单测覆盖率监控工具 - 最后更新: ' + new Date().toLocaleString()));
|
214
217
|
output((0, table_1.table)(markedData, tableConfig));
|
215
218
|
}
|
219
|
+
function disposeTimer() {
|
220
|
+
if (timer) {
|
221
|
+
clearTimeout(timer);
|
222
|
+
timer = null;
|
223
|
+
}
|
224
|
+
}
|
216
225
|
//# sourceMappingURL=view.js.map
|
package/package.json
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"name": "ai-worktool",
|
3
3
|
"displayName": "吃豆豆:单测智能体",
|
4
|
-
"version": "1.0.
|
4
|
+
"version": "1.0.14",
|
5
5
|
"description": "单元测试智能体,帮你开发单元测试用例代码直到100%单测覆盖通过。",
|
6
6
|
"author": "jianguoke.cn",
|
7
7
|
"license": "MIT",
|
@@ -13,6 +13,7 @@
|
|
13
13
|
"watch": "tsc -w",
|
14
14
|
"build": "tsc",
|
15
15
|
"test": "jest test/tools",
|
16
|
+
"coverage": "jest --coverage test/tools",
|
16
17
|
"plugin": "ts-node ./tools.ts && dify plugin package plugins/dify-plugin",
|
17
18
|
"package": "cross-env PKG_CACHE_PATH=./binaries pkg . --targets node20-win-x64 --out-path aicoder ",
|
18
19
|
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
|
@@ -256,11 +257,12 @@
|
|
256
257
|
"acorn": "^8.15.0",
|
257
258
|
"acorn-walk": "^8.3.4",
|
258
259
|
"typescript": "^5.9.2",
|
259
|
-
"
|
260
|
+
"cli-color": "^2.0.4",
|
260
261
|
"commander": "^14.0.0",
|
261
262
|
"table": "^6.9.0"
|
262
263
|
},
|
263
264
|
"devDependencies": {
|
265
|
+
"@types/cli-color": "^2.0.6",
|
264
266
|
"@types/jest": "^30.0.0",
|
265
267
|
"@types/jsonwebtoken": "^9.0.10",
|
266
268
|
"@types/node": "^24.1.0",
|
package/aicoder-1.0.10.tgz
DELETED
Binary file
|