zen-gitsync 2.5.3 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,16 +1,17 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>Zen-GitSync - Git同步工具</title>
8
- <script type="module" crossorigin src="/assets/index-CnCbYqLq.js"></script>
9
- <link rel="modulepreload" crossorigin href="/assets/vendor-Dys2BbyU.js">
10
- <link rel="stylesheet" crossorigin href="/assets/vendor-HJmoQ7iQ.css">
11
- <link rel="stylesheet" crossorigin href="/assets/index-BApDCqKk.css">
12
- </head>
13
- <body>
14
- <div id="app"></div>
15
- </body>
16
- </html>
1
+ <!doctype html>
2
+ <html lang="zh-CN" translate="no">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="google" content="notranslate" />
6
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+ <title>Zen-GitSync - Git同步工具</title>
9
+ <script type="module" crossorigin src="/assets/index-BlZI1-J0.js"></script>
10
+ <link rel="modulepreload" crossorigin href="/assets/vendor-TIhvVDcs.js">
11
+ <link rel="stylesheet" crossorigin href="/assets/vendor-D9qDBEE1.css">
12
+ <link rel="stylesheet" crossorigin href="/assets/index-D0V6ggoJ.css">
13
+ </head>
14
+ <body>
15
+ <div id="app"></div>
16
+ </body>
17
+ </html>
@@ -625,8 +625,11 @@ async function startUIServer(noOpen = false, savePort = false) {
625
625
  newProjectRoomId: newProjectRoomId
626
626
  });
627
627
 
628
- // 重新初始化文件监控(新路径)
629
- initFileSystemWatcher();
628
+ // 不再自动启动文件监控,只在用户手动开启自动更新开关时启动
629
+ // console.log('[切换目录] 将在3秒后初始化文件监控器');
630
+ // setTimeout(() => {
631
+ // initFileSystemWatcher().catch(err => console.error('[文件监控] 初始化失败:', err));
632
+ // }, 3000);
630
633
 
631
634
  res.json({
632
635
  success: true,
@@ -1973,9 +1976,28 @@ async function startUIServer(noOpen = false, savePort = false) {
1973
1976
  return res.status(400).json({ error: '缺少文件路径参数' });
1974
1977
  }
1975
1978
 
1979
+ // 检查是否是编译/压缩文件(通常很大且不需要查看diff)
1980
+ const isCompiledFile = /\.(min\.js|umd\.cjs|bundle\.js|dist\.js|prod\.js)$/i.test(filePath);
1981
+ if (isCompiledFile) {
1982
+ return res.json({
1983
+ diff: '⚠️ 检测到编译/打包文件,diff内容过大已跳过显示。\n\n提示:这类文件通常是自动生成的,不建议查看diff。\n如需查看,请使用命令行:git diff -- "' + filePath + '"',
1984
+ isLargeFile: true
1985
+ });
1986
+ }
1987
+
1976
1988
  // 执行git diff命令获取文件差异
1977
1989
  const { stdout } = await execGitCommand(`git diff -- "${filePath}"`);
1978
1990
 
1991
+ // 检查diff大小,如果超过500KB,只返回提示
1992
+ const diffSizeKB = Buffer.byteLength(stdout, 'utf8') / 1024;
1993
+ if (diffSizeKB > 500) {
1994
+ return res.json({
1995
+ diff: `⚠️ Diff内容过大 (${diffSizeKB.toFixed(1)}KB),已跳过显示以避免浏览器卡顿。\n\n提示:请使用命令行查看:git diff -- "${filePath}"`,
1996
+ isLargeFile: true,
1997
+ size: diffSizeKB
1998
+ });
1999
+ }
2000
+
1979
2001
  res.json({ diff: stdout });
1980
2002
  } catch (error) {
1981
2003
  res.status(500).json({ error: error.message });
@@ -1990,9 +2012,28 @@ async function startUIServer(noOpen = false, savePort = false) {
1990
2012
  return res.status(400).json({ error: '缺少文件路径参数' });
1991
2013
  }
1992
2014
 
2015
+ // 检查是否是编译/压缩文件
2016
+ const isCompiledFile = /\.(min\.js|umd\.cjs|bundle\.js|dist\.js|prod\.js)$/i.test(filePath);
2017
+ if (isCompiledFile) {
2018
+ return res.json({
2019
+ diff: '⚠️ 检测到编译/打包文件,diff内容过大已跳过显示。\n\n提示:这类文件通常是自动生成的,不建议查看diff。\n如需查看,请使用命令行:git diff --cached -- "' + filePath + '"',
2020
+ isLargeFile: true
2021
+ });
2022
+ }
2023
+
1993
2024
  // 执行git diff --cached命令获取已暂存文件差异
1994
2025
  const { stdout } = await execGitCommand(`git diff --cached -- "${filePath}"`);
1995
2026
 
2027
+ // 检查diff大小
2028
+ const diffSizeKB = Buffer.byteLength(stdout, 'utf8') / 1024;
2029
+ if (diffSizeKB > 500) {
2030
+ return res.json({
2031
+ diff: `⚠️ Diff内容过大 (${diffSizeKB.toFixed(1)}KB),已跳过显示以避免浏览器卡顿。\n\n提示:请使用命令行查看:git diff --cached -- "${filePath}"`,
2032
+ isLargeFile: true,
2033
+ size: diffSizeKB
2034
+ });
2035
+ }
2036
+
1996
2037
  res.json({ diff: stdout });
1997
2038
  } catch (error) {
1998
2039
  res.status(500).json({ error: error.message });
@@ -2924,64 +2965,211 @@ async function startUIServer(noOpen = false, savePort = false) {
2924
2965
 
2925
2966
  // ========== NPM 脚本管理相关 API ==========
2926
2967
 
2968
+ // 存储正在进行的扫描任务
2969
+ let currentScanAbortController = null;
2970
+
2927
2971
  // 扫描项目目录及子目录下的所有package.json,并提取scripts
2928
2972
  app.get('/api/scan-npm-scripts', async (req, res) => {
2973
+ // 取消之前的扫描
2974
+ if (currentScanAbortController) {
2975
+ currentScanAbortController.aborted = true;
2976
+ }
2977
+
2978
+ // 创建新的abort controller
2979
+ currentScanAbortController = {
2980
+ aborted: false,
2981
+ abort() { this.aborted = true; }
2982
+ };
2983
+ const scanController = currentScanAbortController;
2929
2984
  try {
2930
2985
  const projectRoot = process.cwd();
2931
2986
  const packageJsons = [];
2987
+ const startTime = Date.now();
2988
+
2989
+ console.log(`[NPM扫描-后端] 开始扫描项目: ${projectRoot}`);
2990
+
2991
+ // 需要忽略的目录列表(更全面)
2992
+ const IGNORED_DIRS = new Set([
2993
+ 'node_modules',
2994
+ '.git',
2995
+ '.svn',
2996
+ '.hg',
2997
+ 'dist',
2998
+ 'build',
2999
+ 'coverage',
3000
+ 'out',
3001
+ 'target',
3002
+ 'vendor',
3003
+ '__pycache__',
3004
+ '.next',
3005
+ '.nuxt',
3006
+ '.vscode',
3007
+ '.idea',
3008
+ 'tmp',
3009
+ 'temp',
3010
+ 'cache',
3011
+ '.cache'
3012
+ ]);
3013
+
3014
+ // 优先扫描的子目录(monorepo常见结构)
3015
+ const PRIORITY_DIRS = ['packages', 'apps', 'libs', 'services', 'modules'];
3016
+
3017
+ let scannedCount = 0;
3018
+ let skippedCount = 0;
3019
+ let fileReadCount = 0; // 统计实际读取的文件数量
3020
+
3021
+ // 检查指定目录下是否有package.json
3022
+ async function checkPackageJson(dir) {
3023
+ if (scanController.aborted) return false;
3024
+
3025
+ try {
3026
+ const packagePath = path.join(dir, 'package.json');
3027
+
3028
+ // 先检查文件是否存在,避免不必要的读取
3029
+ try {
3030
+ await fs.access(packagePath);
3031
+ } catch {
3032
+ // 文件不存在,直接返回
3033
+ return false;
3034
+ }
3035
+
3036
+ // 检查文件大小,避免读取异常大的文件
3037
+ const stats = await fs.stat(packagePath);
3038
+ const fileSizeMB = stats.size / (1024 * 1024);
3039
+ if (fileSizeMB > 1) {
3040
+ // package.json超过1MB是异常情况,跳过
3041
+ console.log(`[NPM扫描] 跳过超大文件 (${fileSizeMB.toFixed(2)}MB): ${packagePath}`);
3042
+ return false;
3043
+ }
3044
+
3045
+ fileReadCount++; // 只有文件存在且大小合理时才计数
3046
+ const content = await fs.readFile(packagePath, 'utf8');
3047
+ const packageData = JSON.parse(content);
3048
+
3049
+ // 只有当scripts存在且至少有一个脚本时才添加
3050
+ if (packageData.scripts && Object.keys(packageData.scripts).length > 0) {
3051
+ const relativePath = path.relative(projectRoot, dir);
3052
+ packageJsons.push({
3053
+ path: dir,
3054
+ relativePath: relativePath || '.',
3055
+ name: packageData.name || path.basename(dir),
3056
+ scripts: packageData.scripts
3057
+ });
3058
+ return true;
3059
+ }
3060
+ } catch (error) {
3061
+ // 文件不存在或解析失败,忽略
3062
+ }
3063
+ return false;
3064
+ }
3065
+
3066
+ // 递归扫描目录,最大深度4层
3067
+ const MAX_DEPTH = 4;
3068
+ const MAX_DIRS_PER_LEVEL = 50; // 每层最多扫描50个子目录
3069
+
3070
+ // 统计每层深度的扫描数量
3071
+ const depthStats = Array(MAX_DEPTH + 1).fill(0).map(() => ({ count: 0, time: 0 }));
2932
3072
 
2933
- // 递归扫描目录查找package.json
2934
3073
  async function scanDirectory(dir, depth = 0) {
2935
- // 限制扫描深度,避免扫描过深
2936
- if (depth > 5) return;
3074
+ if (scanController.aborted) return;
3075
+ if (depth > MAX_DEPTH) return;
3076
+
3077
+ const depthStart = Date.now();
3078
+ scannedCount++;
3079
+ depthStats[depth].count++;
2937
3080
 
3081
+ // 检查当前目录的package.json
3082
+ await checkPackageJson(dir);
3083
+
3084
+ // 如果已经达到最大深度,不再继续
3085
+ if (depth >= MAX_DEPTH) return;
3086
+
3087
+ // 读取子目录
2938
3088
  try {
3089
+ if (scanController.aborted) return;
3090
+
2939
3091
  const items = await fs.readdir(dir, { withFileTypes: true });
3092
+ const subDirs = [];
2940
3093
 
3094
+ // 收集所有子目录
2941
3095
  for (const item of items) {
2942
- const fullPath = path.join(dir, item.name);
3096
+ if (scanController.aborted) return;
3097
+ if (!item.isDirectory()) continue;
2943
3098
 
2944
- // 跳过常见的不需要扫描的目录
2945
- if (item.isDirectory()) {
2946
- const dirName = item.name;
2947
- if (dirName === 'node_modules' ||
2948
- dirName === '.git' ||
2949
- dirName === 'dist' ||
2950
- dirName === 'build' ||
2951
- dirName.startsWith('.')) {
2952
- continue;
2953
- }
2954
-
2955
- // 递归扫描子目录
2956
- await scanDirectory(fullPath, depth + 1);
2957
- } else if (item.name === 'package.json') {
2958
- // 读取package.json文件
2959
- try {
2960
- const content = await fs.readFile(fullPath, 'utf8');
2961
- const packageData = JSON.parse(content);
2962
-
2963
- // 只有当scripts存在且至少有一个脚本时才添加
2964
- if (packageData.scripts && Object.keys(packageData.scripts).length > 0) {
2965
- const relativePath = path.relative(projectRoot, dir);
2966
- packageJsons.push({
2967
- path: dir,
2968
- relativePath: relativePath || '.',
2969
- name: packageData.name || path.basename(dir),
2970
- scripts: packageData.scripts
2971
- });
2972
- }
2973
- } catch (error) {
2974
- console.error(`读取package.json失败: ${fullPath}`, error);
2975
- }
3099
+ const dirName = item.name;
3100
+
3101
+ // 跳过忽略的目录
3102
+ if (IGNORED_DIRS.has(dirName) || dirName.startsWith('.')) {
3103
+ skippedCount++;
3104
+ continue;
2976
3105
  }
3106
+
3107
+ subDirs.push(item);
3108
+ }
3109
+
3110
+ // 限制每层扫描的子目录数量
3111
+ const dirsToScan = subDirs.slice(0, MAX_DIRS_PER_LEVEL);
3112
+ if (subDirs.length > MAX_DIRS_PER_LEVEL) {
3113
+ skippedCount += subDirs.length - MAX_DIRS_PER_LEVEL;
3114
+ }
3115
+
3116
+ // 优先处理优先目录
3117
+ const priorityDirs = dirsToScan.filter(item => PRIORITY_DIRS.includes(item.name));
3118
+ const normalDirs = dirsToScan.filter(item => !PRIORITY_DIRS.includes(item.name));
3119
+
3120
+ // 先扫描优先目录
3121
+ for (const item of priorityDirs) {
3122
+ if (scanController.aborted) return;
3123
+ const subDirPath = path.join(dir, item.name);
3124
+ await scanDirectory(subDirPath, depth + 1);
2977
3125
  }
3126
+
3127
+ // 再扫描普通目录
3128
+ for (const item of normalDirs) {
3129
+ if (scanController.aborted) return;
3130
+ const subDirPath = path.join(dir, item.name);
3131
+ await scanDirectory(subDirPath, depth + 1);
3132
+ }
3133
+
2978
3134
  } catch (error) {
2979
- console.error(`扫描目录失败: ${dir}`, error);
3135
+ // 忽略无法访问的目录
2980
3136
  }
3137
+
3138
+ // 记录该深度的耗时
3139
+ depthStats[depth].time += Date.now() - depthStart;
2981
3140
  }
2982
3141
 
2983
- // 从项目根目录开始扫描
2984
- await scanDirectory(projectRoot);
3142
+ // 执行递归扫描
3143
+ console.log(`[NPM扫描-后端] 开始递归扫描(最大深度${MAX_DEPTH}层)`);
3144
+ const scanStart = Date.now();
3145
+ await scanDirectory(projectRoot, 0);
3146
+ console.log(`[NPM扫描-后端] 递归扫描完成,耗时${Date.now() - scanStart}ms`);
3147
+
3148
+ // 扫描完成,清除abort controller
3149
+ if (currentScanAbortController === scanController) {
3150
+ currentScanAbortController = null;
3151
+ }
3152
+
3153
+ const scanTime = Date.now() - startTime;
3154
+
3155
+ if (scanController.aborted) {
3156
+ console.log(`npm脚本扫描被取消,耗时${scanTime}ms`);
3157
+ return res.json({
3158
+ success: true,
3159
+ packages: [],
3160
+ totalScripts: 0,
3161
+ cancelled: true
3162
+ });
3163
+ }
3164
+
3165
+ // 输出每层深度的统计
3166
+ const depthInfo = depthStats
3167
+ .map((stat, depth) => stat.count > 0 ? `深度${depth}:${stat.count}个(${stat.time}ms)` : null)
3168
+ .filter(Boolean)
3169
+ .join(', ');
3170
+
3171
+ console.log(`npm脚本扫描完成,耗时${scanTime}ms,扫描了${scannedCount}个目录,读取了${fileReadCount}个package.json文件,跳过${skippedCount}个目录,找到${packageJsons.length}个有效的package.json`);
3172
+ console.log(`[NPM扫描-后端] 深度分布: ${depthInfo}`);
2985
3173
 
2986
3174
  res.json({
2987
3175
  success: true,
@@ -3076,7 +3264,7 @@ async function startUIServer(noOpen = false, savePort = false) {
3076
3264
  // 客户端可以请求开始/停止监控
3077
3265
  socket.on('start_monitoring', () => {
3078
3266
  if (!watcher) {
3079
- initFileSystemWatcher();
3267
+ initFileSystemWatcher().catch(err => console.error('[文件监控] 初始化失败:', err));
3080
3268
  socket.emit('monitoring_status', { active: true });
3081
3269
  }
3082
3270
  });
@@ -3114,8 +3302,99 @@ async function startUIServer(noOpen = false, savePort = false) {
3114
3302
  });
3115
3303
  });
3116
3304
 
3305
+ // 读取并解析.gitignore文件
3306
+ async function parseGitignore(projectPath) {
3307
+ const gitignorePath = path.join(projectPath, '.gitignore');
3308
+ const ignorePatterns = [
3309
+ /(^|[\/\\])\../, // 始终忽略.开头的文件(除了.gitignore本身)
3310
+ '**/.git/**', // 始终忽略.git目录
3311
+
3312
+ // 额外排除常见的编译产物和大文件,减少监控开销
3313
+ '**/*.umd.cjs', // UMD打包文件
3314
+ '**/*.min.js', // 压缩JS文件
3315
+ '**/*.bundle.js', // Webpack打包文件
3316
+ '**/*.dist.js', // 构建产物
3317
+ '**/*.prod.js', // 生产环境文件
3318
+ '**/lib/**', // 通常是编译产物
3319
+ '**/es/**', // ES模块编译产物
3320
+ '**/esm/**', // ES模块编译产物
3321
+ '**/*.map', // Source map文件
3322
+ '**/*.chunk.js', // 代码分割chunk
3323
+ ];
3324
+
3325
+ try {
3326
+ const gitignoreContent = await fs.readFile(gitignorePath, 'utf8');
3327
+ const lines = gitignoreContent.split('\n');
3328
+ let validRules = 0;
3329
+
3330
+ for (let line of lines) {
3331
+ line = line.trim();
3332
+
3333
+ // 跳过空行和注释
3334
+ if (!line || line.startsWith('#')) continue;
3335
+
3336
+ // 移除行尾的空格
3337
+ line = line.replace(/\s+$/, '');
3338
+
3339
+ // 跳过否定规则(chokidar不支持否定规则,这些规则会被忽略)
3340
+ if (line.startsWith('!')) {
3341
+ continue;
3342
+ }
3343
+
3344
+ // 将gitignore规则转换为glob模式
3345
+ let pattern;
3346
+
3347
+ // 如果以/开头,表示从根目录开始匹配
3348
+ if (line.startsWith('/')) {
3349
+ pattern = line.substring(1);
3350
+ // 如果是目录,添加/**后缀
3351
+ if (!pattern.includes('*') && !pattern.includes('.')) {
3352
+ ignorePatterns.push(pattern);
3353
+ ignorePatterns.push(pattern + '/**');
3354
+ } else {
3355
+ ignorePatterns.push(pattern);
3356
+ }
3357
+ } else if (line.endsWith('/')) {
3358
+ // 明确的目录规则
3359
+ const dirName = line.slice(0, -1);
3360
+ ignorePatterns.push('**/' + dirName);
3361
+ ignorePatterns.push('**/' + dirName + '/**');
3362
+ } else {
3363
+ // 文件或目录规则
3364
+ // 如果包含*通配符,直接使用
3365
+ if (line.includes('*')) {
3366
+ ignorePatterns.push('**/' + line);
3367
+ } else {
3368
+ // 既匹配文件也匹配目录
3369
+ ignorePatterns.push('**/' + line);
3370
+ ignorePatterns.push('**/' + line + '/**');
3371
+ }
3372
+ }
3373
+
3374
+ validRules++;
3375
+ }
3376
+
3377
+ console.log(`[文件监控] 从.gitignore读取了 ${validRules} 条有效的忽略规则`);
3378
+ } catch (error) {
3379
+ // .gitignore不存在或读取失败,使用默认规则
3380
+ console.log('[文件监控] 未找到.gitignore,使用默认忽略规则');
3381
+ ignorePatterns.push(
3382
+ '**/node_modules/**',
3383
+ '**/dist/**',
3384
+ '**/build/**',
3385
+ '**/coverage/**',
3386
+ '**/.nuxt/**',
3387
+ '**/.next/**',
3388
+ '**/out/**',
3389
+ '**/*.log'
3390
+ );
3391
+ }
3392
+
3393
+ return ignorePatterns;
3394
+ }
3395
+
3117
3396
  // 初始化文件系统监控
3118
- function initFileSystemWatcher() {
3397
+ async function initFileSystemWatcher() {
3119
3398
  // 停止已有的监控器
3120
3399
  if (watcher) {
3121
3400
  watcher.close().catch(err => console.error('关闭旧监控器失败:', err));
@@ -3133,15 +3412,18 @@ async function startUIServer(noOpen = false, savePort = false) {
3133
3412
  return;
3134
3413
  }
3135
3414
 
3415
+ const watcherStartTime = Date.now();
3416
+ console.log('[文件监控] 开始初始化监控器...');
3417
+
3418
+ // 从.gitignore读取忽略规则
3419
+ const ignorePatterns = await parseGitignore(currentDir);
3420
+
3136
3421
  // 使用chokidar监控文件变动
3137
3422
  watcher = chokidar.watch(currentDir, {
3138
- ignored: [
3139
- /(^|[\/\\])\../, // 忽略.开头的文件和目录
3140
- '**/node_modules/**', // 忽略node_modules
3141
- '**/.git/**', // 忽略.git目录
3142
- ],
3423
+ ignored: ignorePatterns,
3143
3424
  persistent: true,
3144
3425
  ignoreInitial: true, // 忽略初始扫描时的文件
3426
+ depth: 10, // 限制扫描深度,避免过深的目录结构
3145
3427
  awaitWriteFinish: {
3146
3428
  stabilityThreshold: 300, // 等待文件写入完成的时间
3147
3429
  pollInterval: 100 // 轮询间隔
@@ -3157,11 +3439,16 @@ async function startUIServer(noOpen = false, savePort = false) {
3157
3439
  });
3158
3440
  });
3159
3441
 
3442
+ watcher.on('ready', () => {
3443
+ const initTime = Date.now() - watcherStartTime;
3444
+ console.log(`[文件监控] 监控器初始化完成,耗时 ${initTime}ms`);
3445
+ });
3446
+
3160
3447
  watcher.on('error', error => {
3161
- console.error('文件监控错误:', error);
3448
+ console.error('[文件监控] 监控错误:', error);
3162
3449
  });
3163
3450
 
3164
- console.log('文件系统监控器已启动');
3451
+ console.log('[文件监控] 监控器已启动(异步初始化中...)');
3165
3452
  } catch (error) {
3166
3453
  console.error('启动文件监控失败:', error);
3167
3454
  }
@@ -3308,9 +3595,10 @@ async function startUIServer(noOpen = false, savePort = false) {
3308
3595
  console.log(chalk.green(` 启动时间: ${new Date().toLocaleString()}`));
3309
3596
 
3310
3597
  if (isGitRepo) {
3311
- console.log(chalk.green(` 当前目录是Git仓库,文件监控已启动`));
3312
- // 启动文件监控
3313
- initFileSystemWatcher();
3598
+ console.log(chalk.green(` 当前目录是Git仓库`));
3599
+ console.log(chalk.yellow(` 文件监控已禁用(默认),需要时请在前端开启自动更新开关`));
3600
+ // 不再自动启动文件监控,只在用户开启自动更新开关时启动
3601
+ // initFileSystemWatcher().catch(err => console.error('[文件监控] 初始化失败:', err));
3314
3602
  } else {
3315
3603
  console.log(chalk.yellow(` 当前目录不是Git仓库,文件监控未启动`));
3316
3604
  }