zen-gitsync 2.1.26 → 2.1.29

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.
@@ -2,7 +2,7 @@ import express from 'express';
2
2
  import { createServer } from 'http';
3
3
  import { fileURLToPath } from 'url';
4
4
  import path from 'path';
5
- import { execGitCommand } from '../../utils/index.js';
5
+ import { execGitCommand, getCommandHistory, clearCommandHistory, registerSocketIO } from '../../utils/index.js';
6
6
  import open from 'open';
7
7
  import config from '../../config.js';
8
8
  import chalk from 'chalk';
@@ -23,11 +23,45 @@ let debounceTimer = null;
23
23
  // 防抖延迟时间 (毫秒)
24
24
  const DEBOUNCE_DELAY = 1000;
25
25
 
26
+ // 分支状态缓存
27
+ let branchStatusCache = {
28
+ currentBranch: null,
29
+ upstreamBranch: null,
30
+ lastUpdate: 0,
31
+ cacheTimeout: 5000 // 5秒缓存
32
+ };
33
+
34
+ // 当前分支缓存 - 只在特定情况下更新
35
+ let currentBranchCache = {
36
+ branchName: null,
37
+ lastUpdate: 0,
38
+ // 分支名缓存时间更长,因为分支切换不频繁
39
+ cacheTimeout: 300000 // 5分钟缓存,或者直到主动清除
40
+ };
41
+
42
+ // 上游分支缓存 - 只在特定情况下更新
43
+ let upstreamBranchCache = {
44
+ upstreamBranch: null,
45
+ lastUpdate: 0,
46
+ // 上游分支缓存时间也较长,因为上游分支设置不频繁
47
+ cacheTimeout: 300000 // 5分钟缓存,或者直到主动清除
48
+ };
49
+
50
+ // 推送状态标记 - 用于优化推送后的分支状态查询
51
+ let recentPushStatus = {
52
+ justPushed: false,
53
+ pushTime: 0,
54
+ validDuration: 10000 // 推送后10秒内认为分支状态是同步的
55
+ };
56
+
26
57
  async function startUIServer(noOpen = false, savePort = false) {
27
58
  const app = express();
28
59
  const httpServer = createServer(app);
29
60
  const io = new Server(httpServer);
30
61
 
62
+ // 注册Socket.io实例,用于命令历史通知
63
+ registerSocketIO(io);
64
+
31
65
  // 添加全局中间件来解析JSON请求体
32
66
  app.use(express.json());
33
67
 
@@ -42,14 +76,18 @@ async function startUIServer(noOpen = false, savePort = false) {
42
76
  app.use(express.static(path.join(__dirname, '../public')));
43
77
 
44
78
  // API路由
45
- app.get('/api/status', async (req, res) => {
79
+ // 移除了 /api/status 端点,因为前端只使用 porcelain 格式
80
+
81
+ // Add new endpoint for command history
82
+ app.get('/api/command-history', async (req, res) => {
46
83
  try {
47
- const { stdout } = await execGitCommand('git status');
48
- res.json({ status: stdout });
84
+ const history = getCommandHistory();
85
+ res.json({ success: true, history });
49
86
  } catch (error) {
50
- res.status(500).json({ error: error.message });
87
+ res.status(500).json({ success: false, error: error.message });
51
88
  }
52
89
  });
90
+
53
91
  app.get('/api/status_porcelain', async (req, res) => {
54
92
  try {
55
93
  const { stdout } = await execGitCommand('git status --porcelain --untracked-files=all');
@@ -59,42 +97,190 @@ async function startUIServer(noOpen = false, savePort = false) {
59
97
  }
60
98
  });
61
99
 
62
- // 获取当前分支
100
+ // 获取当前分支的优化函数
101
+ async function getCurrentBranchOptimized(forceRefresh = false) {
102
+ const now = Date.now();
103
+
104
+ // 如果不是强制刷新且缓存有效,使用缓存
105
+ if (!forceRefresh &&
106
+ currentBranchCache.branchName &&
107
+ (now - currentBranchCache.lastUpdate) < currentBranchCache.cacheTimeout) {
108
+ console.log(`使用缓存的分支名: ${currentBranchCache.branchName}`);
109
+ return currentBranchCache.branchName;
110
+ }
111
+
112
+ // 缓存失效或强制刷新,重新获取
113
+ console.log('重新获取当前分支名...');
114
+ const { stdout } = await execGitCommand('git symbolic-ref --short HEAD');
115
+ const branchName = stdout.trim();
116
+
117
+ // 更新缓存
118
+ currentBranchCache = {
119
+ branchName,
120
+ lastUpdate: now,
121
+ cacheTimeout: 300000 // 5分钟缓存
122
+ };
123
+
124
+ return branchName;
125
+ }
126
+
127
+ // 获取上游分支的优化函数
128
+ async function getUpstreamBranchOptimized(forceRefresh = false) {
129
+ const now = Date.now();
130
+
131
+ // 如果不是强制刷新且缓存有效,使用缓存
132
+ if (!forceRefresh &&
133
+ upstreamBranchCache.upstreamBranch !== null &&
134
+ (now - upstreamBranchCache.lastUpdate) < upstreamBranchCache.cacheTimeout) {
135
+ console.log(`使用缓存的上游分支: ${upstreamBranchCache.upstreamBranch}`);
136
+ return upstreamBranchCache.upstreamBranch;
137
+ }
138
+
139
+ // 缓存失效或强制刷新,重新获取
140
+ console.log('重新获取上游分支...');
141
+ const { stdout: upstreamOutput } = await execGitCommand('git rev-parse --abbrev-ref --symbolic-full-name @{u}', { ignoreError: true });
142
+ const upstreamBranch = upstreamOutput.trim() || null;
143
+
144
+ // 更新缓存
145
+ upstreamBranchCache = {
146
+ upstreamBranch,
147
+ lastUpdate: now,
148
+ cacheTimeout: 300000 // 5分钟缓存
149
+ };
150
+
151
+ return upstreamBranch;
152
+ }
153
+
154
+ // 清除分支缓存的函数(在分支切换时调用)
155
+ function clearBranchCache() {
156
+ console.log('清除分支缓存');
157
+ currentBranchCache = {
158
+ branchName: null,
159
+ lastUpdate: 0,
160
+ cacheTimeout: 300000
161
+ };
162
+ // 清除上游分支缓存
163
+ upstreamBranchCache = {
164
+ upstreamBranch: null,
165
+ lastUpdate: 0,
166
+ cacheTimeout: 300000
167
+ };
168
+ // 同时清除分支状态缓存
169
+ branchStatusCache = {
170
+ currentBranch: null,
171
+ upstreamBranch: null,
172
+ lastUpdate: 0,
173
+ cacheTimeout: 5000
174
+ };
175
+ // 清除推送状态标记
176
+ recentPushStatus = {
177
+ justPushed: false,
178
+ pushTime: 0,
179
+ validDuration: 10000
180
+ };
181
+ }
182
+
183
+ // 获取当前分支 - 使用缓存优化
63
184
  app.get('/api/branch', async (req, res) => {
64
185
  try {
65
- const { stdout } = await execGitCommand('git rev-parse --abbrev-ref HEAD');
66
- res.json({ branch: stdout.trim() });
186
+ const forceRefresh = req.query.force === 'true';
187
+ const branch = await getCurrentBranchOptimized(forceRefresh);
188
+ res.json({ branch });
67
189
  } catch (error) {
68
190
  res.status(500).json({ error: error.message });
69
191
  }
70
192
  });
71
193
 
72
- // 获取分支与远程的差异状态(领先/落后提交数)
194
+ // 获取分支与远程的差异状态(领先/落后提交数)- 优化版本
73
195
  app.get('/api/branch-status', async (req, res) => {
74
196
  try {
75
197
  // 检查当前目录是否是Git仓库
76
198
  if (!isGitRepo) {
77
199
  return res.json({ hasUpstream: false, ahead: 0, behind: 0 });
78
200
  }
79
-
80
- // 获取当前分支
81
- const { stdout: branchOutput } = await execGitCommand('git symbolic-ref --short HEAD');
82
- const currentBranch = branchOutput.trim();
83
-
84
- // 获取上游分支
85
- const { stdout: upstreamOutput } = await execGitCommand('git rev-parse --abbrev-ref --symbolic-full-name @{u}', { ignoreError: true });
86
-
87
- if (!upstreamOutput.trim()) {
88
- // 没有上游分支
201
+
202
+ const now = Date.now();
203
+ const forceRefresh = req.query.force === 'true';
204
+ const refreshCountOnly = req.query.countOnly === 'true'; // 新增:只刷新计数
205
+
206
+ // 检查是否刚刚推送过,如果是则直接返回同步状态
207
+ if (recentPushStatus.justPushed &&
208
+ (now - recentPushStatus.pushTime) < recentPushStatus.validDuration) {
209
+ console.log('检测到最近推送过,直接返回同步状态');
210
+ return res.json({
211
+ hasUpstream: true,
212
+ upstreamBranch: branchStatusCache.upstreamBranch || 'origin/main',
213
+ ahead: 0,
214
+ behind: 0
215
+ });
216
+ }
217
+
218
+ // 检查分支信息缓存是否有效
219
+ const branchInfoCacheValid = branchStatusCache.currentBranch &&
220
+ branchStatusCache.upstreamBranch &&
221
+ (now - branchStatusCache.lastUpdate) < branchStatusCache.cacheTimeout;
222
+
223
+ // 如果只需要刷新计数,或者缓存有效且不是强制刷新
224
+ if ((refreshCountOnly && branchInfoCacheValid) || (!forceRefresh && branchInfoCacheValid)) {
225
+
226
+ // 使用缓存的分支信息,只重新计算领先/落后状态
227
+ const { stdout: aheadBehindOutput } = await execGitCommand(
228
+ `git rev-list --left-right --count ${branchStatusCache.currentBranch}...${branchStatusCache.upstreamBranch}`
229
+ );
230
+ const [ahead, behind] = aheadBehindOutput.trim().split('\t').map(Number);
231
+
232
+ console.log(`使用缓存的分支信息: ${branchStatusCache.currentBranch} -> ${branchStatusCache.upstreamBranch} (${refreshCountOnly ? '只刷新计数' : '缓存有效'})`);
233
+
234
+ return res.json({
235
+ hasUpstream: true,
236
+ upstreamBranch: branchStatusCache.upstreamBranch,
237
+ ahead,
238
+ behind
239
+ });
240
+ }
241
+
242
+ // 缓存失效或强制刷新,重新获取分支信息
243
+ console.log('重新获取分支信息...');
244
+
245
+ // 分支名缓存和分支状态缓存独立工作
246
+ // 只有在分支状态强制刷新且分支名缓存也失效时,才强制刷新分支名
247
+ const shouldForceRefreshBranch = forceRefresh &&
248
+ (!currentBranchCache.branchName ||
249
+ (Date.now() - currentBranchCache.lastUpdate) >= currentBranchCache.cacheTimeout);
250
+
251
+ const currentBranch = await getCurrentBranchOptimized(shouldForceRefreshBranch);
252
+
253
+ // 使用优化后的上游分支获取函数
254
+ // 只有在分支状态强制刷新且上游分支缓存也失效时,才强制刷新上游分支
255
+ const shouldForceRefreshUpstream = forceRefresh &&
256
+ (upstreamBranchCache.upstreamBranch === null ||
257
+ (Date.now() - upstreamBranchCache.lastUpdate) >= upstreamBranchCache.cacheTimeout);
258
+
259
+ const upstreamBranch = await getUpstreamBranchOptimized(shouldForceRefreshUpstream);
260
+
261
+ if (!upstreamBranch) {
262
+ // 没有上游分支,清空缓存
263
+ branchStatusCache = {
264
+ currentBranch: null,
265
+ upstreamBranch: null,
266
+ lastUpdate: 0,
267
+ cacheTimeout: 5000
268
+ };
89
269
  return res.json({ hasUpstream: false, ahead: 0, behind: 0 });
90
270
  }
91
-
92
- const upstreamBranch = upstreamOutput.trim();
93
-
271
+
272
+ // 更新缓存
273
+ branchStatusCache = {
274
+ currentBranch,
275
+ upstreamBranch,
276
+ lastUpdate: now,
277
+ cacheTimeout: 5000
278
+ };
279
+
94
280
  // 获取领先/落后提交数
95
281
  const { stdout: aheadBehindOutput } = await execGitCommand(`git rev-list --left-right --count ${currentBranch}...${upstreamBranch}`);
96
282
  const [ahead, behind] = aheadBehindOutput.trim().split('\t').map(Number);
97
-
283
+
98
284
  res.json({
99
285
  hasUpstream: true,
100
286
  upstreamBranch,
@@ -167,7 +353,10 @@ async function startUIServer(noOpen = false, savePort = false) {
167
353
 
168
354
  // 切换到新创建的分支
169
355
  await execGitCommand(`git checkout ${newBranchName}`);
170
-
356
+
357
+ // 清除分支缓存,因为分支已切换
358
+ clearBranchCache();
359
+
171
360
  res.json({ success: true, branch: newBranchName });
172
361
  } catch (error) {
173
362
  console.error('创建分支失败:', error);
@@ -185,7 +374,10 @@ async function startUIServer(noOpen = false, savePort = false) {
185
374
 
186
375
  // 执行分支切换
187
376
  await execGitCommand(`git checkout ${branch}`);
188
-
377
+
378
+ // 清除分支缓存,因为分支已切换
379
+ clearBranchCache();
380
+
189
381
  res.json({ success: true });
190
382
  } catch (error) {
191
383
  console.error('切换分支失败:', error);
@@ -819,6 +1011,15 @@ async function startUIServer(noOpen = false, savePort = false) {
819
1011
  app.post('/api/push', async (req, res) => {
820
1012
  try {
821
1013
  const { stdout } = await execGitCommand('git push');
1014
+
1015
+ // 推送成功后,设置推送状态标记
1016
+ recentPushStatus = {
1017
+ justPushed: true,
1018
+ pushTime: Date.now(),
1019
+ validDuration: 10000 // 10秒内认为分支状态是同步的
1020
+ };
1021
+
1022
+ console.log('推送成功,已设置推送状态标记');
822
1023
  res.json({ success: true, message: stdout });
823
1024
  } catch (error) {
824
1025
  res.status(500).json({ success: false, error: error.message });
@@ -865,7 +1066,7 @@ async function startUIServer(noOpen = false, savePort = false) {
865
1066
  try {
866
1067
  // 获取分页参数
867
1068
  const page = parseInt(req.query.page) || 1;
868
- const limit = parseInt(req.query.limit) || 100;
1069
+ const limit = parseInt(req.query.limit) || 20;
869
1070
  const skip = (page - 1) * limit;
870
1071
 
871
1072
  // 获取筛选参数
@@ -939,40 +1140,16 @@ async function startUIServer(noOpen = false, savePort = false) {
939
1140
  formatString = '%H%x1E%an%x1E%ae%x1E%ad%x1E%B%x1E%D%x1E%P';
940
1141
  }
941
1142
 
942
- console.log(`执行Git命令: git log --all --pretty=format:"${formatString}" --date=short ${options}`);
943
-
1143
+ console.log(`执行Git命令: git log --all --pretty=format:"${formatString}" --date=format:"%Y-%m-%d %H:%M" ${options}`);
1144
+
944
1145
  // 使用 git log 命令获取提交历史
945
1146
  let { stdout: logOutput } = await execGitCommand(
946
- `git log --all --pretty=format:"${formatString}" --date=short ${options}`
1147
+ `git log --all --pretty=format:"${formatString}" --date=format:"%Y-%m-%d %H:%M" ${options}`
947
1148
  );
948
1149
 
949
- // 获取总提交数量(考虑筛选条件)
950
- let totalCommits = 0;
951
- try {
952
- // 构建计数命令,包含相同的筛选条件
953
- // 对于作者筛选,使用 rev-list 并手动计数,避免 --count 与复杂作者筛选结合可能的问题
954
- if (author.length > 1) {
955
- // 多作者情况,使用 wc -l 手动计数
956
- const countCommand = `git rev-list --all ${commandOptions.join(' ')}`;
957
- console.log(`执行计数命令(多作者): ${countCommand}`);
958
-
959
- const { stdout: countOutput } = await execGitCommand(countCommand);
960
- // 计算行数作为提交数
961
- totalCommits = countOutput.trim().split('\n').filter(line => line.trim() !== '').length;
962
- } else {
963
- // 单作者或无作者筛选,可以直接使用 --count
964
- const countCommand = `git rev-list --all --count ${commandOptions.join(' ')}`;
965
- console.log(`执行计数命令(单作者): ${countCommand}`);
966
-
967
- const { stdout: countOutput } = await execGitCommand(countCommand);
968
- totalCommits = parseInt(countOutput.trim());
969
- }
970
- } catch (error) {
971
- console.error('获取提交总数失败:', error);
972
- totalCommits = 0;
973
- }
974
-
975
- processAndSendLogOutput(res, logOutput, totalCommits, page, limit, withParents);
1150
+ // 分页加载优化:不需要获取总数,通过实际返回的数据量判断是否还有更多
1151
+ // 这里直接处理已获取的数据,通过返回数据量判断是否还有更多
1152
+ processAndSendLogOutput(res, logOutput, page, limit, withParents);
976
1153
  } catch (error) {
977
1154
  console.error('获取Git日志失败:', error);
978
1155
  res.status(500).json({ error: '获取日志失败: ' + error.message });
@@ -1038,35 +1215,22 @@ async function startUIServer(noOpen = false, savePort = false) {
1038
1215
  }
1039
1216
 
1040
1217
  // 构建执行的命令
1041
- const command = `git log ${formattedBranchRefs} --pretty=format:"${formatString}" --date=short ${options}`;
1218
+ const command = `git log ${formattedBranchRefs} --pretty=format:"${formatString}" --date=format:"%Y-%m-%d %H:%M" ${options}`;
1042
1219
  console.log(`执行Git命令(带分支引用): ${command}`);
1043
1220
 
1044
1221
  // 执行命令
1045
1222
  const { stdout: logOutput } = await execGitCommand(command);
1046
1223
 
1047
- // 获取总提交数
1048
- let totalCommits = 0;
1049
- try {
1050
- // 构建计数命令
1051
- const countCommand = `git rev-list ${formattedBranchRefs} --count ${commandOptions.join(' ')}`;
1052
- console.log(`执行计数命令(分支): ${countCommand}`);
1053
-
1054
- const { stdout: countOutput } = await execGitCommand(countCommand);
1055
- totalCommits = parseInt(countOutput.trim());
1056
- } catch (error) {
1057
- console.error('获取提交总数失败:', error);
1058
- totalCommits = 0;
1059
- }
1060
-
1061
- processAndSendLogOutput(res, logOutput, totalCommits, skip / limit + 1, limit, withParents);
1224
+ // 分页加载优化:不需要获取总数,通过实际返回的数据量判断是否还有更多
1225
+ processAndSendLogOutput(res, logOutput, skip / limit + 1, limit, withParents);
1062
1226
  } catch (error) {
1063
1227
  console.error('执行Git日志命令失败:', error);
1064
1228
  res.status(500).json({ error: '获取日志失败: ' + error.message });
1065
1229
  }
1066
1230
  }
1067
1231
 
1068
- // 抽取处理输出并发送响应的函数
1069
- function processAndSendLogOutput(res, logOutput, totalCommits, page, limit, withParents = false) {
1232
+ // 抽取处理输出并发送响应的函数(优化版本:不再依赖总数计算)
1233
+ function processAndSendLogOutput(res, logOutput, page, limit, withParents = false) {
1070
1234
  // 替换提交记录之间的换行符 - 使用 ASCII 控制字符 \x1E 匹配
1071
1235
  logOutput = logOutput.replace(/\n(?=[a-f0-9]{40}\x1E)/g, "<<<RECORD_SEPARATOR>>>");
1072
1236
 
@@ -1097,15 +1261,17 @@ async function startUIServer(noOpen = false, savePort = false) {
1097
1261
  return null;
1098
1262
  }).filter(item => item !== null);
1099
1263
 
1100
- // 计算是否有更多数据
1101
- const hasMore = page * limit < totalCommits;
1102
-
1103
- console.log(`分页查询 - 页码: ${page}, 每页数量: ${limit}, 总数: ${totalCommits}, 返回数量: ${data.length}, 是否有更多: ${hasMore}`);
1104
-
1264
+ // 优化:通过返回的数据量判断是否有更多数据,而不依赖总数计算
1265
+ // 如果返回的数据量等于请求的limit,说明可能还有更多数据
1266
+ // 如果返回的数据量小于limit,说明已经到底了
1267
+ const hasMore = data.length === limit;
1268
+
1269
+ console.log(`分页查询 - 页码: ${page}, 每页数量: ${limit}, 返回数量: ${data.length}, 是否有更多: ${hasMore} (优化版本,不计算总数)`);
1270
+
1105
1271
  // 返回提交历史数据,包括是否有更多数据的标志
1106
1272
  res.json({
1107
1273
  data: data,
1108
- total: totalCommits,
1274
+ total: -1, // 不再提供总数,前端不应依赖此字段
1109
1275
  page: page,
1110
1276
  limit: limit,
1111
1277
  hasMore: hasMore
@@ -1715,6 +1881,10 @@ async function startUIServer(noOpen = false, savePort = false) {
1715
1881
  // 当客户端连接时,立即发送一次Git状态
1716
1882
  getAndBroadcastStatus();
1717
1883
 
1884
+ // 发送当前命令历史
1885
+ const history = getCommandHistory();
1886
+ socket.emit('initial_command_history', { history });
1887
+
1718
1888
  // 客户端可以请求开始/停止监控
1719
1889
  socket.on('start_monitoring', () => {
1720
1890
  if (!watcher) {
@@ -1731,6 +1901,18 @@ async function startUIServer(noOpen = false, savePort = false) {
1731
1901
  }
1732
1902
  });
1733
1903
 
1904
+ // 请求完整命令历史
1905
+ socket.on('request_full_history', () => {
1906
+ const fullHistory = getCommandHistory();
1907
+ socket.emit('full_command_history', { history: fullHistory });
1908
+ });
1909
+
1910
+ // 清空命令历史
1911
+ socket.on('clear_command_history', () => {
1912
+ const result = clearCommandHistory();
1913
+ socket.emit('command_history_cleared', { success: result });
1914
+ });
1915
+
1734
1916
  // 客户端断开连接
1735
1917
  socket.on('disconnect', () => {
1736
1918
  console.log('客户端已断开连接:', socket.id);
@@ -1790,34 +1972,29 @@ async function startUIServer(noOpen = false, savePort = false) {
1790
1972
  }
1791
1973
  }
1792
1974
 
1793
- // 获取并广播Git状态
1975
+ // 获取并广播Git状态 (优化版本 - 只获取porcelain格式)
1794
1976
  async function getAndBroadcastStatus() {
1795
1977
  try {
1796
1978
  // 如果不是Git仓库,发送特殊状态
1797
1979
  if (!isGitRepo) {
1798
- io.emit('git_status_update', {
1980
+ io.emit('git_status_update', {
1799
1981
  isGitRepo: false,
1800
- status: '当前目录不是Git仓库',
1801
1982
  porcelain: '',
1802
1983
  timestamp: new Date().toISOString()
1803
1984
  });
1804
1985
  return;
1805
1986
  }
1806
-
1807
- // 获取常规状态
1808
- const { stdout: statusOutput } = await execGitCommand('git status');
1809
-
1810
- // 获取porcelain格式状态
1987
+
1988
+ // 只获取porcelain格式状态,不再获取完整的git status
1811
1989
  const { stdout: porcelainOutput } = await execGitCommand('git status --porcelain --untracked-files=all');
1812
-
1990
+
1813
1991
  // 广播到所有连接的客户端
1814
- io.emit('git_status_update', {
1992
+ io.emit('git_status_update', {
1815
1993
  isGitRepo: true,
1816
- status: statusOutput,
1817
1994
  porcelain: porcelainOutput,
1818
1995
  timestamp: new Date().toISOString()
1819
1996
  });
1820
-
1997
+
1821
1998
  console.log('已广播Git状态更新');
1822
1999
  } catch (error) {
1823
2000
  console.error('获取或广播Git状态失败:', error);
@@ -1851,70 +2028,138 @@ async function startUIServer(noOpen = false, savePort = false) {
1851
2028
  // 启动服务器
1852
2029
  const PORT = 3000;
1853
2030
 
1854
- // 创建一个函数来保存端口号到文件
1855
- async function savePortToFile(port) {
1856
- try {
1857
- // 只有当savePort为true时才保存端口号
1858
- if (savePort) {
1859
- // 保存到项目根目录的.port文件
1860
- const portFilePath = path.join(process.cwd(), '.port');
1861
- await fs.writeFile(portFilePath, port.toString(), 'utf8');
1862
- console.log(`端口号 ${port} 已保存到 ${portFilePath}`);
2031
+ // 创建一个函数来保存端口号到文件和环境变量
2032
+ // 使用闭包保存端口状态,防止多次写入相同端口
2033
+ const savePortToFile = (function() {
2034
+ let savedPort = null;
2035
+
2036
+ return async function(port) {
2037
+ try {
2038
+ // 只有当savePort为true且端口没有保存过时才保存端口号
2039
+ if (savePort && savedPort !== port) {
2040
+ savedPort = port;
2041
+
2042
+ // 保存到项目根目录的.port文件
2043
+ const portFilePath = path.join(process.cwd(), '.port');
2044
+ await fs.writeFile(portFilePath, port.toString(), 'utf8');
2045
+ console.log(`端口号 ${port} 已保存到 ${portFilePath}`);
2046
+
2047
+ // 同时保存到前端Vite项目的.env.local文件
2048
+ try {
2049
+ const clientPath = path.join(process.cwd(), 'src', 'ui', 'client');
2050
+ const envPath = path.join(clientPath, '.env.local');
2051
+
2052
+ // 检查目录是否存在
2053
+ await fs.access(clientPath).catch(() => {
2054
+ console.log(`客户端目录 ${clientPath} 不存在,跳过环境变量设置`);
2055
+ return Promise.reject(new Error('Client directory not found'));
2056
+ });
2057
+
2058
+ // 写入环境变量
2059
+ await fs.writeFile(envPath, `VITE_BACKEND_PORT=${port}\n`, 'utf8');
2060
+ console.log(`端口号环境变量已保存到 ${envPath}`);
2061
+ } catch (envError) {
2062
+ console.error('保存端口号到环境变量失败,但不影响主要功能:', envError);
2063
+ }
2064
+ }
2065
+ } catch (error) {
2066
+ console.error('保存端口号到文件失败:', error);
1863
2067
  }
1864
- } catch (error) {
1865
- console.error('保存端口号到文件失败:', error);
1866
- }
1867
- }
2068
+ };
2069
+ })();
1868
2070
 
1869
- httpServer.listen(PORT, () => {
1870
- console.log(chalk.green('======================================'));
1871
- console.log(chalk.green(` Zen GitSync 服务器已启动`));
1872
- console.log(chalk.green(` 访问地址: http://localhost:${PORT}`));
1873
- console.log(chalk.green(` 启动时间: ${new Date().toLocaleString()}`));
1874
- if (isGitRepo) {
1875
- console.log(chalk.green(` 当前目录是Git仓库,文件监控已启动`));
1876
- // 启动文件监控
1877
- initFileSystemWatcher();
1878
- } else {
1879
- console.log(chalk.yellow(` 当前目录不是Git仓库,文件监控未启动`));
1880
- }
1881
- console.log(chalk.green('======================================'));
1882
-
1883
- // 保存端口号到文件
1884
- savePortToFile(PORT);
2071
+ // 尝试在可用端口上启动服务器
2072
+ await startServerOnAvailablePort(PORT);
2073
+
2074
+ // 启动服务器函数
2075
+ async function startServerOnAvailablePort(startPort) {
2076
+ let currentPort = startPort;
2077
+ const maxPort = startPort + 100; // 最多尝试100个端口
1885
2078
 
1886
- // 只有在noOpen为false时才打开浏览器
1887
- if (!noOpen) {
1888
- open(`http://localhost:${PORT}`);
1889
- }
1890
- }).on('error', async (err) => {
1891
- if (err.code === 'EADDRINUSE') {
1892
- console.log(`端口 ${PORT} 被占用,尝试其他端口...`);
1893
- let newPort = PORT + 1;
1894
- while (newPort < PORT + 100) {
1895
- try {
1896
- await new Promise((resolve, reject) => {
1897
- httpServer.listen(newPort, () => {
1898
- console.log(chalk.green('======================================'));
1899
- console.log(chalk.green(` Zen GitSync 服务器已启动`));
1900
- console.log(chalk.green(` 访问地址: http://localhost:${newPort}`));
1901
- console.log(chalk.green(` 启动时间: ${new Date().toLocaleString()}`));
1902
- console.log(chalk.green('======================================'));
1903
-
1904
- // 保存新端口号到文件
1905
- savePortToFile(newPort);
1906
-
1907
- resolve();
1908
- });
2079
+ while (currentPort < maxPort) {
2080
+ try {
2081
+ // 等待1秒,避免快速尝试多个端口
2082
+ if (currentPort > startPort) {
2083
+ await new Promise(resolve => setTimeout(resolve, 1000));
2084
+ console.log(`尝试端口 ${currentPort}...`);
2085
+ }
2086
+
2087
+ // 尝试在当前端口启动服务器
2088
+ await new Promise((resolve, reject) => {
2089
+ // 仅监听一次error事件
2090
+ const errorHandler = (err) => {
2091
+ httpServer.removeListener('error', errorHandler);
2092
+ reject(err);
2093
+ };
2094
+
2095
+ // 使用变量标记回调是否已执行,防止多次触发
2096
+ let callbackExecuted = false;
2097
+
2098
+ httpServer.once('error', errorHandler);
2099
+
2100
+ httpServer.listen(currentPort, () => {
2101
+ // 确保回调只执行一次
2102
+ if (callbackExecuted) return;
2103
+ callbackExecuted = true;
2104
+
2105
+ // 成功监听,移除错误处理器
2106
+ httpServer.removeListener('error', errorHandler);
2107
+
2108
+ // 输出服务器信息
2109
+ console.log(chalk.green('======================================'));
2110
+ console.log(chalk.green(` Zen GitSync 服务器已启动`));
2111
+ console.log(chalk.green(` 访问地址: http://localhost:${currentPort}`));
2112
+ console.log(chalk.green(` 启动时间: ${new Date().toLocaleString()}`));
2113
+
2114
+ if (isGitRepo) {
2115
+ console.log(chalk.green(` 当前目录是Git仓库,文件监控已启动`));
2116
+ // 启动文件监控
2117
+ initFileSystemWatcher();
2118
+ } else {
2119
+ console.log(chalk.yellow(` 当前目录不是Git仓库,文件监控未启动`));
2120
+ }
2121
+
2122
+ console.log(chalk.green('======================================'));
2123
+
2124
+ // 保存端口号到文件
2125
+ savePortToFile(currentPort);
2126
+
2127
+ // 只有在noOpen为false时才打开浏览器
2128
+ if (!noOpen) {
2129
+ open(`http://localhost:${currentPort}`);
2130
+ }
2131
+
2132
+ resolve();
1909
2133
  });
1910
- break;
1911
- } catch (error) {
1912
- newPort++;
2134
+ });
2135
+
2136
+ // 如果成功启动,退出循环
2137
+ return;
2138
+ } catch (err) {
2139
+ // 处理端口被占用的情况
2140
+ if (err.code === 'EADDRINUSE') {
2141
+ console.log(`端口 ${currentPort} 被占用,尝试下一个端口...`);
2142
+ currentPort++;
2143
+ } else {
2144
+ // 其他错误,直接抛出
2145
+ console.error('启动服务器失败:', err);
2146
+ process.exit(1);
1913
2147
  }
1914
2148
  }
1915
- } else {
1916
- console.error('启动服务器失败:', err);
1917
- process.exit(1);
2149
+ }
2150
+
2151
+ // 如果尝试了所有端口都失败
2152
+ console.error(`无法找到可用端口 (尝试范围: ${startPort}-${maxPort-1})`);
2153
+ process.exit(1);
2154
+ }
2155
+
2156
+ // 添加命令历史的清空API
2157
+ app.post('/api/clear-command-history', async (req, res) => {
2158
+ try {
2159
+ const result = clearCommandHistory();
2160
+ res.json({ success: result });
2161
+ } catch (error) {
2162
+ res.status(500).json({ success: false, error: error.message });
1918
2163
  }
1919
2164
  });
1920
2165
  }