zen-gitsync 2.6.5 → 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.
- package/README.md +148 -148
- package/package.json +2 -1
- package/src/ui/public/assets/index-B2e9R1rl.js +76 -0
- package/src/ui/public/assets/index-M7x7bOZu.css +1 -0
- package/src/ui/public/assets/vendor-CHpDISRi.js +75 -0
- package/src/ui/public/assets/vendor-HJmoQ7iQ.css +1 -0
- package/src/ui/public/index.html +4 -4
- package/src/ui/server/index.js +299 -0
- package/src/ui/public/assets/index-D0xeSSyV.js +0 -59
- package/src/ui/public/assets/index-DmFxzTUu.css +0 -1
- package/src/ui/public/assets/vendor-CiRPTLQ-.js +0 -68
- package/src/ui/public/assets/vendor-D9qDBEE1.css +0 -1
package/src/ui/public/index.html
CHANGED
|
@@ -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-
|
|
10
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-
|
|
11
|
-
<link rel="stylesheet" crossorigin href="/assets/vendor-
|
|
12
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-B2e9R1rl.js"></script>
|
|
10
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-CHpDISRi.js">
|
|
11
|
+
<link rel="stylesheet" crossorigin href="/assets/vendor-HJmoQ7iQ.css">
|
|
12
|
+
<link rel="stylesheet" crossorigin href="/assets/index-M7x7bOZu.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
15
15
|
<div id="app"></div>
|
package/src/ui/server/index.js
CHANGED
|
@@ -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 {
|
|
@@ -1569,6 +1698,176 @@ async function startUIServer(noOpen = false, savePort = false) {
|
|
|
1569
1698
|
}
|
|
1570
1699
|
})
|
|
1571
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
|
+
|
|
1572
1871
|
// 提交更改
|
|
1573
1872
|
app.post('/api/commit', express.json(), async (req, res) => {
|
|
1574
1873
|
try {
|