crabatool 1.0.431 → 1.0.432

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/apis/data.js CHANGED
@@ -10,6 +10,7 @@ class data {
10
10
  constructor() {
11
11
  this.dslMap = {};
12
12
  this.pagesDataChanged = {};
13
+ this.pageDataValitions = {};
13
14
  this.dsl = {};
14
15
  this.dataRefs = [];
15
16
  }
@@ -48,6 +49,7 @@ class data {
48
49
  });
49
50
 
50
51
  this.pagesDataChanged = dslData.pagesDataChanged ? dslData.pagesDataChanged : {}
52
+ this.pageDataValitions = dslData.pageDataValitions ? dslData.pageDataValitions : {}
51
53
  }
52
54
 
53
55
  _getDb(req) {
@@ -161,7 +163,7 @@ class data {
161
163
  }
162
164
  }
163
165
 
164
- async reverseData(req, pageName, body) {
166
+ async reverseData(req, pageName, body, newData) {
165
167
  var oldEntity = await this._getDb(req).get(body.name, body.data.id);
166
168
  if (!oldEntity) {
167
169
  crabatool.utils.end(res, {
@@ -170,6 +172,7 @@ class data {
170
172
  });
171
173
  return;
172
174
  }
175
+ await this.pageSaveToDataValidation(pageName, oldEntity, newData);
173
176
  //退回联动老数据
174
177
  var rt = await this.pageSaveToDataLinkage(req, body.name, pageName, oldEntity, null, true);
175
178
  return {
@@ -186,7 +189,7 @@ class data {
186
189
  var rtData = JSON.parse(JSON.stringify(body.data));
187
190
  var name = body.name;
188
191
  if (body.data.id) {
189
- var oldData = await this.reverseData(req, pageName, body);
192
+ var oldData = await this.reverseData(req, pageName, body, body.data);
190
193
  if (!oldData) {
191
194
  return;
192
195
  }
@@ -197,6 +200,7 @@ class data {
197
200
  await db.update(name, body.data.id, body.data);
198
201
  newData = body.data;
199
202
  } else {
203
+ await this.pageSaveToDataValidation(pageName, null, rtData);
200
204
  //提前制定ID
201
205
  body.data.id = cuid.newCuidString()
202
206
  await this.buildDataPrimaryAndForeignKey(name, body.data);
@@ -271,6 +275,49 @@ class data {
271
275
  return this.findLayerData(data[items[0]], items.slice(1), layer)
272
276
  }
273
277
 
278
+ // 保存页面数据验证
279
+ async pageSaveToDataValidation(pageName, oldData, newData) {
280
+ if(!this.pageDataValitions || !this.pageDataValitions[pageName]){
281
+ return;
282
+ }
283
+ var validation = this.pageDataValitions[pageName];
284
+ // 数据存在验证只需要验证新数据(必填验证/关联数据存在验证)
285
+ var exist = validation.exist;
286
+ await this.validationExist(exist, pageName, newData);
287
+ // 数据对比验证需要把新老数据合并后验证,合并需要能读取到合并条件
288
+ }
289
+
290
+ async validationExist(exist, pageName, newData){
291
+ // var _this = this;
292
+ // var rt = new Set();
293
+ // for(var i = 0; exist && i < exist.length; i++){
294
+ // var item = exist[i];
295
+ // var cs = _this.findFormateItem(item.filter.refField)
296
+ // var rtData = _this.findItemMapData(pageName, crabatool.stringUtils.getFullPinyinCode(item.ref), newData, cs);
297
+ // var levels = item.filter.refField.split('.');
298
+ // if(!rtData){
299
+ // rt.add(item.info);
300
+ // continue;
301
+ // }
302
+ // if(levels.length == 1){
303
+ // if(!rtData[levels[0]]){
304
+ // rt.add(item.info);
305
+ // continue;
306
+ // }
307
+ // } else {
308
+ // var array = rtData[levels[0]];
309
+ // for(var m = 0 ; m < array.length; m++){
310
+ // var a = array[m];
311
+ // if(!a[levels[1]]){//有一条存在空,就提示
312
+ // rt.add(item.info);
313
+ // continue;
314
+ // }
315
+ // }
316
+ // }
317
+ // }
318
+ // console.log(rt);
319
+ }
320
+
274
321
  //reverseNumber:true,按规则撤销原始数据对外表的变动
275
322
  async pageSaveToDataLinkage(req, name, pageName, data, oldData, reverseNumber) {
276
323
  //页面数据保存带来的多数据联动处理
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env node
2
+
3
+ /*
4
+ 简化的打开文件监听示例 + 代码检测
5
+ 监听文件变化并自动执行代码检测
6
+ */
7
+
8
+ var path = require('path');
9
+ var fs = require('fs');
10
+ var openFileWatcher = require('../tool/openFileWatcher.js');
11
+
12
+ console.log('📁 文件监听 + 代码检测示例');
13
+ console.log('==========================\n');
14
+
15
+ // 示例1: 基本监听 + 代码检测
16
+ function basicWatch() {
17
+ console.log('🔍 启动文件监听 + 代码检测...');
18
+
19
+ const watcher = openFileWatcher.createWatcher({
20
+ watchExtensions: ['.js'],
21
+ debounceDelay: 300,
22
+ verbose: false
23
+ });
24
+
25
+ // 启动监听
26
+ watcher.start('./test');
27
+
28
+ // 手动添加一些文件到监听列表
29
+ // if (fs.existsSync('./test/test.js')) {
30
+ // watcher.addOpenFile('./test/test.js');
31
+ // }
32
+ // if (fs.existsSync('./test/craba.js')) {
33
+ // watcher.addOpenFile('./test/craba.js');
34
+ // }
35
+
36
+ console.log('✅ 监听已启动');
37
+ console.log('📂 监听目录: ./test');
38
+ console.log('📄 监听文件:', watcher.getOpenFiles());
39
+ console.log('');
40
+ console.log('💡 现在可以修改 test/ 目录下的 .js 文件来观察代码检测效果');
41
+ console.log('🔍 JavaScript 文件变化时会自动执行:');
42
+ console.log(' - ESLint 语法检查');
43
+ console.log(' - 文件编码检查');
44
+ console.log(' - 文件大小检查');
45
+ console.log(' - ES6+ 兼容性检查');
46
+ console.log('⏹️ 按 Ctrl+C 停止监听');
47
+
48
+ // 优雅退出处理
49
+ process.on('SIGINT', () => {
50
+ console.log('\n🛑 正在停止监听...');
51
+ watcher.stop();
52
+ console.log('✅ 监听已停止');
53
+ process.exit(0);
54
+ });
55
+ }
56
+
57
+ // 示例2: 监听当前目录的重要文件
58
+ function watchCurrentDir() {
59
+ console.log('🔍 监听当前目录的重要文件...');
60
+
61
+ const watcher = openFileWatcher.createWatcher({
62
+ watchExtensions: ['.js'],
63
+ ignored: [
64
+ /node_modules/,
65
+ /\.git/,
66
+ /examples/,
67
+ /docs/
68
+ ],
69
+ debounceDelay: 500,
70
+ verbose: false
71
+ });
72
+
73
+ watcher.start('./');
74
+
75
+ // 添加一些重要文件
76
+ const importantFiles = [
77
+ './index.js',
78
+ './run.js'
79
+ ];
80
+
81
+ importantFiles.forEach(file => {
82
+ if (fs.existsSync(file)) {
83
+ watcher.addOpenFile(file);
84
+ }
85
+ });
86
+
87
+ console.log('✅ 当前目录监听已启动');
88
+ console.log('📄 重要文件:', watcher.getOpenFiles());
89
+ console.log('');
90
+ console.log('💡 修改 index.js 或 run.js 来观察代码检测');
91
+ console.log('⏹️ 按 Ctrl+C 停止监听');
92
+
93
+ process.on('SIGINT', () => {
94
+ console.log('\n🛑 正在停止监听...');
95
+ watcher.stop();
96
+ console.log('✅ 监听已停止');
97
+ process.exit(0);
98
+ });
99
+ }
100
+
101
+ // 示例3: 显示使用说明
102
+ function showUsage() {
103
+ console.log('📖 文件监听 + 代码检测使用说明');
104
+ console.log('==============================\n');
105
+
106
+ console.log('🎯 功能特点:');
107
+ console.log('- 监听指定的 JavaScript 文件变化');
108
+ console.log('- 文件变化时自动执行代码检测');
109
+ console.log('- 集成 ESLint 语法检查');
110
+ console.log('- 检查文件编码和大小');
111
+ console.log('- 检查 ES6+ 兼容性问题');
112
+ console.log('- 详细的检测结果输出');
113
+ console.log('');
114
+
115
+ console.log('🚀 在 crabaTool 中使用:');
116
+ console.log('1. 启动开发服务器:');
117
+ console.log(' crabatool -run -webPath ./your-project -refresh true');
118
+ console.log('');
119
+ console.log('2. 服务器会自动启动文件监听');
120
+ console.log('3. 修改 JavaScript 文件时会自动执行代码检测');
121
+ console.log('4. 在服务器控制台看到详细的检测结果');
122
+ console.log('');
123
+
124
+ console.log('🔧 检测内容:');
125
+ console.log('- ESLint 语法检查 (错误、警告、未定义变量等)');
126
+ console.log('- 文件编码检查 (建议使用 UTF-8)');
127
+ console.log('- 文件大小检查 (超过 300KB 会提示)');
128
+ console.log('- ES6+ 语法检查 (let, const, 箭头函数等)');
129
+ console.log('- 兼容性问题检测 (IE 浏览器兼容性)');
130
+ console.log('');
131
+
132
+ console.log('📝 检测结果示例:');
133
+ console.log('');
134
+ console.log('[13:41:45] /Users/dev/project/src/demo-check.js (change) - 1.01 KB');
135
+ console.log('开始检测 JavaScript 文件...');
136
+ console.log(' 编码: UTF-8 | 大小: 1.01KB | 语法: 2错误,2未定义 | 兼容性: 发现4个ES6+语法');
137
+ console.log(' 严重问题详情:');
138
+ console.log(' 第33行: Do not use let.');
139
+ console.log(' 第34行: Do not use const.');
140
+ console.log(' 兼容性问题: let, const');
141
+ console.log('');
142
+ console.log('正常文件示例:');
143
+ console.log('[13:42:10] /Users/dev/project/src/utils.js (change) - 2.3 KB');
144
+ console.log('开始检测 JavaScript 文件...');
145
+ console.log(' 编码: UTF-8 | 大小: 2.30KB | 语法: 无问题 | 兼容性: 无ES6+语法');
146
+ console.log('');
147
+ console.log('注意: 实际运行时会显示彩色输出');
148
+ console.log('- 绿色: 检查通过');
149
+ console.log('- 黄色: 警告问题');
150
+ console.log('- 红色: 严重问题');
151
+ console.log('');
152
+
153
+ console.log('💡 提示:');
154
+ console.log('- 红色 ❌ 表示必须修复的问题');
155
+ console.log('- 黄色 ⚠️ 表示建议修复的问题');
156
+ console.log('- 蓝色 💡 表示可选修复的问题');
157
+ console.log('- 绿色 ✅ 表示检查通过');
158
+ }
159
+
160
+ // 主函数
161
+ function main() {
162
+ const args = process.argv.slice(2);
163
+
164
+ switch (args[0]) {
165
+ case 'basic':
166
+ basicWatch();
167
+ break;
168
+ case 'current':
169
+ watchCurrentDir();
170
+ break;
171
+ case 'usage':
172
+ showUsage();
173
+ break;
174
+ default:
175
+ console.log('🚀 可用示例:');
176
+ console.log('');
177
+ console.log('node examples/simple-file-watch.js basic - 基本文件监听 + 代码检测');
178
+ console.log('node examples/simple-file-watch.js current - 监听当前目录重要文件');
179
+ console.log('node examples/simple-file-watch.js usage - 使用说明');
180
+ console.log('');
181
+ console.log('💡 提示: 运行示例后,修改相应的 JavaScript 文件来观察代码检测效果');
182
+ }
183
+ }
184
+
185
+ if (require.main === module) {
186
+ main();
187
+ }
188
+
189
+ module.exports = {
190
+ basicWatch,
191
+ watchCurrentDir,
192
+ showUsage
193
+ };
package/lib/config.js CHANGED
@@ -7,6 +7,8 @@ class Config {
7
7
  //this.reportHost = "http://crabadoc.ca.com"; // 用于接收检测报告的服务器,可配置的。由于产品没有提供服务,目前统一放到平台的文档服务器
8
8
  //this.reportHost = "http://127.0.0.1:9998";
9
9
  this.reportHost = 'http://172.17.0.201:9998';
10
+ this.baseWebHost = 'http://crabawork.ca.com';
11
+ this.baseWebHost ='http://127.0.0.1:9977';
10
12
 
11
13
  //this.SourceHost = "http://crabadoc.mygjp.com.cn";
12
14
  //this.SourceHost = "http://crabadoc.ca.com";
package/lib/utils.js CHANGED
@@ -721,6 +721,12 @@ class Utils {
721
721
  if (!p1.endsWith('/') && !temp.startsWith('/')) s = '/';
722
722
  return p1 + s + temp;
723
723
  }
724
+ joinSep(temp) {
725
+ if (!temp) return temp;
726
+ var s = '';
727
+ if (!temp.startsWith('/')) s = '/';
728
+ return s + temp;
729
+ }
724
730
 
725
731
  downloadFile(uri, savePath, callback) {
726
732
  if (!uri || !savePath) throw new Error('uri或savePath不能为空');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crabatool",
3
- "version": "1.0.431",
3
+ "version": "1.0.432",
4
4
  "description": "crabatool",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -21,7 +21,7 @@
21
21
  "upload": "node run.js -upload -version master -Debug true -host http://127.0.0.1:9998 -hidejspath true -targetPath F:\\CarpaNET_NEW",
22
22
  "crabaNgp": "node run.js -mergejs -hidejspath true -targetPath F:\\CarpaNET_NEW\\src\\Carpa.Web\\js\\crabaNgp -inNames initMs.js,businessControl.js -outName F:\\CarpaMS\\src\\js\\crabaNgp.js",
23
23
  "mergeJs2": "node run.js -mergejs -targetPath -inNames F:\\CarpaNET_NEW\\src\\Carpa.Web\\js\\craba.js,F:\\CarpaNET_NEW\\src\\Carpa.Web\\js\\controls.js -outName F:\\a.js -ignorecompress true",
24
- "webPath": "node ./test/test.js -checkjs -webPath F:\\crabaevery\\www -modName crabaevery -Debug true",
24
+ "checkcrabaevery": "node ./test/test.js -checkjs -webPath F:\\crabaevery\\www -modName crabaevery -Debug false",
25
25
  "biconfont": "node ./test/test.js -iconfont -zipPath F:\\crabaevery\\www\\biconfont -fontPath F:\\crabaevery\\www\\skins\\bicon -prefix .bicon- -fontName biconfont",
26
26
  "aiconfont": "node ./test/test.js -iconfont -zipPath F:\\crabaevery\\www\\iconfont -fontPath F:\\CarpaNET_NEW\\src\\Carpa.Web\\skins -prefix .aicon- -fontName iconfont",
27
27
  "makeHash": "node run.js -makeHash -webPath ./test/ -modName craba -files http://crabadoc.ca.com/js/utils/utils.js,F:/newcrabadoc/www/js/utils/utils.js,./test/test.js,./test/beefun.js,https://s5.vip.wpscdn.cn/web-libs/2t/js/userinfo-collect/1.0.3/vas2t-userinfo-collect.min.js?a=1 -outJson ./hash.json -hashName sha384",
@@ -31,39 +31,43 @@
31
31
  "gspxHash": "node run.js -gspxHash -webPath F:\\Beefun\\srcs\\Beefun",
32
32
  "runShell": "node run.js -run -webPath F:\\shell-gerrit\\web\\src\\main\\resources\\static\\shell\\ -port 10000 -modName shell",
33
33
  "runShellLocal": "node run.js -run -webPath F:\\shell-gerrit\\web\\src\\main\\resources\\static\\shell\\ -port 10000 -modName shell -localLogin true",
34
- "runJxc": "node run.js -run -webPath F:\\jxc\\jxc-web\\src\\main\\resources\\static\\jxc\\ -port 10001 -modName jxc"
34
+ "runJxc": "node run.js -run -webPath F:\\jxc\\jxc-web\\src\\main\\resources\\static\\jxc\\ -port 10001 -modName jxc",
35
+ "open-file-watch": "node examples/simple-file-watch.js",
36
+ "open-file-test": "node examples/simple-file-watch.js basic",
37
+ "file-watch-usage": "node examples/simple-file-watch.js usage"
35
38
  },
36
39
  "author": "wssf",
37
40
  "dependencies": {
38
- "body-parser": "^1.18.3",
39
- "cookie-parser": "^1.4.5",
40
- "cookie-signature": "^1.1.0",
41
41
  "axios": "^0.27.2",
42
+ "body-parser": "^1.18.3",
43
+ "chalk": "^4.1.2",
42
44
  "chokidar": "^3.5.3",
43
45
  "compressing": "^1.6.0",
44
46
  "compression": "^1.7.4",
47
+ "cookie-parser": "^1.4.5",
48
+ "cookie-signature": "^1.1.0",
45
49
  "crypto": "^1.0.1",
50
+ "crypto-js": "^3.1.9-1",
46
51
  "eslint": "^8.18.0",
47
52
  "eslint-plugin-native-ie": "0.1.2",
48
53
  "express": "^4.17.3",
54
+ "express-session": "^1.17.1",
49
55
  "htmlparser2": "^8.0.1",
50
56
  "http-proxy-middleware": "^2.0.6",
51
57
  "iconv-jschardet": "^2.0.26",
52
58
  "iconv-lite": "^0.5.2",
59
+ "js-pinyin": "^0.1.9",
60
+ "multiparty": "^4.2.2",
61
+ "mysql": "^2.18.1",
62
+ "node-xlsx": "0.21.0",
53
63
  "nodejs-websocket": "^1.7.2",
54
64
  "readline-sync": "^1.4.10",
55
65
  "request": "^2.88.2",
56
66
  "single-line-log": "^1.1.2",
57
67
  "sync-request": "^6.1.0",
58
68
  "uglify-js": "^3.15.1",
59
- "xml-js": "^1.6.11",
60
69
  "uglifycss": "^0.0.29",
61
- "mysql": "^2.18.1",
62
- "crypto-js": "^3.1.9-1",
63
- "js-pinyin": "^0.1.9",
64
- "express-session": "^1.17.1",
65
- "multiparty": "^4.2.2",
66
- "node-xlsx": "0.21.0"
70
+ "xml-js": "^1.6.11"
67
71
  },
68
72
  "repository": {
69
73
  "type": "git",
@@ -73,4 +77,4 @@
73
77
  "crabatool"
74
78
  ],
75
79
  "license": "UNLICENSED"
76
- }
80
+ }
package/tool/checkgspx.js CHANGED
@@ -10,6 +10,8 @@ module.exports.getResults = function() {
10
10
  count: 0,
11
11
  tags: {},
12
12
  tagList: [],
13
+ customs1: {},
14
+ customs2: {},
13
15
  warnList: {},
14
16
  errorList: []
15
17
  };
@@ -63,6 +65,25 @@ function readGspxToJSON(fileName, data) {
63
65
  data.tags[name]++;
64
66
  }
65
67
 
68
+ // 统计customs
69
+ if (name == 'CustomControl') {
70
+ if (attrs.Src) {
71
+ // 统计当前页面引用了哪些CustomControl
72
+ var key1 = utils.joinSep(fileName.replaceAll('\\', '/'));
73
+ var key2 = utils.joinSep(attrs.Src.replace('~', '').replace('..',''));
74
+ if (!data.customs1[key1]) {
75
+ data.customs1[key1] = [];
76
+ }
77
+ data.customs1[key1].push(key2);
78
+
79
+ // 统计某个CustomControl被哪些页面引用
80
+ if (!data.customs2[key2]) {
81
+ data.customs2[key2] = [];
82
+ }
83
+ data.customs2[key2].push(key1);
84
+ }
85
+ }
86
+
66
87
  // 建议优化页面汇总
67
88
  if (warnTags.includes(name)) {
68
89
  if (name == 'CancelButton' || name == 'CloseButton') {
package/tool/checkjs.js CHANGED
@@ -487,6 +487,7 @@ module.exports.start = async function() {
487
487
  var content = info.concat(detail).join(' \r\n');
488
488
  postReport(id, reportUrl, content, sonar);
489
489
  postReportData(reportData)
490
+ postBaseData(reportData, gspxData);
490
491
 
491
492
  // 推送到钉钉
492
493
  if (status != '无异常') { // 检查结果检查就不发到钉钉了; ps:健康也发到钉钉,鼓励开发继续保持;
@@ -728,4 +729,24 @@ function postReportData(data) {
728
729
  console.log("推送报告数据:" + error.message);
729
730
  //console.log(error);
730
731
  });
732
+ }
733
+
734
+ function postBaseData(data0, gspxData) {
735
+ axios({
736
+ method: 'post',
737
+ url: config.baseWebHost + '/apis/data_customs/save',
738
+ data: {
739
+ customs1: gspxData.customs1,
740
+ customs2: gspxData.customs2,
741
+ branchName: data0.branchName,
742
+ projectName: data0.projectName
743
+ },
744
+ headers: { 'Content-Type': 'application/json' }
745
+ }).then(function(response) {
746
+ console.log("推送customs数据");
747
+ console.log(response.data);
748
+ }).catch(function(error) {
749
+ console.log("推送customs数据:" + error.message);
750
+ console.log(error);
751
+ });
731
752
  }
@@ -0,0 +1,150 @@
1
+ /*
2
+ 打开文件监听器配置文件
3
+ 用于配置监听行为和处理规则
4
+ */
5
+
6
+ module.exports = {
7
+ // 基本配置
8
+ enabled: true, // 是否启用打开文件监听
9
+
10
+ // 监听的文件扩展名
11
+ watchExtensions: [
12
+ '.js',
13
+ '.css',
14
+ '.html',
15
+ '.gspx'
16
+ ],
17
+
18
+ // 忽略的文件和目录模式
19
+ ignored: [
20
+ // 目录
21
+ /node_modules/,
22
+ /\.git/,
23
+ /\.cache/,
24
+ /\.tmp/,
25
+ /dist/,
26
+ /build/,
27
+ /coverage/,
28
+ /\.nyc_output/,
29
+
30
+ // 文件模式
31
+ /\.min\.(js|css)$/,
32
+ /\.bundle\.(js|css)$/,
33
+ /\.chunk\.(js|css)$/,
34
+ /\.map$/,
35
+ /\.d\.ts$/,
36
+ /\.test\.(js)$/,
37
+ /\.spec\.(js)$/,
38
+
39
+ // 临时文件
40
+ /~$/,
41
+ /\.tmp$/,
42
+ /\.temp$/,
43
+ /\.swp$/,
44
+ /\.swo$/,
45
+
46
+ // 系统文件
47
+ /\.DS_Store$/,
48
+ /Thumbs\.db$/,
49
+ /desktop\.ini$/
50
+ ],
51
+
52
+ // 防抖配置
53
+ debounce: {
54
+ delay: 300, // 防抖延迟时间(毫秒)
55
+ maxWait: 1000 // 最大等待时间(毫秒)
56
+ },
57
+
58
+ // 自动检测配置
59
+ autoDetection: {
60
+ enabled: true, // 是否启用自动检测
61
+ timeWindow: 24 * 60 * 60 * 1000, // 检测时间窗口(24小时)
62
+ maxFiles: 50, // 最大自动检测文件数
63
+ scanDepth: 5 // 扫描目录深度
64
+ },
65
+
66
+ // 文件类型处理配置
67
+ handlers: {
68
+ javascript: {
69
+ extensions: ['.js'],
70
+ hotReload: true, // 是否支持热重载
71
+ syntaxCheck: false, // 是否进行语法检查
72
+ autoFormat: false, // 是否自动格式化
73
+ buildTrigger: true // 是否触发构建
74
+ },
75
+
76
+ stylesheet: {
77
+ extensions: ['.css'],
78
+ hotReload: true,
79
+ autoCompile: false, // 是否自动编译预处理器
80
+ minify: false, // 是否自动压缩
81
+ buildTrigger: false
82
+ },
83
+
84
+ template: {
85
+ extensions: ['.html', '.gspx'],
86
+ hotReload: true,
87
+ validateSyntax: false, // 是否验证语法
88
+ buildTrigger: true
89
+ }
90
+ },
91
+
92
+ // 通知配置
93
+ notifications: {
94
+ enabled: true, // 是否启用通知
95
+ showFileChanges: true, // 是否显示文件变化通知
96
+ showErrors: true, // 是否显示错误通知
97
+ showStats: false, // 是否显示统计信息
98
+
99
+ // 通知级别
100
+ levels: {
101
+ info: true,
102
+ warning: true,
103
+ error: true,
104
+ debug: false
105
+ }
106
+ },
107
+
108
+ // 性能配置
109
+ performance: {
110
+ maxOpenFiles: 100, // 最大监听文件数
111
+ maxChangeQueue: 50, // 最大变化队列长度
112
+ cleanupInterval: 5 * 60 * 1000, // 清理间隔(5分钟)
113
+ memoryThreshold: 100 * 1024 * 1024 // 内存阈值(100MB)
114
+ },
115
+
116
+ // 集成配置
117
+ integration: {
118
+ // 与现有刷新系统的集成
119
+ browserRefresh: {
120
+ enabled: true,
121
+ delay: 500, // 刷新延迟
122
+ selective: true // 是否选择性刷新
123
+ },
124
+ },
125
+
126
+ // 日志配置
127
+ logging: {
128
+ enabled: true,
129
+ level: 'info', // debug, info, warn, error
130
+ file: null, // 日志文件路径,null 表示控制台输出
131
+ maxSize: 10 * 1024 * 1024, // 最大日志文件大小(10MB)
132
+ maxFiles: 5, // 最大日志文件数
133
+
134
+ // 日志格式
135
+ format: {
136
+ timestamp: true,
137
+ level: true,
138
+ message: true,
139
+ metadata: false
140
+ }
141
+ },
142
+
143
+ // 开发模式配置
144
+ development: {
145
+ verbose: false, // 详细输出
146
+ debugMode: false, // 调试模式
147
+ profiling: false, // 性能分析
148
+ mockData: false // 使用模拟数据
149
+ },
150
+ };
@@ -0,0 +1,760 @@
1
+ /*
2
+ comment: 开发目录中打开文件的监听器
3
+ 功能: 监听当前开发目录中打开的文件变化,支持热重载和自动构建
4
+ */
5
+
6
+ var chokidar = require('chokidar');
7
+ var path = require('path');
8
+ var fs = require('fs');
9
+ var utils = require('../lib/utils.js');
10
+ var config = require('../lib/config.js');
11
+ var eslint = require('eslint');
12
+ var jschardet = require('iconv-jschardet');
13
+ var chalk = require('chalk');
14
+
15
+ /**
16
+ * 打开文件监听器类
17
+ * 专门用于监听开发目录中当前打开的文件
18
+ */
19
+ class OpenFileWatcher {
20
+ constructor(options = {}) {
21
+ this.options = {
22
+ // 监听的文件扩展名
23
+ watchExtensions: ['.js', '.css', '.html', '.gspx'],
24
+ // 忽略的目录和文件
25
+ ignored: [
26
+ /node_modules/,
27
+ /\.git/,
28
+ /\.cache/,
29
+ /\.tmp/,
30
+ /dist/,
31
+ /build/,
32
+ /\.min\.(js|css)$/,
33
+ /\.bundle\.(js|css)$/
34
+ ],
35
+ // 防抖延迟时间(毫秒)
36
+ debounceDelay: 300,
37
+ // 是否启用详细日志
38
+ verbose: false,
39
+ // 自定义处理器
40
+ handlers: {},
41
+ ...options
42
+ };
43
+
44
+ this.watcher = null;
45
+ this.isWatching = false;
46
+ this.openFiles = new Set(); // 当前打开的文件
47
+ this.changeQueue = new Map(); // 变化队列
48
+ this.debounceTimers = new Map(); // 防抖定时器
49
+
50
+ // 绑定方法
51
+ this.handleFileChange = this.handleFileChange.bind(this);
52
+ this.processFileChange = this.processFileChange.bind(this);
53
+ }
54
+
55
+ /**
56
+ * 开始监听指定目录
57
+ * @param {string} watchPath - 要监听的目录路径
58
+ */
59
+ start(watchPath = config.webPath) {
60
+ if (this.isWatching) {
61
+ utils.log('文件监听器已在运行中...');
62
+ return this;
63
+ }
64
+
65
+ if (!watchPath || !fs.existsSync(watchPath)) {
66
+ throw new Error(`监听路径不存在: ${watchPath}`);
67
+ }
68
+
69
+ this.watchPath = path.resolve(watchPath);
70
+ utils.log(`开始监听开发目录: ${this.watchPath}`);
71
+
72
+ // 创建 chokidar 监听器
73
+ this.watcher = chokidar.watch(this.watchPath, {
74
+ ignored: this.options.ignored,
75
+ persistent: true,
76
+ ignoreInitial: true,
77
+ followSymlinks: true,
78
+ depth: 10,
79
+ awaitWriteFinish: {
80
+ stabilityThreshold: 200,
81
+ pollInterval: 100
82
+ }
83
+ });
84
+
85
+ // 绑定事件监听器
86
+ this.watcher
87
+ .on('ready', () => {
88
+ this.isWatching = true;
89
+ utils.log('文件监听器已就绪');
90
+ this.scanOpenFiles();
91
+ })
92
+ .on('change', (filePath) => {
93
+ this.handleFileChange('change', filePath);
94
+ })
95
+ .on('add', (filePath) => {
96
+ this.handleFileChange('add', filePath);
97
+ })
98
+ .on('unlink', (filePath) => {
99
+ this.handleFileChange('unlink', filePath);
100
+ this.openFiles.delete(filePath);
101
+ })
102
+ .on('error', (error) => {
103
+ utils.log(`文件监听错误: ${error.message}`);
104
+ });
105
+
106
+ return this;
107
+ }
108
+
109
+ /**
110
+ * 停止监听
111
+ */
112
+ stop() {
113
+ if (this.watcher) {
114
+ this.watcher.close();
115
+ this.isWatching = false;
116
+ this.openFiles.clear();
117
+ this.changeQueue.clear();
118
+
119
+ // 清理所有防抖定时器
120
+ this.debounceTimers.forEach(timer => clearTimeout(timer));
121
+ this.debounceTimers.clear();
122
+
123
+ utils.log('文件监听器已停止');
124
+ }
125
+ return this;
126
+ }
127
+
128
+ /**
129
+ * 添加要监听的打开文件
130
+ * @param {string|Array} filePaths - 文件路径或路径数组
131
+ */
132
+ addOpenFile(filePaths) {
133
+ const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
134
+
135
+ paths.forEach(filePath => {
136
+ const absolutePath = path.resolve(filePath);
137
+ if (this.shouldWatchFile(absolutePath)) {
138
+ this.openFiles.add(absolutePath);
139
+ if (this.options.verbose) {
140
+ utils.debug(`添加监听文件: ${absolutePath}`);
141
+ }
142
+ }
143
+ });
144
+
145
+ return this;
146
+ }
147
+
148
+ /**
149
+ * 移除监听的打开文件
150
+ * @param {string|Array} filePaths - 文件路径或路径数组
151
+ */
152
+ removeOpenFile(filePaths) {
153
+ const paths = Array.isArray(filePaths) ? filePaths : [filePaths];
154
+
155
+ paths.forEach(filePath => {
156
+ const absolutePath = path.resolve(filePath);
157
+ this.openFiles.delete(absolutePath);
158
+ if (this.options.verbose) {
159
+ utils.debug(`移除监听文件: ${absolutePath}`);
160
+ }
161
+ });
162
+
163
+ return this;
164
+ }
165
+
166
+ /**
167
+ * 获取当前监听的打开文件列表
168
+ * @returns {Array} 文件路径数组
169
+ */
170
+ getOpenFiles() {
171
+ return Array.from(this.openFiles);
172
+ }
173
+
174
+ /**
175
+ * 扫描并自动检测可能打开的文件
176
+ * 基于最近修改时间和文件类型
177
+ */
178
+ scanOpenFiles() {
179
+ if (!this.watchPath) return;
180
+
181
+ try {
182
+ const recentFiles = this.findRecentlyModifiedFiles(this.watchPath, 24 * 60 * 60 * 1000); // 24小时内
183
+ recentFiles.forEach(filePath => {
184
+ if (this.shouldWatchFile(filePath)) {
185
+ this.openFiles.add(filePath);
186
+ }
187
+ });
188
+
189
+ utils.log(`自动检测到 ${this.openFiles.size} 个可能打开的文件`);
190
+ } catch (error) {
191
+ utils.debug(`扫描打开文件时出错: ${error.message}`);
192
+ }
193
+ }
194
+
195
+ /**
196
+ * 查找最近修改的文件
197
+ * @param {string} dir - 目录路径
198
+ * @param {number} timeWindow - 时间窗口(毫秒)
199
+ * @returns {Array} 文件路径数组
200
+ */
201
+ findRecentlyModifiedFiles(dir, timeWindow) {
202
+ const recentFiles = [];
203
+ const now = Date.now();
204
+
205
+ const scanDir = (dirPath) => {
206
+ try {
207
+ const items = fs.readdirSync(dirPath);
208
+
209
+ items.forEach(item => {
210
+ const itemPath = path.join(dirPath, item);
211
+ const stats = fs.statSync(itemPath);
212
+
213
+ if (stats.isDirectory()) {
214
+ // 递归扫描子目录(限制深度)
215
+ if (this.shouldScanDirectory(itemPath)) {
216
+ scanDir(itemPath);
217
+ }
218
+ } else if (stats.isFile()) {
219
+ // 检查文件是否在时间窗口内修改过
220
+ if (now - stats.mtime.getTime() < timeWindow) {
221
+ recentFiles.push(itemPath);
222
+ }
223
+ }
224
+ });
225
+ } catch (error) {
226
+ // 忽略权限错误等
227
+ }
228
+ };
229
+
230
+ scanDir(dir);
231
+ return recentFiles;
232
+ }
233
+
234
+ /**
235
+ * 处理文件变化事件
236
+ * @param {string} event - 事件类型
237
+ * @param {string} filePath - 文件路径
238
+ */
239
+ handleFileChange(event, filePath) {
240
+ const absolutePath = path.resolve(filePath);
241
+
242
+ // 只处理打开的文件或符合条件的文件
243
+ if (!this.openFiles.has(absolutePath) && !this.shouldAutoWatch(absolutePath)) {
244
+ return;
245
+ }
246
+
247
+ // 防抖处理
248
+ const fileKey = absolutePath;
249
+ if (this.debounceTimers.has(fileKey)) {
250
+ clearTimeout(this.debounceTimers.get(fileKey));
251
+ }
252
+
253
+ this.debounceTimers.set(fileKey, setTimeout(() => {
254
+ this.processFileChange(event, absolutePath);
255
+ this.debounceTimers.delete(fileKey);
256
+ }, this.options.debounceDelay));
257
+ }
258
+
259
+ /**
260
+ * 处理文件变化
261
+ * @param {string} event - 事件类型
262
+ * @param {string} filePath - 文件路径
263
+ */
264
+ processFileChange(event, filePath) {
265
+ const ext = path.extname(filePath).toLowerCase();
266
+ const relativePath = path.relative(this.watchPath, filePath);
267
+
268
+ // 简化的基本信息输出
269
+ const timestamp = new Date().toLocaleTimeString();
270
+ const fileSize = this.getFileSize(filePath);
271
+
272
+ console.log(chalk.cyan(`\n[${timestamp}] ${filePath} (${event}) - ${fileSize}`));
273
+
274
+ // 如果是 JavaScript 文件变化,执行代码检测
275
+ if (event === 'change' && ext === '.js') {
276
+ console.log(chalk.blue('开始检测 JavaScript 文件...'));
277
+ // 异步执行检测,不阻塞主流程
278
+ this.checkJavaScriptFile(filePath, relativePath).catch(error => {
279
+ console.log(chalk.red(`检测失败: ${error.message}`));
280
+ });
281
+ } else if (event === 'change') {
282
+ // 其他文件类型的简单提示
283
+ this.logFileTypeInfo(ext, event, relativePath);
284
+ }
285
+ }
286
+
287
+ /**
288
+ * 根据文件类型显示日志信息
289
+ * @param {string} ext - 文件扩展名
290
+ * @param {string} event - 事件类型
291
+ * @param {string} relativePath - 相对路径
292
+ */
293
+ logFileTypeInfo(ext, event, relativePath) {
294
+ switch (ext) {
295
+ case '.js':
296
+ console.log(chalk.yellow('JavaScript 文件变动'));
297
+ break;
298
+
299
+ case '.css':
300
+ console.log(chalk.blue('CSS 文件变动'));
301
+ break;
302
+
303
+ case '.html':
304
+ case '.gspx':
305
+ console.log(chalk.green('模板文件变动'));
306
+ break;
307
+
308
+ default:
309
+ console.log(chalk.gray('文件变动'));
310
+ }
311
+ }
312
+
313
+ /**
314
+ * 检查 JavaScript 文件
315
+ * @param {string} filePath - 文件路径
316
+ * @param {string} relativePath - 相对路径
317
+ */
318
+ async checkJavaScriptFile(filePath, relativePath) {
319
+ try {
320
+ // 读取文件内容
321
+ const byte = fs.readFileSync(filePath);
322
+ let content = byte.toString();
323
+ content = utils.toUtf8(byte);
324
+
325
+ // 快速检查
326
+ const encoding = this.checkFileEncoding(byte);
327
+ const sizeCheck = this.checkFileSize(filePath);
328
+
329
+ // ESLint 检查
330
+ const eslintResults = await this.eslintCheck(content);
331
+ const eslintSummary = this.processEslintResults(eslintResults);
332
+
333
+ // 兼容性检查
334
+ const compatSummary = this.checkDangerousSyntax(content);
335
+
336
+ // 汇总输出
337
+ this.printCheckSummary(relativePath, encoding, sizeCheck, eslintSummary, compatSummary);
338
+
339
+ } catch (error) {
340
+ console.log(`❌ 检测失败: ${error.message}`);
341
+ }
342
+ }
343
+
344
+ /**
345
+ * 检查文件编码
346
+ * @param {Buffer} byte - 文件字节
347
+ * @returns {Object} 编码检查结果
348
+ */
349
+ checkFileEncoding(byte) {
350
+ try {
351
+ const ret = jschardet.detect(byte);
352
+ const content = byte.toString();
353
+
354
+ if (utils.isUtf8(byte, ret.encoding) || ret.encoding == 'UTF-8') {
355
+ return { encoding: 'UTF-8', status: 'ok', message: 'UTF-8' };
356
+ } else {
357
+ const reg = /[\u4e00-\u9FA5]+/;
358
+ if (!reg.test(content)) {
359
+ return { encoding: 'UTF-8', status: 'ok', message: 'UTF-8 (无中文)' };
360
+ } else {
361
+ return { encoding: ret.encoding, status: 'warning', message: `${ret.encoding} (建议改为 UTF-8)` };
362
+ }
363
+ }
364
+ } catch (error) {
365
+ return { encoding: '未知', status: 'error', message: `检查失败: ${error.message}` };
366
+ }
367
+ }
368
+
369
+ /**
370
+ * 检查文件大小
371
+ * @param {string} filePath - 文件路径
372
+ * @returns {Object} 文件大小检查结果
373
+ */
374
+ checkFileSize(filePath) {
375
+ try {
376
+ const stats = fs.statSync(filePath);
377
+ const sizeKB = stats.size / 1024;
378
+ const threshold = 300; // 300KB 阈值
379
+
380
+ if (sizeKB > threshold) {
381
+ return {
382
+ size: sizeKB.toFixed(2),
383
+ status: 'warning',
384
+ message: `${sizeKB.toFixed(2)}KB (超过 ${threshold}KB)`
385
+ };
386
+ } else {
387
+ return {
388
+ size: sizeKB.toFixed(2),
389
+ status: 'ok',
390
+ message: `${sizeKB.toFixed(2)}KB`
391
+ };
392
+ }
393
+ } catch (error) {
394
+ return {
395
+ size: '未知',
396
+ status: 'error',
397
+ message: `检查失败: ${error.message}`
398
+ };
399
+ }
400
+ }
401
+
402
+ /**
403
+ * ESLint 检查
404
+ * @param {string} content - 文件内容
405
+ * @returns {Array} 检查结果
406
+ */
407
+ async eslintCheck(content) {
408
+ try {
409
+ const options = {
410
+ cwd: __dirname,
411
+ overrideConfig: {
412
+ globals: {
413
+ 'crabadoc': 'readonly'
414
+ }
415
+ }
416
+ };
417
+
418
+ // 添加配置的全局变量
419
+ const globals = config.globals;
420
+ if (globals) {
421
+ globals.forEach(key => {
422
+ options.overrideConfig.globals[key] = 'readonly';
423
+ });
424
+ }
425
+
426
+ // 自动添加 namespace 的全局对象
427
+ const strs = content.match(/Type\.registerNamespace\((.*?)\)/g);
428
+ if (strs && strs.length > 0) {
429
+ strs.forEach((str) => {
430
+ const names = str.substring(24, str.length - 2).split('.');
431
+ const namespace = names[0];
432
+ options.overrideConfig.globals[namespace] = 'readonly';
433
+ });
434
+ }
435
+
436
+ // 混淆后的变量通配符
437
+ for (let i = 0; i < 300; i++) {
438
+ const key = '_Z_' + i;
439
+ options.overrideConfig.globals[key] = 'writable';
440
+ }
441
+
442
+ const lint = new eslint.ESLint(options);
443
+ const res = await lint.lintText(content);
444
+ return res;
445
+ } catch (error) {
446
+ console.log(`❌ ESLint 检查失败: ${error.message}`);
447
+ return [];
448
+ }
449
+ }
450
+
451
+ /**
452
+ * 处理 ESLint 检查结果
453
+ * @param {Array} eslintResults - ESLint 结果
454
+ * @returns {Object} 检查结果汇总
455
+ */
456
+ processEslintResults(eslintResults) {
457
+ if (!eslintResults || eslintResults.length === 0) {
458
+ return { status: 'no-result', total: 0, errors: 0, warnings: 0, issues: [] };
459
+ }
460
+
461
+ const result = eslintResults[0];
462
+ if (!result.messages || result.messages.length === 0) {
463
+ return { status: 'ok', total: 0, errors: 0, warnings: 0, issues: [] };
464
+ }
465
+
466
+ let errorCount = 0;
467
+ let warningCount = 0;
468
+ let undefCount = 0;
469
+ const issues = [];
470
+
471
+ result.messages.forEach((item) => {
472
+ // 忽略 $ 开头的全局变量
473
+ const reg = new RegExp(/'\$\w+'\sis\snot\sdefined\./);
474
+ if (reg.test(item.message)) {
475
+ return;
476
+ }
477
+
478
+ let type = '提示';
479
+ let severity = 'info';
480
+
481
+ if (!item.ruleId) {
482
+ type = '解析失败';
483
+ severity = 'error';
484
+ errorCount++;
485
+ } else if (item.ruleId.includes('no-undef') || item.ruleId.includes('no-dupe-keys')) {
486
+ type = '未定义';
487
+ severity = 'warning';
488
+ undefCount++;
489
+ } else if (item.ruleId.includes('native-ie')) {
490
+ type = '兼容性';
491
+ severity = 'error';
492
+ errorCount++;
493
+ } else if (item.ruleId.indexOf('no-unused-vars') > -1) {
494
+ if (item.message && (item.message.includes("'sender'") || item.message.includes("'eventArgs'") || item.message.includes("'args'"))) {
495
+ return;
496
+ }
497
+ type = '未使用';
498
+ severity = 'info';
499
+ } else {
500
+ type = '警告';
501
+ severity = 'warning';
502
+ warningCount++;
503
+ }
504
+
505
+ issues.push({
506
+ type,
507
+ severity,
508
+ line: item.line,
509
+ column: item.column,
510
+ message: item.message,
511
+ ruleId: item.ruleId
512
+ });
513
+ });
514
+
515
+ return {
516
+ status: errorCount > 0 ? 'error' : (warningCount > 0 || undefCount > 0) ? 'warning' : 'ok',
517
+ total: issues.length,
518
+ errors: errorCount,
519
+ warnings: warningCount,
520
+ undefined: undefCount,
521
+ issues: issues.slice(0, 3) // 只返回前3个问题
522
+ };
523
+ }
524
+
525
+ /**
526
+ * 检查危险语法
527
+ * @param {string} content - 文件内容
528
+ * @returns {Object} 兼容性检查结果
529
+ */
530
+ checkDangerousSyntax(content) {
531
+ // 危险语法规则
532
+ const dangerousJs = ['\\slet\\s', '\\sconst\\s', '\=\>\\s?\\(?\\{', '\\sasync\\s', '\\sawait\\s', '\\.then\\(', '\\sdebugger\\s'];
533
+
534
+ const reg = new RegExp(dangerousJs.join('|'));
535
+ const noComment = utils.clearComment(content); // 去掉注释的代码
536
+
537
+ if (reg.test(noComment)) {
538
+ const nreg = new RegExp(dangerousJs.join('|'), 'ig');
539
+ const matches = [...noComment.matchAll(nreg)];
540
+
541
+ return {
542
+ status: 'warning',
543
+ hasES6: true,
544
+ count: matches.length,
545
+ samples: matches.slice(0, 3).map(match => ({
546
+ syntax: match[0].trim(),
547
+ position: match.index
548
+ }))
549
+ };
550
+ } else {
551
+ return {
552
+ status: 'ok',
553
+ hasES6: false,
554
+ count: 0,
555
+ samples: []
556
+ };
557
+ }
558
+ }
559
+
560
+ /**
561
+ * 打印检查汇总
562
+ * @param {string} relativePath - 相对路径
563
+ * @param {Object} encoding - 编码检查结果
564
+ * @param {Object} sizeCheck - 大小检查结果
565
+ * @param {Object} eslintSummary - ESLint 检查结果
566
+ * @param {Object} compatSummary - 兼容性检查结果
567
+ */
568
+ printCheckSummary(relativePath, encoding, sizeCheck, eslintSummary, compatSummary) {
569
+ // 获取颜色化的状态文本
570
+ const getColoredStatus = (status, message) => {
571
+ switch (status) {
572
+ case 'ok': return chalk.green(message);
573
+ case 'warning': return chalk.yellow(message);
574
+ case 'error': return chalk.red(message);
575
+ default: return chalk.gray(message);
576
+ }
577
+ };
578
+
579
+ // 简化的一行汇总
580
+ const encodingText = getColoredStatus(encoding.status, `编码: ${encoding.message}`);
581
+ const sizeText = getColoredStatus(sizeCheck.status, `大小: ${sizeCheck.message}`);
582
+ const eslintText = getColoredStatus(eslintSummary.status, `语法: ${this.getEslintSummary(eslintSummary)}`);
583
+ const compatText = getColoredStatus(compatSummary.status, `兼容性: ${this.getCompatSummary(compatSummary)}`);
584
+
585
+ console.log(` ${encodingText} | ${sizeText} | ${eslintText} | ${compatText}`);
586
+
587
+ // 如果有严重问题,显示详细信息
588
+ if (eslintSummary.status === 'error' || eslintSummary.errors > 0) {
589
+ console.log(chalk.red(' 严重问题详情:'));
590
+ eslintSummary.issues.filter(issue => issue.severity === 'error').forEach(issue => {
591
+ console.log(chalk.red(` 第${issue.line}行: ${issue.message}`));
592
+ });
593
+ }
594
+
595
+ // 如果有兼容性问题,显示详情
596
+ if (compatSummary.hasES6 && compatSummary.samples.length > 0) {
597
+ console.log(chalk.yellow(` 兼容性问题: ${compatSummary.samples.map(s => s.syntax).join(', ')}`));
598
+ }
599
+ }
600
+
601
+ /**
602
+ * 获取 ESLint 汇总信息
603
+ * @param {Object} summary - ESLint 检查结果
604
+ * @returns {string} 汇总信息
605
+ */
606
+ getEslintSummary(summary) {
607
+ if (summary.status === 'no-result') return '无结果';
608
+ if (summary.status === 'ok') return '无问题';
609
+
610
+ const parts = [];
611
+ if (summary.errors > 0) parts.push(`${summary.errors}错误`);
612
+ if (summary.warnings > 0) parts.push(`${summary.warnings}警告`);
613
+ if (summary.undefined > 0) parts.push(`${summary.undefined}未定义`);
614
+
615
+ return parts.join(',') || `${summary.total}问题`;
616
+ }
617
+
618
+ /**
619
+ * 获取兼容性汇总信息
620
+ * @param {Object} summary - 兼容性检查结果
621
+ * @returns {string} 汇总信息
622
+ */
623
+ getCompatSummary(summary) {
624
+ if (summary.hasES6) {
625
+ return `发现${summary.count}个ES6+语法`;
626
+ }
627
+ return '无ES6+语法';
628
+ }
629
+
630
+ /**
631
+ * 获取文件大小
632
+ * @param {string} filePath - 文件路径
633
+ * @returns {string} 格式化的文件大小
634
+ */
635
+ getFileSize(filePath) {
636
+ try {
637
+ const stats = fs.statSync(filePath);
638
+ const bytes = stats.size;
639
+
640
+ if (bytes === 0) return '0 B';
641
+
642
+ const k = 1024;
643
+ const sizes = ['B', 'KB', 'MB', 'GB'];
644
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
645
+
646
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
647
+ } catch (error) {
648
+ return '未知';
649
+ }
650
+ }
651
+
652
+ /**
653
+ * 判断是否应该监听该文件
654
+ * @param {string} filePath - 文件路径
655
+ * @returns {boolean}
656
+ */
657
+ shouldWatchFile(filePath) {
658
+ const ext = path.extname(filePath).toLowerCase();
659
+ return this.options.watchExtensions.includes(ext) &&
660
+ !this.isIgnored(filePath);
661
+ }
662
+
663
+ /**
664
+ * 判断是否应该自动监听该文件
665
+ * @param {string} filePath - 文件路径
666
+ * @returns {boolean}
667
+ */
668
+ shouldAutoWatch(filePath) {
669
+ // 可以根据需要实现自动监听逻辑
670
+ // 例如:最近修改的文件、特定模式的文件等
671
+ return false;
672
+ }
673
+
674
+ /**
675
+ * 判断是否应该扫描该目录
676
+ * @param {string} dirPath - 目录路径
677
+ * @returns {boolean}
678
+ */
679
+ shouldScanDirectory(dirPath) {
680
+ return !this.isIgnored(dirPath);
681
+ }
682
+
683
+ /**
684
+ * 判断路径是否被忽略
685
+ * @param {string} filePath - 文件路径
686
+ * @returns {boolean}
687
+ */
688
+ isIgnored(filePath) {
689
+ return this.options.ignored.some(pattern => {
690
+ if (pattern instanceof RegExp) {
691
+ return pattern.test(filePath);
692
+ }
693
+ return filePath.includes(pattern);
694
+ });
695
+ }
696
+
697
+ /**
698
+ * 简单的事件发射器
699
+ * @param {string} event - 事件名
700
+ * @param {*} data - 事件数据
701
+ */
702
+ emit(event, data) {
703
+ // 这里可以集成到现有的事件系统中
704
+ // 或者通过 WebSocket 发送到前端
705
+ if (config.refresh && global.socketBroadcast) {
706
+ global.socketBroadcast(JSON.stringify({
707
+ mode: 'openFileChange',
708
+ event: event,
709
+ data: data
710
+ }));
711
+ }
712
+ }
713
+
714
+ /**
715
+ * 获取监听状态
716
+ * @returns {boolean}
717
+ */
718
+ isActive() {
719
+ return this.isWatching;
720
+ }
721
+
722
+ /**
723
+ * 获取监听统计信息
724
+ * @returns {Object}
725
+ */
726
+ getStats() {
727
+ return {
728
+ isWatching: this.isWatching,
729
+ watchPath: this.watchPath,
730
+ openFilesCount: this.openFiles.size,
731
+ queuedChanges: this.changeQueue.size,
732
+ activeTimers: this.debounceTimers.size
733
+ };
734
+ }
735
+ }
736
+
737
+ // 导出类和工厂函数
738
+ module.exports = {
739
+ OpenFileWatcher,
740
+
741
+ /**
742
+ * 创建打开文件监听器实例
743
+ * @param {Object} options - 配置选项
744
+ * @returns {OpenFileWatcher}
745
+ */
746
+ createWatcher(options) {
747
+ return new OpenFileWatcher(options);
748
+ },
749
+
750
+ /**
751
+ * 快速启动打开文件监听
752
+ * @param {string} watchPath - 监听路径
753
+ * @param {Object} options - 配置选项
754
+ * @returns {OpenFileWatcher}
755
+ */
756
+ startWatching(watchPath, options = {}) {
757
+ const watcher = new OpenFileWatcher(options);
758
+ return watcher.start(watchPath);
759
+ }
760
+ };