zen-gitsync 2.6.5 → 2.6.8

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.
@@ -6,10 +6,10 @@
6
6
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
7
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
8
  <title>Zen-GitSync - Git同步工具</title>
9
- <script type="module" crossorigin src="/assets/index-D0xeSSyV.js"></script>
10
- <link rel="modulepreload" crossorigin href="/assets/vendor-CiRPTLQ-.js">
11
- <link rel="stylesheet" crossorigin href="/assets/vendor-D9qDBEE1.css">
12
- <link rel="stylesheet" crossorigin href="/assets/index-DmFxzTUu.css">
9
+ <script type="module" crossorigin src="/assets/index-DVrs6nuO.js"></script>
10
+ <link rel="modulepreload" crossorigin href="/assets/vendor-Cl3C22xu.js">
11
+ <link rel="stylesheet" crossorigin href="/assets/vendor-HJmoQ7iQ.css">
12
+ <link rel="stylesheet" crossorigin href="/assets/index-oq3DAZAl.css">
13
13
  </head>
14
14
  <body>
15
15
  <div id="app"></div>
@@ -11,6 +11,7 @@ import fsSync from 'fs';
11
11
  import os from 'os';
12
12
  import { Server } from 'socket.io';
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);
@@ -148,6 +149,134 @@ async function startUIServer(noOpen = false, savePort = false) {
148
149
  }
149
150
  });
150
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
+
151
280
  // 在新终端中执行自定义命令
152
281
  app.post('/api/exec-in-terminal', async (req, res) => {
153
282
  try {
@@ -232,7 +361,7 @@ async function startUIServer(noOpen = false, savePort = false) {
232
361
  }
233
362
 
234
363
  // 缓存失效或强制刷新,重新获取
235
- console.log('重新获取当前分支名...');
364
+ // console.log('重新获取当前分支名...');
236
365
  const { stdout } = await execGitCommand('git symbolic-ref --short HEAD');
237
366
  const branchName = stdout.trim();
238
367
 
@@ -362,7 +491,7 @@ async function startUIServer(noOpen = false, savePort = false) {
362
491
  }
363
492
 
364
493
  // 缓存失效或强制刷新,重新获取分支信息
365
- console.log('重新获取分支信息...');
494
+ // console.log('重新获取分支信息...');
366
495
 
367
496
  // 分支名缓存和分支状态缓存独立工作
368
497
  // 只有在分支状态强制刷新且分支名缓存也失效时,才强制刷新分支名
@@ -442,10 +571,6 @@ async function startUIServer(noOpen = false, savePort = false) {
442
571
  ...remoteBranchList
443
572
  ];
444
573
 
445
- console.log('本地分支:', localBranchList);
446
- console.log('远程分支:', remoteBranchList);
447
- console.log('所有分支:', allBranches);
448
-
449
574
  res.json({ branches: allBranches });
450
575
  } catch (error) {
451
576
  console.error('获取分支列表失败:', error);
@@ -1158,12 +1283,12 @@ async function startUIServer(noOpen = false, savePort = false) {
1158
1283
  // 获取配置
1159
1284
  app.get('/api/config/getConfig', async (req, res) => {
1160
1285
  try {
1161
- console.log('获取配置中。。。')
1286
+ // console.log('获取配置中。。。')
1162
1287
  const config = await configManager.loadConfig()
1163
- console.log('获取配置成功')
1288
+ // console.log('获取配置成功')
1164
1289
  res.json(config)
1165
1290
  } catch (error) {
1166
- console.log('获取配置失败')
1291
+ // console.log('获取配置失败')
1167
1292
  res.status(500).json({ error: error.message })
1168
1293
  }
1169
1294
  })
@@ -1569,6 +1694,176 @@ async function startUIServer(noOpen = false, savePort = false) {
1569
1694
  }
1570
1695
  })
1571
1696
 
1697
+ // 保存指令编排
1698
+ app.post('/api/config/save-orchestration', express.json(), async (req, res) => {
1699
+ try {
1700
+ const { orchestration } = req.body
1701
+
1702
+ if (!orchestration || !orchestration.name || !Array.isArray(orchestration.steps)) {
1703
+ return res.status(400).json({ success: false, error: '缺少必要参数' })
1704
+ }
1705
+
1706
+ const config = await configManager.loadConfig()
1707
+
1708
+ // 确保编排数组存在
1709
+ if (!Array.isArray(config.orchestrations)) {
1710
+ config.orchestrations = []
1711
+ }
1712
+
1713
+ // 生成唯一ID
1714
+ const id = `orch_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
1715
+ const newOrchestration = {
1716
+ id,
1717
+ name: orchestration.name,
1718
+ description: orchestration.description || '',
1719
+ steps: orchestration.steps
1720
+ }
1721
+
1722
+ config.orchestrations.push(newOrchestration)
1723
+ await configManager.saveConfig(config)
1724
+
1725
+ res.json({ success: true, orchestration: newOrchestration })
1726
+ } catch (error) {
1727
+ res.status(500).json({ success: false, error: error.message })
1728
+ }
1729
+ })
1730
+
1731
+ // 删除指令编排
1732
+ app.post('/api/config/delete-orchestration', express.json(), async (req, res) => {
1733
+ try {
1734
+ const { id } = req.body
1735
+
1736
+ if (!id) {
1737
+ return res.status(400).json({ success: false, error: '缺少编排ID参数' })
1738
+ }
1739
+
1740
+ const config = await configManager.loadConfig()
1741
+
1742
+ if (Array.isArray(config.orchestrations)) {
1743
+ const index = config.orchestrations.findIndex(orch => orch.id === id)
1744
+ if (index !== -1) {
1745
+ config.orchestrations.splice(index, 1)
1746
+ await configManager.saveConfig(config)
1747
+ }
1748
+ }
1749
+
1750
+ res.json({ success: true })
1751
+ } catch (error) {
1752
+ res.status(500).json({ success: false, error: error.message })
1753
+ }
1754
+ })
1755
+
1756
+ // 更新指令编排
1757
+ app.post('/api/config/update-orchestration', express.json(), async (req, res) => {
1758
+ try {
1759
+ const { id, orchestration } = req.body
1760
+
1761
+ if (!id || !orchestration || !orchestration.name || !Array.isArray(orchestration.steps)) {
1762
+ return res.status(400).json({ success: false, error: '缺少必要参数' })
1763
+ }
1764
+
1765
+ const config = await configManager.loadConfig()
1766
+
1767
+ if (Array.isArray(config.orchestrations)) {
1768
+ const index = config.orchestrations.findIndex(orch => orch.id === id)
1769
+ if (index !== -1) {
1770
+ config.orchestrations[index] = {
1771
+ id,
1772
+ name: orchestration.name,
1773
+ description: orchestration.description || '',
1774
+ steps: orchestration.steps
1775
+ }
1776
+ await configManager.saveConfig(config)
1777
+ } else {
1778
+ return res.status(404).json({ success: false, error: '未找到指定编排' })
1779
+ }
1780
+ } else {
1781
+ return res.status(404).json({ success: false, error: '编排列表不存在' })
1782
+ }
1783
+
1784
+ res.json({ success: true })
1785
+ } catch (error) {
1786
+ res.status(500).json({ success: false, error: error.message })
1787
+ }
1788
+ })
1789
+
1790
+ // 版本号递增
1791
+ app.post('/api/version-bump', express.json(), async (req, res) => {
1792
+ try {
1793
+ const { bumpType, packageJsonPath } = req.body
1794
+
1795
+ // 确定 package.json 的路径
1796
+ let pkgPath
1797
+ if (packageJsonPath && packageJsonPath.trim()) {
1798
+ pkgPath = path.isAbsolute(packageJsonPath)
1799
+ ? packageJsonPath
1800
+ : path.join(currentProjectPath, packageJsonPath)
1801
+ } else {
1802
+ pkgPath = path.join(currentProjectPath, 'package.json')
1803
+ }
1804
+
1805
+ // 检查文件是否存在(使用 Node.js 原生方法)
1806
+ try {
1807
+ await fs.access(pkgPath)
1808
+ } catch (err) {
1809
+ return res.status(404).json({
1810
+ success: false,
1811
+ error: `未找到 package.json 文件: ${pkgPath}`
1812
+ })
1813
+ }
1814
+
1815
+ // 读取 package.json
1816
+ const pkgContent = await fs.readFile(pkgPath, 'utf8')
1817
+ const pkg = JSON.parse(pkgContent)
1818
+
1819
+ if (!pkg.version) {
1820
+ return res.status(400).json({
1821
+ success: false,
1822
+ error: 'package.json 中未找到 version 字段'
1823
+ })
1824
+ }
1825
+
1826
+ const oldVersion = pkg.version
1827
+
1828
+ // 解析版本号
1829
+ const versionParts = oldVersion.split('.').map(Number)
1830
+ if (versionParts.length !== 3 || versionParts.some(isNaN)) {
1831
+ return res.status(400).json({
1832
+ success: false,
1833
+ error: `无效的版本号格式: ${oldVersion},应为 x.y.z 格式`
1834
+ })
1835
+ }
1836
+
1837
+ // 根据类型递增版本号
1838
+ let [major, minor, patch] = versionParts
1839
+ if (bumpType === 'major') {
1840
+ major += 1
1841
+ minor = 0
1842
+ patch = 0
1843
+ } else if (bumpType === 'minor') {
1844
+ minor += 1
1845
+ patch = 0
1846
+ } else { // patch
1847
+ patch += 1
1848
+ }
1849
+
1850
+ const newVersion = `${major}.${minor}.${patch}`
1851
+ pkg.version = newVersion
1852
+
1853
+ // 写回 package.json(保持格式化)
1854
+ await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8')
1855
+
1856
+ res.json({
1857
+ success: true,
1858
+ oldVersion,
1859
+ newVersion,
1860
+ filePath: pkgPath
1861
+ })
1862
+ } catch (error) {
1863
+ res.status(500).json({ success: false, error: error.message })
1864
+ }
1865
+ })
1866
+
1572
1867
  // 提交更改
1573
1868
  app.post('/api/commit', express.json(), async (req, res) => {
1574
1869
  try {
@@ -2001,7 +2296,7 @@ async function startUIServer(noOpen = false, savePort = false) {
2001
2296
  formatString = '%H%x1E%an%x1E%ae%x1E%ad%x1E%B%x1E%D%x1E%P';
2002
2297
  }
2003
2298
 
2004
- console.log(`执行Git命令: git log --all --pretty=format:"${formatString}" --date=format-local:"%Y-%m-%d %H:%M" ${options}`);
2299
+ // console.log(`执行Git命令: git log --all --pretty=format:"${formatString}" --date=format-local:"%Y-%m-%d %H:%M" ${options}`);
2005
2300
 
2006
2301
  // 使用 git log 命令获取提交历史
2007
2302
  let { stdout: logOutput } = await execGitCommand(
@@ -2127,7 +2422,7 @@ async function startUIServer(noOpen = false, savePort = false) {
2127
2422
  // 如果返回的数据量小于limit,说明已经到底了
2128
2423
  const hasMore = data.length === limit;
2129
2424
 
2130
- console.log(`分页查询 - 页码: ${page}, 每页数量: ${limit}, 返回数量: ${data.length}, 是否有更多: ${hasMore} (优化版本,不计算总数)`);
2425
+ // console.log(`分页查询 - 页码: ${page}, 每页数量: ${limit}, 返回数量: ${data.length}, 是否有更多: ${hasMore} (优化版本,不计算总数)`);
2131
2426
 
2132
2427
  // 返回提交历史数据,包括是否有更多数据的标志
2133
2428
  res.json({
@@ -3330,7 +3625,7 @@ async function startUIServer(noOpen = false, savePort = false) {
3330
3625
  const packageJsons = [];
3331
3626
  const startTime = Date.now();
3332
3627
 
3333
- console.log(`[NPM扫描-后端] 开始扫描项目: ${projectRoot}`);
3628
+ // console.log(`[NPM扫描-后端] 开始扫描项目: ${projectRoot}`);
3334
3629
 
3335
3630
  // 需要忽略的目录列表(更全面)
3336
3631
  const IGNORED_DIRS = new Set([
@@ -3485,10 +3780,10 @@ async function startUIServer(noOpen = false, savePort = false) {
3485
3780
  }
3486
3781
 
3487
3782
  // 执行递归扫描
3488
- console.log(`[NPM扫描-后端] 开始递归扫描(最大深度${MAX_DEPTH}层)`);
3783
+ // console.log(`[NPM扫描-后端] 开始递归扫描(最大深度${MAX_DEPTH}层)`);
3489
3784
  const scanStart = Date.now();
3490
3785
  await scanDirectory(projectRoot, 0);
3491
- console.log(`[NPM扫描-后端] 递归扫描完成,耗时${Date.now() - scanStart}ms`);
3786
+ // console.log(`[NPM扫描-后端] 递归扫描完成,耗时${Date.now() - scanStart}ms`);
3492
3787
 
3493
3788
  // 扫描完成,清除abort controller
3494
3789
  if (currentScanAbortController === scanController) {
@@ -3513,8 +3808,8 @@ async function startUIServer(noOpen = false, savePort = false) {
3513
3808
  .filter(Boolean)
3514
3809
  .join(', ');
3515
3810
 
3516
- console.log(`npm脚本扫描完成,耗时${scanTime}ms,扫描了${scannedCount}个目录,读取了${fileReadCount}个package.json文件,跳过${skippedCount}个目录,找到${packageJsons.length}个有效的package.json`);
3517
- console.log(`[NPM扫描-后端] 深度分布: ${depthInfo}`);
3811
+ // console.log(`npm脚本扫描完成,耗时${scanTime}ms,扫描了${scannedCount}个目录,读取了${fileReadCount}个package.json文件,跳过${skippedCount}个目录,找到${packageJsons.length}个有效的package.json`);
3812
+ // console.log(`[NPM扫描-后端] 深度分布: ${depthInfo}`);
3518
3813
 
3519
3814
  res.json({
3520
3815
  success: true,
@@ -4084,9 +4379,6 @@ async function startUIServer(noOpen = false, savePort = false) {
4084
4379
 
4085
4380
  if (isGitRepo) {
4086
4381
  console.log(chalk.green(` 当前目录是Git仓库`));
4087
- console.log(chalk.yellow(` 文件监控已禁用(默认),需要时请在前端开启自动更新开关`));
4088
- // 不再自动启动文件监控,只在用户开启自动更新开关时启动
4089
- // initFileSystemWatcher().catch(err => console.error('[文件监控] 初始化失败:', err));
4090
4382
  } else {
4091
4383
  console.log(chalk.yellow(` 当前目录不是Git仓库,文件监控未启动`));
4092
4384
  }
@@ -158,11 +158,33 @@ const tableLog = (commandLine, content, type) => {
158
158
  default:
159
159
  break;
160
160
  }
161
+
162
+ // 限制输出内容
163
+ const MAX_LINES = 10; // 最大行数
164
+ const MAX_LINE_LENGTH = 200; // 每行最大字符数
165
+ let isTruncated = false;
166
+
167
+ if (content.length > MAX_LINES) {
168
+ content = content.slice(0, MAX_LINES);
169
+ isTruncated = true;
170
+ }
171
+
161
172
  content = content.map(item => {
162
173
  let fontColor = calcColor(commandLine, item)
163
174
  let row = item.replaceAll('\t', ' ')
175
+
176
+ // 截断过长的行
177
+ if (row.length > MAX_LINE_LENGTH) {
178
+ row = row.substring(0, MAX_LINE_LENGTH) + '...';
179
+ }
180
+
164
181
  return chalk[fontColor](row)
165
182
  })
183
+
184
+ // 如果内容被截断,添加提示
185
+ if (isTruncated) {
186
+ content.push(chalk.dim('... (输出内容过多,已省略)'));
187
+ }
166
188
 
167
189
  printTableWithHeaderUnderline(head, content, style)
168
190
  }