zen-gitsync 2.6.3 → 2.6.5
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/package.json +2 -2
- package/src/config.js +2 -1
- package/src/ui/public/assets/index-D0xeSSyV.js +59 -0
- package/src/ui/public/assets/index-DmFxzTUu.css +1 -0
- package/src/ui/public/assets/vendor-CiRPTLQ-.js +68 -0
- package/src/ui/public/index.html +3 -3
- package/src/ui/server/index.js +528 -212
- package/src/ui/public/assets/index-DhhwG1j0.css +0 -1
- package/src/ui/public/assets/index-DkDsLMia.js +0 -59
- package/src/ui/public/assets/vendor-DBEYYHfT.js +0 -68
package/src/ui/server/index.js
CHANGED
|
@@ -7,23 +7,15 @@ 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
14
|
// import { exec } from 'child_process';
|
|
15
15
|
|
|
16
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
17
17
|
const __dirname = path.dirname(__filename);
|
|
18
18
|
const configManager = config; // 确保 configManager 可用
|
|
19
|
-
|
|
20
|
-
// 文件系统变动监控器
|
|
21
|
-
let watcher = null;
|
|
22
|
-
// 防抖计时器
|
|
23
|
-
let debounceTimer = null;
|
|
24
|
-
// 防抖延迟时间 (毫秒)
|
|
25
|
-
const DEBOUNCE_DELAY = 1000;
|
|
26
|
-
|
|
27
19
|
// 分支状态缓存
|
|
28
20
|
let branchStatusCache = {
|
|
29
21
|
currentBranch: null,
|
|
@@ -156,6 +148,55 @@ async function startUIServer(noOpen = false, savePort = false) {
|
|
|
156
148
|
}
|
|
157
149
|
});
|
|
158
150
|
|
|
151
|
+
// 在新终端中执行自定义命令
|
|
152
|
+
app.post('/api/exec-in-terminal', async (req, res) => {
|
|
153
|
+
try {
|
|
154
|
+
const { command } = req.body || {};
|
|
155
|
+
if (!command || typeof command !== 'string' || !command.trim()) {
|
|
156
|
+
return res.status(400).json({ success: false, error: 'command 不能为空' });
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log(`在终端中执行命令: ${command}`);
|
|
160
|
+
|
|
161
|
+
// 根据操作系统选择合适的终端命令
|
|
162
|
+
let terminalCommand;
|
|
163
|
+
|
|
164
|
+
if (process.platform === 'win32') {
|
|
165
|
+
// Windows: 使用 start 命令打开新的 cmd 窗口
|
|
166
|
+
// /K 参数表示执行命令后保持窗口打开
|
|
167
|
+
terminalCommand = `start cmd /K "cd /d ${currentProjectPath} && ${command}"`;
|
|
168
|
+
} else if (process.platform === 'darwin') {
|
|
169
|
+
// macOS: 使用 osascript 打开 Terminal.app
|
|
170
|
+
const script = `tell application "Terminal" to do script "cd ${currentProjectPath} && ${command}"`;
|
|
171
|
+
terminalCommand = `osascript -e '${script}'`;
|
|
172
|
+
} else {
|
|
173
|
+
// Linux: 尝试常见的终端模拟器
|
|
174
|
+
terminalCommand = `gnome-terminal -- bash -c "cd ${currentProjectPath} && ${command}; exec bash" || xterm -e "cd ${currentProjectPath} && ${command}; bash"`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// 执行命令打开新终端
|
|
178
|
+
const { exec } = await import('child_process');
|
|
179
|
+
exec(terminalCommand, (error, stdout, stderr) => {
|
|
180
|
+
if (error) {
|
|
181
|
+
console.error('打开终端失败:', error);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
res.json({
|
|
186
|
+
success: true,
|
|
187
|
+
message: `已在新终端中执行命令`,
|
|
188
|
+
command: command,
|
|
189
|
+
path: currentProjectPath
|
|
190
|
+
});
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error('在终端中执行命令失败:', error);
|
|
193
|
+
res.status(500).json({
|
|
194
|
+
success: false,
|
|
195
|
+
error: `在终端中执行命令失败: ${error.message}`
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
159
200
|
// API路由
|
|
160
201
|
// 移除了 /api/status 端点,因为前端只使用 porcelain 格式
|
|
161
202
|
|
|
@@ -612,12 +653,6 @@ async function startUIServer(noOpen = false, savePort = false) {
|
|
|
612
653
|
console.warn('初始化项目配置失败:', e?.message || e);
|
|
613
654
|
}
|
|
614
655
|
|
|
615
|
-
// 关闭旧的文件监控
|
|
616
|
-
if (watcher) {
|
|
617
|
-
watcher.close().catch(err => console.error('关闭旧监控器失败:', err));
|
|
618
|
-
watcher = null;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
656
|
// 通知所有旧房间的客户端项目已切换
|
|
622
657
|
io.to(projectRoomId).emit('project_changed', {
|
|
623
658
|
oldProjectPath: currentProjectPath,
|
|
@@ -645,11 +680,6 @@ async function startUIServer(noOpen = false, savePort = false) {
|
|
|
645
680
|
projectRoomId = newProjectRoomId;
|
|
646
681
|
isGitRepo = false;
|
|
647
682
|
|
|
648
|
-
if (watcher) {
|
|
649
|
-
watcher.close().catch(err => console.error('关闭监控器失败:', err));
|
|
650
|
-
watcher = null;
|
|
651
|
-
}
|
|
652
|
-
|
|
653
683
|
// 通知所有旧房间的客户端项目已切换
|
|
654
684
|
io.to(projectRoomId).emit('project_changed', {
|
|
655
685
|
oldProjectPath: currentProjectPath,
|
|
@@ -1397,6 +1427,148 @@ async function startUIServer(noOpen = false, savePort = false) {
|
|
|
1397
1427
|
}
|
|
1398
1428
|
})
|
|
1399
1429
|
|
|
1430
|
+
// 置顶模板
|
|
1431
|
+
app.post('/api/config/pin-template', express.json(), async (req, res) => {
|
|
1432
|
+
try {
|
|
1433
|
+
const { template, type } = req.body
|
|
1434
|
+
|
|
1435
|
+
if (!template || !type) {
|
|
1436
|
+
return res.status(400).json({ success: false, error: '缺少必要参数' })
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
const config = await configManager.loadConfig()
|
|
1440
|
+
|
|
1441
|
+
if (type === 'description') {
|
|
1442
|
+
if (config.descriptionTemplates) {
|
|
1443
|
+
// 删除原位置的模板
|
|
1444
|
+
config.descriptionTemplates = config.descriptionTemplates.filter(t => t !== template)
|
|
1445
|
+
// 添加到第一位
|
|
1446
|
+
config.descriptionTemplates.unshift(template)
|
|
1447
|
+
await configManager.saveConfig(config)
|
|
1448
|
+
} else {
|
|
1449
|
+
return res.status(404).json({ success: false, error: '模板列表不存在' })
|
|
1450
|
+
}
|
|
1451
|
+
} else if (type === 'scope') {
|
|
1452
|
+
if (config.scopeTemplates) {
|
|
1453
|
+
config.scopeTemplates = config.scopeTemplates.filter(t => t !== template)
|
|
1454
|
+
config.scopeTemplates.unshift(template)
|
|
1455
|
+
await configManager.saveConfig(config)
|
|
1456
|
+
} else {
|
|
1457
|
+
return res.status(404).json({ success: false, error: '模板列表不存在' })
|
|
1458
|
+
}
|
|
1459
|
+
} else if (type === 'message') {
|
|
1460
|
+
if (config.messageTemplates) {
|
|
1461
|
+
config.messageTemplates = config.messageTemplates.filter(t => t !== template)
|
|
1462
|
+
config.messageTemplates.unshift(template)
|
|
1463
|
+
await configManager.saveConfig(config)
|
|
1464
|
+
} else {
|
|
1465
|
+
return res.status(404).json({ success: false, error: '模板列表不存在' })
|
|
1466
|
+
}
|
|
1467
|
+
} else {
|
|
1468
|
+
return res.status(400).json({ success: false, error: '不支持的模板类型' })
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
res.json({ success: true })
|
|
1472
|
+
} catch (error) {
|
|
1473
|
+
res.status(500).json({ success: false, error: error.message })
|
|
1474
|
+
}
|
|
1475
|
+
})
|
|
1476
|
+
|
|
1477
|
+
// 保存自定义命令
|
|
1478
|
+
app.post('/api/config/save-custom-command', express.json(), async (req, res) => {
|
|
1479
|
+
try {
|
|
1480
|
+
const { command } = req.body
|
|
1481
|
+
|
|
1482
|
+
if (!command || !command.name || !command.command) {
|
|
1483
|
+
return res.status(400).json({ success: false, error: '缺少必要参数' })
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
const config = await configManager.loadConfig()
|
|
1487
|
+
|
|
1488
|
+
// 确保自定义命令数组存在
|
|
1489
|
+
if (!Array.isArray(config.customCommands)) {
|
|
1490
|
+
config.customCommands = []
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
// 生成唯一ID
|
|
1494
|
+
const id = `cmd_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
|
1495
|
+
const newCommand = {
|
|
1496
|
+
id,
|
|
1497
|
+
name: command.name,
|
|
1498
|
+
description: command.description || '',
|
|
1499
|
+
directory: command.directory || '',
|
|
1500
|
+
command: command.command
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
config.customCommands.push(newCommand)
|
|
1504
|
+
await configManager.saveConfig(config)
|
|
1505
|
+
|
|
1506
|
+
res.json({ success: true, command: newCommand })
|
|
1507
|
+
} catch (error) {
|
|
1508
|
+
res.status(500).json({ success: false, error: error.message })
|
|
1509
|
+
}
|
|
1510
|
+
})
|
|
1511
|
+
|
|
1512
|
+
// 删除自定义命令
|
|
1513
|
+
app.post('/api/config/delete-custom-command', express.json(), async (req, res) => {
|
|
1514
|
+
try {
|
|
1515
|
+
const { id } = req.body
|
|
1516
|
+
|
|
1517
|
+
if (!id) {
|
|
1518
|
+
return res.status(400).json({ success: false, error: '缺少命令ID参数' })
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
const config = await configManager.loadConfig()
|
|
1522
|
+
|
|
1523
|
+
if (Array.isArray(config.customCommands)) {
|
|
1524
|
+
const index = config.customCommands.findIndex(cmd => cmd.id === id)
|
|
1525
|
+
if (index !== -1) {
|
|
1526
|
+
config.customCommands.splice(index, 1)
|
|
1527
|
+
await configManager.saveConfig(config)
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
res.json({ success: true })
|
|
1532
|
+
} catch (error) {
|
|
1533
|
+
res.status(500).json({ success: false, error: error.message })
|
|
1534
|
+
}
|
|
1535
|
+
})
|
|
1536
|
+
|
|
1537
|
+
// 更新自定义命令
|
|
1538
|
+
app.post('/api/config/update-custom-command', express.json(), async (req, res) => {
|
|
1539
|
+
try {
|
|
1540
|
+
const { id, command } = req.body
|
|
1541
|
+
|
|
1542
|
+
if (!id || !command || !command.name || !command.command) {
|
|
1543
|
+
return res.status(400).json({ success: false, error: '缺少必要参数' })
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
const config = await configManager.loadConfig()
|
|
1547
|
+
|
|
1548
|
+
if (Array.isArray(config.customCommands)) {
|
|
1549
|
+
const index = config.customCommands.findIndex(cmd => cmd.id === id)
|
|
1550
|
+
if (index !== -1) {
|
|
1551
|
+
config.customCommands[index] = {
|
|
1552
|
+
id,
|
|
1553
|
+
name: command.name,
|
|
1554
|
+
description: command.description || '',
|
|
1555
|
+
directory: command.directory || '',
|
|
1556
|
+
command: command.command
|
|
1557
|
+
}
|
|
1558
|
+
await configManager.saveConfig(config)
|
|
1559
|
+
} else {
|
|
1560
|
+
return res.status(404).json({ success: false, error: '未找到指定命令' })
|
|
1561
|
+
}
|
|
1562
|
+
} else {
|
|
1563
|
+
return res.status(404).json({ success: false, error: '命令列表不存在' })
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
res.json({ success: true })
|
|
1567
|
+
} catch (error) {
|
|
1568
|
+
res.status(500).json({ success: false, error: error.message })
|
|
1569
|
+
}
|
|
1570
|
+
})
|
|
1571
|
+
|
|
1400
1572
|
// 提交更改
|
|
1401
1573
|
app.post('/api/commit', express.json(), async (req, res) => {
|
|
1402
1574
|
try {
|
|
@@ -3225,7 +3397,8 @@ async function startUIServer(noOpen = false, savePort = false) {
|
|
|
3225
3397
|
path: dir,
|
|
3226
3398
|
relativePath: relativePath || '.',
|
|
3227
3399
|
name: packageData.name || path.basename(dir),
|
|
3228
|
-
scripts: packageData.scripts
|
|
3400
|
+
scripts: packageData.scripts,
|
|
3401
|
+
version: packageData.version || '0.0.0'
|
|
3229
3402
|
});
|
|
3230
3403
|
return true;
|
|
3231
3404
|
}
|
|
@@ -3411,6 +3584,340 @@ async function startUIServer(noOpen = false, savePort = false) {
|
|
|
3411
3584
|
});
|
|
3412
3585
|
}
|
|
3413
3586
|
});
|
|
3587
|
+
|
|
3588
|
+
// API: 更新npm版本号
|
|
3589
|
+
app.post('/api/update-npm-version', async (req, res) => {
|
|
3590
|
+
try {
|
|
3591
|
+
const { packagePath, versionType } = req.body;
|
|
3592
|
+
|
|
3593
|
+
if (!packagePath || !versionType) {
|
|
3594
|
+
return res.status(400).json({
|
|
3595
|
+
success: false,
|
|
3596
|
+
error: '缺少必要参数: packagePath, versionType'
|
|
3597
|
+
});
|
|
3598
|
+
}
|
|
3599
|
+
|
|
3600
|
+
// 确保路径指向package.json文件
|
|
3601
|
+
let packageJsonPath = path.resolve(packagePath);
|
|
3602
|
+
if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
|
|
3603
|
+
packageJsonPath = path.join(packageJsonPath, 'package.json');
|
|
3604
|
+
}
|
|
3605
|
+
|
|
3606
|
+
// 检查文件是否存在
|
|
3607
|
+
if (!fsSync.existsSync(packageJsonPath)) {
|
|
3608
|
+
return res.status(404).json({
|
|
3609
|
+
success: false,
|
|
3610
|
+
error: '找不到package.json文件'
|
|
3611
|
+
});
|
|
3612
|
+
}
|
|
3613
|
+
|
|
3614
|
+
// 读取package.json
|
|
3615
|
+
const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
|
|
3616
|
+
|
|
3617
|
+
if (!packageJson.version) {
|
|
3618
|
+
return res.status(400).json({
|
|
3619
|
+
success: false,
|
|
3620
|
+
error: 'package.json中没有version字段'
|
|
3621
|
+
});
|
|
3622
|
+
}
|
|
3623
|
+
|
|
3624
|
+
const oldVersion = packageJson.version;
|
|
3625
|
+
const versionParts = oldVersion.split('.').map(Number);
|
|
3626
|
+
|
|
3627
|
+
// 根据类型增加版本号
|
|
3628
|
+
switch (versionType) {
|
|
3629
|
+
case 'major':
|
|
3630
|
+
versionParts[0]++;
|
|
3631
|
+
versionParts[1] = 0;
|
|
3632
|
+
versionParts[2] = 0;
|
|
3633
|
+
break;
|
|
3634
|
+
case 'minor':
|
|
3635
|
+
versionParts[1]++;
|
|
3636
|
+
versionParts[2] = 0;
|
|
3637
|
+
break;
|
|
3638
|
+
case 'patch':
|
|
3639
|
+
versionParts[2]++;
|
|
3640
|
+
break;
|
|
3641
|
+
default:
|
|
3642
|
+
return res.status(400).json({
|
|
3643
|
+
success: false,
|
|
3644
|
+
error: '无效的版本类型,必须是 major, minor 或 patch'
|
|
3645
|
+
});
|
|
3646
|
+
}
|
|
3647
|
+
|
|
3648
|
+
const newVersion = versionParts.join('.');
|
|
3649
|
+
packageJson.version = newVersion;
|
|
3650
|
+
|
|
3651
|
+
// 写回文件
|
|
3652
|
+
fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
|
|
3653
|
+
|
|
3654
|
+
console.log(`已更新npm版本号: ${oldVersion} → ${newVersion} (${packagePath})`);
|
|
3655
|
+
|
|
3656
|
+
res.json({
|
|
3657
|
+
success: true,
|
|
3658
|
+
oldVersion,
|
|
3659
|
+
newVersion
|
|
3660
|
+
});
|
|
3661
|
+
} catch (error) {
|
|
3662
|
+
console.error('更新版本号失败:', error);
|
|
3663
|
+
res.status(500).json({
|
|
3664
|
+
success: false,
|
|
3665
|
+
error: `更新版本号失败: ${error.message}`
|
|
3666
|
+
});
|
|
3667
|
+
}
|
|
3668
|
+
});
|
|
3669
|
+
|
|
3670
|
+
// API: 添加npm脚本
|
|
3671
|
+
app.post('/api/add-npm-script', async (req, res) => {
|
|
3672
|
+
try {
|
|
3673
|
+
const { packagePath, scriptName, scriptCommand } = req.body;
|
|
3674
|
+
|
|
3675
|
+
if (!packagePath || !scriptName || !scriptCommand) {
|
|
3676
|
+
return res.status(400).json({
|
|
3677
|
+
success: false,
|
|
3678
|
+
error: '缺少必要参数: packagePath, scriptName, scriptCommand'
|
|
3679
|
+
});
|
|
3680
|
+
}
|
|
3681
|
+
|
|
3682
|
+
// 确保路径指向package.json文件
|
|
3683
|
+
let packageJsonPath = path.resolve(packagePath);
|
|
3684
|
+
if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
|
|
3685
|
+
packageJsonPath = path.join(packageJsonPath, 'package.json');
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
// 检查文件是否存在
|
|
3689
|
+
if (!fsSync.existsSync(packageJsonPath)) {
|
|
3690
|
+
return res.status(404).json({
|
|
3691
|
+
success: false,
|
|
3692
|
+
error: '找不到package.json文件'
|
|
3693
|
+
});
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3696
|
+
// 读取package.json
|
|
3697
|
+
const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
|
|
3698
|
+
|
|
3699
|
+
// 确保scripts对象存在
|
|
3700
|
+
if (!packageJson.scripts) {
|
|
3701
|
+
packageJson.scripts = {};
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
// 检查脚本是否已存在
|
|
3705
|
+
if (packageJson.scripts[scriptName]) {
|
|
3706
|
+
return res.status(400).json({
|
|
3707
|
+
success: false,
|
|
3708
|
+
error: `脚本 "${scriptName}" 已存在`
|
|
3709
|
+
});
|
|
3710
|
+
}
|
|
3711
|
+
|
|
3712
|
+
// 添加脚本
|
|
3713
|
+
packageJson.scripts[scriptName] = scriptCommand;
|
|
3714
|
+
|
|
3715
|
+
// 写回文件(保持格式化)
|
|
3716
|
+
fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
|
|
3717
|
+
|
|
3718
|
+
console.log(`已添加npm脚本: ${scriptName} = ${scriptCommand} (${packagePath})`);
|
|
3719
|
+
|
|
3720
|
+
res.json({
|
|
3721
|
+
success: true,
|
|
3722
|
+
scriptName,
|
|
3723
|
+
scriptCommand
|
|
3724
|
+
});
|
|
3725
|
+
} catch (error) {
|
|
3726
|
+
console.error('添加npm脚本失败:', error);
|
|
3727
|
+
res.status(500).json({
|
|
3728
|
+
success: false,
|
|
3729
|
+
error: `添加npm脚本失败: ${error.message}`
|
|
3730
|
+
});
|
|
3731
|
+
}
|
|
3732
|
+
});
|
|
3733
|
+
|
|
3734
|
+
// API: 更新npm脚本
|
|
3735
|
+
app.post('/api/update-npm-script', async (req, res) => {
|
|
3736
|
+
try {
|
|
3737
|
+
const { packagePath, scriptName, scriptCommand, oldScriptName } = req.body;
|
|
3738
|
+
|
|
3739
|
+
if (!packagePath || !scriptName || !scriptCommand) {
|
|
3740
|
+
return res.status(400).json({
|
|
3741
|
+
success: false,
|
|
3742
|
+
error: '缺少必要参数: packagePath, scriptName, scriptCommand'
|
|
3743
|
+
});
|
|
3744
|
+
}
|
|
3745
|
+
|
|
3746
|
+
// 确保路径指向package.json文件
|
|
3747
|
+
let packageJsonPath = path.resolve(packagePath);
|
|
3748
|
+
if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
|
|
3749
|
+
packageJsonPath = path.join(packageJsonPath, 'package.json');
|
|
3750
|
+
}
|
|
3751
|
+
|
|
3752
|
+
// 检查文件是否存在
|
|
3753
|
+
if (!fsSync.existsSync(packageJsonPath)) {
|
|
3754
|
+
return res.status(404).json({
|
|
3755
|
+
success: false,
|
|
3756
|
+
error: '找不到package.json文件'
|
|
3757
|
+
});
|
|
3758
|
+
}
|
|
3759
|
+
|
|
3760
|
+
// 读取package.json
|
|
3761
|
+
const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
|
|
3762
|
+
|
|
3763
|
+
// 确保scripts对象存在
|
|
3764
|
+
if (!packageJson.scripts) {
|
|
3765
|
+
packageJson.scripts = {};
|
|
3766
|
+
}
|
|
3767
|
+
|
|
3768
|
+
// 如果改了脚本名称,删除旧的
|
|
3769
|
+
if (oldScriptName && oldScriptName !== scriptName) {
|
|
3770
|
+
delete packageJson.scripts[oldScriptName];
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3773
|
+
// 更新脚本
|
|
3774
|
+
packageJson.scripts[scriptName] = scriptCommand;
|
|
3775
|
+
|
|
3776
|
+
// 写回文件
|
|
3777
|
+
fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
|
|
3778
|
+
|
|
3779
|
+
console.log(`已更新npm脚本: ${scriptName} = ${scriptCommand} (${packagePath})`);
|
|
3780
|
+
|
|
3781
|
+
res.json({
|
|
3782
|
+
success: true,
|
|
3783
|
+
scriptName,
|
|
3784
|
+
scriptCommand
|
|
3785
|
+
});
|
|
3786
|
+
} catch (error) {
|
|
3787
|
+
console.error('更新npm脚本失败:', error);
|
|
3788
|
+
res.status(500).json({
|
|
3789
|
+
success: false,
|
|
3790
|
+
error: `更新npm脚本失败: ${error.message}`
|
|
3791
|
+
});
|
|
3792
|
+
}
|
|
3793
|
+
});
|
|
3794
|
+
|
|
3795
|
+
// API: 删除npm脚本
|
|
3796
|
+
app.post('/api/delete-npm-script', async (req, res) => {
|
|
3797
|
+
try {
|
|
3798
|
+
const { packagePath, scriptName } = req.body;
|
|
3799
|
+
|
|
3800
|
+
if (!packagePath || !scriptName) {
|
|
3801
|
+
return res.status(400).json({
|
|
3802
|
+
success: false,
|
|
3803
|
+
error: '缺少必要参数: packagePath, scriptName'
|
|
3804
|
+
});
|
|
3805
|
+
}
|
|
3806
|
+
|
|
3807
|
+
// 确保路径指向package.json文件
|
|
3808
|
+
let packageJsonPath = path.resolve(packagePath);
|
|
3809
|
+
if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
|
|
3810
|
+
packageJsonPath = path.join(packageJsonPath, 'package.json');
|
|
3811
|
+
}
|
|
3812
|
+
|
|
3813
|
+
// 检查文件是否存在
|
|
3814
|
+
if (!fsSync.existsSync(packageJsonPath)) {
|
|
3815
|
+
return res.status(404).json({
|
|
3816
|
+
success: false,
|
|
3817
|
+
error: '找不到package.json文件'
|
|
3818
|
+
});
|
|
3819
|
+
}
|
|
3820
|
+
|
|
3821
|
+
// 读取package.json
|
|
3822
|
+
const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
|
|
3823
|
+
|
|
3824
|
+
// 检查scripts对象和脚本是否存在
|
|
3825
|
+
if (!packageJson.scripts || !packageJson.scripts[scriptName]) {
|
|
3826
|
+
return res.status(404).json({
|
|
3827
|
+
success: false,
|
|
3828
|
+
error: `脚本 "${scriptName}" 不存在`
|
|
3829
|
+
});
|
|
3830
|
+
}
|
|
3831
|
+
|
|
3832
|
+
// 删除脚本
|
|
3833
|
+
delete packageJson.scripts[scriptName];
|
|
3834
|
+
|
|
3835
|
+
// 写回文件
|
|
3836
|
+
fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
|
|
3837
|
+
|
|
3838
|
+
console.log(`已删除npm脚本: ${scriptName} (${packagePath})`);
|
|
3839
|
+
|
|
3840
|
+
res.json({
|
|
3841
|
+
success: true,
|
|
3842
|
+
scriptName
|
|
3843
|
+
});
|
|
3844
|
+
} catch (error) {
|
|
3845
|
+
console.error('删除npm脚本失败:', error);
|
|
3846
|
+
res.status(500).json({
|
|
3847
|
+
success: false,
|
|
3848
|
+
error: `删除npm脚本失败: ${error.message}`
|
|
3849
|
+
});
|
|
3850
|
+
}
|
|
3851
|
+
});
|
|
3852
|
+
|
|
3853
|
+
// API: 置顶npm脚本
|
|
3854
|
+
app.post('/api/pin-npm-script', async (req, res) => {
|
|
3855
|
+
try {
|
|
3856
|
+
const { packagePath, scriptName } = req.body;
|
|
3857
|
+
|
|
3858
|
+
if (!packagePath || !scriptName) {
|
|
3859
|
+
return res.status(400).json({
|
|
3860
|
+
success: false,
|
|
3861
|
+
error: '缺少必要参数: packagePath, scriptName'
|
|
3862
|
+
});
|
|
3863
|
+
}
|
|
3864
|
+
|
|
3865
|
+
// 确保路径指向package.json文件
|
|
3866
|
+
let packageJsonPath = path.resolve(packagePath);
|
|
3867
|
+
if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
|
|
3868
|
+
packageJsonPath = path.join(packageJsonPath, 'package.json');
|
|
3869
|
+
}
|
|
3870
|
+
|
|
3871
|
+
// 检查文件是否存在
|
|
3872
|
+
if (!fsSync.existsSync(packageJsonPath)) {
|
|
3873
|
+
return res.status(404).json({
|
|
3874
|
+
success: false,
|
|
3875
|
+
error: '找不到package.json文件'
|
|
3876
|
+
});
|
|
3877
|
+
}
|
|
3878
|
+
|
|
3879
|
+
// 读取package.json
|
|
3880
|
+
const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
|
|
3881
|
+
|
|
3882
|
+
// 检查scripts对象和脚本是否存在
|
|
3883
|
+
if (!packageJson.scripts || !packageJson.scripts[scriptName]) {
|
|
3884
|
+
return res.status(404).json({
|
|
3885
|
+
success: false,
|
|
3886
|
+
error: `脚本 "${scriptName}" 不存在`
|
|
3887
|
+
});
|
|
3888
|
+
}
|
|
3889
|
+
|
|
3890
|
+
// 保存要置顶的脚本内容
|
|
3891
|
+
const scriptCommand = packageJson.scripts[scriptName];
|
|
3892
|
+
|
|
3893
|
+
// 删除该脚本
|
|
3894
|
+
delete packageJson.scripts[scriptName];
|
|
3895
|
+
|
|
3896
|
+
// 创建新的scripts对象,将置顶脚本放在最前面
|
|
3897
|
+
const newScripts = {
|
|
3898
|
+
[scriptName]: scriptCommand,
|
|
3899
|
+
...packageJson.scripts
|
|
3900
|
+
};
|
|
3901
|
+
|
|
3902
|
+
packageJson.scripts = newScripts;
|
|
3903
|
+
|
|
3904
|
+
// 写回文件
|
|
3905
|
+
fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
|
|
3906
|
+
|
|
3907
|
+
console.log(`已置顶npm脚本: ${scriptName} (${packagePath})`);
|
|
3908
|
+
|
|
3909
|
+
res.json({
|
|
3910
|
+
success: true,
|
|
3911
|
+
scriptName
|
|
3912
|
+
});
|
|
3913
|
+
} catch (error) {
|
|
3914
|
+
console.error('置顶npm脚本失败:', error);
|
|
3915
|
+
res.status(500).json({
|
|
3916
|
+
success: false,
|
|
3917
|
+
error: `置顶npm脚本失败: ${error.message}`
|
|
3918
|
+
});
|
|
3919
|
+
}
|
|
3920
|
+
});
|
|
3414
3921
|
|
|
3415
3922
|
// Socket.io 实时更新
|
|
3416
3923
|
io.on('connection', (socket) => {
|
|
@@ -3427,34 +3934,6 @@ async function startUIServer(noOpen = false, savePort = false) {
|
|
|
3427
3934
|
const history = getCommandHistory();
|
|
3428
3935
|
socket.emit('initial_command_history', { history });
|
|
3429
3936
|
|
|
3430
|
-
// 发送项目信息给客户端
|
|
3431
|
-
socket.emit('project_info', {
|
|
3432
|
-
projectPath: currentProjectPath,
|
|
3433
|
-
projectRoomId: projectRoomId
|
|
3434
|
-
});
|
|
3435
|
-
|
|
3436
|
-
// 客户端可以请求开始/停止监控
|
|
3437
|
-
socket.on('start_monitoring', () => {
|
|
3438
|
-
if (!watcher) {
|
|
3439
|
-
initFileSystemWatcher().catch(err => console.error('[文件监控] 初始化失败:', err));
|
|
3440
|
-
socket.emit('monitoring_status', { active: true });
|
|
3441
|
-
}
|
|
3442
|
-
});
|
|
3443
|
-
|
|
3444
|
-
// 处理客户端加入新房间的请求
|
|
3445
|
-
socket.on('join_room', (roomId) => {
|
|
3446
|
-
socket.join(roomId);
|
|
3447
|
-
console.log(`客户端 ${socket.id} 已加入房间: ${roomId}`);
|
|
3448
|
-
});
|
|
3449
|
-
|
|
3450
|
-
socket.on('stop_monitoring', () => {
|
|
3451
|
-
if (watcher) {
|
|
3452
|
-
watcher.close().catch(err => console.error('关闭监控器失败:', err));
|
|
3453
|
-
watcher = null;
|
|
3454
|
-
socket.emit('monitoring_status', { active: false });
|
|
3455
|
-
}
|
|
3456
|
-
});
|
|
3457
|
-
|
|
3458
3937
|
// 请求完整命令历史
|
|
3459
3938
|
socket.on('request_full_history', () => {
|
|
3460
3939
|
const fullHistory = getCommandHistory();
|
|
@@ -3474,158 +3953,6 @@ async function startUIServer(noOpen = false, savePort = false) {
|
|
|
3474
3953
|
});
|
|
3475
3954
|
});
|
|
3476
3955
|
|
|
3477
|
-
// 读取并解析.gitignore文件
|
|
3478
|
-
async function parseGitignore(projectPath) {
|
|
3479
|
-
const gitignorePath = path.join(projectPath, '.gitignore');
|
|
3480
|
-
const ignorePatterns = [
|
|
3481
|
-
/(^|[\/\\])\../, // 始终忽略.开头的文件(除了.gitignore本身)
|
|
3482
|
-
'**/.git/**', // 始终忽略.git目录
|
|
3483
|
-
|
|
3484
|
-
// 额外排除常见的编译产物和大文件,减少监控开销
|
|
3485
|
-
'**/*.umd.cjs', // UMD打包文件
|
|
3486
|
-
'**/*.min.js', // 压缩JS文件
|
|
3487
|
-
'**/*.bundle.js', // Webpack打包文件
|
|
3488
|
-
'**/*.dist.js', // 构建产物
|
|
3489
|
-
'**/*.prod.js', // 生产环境文件
|
|
3490
|
-
'**/lib/**', // 通常是编译产物
|
|
3491
|
-
'**/es/**', // ES模块编译产物
|
|
3492
|
-
'**/esm/**', // ES模块编译产物
|
|
3493
|
-
'**/*.map', // Source map文件
|
|
3494
|
-
'**/*.chunk.js', // 代码分割chunk
|
|
3495
|
-
];
|
|
3496
|
-
|
|
3497
|
-
try {
|
|
3498
|
-
const gitignoreContent = await fs.readFile(gitignorePath, 'utf8');
|
|
3499
|
-
const lines = gitignoreContent.split('\n');
|
|
3500
|
-
let validRules = 0;
|
|
3501
|
-
|
|
3502
|
-
for (let line of lines) {
|
|
3503
|
-
line = line.trim();
|
|
3504
|
-
|
|
3505
|
-
// 跳过空行和注释
|
|
3506
|
-
if (!line || line.startsWith('#')) continue;
|
|
3507
|
-
|
|
3508
|
-
// 移除行尾的空格
|
|
3509
|
-
line = line.replace(/\s+$/, '');
|
|
3510
|
-
|
|
3511
|
-
// 跳过否定规则(chokidar不支持否定规则,这些规则会被忽略)
|
|
3512
|
-
if (line.startsWith('!')) {
|
|
3513
|
-
continue;
|
|
3514
|
-
}
|
|
3515
|
-
|
|
3516
|
-
// 将gitignore规则转换为glob模式
|
|
3517
|
-
let pattern;
|
|
3518
|
-
|
|
3519
|
-
// 如果以/开头,表示从根目录开始匹配
|
|
3520
|
-
if (line.startsWith('/')) {
|
|
3521
|
-
pattern = line.substring(1);
|
|
3522
|
-
// 如果是目录,添加/**后缀
|
|
3523
|
-
if (!pattern.includes('*') && !pattern.includes('.')) {
|
|
3524
|
-
ignorePatterns.push(pattern);
|
|
3525
|
-
ignorePatterns.push(pattern + '/**');
|
|
3526
|
-
} else {
|
|
3527
|
-
ignorePatterns.push(pattern);
|
|
3528
|
-
}
|
|
3529
|
-
} else if (line.endsWith('/')) {
|
|
3530
|
-
// 明确的目录规则
|
|
3531
|
-
const dirName = line.slice(0, -1);
|
|
3532
|
-
ignorePatterns.push('**/' + dirName);
|
|
3533
|
-
ignorePatterns.push('**/' + dirName + '/**');
|
|
3534
|
-
} else {
|
|
3535
|
-
// 文件或目录规则
|
|
3536
|
-
// 如果包含*通配符,直接使用
|
|
3537
|
-
if (line.includes('*')) {
|
|
3538
|
-
ignorePatterns.push('**/' + line);
|
|
3539
|
-
} else {
|
|
3540
|
-
// 既匹配文件也匹配目录
|
|
3541
|
-
ignorePatterns.push('**/' + line);
|
|
3542
|
-
ignorePatterns.push('**/' + line + '/**');
|
|
3543
|
-
}
|
|
3544
|
-
}
|
|
3545
|
-
|
|
3546
|
-
validRules++;
|
|
3547
|
-
}
|
|
3548
|
-
|
|
3549
|
-
console.log(`[文件监控] 从.gitignore读取了 ${validRules} 条有效的忽略规则`);
|
|
3550
|
-
} catch (error) {
|
|
3551
|
-
// .gitignore不存在或读取失败,使用默认规则
|
|
3552
|
-
console.log('[文件监控] 未找到.gitignore,使用默认忽略规则');
|
|
3553
|
-
ignorePatterns.push(
|
|
3554
|
-
'**/node_modules/**',
|
|
3555
|
-
'**/dist/**',
|
|
3556
|
-
'**/build/**',
|
|
3557
|
-
'**/coverage/**',
|
|
3558
|
-
'**/.nuxt/**',
|
|
3559
|
-
'**/.next/**',
|
|
3560
|
-
'**/out/**',
|
|
3561
|
-
'**/*.log'
|
|
3562
|
-
);
|
|
3563
|
-
}
|
|
3564
|
-
|
|
3565
|
-
return ignorePatterns;
|
|
3566
|
-
}
|
|
3567
|
-
|
|
3568
|
-
// 初始化文件系统监控
|
|
3569
|
-
async function initFileSystemWatcher() {
|
|
3570
|
-
// 停止已有的监控器
|
|
3571
|
-
if (watcher) {
|
|
3572
|
-
watcher.close().catch(err => console.error('关闭旧监控器失败:', err));
|
|
3573
|
-
}
|
|
3574
|
-
|
|
3575
|
-
try {
|
|
3576
|
-
// 获取当前工作目录
|
|
3577
|
-
const currentDir = process.cwd();
|
|
3578
|
-
|
|
3579
|
-
console.log(`初始化文件系统监控器,路径: ${currentDir}`);
|
|
3580
|
-
|
|
3581
|
-
// 检查是否是Git仓库
|
|
3582
|
-
if (!isGitRepo) {
|
|
3583
|
-
console.log('当前目录不是Git仓库,不启动监控');
|
|
3584
|
-
return;
|
|
3585
|
-
}
|
|
3586
|
-
|
|
3587
|
-
const watcherStartTime = Date.now();
|
|
3588
|
-
console.log('[文件监控] 开始初始化监控器...');
|
|
3589
|
-
|
|
3590
|
-
// 从.gitignore读取忽略规则
|
|
3591
|
-
const ignorePatterns = await parseGitignore(currentDir);
|
|
3592
|
-
|
|
3593
|
-
// 使用chokidar监控文件变动
|
|
3594
|
-
watcher = chokidar.watch(currentDir, {
|
|
3595
|
-
ignored: ignorePatterns,
|
|
3596
|
-
persistent: true,
|
|
3597
|
-
ignoreInitial: true, // 忽略初始扫描时的文件
|
|
3598
|
-
depth: 10, // 限制扫描深度,避免过深的目录结构
|
|
3599
|
-
awaitWriteFinish: {
|
|
3600
|
-
stabilityThreshold: 300, // 等待文件写入完成的时间
|
|
3601
|
-
pollInterval: 100 // 轮询间隔
|
|
3602
|
-
}
|
|
3603
|
-
});
|
|
3604
|
-
|
|
3605
|
-
// 合并所有变动事件到一个处理程序
|
|
3606
|
-
const events = ['add', 'change', 'unlink'];
|
|
3607
|
-
events.forEach(event => {
|
|
3608
|
-
watcher.on(event, path => {
|
|
3609
|
-
console.log(`检测到文件变动 [${event}]: ${path}`);
|
|
3610
|
-
debouncedNotifyChanges();
|
|
3611
|
-
});
|
|
3612
|
-
});
|
|
3613
|
-
|
|
3614
|
-
watcher.on('ready', () => {
|
|
3615
|
-
const initTime = Date.now() - watcherStartTime;
|
|
3616
|
-
console.log(`[文件监控] 监控器初始化完成,耗时 ${initTime}ms`);
|
|
3617
|
-
});
|
|
3618
|
-
|
|
3619
|
-
watcher.on('error', error => {
|
|
3620
|
-
console.error('[文件监控] 监控错误:', error);
|
|
3621
|
-
});
|
|
3622
|
-
|
|
3623
|
-
console.log('[文件监控] 监控器已启动(异步初始化中...)');
|
|
3624
|
-
} catch (error) {
|
|
3625
|
-
console.error('启动文件监控失败:', error);
|
|
3626
|
-
}
|
|
3627
|
-
}
|
|
3628
|
-
|
|
3629
3956
|
// 获取并广播Git状态 (优化版本 - 只获取porcelain格式)
|
|
3630
3957
|
async function getAndBroadcastStatus() {
|
|
3631
3958
|
try {
|
|
@@ -3657,17 +3984,6 @@ async function startUIServer(noOpen = false, savePort = false) {
|
|
|
3657
3984
|
}
|
|
3658
3985
|
}
|
|
3659
3986
|
|
|
3660
|
-
// 防抖处理函数
|
|
3661
|
-
function debouncedNotifyChanges() {
|
|
3662
|
-
if (debounceTimer) {
|
|
3663
|
-
clearTimeout(debounceTimer);
|
|
3664
|
-
}
|
|
3665
|
-
|
|
3666
|
-
debounceTimer = setTimeout(() => {
|
|
3667
|
-
getAndBroadcastStatus();
|
|
3668
|
-
}, DEBOUNCE_DELAY);
|
|
3669
|
-
}
|
|
3670
|
-
|
|
3671
3987
|
// 检查当前目录是否是Git仓库
|
|
3672
3988
|
let isGitRepo = false;
|
|
3673
3989
|
try {
|