@wmj-code/clone_npm 1.0.0

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/clone_npm.js ADDED
@@ -0,0 +1,646 @@
1
+ #!/usr/bin/env node
2
+
3
+ // const { batchHandleProjects } = require('./clear_yl_npm_i.js');
4
+ // const { batchCloneAndSwitchBranch } = require('./clone_check_branch.js');
5
+ const { exec, spawn, execPromise, execSync } = require('child_process');
6
+ const { resolve } = require('path');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const os = require('os');
10
+ // const { configList, CodingEditPath } = require('./config.js');
11
+ const CONFIG_FILE = path.join(__dirname, 'config.json');
12
+ const SERVER_FILE = path.join(__dirname, 'server.js');
13
+ const SERVER_PID_FILE = path.join(__dirname, 'server.pid');
14
+ const EDITOR_URL = 'http://localhost:3000';
15
+ let serverProcess = null; // 保存进程引用,用于后续关闭
16
+ console.log('温馨提示1:', '若在编辑器终端里执行脚本,可能会有权限问题,建议在终端里执行');
17
+ console.log('温馨提示2:', '若将脚本安装至本地,则可以直接通过cn 执行命令,否则需要通过node clone_npm执行');
18
+ console.table({
19
+ '执行 cn': '启动配置服务并打开浏览器',
20
+ '执行 cn 1': '下载代码、切换分支、打开项目文件夹',
21
+ '执行 cn 2': '清除依赖、下载依赖',
22
+ '执行 cn 3': '批量打开项目文件夹',
23
+ '执行 cn 4': '批量拉取最新代码, npm run dev',
24
+ '执行 cn 5': '下载代码、切换分支、打开项目文件夹、清除依赖、下载依赖',
25
+ });
26
+ // 2. 提取自定义参数(排除前两个默认参数)
27
+ const customArgs = process.argv.slice(2);
28
+ console.log('自定义参数列表:', customArgs);
29
+ let GITLAB_PROJECT_LIST = [] //配置
30
+ let CodingEditPath = '' //软件地址
31
+
32
+
33
+
34
+ // 1. 自动启动配置服务并打开浏览器
35
+ async function startConfigServer() {
36
+ try {
37
+ // ========== 修复1:安全杀死旧进程 ==========
38
+ if (fs.existsSync(SERVER_PID_FILE)) {
39
+ const pidStr = fs.readFileSync(SERVER_PID_FILE, 'utf8').trim();
40
+ const pid = parseInt(pidStr);
41
+ console.log("🚀 ~ startConfigServer ~ 检测到旧PID:", pidStr);
42
+
43
+ // 校验PID是否为有效数字
44
+ if (!isNaN(pid) && pid > 0) {
45
+ try {
46
+ // 尝试杀死进程(SIGINT等同于Ctrl+C,更友好)
47
+ process.kill(pid, 'SIGINT');
48
+ console.log(`✅ 已向旧进程 ${pid} 发送终止信号`);
49
+ // 杀死后删除PID文件,避免残留
50
+ fs.unlinkSync(SERVER_PID_FILE);
51
+ } catch (killErr) {
52
+ // 捕获"进程不存在"错误,仅打印提示,不中断流程
53
+ if (killErr.code === 'ESRCH') {
54
+ console.log(`ℹ️ 旧进程 ${pid} 已不存在,跳过杀死操作`);
55
+ } else if (killErr.code === 'EPERM') {
56
+ console.warn(`⚠️ 无权限杀死进程 ${pid},请手动关闭或提升权限`);
57
+ } else {
58
+ console.warn(`⚠️ 杀死旧进程失败:${killErr.message}`);
59
+ }
60
+ // 无论是否杀死成功,都删除无效的PID文件
61
+ fs.unlinkSync(SERVER_PID_FILE);
62
+ }
63
+ } else {
64
+ // PID不是有效数字,直接删除PID文件
65
+ fs.unlinkSync(SERVER_PID_FILE);
66
+ console.log(`ℹ️ PID文件内容无效,已删除`);
67
+ }
68
+ }
69
+ // ========== 启动新服务 ==========
70
+ console.log(`🚀 正在启动配置服务...`);
71
+ serverProcess = spawn('node', [SERVER_FILE], {
72
+ detached: true,
73
+ stdio: 'ignore', // 彻底后台化
74
+ windowsHide: true // Windows隐藏窗口
75
+ });
76
+
77
+ // 监听进程启动失败事件
78
+ serverProcess.on('error', (spawnErr) => {
79
+ console.error(`❌ 服务启动失败:${spawnErr.message}`);
80
+ // 清理PID文件(如果已写入)
81
+ if (fs.existsSync(SERVER_PID_FILE)) {
82
+ fs.unlinkSync(SERVER_PID_FILE);
83
+ }
84
+ });
85
+
86
+ // 确认进程启动后写入PID文件
87
+ serverProcess.on('spawn', () => {
88
+ console.log(`✅ 服务已启动,PID:${serverProcess.pid}`);
89
+ fs.writeFileSync(SERVER_PID_FILE, serverProcess.pid.toString());
90
+ serverProcess.unref(); // 解除主进程引用,后台运行
91
+ });
92
+
93
+ // 延迟1秒(等服务启动),打开浏览器
94
+ setTimeout(() => {
95
+ // 修复:使用跨平台的浏览器打开方式
96
+ let cmd;
97
+ const platform = os.platform();
98
+ if (platform === 'win32') {
99
+ // Windows:正确的start命令格式(必须加引号,且start后加空串处理URL)
100
+ cmd = `start "" "${EDITOR_URL}"`;
101
+ } else if (platform === 'darwin') {
102
+ // macOS:直接用open命令
103
+ cmd = `open "${EDITOR_URL}"`;
104
+ } else {
105
+ // Linux:优先用xdg-open,备选x-www-browser
106
+ cmd = `which xdg-open >/dev/null 2>&1 && xdg-open "${EDITOR_URL}" || x-www-browser "${EDITOR_URL}"`;
107
+ }
108
+
109
+ // 执行命令并处理错误
110
+ exec(cmd, (err, stdout, stderr) => {
111
+ if (err) {
112
+ console.warn('\x1B[33m无法自动打开浏览器,请手动访问:\x1B[0m', EDITOR_URL);
113
+ console.debug('浏览器启动错误:', err.message, stderr); // 调试用
114
+ } else {
115
+ console.log('\x1B[32m浏览器已打开,正在访问配置编辑器...\x1B[0m');
116
+ }
117
+ });
118
+ }, 1000);
119
+
120
+ console.log('\x1B[36m配置编辑器已启动:\x1B[0m', EDITOR_URL);
121
+ console.log('\x1B[33m请在浏览器中配置后保存,再重新执行 cn 命令\x1B[0m');
122
+ // process.exit(0); // 启动编辑器后退出,让用户配置完成后重新执行
123
+ } catch (err) {
124
+ console.error('\x1B[31m启动配置服务失败:\x1B[0m', err.message);
125
+ if (fs.existsSync(SERVER_PID_FILE)) {
126
+ try {
127
+ fs.unlinkSync(SERVER_PID_FILE);
128
+ } catch (unlinkErr) {
129
+ console.debug('清理PID文件失败:', unlinkErr.message);
130
+ }
131
+ }
132
+ }
133
+ }
134
+
135
+
136
+ // 2. 加载配置文件
137
+ function loadConfig() {
138
+ try {
139
+ if (!fs.existsSync(CONFIG_FILE)) {
140
+ console.log('\x1B[33m未找到config.json,自动启动配置编辑器...\x1B[0m');
141
+ startConfigServer(); // 无配置则启动编辑器
142
+ return null; // 终止后续逻辑
143
+ }
144
+
145
+ const configContent = fs.readFileSync(CONFIG_FILE, 'utf8');
146
+ const config = JSON.parse(configContent);
147
+
148
+ // 基础校验
149
+ if (!Array.isArray(config.configList)) {
150
+ throw new Error('config.json 格式错误:configList 必须是数组');
151
+ }
152
+
153
+ // 过滤出需要执行的配置项
154
+ const enableItems = config.configList.filter(item => item.enable === true);
155
+ if (enableItems.length === 0) {
156
+ console.log('\x1B[33m无勾选「执行」的仓库配置,\x1B[0m');
157
+ // startConfigServer(); // 无执行项则启动编辑器
158
+ return null;
159
+ }
160
+ GITLAB_PROJECT_LIST = enableItems
161
+ CodingEditPath = config.CodingEditPath;
162
+ console.log(`\x1B[36m已加载配置:\x1B[0m 共 ${config.configList.length} 项,其中 ${enableItems.length} 项勾选执行`);
163
+ } catch (err) {
164
+ console.error('\x1B[31m配置加载失败:\x1B[0m', err.message);
165
+ process.exit(1);
166
+ }
167
+ }
168
+
169
+
170
+
171
+
172
+ //!!!在编辑器里执行脚本可能会有权限问题,建议在终端里执行
173
+ //!!!删除依赖,所属项目不能在运行中,否则会删除失败
174
+ if (customArgs.length == 0) {
175
+ handleJsProject()
176
+ } else {
177
+ handleJsProject(customArgs[0])
178
+ }
179
+ // handleJsProject(1) //下载代码、切换分支、打开项目文件夹
180
+ // handleJsProject(2) //清除依赖、下载依赖
181
+ // handleJsProject(3) //批量打开项目文件夹
182
+ // handleJsProject(4) //批量拉取最新代码, npm run dev
183
+ // handleJsProject(5) //下载代码、切换分支、打开项目文件夹、清除依赖、下载依赖
184
+
185
+
186
+ /**
187
+ * 执行脚本
188
+ * @param {string} flag 执行标志位,1:下载代码、切换分支 ,2:清除依赖、下载依赖 ,3:批量打开项目文件夹 ,不传flag 顺序执行下载代码、切换分支、清除依赖、下载依赖
189
+ */
190
+ async function handleJsProject(flag) {
191
+ flag && loadConfig(); // 加载配置(无配置/无执行项会自动启动编辑器)
192
+ if (!flag) {
193
+ //通过trae打开clone_npm.js文件
194
+ // await openFolderWithApp(CodingEditPath, __dirname + '\\config.js');
195
+ await startConfigServer();
196
+ } else if (flag == 1) {
197
+ //下载代码、切换分支
198
+ await batchCloneAndSwitchBranch(GITLAB_PROJECT_LIST);
199
+ //打开项目文件夹
200
+ await openProjectFolders();
201
+ } else if (flag == 2) {
202
+ //清除依赖、下载依赖
203
+ await batchHandleProjects(GITLAB_PROJECT_LIST);
204
+ } else if (flag == 3) {
205
+ //打开项目文件夹
206
+ await openProjectFolders();
207
+ } else if (flag == 4) {
208
+ //批量拉取最新代码
209
+ await gitPullLatestCode();
210
+ } else if (flag == 5) {
211
+ //下载代码、切换分支
212
+ await batchCloneAndSwitchBranch(GITLAB_PROJECT_LIST);
213
+ //打开项目文件夹
214
+ await openProjectFolders();
215
+ //清除依赖、下载依赖
216
+ await batchHandleProjects(GITLAB_PROJECT_LIST);
217
+ }
218
+ }
219
+
220
+ /**
221
+ * 用指定软件打开指定文件夹/文件
222
+ * @param {string} appPath - 软件可执行文件路径(绝对路径)
223
+ * @param {string} folderPath - 要打开的文件路径(相对/绝对路径)
224
+ * @returns {Promise<void>}
225
+ */
226
+ function openFolderWithApp(appPath, folderPath) {
227
+ //写法1
228
+ const absoluteFolderPath = resolve(folderPath);
229
+ const absoluteAppPath = resolve(appPath);
230
+
231
+ return new Promise((resolve, reject) => {
232
+ console.log(`应用路径:${absoluteAppPath}`);
233
+ console.log(`文件路径:${absoluteFolderPath}`);
234
+
235
+ if (process.platform === 'win32') {
236
+ // Windows: 直接用 spawn 启动程序
237
+ const child = spawn(absoluteAppPath, [absoluteFolderPath], {
238
+ detached: true,
239
+ stdio: 'ignore',
240
+ windowsHide: false // Node 20 支持,显示窗口
241
+ });
242
+
243
+ child.on('error', (err) => {
244
+ reject(new Error(`启动失败:${err.message}`));
245
+ });
246
+
247
+ child.unref();
248
+ console.log(`成功启动`);
249
+ resolve();
250
+
251
+ } else if (process.platform === 'darwin') {
252
+ spawn('open', ['-a', absoluteAppPath, absoluteFolderPath], {
253
+ stdio: 'ignore'
254
+ });
255
+ resolve();
256
+
257
+ } else {
258
+ spawn(absoluteAppPath, [absoluteFolderPath], {
259
+ detached: true,
260
+ stdio: 'ignore'
261
+ });
262
+ resolve();
263
+ }
264
+ });
265
+ //写法2
266
+ // const absoluteFolderPath = resolve(folderPath);
267
+ // const absoluteAppPath = resolve(appPath);
268
+
269
+ // return new Promise((resolve, reject) => {
270
+ // console.log(`应用路径:${absoluteAppPath}`);
271
+ // console.log(`文件夹路径:${absoluteFolderPath}`);
272
+
273
+ // // Windows: 使用 exec + start 命令
274
+ // // 关键:使用 { encoding: 'utf8', shell: 'cmd.exe' }
275
+ // const command = `start "" "${absoluteAppPath}" "${absoluteFolderPath}"`;
276
+
277
+ // exec(command, {
278
+ // encoding: 'utf8',
279
+ // }, (error, stdout, stderr) => {
280
+ // if (error) {
281
+ // console.error('错误:', error.message);
282
+ // reject(error);
283
+ // return;
284
+ // }
285
+ // console.log('启动成功');
286
+ // resolve();
287
+ // });
288
+ // });
289
+ }
290
+
291
+ async function openProjectFolders() {
292
+ for (const project of GITLAB_PROJECT_LIST) {
293
+ await openFolderWithApp(CodingEditPath, project.savePath);
294
+ }
295
+ }
296
+
297
+ //git拉取当前分支的代码,执行npm run dev
298
+ // 拉取代码并启动项目的核心函数
299
+ async function gitPullLatestCode() {
300
+ for (const project of GITLAB_PROJECT_LIST) {
301
+ const { savePath, codePath, gitUrl } = project;
302
+ const projectName = getProjectNameFromGitUrl(gitUrl);
303
+ // 用path.join拼接路径,自动适配系统分隔符(\或/)
304
+ const projectPath = path.join(savePath, projectName);
305
+ const codePathFull = path.join(savePath, codePath);
306
+
307
+ console.log(`===== 开始处理项目:${projectName} =====`);
308
+
309
+ // 拉取最新代码
310
+ try {
311
+ // 前置校验:项目路径是否存在
312
+ checkPathExist(projectPath);
313
+ // 前置校验:是否为Git仓库
314
+ checkGitRepository(projectPath);
315
+
316
+ console.log(`拉取最新代码:${projectPath}`);
317
+ const pullResult = await execPromise1(`cd "${projectPath}" && git pull`);
318
+ console.log(`拉取最新代码成功:${projectPath}\n${pullResult.stdout}`);
319
+ } catch (error) {
320
+ console.error(`拉取最新代码失败:${projectPath}\n${error.message}`);
321
+ // 拉取失败仍继续处理npm(也可以根据需要return跳过)
322
+ }
323
+
324
+ // 执行npm相关命令
325
+ try {
326
+ // 前置校验:webapp路径是否存在
327
+ checkPathExist(codePathFull);
328
+ // 前置校验:是否有package.json
329
+ checkPackageJson(codePathFull);
330
+
331
+ // console.log(`安装/更新依赖:${codePathFull}`);
332
+ // // 先安装依赖(--force解决可能的依赖冲突)
333
+ // await execPromise1(`cd "${codePathFull}" && npm install --force`);
334
+
335
+ console.log(`执行npm run dev:${codePathFull}`);
336
+ // 关键:加&让dev后台运行(Windows用start,Mac/Linux用&)
337
+ // Windows:start cmd /k 保持窗口打开,方便查看日志
338
+ const devCommand = process.platform === 'win32' ?
339
+ `cd "${codePathFull}" && start cmd /k npm run dev` :
340
+ `cd "${codePathFull}" && npm run dev &`;
341
+ await execPromise1(devCommand);
342
+ console.log(`执行npm run dev成功:${codePathFull}(已后台启动)`);
343
+ } catch (error) {
344
+ console.error(`执行npm run dev失败:${codePathFull}\n${error.message}`);
345
+ }
346
+
347
+ console.log(`===== 结束处理项目:${projectName} =====\n`);
348
+ }
349
+ }
350
+
351
+ // 封装带Promise的exec,增加超时和错误详情
352
+ function execPromise1(command, options = {}) {
353
+ return new Promise((resolve, reject) => {
354
+ const child = exec(command, { timeout: 60000, ...options }, (error, stdout, stderr) => {
355
+ if (error) {
356
+ // 整合错误信息:包含命令、错误、stdout、stderr
357
+ error.message = `命令执行失败:${command}\n错误详情:${error.message}\nstdout:${stdout}\nstderr:${stderr}`;
358
+ reject(error);
359
+ return;
360
+ }
361
+ resolve({ stdout, stderr });
362
+ });
363
+ });
364
+ }
365
+ // 校验路径是否存在
366
+ function checkPathExist(checkPath) {
367
+ if (!fs.existsSync(checkPath)) {
368
+ throw new Error(`路径不存在:${checkPath}`);
369
+ }
370
+ }
371
+
372
+ // 校验是否为Git仓库
373
+ function checkGitRepository(repoPath) {
374
+ const gitDir = path.join(repoPath, '.git');
375
+ if (!fs.existsSync(gitDir)) {
376
+ throw new Error(`不是Git仓库:${repoPath}(未找到.git目录)`);
377
+ }
378
+ }
379
+
380
+ // 校验是否有package.json
381
+ function checkPackageJson(projectPath) {
382
+ const pkgPath = path.join(projectPath, 'package.json');
383
+ if (!fs.existsSync(pkgPath)) {
384
+ throw new Error(`未找到package.json:${pkgPath}`);
385
+ }
386
+ }
387
+
388
+
389
+
390
+ // ==================== 脚本说明 =====================
391
+ // 本脚本用于批量克隆GitLab上的多个项目,并切换到指定分支。
392
+ // 支持为每个项目单独指定存放路径,或使用默认路径(脚本所在目录)。
393
+ // 使用前请确保已安装Git,并根据实际需求修改配置项。
394
+ // =====================================================
395
+
396
+ /**
397
+ * 批量克隆GitLab项目并切换指定分支(支持指定存放路径)
398
+ * @param {Array} projectConfigList 项目配置数组
399
+ * 格式:[{ gitUrl: 'GitLab项目地址', branch: '目标分支名', savePath?: '自定义存放路径' }, ...]
400
+ */
401
+ async function batchCloneAndSwitchBranch(projectConfigList) {
402
+ if (!Array.isArray(projectConfigList) || projectConfigList.length === 0) {
403
+ console.error('❌ 错误:请传入有效的项目配置数组(包含gitUrl和branch)');
404
+ return;
405
+ }
406
+
407
+ console.log(`🚀 开始批量处理 ${projectConfigList.length} 个GitLab项目...\n`);
408
+
409
+ // 遍历每个项目,按顺序执行克隆和切换分支
410
+ for (let index = 0; index < projectConfigList.length; index++) {
411
+ const { gitUrl, branch, savePath } = projectConfigList[index];
412
+ const projectIndex = index + 1;
413
+
414
+ // 校验核心配置项是否完整
415
+ if (!gitUrl || !branch) {
416
+ console.error(`❌ 第 ${projectIndex} 个项目配置不完整,缺少gitUrl或branch,跳过该项目`);
417
+ console.log(`----------------------------------------------------\n`);
418
+ continue;
419
+ }
420
+
421
+ try {
422
+ console.log(`====================================================`);
423
+ console.log(`正在处理第 ${projectIndex} 个项目`);
424
+ console.log(`Git地址:${gitUrl}`);
425
+ console.log(`目标分支:${branch}`);
426
+ console.log(`指定存放路径:${savePath || '默认(脚本所在目录)'}`);
427
+ console.log(`====================================================`);
428
+
429
+ // 1. 提取项目名称(从git地址中解析,支持.git后缀和无后缀)
430
+ const projectName = getProjectNameFromGitUrl(gitUrl);
431
+ console.log(`\n🔍 解析出项目名称:${projectName}`);
432
+
433
+ // 2. 确定最终的项目存放目录(优先使用单个项目自定义路径,无则使用默认路径)
434
+ let finalProjectDir;
435
+ if (savePath) {
436
+ // 格式化自定义存放路径(兼容Windows/Mac/Linux路径分隔符)
437
+ const normalizedSavePath = path.normalize(savePath);
438
+ // 确保自定义存放目录存在,不存在则自动创建
439
+ if (!fs.existsSync(normalizedSavePath)) {
440
+ fs.mkdirSync(normalizedSavePath, { recursive: true }); // recursive: true 支持创建多级目录
441
+ console.log(`ℹ️ 自定义存放目录不存在,已自动创建:${normalizedSavePath}`);
442
+ }
443
+ // 拼接「自定义存放路径 + 项目名称」作为最终项目目录
444
+ finalProjectDir = path.join(normalizedSavePath, projectName);
445
+ } else {
446
+ // 默认路径:脚本所在目录 + 项目名称
447
+ finalProjectDir = path.resolve(process.cwd(), projectName);
448
+ }
449
+ console.log(`\n📂 项目最终存放目录:${finalProjectDir}`);
450
+
451
+ // 3. 检查项目是否已存在,避免重复克隆
452
+ if (fs.existsSync(finalProjectDir)) {
453
+ console.log(`ℹ️ 项目目录 ${projectName} 已存在,跳过克隆,直接切换分支`);
454
+ // 切换到项目目录,执行分支切换
455
+ switchBranch(finalProjectDir, branch);
456
+ } else {
457
+ // 4. 克隆GitLab项目到指定目录
458
+ console.log(`\n📥 开始克隆项目到指定目录...`);
459
+ // git clone 语法:git clone <git地址> <目标存放路径/项目名>
460
+ execSync(`git clone ${gitUrl} "${finalProjectDir}"`, {
461
+ stdio: 'inherit', // 实时输出克隆日志到控制台
462
+ encoding: 'utf-8'
463
+ });
464
+ console.log(`✅ 项目 ${projectName} 克隆成功`);
465
+
466
+ // 5. 切换到指定分支
467
+ switchBranch(finalProjectDir, branch);
468
+ }
469
+
470
+ console.log(`\n✅ 第 ${projectIndex} 个项目处理完成!`);
471
+ console.log(`----------------------------------------------------\n`);
472
+
473
+ } catch (error) {
474
+ console.error(`\n❌ 第 ${projectIndex} 个项目处理失败:${error.message}`);
475
+ console.log(`----------------------------------------------------\n`);
476
+ continue;
477
+ }
478
+ }
479
+
480
+ console.log(`🎉 所有项目批量处理完毕!`);
481
+ }
482
+
483
+ /**
484
+ * 从Git地址中提取项目名称
485
+ * @param {string} gitUrl GitLab项目地址(支持http/https/ssh格式)
486
+ * @returns {string} 项目名称
487
+ */
488
+ function getProjectNameFromGitUrl(gitUrl) {
489
+ // 处理规则:截取最后一个斜杠后的内容,去除.git后缀(如果有)
490
+ const lastSlashIndex = gitUrl.lastIndexOf('/');
491
+ let projectName = gitUrl.slice(lastSlashIndex + 1);
492
+ // 去除.git后缀
493
+ if (projectName.endsWith('.git')) {
494
+ projectName = projectName.slice(0, -4);
495
+ }
496
+ return projectName;
497
+ }
498
+
499
+ /**
500
+ * 切换项目到指定分支
501
+ * @param {string} projectDir 项目目录路径
502
+ * @param {string} targetBranch 目标分支名
503
+ */
504
+ function switchBranch(projectDir, targetBranch) {
505
+ console.log(`\n🔀 开始切换到 ${targetBranch} 分支...`);
506
+
507
+ // 1. 进入项目目录
508
+ process.chdir(projectDir);
509
+
510
+ // 2. 拉取远程所有分支信息(确保本地有目标分支的缓存)
511
+ console.log(`ℹ️ 拉取远程分支最新信息...`);
512
+ execSync(`git fetch origin`, {
513
+ stdio: 'inherit',
514
+ encoding: 'utf-8'
515
+ });
516
+
517
+ // 3. 尝试切换分支(优先使用git switch,兼容git checkout)
518
+ try {
519
+ // 先检查本地是否存在目标分支
520
+ const localBranches = execSync(`git branch`, { encoding: 'utf-8' });
521
+ if (localBranches.includes(targetBranch)) {
522
+ // 本地已存在分支,直接切换
523
+ execSync(`git switch ${targetBranch}`, { stdio: 'inherit' });
524
+ } else {
525
+ // 本地不存在,拉取远程分支并切换(一步到位)
526
+ execSync(`git switch -c ${targetBranch} origin/${targetBranch}`, { stdio: 'inherit' });
527
+ }
528
+ console.log(`✅ 成功切换到 ${targetBranch} 分支`);
529
+ } catch (switchError) {
530
+ // 兼容旧版本Git(无git switch命令),使用git checkout
531
+ console.log(`ℹ️ git switch命令不支持,使用git checkout兼容...`);
532
+ try {
533
+ const localBranches = execSync(`git branch`, { encoding: 'utf-8' });
534
+ if (localBranches.includes(targetBranch)) {
535
+ execSync(`git checkout ${targetBranch}`, { stdio: 'inherit' });
536
+ } else {
537
+ execSync(`git checkout -b ${targetBranch} origin/${targetBranch}`, { stdio: 'inherit' });
538
+ }
539
+ console.log(`✅ 成功切换到 ${targetBranch} 分支`);
540
+ } catch (checkoutError) {
541
+ throw new Error(`切换分支失败:${checkoutError.message}`);
542
+ }
543
+ }
544
+ }
545
+
546
+
547
+ // ===================== 脚本说明 =====================
548
+ // 本脚本用于批量清理多个Node.js项目的依赖文件(node_modules和package-lock.json),
549
+ // 并重新安装依赖。适用于需要统一更新依赖或解决依赖冲突的场景。
550
+ // 使用前请确保已安装Node.js和npm,并根据实际项目路径修改配置项。
551
+ // =====================================================
552
+
553
+
554
+ /**
555
+ * 休眠函数(可选,用于日志输出更清晰,可删除)
556
+ * @param {number} ms 休眠毫秒数
557
+ */
558
+ function sleep(ms) {
559
+ return new Promise(resolve => setTimeout(resolve, ms));
560
+ }
561
+
562
+ /**
563
+ * 删除文件/目录
564
+ * @param {string} targetPath 要删除的文件/目录路径
565
+ */
566
+ function deleteTarget(targetPath) {
567
+ try {
568
+ if (fs.existsSync(targetPath)) {
569
+ // 判断是目录还是文件
570
+ const stats = fs.statSync(targetPath);
571
+ if (stats.isDirectory()) {
572
+ // 删除目录(递归删除所有内容)
573
+ fs.rmSync(targetPath, { recursive: true, force: true });
574
+ console.log(`✅ 成功删除目录:${targetPath}`);
575
+ } else {
576
+ // 删除文件
577
+ fs.unlinkSync(targetPath);
578
+ console.log(`✅ 成功删除文件:${targetPath}`);
579
+ }
580
+ } else {
581
+ console.log(`ℹ️ 目标不存在,无需删除:${targetPath}`);
582
+ }
583
+ } catch (error) {
584
+ console.error(`❌ 删除失败:${targetPath},错误信息:${error.message}`);
585
+ // 若删除失败,抛出错误终止当前项目处理,避免影响后续项目
586
+ throw error;
587
+ }
588
+ }
589
+
590
+ /**
591
+ * 处理单个项目的完整流程
592
+ * @param {string} projectPath 项目路径
593
+ */
594
+ async function handleSingleProject(projectPath) {
595
+ try {
596
+ // 1. 格式化项目路径(解决不同系统路径分隔符问题)
597
+ const normalizedProjectPath = path.normalize(projectPath);
598
+ console.log(`\n=================================================`);
599
+ console.log(`开始处理项目:${normalizedProjectPath}`);
600
+ console.log(`=================================================`);
601
+
602
+ // 2. 定义要删除的目标文件/目录路径
603
+ const nodeModulesPath = path.join(normalizedProjectPath, 'node_modules');
604
+ const packageLockPath = path.join(normalizedProjectPath, 'package-lock.json');
605
+
606
+ // 3. 顺序删除 node_modules 和 package-lock.json
607
+ console.log(`\n--- 开始删除依赖文件/目录 ---`);
608
+ deleteTarget(nodeModulesPath); // 先删node_modules
609
+ deleteTarget(packageLockPath); // 再删package-lock.json
610
+
611
+ // 4. 清除npm依赖缓存(--force 强制清除)
612
+ console.log(`\n--- 开始清除npm依赖缓存 ---`);
613
+ execSync('npm cache clean --force', {
614
+ cwd: normalizedProjectPath, // 执行目录:当前项目根目录
615
+ stdio: 'inherit' // 输出日志到控制台
616
+ });
617
+ console.log(`✅ npm依赖缓存清除成功`);
618
+
619
+ // 5. 执行npm install安装依赖
620
+ console.log(`\n--- 开始执行npm install ---`);
621
+ execSync('npm i', {
622
+ cwd: normalizedProjectPath,
623
+ stdio: 'inherit'
624
+ });
625
+ console.log(`✅ 项目 ${normalizedProjectPath} 依赖安装完成`);
626
+
627
+ // 可选:休眠1秒,让日志输出更清晰(可根据需要调整或删除)
628
+ await sleep(1000);
629
+
630
+ } catch (error) {
631
+ console.error(`\n❌ 项目 ${projectPath} 处理失败,跳过该项目继续处理下一个`, error.message);
632
+ }
633
+ }
634
+
635
+ /**
636
+ * 批量处理所有项目(按顺序执行)
637
+ */
638
+ async function batchHandleProjects(PROJECT) {
639
+ console.log(`🚀 开始(任务2)批量处理 ${PROJECT.length} 个项目...`);
640
+ console.log(`🚀 请确保项目未启动,否则依赖删除失败!!!`);
641
+ for (const project of PROJECT) {
642
+ const projectPath = path.join(project.savePath, project.codePath);
643
+ await handleSingleProject(projectPath); // 按顺序处理,上一个项目完成后再处理下一个
644
+ }
645
+ console.log(`\n🎉 所有项目处理完毕!`);
646
+ }