ai-worktool 1.0.72 → 1.0.74
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 +2 -1
- package/dist/agents/jianguoke.js +2 -2
- package/dist/agents/toolCall.js +14 -12
- package/dist/logger.js +189 -0
- package/dist/program.js +25 -8
- package/dist/report.js +6 -3
- package/dist/tools/file.js +23 -12
- package/dist/view.js +39 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
## 1.0.
|
|
1
|
+
## 1.0.74 (2025-08-20)
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
### Bug Fixes
|
|
@@ -117,6 +117,7 @@
|
|
|
117
117
|
* **program:** 增加代码覆盖率报告展示功能 ([60da9b5](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/60da9b59e4b913695daa8697dd9aeec16cf78a5f))
|
|
118
118
|
* **program:** 增加代码覆盖率报告展示功能 ([f693e27](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/f693e272ef0fe7e58083d2589339af17e45cff95))
|
|
119
119
|
* **program:** 增加代码覆盖率的 web 报表展示功能 ([cccf58d](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/cccf58d2bfdcf2016fee011b1623821598d97e21))
|
|
120
|
+
* **program:** 增加日志记录功能并优化输出 ([1332b7f](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/1332b7fc2bb773e266e511bdcd2e6029eec09d45))
|
|
120
121
|
* **program:** 添加测试配置文件选项并优化 Jest 命令生成 ([5f16c21](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/5f16c21da3a12e46a49611dbd5b2a6e3c7cdcf24))
|
|
121
122
|
* **project:** 优化项目类型检测逻辑 ([9bc024d](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/9bc024deeed239c05c9cab810f9f12a6a3306aab))
|
|
122
123
|
* **project:** 增加加载 aicoder 配置文件功能 ([b4dc70b](https://codeup.aliyun.com/666cf9ed29ecbe23053513a3/JianGuoKe/ai-worktools/commits/b4dc70b3afa88cf45135cf56e63cbb2ffa2fed82))
|
package/dist/agents/jianguoke.js
CHANGED
|
@@ -59,7 +59,7 @@ async function handleToolCall(toolCall, cwd) {
|
|
|
59
59
|
let success;
|
|
60
60
|
try {
|
|
61
61
|
data = await (0, toolCall_1.callWithObject)(toolCall.toolName, toolCall.params, cwd);
|
|
62
|
-
console.log(data);
|
|
62
|
+
// console.log(data);
|
|
63
63
|
success = true;
|
|
64
64
|
}
|
|
65
65
|
catch (err) {
|
|
@@ -161,7 +161,7 @@ async function startAgent(name, question, vars, withConversation, ctrl, mode, on
|
|
|
161
161
|
lasttool = '';
|
|
162
162
|
}
|
|
163
163
|
if (ev?.event === 'DONE') {
|
|
164
|
-
console.log(msg);
|
|
164
|
+
// console.log(msg);
|
|
165
165
|
}
|
|
166
166
|
if (ev?.event === 'ERROR') {
|
|
167
167
|
err = new AgentException(data.error, data.name, data.code);
|
package/dist/agents/toolCall.js
CHANGED
|
@@ -144,8 +144,7 @@ async function callWithObject(name, argsObj, projectDir) {
|
|
|
144
144
|
function getParamNames(fn) {
|
|
145
145
|
// 匹配函数参数列表的正则表达式
|
|
146
146
|
const paramRegex = /\(([^)]*)\)/;
|
|
147
|
-
//
|
|
148
|
-
const nameRegex = /([^\s,=]+)/g;
|
|
147
|
+
// 修正后的修正
|
|
149
148
|
// 获取函数源码
|
|
150
149
|
const fnStr = fn.toString();
|
|
151
150
|
// 提取参数列表字符串
|
|
@@ -157,13 +156,16 @@ function getParamNames(fn) {
|
|
|
157
156
|
if (!params) {
|
|
158
157
|
return [];
|
|
159
158
|
}
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
159
|
+
// 分割参数并提取参数名(处理有默认值的情况)
|
|
160
|
+
return params
|
|
161
|
+
.split(',')
|
|
162
|
+
.map((param) => {
|
|
163
|
+
// 移除可能的默认值部分
|
|
164
|
+
const paramName = param.split('=')[0].trim();
|
|
165
|
+
// 排除空字符串(处理可能的空参数情况)
|
|
166
|
+
return paramName || null;
|
|
167
|
+
})
|
|
168
|
+
.filter(Boolean);
|
|
167
169
|
}
|
|
168
170
|
function formatDisplayText(txt) {
|
|
169
171
|
return txt.length > 50 ? txt.slice(0, 50) + '...' : txt;
|
|
@@ -174,7 +176,7 @@ toolCall, root, result) {
|
|
|
174
176
|
root = root || '/';
|
|
175
177
|
const params = toolCall.params || {};
|
|
176
178
|
if (toolCall.function_name === 'readFile') {
|
|
177
|
-
const outputResult = result ? (result?.success ? ` ✅` : ` ❌${result?.message}`) : '';
|
|
179
|
+
const outputResult = result ? (result?.success ? ` ✅` : ` ❌${result?.message}`) : '...';
|
|
178
180
|
const filePath = params[0] || params.filePath;
|
|
179
181
|
return `读取文件:${formatDisplayText(filePath ? (path_1.default.isAbsolute(filePath) ? path_1.default.relative(root, filePath) : filePath) : '')}${outputResult}`;
|
|
180
182
|
}
|
|
@@ -183,9 +185,9 @@ toolCall, root, result) {
|
|
|
183
185
|
toolCall.function_name === 'modifyLine' ||
|
|
184
186
|
toolCall.function_name === 'deleteLine' ||
|
|
185
187
|
toolCall.function_name === 'batchModifyLines') {
|
|
186
|
-
const outputResult = result ? (result?.success ? ` ✅` : ` ❌${result?.message}`) : '';
|
|
188
|
+
const outputResult = result ? (result?.success ? ` ✅` : ` ❌${result?.message}`) : '...';
|
|
187
189
|
const filePath = params[0] || params.filePath;
|
|
188
|
-
return
|
|
190
|
+
return `修改文件${process.env.NODE_ENV === 'test' ? `(${toolCall.function_name})` : ''}: ${formatDisplayText(filePath ? (path_1.default.isAbsolute(filePath) ? path_1.default.relative(root, filePath) : filePath) : '')}${outputResult}`;
|
|
189
191
|
}
|
|
190
192
|
if (toolCall.function_name === 'searchFilesByExtension') {
|
|
191
193
|
const outputResult = result
|
package/dist/logger.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
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.LogLevel = void 0;
|
|
7
|
+
exports.debug = debug;
|
|
8
|
+
exports.info = info;
|
|
9
|
+
exports.warn = warn;
|
|
10
|
+
exports.error = error;
|
|
11
|
+
exports.createLogger = createLogger;
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const util_1 = require("util");
|
|
15
|
+
// 定义日志级别
|
|
16
|
+
var LogLevel;
|
|
17
|
+
(function (LogLevel) {
|
|
18
|
+
LogLevel["DEBUG"] = "DEBUG";
|
|
19
|
+
LogLevel["INFO"] = "INFO";
|
|
20
|
+
LogLevel["WARN"] = "WARN";
|
|
21
|
+
LogLevel["ERROR"] = "ERROR";
|
|
22
|
+
})(LogLevel || (exports.LogLevel = LogLevel = {}));
|
|
23
|
+
// 内部使用的异步工具函数
|
|
24
|
+
const appendFile = (0, util_1.promisify)(fs_1.default.appendFile);
|
|
25
|
+
const exists = (0, util_1.promisify)(fs_1.default.exists);
|
|
26
|
+
const mkdir = (0, util_1.promisify)(fs_1.default.mkdir);
|
|
27
|
+
const stat = (0, util_1.promisify)(fs_1.default.stat);
|
|
28
|
+
const rename = (0, util_1.promisify)(fs_1.default.rename);
|
|
29
|
+
// 日志任务队列和状态管理
|
|
30
|
+
const logQueue = [];
|
|
31
|
+
let isProcessing = false;
|
|
32
|
+
/**
|
|
33
|
+
* 处理日志队列中的任务
|
|
34
|
+
*/
|
|
35
|
+
async function processLogQueue() {
|
|
36
|
+
// 如果正在处理或队列为空,则退出
|
|
37
|
+
if (isProcessing || logQueue.length === 0) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
isProcessing = true;
|
|
41
|
+
try {
|
|
42
|
+
// 取出队列中的第一个任务并执行
|
|
43
|
+
const logTask = logQueue.shift();
|
|
44
|
+
if (logTask) {
|
|
45
|
+
await logTask();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error('Error processing log queue:', error);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
isProcessing = false;
|
|
53
|
+
// 递归处理下一个任务
|
|
54
|
+
processLogQueue();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 确保日志目录存在(内部异步)
|
|
59
|
+
*/
|
|
60
|
+
async function ensureLogDirExists(logDir) {
|
|
61
|
+
try {
|
|
62
|
+
const dirExists = await exists(logDir);
|
|
63
|
+
if (!dirExists) {
|
|
64
|
+
await mkdir(logDir, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
console.error('Failed to create log directory:', error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 获取当前日志文件名(按日期)
|
|
73
|
+
*/
|
|
74
|
+
function getCurrentLogFileName(prefix) {
|
|
75
|
+
const date = new Date();
|
|
76
|
+
const year = date.getFullYear();
|
|
77
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
78
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
79
|
+
return `${prefix}-${year}-${month}-${day}.log`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 获取当前日志文件路径
|
|
83
|
+
*/
|
|
84
|
+
function getCurrentLogFilePath(config) {
|
|
85
|
+
return path_1.default.join(config.logDir, getCurrentLogFileName(config.fileNamePrefix));
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 检查文件是否需要轮转(内部异步)
|
|
89
|
+
*/
|
|
90
|
+
async function shouldRotateLogFile(filePath, maxSize) {
|
|
91
|
+
try {
|
|
92
|
+
const stats = await stat(filePath);
|
|
93
|
+
return stats.size >= maxSize;
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
return false; // 文件不存在时不需要轮转
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 轮转日志文件(内部异步)
|
|
101
|
+
*/
|
|
102
|
+
async function rotateLogFile(filePath) {
|
|
103
|
+
const timestamp = new Date().getTime();
|
|
104
|
+
const newPath = `${filePath}.${timestamp}`;
|
|
105
|
+
await rename(filePath, newPath);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* 核心日志写入函数(加入队列确保顺序)
|
|
109
|
+
*/
|
|
110
|
+
function writeLog(config, level, message, meta) {
|
|
111
|
+
// 确保配置完整
|
|
112
|
+
const fullConfig = {
|
|
113
|
+
maxFileSize: 1024 * 1024 * 5, // 默认5MB
|
|
114
|
+
...config
|
|
115
|
+
};
|
|
116
|
+
// 创建日志任务并加入队列
|
|
117
|
+
const logTask = async () => {
|
|
118
|
+
try {
|
|
119
|
+
// 确保日志目录存在
|
|
120
|
+
await ensureLogDirExists(fullConfig.logDir);
|
|
121
|
+
// 构建日志消息
|
|
122
|
+
const now = new Date().toISOString();
|
|
123
|
+
let logMessage = `[${now}] [${level}] ${message}`;
|
|
124
|
+
// 添加元数据
|
|
125
|
+
if (meta) {
|
|
126
|
+
logMessage += ` | ${JSON.stringify(meta)}`;
|
|
127
|
+
}
|
|
128
|
+
logMessage += '\n';
|
|
129
|
+
// 获取日志文件路径
|
|
130
|
+
const logFilePath = getCurrentLogFilePath(fullConfig);
|
|
131
|
+
// 检查是否需要轮转
|
|
132
|
+
if (await shouldRotateLogFile(logFilePath, fullConfig.maxFileSize)) {
|
|
133
|
+
await rotateLogFile(logFilePath);
|
|
134
|
+
}
|
|
135
|
+
// 异步写入日志
|
|
136
|
+
await appendFile(logFilePath, logMessage, 'utf8');
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
console.error('Failed to write log:', error, 'Log message:', message);
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
// 将任务加入队列
|
|
143
|
+
logQueue.push(logTask);
|
|
144
|
+
// 开始处理队列
|
|
145
|
+
processLogQueue();
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* 记录DEBUG级别日志
|
|
149
|
+
*/
|
|
150
|
+
function debug(config, message, meta) {
|
|
151
|
+
writeLog(config, LogLevel.DEBUG, message, meta);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 记录INFO级别日志
|
|
155
|
+
*/
|
|
156
|
+
function info(config, message, meta) {
|
|
157
|
+
writeLog(config, LogLevel.INFO, message, meta);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* 记录WARN级别日志
|
|
161
|
+
*/
|
|
162
|
+
function warn(config, message, meta) {
|
|
163
|
+
writeLog(config, LogLevel.WARN, message, meta);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 记录ERROR级别日志
|
|
167
|
+
*/
|
|
168
|
+
function error(config, message, meta) {
|
|
169
|
+
writeLog(config, LogLevel.ERROR, message, meta);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* 创建带预设配置的日志函数
|
|
173
|
+
*/
|
|
174
|
+
function createLogger(config) {
|
|
175
|
+
return {
|
|
176
|
+
debug: (message, meta) => debug(config, message, meta),
|
|
177
|
+
info: (message, meta) => info(config, message, meta),
|
|
178
|
+
warn: (message, meta) => warn(config, message, meta),
|
|
179
|
+
error: (message, meta) => error(config, message, meta),
|
|
180
|
+
// 新增:等待所有日志写入完成
|
|
181
|
+
flush: async () => {
|
|
182
|
+
// 等待队列处理完成
|
|
183
|
+
while (isProcessing || logQueue.length > 0) {
|
|
184
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=logger.js.map
|
package/dist/program.js
CHANGED
|
@@ -13,25 +13,36 @@ const tools_1 = require("./tools");
|
|
|
13
13
|
const view_1 = require("./view");
|
|
14
14
|
const user_1 = require("./user");
|
|
15
15
|
const report_1 = require("./report");
|
|
16
|
+
const logger_1 = require("./logger");
|
|
16
17
|
const program = new commander_1.Command();
|
|
18
|
+
const logger = (0, logger_1.createLogger)({
|
|
19
|
+
fileNamePrefix: 'ai-worktools-log',
|
|
20
|
+
logDir: 'logs'
|
|
21
|
+
});
|
|
17
22
|
program.name('testAgent').description('吃豆豆:单测智能体').version('1.0.1');
|
|
18
23
|
// 屏蔽调试日志
|
|
19
24
|
let lastLineNo;
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
let lastMessage = '';
|
|
26
|
+
const outputLine = (message, id, level = 'info') => {
|
|
27
|
+
if (lastLineNo !== id && lastMessage) {
|
|
22
28
|
process.stdout.write('\n');
|
|
29
|
+
logger[level](lastMessage);
|
|
30
|
+
lastMessage = '';
|
|
23
31
|
}
|
|
24
32
|
process.stdout.write(message);
|
|
33
|
+
lastMessage = message;
|
|
25
34
|
if (!id) {
|
|
26
35
|
process.stdout.write('\n');
|
|
36
|
+
logger[level](lastMessage);
|
|
37
|
+
lastMessage = '';
|
|
27
38
|
}
|
|
28
39
|
lastLineNo = id;
|
|
29
40
|
};
|
|
30
41
|
if (process.env.NODE_ENV !== 'test') {
|
|
31
|
-
console.log = () => { };
|
|
42
|
+
console.log = console.error = console.warn = console.debug = console.info = () => { };
|
|
32
43
|
}
|
|
33
44
|
else {
|
|
34
|
-
|
|
45
|
+
const injectLog = (level) => (...txt) => outputLine(txt
|
|
35
46
|
.map((arg) => {
|
|
36
47
|
// 处理 null 和 undefined
|
|
37
48
|
if (arg === null) {
|
|
@@ -58,7 +69,11 @@ else {
|
|
|
58
69
|
return String(arg);
|
|
59
70
|
}
|
|
60
71
|
})
|
|
61
|
-
.join(''));
|
|
72
|
+
.join(' '), undefined, level);
|
|
73
|
+
console.log = injectLog('info');
|
|
74
|
+
console.error = injectLog('error');
|
|
75
|
+
console.warn = injectLog('warn');
|
|
76
|
+
console.debug = injectLog('debug');
|
|
62
77
|
}
|
|
63
78
|
const tokenFile = path_1.default.join(os_1.default.homedir(), '.aiworktools');
|
|
64
79
|
// token存储在系统用户文件夹的.ai-worktools
|
|
@@ -91,6 +106,7 @@ program
|
|
|
91
106
|
try {
|
|
92
107
|
outputLine(`正在打开浏览器进行登录...`);
|
|
93
108
|
await (0, user_1.login)('ai-worktools-cli');
|
|
109
|
+
await logger.flush();
|
|
94
110
|
outputLine('登录成功!');
|
|
95
111
|
}
|
|
96
112
|
catch (err) {
|
|
@@ -103,6 +119,7 @@ program
|
|
|
103
119
|
.action(async () => {
|
|
104
120
|
await (0, user_1.logout)();
|
|
105
121
|
outputLine('已成功退出');
|
|
122
|
+
await logger.flush();
|
|
106
123
|
});
|
|
107
124
|
program
|
|
108
125
|
.command('start [projectRoot]')
|
|
@@ -119,14 +136,13 @@ program
|
|
|
119
136
|
.option('-a, --autoCommit <boolean>', '覆盖率报告文件夹', (txt) => txt === 'true', true)
|
|
120
137
|
.option('--fullCoverageEnd <boolean>', '是否在达到100%覆盖率后结束', (txt) => txt === 'true', true)
|
|
121
138
|
.action(async (projectRoot, options) => {
|
|
122
|
-
const tasks = [];
|
|
123
139
|
const result = await (0, testAgent_1.startTestAgent)({
|
|
124
140
|
...options,
|
|
125
141
|
projectRoot: projectRoot || options.projectRoot,
|
|
126
142
|
mode: process.env.TEXT_MODE || 'increment',
|
|
127
143
|
onMessage: outputLine
|
|
128
144
|
});
|
|
129
|
-
await
|
|
145
|
+
await logger.flush();
|
|
130
146
|
if (result?.name === '') {
|
|
131
147
|
if (result?.name === 'ExpiredException') {
|
|
132
148
|
outputLine('您的Tokens已用完,请充值。执行下面命令打开使用面板:');
|
|
@@ -140,7 +156,7 @@ program
|
|
|
140
156
|
.option('--projectRoot <directory>', '源代码文件夹', (txt) => path_1.default.resolve(txt), process.cwd())
|
|
141
157
|
.option('--coverageDir <string>', '覆盖率报告文件夹', 'coverage')
|
|
142
158
|
.option('--web <boolean>', '是否打开web报表页面', (txt) => txt === 'false', false)
|
|
143
|
-
.option('--retest <boolean>', '是否重新生成测试报表', (txt) => txt
|
|
159
|
+
.option('--retest <boolean>', '是否重新生成测试报表', (txt) => txt === 'true', false)
|
|
144
160
|
.option('-w, --watch <boolean>', '是否监控代码覆盖率报告变化', (txt) => txt !== 'false', false)
|
|
145
161
|
.action(async (projectRoot, options) => {
|
|
146
162
|
if (options.retest) {
|
|
@@ -166,6 +182,7 @@ program
|
|
|
166
182
|
await (0, view_1.loadAndDisplayCoverage)(path_1.default.join(projectRoot || options.projectRoot, options.coverageDir, view_1.COVERAGE_FILE_PATH));
|
|
167
183
|
}
|
|
168
184
|
}
|
|
185
|
+
await logger.flush();
|
|
169
186
|
});
|
|
170
187
|
program
|
|
171
188
|
.command('usage')
|
package/dist/report.js
CHANGED
|
@@ -142,7 +142,7 @@ function setupProjectWatcher(projectName) {
|
|
|
142
142
|
if (eventType === 'change' || eventType === 'rename') {
|
|
143
143
|
const time = Date.now();
|
|
144
144
|
lastChangeTime.set(projectName, time);
|
|
145
|
-
console.log(`项目 ${projectName} 的文件变化 detected at ${new Date(time).toLocaleTimeString()}`);
|
|
145
|
+
// console.log(`项目 ${projectName} 的文件变化 detected at ${new Date(time).toLocaleTimeString()}`);
|
|
146
146
|
// 通知该项目的所有客户端
|
|
147
147
|
const projectClients = clients.filter((c) => c.project === projectName);
|
|
148
148
|
projectClients.forEach(({ res }) => {
|
|
@@ -228,7 +228,7 @@ function injectSilentRefreshScript(htmlContent, projectName) {
|
|
|
228
228
|
document.body.innerHTML = newDocument.body.innerHTML;
|
|
229
229
|
|
|
230
230
|
// 重新加载样式表
|
|
231
|
-
reloadStylesheets();
|
|
231
|
+
// reloadStylesheets();
|
|
232
232
|
|
|
233
233
|
// 恢复滚动位置
|
|
234
234
|
restoreScrollPosition(scrollPos);
|
|
@@ -240,10 +240,12 @@ function injectSilentRefreshScript(htmlContent, projectName) {
|
|
|
240
240
|
window.location.reload();
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
|
+
|
|
244
|
+
var lastChange = 0;
|
|
243
245
|
|
|
244
246
|
// 长轮询检查变化
|
|
245
247
|
function checkForChanges() {
|
|
246
|
-
fetch(\`/\${currentProject}/__watch\`)
|
|
248
|
+
fetch(\`/\${currentProject}/__watch?lastChange=\${lastChange}\`)
|
|
247
249
|
.then(response => {
|
|
248
250
|
if (response.ok) {
|
|
249
251
|
return response.text();
|
|
@@ -257,6 +259,7 @@ function injectSilentRefreshScript(htmlContent, projectName) {
|
|
|
257
259
|
|
|
258
260
|
// 项目页面的内容变化
|
|
259
261
|
if (jsonData.changed && jsonData.project === currentProject) {
|
|
262
|
+
lastChange = jsonData.time;
|
|
260
263
|
console.log(\`检测到项目 \${currentProject} 的文件变化,正在更新页面...\`);
|
|
261
264
|
updatePageContent();
|
|
262
265
|
}
|
package/dist/tools/file.js
CHANGED
|
@@ -294,9 +294,7 @@ async function writeFile(filePath, content, encoding = 'utf8', overwrite = true)
|
|
|
294
294
|
const data = content.replace(/\r\n/g, '\n');
|
|
295
295
|
const old = !!onChanged && exists ? await fs.readFile(filePath, encoding) : '';
|
|
296
296
|
await fs.writeFile(filePath, data, encoding);
|
|
297
|
-
|
|
298
|
-
await onChanged(filePath, old, data);
|
|
299
|
-
}
|
|
297
|
+
await onChanged?.(filePath, old, data);
|
|
300
298
|
}
|
|
301
299
|
catch (error) {
|
|
302
300
|
throw new Error(`写入文件失败: ${error.message}`);
|
|
@@ -356,7 +354,7 @@ async function renameFile(oldPath, newPath, overwrite = false) {
|
|
|
356
354
|
* @param filePath 文件路径,如 'src/main.js'
|
|
357
355
|
* @param startLine 起始行号(从1开始)
|
|
358
356
|
* @param endLine 结束行号(可选,默认为startLine)
|
|
359
|
-
* @param newText
|
|
357
|
+
* @param newText 新的文本内容(多行用\n分割)
|
|
360
358
|
* @param encoding 文件编码,默认为 'utf8'
|
|
361
359
|
* @example
|
|
362
360
|
* await modifyLine('file.txt', 5, 5, 'New content for line 5');
|
|
@@ -368,15 +366,19 @@ async function modifyLine(filePath, startLine, endLine = startLine, newText, enc
|
|
|
368
366
|
throw new Error(`行号参数错误: startLine=${startLine}, endLine=${endLine}`);
|
|
369
367
|
}
|
|
370
368
|
encoding = encoding || 'utf8';
|
|
371
|
-
const
|
|
369
|
+
const old = await readFile(filePath, encoding);
|
|
370
|
+
const lines = old.split('\n');
|
|
372
371
|
const lineCount = lines.length;
|
|
373
372
|
if (endLine > lineCount) {
|
|
374
373
|
throw new Error(`行号超出范围: ${endLine} (总行数: ${lineCount})`);
|
|
375
374
|
}
|
|
376
|
-
|
|
375
|
+
console.debug(filePath, startLine, endLine, newText);
|
|
376
|
+
const replacementLines = newText.split('\n');
|
|
377
377
|
const replaceCount = endLine - startLine + 1;
|
|
378
378
|
lines.splice(startLine - 1, replaceCount, ...replacementLines);
|
|
379
|
-
|
|
379
|
+
const data = lines.join('\n');
|
|
380
|
+
await writeFile(filePath, data, encoding, true);
|
|
381
|
+
await onChanged?.(filePath, old, data);
|
|
380
382
|
}
|
|
381
383
|
/**
|
|
382
384
|
* 在指定位置插入一行文本
|
|
@@ -390,9 +392,12 @@ async function modifyLine(filePath, startLine, endLine = startLine, newText, enc
|
|
|
390
392
|
*/
|
|
391
393
|
async function insertLine(filePath, lineNumber, text, encoding = 'utf8') {
|
|
392
394
|
encoding = encoding || 'utf8';
|
|
393
|
-
const
|
|
395
|
+
const old = await readFile(filePath, encoding, false);
|
|
396
|
+
const lines = old.split('\n');
|
|
394
397
|
lines.splice(lineNumber, 0, text);
|
|
395
|
-
|
|
398
|
+
const data = lines.join('\n');
|
|
399
|
+
await writeFile(filePath, data, encoding, true);
|
|
400
|
+
await onChanged?.(filePath, old, data);
|
|
396
401
|
}
|
|
397
402
|
/**
|
|
398
403
|
* 查找指定行的文本内容
|
|
@@ -426,14 +431,17 @@ async function deleteLine(filePath, startLine, endLine = startLine, encoding = '
|
|
|
426
431
|
throw new Error(`行号参数错误: startLine=${startLine}, endLine=${endLine}`);
|
|
427
432
|
}
|
|
428
433
|
encoding = encoding || 'utf8';
|
|
429
|
-
const
|
|
434
|
+
const old = await readFile(filePath, encoding, false);
|
|
435
|
+
const lines = old.split('\n');
|
|
430
436
|
const lineCount = lines.length;
|
|
431
437
|
if (endLine > lineCount) {
|
|
432
438
|
throw new Error(`行号超出范围: ${endLine} (总行数: ${lineCount})`);
|
|
433
439
|
}
|
|
434
440
|
const deleteCount = endLine - startLine + 1;
|
|
435
441
|
lines.splice(startLine - 1, deleteCount);
|
|
436
|
-
|
|
442
|
+
const data = lines.join('\n');
|
|
443
|
+
await writeFile(filePath, data, encoding, true);
|
|
444
|
+
await onChanged?.(filePath, old, data);
|
|
437
445
|
}
|
|
438
446
|
/**
|
|
439
447
|
* 多行批量操作,支持同时插入、更新和删除操作非连续行。
|
|
@@ -450,6 +458,7 @@ async function batchModifyLines(filePath, operations, encoding = 'utf8') {
|
|
|
450
458
|
}
|
|
451
459
|
// 解析操作字符串
|
|
452
460
|
const ops = parseOperations(operations);
|
|
461
|
+
console.debug(ops);
|
|
453
462
|
encoding = encoding || 'utf8';
|
|
454
463
|
const originalContent = await readFile(filePath, encoding, false);
|
|
455
464
|
let lines = originalContent.split('\n');
|
|
@@ -504,7 +513,9 @@ async function batchModifyLines(filePath, operations, encoding = 'utf8') {
|
|
|
504
513
|
lines.splice(actualStartLine, 0, ...insertLines);
|
|
505
514
|
});
|
|
506
515
|
// 写回文件
|
|
507
|
-
|
|
516
|
+
const data = lines.join('\n');
|
|
517
|
+
await writeFile(filePath, data, encoding, true);
|
|
518
|
+
await onChanged?.(filePath, originalContent, data);
|
|
508
519
|
}
|
|
509
520
|
/**
|
|
510
521
|
* 安全递归删除文件夹及其内容
|
package/dist/view.js
CHANGED
|
@@ -97,12 +97,50 @@ async function readCoverageFile(coveragePath) {
|
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* 计算多个文件路径的共同前缀,并返回每个路径相对于该前缀的相对路径
|
|
102
|
+
* @param filePaths 要处理的文件路径数组
|
|
103
|
+
* @returns 包含共同前缀和相对路径数组的对象
|
|
104
|
+
*/
|
|
105
|
+
function getRelativePathsFromCommonPrefix(filePaths) {
|
|
106
|
+
// 处理空数组情况
|
|
107
|
+
if (filePaths.length === 0) {
|
|
108
|
+
return { commonPrefix: '', relativePaths: [] };
|
|
109
|
+
}
|
|
110
|
+
// 标准化所有路径,统一路径格式和分隔符
|
|
111
|
+
const normalizedPaths = filePaths.map((fp) => path_1.default.normalize(fp));
|
|
112
|
+
// 将每个路径分割为组件数组
|
|
113
|
+
const pathComponents = normalizedPaths.map((fp) => fp.split(path_1.default.sep));
|
|
114
|
+
// 找到所有路径组件数组的共同前缀
|
|
115
|
+
const minLength = Math.min(...pathComponents.map((components) => components.length));
|
|
116
|
+
const commonComponents = [];
|
|
117
|
+
for (let i = 0; i < minLength; i++) {
|
|
118
|
+
// 获取当前索引位置的所有组件
|
|
119
|
+
const currentComponents = pathComponents.map((components) => components[i]);
|
|
120
|
+
// 检查所有组件是否相同
|
|
121
|
+
const allSame = currentComponents.every((comp) => comp === currentComponents[0]);
|
|
122
|
+
if (allSame) {
|
|
123
|
+
commonComponents.push(currentComponents[0]);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// 构建共同前缀路径
|
|
130
|
+
const commonPrefix = commonComponents.join(path_1.default.sep);
|
|
131
|
+
// 计算每个路径相对于共同前缀的相对路径
|
|
132
|
+
const relativePaths = normalizedPaths.map((fp) => {
|
|
133
|
+
return path_1.default.relative(commonPrefix, fp);
|
|
134
|
+
});
|
|
135
|
+
return { commonPrefix, relativePaths };
|
|
136
|
+
}
|
|
100
137
|
// 生成表格数据
|
|
101
138
|
function generateTableData(coverage) {
|
|
102
139
|
const tableData = [['文件路径', '行覆盖率(%)', '语句覆盖率(%)', '函数覆盖率(%)', '分支覆盖率(%)']];
|
|
103
140
|
// 提取并排序文件路径
|
|
104
141
|
const filePaths = Object.keys(coverage).filter((key) => key !== 'total');
|
|
105
142
|
filePaths.sort();
|
|
143
|
+
const { commonPrefix } = getRelativePathsFromCommonPrefix(filePaths);
|
|
106
144
|
// 添加总计行
|
|
107
145
|
tableData.push([
|
|
108
146
|
cli_color_1.default.bold('总计'),
|
|
@@ -117,7 +155,7 @@ function generateTableData(coverage) {
|
|
|
117
155
|
filePaths.forEach((filePath) => {
|
|
118
156
|
const fileCoverage = coverage[filePath];
|
|
119
157
|
tableData.push([
|
|
120
|
-
path_1.default.
|
|
158
|
+
path_1.default.relative(commonPrefix, filePath),
|
|
121
159
|
formatPercentage(fileCoverage.lines.pct),
|
|
122
160
|
formatPercentage(fileCoverage.statements.pct),
|
|
123
161
|
formatPercentage(fileCoverage.functions.pct),
|
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.74",
|
|
5
5
|
"description": "单元测试智能体,帮你开发单元测试用例代码直到100%单测覆盖通过。",
|
|
6
6
|
"author": "jianguoke.cn",
|
|
7
7
|
"license": "MIT",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
"createup": "node ./uposs.js create-local-folder",
|
|
29
29
|
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
|
|
30
30
|
"puball": "yarn ver && yarn exepack && yarn vspack && yarn pubvs && yarn pubovsx && yarn puboss && yarn pubnpm",
|
|
31
|
-
"exepack": "yarn createup && cross-env PKG_CACHE_PATH=./binaries pkg -o packages/aicoder-1.0.
|
|
31
|
+
"exepack": "yarn createup && cross-env PKG_CACHE_PATH=./binaries pkg -o packages/aicoder-1.0.74 . && yarn upicon",
|
|
32
32
|
"pubvs": "yarn remove-deps && yarn changelog && vsce publish --yarn --baseContentUrl https://aicoder.jianguoke.cn/assets && yarn restore-deps",
|
|
33
33
|
"pubovsx": "yarn remove-deps && ovsx publish -p 47621ff6-be56-4814-865e-d2a8e8a76f86 --yarn --baseContentUrl https://aicoder.jianguoke.cn/assets && yarn restore-deps",
|
|
34
34
|
"patch": "yarn remove-deps && vsce publish patch --yarn && yarn restore-deps",
|
|
35
|
-
"vspack": "yarn createup && yarn remove-deps && vsce package -o packages/aicoder-1.0.
|
|
35
|
+
"vspack": "yarn createup && yarn remove-deps && vsce package -o packages/aicoder-1.0.74.vsix --yarn --baseContentUrl https://aicoder.jianguoke.cn/assets && yarn restore-deps",
|
|
36
36
|
"puboss": "node ./uposs.js upload",
|
|
37
37
|
"pubnpm": "npm publish --registry=https://registry.npmjs.org",
|
|
38
38
|
"prepare": "husky"
|