zen-gitsync 2.6.4 → 2.6.7

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,16 @@ 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
+ import iconv from 'iconv-lite';
14
15
  // import { exec } from 'child_process';
15
16
 
16
17
  const __filename = fileURLToPath(import.meta.url);
17
18
  const __dirname = path.dirname(__filename);
18
19
  const configManager = config; // 确保 configManager 可用
19
-
20
- // 文件系统变动监控器
21
- let watcher = null;
22
- // 防抖计时器
23
- let debounceTimer = null;
24
- // 防抖延迟时间 (毫秒)
25
- const DEBOUNCE_DELAY = 1000;
26
-
27
20
  // 分支状态缓存
28
21
  let branchStatusCache = {
29
22
  currentBranch: null,
@@ -156,6 +149,134 @@ async function startUIServer(noOpen = false, savePort = false) {
156
149
  }
157
150
  });
158
151
 
152
+ // 流式执行命令接口(支持实时输出)
153
+ app.post('/api/exec-stream', async (req, res) => {
154
+ try {
155
+ const { command, directory } = req.body || {};
156
+ if (!command || typeof command !== 'string' || !command.trim()) {
157
+ return res.status(400).json({ success: false, error: 'command 不能为空' });
158
+ }
159
+
160
+ // 确定执行目录
161
+ const execDirectory = directory && directory.trim()
162
+ ? (path.isAbsolute(directory) ? directory : path.join(currentProjectPath, directory))
163
+ : currentProjectPath;
164
+
165
+ console.log(`流式执行命令: ${command}`);
166
+ console.log(`执行目录: ${execDirectory}`);
167
+
168
+ // 设置响应头为流式传输
169
+ res.setHeader('Content-Type', 'text/event-stream');
170
+ res.setHeader('Cache-Control', 'no-cache');
171
+ res.setHeader('Connection', 'keep-alive');
172
+ res.setHeader('X-Accel-Buffering', 'no'); // 禁用nginx缓冲
173
+
174
+ // 使用 spawn 执行命令,支持实时输出
175
+ console.log(`[流式输出] 准备执行命令: ${command}`);
176
+ console.log(`[流式输出] 当前平台: ${process.platform}`);
177
+ console.log(`[流式输出] 工作目录: ${execDirectory}`);
178
+
179
+ // 使用 shell: true 来支持 Windows 内置命令(如 dir、cd 等)
180
+ const childProcess = spawn(command.trim(), [], {
181
+ cwd: execDirectory,
182
+ shell: true, // 通过 shell 执行,支持 Windows 内置命令
183
+ env: {
184
+ ...process.env,
185
+ GIT_CONFIG_PARAMETERS: "'core.quotepath=false'"
186
+ }
187
+ });
188
+
189
+ console.log(`[流式输出] childProcess.stdout 是否存在:`, !!childProcess.stdout);
190
+ console.log(`[流式输出] childProcess.stderr 是否存在:`, !!childProcess.stderr);
191
+
192
+ let outputReceived = false;
193
+
194
+ // 发送数据到客户端的辅助函数
195
+ const sendData = (type, data) => {
196
+ const message = `data: ${JSON.stringify({ type, data })}\n\n`;
197
+ console.log(`[流式输出] 发送数据 - 类型: ${type}, 长度: ${data?.length || 0}`);
198
+ res.write(message);
199
+ };
200
+
201
+ // 在 Windows 上,CMD 默认使用 GBK 编码
202
+ // 不设置 encoding,直接处理 Buffer,然后用 iconv-lite 转换
203
+ const isWindows = process.platform === 'win32';
204
+ console.log(`[流式输出] 平台: ${process.platform}, 使用编码转换: ${isWindows}`);
205
+
206
+ // 监听标准输出
207
+ childProcess.stdout?.on('data', (data) => {
208
+ // data 是 Buffer 对象
209
+ let output;
210
+ if (isWindows) {
211
+ // Windows 系统,从 GBK 转换为 UTF-8
212
+ output = iconv.decode(data, 'gbk');
213
+ console.log(`[流式输出] 收到stdout(GBK转UTF8):`, output.substring(0, 100));
214
+ } else {
215
+ // Unix 系统,直接使用 UTF-8
216
+ output = data.toString('utf8');
217
+ console.log(`[流式输出] 收到stdout(UTF8):`, output.substring(0, 100));
218
+ }
219
+ outputReceived = true;
220
+ sendData('stdout', output);
221
+ });
222
+
223
+ // 监听标准错误输出
224
+ childProcess.stderr?.on('data', (data) => {
225
+ // data 是 Buffer 对象
226
+ let output;
227
+ if (isWindows) {
228
+ // Windows 系统,从 GBK 转换为 UTF-8
229
+ output = iconv.decode(data, 'gbk');
230
+ console.log(`[流式输出] 收到stderr(GBK转UTF8):`, output.substring(0, 100));
231
+ } else {
232
+ // Unix 系统,直接使用 UTF-8
233
+ output = data.toString('utf8');
234
+ console.log(`[流式输出] 收到stderr(UTF8):`, output.substring(0, 100));
235
+ }
236
+ outputReceived = true;
237
+ // 不再自动标记为错误,只显示 stderr 输出
238
+ // Git 的警告信息会输出到 stderr 但退出码仍为 0
239
+ sendData('stderr', output);
240
+ });
241
+
242
+ // 监听进程退出(exit 在流关闭前触发)
243
+ childProcess.on('exit', (code, signal) => {
244
+ console.log(`[流式输出] 进程 exit 事件 - 代码: ${code}, 信号: ${signal}`);
245
+ });
246
+
247
+ // 监听进程关闭(close 在流关闭后触发)
248
+ childProcess.on('close', (code, signal) => {
249
+ console.log(`[流式输出] 进程 close 事件 - 代码: ${code}, 信号: ${signal}, 有输出: ${outputReceived}`);
250
+ // 只根据退出码判断成功与否,退出码为 0 表示成功
251
+ sendData('exit', { code, success: code === 0 });
252
+ res.end();
253
+ });
254
+
255
+ // 监听错误
256
+ childProcess.on('error', (error) => {
257
+ console.error(`[流式输出] 进程错误:`, error);
258
+ sendData('error', error.message);
259
+ res.end();
260
+ });
261
+
262
+ // 添加spawn事件监听
263
+ childProcess.on('spawn', () => {
264
+ console.log(`[流式输出] 进程已启动 - PID: ${childProcess.pid}`);
265
+ });
266
+
267
+ // 注意:不监听req.on('close'),参考git push的实现
268
+ // 进程会自然结束,close事件会触发res.end()
269
+ // 如果监听req.on('close')可能会导致进程被提前kill
270
+
271
+ } catch (error) {
272
+ console.error('流式执行命令失败:', error);
273
+ res.status(500).json({
274
+ success: false,
275
+ error: `流式执行命令失败: ${error.message}`
276
+ });
277
+ }
278
+ });
279
+
159
280
  // 在新终端中执行自定义命令
160
281
  app.post('/api/exec-in-terminal', async (req, res) => {
161
282
  try {
@@ -661,12 +782,6 @@ async function startUIServer(noOpen = false, savePort = false) {
661
782
  console.warn('初始化项目配置失败:', e?.message || e);
662
783
  }
663
784
 
664
- // 关闭旧的文件监控
665
- if (watcher) {
666
- watcher.close().catch(err => console.error('关闭旧监控器失败:', err));
667
- watcher = null;
668
- }
669
-
670
785
  // 通知所有旧房间的客户端项目已切换
671
786
  io.to(projectRoomId).emit('project_changed', {
672
787
  oldProjectPath: currentProjectPath,
@@ -694,11 +809,6 @@ async function startUIServer(noOpen = false, savePort = false) {
694
809
  projectRoomId = newProjectRoomId;
695
810
  isGitRepo = false;
696
811
 
697
- if (watcher) {
698
- watcher.close().catch(err => console.error('关闭监控器失败:', err));
699
- watcher = null;
700
- }
701
-
702
812
  // 通知所有旧房间的客户端项目已切换
703
813
  io.to(projectRoomId).emit('project_changed', {
704
814
  oldProjectPath: currentProjectPath,
@@ -1446,6 +1556,53 @@ async function startUIServer(noOpen = false, savePort = false) {
1446
1556
  }
1447
1557
  })
1448
1558
 
1559
+ // 置顶模板
1560
+ app.post('/api/config/pin-template', express.json(), async (req, res) => {
1561
+ try {
1562
+ const { template, type } = req.body
1563
+
1564
+ if (!template || !type) {
1565
+ return res.status(400).json({ success: false, error: '缺少必要参数' })
1566
+ }
1567
+
1568
+ const config = await configManager.loadConfig()
1569
+
1570
+ if (type === 'description') {
1571
+ if (config.descriptionTemplates) {
1572
+ // 删除原位置的模板
1573
+ config.descriptionTemplates = config.descriptionTemplates.filter(t => t !== template)
1574
+ // 添加到第一位
1575
+ config.descriptionTemplates.unshift(template)
1576
+ await configManager.saveConfig(config)
1577
+ } else {
1578
+ return res.status(404).json({ success: false, error: '模板列表不存在' })
1579
+ }
1580
+ } else if (type === 'scope') {
1581
+ if (config.scopeTemplates) {
1582
+ config.scopeTemplates = config.scopeTemplates.filter(t => t !== template)
1583
+ config.scopeTemplates.unshift(template)
1584
+ await configManager.saveConfig(config)
1585
+ } else {
1586
+ return res.status(404).json({ success: false, error: '模板列表不存在' })
1587
+ }
1588
+ } else if (type === 'message') {
1589
+ if (config.messageTemplates) {
1590
+ config.messageTemplates = config.messageTemplates.filter(t => t !== template)
1591
+ config.messageTemplates.unshift(template)
1592
+ await configManager.saveConfig(config)
1593
+ } else {
1594
+ return res.status(404).json({ success: false, error: '模板列表不存在' })
1595
+ }
1596
+ } else {
1597
+ return res.status(400).json({ success: false, error: '不支持的模板类型' })
1598
+ }
1599
+
1600
+ res.json({ success: true })
1601
+ } catch (error) {
1602
+ res.status(500).json({ success: false, error: error.message })
1603
+ }
1604
+ })
1605
+
1449
1606
  // 保存自定义命令
1450
1607
  app.post('/api/config/save-custom-command', express.json(), async (req, res) => {
1451
1608
  try {
@@ -1541,6 +1698,176 @@ async function startUIServer(noOpen = false, savePort = false) {
1541
1698
  }
1542
1699
  })
1543
1700
 
1701
+ // 保存指令编排
1702
+ app.post('/api/config/save-orchestration', express.json(), async (req, res) => {
1703
+ try {
1704
+ const { orchestration } = req.body
1705
+
1706
+ if (!orchestration || !orchestration.name || !Array.isArray(orchestration.steps)) {
1707
+ return res.status(400).json({ success: false, error: '缺少必要参数' })
1708
+ }
1709
+
1710
+ const config = await configManager.loadConfig()
1711
+
1712
+ // 确保编排数组存在
1713
+ if (!Array.isArray(config.orchestrations)) {
1714
+ config.orchestrations = []
1715
+ }
1716
+
1717
+ // 生成唯一ID
1718
+ const id = `orch_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
1719
+ const newOrchestration = {
1720
+ id,
1721
+ name: orchestration.name,
1722
+ description: orchestration.description || '',
1723
+ steps: orchestration.steps
1724
+ }
1725
+
1726
+ config.orchestrations.push(newOrchestration)
1727
+ await configManager.saveConfig(config)
1728
+
1729
+ res.json({ success: true, orchestration: newOrchestration })
1730
+ } catch (error) {
1731
+ res.status(500).json({ success: false, error: error.message })
1732
+ }
1733
+ })
1734
+
1735
+ // 删除指令编排
1736
+ app.post('/api/config/delete-orchestration', express.json(), async (req, res) => {
1737
+ try {
1738
+ const { id } = req.body
1739
+
1740
+ if (!id) {
1741
+ return res.status(400).json({ success: false, error: '缺少编排ID参数' })
1742
+ }
1743
+
1744
+ const config = await configManager.loadConfig()
1745
+
1746
+ if (Array.isArray(config.orchestrations)) {
1747
+ const index = config.orchestrations.findIndex(orch => orch.id === id)
1748
+ if (index !== -1) {
1749
+ config.orchestrations.splice(index, 1)
1750
+ await configManager.saveConfig(config)
1751
+ }
1752
+ }
1753
+
1754
+ res.json({ success: true })
1755
+ } catch (error) {
1756
+ res.status(500).json({ success: false, error: error.message })
1757
+ }
1758
+ })
1759
+
1760
+ // 更新指令编排
1761
+ app.post('/api/config/update-orchestration', express.json(), async (req, res) => {
1762
+ try {
1763
+ const { id, orchestration } = req.body
1764
+
1765
+ if (!id || !orchestration || !orchestration.name || !Array.isArray(orchestration.steps)) {
1766
+ return res.status(400).json({ success: false, error: '缺少必要参数' })
1767
+ }
1768
+
1769
+ const config = await configManager.loadConfig()
1770
+
1771
+ if (Array.isArray(config.orchestrations)) {
1772
+ const index = config.orchestrations.findIndex(orch => orch.id === id)
1773
+ if (index !== -1) {
1774
+ config.orchestrations[index] = {
1775
+ id,
1776
+ name: orchestration.name,
1777
+ description: orchestration.description || '',
1778
+ steps: orchestration.steps
1779
+ }
1780
+ await configManager.saveConfig(config)
1781
+ } else {
1782
+ return res.status(404).json({ success: false, error: '未找到指定编排' })
1783
+ }
1784
+ } else {
1785
+ return res.status(404).json({ success: false, error: '编排列表不存在' })
1786
+ }
1787
+
1788
+ res.json({ success: true })
1789
+ } catch (error) {
1790
+ res.status(500).json({ success: false, error: error.message })
1791
+ }
1792
+ })
1793
+
1794
+ // 版本号递增
1795
+ app.post('/api/version-bump', express.json(), async (req, res) => {
1796
+ try {
1797
+ const { bumpType, packageJsonPath } = req.body
1798
+
1799
+ // 确定 package.json 的路径
1800
+ let pkgPath
1801
+ if (packageJsonPath && packageJsonPath.trim()) {
1802
+ pkgPath = path.isAbsolute(packageJsonPath)
1803
+ ? packageJsonPath
1804
+ : path.join(currentProjectPath, packageJsonPath)
1805
+ } else {
1806
+ pkgPath = path.join(currentProjectPath, 'package.json')
1807
+ }
1808
+
1809
+ // 检查文件是否存在(使用 Node.js 原生方法)
1810
+ try {
1811
+ await fs.access(pkgPath)
1812
+ } catch (err) {
1813
+ return res.status(404).json({
1814
+ success: false,
1815
+ error: `未找到 package.json 文件: ${pkgPath}`
1816
+ })
1817
+ }
1818
+
1819
+ // 读取 package.json
1820
+ const pkgContent = await fs.readFile(pkgPath, 'utf8')
1821
+ const pkg = JSON.parse(pkgContent)
1822
+
1823
+ if (!pkg.version) {
1824
+ return res.status(400).json({
1825
+ success: false,
1826
+ error: 'package.json 中未找到 version 字段'
1827
+ })
1828
+ }
1829
+
1830
+ const oldVersion = pkg.version
1831
+
1832
+ // 解析版本号
1833
+ const versionParts = oldVersion.split('.').map(Number)
1834
+ if (versionParts.length !== 3 || versionParts.some(isNaN)) {
1835
+ return res.status(400).json({
1836
+ success: false,
1837
+ error: `无效的版本号格式: ${oldVersion},应为 x.y.z 格式`
1838
+ })
1839
+ }
1840
+
1841
+ // 根据类型递增版本号
1842
+ let [major, minor, patch] = versionParts
1843
+ if (bumpType === 'major') {
1844
+ major += 1
1845
+ minor = 0
1846
+ patch = 0
1847
+ } else if (bumpType === 'minor') {
1848
+ minor += 1
1849
+ patch = 0
1850
+ } else { // patch
1851
+ patch += 1
1852
+ }
1853
+
1854
+ const newVersion = `${major}.${minor}.${patch}`
1855
+ pkg.version = newVersion
1856
+
1857
+ // 写回 package.json(保持格式化)
1858
+ await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8')
1859
+
1860
+ res.json({
1861
+ success: true,
1862
+ oldVersion,
1863
+ newVersion,
1864
+ filePath: pkgPath
1865
+ })
1866
+ } catch (error) {
1867
+ res.status(500).json({ success: false, error: error.message })
1868
+ }
1869
+ })
1870
+
1544
1871
  // 提交更改
1545
1872
  app.post('/api/commit', express.json(), async (req, res) => {
1546
1873
  try {
@@ -3369,7 +3696,8 @@ async function startUIServer(noOpen = false, savePort = false) {
3369
3696
  path: dir,
3370
3697
  relativePath: relativePath || '.',
3371
3698
  name: packageData.name || path.basename(dir),
3372
- scripts: packageData.scripts
3699
+ scripts: packageData.scripts,
3700
+ version: packageData.version || '0.0.0'
3373
3701
  });
3374
3702
  return true;
3375
3703
  }
@@ -3555,6 +3883,340 @@ async function startUIServer(noOpen = false, savePort = false) {
3555
3883
  });
3556
3884
  }
3557
3885
  });
3886
+
3887
+ // API: 更新npm版本号
3888
+ app.post('/api/update-npm-version', async (req, res) => {
3889
+ try {
3890
+ const { packagePath, versionType } = req.body;
3891
+
3892
+ if (!packagePath || !versionType) {
3893
+ return res.status(400).json({
3894
+ success: false,
3895
+ error: '缺少必要参数: packagePath, versionType'
3896
+ });
3897
+ }
3898
+
3899
+ // 确保路径指向package.json文件
3900
+ let packageJsonPath = path.resolve(packagePath);
3901
+ if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
3902
+ packageJsonPath = path.join(packageJsonPath, 'package.json');
3903
+ }
3904
+
3905
+ // 检查文件是否存在
3906
+ if (!fsSync.existsSync(packageJsonPath)) {
3907
+ return res.status(404).json({
3908
+ success: false,
3909
+ error: '找不到package.json文件'
3910
+ });
3911
+ }
3912
+
3913
+ // 读取package.json
3914
+ const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
3915
+
3916
+ if (!packageJson.version) {
3917
+ return res.status(400).json({
3918
+ success: false,
3919
+ error: 'package.json中没有version字段'
3920
+ });
3921
+ }
3922
+
3923
+ const oldVersion = packageJson.version;
3924
+ const versionParts = oldVersion.split('.').map(Number);
3925
+
3926
+ // 根据类型增加版本号
3927
+ switch (versionType) {
3928
+ case 'major':
3929
+ versionParts[0]++;
3930
+ versionParts[1] = 0;
3931
+ versionParts[2] = 0;
3932
+ break;
3933
+ case 'minor':
3934
+ versionParts[1]++;
3935
+ versionParts[2] = 0;
3936
+ break;
3937
+ case 'patch':
3938
+ versionParts[2]++;
3939
+ break;
3940
+ default:
3941
+ return res.status(400).json({
3942
+ success: false,
3943
+ error: '无效的版本类型,必须是 major, minor 或 patch'
3944
+ });
3945
+ }
3946
+
3947
+ const newVersion = versionParts.join('.');
3948
+ packageJson.version = newVersion;
3949
+
3950
+ // 写回文件
3951
+ fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
3952
+
3953
+ console.log(`已更新npm版本号: ${oldVersion} → ${newVersion} (${packagePath})`);
3954
+
3955
+ res.json({
3956
+ success: true,
3957
+ oldVersion,
3958
+ newVersion
3959
+ });
3960
+ } catch (error) {
3961
+ console.error('更新版本号失败:', error);
3962
+ res.status(500).json({
3963
+ success: false,
3964
+ error: `更新版本号失败: ${error.message}`
3965
+ });
3966
+ }
3967
+ });
3968
+
3969
+ // API: 添加npm脚本
3970
+ app.post('/api/add-npm-script', async (req, res) => {
3971
+ try {
3972
+ const { packagePath, scriptName, scriptCommand } = req.body;
3973
+
3974
+ if (!packagePath || !scriptName || !scriptCommand) {
3975
+ return res.status(400).json({
3976
+ success: false,
3977
+ error: '缺少必要参数: packagePath, scriptName, scriptCommand'
3978
+ });
3979
+ }
3980
+
3981
+ // 确保路径指向package.json文件
3982
+ let packageJsonPath = path.resolve(packagePath);
3983
+ if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
3984
+ packageJsonPath = path.join(packageJsonPath, 'package.json');
3985
+ }
3986
+
3987
+ // 检查文件是否存在
3988
+ if (!fsSync.existsSync(packageJsonPath)) {
3989
+ return res.status(404).json({
3990
+ success: false,
3991
+ error: '找不到package.json文件'
3992
+ });
3993
+ }
3994
+
3995
+ // 读取package.json
3996
+ const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
3997
+
3998
+ // 确保scripts对象存在
3999
+ if (!packageJson.scripts) {
4000
+ packageJson.scripts = {};
4001
+ }
4002
+
4003
+ // 检查脚本是否已存在
4004
+ if (packageJson.scripts[scriptName]) {
4005
+ return res.status(400).json({
4006
+ success: false,
4007
+ error: `脚本 "${scriptName}" 已存在`
4008
+ });
4009
+ }
4010
+
4011
+ // 添加脚本
4012
+ packageJson.scripts[scriptName] = scriptCommand;
4013
+
4014
+ // 写回文件(保持格式化)
4015
+ fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
4016
+
4017
+ console.log(`已添加npm脚本: ${scriptName} = ${scriptCommand} (${packagePath})`);
4018
+
4019
+ res.json({
4020
+ success: true,
4021
+ scriptName,
4022
+ scriptCommand
4023
+ });
4024
+ } catch (error) {
4025
+ console.error('添加npm脚本失败:', error);
4026
+ res.status(500).json({
4027
+ success: false,
4028
+ error: `添加npm脚本失败: ${error.message}`
4029
+ });
4030
+ }
4031
+ });
4032
+
4033
+ // API: 更新npm脚本
4034
+ app.post('/api/update-npm-script', async (req, res) => {
4035
+ try {
4036
+ const { packagePath, scriptName, scriptCommand, oldScriptName } = req.body;
4037
+
4038
+ if (!packagePath || !scriptName || !scriptCommand) {
4039
+ return res.status(400).json({
4040
+ success: false,
4041
+ error: '缺少必要参数: packagePath, scriptName, scriptCommand'
4042
+ });
4043
+ }
4044
+
4045
+ // 确保路径指向package.json文件
4046
+ let packageJsonPath = path.resolve(packagePath);
4047
+ if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
4048
+ packageJsonPath = path.join(packageJsonPath, 'package.json');
4049
+ }
4050
+
4051
+ // 检查文件是否存在
4052
+ if (!fsSync.existsSync(packageJsonPath)) {
4053
+ return res.status(404).json({
4054
+ success: false,
4055
+ error: '找不到package.json文件'
4056
+ });
4057
+ }
4058
+
4059
+ // 读取package.json
4060
+ const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
4061
+
4062
+ // 确保scripts对象存在
4063
+ if (!packageJson.scripts) {
4064
+ packageJson.scripts = {};
4065
+ }
4066
+
4067
+ // 如果改了脚本名称,删除旧的
4068
+ if (oldScriptName && oldScriptName !== scriptName) {
4069
+ delete packageJson.scripts[oldScriptName];
4070
+ }
4071
+
4072
+ // 更新脚本
4073
+ packageJson.scripts[scriptName] = scriptCommand;
4074
+
4075
+ // 写回文件
4076
+ fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
4077
+
4078
+ console.log(`已更新npm脚本: ${scriptName} = ${scriptCommand} (${packagePath})`);
4079
+
4080
+ res.json({
4081
+ success: true,
4082
+ scriptName,
4083
+ scriptCommand
4084
+ });
4085
+ } catch (error) {
4086
+ console.error('更新npm脚本失败:', error);
4087
+ res.status(500).json({
4088
+ success: false,
4089
+ error: `更新npm脚本失败: ${error.message}`
4090
+ });
4091
+ }
4092
+ });
4093
+
4094
+ // API: 删除npm脚本
4095
+ app.post('/api/delete-npm-script', async (req, res) => {
4096
+ try {
4097
+ const { packagePath, scriptName } = req.body;
4098
+
4099
+ if (!packagePath || !scriptName) {
4100
+ return res.status(400).json({
4101
+ success: false,
4102
+ error: '缺少必要参数: packagePath, scriptName'
4103
+ });
4104
+ }
4105
+
4106
+ // 确保路径指向package.json文件
4107
+ let packageJsonPath = path.resolve(packagePath);
4108
+ if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
4109
+ packageJsonPath = path.join(packageJsonPath, 'package.json');
4110
+ }
4111
+
4112
+ // 检查文件是否存在
4113
+ if (!fsSync.existsSync(packageJsonPath)) {
4114
+ return res.status(404).json({
4115
+ success: false,
4116
+ error: '找不到package.json文件'
4117
+ });
4118
+ }
4119
+
4120
+ // 读取package.json
4121
+ const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
4122
+
4123
+ // 检查scripts对象和脚本是否存在
4124
+ if (!packageJson.scripts || !packageJson.scripts[scriptName]) {
4125
+ return res.status(404).json({
4126
+ success: false,
4127
+ error: `脚本 "${scriptName}" 不存在`
4128
+ });
4129
+ }
4130
+
4131
+ // 删除脚本
4132
+ delete packageJson.scripts[scriptName];
4133
+
4134
+ // 写回文件
4135
+ fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
4136
+
4137
+ console.log(`已删除npm脚本: ${scriptName} (${packagePath})`);
4138
+
4139
+ res.json({
4140
+ success: true,
4141
+ scriptName
4142
+ });
4143
+ } catch (error) {
4144
+ console.error('删除npm脚本失败:', error);
4145
+ res.status(500).json({
4146
+ success: false,
4147
+ error: `删除npm脚本失败: ${error.message}`
4148
+ });
4149
+ }
4150
+ });
4151
+
4152
+ // API: 置顶npm脚本
4153
+ app.post('/api/pin-npm-script', async (req, res) => {
4154
+ try {
4155
+ const { packagePath, scriptName } = req.body;
4156
+
4157
+ if (!packagePath || !scriptName) {
4158
+ return res.status(400).json({
4159
+ success: false,
4160
+ error: '缺少必要参数: packagePath, scriptName'
4161
+ });
4162
+ }
4163
+
4164
+ // 确保路径指向package.json文件
4165
+ let packageJsonPath = path.resolve(packagePath);
4166
+ if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
4167
+ packageJsonPath = path.join(packageJsonPath, 'package.json');
4168
+ }
4169
+
4170
+ // 检查文件是否存在
4171
+ if (!fsSync.existsSync(packageJsonPath)) {
4172
+ return res.status(404).json({
4173
+ success: false,
4174
+ error: '找不到package.json文件'
4175
+ });
4176
+ }
4177
+
4178
+ // 读取package.json
4179
+ const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
4180
+
4181
+ // 检查scripts对象和脚本是否存在
4182
+ if (!packageJson.scripts || !packageJson.scripts[scriptName]) {
4183
+ return res.status(404).json({
4184
+ success: false,
4185
+ error: `脚本 "${scriptName}" 不存在`
4186
+ });
4187
+ }
4188
+
4189
+ // 保存要置顶的脚本内容
4190
+ const scriptCommand = packageJson.scripts[scriptName];
4191
+
4192
+ // 删除该脚本
4193
+ delete packageJson.scripts[scriptName];
4194
+
4195
+ // 创建新的scripts对象,将置顶脚本放在最前面
4196
+ const newScripts = {
4197
+ [scriptName]: scriptCommand,
4198
+ ...packageJson.scripts
4199
+ };
4200
+
4201
+ packageJson.scripts = newScripts;
4202
+
4203
+ // 写回文件
4204
+ fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
4205
+
4206
+ console.log(`已置顶npm脚本: ${scriptName} (${packagePath})`);
4207
+
4208
+ res.json({
4209
+ success: true,
4210
+ scriptName
4211
+ });
4212
+ } catch (error) {
4213
+ console.error('置顶npm脚本失败:', error);
4214
+ res.status(500).json({
4215
+ success: false,
4216
+ error: `置顶npm脚本失败: ${error.message}`
4217
+ });
4218
+ }
4219
+ });
3558
4220
 
3559
4221
  // Socket.io 实时更新
3560
4222
  io.on('connection', (socket) => {
@@ -3571,34 +4233,6 @@ async function startUIServer(noOpen = false, savePort = false) {
3571
4233
  const history = getCommandHistory();
3572
4234
  socket.emit('initial_command_history', { history });
3573
4235
 
3574
- // 发送项目信息给客户端
3575
- socket.emit('project_info', {
3576
- projectPath: currentProjectPath,
3577
- projectRoomId: projectRoomId
3578
- });
3579
-
3580
- // 客户端可以请求开始/停止监控
3581
- socket.on('start_monitoring', () => {
3582
- if (!watcher) {
3583
- initFileSystemWatcher().catch(err => console.error('[文件监控] 初始化失败:', err));
3584
- socket.emit('monitoring_status', { active: true });
3585
- }
3586
- });
3587
-
3588
- // 处理客户端加入新房间的请求
3589
- socket.on('join_room', (roomId) => {
3590
- socket.join(roomId);
3591
- console.log(`客户端 ${socket.id} 已加入房间: ${roomId}`);
3592
- });
3593
-
3594
- socket.on('stop_monitoring', () => {
3595
- if (watcher) {
3596
- watcher.close().catch(err => console.error('关闭监控器失败:', err));
3597
- watcher = null;
3598
- socket.emit('monitoring_status', { active: false });
3599
- }
3600
- });
3601
-
3602
4236
  // 请求完整命令历史
3603
4237
  socket.on('request_full_history', () => {
3604
4238
  const fullHistory = getCommandHistory();
@@ -3618,158 +4252,6 @@ async function startUIServer(noOpen = false, savePort = false) {
3618
4252
  });
3619
4253
  });
3620
4254
 
3621
- // 读取并解析.gitignore文件
3622
- async function parseGitignore(projectPath) {
3623
- const gitignorePath = path.join(projectPath, '.gitignore');
3624
- const ignorePatterns = [
3625
- /(^|[\/\\])\../, // 始终忽略.开头的文件(除了.gitignore本身)
3626
- '**/.git/**', // 始终忽略.git目录
3627
-
3628
- // 额外排除常见的编译产物和大文件,减少监控开销
3629
- '**/*.umd.cjs', // UMD打包文件
3630
- '**/*.min.js', // 压缩JS文件
3631
- '**/*.bundle.js', // Webpack打包文件
3632
- '**/*.dist.js', // 构建产物
3633
- '**/*.prod.js', // 生产环境文件
3634
- '**/lib/**', // 通常是编译产物
3635
- '**/es/**', // ES模块编译产物
3636
- '**/esm/**', // ES模块编译产物
3637
- '**/*.map', // Source map文件
3638
- '**/*.chunk.js', // 代码分割chunk
3639
- ];
3640
-
3641
- try {
3642
- const gitignoreContent = await fs.readFile(gitignorePath, 'utf8');
3643
- const lines = gitignoreContent.split('\n');
3644
- let validRules = 0;
3645
-
3646
- for (let line of lines) {
3647
- line = line.trim();
3648
-
3649
- // 跳过空行和注释
3650
- if (!line || line.startsWith('#')) continue;
3651
-
3652
- // 移除行尾的空格
3653
- line = line.replace(/\s+$/, '');
3654
-
3655
- // 跳过否定规则(chokidar不支持否定规则,这些规则会被忽略)
3656
- if (line.startsWith('!')) {
3657
- continue;
3658
- }
3659
-
3660
- // 将gitignore规则转换为glob模式
3661
- let pattern;
3662
-
3663
- // 如果以/开头,表示从根目录开始匹配
3664
- if (line.startsWith('/')) {
3665
- pattern = line.substring(1);
3666
- // 如果是目录,添加/**后缀
3667
- if (!pattern.includes('*') && !pattern.includes('.')) {
3668
- ignorePatterns.push(pattern);
3669
- ignorePatterns.push(pattern + '/**');
3670
- } else {
3671
- ignorePatterns.push(pattern);
3672
- }
3673
- } else if (line.endsWith('/')) {
3674
- // 明确的目录规则
3675
- const dirName = line.slice(0, -1);
3676
- ignorePatterns.push('**/' + dirName);
3677
- ignorePatterns.push('**/' + dirName + '/**');
3678
- } else {
3679
- // 文件或目录规则
3680
- // 如果包含*通配符,直接使用
3681
- if (line.includes('*')) {
3682
- ignorePatterns.push('**/' + line);
3683
- } else {
3684
- // 既匹配文件也匹配目录
3685
- ignorePatterns.push('**/' + line);
3686
- ignorePatterns.push('**/' + line + '/**');
3687
- }
3688
- }
3689
-
3690
- validRules++;
3691
- }
3692
-
3693
- console.log(`[文件监控] 从.gitignore读取了 ${validRules} 条有效的忽略规则`);
3694
- } catch (error) {
3695
- // .gitignore不存在或读取失败,使用默认规则
3696
- console.log('[文件监控] 未找到.gitignore,使用默认忽略规则');
3697
- ignorePatterns.push(
3698
- '**/node_modules/**',
3699
- '**/dist/**',
3700
- '**/build/**',
3701
- '**/coverage/**',
3702
- '**/.nuxt/**',
3703
- '**/.next/**',
3704
- '**/out/**',
3705
- '**/*.log'
3706
- );
3707
- }
3708
-
3709
- return ignorePatterns;
3710
- }
3711
-
3712
- // 初始化文件系统监控
3713
- async function initFileSystemWatcher() {
3714
- // 停止已有的监控器
3715
- if (watcher) {
3716
- watcher.close().catch(err => console.error('关闭旧监控器失败:', err));
3717
- }
3718
-
3719
- try {
3720
- // 获取当前工作目录
3721
- const currentDir = process.cwd();
3722
-
3723
- console.log(`初始化文件系统监控器,路径: ${currentDir}`);
3724
-
3725
- // 检查是否是Git仓库
3726
- if (!isGitRepo) {
3727
- console.log('当前目录不是Git仓库,不启动监控');
3728
- return;
3729
- }
3730
-
3731
- const watcherStartTime = Date.now();
3732
- console.log('[文件监控] 开始初始化监控器...');
3733
-
3734
- // 从.gitignore读取忽略规则
3735
- const ignorePatterns = await parseGitignore(currentDir);
3736
-
3737
- // 使用chokidar监控文件变动
3738
- watcher = chokidar.watch(currentDir, {
3739
- ignored: ignorePatterns,
3740
- persistent: true,
3741
- ignoreInitial: true, // 忽略初始扫描时的文件
3742
- depth: 10, // 限制扫描深度,避免过深的目录结构
3743
- awaitWriteFinish: {
3744
- stabilityThreshold: 300, // 等待文件写入完成的时间
3745
- pollInterval: 100 // 轮询间隔
3746
- }
3747
- });
3748
-
3749
- // 合并所有变动事件到一个处理程序
3750
- const events = ['add', 'change', 'unlink'];
3751
- events.forEach(event => {
3752
- watcher.on(event, path => {
3753
- console.log(`检测到文件变动 [${event}]: ${path}`);
3754
- debouncedNotifyChanges();
3755
- });
3756
- });
3757
-
3758
- watcher.on('ready', () => {
3759
- const initTime = Date.now() - watcherStartTime;
3760
- console.log(`[文件监控] 监控器初始化完成,耗时 ${initTime}ms`);
3761
- });
3762
-
3763
- watcher.on('error', error => {
3764
- console.error('[文件监控] 监控错误:', error);
3765
- });
3766
-
3767
- console.log('[文件监控] 监控器已启动(异步初始化中...)');
3768
- } catch (error) {
3769
- console.error('启动文件监控失败:', error);
3770
- }
3771
- }
3772
-
3773
4255
  // 获取并广播Git状态 (优化版本 - 只获取porcelain格式)
3774
4256
  async function getAndBroadcastStatus() {
3775
4257
  try {
@@ -3801,17 +4283,6 @@ async function startUIServer(noOpen = false, savePort = false) {
3801
4283
  }
3802
4284
  }
3803
4285
 
3804
- // 防抖处理函数
3805
- function debouncedNotifyChanges() {
3806
- if (debounceTimer) {
3807
- clearTimeout(debounceTimer);
3808
- }
3809
-
3810
- debounceTimer = setTimeout(() => {
3811
- getAndBroadcastStatus();
3812
- }, DEBOUNCE_DELAY);
3813
- }
3814
-
3815
4286
  // 检查当前目录是否是Git仓库
3816
4287
  let isGitRepo = false;
3817
4288
  try {