zen-gitsync 2.6.3 → 2.6.5

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.
@@ -7,23 +7,15 @@ import open from 'open';
7
7
  import config from '../../config.js';
8
8
  import chalk from 'chalk';
9
9
  import fs from 'fs/promises';
10
+ import fsSync from 'fs';
10
11
  import os from 'os';
11
12
  import { Server } from 'socket.io';
12
- import chokidar from 'chokidar';
13
13
  import { spawn } from 'child_process';
14
14
  // import { exec } from 'child_process';
15
15
 
16
16
  const __filename = fileURLToPath(import.meta.url);
17
17
  const __dirname = path.dirname(__filename);
18
18
  const configManager = config; // 确保 configManager 可用
19
-
20
- // 文件系统变动监控器
21
- let watcher = null;
22
- // 防抖计时器
23
- let debounceTimer = null;
24
- // 防抖延迟时间 (毫秒)
25
- const DEBOUNCE_DELAY = 1000;
26
-
27
19
  // 分支状态缓存
28
20
  let branchStatusCache = {
29
21
  currentBranch: null,
@@ -156,6 +148,55 @@ async function startUIServer(noOpen = false, savePort = false) {
156
148
  }
157
149
  });
158
150
 
151
+ // 在新终端中执行自定义命令
152
+ app.post('/api/exec-in-terminal', async (req, res) => {
153
+ try {
154
+ const { command } = req.body || {};
155
+ if (!command || typeof command !== 'string' || !command.trim()) {
156
+ return res.status(400).json({ success: false, error: 'command 不能为空' });
157
+ }
158
+
159
+ console.log(`在终端中执行命令: ${command}`);
160
+
161
+ // 根据操作系统选择合适的终端命令
162
+ let terminalCommand;
163
+
164
+ if (process.platform === 'win32') {
165
+ // Windows: 使用 start 命令打开新的 cmd 窗口
166
+ // /K 参数表示执行命令后保持窗口打开
167
+ terminalCommand = `start cmd /K "cd /d ${currentProjectPath} && ${command}"`;
168
+ } else if (process.platform === 'darwin') {
169
+ // macOS: 使用 osascript 打开 Terminal.app
170
+ const script = `tell application "Terminal" to do script "cd ${currentProjectPath} && ${command}"`;
171
+ terminalCommand = `osascript -e '${script}'`;
172
+ } else {
173
+ // Linux: 尝试常见的终端模拟器
174
+ terminalCommand = `gnome-terminal -- bash -c "cd ${currentProjectPath} && ${command}; exec bash" || xterm -e "cd ${currentProjectPath} && ${command}; bash"`;
175
+ }
176
+
177
+ // 执行命令打开新终端
178
+ const { exec } = await import('child_process');
179
+ exec(terminalCommand, (error, stdout, stderr) => {
180
+ if (error) {
181
+ console.error('打开终端失败:', error);
182
+ }
183
+ });
184
+
185
+ res.json({
186
+ success: true,
187
+ message: `已在新终端中执行命令`,
188
+ command: command,
189
+ path: currentProjectPath
190
+ });
191
+ } catch (error) {
192
+ console.error('在终端中执行命令失败:', error);
193
+ res.status(500).json({
194
+ success: false,
195
+ error: `在终端中执行命令失败: ${error.message}`
196
+ });
197
+ }
198
+ });
199
+
159
200
  // API路由
160
201
  // 移除了 /api/status 端点,因为前端只使用 porcelain 格式
161
202
 
@@ -612,12 +653,6 @@ async function startUIServer(noOpen = false, savePort = false) {
612
653
  console.warn('初始化项目配置失败:', e?.message || e);
613
654
  }
614
655
 
615
- // 关闭旧的文件监控
616
- if (watcher) {
617
- watcher.close().catch(err => console.error('关闭旧监控器失败:', err));
618
- watcher = null;
619
- }
620
-
621
656
  // 通知所有旧房间的客户端项目已切换
622
657
  io.to(projectRoomId).emit('project_changed', {
623
658
  oldProjectPath: currentProjectPath,
@@ -645,11 +680,6 @@ async function startUIServer(noOpen = false, savePort = false) {
645
680
  projectRoomId = newProjectRoomId;
646
681
  isGitRepo = false;
647
682
 
648
- if (watcher) {
649
- watcher.close().catch(err => console.error('关闭监控器失败:', err));
650
- watcher = null;
651
- }
652
-
653
683
  // 通知所有旧房间的客户端项目已切换
654
684
  io.to(projectRoomId).emit('project_changed', {
655
685
  oldProjectPath: currentProjectPath,
@@ -1397,6 +1427,148 @@ async function startUIServer(noOpen = false, savePort = false) {
1397
1427
  }
1398
1428
  })
1399
1429
 
1430
+ // 置顶模板
1431
+ app.post('/api/config/pin-template', express.json(), async (req, res) => {
1432
+ try {
1433
+ const { template, type } = req.body
1434
+
1435
+ if (!template || !type) {
1436
+ return res.status(400).json({ success: false, error: '缺少必要参数' })
1437
+ }
1438
+
1439
+ const config = await configManager.loadConfig()
1440
+
1441
+ if (type === 'description') {
1442
+ if (config.descriptionTemplates) {
1443
+ // 删除原位置的模板
1444
+ config.descriptionTemplates = config.descriptionTemplates.filter(t => t !== template)
1445
+ // 添加到第一位
1446
+ config.descriptionTemplates.unshift(template)
1447
+ await configManager.saveConfig(config)
1448
+ } else {
1449
+ return res.status(404).json({ success: false, error: '模板列表不存在' })
1450
+ }
1451
+ } else if (type === 'scope') {
1452
+ if (config.scopeTemplates) {
1453
+ config.scopeTemplates = config.scopeTemplates.filter(t => t !== template)
1454
+ config.scopeTemplates.unshift(template)
1455
+ await configManager.saveConfig(config)
1456
+ } else {
1457
+ return res.status(404).json({ success: false, error: '模板列表不存在' })
1458
+ }
1459
+ } else if (type === 'message') {
1460
+ if (config.messageTemplates) {
1461
+ config.messageTemplates = config.messageTemplates.filter(t => t !== template)
1462
+ config.messageTemplates.unshift(template)
1463
+ await configManager.saveConfig(config)
1464
+ } else {
1465
+ return res.status(404).json({ success: false, error: '模板列表不存在' })
1466
+ }
1467
+ } else {
1468
+ return res.status(400).json({ success: false, error: '不支持的模板类型' })
1469
+ }
1470
+
1471
+ res.json({ success: true })
1472
+ } catch (error) {
1473
+ res.status(500).json({ success: false, error: error.message })
1474
+ }
1475
+ })
1476
+
1477
+ // 保存自定义命令
1478
+ app.post('/api/config/save-custom-command', express.json(), async (req, res) => {
1479
+ try {
1480
+ const { command } = req.body
1481
+
1482
+ if (!command || !command.name || !command.command) {
1483
+ return res.status(400).json({ success: false, error: '缺少必要参数' })
1484
+ }
1485
+
1486
+ const config = await configManager.loadConfig()
1487
+
1488
+ // 确保自定义命令数组存在
1489
+ if (!Array.isArray(config.customCommands)) {
1490
+ config.customCommands = []
1491
+ }
1492
+
1493
+ // 生成唯一ID
1494
+ const id = `cmd_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
1495
+ const newCommand = {
1496
+ id,
1497
+ name: command.name,
1498
+ description: command.description || '',
1499
+ directory: command.directory || '',
1500
+ command: command.command
1501
+ }
1502
+
1503
+ config.customCommands.push(newCommand)
1504
+ await configManager.saveConfig(config)
1505
+
1506
+ res.json({ success: true, command: newCommand })
1507
+ } catch (error) {
1508
+ res.status(500).json({ success: false, error: error.message })
1509
+ }
1510
+ })
1511
+
1512
+ // 删除自定义命令
1513
+ app.post('/api/config/delete-custom-command', express.json(), async (req, res) => {
1514
+ try {
1515
+ const { id } = req.body
1516
+
1517
+ if (!id) {
1518
+ return res.status(400).json({ success: false, error: '缺少命令ID参数' })
1519
+ }
1520
+
1521
+ const config = await configManager.loadConfig()
1522
+
1523
+ if (Array.isArray(config.customCommands)) {
1524
+ const index = config.customCommands.findIndex(cmd => cmd.id === id)
1525
+ if (index !== -1) {
1526
+ config.customCommands.splice(index, 1)
1527
+ await configManager.saveConfig(config)
1528
+ }
1529
+ }
1530
+
1531
+ res.json({ success: true })
1532
+ } catch (error) {
1533
+ res.status(500).json({ success: false, error: error.message })
1534
+ }
1535
+ })
1536
+
1537
+ // 更新自定义命令
1538
+ app.post('/api/config/update-custom-command', express.json(), async (req, res) => {
1539
+ try {
1540
+ const { id, command } = req.body
1541
+
1542
+ if (!id || !command || !command.name || !command.command) {
1543
+ return res.status(400).json({ success: false, error: '缺少必要参数' })
1544
+ }
1545
+
1546
+ const config = await configManager.loadConfig()
1547
+
1548
+ if (Array.isArray(config.customCommands)) {
1549
+ const index = config.customCommands.findIndex(cmd => cmd.id === id)
1550
+ if (index !== -1) {
1551
+ config.customCommands[index] = {
1552
+ id,
1553
+ name: command.name,
1554
+ description: command.description || '',
1555
+ directory: command.directory || '',
1556
+ command: command.command
1557
+ }
1558
+ await configManager.saveConfig(config)
1559
+ } else {
1560
+ return res.status(404).json({ success: false, error: '未找到指定命令' })
1561
+ }
1562
+ } else {
1563
+ return res.status(404).json({ success: false, error: '命令列表不存在' })
1564
+ }
1565
+
1566
+ res.json({ success: true })
1567
+ } catch (error) {
1568
+ res.status(500).json({ success: false, error: error.message })
1569
+ }
1570
+ })
1571
+
1400
1572
  // 提交更改
1401
1573
  app.post('/api/commit', express.json(), async (req, res) => {
1402
1574
  try {
@@ -3225,7 +3397,8 @@ async function startUIServer(noOpen = false, savePort = false) {
3225
3397
  path: dir,
3226
3398
  relativePath: relativePath || '.',
3227
3399
  name: packageData.name || path.basename(dir),
3228
- scripts: packageData.scripts
3400
+ scripts: packageData.scripts,
3401
+ version: packageData.version || '0.0.0'
3229
3402
  });
3230
3403
  return true;
3231
3404
  }
@@ -3411,6 +3584,340 @@ async function startUIServer(noOpen = false, savePort = false) {
3411
3584
  });
3412
3585
  }
3413
3586
  });
3587
+
3588
+ // API: 更新npm版本号
3589
+ app.post('/api/update-npm-version', async (req, res) => {
3590
+ try {
3591
+ const { packagePath, versionType } = req.body;
3592
+
3593
+ if (!packagePath || !versionType) {
3594
+ return res.status(400).json({
3595
+ success: false,
3596
+ error: '缺少必要参数: packagePath, versionType'
3597
+ });
3598
+ }
3599
+
3600
+ // 确保路径指向package.json文件
3601
+ let packageJsonPath = path.resolve(packagePath);
3602
+ if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
3603
+ packageJsonPath = path.join(packageJsonPath, 'package.json');
3604
+ }
3605
+
3606
+ // 检查文件是否存在
3607
+ if (!fsSync.existsSync(packageJsonPath)) {
3608
+ return res.status(404).json({
3609
+ success: false,
3610
+ error: '找不到package.json文件'
3611
+ });
3612
+ }
3613
+
3614
+ // 读取package.json
3615
+ const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
3616
+
3617
+ if (!packageJson.version) {
3618
+ return res.status(400).json({
3619
+ success: false,
3620
+ error: 'package.json中没有version字段'
3621
+ });
3622
+ }
3623
+
3624
+ const oldVersion = packageJson.version;
3625
+ const versionParts = oldVersion.split('.').map(Number);
3626
+
3627
+ // 根据类型增加版本号
3628
+ switch (versionType) {
3629
+ case 'major':
3630
+ versionParts[0]++;
3631
+ versionParts[1] = 0;
3632
+ versionParts[2] = 0;
3633
+ break;
3634
+ case 'minor':
3635
+ versionParts[1]++;
3636
+ versionParts[2] = 0;
3637
+ break;
3638
+ case 'patch':
3639
+ versionParts[2]++;
3640
+ break;
3641
+ default:
3642
+ return res.status(400).json({
3643
+ success: false,
3644
+ error: '无效的版本类型,必须是 major, minor 或 patch'
3645
+ });
3646
+ }
3647
+
3648
+ const newVersion = versionParts.join('.');
3649
+ packageJson.version = newVersion;
3650
+
3651
+ // 写回文件
3652
+ fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
3653
+
3654
+ console.log(`已更新npm版本号: ${oldVersion} → ${newVersion} (${packagePath})`);
3655
+
3656
+ res.json({
3657
+ success: true,
3658
+ oldVersion,
3659
+ newVersion
3660
+ });
3661
+ } catch (error) {
3662
+ console.error('更新版本号失败:', error);
3663
+ res.status(500).json({
3664
+ success: false,
3665
+ error: `更新版本号失败: ${error.message}`
3666
+ });
3667
+ }
3668
+ });
3669
+
3670
+ // API: 添加npm脚本
3671
+ app.post('/api/add-npm-script', async (req, res) => {
3672
+ try {
3673
+ const { packagePath, scriptName, scriptCommand } = req.body;
3674
+
3675
+ if (!packagePath || !scriptName || !scriptCommand) {
3676
+ return res.status(400).json({
3677
+ success: false,
3678
+ error: '缺少必要参数: packagePath, scriptName, scriptCommand'
3679
+ });
3680
+ }
3681
+
3682
+ // 确保路径指向package.json文件
3683
+ let packageJsonPath = path.resolve(packagePath);
3684
+ if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
3685
+ packageJsonPath = path.join(packageJsonPath, 'package.json');
3686
+ }
3687
+
3688
+ // 检查文件是否存在
3689
+ if (!fsSync.existsSync(packageJsonPath)) {
3690
+ return res.status(404).json({
3691
+ success: false,
3692
+ error: '找不到package.json文件'
3693
+ });
3694
+ }
3695
+
3696
+ // 读取package.json
3697
+ const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
3698
+
3699
+ // 确保scripts对象存在
3700
+ if (!packageJson.scripts) {
3701
+ packageJson.scripts = {};
3702
+ }
3703
+
3704
+ // 检查脚本是否已存在
3705
+ if (packageJson.scripts[scriptName]) {
3706
+ return res.status(400).json({
3707
+ success: false,
3708
+ error: `脚本 "${scriptName}" 已存在`
3709
+ });
3710
+ }
3711
+
3712
+ // 添加脚本
3713
+ packageJson.scripts[scriptName] = scriptCommand;
3714
+
3715
+ // 写回文件(保持格式化)
3716
+ fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
3717
+
3718
+ console.log(`已添加npm脚本: ${scriptName} = ${scriptCommand} (${packagePath})`);
3719
+
3720
+ res.json({
3721
+ success: true,
3722
+ scriptName,
3723
+ scriptCommand
3724
+ });
3725
+ } catch (error) {
3726
+ console.error('添加npm脚本失败:', error);
3727
+ res.status(500).json({
3728
+ success: false,
3729
+ error: `添加npm脚本失败: ${error.message}`
3730
+ });
3731
+ }
3732
+ });
3733
+
3734
+ // API: 更新npm脚本
3735
+ app.post('/api/update-npm-script', async (req, res) => {
3736
+ try {
3737
+ const { packagePath, scriptName, scriptCommand, oldScriptName } = req.body;
3738
+
3739
+ if (!packagePath || !scriptName || !scriptCommand) {
3740
+ return res.status(400).json({
3741
+ success: false,
3742
+ error: '缺少必要参数: packagePath, scriptName, scriptCommand'
3743
+ });
3744
+ }
3745
+
3746
+ // 确保路径指向package.json文件
3747
+ let packageJsonPath = path.resolve(packagePath);
3748
+ if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
3749
+ packageJsonPath = path.join(packageJsonPath, 'package.json');
3750
+ }
3751
+
3752
+ // 检查文件是否存在
3753
+ if (!fsSync.existsSync(packageJsonPath)) {
3754
+ return res.status(404).json({
3755
+ success: false,
3756
+ error: '找不到package.json文件'
3757
+ });
3758
+ }
3759
+
3760
+ // 读取package.json
3761
+ const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
3762
+
3763
+ // 确保scripts对象存在
3764
+ if (!packageJson.scripts) {
3765
+ packageJson.scripts = {};
3766
+ }
3767
+
3768
+ // 如果改了脚本名称,删除旧的
3769
+ if (oldScriptName && oldScriptName !== scriptName) {
3770
+ delete packageJson.scripts[oldScriptName];
3771
+ }
3772
+
3773
+ // 更新脚本
3774
+ packageJson.scripts[scriptName] = scriptCommand;
3775
+
3776
+ // 写回文件
3777
+ fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
3778
+
3779
+ console.log(`已更新npm脚本: ${scriptName} = ${scriptCommand} (${packagePath})`);
3780
+
3781
+ res.json({
3782
+ success: true,
3783
+ scriptName,
3784
+ scriptCommand
3785
+ });
3786
+ } catch (error) {
3787
+ console.error('更新npm脚本失败:', error);
3788
+ res.status(500).json({
3789
+ success: false,
3790
+ error: `更新npm脚本失败: ${error.message}`
3791
+ });
3792
+ }
3793
+ });
3794
+
3795
+ // API: 删除npm脚本
3796
+ app.post('/api/delete-npm-script', async (req, res) => {
3797
+ try {
3798
+ const { packagePath, scriptName } = req.body;
3799
+
3800
+ if (!packagePath || !scriptName) {
3801
+ return res.status(400).json({
3802
+ success: false,
3803
+ error: '缺少必要参数: packagePath, scriptName'
3804
+ });
3805
+ }
3806
+
3807
+ // 确保路径指向package.json文件
3808
+ let packageJsonPath = path.resolve(packagePath);
3809
+ if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
3810
+ packageJsonPath = path.join(packageJsonPath, 'package.json');
3811
+ }
3812
+
3813
+ // 检查文件是否存在
3814
+ if (!fsSync.existsSync(packageJsonPath)) {
3815
+ return res.status(404).json({
3816
+ success: false,
3817
+ error: '找不到package.json文件'
3818
+ });
3819
+ }
3820
+
3821
+ // 读取package.json
3822
+ const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
3823
+
3824
+ // 检查scripts对象和脚本是否存在
3825
+ if (!packageJson.scripts || !packageJson.scripts[scriptName]) {
3826
+ return res.status(404).json({
3827
+ success: false,
3828
+ error: `脚本 "${scriptName}" 不存在`
3829
+ });
3830
+ }
3831
+
3832
+ // 删除脚本
3833
+ delete packageJson.scripts[scriptName];
3834
+
3835
+ // 写回文件
3836
+ fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
3837
+
3838
+ console.log(`已删除npm脚本: ${scriptName} (${packagePath})`);
3839
+
3840
+ res.json({
3841
+ success: true,
3842
+ scriptName
3843
+ });
3844
+ } catch (error) {
3845
+ console.error('删除npm脚本失败:', error);
3846
+ res.status(500).json({
3847
+ success: false,
3848
+ error: `删除npm脚本失败: ${error.message}`
3849
+ });
3850
+ }
3851
+ });
3852
+
3853
+ // API: 置顶npm脚本
3854
+ app.post('/api/pin-npm-script', async (req, res) => {
3855
+ try {
3856
+ const { packagePath, scriptName } = req.body;
3857
+
3858
+ if (!packagePath || !scriptName) {
3859
+ return res.status(400).json({
3860
+ success: false,
3861
+ error: '缺少必要参数: packagePath, scriptName'
3862
+ });
3863
+ }
3864
+
3865
+ // 确保路径指向package.json文件
3866
+ let packageJsonPath = path.resolve(packagePath);
3867
+ if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
3868
+ packageJsonPath = path.join(packageJsonPath, 'package.json');
3869
+ }
3870
+
3871
+ // 检查文件是否存在
3872
+ if (!fsSync.existsSync(packageJsonPath)) {
3873
+ return res.status(404).json({
3874
+ success: false,
3875
+ error: '找不到package.json文件'
3876
+ });
3877
+ }
3878
+
3879
+ // 读取package.json
3880
+ const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
3881
+
3882
+ // 检查scripts对象和脚本是否存在
3883
+ if (!packageJson.scripts || !packageJson.scripts[scriptName]) {
3884
+ return res.status(404).json({
3885
+ success: false,
3886
+ error: `脚本 "${scriptName}" 不存在`
3887
+ });
3888
+ }
3889
+
3890
+ // 保存要置顶的脚本内容
3891
+ const scriptCommand = packageJson.scripts[scriptName];
3892
+
3893
+ // 删除该脚本
3894
+ delete packageJson.scripts[scriptName];
3895
+
3896
+ // 创建新的scripts对象,将置顶脚本放在最前面
3897
+ const newScripts = {
3898
+ [scriptName]: scriptCommand,
3899
+ ...packageJson.scripts
3900
+ };
3901
+
3902
+ packageJson.scripts = newScripts;
3903
+
3904
+ // 写回文件
3905
+ fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
3906
+
3907
+ console.log(`已置顶npm脚本: ${scriptName} (${packagePath})`);
3908
+
3909
+ res.json({
3910
+ success: true,
3911
+ scriptName
3912
+ });
3913
+ } catch (error) {
3914
+ console.error('置顶npm脚本失败:', error);
3915
+ res.status(500).json({
3916
+ success: false,
3917
+ error: `置顶npm脚本失败: ${error.message}`
3918
+ });
3919
+ }
3920
+ });
3414
3921
 
3415
3922
  // Socket.io 实时更新
3416
3923
  io.on('connection', (socket) => {
@@ -3427,34 +3934,6 @@ async function startUIServer(noOpen = false, savePort = false) {
3427
3934
  const history = getCommandHistory();
3428
3935
  socket.emit('initial_command_history', { history });
3429
3936
 
3430
- // 发送项目信息给客户端
3431
- socket.emit('project_info', {
3432
- projectPath: currentProjectPath,
3433
- projectRoomId: projectRoomId
3434
- });
3435
-
3436
- // 客户端可以请求开始/停止监控
3437
- socket.on('start_monitoring', () => {
3438
- if (!watcher) {
3439
- initFileSystemWatcher().catch(err => console.error('[文件监控] 初始化失败:', err));
3440
- socket.emit('monitoring_status', { active: true });
3441
- }
3442
- });
3443
-
3444
- // 处理客户端加入新房间的请求
3445
- socket.on('join_room', (roomId) => {
3446
- socket.join(roomId);
3447
- console.log(`客户端 ${socket.id} 已加入房间: ${roomId}`);
3448
- });
3449
-
3450
- socket.on('stop_monitoring', () => {
3451
- if (watcher) {
3452
- watcher.close().catch(err => console.error('关闭监控器失败:', err));
3453
- watcher = null;
3454
- socket.emit('monitoring_status', { active: false });
3455
- }
3456
- });
3457
-
3458
3937
  // 请求完整命令历史
3459
3938
  socket.on('request_full_history', () => {
3460
3939
  const fullHistory = getCommandHistory();
@@ -3474,158 +3953,6 @@ async function startUIServer(noOpen = false, savePort = false) {
3474
3953
  });
3475
3954
  });
3476
3955
 
3477
- // 读取并解析.gitignore文件
3478
- async function parseGitignore(projectPath) {
3479
- const gitignorePath = path.join(projectPath, '.gitignore');
3480
- const ignorePatterns = [
3481
- /(^|[\/\\])\../, // 始终忽略.开头的文件(除了.gitignore本身)
3482
- '**/.git/**', // 始终忽略.git目录
3483
-
3484
- // 额外排除常见的编译产物和大文件,减少监控开销
3485
- '**/*.umd.cjs', // UMD打包文件
3486
- '**/*.min.js', // 压缩JS文件
3487
- '**/*.bundle.js', // Webpack打包文件
3488
- '**/*.dist.js', // 构建产物
3489
- '**/*.prod.js', // 生产环境文件
3490
- '**/lib/**', // 通常是编译产物
3491
- '**/es/**', // ES模块编译产物
3492
- '**/esm/**', // ES模块编译产物
3493
- '**/*.map', // Source map文件
3494
- '**/*.chunk.js', // 代码分割chunk
3495
- ];
3496
-
3497
- try {
3498
- const gitignoreContent = await fs.readFile(gitignorePath, 'utf8');
3499
- const lines = gitignoreContent.split('\n');
3500
- let validRules = 0;
3501
-
3502
- for (let line of lines) {
3503
- line = line.trim();
3504
-
3505
- // 跳过空行和注释
3506
- if (!line || line.startsWith('#')) continue;
3507
-
3508
- // 移除行尾的空格
3509
- line = line.replace(/\s+$/, '');
3510
-
3511
- // 跳过否定规则(chokidar不支持否定规则,这些规则会被忽略)
3512
- if (line.startsWith('!')) {
3513
- continue;
3514
- }
3515
-
3516
- // 将gitignore规则转换为glob模式
3517
- let pattern;
3518
-
3519
- // 如果以/开头,表示从根目录开始匹配
3520
- if (line.startsWith('/')) {
3521
- pattern = line.substring(1);
3522
- // 如果是目录,添加/**后缀
3523
- if (!pattern.includes('*') && !pattern.includes('.')) {
3524
- ignorePatterns.push(pattern);
3525
- ignorePatterns.push(pattern + '/**');
3526
- } else {
3527
- ignorePatterns.push(pattern);
3528
- }
3529
- } else if (line.endsWith('/')) {
3530
- // 明确的目录规则
3531
- const dirName = line.slice(0, -1);
3532
- ignorePatterns.push('**/' + dirName);
3533
- ignorePatterns.push('**/' + dirName + '/**');
3534
- } else {
3535
- // 文件或目录规则
3536
- // 如果包含*通配符,直接使用
3537
- if (line.includes('*')) {
3538
- ignorePatterns.push('**/' + line);
3539
- } else {
3540
- // 既匹配文件也匹配目录
3541
- ignorePatterns.push('**/' + line);
3542
- ignorePatterns.push('**/' + line + '/**');
3543
- }
3544
- }
3545
-
3546
- validRules++;
3547
- }
3548
-
3549
- console.log(`[文件监控] 从.gitignore读取了 ${validRules} 条有效的忽略规则`);
3550
- } catch (error) {
3551
- // .gitignore不存在或读取失败,使用默认规则
3552
- console.log('[文件监控] 未找到.gitignore,使用默认忽略规则');
3553
- ignorePatterns.push(
3554
- '**/node_modules/**',
3555
- '**/dist/**',
3556
- '**/build/**',
3557
- '**/coverage/**',
3558
- '**/.nuxt/**',
3559
- '**/.next/**',
3560
- '**/out/**',
3561
- '**/*.log'
3562
- );
3563
- }
3564
-
3565
- return ignorePatterns;
3566
- }
3567
-
3568
- // 初始化文件系统监控
3569
- async function initFileSystemWatcher() {
3570
- // 停止已有的监控器
3571
- if (watcher) {
3572
- watcher.close().catch(err => console.error('关闭旧监控器失败:', err));
3573
- }
3574
-
3575
- try {
3576
- // 获取当前工作目录
3577
- const currentDir = process.cwd();
3578
-
3579
- console.log(`初始化文件系统监控器,路径: ${currentDir}`);
3580
-
3581
- // 检查是否是Git仓库
3582
- if (!isGitRepo) {
3583
- console.log('当前目录不是Git仓库,不启动监控');
3584
- return;
3585
- }
3586
-
3587
- const watcherStartTime = Date.now();
3588
- console.log('[文件监控] 开始初始化监控器...');
3589
-
3590
- // 从.gitignore读取忽略规则
3591
- const ignorePatterns = await parseGitignore(currentDir);
3592
-
3593
- // 使用chokidar监控文件变动
3594
- watcher = chokidar.watch(currentDir, {
3595
- ignored: ignorePatterns,
3596
- persistent: true,
3597
- ignoreInitial: true, // 忽略初始扫描时的文件
3598
- depth: 10, // 限制扫描深度,避免过深的目录结构
3599
- awaitWriteFinish: {
3600
- stabilityThreshold: 300, // 等待文件写入完成的时间
3601
- pollInterval: 100 // 轮询间隔
3602
- }
3603
- });
3604
-
3605
- // 合并所有变动事件到一个处理程序
3606
- const events = ['add', 'change', 'unlink'];
3607
- events.forEach(event => {
3608
- watcher.on(event, path => {
3609
- console.log(`检测到文件变动 [${event}]: ${path}`);
3610
- debouncedNotifyChanges();
3611
- });
3612
- });
3613
-
3614
- watcher.on('ready', () => {
3615
- const initTime = Date.now() - watcherStartTime;
3616
- console.log(`[文件监控] 监控器初始化完成,耗时 ${initTime}ms`);
3617
- });
3618
-
3619
- watcher.on('error', error => {
3620
- console.error('[文件监控] 监控错误:', error);
3621
- });
3622
-
3623
- console.log('[文件监控] 监控器已启动(异步初始化中...)');
3624
- } catch (error) {
3625
- console.error('启动文件监控失败:', error);
3626
- }
3627
- }
3628
-
3629
3956
  // 获取并广播Git状态 (优化版本 - 只获取porcelain格式)
3630
3957
  async function getAndBroadcastStatus() {
3631
3958
  try {
@@ -3657,17 +3984,6 @@ async function startUIServer(noOpen = false, savePort = false) {
3657
3984
  }
3658
3985
  }
3659
3986
 
3660
- // 防抖处理函数
3661
- function debouncedNotifyChanges() {
3662
- if (debounceTimer) {
3663
- clearTimeout(debounceTimer);
3664
- }
3665
-
3666
- debounceTimer = setTimeout(() => {
3667
- getAndBroadcastStatus();
3668
- }, DEBOUNCE_DELAY);
3669
- }
3670
-
3671
3987
  // 检查当前目录是否是Git仓库
3672
3988
  let isGitRepo = false;
3673
3989
  try {