zen-gitsync 2.9.8 → 2.9.10

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.
@@ -0,0 +1,981 @@
1
+ import express from 'express';
2
+ import fs from 'fs/promises';
3
+ import fsSync from 'fs';
4
+ import path from 'path';
5
+ import { exec } from 'child_process';
6
+
7
+ export function registerNpmRoutes({
8
+ app,
9
+ getCurrentProjectPath
10
+ }) {
11
+ // 读取 package.json 文件内容
12
+ app.post('/api/read-package-json', express.json(), async (req, res) => {
13
+ try {
14
+ const { packageJsonPath } = req.body
15
+
16
+ // 确定 package.json 的路径
17
+ let pkgPath
18
+ if (packageJsonPath && packageJsonPath.trim()) {
19
+ pkgPath = path.isAbsolute(packageJsonPath)
20
+ ? packageJsonPath
21
+ : path.join(getCurrentProjectPath(), packageJsonPath)
22
+ } else {
23
+ pkgPath = path.join(getCurrentProjectPath(), 'package.json')
24
+ }
25
+
26
+ // 检查文件是否存在
27
+ try {
28
+ await fs.access(pkgPath)
29
+ } catch (err) {
30
+ return res.status(404).json({
31
+ success: false,
32
+ error: `未找到 package.json 文件: ${pkgPath}`
33
+ })
34
+ }
35
+
36
+ // 读取 package.json
37
+ const pkgContent = await fs.readFile(pkgPath, 'utf8')
38
+ const pkg = JSON.parse(pkgContent)
39
+
40
+ res.json({
41
+ success: true,
42
+ dependencies: pkg.dependencies || {},
43
+ devDependencies: pkg.devDependencies || {},
44
+ version: pkg.version
45
+ })
46
+ } catch (error) {
47
+ res.status(500).json({ success: false, error: error.message })
48
+ }
49
+ })
50
+
51
+ // 版本号递增或依赖版本修改
52
+ app.post('/api/version-bump', express.json(), async (req, res) => {
53
+ try {
54
+ const { bumpType, packageJsonPath, versionTarget, dependencyName, dependencyVersion, dependencyVersionBump, dependencyType } = req.body
55
+
56
+ // 确定 package.json 的路径
57
+ let pkgPath
58
+ if (packageJsonPath && packageJsonPath.trim()) {
59
+ pkgPath = path.isAbsolute(packageJsonPath)
60
+ ? packageJsonPath
61
+ : path.join(getCurrentProjectPath(), packageJsonPath)
62
+ } else {
63
+ pkgPath = path.join(getCurrentProjectPath(), 'package.json')
64
+ }
65
+
66
+ // 检查文件是否存在(使用 Node.js 原生方法)
67
+ try {
68
+ await fs.access(pkgPath)
69
+ } catch (err) {
70
+ return res.status(404).json({
71
+ success: false,
72
+ error: `未找到 package.json 文件: ${pkgPath}`
73
+ })
74
+ }
75
+
76
+ // 读取 package.json
77
+ const pkgContent = await fs.readFile(pkgPath, 'utf8')
78
+ const pkg = JSON.parse(pkgContent)
79
+
80
+ // 判断是修改 version 还是 dependency
81
+ if (versionTarget === 'dependency') {
82
+ // 修改依赖版本
83
+ const depType = dependencyType || 'dependencies'
84
+
85
+ if (!pkg[depType]) {
86
+ return res.status(400).json({
87
+ success: false,
88
+ error: `package.json 中未找到 ${depType} 字段`
89
+ })
90
+ }
91
+
92
+ if (!pkg[depType][dependencyName]) {
93
+ return res.status(400).json({
94
+ success: false,
95
+ error: `在 ${depType} 中未找到依赖包: ${dependencyName}`
96
+ })
97
+ }
98
+
99
+ const oldVersion = pkg[depType][dependencyName]
100
+ let newVersion
101
+
102
+ // 判断是自动递增还是手动输入
103
+ if (dependencyVersionBump) {
104
+ // 自动递增模式:解析当前版本号并递增
105
+ // 提取版本号中的数字部分(去除 ^, ~, >=, 等前缀)
106
+ const versionMatch = oldVersion.match(/(\^|~|>=|>|<=|<)?(\d+\.\d+\.\d+)/)
107
+ if (!versionMatch) {
108
+ return res.status(400).json({
109
+ success: false,
110
+ error: `无法解析版本号: ${oldVersion},应为 x.y.z 格式(可带 ^, ~ 等前缀)`
111
+ })
112
+ }
113
+
114
+ const prefix = versionMatch[1] || ''
115
+ const versionNumber = versionMatch[2]
116
+ const versionParts = versionNumber.split('.').map(Number)
117
+
118
+ if (versionParts.length !== 3 || versionParts.some(isNaN)) {
119
+ return res.status(400).json({
120
+ success: false,
121
+ error: `无效的版本号格式: ${versionNumber}`
122
+ })
123
+ }
124
+
125
+ // 根据类型递增版本号
126
+ let [major, minor, patch] = versionParts
127
+ if (dependencyVersionBump === 'major') {
128
+ major += 1
129
+ minor = 0
130
+ patch = 0
131
+ } else if (dependencyVersionBump === 'minor') {
132
+ minor += 1
133
+ patch = 0
134
+ } else { // patch
135
+ patch += 1
136
+ }
137
+
138
+ newVersion = `${prefix}${major}.${minor}.${patch}`
139
+ } else {
140
+ // 手动输入模式
141
+ newVersion = dependencyVersion
142
+ }
143
+
144
+ pkg[depType][dependencyName] = newVersion
145
+
146
+ // 写回 package.json(保持格式化)
147
+ await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8')
148
+
149
+ res.json({
150
+ success: true,
151
+ oldVersion,
152
+ newVersion,
153
+ filePath: pkgPath,
154
+ dependencyName,
155
+ dependencyType: depType
156
+ })
157
+ } else {
158
+ // 修改 version 字段(原有逻辑)
159
+ if (!pkg.version) {
160
+ return res.status(400).json({
161
+ success: false,
162
+ error: 'package.json 中未找到 version 字段'
163
+ })
164
+ }
165
+
166
+ const oldVersion = pkg.version
167
+
168
+ // 解析版本号
169
+ const versionParts = oldVersion.split('.').map(Number)
170
+ if (versionParts.length !== 3 || versionParts.some(isNaN)) {
171
+ return res.status(400).json({
172
+ success: false,
173
+ error: `无效的版本号格式: ${oldVersion},应为 x.y.z 格式`
174
+ })
175
+ }
176
+
177
+ // 根据类型递增版本号
178
+ let [major, minor, patch] = versionParts
179
+ if (bumpType === 'major') {
180
+ major += 1
181
+ minor = 0
182
+ patch = 0
183
+ } else if (bumpType === 'minor') {
184
+ minor += 1
185
+ patch = 0
186
+ } else { // patch
187
+ patch += 1
188
+ }
189
+
190
+ const newVersion = `${major}.${minor}.${patch}`
191
+ pkg.version = newVersion
192
+
193
+ // 写回 package.json(保持格式化)
194
+ await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8')
195
+
196
+ res.json({
197
+ success: true,
198
+ oldVersion,
199
+ newVersion,
200
+ filePath: pkgPath
201
+ })
202
+ }
203
+ } catch (error) {
204
+ res.status(500).json({ success: false, error: error.message })
205
+ }
206
+ })
207
+
208
+ // ========== NPM 脚本管理相关 API ==========
209
+
210
+ // 存储正在进行的扫描任务
211
+ let currentScanAbortController = null;
212
+
213
+ // 扫描项目目录及子目录下的所有package.json,并提取scripts
214
+ app.get('/api/scan-npm-scripts', async (req, res) => {
215
+ // 取消之前的扫描
216
+ if (currentScanAbortController) {
217
+ currentScanAbortController.aborted = true;
218
+ }
219
+
220
+ // 创建新的abort controller
221
+ currentScanAbortController = {
222
+ aborted: false,
223
+ abort() { this.aborted = true; }
224
+ };
225
+ const scanController = currentScanAbortController;
226
+ try {
227
+ const projectRoot = process.cwd();
228
+ const packageJsons = [];
229
+ const startTime = Date.now();
230
+
231
+ // console.log(`[NPM扫描-后端] 开始扫描项目: ${projectRoot}`);
232
+
233
+ // 需要忽略的目录列表(更全面)
234
+ const IGNORED_DIRS = new Set([
235
+ 'node_modules',
236
+ '.git',
237
+ '.svn',
238
+ '.hg',
239
+ 'dist',
240
+ 'build',
241
+ 'coverage',
242
+ 'out',
243
+ 'target',
244
+ 'vendor',
245
+ '__pycache__',
246
+ '.next',
247
+ '.nuxt',
248
+ '.vscode',
249
+ '.idea',
250
+ 'tmp',
251
+ 'temp',
252
+ 'cache',
253
+ '.cache'
254
+ ]);
255
+
256
+ // 优先扫描的子目录(monorepo常见结构)
257
+ const PRIORITY_DIRS = ['packages', 'apps', 'libs', 'services', 'modules'];
258
+
259
+ let scannedCount = 0;
260
+ let skippedCount = 0;
261
+ let fileReadCount = 0; // 统计实际读取的文件数量
262
+
263
+ // 检查指定目录下是否有package.json
264
+ async function checkPackageJson(dir) {
265
+ if (scanController.aborted) return false;
266
+
267
+ try {
268
+ const packagePath = path.join(dir, 'package.json');
269
+
270
+ // 先检查文件是否存在,避免不必要的读取
271
+ try {
272
+ await fs.access(packagePath);
273
+ } catch {
274
+ // 文件不存在,直接返回
275
+ return false;
276
+ }
277
+
278
+ // 检查文件大小,避免读取异常大的文件
279
+ const stats = await fs.stat(packagePath);
280
+ const fileSizeMB = stats.size / (1024 * 1024);
281
+ if (fileSizeMB > 1) {
282
+ // package.json超过1MB是异常情况,跳过
283
+ console.log(`[NPM扫描] 跳过超大文件 (${fileSizeMB.toFixed(2)}MB): ${packagePath}`);
284
+ return false;
285
+ }
286
+
287
+ fileReadCount++; // 只有文件存在且大小合理时才计数
288
+ const content = await fs.readFile(packagePath, 'utf8');
289
+ const packageData = JSON.parse(content);
290
+
291
+ // 只有当scripts存在且至少有一个脚本时才添加
292
+ if (packageData.scripts && Object.keys(packageData.scripts).length > 0) {
293
+ const relativePath = path.relative(projectRoot, dir);
294
+ packageJsons.push({
295
+ path: dir,
296
+ relativePath: relativePath || '.',
297
+ name: packageData.name || path.basename(dir),
298
+ scripts: packageData.scripts,
299
+ version: packageData.version || '0.0.0',
300
+ repository: packageData.repository
301
+ });
302
+ return true;
303
+ }
304
+ } catch (error) {
305
+ // 文件不存在或解析失败,忽略
306
+ }
307
+ return false;
308
+ }
309
+
310
+ // 递归扫描目录,最大深度4层
311
+ const MAX_DEPTH = 4;
312
+ const MAX_DIRS_PER_LEVEL = 50; // 每层最多扫描50个子目录
313
+
314
+ // 统计每层深度的扫描数量
315
+ const depthStats = Array(MAX_DEPTH + 1).fill(0).map(() => ({ count: 0, time: 0 }));
316
+
317
+ async function scanDirectory(dir, depth = 0) {
318
+ if (scanController.aborted) return;
319
+ if (depth > MAX_DEPTH) return;
320
+
321
+ const depthStart = Date.now();
322
+ scannedCount++;
323
+ depthStats[depth].count++;
324
+
325
+ // 检查当前目录的package.json
326
+ await checkPackageJson(dir);
327
+
328
+ // 如果已经达到最大深度,不再继续
329
+ if (depth >= MAX_DEPTH) return;
330
+
331
+ // 读取子目录
332
+ try {
333
+ if (scanController.aborted) return;
334
+
335
+ const items = await fs.readdir(dir, { withFileTypes: true });
336
+ const subDirs = [];
337
+
338
+ // 收集所有子目录
339
+ for (const item of items) {
340
+ if (scanController.aborted) return;
341
+ if (!item.isDirectory()) continue;
342
+
343
+ const dirName = item.name;
344
+
345
+ // 跳过忽略的目录
346
+ if (IGNORED_DIRS.has(dirName) || dirName.startsWith('.')) {
347
+ skippedCount++;
348
+ continue;
349
+ }
350
+
351
+ subDirs.push(item);
352
+ }
353
+
354
+ // 限制每层扫描的子目录数量
355
+ const dirsToScan = subDirs.slice(0, MAX_DIRS_PER_LEVEL);
356
+ if (subDirs.length > MAX_DIRS_PER_LEVEL) {
357
+ skippedCount += subDirs.length - MAX_DIRS_PER_LEVEL;
358
+ }
359
+
360
+ // 优先处理优先目录
361
+ const priorityDirs = dirsToScan.filter(item => PRIORITY_DIRS.includes(item.name));
362
+ const normalDirs = dirsToScan.filter(item => !PRIORITY_DIRS.includes(item.name));
363
+
364
+ // 先扫描优先目录
365
+ for (const item of priorityDirs) {
366
+ if (scanController.aborted) return;
367
+ const subDirPath = path.join(dir, item.name);
368
+ await scanDirectory(subDirPath, depth + 1);
369
+ }
370
+
371
+ // 再扫描普通目录
372
+ for (const item of normalDirs) {
373
+ if (scanController.aborted) return;
374
+ const subDirPath = path.join(dir, item.name);
375
+ await scanDirectory(subDirPath, depth + 1);
376
+ }
377
+
378
+ } catch (error) {
379
+ // 忽略无法访问的目录
380
+ }
381
+
382
+ // 记录该深度的耗时
383
+ depthStats[depth].time += Date.now() - depthStart;
384
+ }
385
+
386
+ // 执行递归扫描
387
+ // console.log(`[NPM扫描-后端] 开始递归扫描(最大深度${MAX_DEPTH}层)`);
388
+ const scanStart = Date.now();
389
+ await scanDirectory(projectRoot, 0);
390
+ // console.log(`[NPM扫描-后端] 递归扫描完成,耗时${Date.now() - scanStart}ms`);
391
+
392
+ // 扫描完成,清除abort controller
393
+ if (currentScanAbortController === scanController) {
394
+ currentScanAbortController = null;
395
+ }
396
+
397
+ const scanTime = Date.now() - startTime;
398
+
399
+ if (scanController.aborted) {
400
+ console.log(`npm脚本扫描被取消,耗时${scanTime}ms`);
401
+ return res.json({
402
+ success: true,
403
+ packages: [],
404
+ totalScripts: 0,
405
+ cancelled: true
406
+ });
407
+ }
408
+
409
+ // 输出每层深度的统计
410
+ const depthInfo = depthStats
411
+ .map((stat, depth) => stat.count > 0 ? `深度${depth}:${stat.count}个(${stat.time}ms)` : null)
412
+ .filter(Boolean)
413
+ .join(', ');
414
+
415
+ // console.log(`npm脚本扫描完成,耗时${scanTime}ms,扫描了${scannedCount}个目录,读取了${fileReadCount}个package.json文件,跳过${skippedCount}个目录,找到${packageJsons.length}个有效的package.json`);
416
+ // console.log(`[NPM扫描-后端] 深度分布: ${depthInfo}`);
417
+
418
+ res.json({
419
+ success: true,
420
+ packages: packageJsons,
421
+ totalScripts: packageJsons.reduce((sum, pkg) => sum + Object.keys(pkg.scripts).length, 0)
422
+ });
423
+ } catch (error) {
424
+ console.error('扫描npm脚本失败:', error);
425
+ res.status(500).json({
426
+ success: false,
427
+ error: `扫描npm脚本失败: ${error.message}`
428
+ });
429
+ }
430
+ });
431
+
432
+ // 扫描项目目录下的所有package.json文件(用于版本管理)
433
+ app.get('/api/scan-package-files', async (req, res) => {
434
+ try {
435
+ // 支持通过查询参数指定扫描目录,如果没有指定则使用当前工作目录
436
+ const customDirectory = req.query.directory;
437
+ const projectRoot = customDirectory || process.cwd();
438
+
439
+ // 验证目录是否存在且可访问
440
+ try {
441
+ const stats = await fs.stat(projectRoot);
442
+ if (!stats.isDirectory()) {
443
+ return res.status(400).json({
444
+ success: false,
445
+ error: '指定的路径不是一个有效的目录'
446
+ });
447
+ }
448
+ } catch (error) {
449
+ return res.status(400).json({
450
+ success: false,
451
+ error: `无法访问指定的目录: ${error.message}`
452
+ });
453
+ }
454
+
455
+ const packageFiles = [];
456
+ const startTime = Date.now();
457
+
458
+ // 需要忽略的目录列表
459
+ const IGNORED_DIRS = new Set([
460
+ 'node_modules',
461
+ '.git',
462
+ '.svn',
463
+ '.hg',
464
+ 'dist',
465
+ 'build',
466
+ 'coverage',
467
+ 'out',
468
+ 'target',
469
+ 'vendor',
470
+ '__pycache__',
471
+ '.next',
472
+ '.nuxt',
473
+ '.vscode',
474
+ '.idea',
475
+ 'tmp',
476
+ 'temp',
477
+ 'cache',
478
+ '.cache'
479
+ ]);
480
+
481
+ let scannedCount = 0;
482
+ let fileReadCount = 0;
483
+
484
+ // 检查指定目录下是否有package.json
485
+ async function checkPackageJson(dir) {
486
+ try {
487
+ const packagePath = path.join(dir, 'package.json');
488
+
489
+ // 先检查文件是否存在
490
+ try {
491
+ await fs.access(packagePath);
492
+ } catch {
493
+ return false;
494
+ }
495
+
496
+ // 检查文件大小
497
+ const stats = await fs.stat(packagePath);
498
+ const fileSizeMB = stats.size / (1024 * 1024);
499
+ if (fileSizeMB > 1) {
500
+ return false;
501
+ }
502
+
503
+ fileReadCount++;
504
+ const content = await fs.readFile(packagePath, 'utf8');
505
+ const packageData = JSON.parse(content);
506
+
507
+ // 添加所有有效的package.json文件(不仅仅是有scripts的)
508
+ if (packageData.name || packageData.version) {
509
+ const relativePath = path.relative(projectRoot, dir);
510
+ packageFiles.push({
511
+ path: dir,
512
+ relativePath: relativePath || '.',
513
+ name: packageData.name || path.basename(dir),
514
+ version: packageData.version || '0.0.0',
515
+ displayName: packageData.name ? `${packageData.name} (${packageData.version || '0.0.0'})` : `${path.basename(dir)} (${packageData.version || '0.0.0'})`,
516
+ fullPath: packagePath
517
+ });
518
+ return true;
519
+ }
520
+ } catch (error) {
521
+ // 文件不存在或解析失败,忽略
522
+ }
523
+ return false;
524
+ }
525
+
526
+ // 递归扫描目录,最大深度4层
527
+ const MAX_DEPTH = 4;
528
+ const MAX_DIRS_PER_LEVEL = 50;
529
+
530
+ async function scanDirectory(dir, depth = 0) {
531
+ if (depth > MAX_DEPTH) return;
532
+
533
+ scannedCount++;
534
+
535
+ // 检查当前目录的package.json
536
+ await checkPackageJson(dir);
537
+
538
+ // 如果已经达到最大深度,不再继续
539
+ if (depth >= MAX_DEPTH) return;
540
+
541
+ // 读取子目录
542
+ try {
543
+ const items = await fs.readdir(dir, { withFileTypes: true });
544
+ const subDirs = [];
545
+
546
+ // 收集所有子目录
547
+ for (const item of items) {
548
+ if (!item.isDirectory()) continue;
549
+
550
+ const dirName = item.name;
551
+
552
+ // 跳过忽略的目录
553
+ if (IGNORED_DIRS.has(dirName) || dirName.startsWith('.')) {
554
+ continue;
555
+ }
556
+
557
+ subDirs.push(item);
558
+ }
559
+
560
+ // 限制每层扫描的子目录数量
561
+ const dirsToScan = subDirs.slice(0, MAX_DIRS_PER_LEVEL);
562
+
563
+ // 递归扫描子目录
564
+ for (const item of dirsToScan) {
565
+ const subDirPath = path.join(dir, item.name);
566
+ await scanDirectory(subDirPath, depth + 1);
567
+ }
568
+ } catch (error) {
569
+ // 忽略无法读取的目录
570
+ }
571
+ }
572
+
573
+ // 开始扫描
574
+ await scanDirectory(projectRoot);
575
+
576
+ const scanTime = Date.now() - startTime;
577
+
578
+ res.json({
579
+ success: true,
580
+ packages: packageFiles,
581
+ scanTime,
582
+ scannedCount,
583
+ fileReadCount
584
+ });
585
+ } catch (error) {
586
+ console.error('扫描package.json文件失败:', error);
587
+ res.status(500).json({
588
+ success: false,
589
+ error: `扫描package.json文件失败: ${error.message}`
590
+ });
591
+ }
592
+ });
593
+
594
+ // 在新终端中执行npm脚本
595
+ app.post('/api/run-npm-script', async (req, res) => {
596
+ try {
597
+ const { packagePath, scriptName } = req.body;
598
+
599
+ if (!packagePath || !scriptName) {
600
+ return res.status(400).json({
601
+ success: false,
602
+ error: '缺少必要参数:packagePath 和 scriptName'
603
+ });
604
+ }
605
+
606
+ console.log(`执行npm脚本: ${scriptName} in ${packagePath}`);
607
+
608
+ // 根据操作系统选择合适的终端命令
609
+ let terminalCommand;
610
+ const npmCommand = `npm run ${scriptName}`;
611
+
612
+ if (process.platform === 'win32') {
613
+ // Windows: 使用 start 命令打开新的 cmd 窗口
614
+ // /K 参数表示执行命令后保持窗口打开
615
+ terminalCommand = `start cmd /K "cd /d ${packagePath} && ${npmCommand}"`;
616
+ } else if (process.platform === 'darwin') {
617
+ // macOS: 使用 osascript 打开 Terminal.app
618
+ const script = `tell application "Terminal" to do script "cd ${packagePath} && ${npmCommand}"`;
619
+ terminalCommand = `osascript -e '${script}'`;
620
+ } else {
621
+ // Linux: 尝试常见的终端模拟器
622
+ // 优先使用 gnome-terminal, 然后是 xterm
623
+ terminalCommand = `gnome-terminal -- bash -c "cd ${packagePath} && ${npmCommand}; exec bash" || xterm -e "cd ${packagePath} && ${npmCommand}; bash"`;
624
+ }
625
+
626
+ // 执行命令打开新终端(使用已导入的 exec)
627
+ exec(terminalCommand, (error, stdout, stderr) => {
628
+ if (error) {
629
+ console.error('打开终端失败:', error);
630
+ }
631
+ });
632
+
633
+ res.json({
634
+ success: true,
635
+ message: `已在新终端中执行: ${scriptName}`,
636
+ command: npmCommand,
637
+ path: packagePath
638
+ });
639
+ } catch (error) {
640
+ console.error('执行npm脚本失败:', error);
641
+ res.status(500).json({
642
+ success: false,
643
+ error: `执行npm脚本失败: ${error.message}`
644
+ });
645
+ }
646
+ });
647
+
648
+ // API: 更新npm版本号
649
+ app.post('/api/update-npm-version', async (req, res) => {
650
+ try {
651
+ const { packagePath, versionType } = req.body;
652
+
653
+ if (!packagePath || !versionType) {
654
+ return res.status(400).json({
655
+ success: false,
656
+ error: '缺少必要参数: packagePath, versionType'
657
+ });
658
+ }
659
+
660
+ // 确保路径指向package.json文件
661
+ let packageJsonPath = path.resolve(packagePath);
662
+ if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
663
+ packageJsonPath = path.join(packageJsonPath, 'package.json');
664
+ }
665
+
666
+ // 检查文件是否存在
667
+ if (!fsSync.existsSync(packageJsonPath)) {
668
+ return res.status(404).json({
669
+ success: false,
670
+ error: '找不到package.json文件'
671
+ });
672
+ }
673
+
674
+ // 读取package.json
675
+ const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
676
+
677
+ if (!packageJson.version) {
678
+ return res.status(400).json({
679
+ success: false,
680
+ error: 'package.json中没有version字段'
681
+ });
682
+ }
683
+
684
+ const oldVersion = packageJson.version;
685
+ const versionParts = oldVersion.split('.').map(Number);
686
+
687
+ // 根据类型增加版本号
688
+ switch (versionType) {
689
+ case 'major':
690
+ versionParts[0]++;
691
+ versionParts[1] = 0;
692
+ versionParts[2] = 0;
693
+ break;
694
+ case 'minor':
695
+ versionParts[1]++;
696
+ versionParts[2] = 0;
697
+ break;
698
+ case 'patch':
699
+ versionParts[2]++;
700
+ break;
701
+ default:
702
+ return res.status(400).json({
703
+ success: false,
704
+ error: '无效的版本类型,必须是 major, minor 或 patch'
705
+ });
706
+ }
707
+
708
+ const newVersion = versionParts.join('.');
709
+ packageJson.version = newVersion;
710
+
711
+ // 写回文件
712
+ fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
713
+
714
+ console.log(`已更新npm版本号: ${oldVersion} → ${newVersion} (${packagePath})`);
715
+
716
+ res.json({
717
+ success: true,
718
+ oldVersion,
719
+ newVersion
720
+ });
721
+ } catch (error) {
722
+ console.error('更新版本号失败:', error);
723
+ res.status(500).json({
724
+ success: false,
725
+ error: `更新版本号失败: ${error.message}`
726
+ });
727
+ }
728
+ });
729
+
730
+ // API: 添加npm脚本
731
+ app.post('/api/add-npm-script', async (req, res) => {
732
+ try {
733
+ const { packagePath, scriptName, scriptCommand } = req.body;
734
+
735
+ if (!packagePath || !scriptName || !scriptCommand) {
736
+ return res.status(400).json({
737
+ success: false,
738
+ error: '缺少必要参数: packagePath, scriptName, scriptCommand'
739
+ });
740
+ }
741
+
742
+ // 确保路径指向package.json文件
743
+ let packageJsonPath = path.resolve(packagePath);
744
+ if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
745
+ packageJsonPath = path.join(packageJsonPath, 'package.json');
746
+ }
747
+
748
+ // 检查文件是否存在
749
+ if (!fsSync.existsSync(packageJsonPath)) {
750
+ return res.status(404).json({
751
+ success: false,
752
+ error: '找不到package.json文件'
753
+ });
754
+ }
755
+
756
+ // 读取package.json
757
+ const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
758
+
759
+ // 确保scripts对象存在
760
+ if (!packageJson.scripts) {
761
+ packageJson.scripts = {};
762
+ }
763
+
764
+ // 检查脚本是否已存在
765
+ if (packageJson.scripts[scriptName]) {
766
+ return res.status(400).json({
767
+ success: false,
768
+ error: `脚本 "${scriptName}" 已存在`
769
+ });
770
+ }
771
+
772
+ // 添加脚本
773
+ packageJson.scripts[scriptName] = scriptCommand;
774
+
775
+ // 写回文件(保持格式化)
776
+ fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
777
+
778
+ console.log(`已添加npm脚本: ${scriptName} = ${scriptCommand} (${packagePath})`);
779
+
780
+ res.json({
781
+ success: true,
782
+ scriptName,
783
+ scriptCommand
784
+ });
785
+ } catch (error) {
786
+ console.error('添加npm脚本失败:', error);
787
+ res.status(500).json({
788
+ success: false,
789
+ error: `添加npm脚本失败: ${error.message}`
790
+ });
791
+ }
792
+ });
793
+
794
+ // API: 更新npm脚本
795
+ app.post('/api/update-npm-script', async (req, res) => {
796
+ try {
797
+ const { packagePath, scriptName, scriptCommand, oldScriptName } = req.body;
798
+
799
+ if (!packagePath || !scriptName || !scriptCommand) {
800
+ return res.status(400).json({
801
+ success: false,
802
+ error: '缺少必要参数: packagePath, scriptName, scriptCommand'
803
+ });
804
+ }
805
+
806
+ // 确保路径指向package.json文件
807
+ let packageJsonPath = path.resolve(packagePath);
808
+ if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
809
+ packageJsonPath = path.join(packageJsonPath, 'package.json');
810
+ }
811
+
812
+ // 检查文件是否存在
813
+ if (!fsSync.existsSync(packageJsonPath)) {
814
+ return res.status(404).json({
815
+ success: false,
816
+ error: '找不到package.json文件'
817
+ });
818
+ }
819
+
820
+ // 读取package.json
821
+ const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
822
+
823
+ // 确保scripts对象存在
824
+ if (!packageJson.scripts) {
825
+ packageJson.scripts = {};
826
+ }
827
+
828
+ // 如果改了脚本名称,删除旧的
829
+ if (oldScriptName && oldScriptName !== scriptName) {
830
+ delete packageJson.scripts[oldScriptName];
831
+ }
832
+
833
+ // 更新脚本
834
+ packageJson.scripts[scriptName] = scriptCommand;
835
+
836
+ // 写回文件
837
+ fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
838
+
839
+ console.log(`已更新npm脚本: ${scriptName} = ${scriptCommand} (${packagePath})`);
840
+
841
+ res.json({
842
+ success: true,
843
+ scriptName,
844
+ scriptCommand
845
+ });
846
+ } catch (error) {
847
+ console.error('更新npm脚本失败:', error);
848
+ res.status(500).json({
849
+ success: false,
850
+ error: `更新npm脚本失败: ${error.message}`
851
+ });
852
+ }
853
+ });
854
+
855
+ // API: 删除npm脚本
856
+ app.post('/api/delete-npm-script', async (req, res) => {
857
+ try {
858
+ const { packagePath, scriptName } = req.body;
859
+
860
+ if (!packagePath || !scriptName) {
861
+ return res.status(400).json({
862
+ success: false,
863
+ error: '缺少必要参数: packagePath, scriptName'
864
+ });
865
+ }
866
+
867
+ // 确保路径指向package.json文件
868
+ let packageJsonPath = path.resolve(packagePath);
869
+ if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
870
+ packageJsonPath = path.join(packageJsonPath, 'package.json');
871
+ }
872
+
873
+ // 检查文件是否存在
874
+ if (!fsSync.existsSync(packageJsonPath)) {
875
+ return res.status(404).json({
876
+ success: false,
877
+ error: '找不到package.json文件'
878
+ });
879
+ }
880
+
881
+ // 读取package.json
882
+ const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
883
+
884
+ // 检查scripts对象和脚本是否存在
885
+ if (!packageJson.scripts || !packageJson.scripts[scriptName]) {
886
+ return res.status(404).json({
887
+ success: false,
888
+ error: `脚本 "${scriptName}" 不存在`
889
+ });
890
+ }
891
+
892
+ // 删除脚本
893
+ delete packageJson.scripts[scriptName];
894
+
895
+ // 写回文件
896
+ fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
897
+
898
+ console.log(`已删除npm脚本: ${scriptName} (${packagePath})`);
899
+
900
+ res.json({
901
+ success: true,
902
+ scriptName
903
+ });
904
+ } catch (error) {
905
+ console.error('删除npm脚本失败:', error);
906
+ res.status(500).json({
907
+ success: false,
908
+ error: `删除npm脚本失败: ${error.message}`
909
+ });
910
+ }
911
+ });
912
+
913
+ // API: 置顶npm脚本
914
+ app.post('/api/pin-npm-script', async (req, res) => {
915
+ try {
916
+ const { packagePath, scriptName } = req.body;
917
+
918
+ if (!packagePath || !scriptName) {
919
+ return res.status(400).json({
920
+ success: false,
921
+ error: '缺少必要参数: packagePath, scriptName'
922
+ });
923
+ }
924
+
925
+ // 确保路径指向package.json文件
926
+ let packageJsonPath = path.resolve(packagePath);
927
+ if (fsSync.existsSync(packageJsonPath) && fsSync.statSync(packageJsonPath).isDirectory()) {
928
+ packageJsonPath = path.join(packageJsonPath, 'package.json');
929
+ }
930
+
931
+ // 检查文件是否存在
932
+ if (!fsSync.existsSync(packageJsonPath)) {
933
+ return res.status(404).json({
934
+ success: false,
935
+ error: '找不到package.json文件'
936
+ });
937
+ }
938
+
939
+ // 读取package.json
940
+ const packageJson = JSON.parse(fsSync.readFileSync(packageJsonPath, 'utf8'));
941
+
942
+ // 检查scripts对象和脚本是否存在
943
+ if (!packageJson.scripts || !packageJson.scripts[scriptName]) {
944
+ return res.status(404).json({
945
+ success: false,
946
+ error: `脚本 "${scriptName}" 不存在`
947
+ });
948
+ }
949
+
950
+ // 保存要置顶的脚本内容
951
+ const scriptCommand = packageJson.scripts[scriptName];
952
+
953
+ // 删除该脚本
954
+ delete packageJson.scripts[scriptName];
955
+
956
+ // 创建新的scripts对象,将置顶脚本放在最前面
957
+ const newScripts = {
958
+ [scriptName]: scriptCommand,
959
+ ...packageJson.scripts
960
+ };
961
+
962
+ packageJson.scripts = newScripts;
963
+
964
+ // 写回文件
965
+ fsSync.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n', 'utf8');
966
+
967
+ console.log(`已置顶npm脚本: ${scriptName} (${packagePath})`);
968
+
969
+ res.json({
970
+ success: true,
971
+ scriptName
972
+ });
973
+ } catch (error) {
974
+ console.error('置顶npm脚本失败:', error);
975
+ res.status(500).json({
976
+ success: false,
977
+ error: `置顶npm脚本失败: ${error.message}`
978
+ });
979
+ }
980
+ });
981
+ }