zen-gitsync 2.12.2 → 2.12.4

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.
Files changed (47) hide show
  1. package/LICENSE +190 -21
  2. package/index.js +25 -11
  3. package/package.json +18 -5
  4. package/scripts/convert-colors-to-vars.cjs +286 -272
  5. package/scripts/convert-fontsize-to-vars.cjs +221 -207
  6. package/scripts/convert-spacing-to-vars.cjs +256 -242
  7. package/scripts/convert-to-standard-vars.cjs +282 -268
  8. package/scripts/release.js +599 -585
  9. package/src/config.js +350 -336
  10. package/src/gitCommit.js +455 -440
  11. package/src/ui/public/assets/{EditorView-bnJmBq-i.js → EditorView-BZaOzahT.js} +2 -2
  12. package/src/ui/public/assets/EditorView-CbqSI9nw.css +1 -0
  13. package/src/ui/public/assets/{SourceMapView-Rz5SD0A0.js → SourceMapView-D_8mnVt2.js} +3 -3
  14. package/src/ui/public/assets/SourceMapView-DyMK80hS.css +1 -0
  15. package/src/ui/public/assets/{index-Bo3tntQh.js → index-BgFwmXzV.js} +11 -11
  16. package/src/ui/public/assets/{index-bOs5P8fz.css → index-Dsi6tg7k.css} +1 -1
  17. package/src/ui/public/index.html +2 -2
  18. package/src/ui/server/index.js +410 -396
  19. package/src/ui/server/middleware/requestLogger.js +51 -37
  20. package/src/ui/server/routes/branchStatus.js +101 -87
  21. package/src/ui/server/routes/code.js +110 -96
  22. package/src/ui/server/routes/codeAnalysis.js +995 -981
  23. package/src/ui/server/routes/config.js +1190 -1158
  24. package/src/ui/server/routes/exec.js +272 -258
  25. package/src/ui/server/routes/fileOpen.js +279 -265
  26. package/src/ui/server/routes/fs.js +701 -687
  27. package/src/ui/server/routes/git/diff.js +352 -338
  28. package/src/ui/server/routes/git/diffUtils.js +128 -114
  29. package/src/ui/server/routes/git/stash.js +552 -538
  30. package/src/ui/server/routes/git/tags.js +172 -158
  31. package/src/ui/server/routes/git.js +190 -176
  32. package/src/ui/server/routes/gitOps.js +1179 -1165
  33. package/src/ui/server/routes/instances.js +14 -0
  34. package/src/ui/server/routes/npm.js +1023 -1009
  35. package/src/ui/server/routes/process.js +82 -68
  36. package/src/ui/server/routes/status.js +67 -53
  37. package/src/ui/server/routes/terminal.js +319 -305
  38. package/src/ui/server/socket/registerUiSocketHandlers.js +226 -212
  39. package/src/ui/server/utils/createSavePortToFile.js +46 -32
  40. package/src/ui/server/utils/instanceRegistry.js +14 -0
  41. package/src/ui/server/utils/pathGuard.js +14 -0
  42. package/src/ui/server/utils/pathGuard.test.js +14 -0
  43. package/src/ui/server/utils/randomStartPort.js +14 -0
  44. package/src/ui/server/utils/startServerOnAvailablePort.js +101 -87
  45. package/src/utils/index.js +14 -0
  46. package/src/ui/public/assets/EditorView-CHBjgiZc.css +0 -1
  47. package/src/ui/public/assets/SourceMapView-DhQX0K7t.css +0 -1
@@ -1,258 +1,272 @@
1
- import path from 'path';
2
- import iconv from 'iconv-lite';
3
- import { spawn } from 'child_process';
4
-
5
- export function registerExecRoutes({
6
- app,
7
- execGitCommand,
8
- addCommandToHistory,
9
- getCurrentProjectPath,
10
- nextProcessId,
11
- runningProcesses
12
- }) {
13
- // 通用命令执行接口(非流式)
14
- app.post('/api/exec', async (req, res) => {
15
- try {
16
- const { command } = req.body || {};
17
- if (!command || typeof command !== 'string' || !command.trim()) {
18
- return res.status(400).json({ success: false, error: 'command 不能为空' });
19
- }
20
-
21
- try {
22
- const { stdout = '', stderr = '' } = await execGitCommand(command, { log: false });
23
- return res.json({ success: true, stdout, stderr });
24
- } catch (err) {
25
- return res.status(400).json({ success: false, error: err?.message || String(err) });
26
- }
27
- } catch (error) {
28
- res.status(500).json({ success: false, error: error.message });
29
- }
30
- });
31
-
32
- // 流式执行命令接口(支持实时输出)
33
- app.post('/api/exec-stream', async (req, res) => {
34
- try {
35
- const { command, directory } = req.body || {};
36
- if (!command || typeof command !== 'string' || !command.trim()) {
37
- return res.status(400).json({ success: false, error: 'command 不能为空' });
38
- }
39
-
40
- const currentProjectPath = getCurrentProjectPath();
41
-
42
- // 确定执行目录
43
- const execDirectory = directory && directory.trim()
44
- ? (path.isAbsolute(directory) ? directory : path.join(currentProjectPath, directory))
45
- : currentProjectPath;
46
-
47
- console.log(`流式执行命令: ${command}`);
48
- console.log(`执行目录: ${execDirectory}`);
49
-
50
- // 分配进程 ID
51
- const processId = nextProcessId();
52
-
53
- // 设置响应头为流式传输
54
- res.setHeader('Content-Type', 'text/event-stream');
55
- res.setHeader('Cache-Control', 'no-cache');
56
- res.setHeader('Connection', 'keep-alive');
57
- res.setHeader('X-Accel-Buffering', 'no'); // 禁用nginx缓冲
58
-
59
- // 记录执行开始时间(用于命令历史)
60
- const startTime = Date.now();
61
-
62
- // 用于收集输出(用于命令历史)
63
- let collectedStdout = '';
64
- let collectedStderr = '';
65
-
66
- // 使用 shell: true 来支持 Windows 内置命令(如 dir、cd 等)
67
- const childProcess = spawn(command.trim(), [], {
68
- cwd: execDirectory,
69
- shell: true, // 通过 shell 执行,支持 Windows 内置命令
70
- env: {
71
- ...process.env,
72
- // Git 配置:启用颜色输出和禁用路径引用
73
- GIT_CONFIG_PARAMETERS: "'color.ui=always' 'color.status=always' 'core.quotepath=false'",
74
- // 强制启用颜色输出 - 多种工具的配置
75
- FORCE_COLOR: '3', // 使用级别3(最强),支持 chalk 等库
76
- NPM_CONFIG_COLOR: 'always',
77
- TERM: 'xterm-256color', // 模拟256色终端环境
78
- COLORTERM: 'truecolor', // 支持真彩色
79
- CLICOLOR_FORCE: '1', // 强制启用颜色(某些工具检测此变量)
80
- // 确保输出不被缓冲
81
- PYTHONUNBUFFERED: '1'
82
- // 注意:不设置 CI=true 和 NO_COLOR,避免禁用颜色输出
83
- }
84
- });
85
-
86
- // 存储进程信息
87
- runningProcesses.set(processId, {
88
- childProcess,
89
- command: command.trim(),
90
- startTime,
91
- directory: execDirectory
92
- });
93
- console.log(`[进程管理] 创建进程 #${processId}: ${command.substring(0, 50)}`);
94
-
95
- // 发送数据到客户端的辅助函数
96
- const sendData = (type, data) => {
97
- const message = `data: ${JSON.stringify({ type, data })}\n\n`;
98
- // console.log(`[流式输出] 发送数据 - 类型: ${type}, 长度: ${data?.length || 0}`);
99
- res.write(message);
100
- };
101
-
102
- // 立即发送 processId 给前端
103
- sendData('process_id', processId);
104
-
105
- let outputReceived = false;
106
-
107
- // 判断是否需要 GBK 转换
108
- // 只有 Windows CMD 内置命令(如 dir、type 等)才需要 GBK 转换
109
- // npm、node、git 等现代工具都输出 UTF-8
110
- const isWindows = process.platform === 'win32';
111
- const cmdBuiltins = ['dir', 'type', 'set', 'path', 'cd', 'md', 'rd', 'del', 'copy', 'move', 'ren'];
112
-
113
- // echo 命令特殊处理:如果包含变量替换(如 {{xxx}}),说明内容可能已经是 UTF-8,不转换
114
- const isEchoCommand = command.trim().toLowerCase().startsWith('echo ');
115
- const hasVariableSubstitution = isEchoCommand && (
116
- command.includes('{{') ||
117
- command.includes('${') ||
118
- command.includes('%') // Windows 环境变量
119
- );
120
-
121
- const needsGbkConversion = isWindows && (
122
- cmdBuiltins.some(builtin =>
123
- command.trim().toLowerCase().startsWith(builtin + ' ') ||
124
- command.trim().toLowerCase() === builtin
125
- ) || (isEchoCommand && !hasVariableSubstitution) // echo 只在没有变量替换时才转换
126
- );
127
-
128
- console.log(`[流式输出] 命令: ${command.substring(0, 50)}, 需要GBK转换: ${needsGbkConversion}`);
129
-
130
- // 监听标准输出
131
- childProcess.stdout?.on('data', (data) => {
132
- // data 是 Buffer 对象
133
- let output;
134
- if (needsGbkConversion) {
135
- // Windows CMD 内置命令,从 GBK 转换为 UTF-8
136
- output = iconv.decode(data, 'gbk');
137
- console.log(`[流式输出] 收到stdout(GBK转UTF8):`, output.substring(0, 200));
138
- } else {
139
- // 现代工具或 Unix 系统,直接使用 UTF-8
140
- output = data.toString('utf8');
141
- console.log(`[流式输出] 收到stdout(UTF8):`, output.substring(0, 200));
142
- }
143
- outputReceived = true;
144
- collectedStdout += output; // 收集输出用于历史记录
145
- sendData('stdout', output);
146
- });
147
-
148
- // 监听标准错误输出
149
- childProcess.stderr?.on('data', (data) => {
150
- // data 是 Buffer 对象
151
- let output;
152
-
153
- if (isWindows) {
154
- // Windows 平台需要智能检测编码
155
- // 先尝试 UTF-8 解码
156
- const utf8Output = data.toString('utf8');
157
-
158
- // 检测是否包含 UTF-8 替换字符(�),这通常表示解码失败
159
- // 如果没有替换字符且包含正常字符,说明是有效的 UTF-8
160
- if (!utf8Output.includes('�') || utf8Output.match(/[\u4e00-\u9fa5]/)) {
161
- // UTF-8 解码成功(包含有效中文或没有替换字符)
162
- output = utf8Output;
163
- console.log(`[流式输出] 收到stderr(UTF8):`, output.substring(0, 200));
164
- } else {
165
- // UTF-8 解码失败,尝试 GBK(可能是 CMD shell 的系统消息)
166
- try {
167
- output = iconv.decode(data, 'gbk');
168
- console.log(`[流式输出] 收到stderr(GBK转UTF8):`, output.substring(0, 200));
169
- } catch (e) {
170
- // GBK 也失败,使用原始 UTF-8 结果
171
- output = utf8Output;
172
- console.log(`[流式输出] GBK解码失败,使用UTF8:`, output.substring(0, 200));
173
- }
174
- }
175
- } else {
176
- // Unix系统,直接使用 UTF-8
177
- output = data.toString('utf8');
178
- console.log(`[流式输出] 收到stderr(UTF8):`, output.substring(0, 200));
179
- }
180
-
181
- outputReceived = true;
182
- collectedStderr += output; // 收集错误输出用于历史记录
183
- // 不再自动标记为错误,只显示 stderr 输出
184
- // Git 的警告信息会输出到 stderr 但退出码仍为 0
185
- sendData('stderr', output);
186
- });
187
-
188
- // 监听进程退出(exit 在流关闭前触发)
189
- childProcess.on('exit', (code, signal) => {
190
- // console.log(`[流式输出] 进程 exit 事件 - 代码: ${code}, 信号: ${signal}`);
191
- });
192
-
193
- // 监听进程关闭(close 在流关闭后触发)
194
- childProcess.on('close', (code, signal) => {
195
- // console.log(`[流式输出] 进程 close 事件 - 代码: ${code}, 信号: ${signal}, 有输出: ${outputReceived}`);
196
-
197
- // 从运行进程列表中移除
198
- runningProcesses.delete(processId);
199
- console.log(`[进程管理] 进程 #${processId} 已结束,剩余进程数: ${runningProcesses.size}`);
200
-
201
- // 计算执行时间
202
- const executionTime = Date.now() - startTime;
203
-
204
- // 添加到命令历史
205
- const error = code !== 0 ? `Command exited with code ${code}` : null;
206
- addCommandToHistory(
207
- command.trim(),
208
- collectedStdout,
209
- collectedStderr,
210
- error,
211
- executionTime
212
- );
213
-
214
- // 只根据退出码判断成功与否,退出码为 0 表示成功
215
- sendData('exit', { code, success: code === 0 });
216
- res.end();
217
- });
218
-
219
- // 监听错误
220
- childProcess.on('error', (error) => {
221
- // console.error(`[流式输出] 进程错误:`, error);
222
-
223
- // 从运行进程列表中移除
224
- runningProcesses.delete(processId);
225
- console.log(`[进程管理] 进程 #${processId} 出错并结束,剩余进程数: ${runningProcesses.size}`);
226
-
227
- // 添加到命令历史(错误情况)
228
- const executionTime = Date.now() - startTime;
229
- addCommandToHistory(
230
- command.trim(),
231
- collectedStdout,
232
- collectedStderr,
233
- error.message,
234
- executionTime
235
- );
236
-
237
- sendData('error', error.message);
238
- res.end();
239
- });
240
-
241
- // 添加spawn事件监听
242
- childProcess.on('spawn', () => {
243
- // console.log(`[流式输出] 进程已启动 - PID: ${childProcess.pid}`);
244
- });
245
-
246
- // 注意:不监听req.on('close'),参考git push的实现
247
- // 进程会自然结束,close事件会触发res.end()
248
- // 如果监听req.on('close')可能会导致进程被提前kill
249
-
250
- } catch (error) {
251
- console.error('流式执行命令失败:', error);
252
- res.status(500).json({
253
- success: false,
254
- error: `流式执行命令失败: ${error.message}`
255
- });
256
- }
257
- });
258
- }
1
+ // Copyright 2026 xz333221
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ //
15
+ import path from 'path';
16
+ import iconv from 'iconv-lite';
17
+ import { spawn } from 'child_process';
18
+
19
+ export function registerExecRoutes({
20
+ app,
21
+ execGitCommand,
22
+ addCommandToHistory,
23
+ getCurrentProjectPath,
24
+ nextProcessId,
25
+ runningProcesses
26
+ }) {
27
+ // 通用命令执行接口(非流式)
28
+ app.post('/api/exec', async (req, res) => {
29
+ try {
30
+ const { command } = req.body || {};
31
+ if (!command || typeof command !== 'string' || !command.trim()) {
32
+ return res.status(400).json({ success: false, error: 'command 不能为空' });
33
+ }
34
+
35
+ try {
36
+ const { stdout = '', stderr = '' } = await execGitCommand(command, { log: false });
37
+ return res.json({ success: true, stdout, stderr });
38
+ } catch (err) {
39
+ return res.status(400).json({ success: false, error: err?.message || String(err) });
40
+ }
41
+ } catch (error) {
42
+ res.status(500).json({ success: false, error: error.message });
43
+ }
44
+ });
45
+
46
+ // 流式执行命令接口(支持实时输出)
47
+ app.post('/api/exec-stream', async (req, res) => {
48
+ try {
49
+ const { command, directory } = req.body || {};
50
+ if (!command || typeof command !== 'string' || !command.trim()) {
51
+ return res.status(400).json({ success: false, error: 'command 不能为空' });
52
+ }
53
+
54
+ const currentProjectPath = getCurrentProjectPath();
55
+
56
+ // 确定执行目录
57
+ const execDirectory = directory && directory.trim()
58
+ ? (path.isAbsolute(directory) ? directory : path.join(currentProjectPath, directory))
59
+ : currentProjectPath;
60
+
61
+ console.log(`流式执行命令: ${command}`);
62
+ console.log(`执行目录: ${execDirectory}`);
63
+
64
+ // 分配进程 ID
65
+ const processId = nextProcessId();
66
+
67
+ // 设置响应头为流式传输
68
+ res.setHeader('Content-Type', 'text/event-stream');
69
+ res.setHeader('Cache-Control', 'no-cache');
70
+ res.setHeader('Connection', 'keep-alive');
71
+ res.setHeader('X-Accel-Buffering', 'no'); // 禁用nginx缓冲
72
+
73
+ // 记录执行开始时间(用于命令历史)
74
+ const startTime = Date.now();
75
+
76
+ // 用于收集输出(用于命令历史)
77
+ let collectedStdout = '';
78
+ let collectedStderr = '';
79
+
80
+ // 使用 shell: true 来支持 Windows 内置命令(如 dir、cd 等)
81
+ const childProcess = spawn(command.trim(), [], {
82
+ cwd: execDirectory,
83
+ shell: true, // 通过 shell 执行,支持 Windows 内置命令
84
+ env: {
85
+ ...process.env,
86
+ // Git 配置:启用颜色输出和禁用路径引用
87
+ GIT_CONFIG_PARAMETERS: "'color.ui=always' 'color.status=always' 'core.quotepath=false'",
88
+ // 强制启用颜色输出 - 多种工具的配置
89
+ FORCE_COLOR: '3', // 使用级别3(最强),支持 chalk 等库
90
+ NPM_CONFIG_COLOR: 'always',
91
+ TERM: 'xterm-256color', // 模拟256色终端环境
92
+ COLORTERM: 'truecolor', // 支持真彩色
93
+ CLICOLOR_FORCE: '1', // 强制启用颜色(某些工具检测此变量)
94
+ // 确保输出不被缓冲
95
+ PYTHONUNBUFFERED: '1'
96
+ // 注意:不设置 CI=true NO_COLOR,避免禁用颜色输出
97
+ }
98
+ });
99
+
100
+ // 存储进程信息
101
+ runningProcesses.set(processId, {
102
+ childProcess,
103
+ command: command.trim(),
104
+ startTime,
105
+ directory: execDirectory
106
+ });
107
+ console.log(`[进程管理] 创建进程 #${processId}: ${command.substring(0, 50)}`);
108
+
109
+ // 发送数据到客户端的辅助函数
110
+ const sendData = (type, data) => {
111
+ const message = `data: ${JSON.stringify({ type, data })}\n\n`;
112
+ // console.log(`[流式输出] 发送数据 - 类型: ${type}, 长度: ${data?.length || 0}`);
113
+ res.write(message);
114
+ };
115
+
116
+ // 立即发送 processId 给前端
117
+ sendData('process_id', processId);
118
+
119
+ let outputReceived = false;
120
+
121
+ // 判断是否需要 GBK 转换
122
+ // 只有 Windows CMD 内置命令(如 dir、type 等)才需要 GBK 转换
123
+ // npm、node、git 等现代工具都输出 UTF-8
124
+ const isWindows = process.platform === 'win32';
125
+ const cmdBuiltins = ['dir', 'type', 'set', 'path', 'cd', 'md', 'rd', 'del', 'copy', 'move', 'ren'];
126
+
127
+ // echo 命令特殊处理:如果包含变量替换(如 {{xxx}}),说明内容可能已经是 UTF-8,不转换
128
+ const isEchoCommand = command.trim().toLowerCase().startsWith('echo ');
129
+ const hasVariableSubstitution = isEchoCommand && (
130
+ command.includes('{{') ||
131
+ command.includes('${') ||
132
+ command.includes('%') // Windows 环境变量
133
+ );
134
+
135
+ const needsGbkConversion = isWindows && (
136
+ cmdBuiltins.some(builtin =>
137
+ command.trim().toLowerCase().startsWith(builtin + ' ') ||
138
+ command.trim().toLowerCase() === builtin
139
+ ) || (isEchoCommand && !hasVariableSubstitution) // echo 只在没有变量替换时才转换
140
+ );
141
+
142
+ console.log(`[流式输出] 命令: ${command.substring(0, 50)}, 需要GBK转换: ${needsGbkConversion}`);
143
+
144
+ // 监听标准输出
145
+ childProcess.stdout?.on('data', (data) => {
146
+ // data 是 Buffer 对象
147
+ let output;
148
+ if (needsGbkConversion) {
149
+ // Windows CMD 内置命令,从 GBK 转换为 UTF-8
150
+ output = iconv.decode(data, 'gbk');
151
+ console.log(`[流式输出] 收到stdout(GBK转UTF8):`, output.substring(0, 200));
152
+ } else {
153
+ // 现代工具或 Unix 系统,直接使用 UTF-8
154
+ output = data.toString('utf8');
155
+ console.log(`[流式输出] 收到stdout(UTF8):`, output.substring(0, 200));
156
+ }
157
+ outputReceived = true;
158
+ collectedStdout += output; // 收集输出用于历史记录
159
+ sendData('stdout', output);
160
+ });
161
+
162
+ // 监听标准错误输出
163
+ childProcess.stderr?.on('data', (data) => {
164
+ // data 是 Buffer 对象
165
+ let output;
166
+
167
+ if (isWindows) {
168
+ // Windows 平台需要智能检测编码
169
+ // 先尝试 UTF-8 解码
170
+ const utf8Output = data.toString('utf8');
171
+
172
+ // 检测是否包含 UTF-8 替换字符(�),这通常表示解码失败
173
+ // 如果没有替换字符且包含正常字符,说明是有效的 UTF-8
174
+ if (!utf8Output.includes('�') || utf8Output.match(/[\u4e00-\u9fa5]/)) {
175
+ // UTF-8 解码成功(包含有效中文或没有替换字符)
176
+ output = utf8Output;
177
+ console.log(`[流式输出] 收到stderr(UTF8):`, output.substring(0, 200));
178
+ } else {
179
+ // UTF-8 解码失败,尝试 GBK(可能是 CMD shell 的系统消息)
180
+ try {
181
+ output = iconv.decode(data, 'gbk');
182
+ console.log(`[流式输出] 收到stderr(GBK转UTF8):`, output.substring(0, 200));
183
+ } catch (e) {
184
+ // GBK 也失败,使用原始 UTF-8 结果
185
+ output = utf8Output;
186
+ console.log(`[流式输出] GBK解码失败,使用UTF8:`, output.substring(0, 200));
187
+ }
188
+ }
189
+ } else {
190
+ // Unix系统,直接使用 UTF-8
191
+ output = data.toString('utf8');
192
+ console.log(`[流式输出] 收到stderr(UTF8):`, output.substring(0, 200));
193
+ }
194
+
195
+ outputReceived = true;
196
+ collectedStderr += output; // 收集错误输出用于历史记录
197
+ // 不再自动标记为错误,只显示 stderr 输出
198
+ // Git 的警告信息会输出到 stderr 但退出码仍为 0
199
+ sendData('stderr', output);
200
+ });
201
+
202
+ // 监听进程退出(exit 在流关闭前触发)
203
+ childProcess.on('exit', (code, signal) => {
204
+ // console.log(`[流式输出] 进程 exit 事件 - 代码: ${code}, 信号: ${signal}`);
205
+ });
206
+
207
+ // 监听进程关闭(close 在流关闭后触发)
208
+ childProcess.on('close', (code, signal) => {
209
+ // console.log(`[流式输出] 进程 close 事件 - 代码: ${code}, 信号: ${signal}, 有输出: ${outputReceived}`);
210
+
211
+ // 从运行进程列表中移除
212
+ runningProcesses.delete(processId);
213
+ console.log(`[进程管理] 进程 #${processId} 已结束,剩余进程数: ${runningProcesses.size}`);
214
+
215
+ // 计算执行时间
216
+ const executionTime = Date.now() - startTime;
217
+
218
+ // 添加到命令历史
219
+ const error = code !== 0 ? `Command exited with code ${code}` : null;
220
+ addCommandToHistory(
221
+ command.trim(),
222
+ collectedStdout,
223
+ collectedStderr,
224
+ error,
225
+ executionTime
226
+ );
227
+
228
+ // 只根据退出码判断成功与否,退出码为 0 表示成功
229
+ sendData('exit', { code, success: code === 0 });
230
+ res.end();
231
+ });
232
+
233
+ // 监听错误
234
+ childProcess.on('error', (error) => {
235
+ // console.error(`[流式输出] 进程错误:`, error);
236
+
237
+ // 从运行进程列表中移除
238
+ runningProcesses.delete(processId);
239
+ console.log(`[进程管理] 进程 #${processId} 出错并结束,剩余进程数: ${runningProcesses.size}`);
240
+
241
+ // 添加到命令历史(错误情况)
242
+ const executionTime = Date.now() - startTime;
243
+ addCommandToHistory(
244
+ command.trim(),
245
+ collectedStdout,
246
+ collectedStderr,
247
+ error.message,
248
+ executionTime
249
+ );
250
+
251
+ sendData('error', error.message);
252
+ res.end();
253
+ });
254
+
255
+ // 添加spawn事件监听
256
+ childProcess.on('spawn', () => {
257
+ // console.log(`[流式输出] 进程已启动 - PID: ${childProcess.pid}`);
258
+ });
259
+
260
+ // 注意:不监听req.on('close'),参考git push的实现
261
+ // 进程会自然结束,close事件会触发res.end()
262
+ // 如果监听req.on('close')可能会导致进程被提前kill
263
+
264
+ } catch (error) {
265
+ console.error('流式执行命令失败:', error);
266
+ res.status(500).json({
267
+ success: false,
268
+ error: `流式执行命令失败: ${error.message}`
269
+ });
270
+ }
271
+ });
272
+ }