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