fe-build-cli 1.1.0 → 1.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fe-build-cli",
3
- "version": "1.1.0",
4
- "description": "前端项目打包部署 CLI 工具,支持多服务器部署、分支管理、回滚、钉钉通知等功能",
3
+ "version": "1.2.2",
4
+ "description": "前端项目打包部署 CLI 工具,支持多服务器部署、分支管理、回滚、钉钉通知、智能处理本地改动等功能",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
7
7
  "bin": {
@@ -27,7 +27,8 @@
27
27
  "build",
28
28
  "ssh",
29
29
  "git",
30
- "dingtalk"
30
+ "dingtalk",
31
+ "stash"
31
32
  ],
32
33
  "author": "",
33
34
  "license": "MIT",
package/src/cli.js CHANGED
@@ -11,7 +11,9 @@ import {
11
11
  getGitSha,
12
12
  executeMainBranchFlow,
13
13
  executeCurrentBranchFlow,
14
- restoreBranch
14
+ executeTestBranchFlow,
15
+ restoreBranch,
16
+ stashPop
15
17
  } from './git-branch.js';
16
18
  import {
17
19
  sendDeploySuccessNotification,
@@ -169,13 +171,19 @@ fe-build-cli - 前端项目打包部署工具
169
171
  --config <路径> 指定配置文件路径
170
172
  --current-branch 使用当前分支发布(不切换分支)
171
173
  --main-branch 使用主分支发布流程(合并到测试分支再合并到主分支)
174
+ --test-branch 使用 test 环境发布流程(智能处理本地改动)
175
+ --merge test 发布时合并本地改动(提交+推送+合并)
176
+ --no-merge test 发布时不合并,使用 stash 储藏本地改动
172
177
  --skip-build 跳过构建步骤
173
- --no-push 主分支发布时不推送到远程
178
+ --no-push 发布时不推送到远程
174
179
 
175
180
  示例:
176
181
  fe-build # 交互式选择环境部署
177
182
  fe-build deploy production # 部署到生产环境
178
- fe-build deploy all # 部署到所有环境
183
+ fe-build deploy test # 部署到测试环境(使用配置的 deployMode)
184
+ fe-build --test-branch # test 环境发布(智能处理本地改动)
185
+ fe-build --test-branch --merge # test 发布,合并本地改动
186
+ fe-build --test-branch --no-merge # test 发布,stash 储藏改动
179
187
  fe-build --current-branch # 当前分支发布
180
188
  fe-build --main-branch # 主分支发布流程
181
189
  fe-build rollback production # 回滚生产环境
@@ -222,13 +230,18 @@ async function deployCommand(config) {
222
230
  const args = process.argv.slice(2);
223
231
  const useCurrentBranch = args.includes('--current-branch');
224
232
  const useMainBranch = args.includes('--main-branch');
233
+ const useTestBranch = args.includes('--test-branch');
234
+ const useMerge = args.includes('--merge');
235
+ const useNoMerge = args.includes('--no-merge');
225
236
  const skipBuild = args.includes('--skip-build');
226
237
  const noPush = args.includes('--no-push');
227
238
 
228
239
  // 确定发布模式
229
240
  let deployMode = config.deployMode || 'main'; // 默认主分支发布
230
241
 
231
- if (useCurrentBranch) {
242
+ if (useTestBranch) {
243
+ deployMode = 'test';
244
+ } else if (useCurrentBranch) {
232
245
  deployMode = 'current';
233
246
  } else if (useMainBranch) {
234
247
  deployMode = 'main';
@@ -268,35 +281,84 @@ async function deployCommand(config) {
268
281
 
269
282
  // 执行分支发布流程
270
283
  let branchResult = null;
271
- let originalBranch = null;
284
+ let originalBranch = getCurrentBranch(); // 记录原始分支
285
+ let needRestore = false;
286
+ let hasStash = false;
287
+ let autoRestore = false; // 是否自动切回(合并模式)
272
288
 
273
- if (deployMode === 'main' && !skipBuild) {
274
- // 主分支发布模式
275
- const branches = config.branches || { test: 'test', main: 'main' };
276
- console.log('\n========================================');
277
- console.log(' 🌿 主分支发布模式');
278
- console.log('========================================');
279
- console.log(`测试分支: ${branches.test}`);
280
- console.log(`主分支: ${branches.main}`);
281
- console.log('========================================');
289
+ // 分支流程(Git 操作)
290
+ try {
291
+ if (deployMode === 'test' && !skipBuild) {
292
+ // test 环境发布模式(智能处理本地改动)
293
+ const branches = config.branches || { test: 'test', main: 'main' };
294
+
295
+ // 确定合并选项
296
+ let mergeChanges = undefined;
297
+ if (useMerge) {
298
+ mergeChanges = true;
299
+ } else if (useNoMerge) {
300
+ mergeChanges = false;
301
+ }
282
302
 
283
- const confirmAnswer = await prompt('确认执行主分支发布流程? (y/n): ');
284
- if (confirmAnswer.toLowerCase() !== 'y') {
285
- console.log('已取消发布');
286
- process.exit(0);
303
+ branchResult = await executeTestBranchFlow({
304
+ testBranch: branches.test,
305
+ mergeChanges,
306
+ pushToRemote: !noPush,
307
+ prompt
308
+ });
309
+ originalBranch = branchResult.originalBranch;
310
+ needRestore = branchResult.needRestore;
311
+ hasStash = branchResult.hasStash;
312
+ autoRestore = branchResult.autoRestore || false;
313
+ } else if (deployMode === 'main' && !skipBuild) {
314
+ // 主分支发布模式
315
+ const branches = config.branches || { test: 'test', main: 'main' };
316
+ console.log('\n========================================');
317
+ console.log(' 🌿 主分支发布模式');
318
+ console.log('========================================');
319
+ console.log(`测试分支: ${branches.test}`);
320
+ console.log(`主分支: ${branches.main}`);
321
+ console.log('========================================');
322
+
323
+ const confirmAnswer = await prompt('确认执行主分支发布流程? (y/n): ');
324
+ if (confirmAnswer.toLowerCase() !== 'y') {
325
+ console.log('已取消发布');
326
+ process.exit(0);
327
+ }
328
+
329
+ branchResult = executeMainBranchFlow({
330
+ testBranch: branches.test,
331
+ mainBranch: branches.main,
332
+ pushToRemote: !noPush
333
+ });
334
+ originalBranch = branchResult.originalBranch;
335
+ needRestore = true;
336
+ } else if (deployMode === 'current') {
337
+ // 当前分支发布模式
338
+ branchResult = executeCurrentBranchFlow();
339
+ originalBranch = branchResult.currentBranch;
340
+ needRestore = false;
341
+ console.log('📌 当前分支发布模式:不切换分支');
287
342
  }
343
+ } catch (branchError) {
344
+ // 分支流程失败,发送钉钉通知
345
+ console.error(`❌ 分支流程失败:`, branchError.message);
288
346
 
289
- branchResult = executeMainBranchFlow({
290
- testBranch: branches.test,
291
- mainBranch: branches.main,
292
- pushToRemote: !noPush
293
- });
294
- originalBranch = branchResult.originalBranch;
295
- } else if (deployMode === 'current') {
296
- // 当前分支发布模式
297
- branchResult = executeCurrentBranchFlow();
298
- originalBranch = branchResult.currentBranch;
299
- console.log('📌 当前分支发布模式:不切换分支');
347
+ if (config.dingtalk && config.dingtalk.enabled && config.dingtalk.webhook) {
348
+ console.log('\n发送钉钉失败通知...');
349
+ const envConfig = getServerConfig(config, selectedServers[0] || serverNames[0]);
350
+ await sendDeployFailureNotification(config.dingtalk.webhook, {
351
+ environment: selectedServers[0] || serverNames[0],
352
+ buildVersion: '未完成',
353
+ serverHost: envConfig?.sshHost || '未知',
354
+ branch: originalBranch,
355
+ error: `分支流程失败: ${branchError.message}`
356
+ });
357
+ }
358
+
359
+ // 切回原分支
360
+ restoreBranch(originalBranch, hasStash);
361
+ process.exit(1);
300
362
  }
301
363
 
302
364
  // 生成构建版本
@@ -366,19 +428,30 @@ async function deployCommand(config) {
366
428
  });
367
429
  }
368
430
 
369
- // 如果是主分支发布模式,尝试切回原分支
370
- if (originalBranch) {
371
- restoreBranch(originalBranch);
431
+ // 出错时切回原分支
432
+ if (needRestore && originalBranch) {
433
+ restoreBranch(originalBranch, hasStash);
372
434
  }
373
435
  process.exit(1);
374
436
  }
375
437
  }
376
438
 
377
- // 主分支发布模式:部署完成后切回原分支
378
- if (deployMode === 'main' && originalBranch) {
379
- const returnAnswer = await prompt('\n是否切回原分支? (y/n): ');
380
- if (returnAnswer.toLowerCase() === 'y') {
381
- restoreBranch(originalBranch);
439
+ // 部署完成后切回原分支
440
+ if (needRestore && originalBranch) {
441
+ // 合并模式:自动切回原分支
442
+ if (autoRestore) {
443
+ console.log('\n📌 自动切回原分支...');
444
+ restoreBranch(originalBranch, false);
445
+ console.log(`✅ 已切回 ${originalBranch},可继续开发`);
446
+ } else {
447
+ // stash 模式:询问是否切回
448
+ const returnAnswer = await prompt('\n是否切回原分支? (y/n): ');
449
+ if (returnAnswer.toLowerCase() === 'y') {
450
+ restoreBranch(originalBranch, hasStash);
451
+ } else if (hasStash) {
452
+ console.log('\n💡 提示: 本地改动已储藏,执行以下命令恢复:');
453
+ console.log(' git stash pop');
454
+ }
382
455
  }
383
456
  }
384
457
  }
package/src/git-branch.js CHANGED
@@ -127,6 +127,63 @@ export function pushBranch(branchName) {
127
127
  }
128
128
  }
129
129
 
130
+ /**
131
+ * 储藏本地改动(stash)
132
+ * @param {string} branchName - 当前分支名(用于备注)
133
+ * @returns {boolean} 是否成功
134
+ */
135
+ export function stashChanges(branchName = '') {
136
+ try {
137
+ console.log('\n📦 储藏本地改动...');
138
+ const stashMessage = branchName
139
+ ? `stash for test deploy from ${branchName}`
140
+ : 'fe-build-cli-auto-stash';
141
+ execSync(`git stash push -m "${stashMessage}"`, { stdio: 'inherit' });
142
+ console.log('✅ 本地改动已储藏');
143
+ return true;
144
+ } catch (error) {
145
+ console.error('❌ 储藏失败:', error.message);
146
+ return false;
147
+ }
148
+ }
149
+
150
+ /**
151
+ * 恢复储藏的改动(stash pop)
152
+ * @returns {boolean} 是否成功
153
+ */
154
+ export function stashPop() {
155
+ try {
156
+ console.log('\n📦 恢复储藏的改动...');
157
+ execSync('git stash pop', { stdio: 'inherit' });
158
+ console.log('✅ 本地改动已恢复');
159
+ return true;
160
+ } catch (error) {
161
+ console.error('❌ 恢复失败:', error.message);
162
+ console.log('💡 提示: 可能存在冲突,请手动执行 git stash pop 解决');
163
+ return false;
164
+ }
165
+ }
166
+
167
+ /**
168
+ * 自动提交当前分支的所有改动
169
+ * @param {string} message - 提交信息
170
+ * @returns {boolean} 是否成功
171
+ */
172
+ export function autoCommit(message) {
173
+ try {
174
+ console.log('\n📝 自动提交改动...');
175
+ // 添加所有改动
176
+ execSync('git add -A', { stdio: 'inherit' });
177
+ // 提交
178
+ execSync(`git commit -m "${message}"`, { stdio: 'inherit' });
179
+ console.log('✅ 改动已提交');
180
+ return true;
181
+ } catch (error) {
182
+ console.error('❌ 提交失败:', error.message);
183
+ return false;
184
+ }
185
+ }
186
+
130
187
  /**
131
188
  * 执行主分支发布流程
132
189
  * 流程:当前分支 -> 测试分支 -> 主分支
@@ -232,16 +289,208 @@ export function executeCurrentBranchFlow() {
232
289
  };
233
290
  }
234
291
 
292
+ /**
293
+ * 执行 test 环境发布流程(智能模式)
294
+ * @param {object} config - 配置
295
+ * @param {string} config.testBranch - test 分支名
296
+ * @param {boolean} config.mergeChanges - 是否合并本地改动
297
+ * @param {boolean} config.pushToRemote - 是否推送到远程
298
+ * @param {function} config.prompt - 交互提示函数
299
+ * @returns {object} 执行结果
300
+ */
301
+ export async function executeTestBranchFlow(config) {
302
+ const { testBranch, mergeChanges, pushToRemote = true, prompt } = config;
303
+ const originalBranch = getCurrentBranch();
304
+ const { clean, changes } = checkUncommittedChanges();
305
+
306
+ console.log('\n========================================');
307
+ console.log(' 🌿 Test 环境发布流程');
308
+ console.log('========================================');
309
+ console.log(`当前分支: ${originalBranch}`);
310
+ console.log(`Test 分支: ${testBranch}`);
311
+ console.log(`工作区状态: ${clean ? '干净' : '有未提交改动'}`);
312
+ console.log('========================================');
313
+
314
+ // 情况1:当前分支本身就是 test
315
+ if (originalBranch === testBranch) {
316
+ console.log('\n✓ 当前已在 test 分支');
317
+ pullBranch(testBranch);
318
+ return {
319
+ success: true,
320
+ originalBranch,
321
+ currentBranch: testBranch,
322
+ needRestore: false,
323
+ hasStash: false
324
+ };
325
+ }
326
+
327
+ // 情况2:当前分支 ≠ test,无本地改动
328
+ if (clean) {
329
+ console.log('\n✓ 无本地改动,直接切换发布');
330
+ checkoutBranch(testBranch);
331
+ pullBranch(testBranch);
332
+ return {
333
+ success: true,
334
+ originalBranch,
335
+ currentBranch: testBranch,
336
+ needRestore: true,
337
+ hasStash: false,
338
+ autoRestore: true // 无改动模式自动切回
339
+ };
340
+ }
341
+
342
+ // 情况3:当前分支 ≠ test,有本地改动
343
+ console.log('\n⚠️ 存在未提交的改动:');
344
+ changes.forEach(change => console.log(` ${change}`));
345
+
346
+ // 如果已确定合并选项,直接执行
347
+ if (mergeChanges === true) {
348
+ return await executeTestMergeFlow(originalBranch, testBranch, pushToRemote);
349
+ } else if (mergeChanges === false) {
350
+ return await executeTestStashFlow(originalBranch, testBranch);
351
+ }
352
+
353
+ // 否则交互询问
354
+ if (prompt) {
355
+ console.log('\n请选择处理方式:');
356
+ console.log(' 1. 合并改动到 test 发布(提交当前分支,合并到 test)');
357
+ console.log(' 2. 不合并,暂存改动(stash 储藏,用纯净 test 发布)');
358
+
359
+ const answer = await prompt('请输入选项 (1/2): ');
360
+
361
+ if (answer === '1') {
362
+ return await executeTestMergeFlow(originalBranch, testBranch, pushToRemote);
363
+ } else if (answer === '2') {
364
+ return await executeTestStashFlow(originalBranch, testBranch);
365
+ } else {
366
+ throw new Error('无效选项,已取消发布');
367
+ }
368
+ }
369
+
370
+ throw new Error('存在未提交改动,请选择处理方式');
371
+ }
372
+
373
+ /**
374
+ * 执行合并流程(提交当前分支,合并到 test)
375
+ */
376
+ async function executeTestMergeFlow(originalBranch, testBranch, pushToRemote) {
377
+ console.log('\n========================================');
378
+ console.log(' 🔀 合并改动发布模式');
379
+ console.log('========================================');
380
+
381
+ try {
382
+ // 1. 自动提交当前分支改动(备注包含原分支名)
383
+ const commitMessage = `auto commit: deploy test from ${originalBranch}`;
384
+ if (!autoCommit(commitMessage)) {
385
+ throw new Error('自动提交失败');
386
+ }
387
+
388
+ // 2. 推送当前分支到远程
389
+ if (pushToRemote) {
390
+ pushBranch(originalBranch);
391
+ }
392
+
393
+ // 3. 切换到 test 分支
394
+ checkoutBranch(testBranch);
395
+ pullBranch(testBranch);
396
+
397
+ // 4. 合并当前分支到 test
398
+ if (!mergeBranch(originalBranch, testBranch)) {
399
+ // 合并冲突,切回原分支
400
+ checkoutBranch(originalBranch);
401
+ throw new Error(`合并冲突: ${originalBranch} 到 ${testBranch},请手动解决冲突后重新发布`);
402
+ }
403
+
404
+ // 5. 推送 test 分支
405
+ if (pushToRemote) {
406
+ pushBranch(testBranch);
407
+ }
408
+
409
+ console.log('\n========================================');
410
+ console.log('✅ 合并发布流程完成');
411
+ console.log(`当前分支: ${testBranch}`);
412
+ console.log('========================================');
413
+
414
+ return {
415
+ success: true,
416
+ originalBranch,
417
+ currentBranch: testBranch,
418
+ needRestore: true,
419
+ hasStash: false,
420
+ merged: true,
421
+ autoRestore: true // 合并模式自动切回
422
+ };
423
+ } catch (error) {
424
+ // 出错时切回原分支
425
+ try {
426
+ checkoutBranch(originalBranch);
427
+ } catch (e) {
428
+ // 忽略
429
+ }
430
+ throw error;
431
+ }
432
+ }
433
+
434
+ /**
435
+ * 执行暂存流程(stash 储藏,用纯净 test 发布)
436
+ */
437
+ async function executeTestStashFlow(originalBranch, testBranch) {
438
+ console.log('\n========================================');
439
+ console.log(' 📦 暂存改动发布模式');
440
+ console.log('========================================');
441
+
442
+ try {
443
+ // 1. 储藏本地改动(备注包含原分支名)
444
+ if (!stashChanges(originalBranch)) {
445
+ throw new Error('储藏改动失败');
446
+ }
447
+
448
+ // 2. 切换到 test 分支
449
+ checkoutBranch(testBranch);
450
+ pullBranch(testBranch);
451
+
452
+ console.log('\n========================================');
453
+ console.log('✅ 暂存发布流程完成');
454
+ console.log(`当前分支: ${testBranch}`);
455
+ console.log('========================================');
456
+
457
+ return {
458
+ success: true,
459
+ originalBranch,
460
+ currentBranch: testBranch,
461
+ needRestore: true,
462
+ hasStash: true,
463
+ autoRestore: false // stash 模式询问是否切回
464
+ };
465
+ } catch (error) {
466
+ // 出错时切回原分支并恢复 stash
467
+ try {
468
+ checkoutBranch(originalBranch);
469
+ stashPop();
470
+ } catch (e) {
471
+ // 忽略
472
+ }
473
+ throw error;
474
+ }
475
+ }
476
+
235
477
  /**
236
478
  * 发布后切回原分支
237
479
  * @param {string} originalBranch - 原分支名
480
+ * @param {boolean} hasStash - 是否有储藏的改动
238
481
  */
239
- export function restoreBranch(originalBranch) {
482
+ export function restoreBranch(originalBranch, hasStash = false) {
240
483
  const currentBranch = getCurrentBranch();
241
484
  if (currentBranch !== originalBranch) {
242
485
  console.log(`\n📌 切回原分支: ${originalBranch}`);
243
486
  checkoutBranch(originalBranch);
244
487
  }
488
+
489
+ // 如果有储藏的改动,提示恢复
490
+ if (hasStash) {
491
+ console.log('\n💡 提示: 本地改动已储藏,执行以下命令恢复:');
492
+ console.log(' git stash pop');
493
+ }
245
494
  }
246
495
 
247
496
  export default {
@@ -253,7 +502,11 @@ export default {
253
502
  pullBranch,
254
503
  mergeBranch,
255
504
  pushBranch,
505
+ stashChanges,
506
+ stashPop,
507
+ autoCommit,
256
508
  executeMainBranchFlow,
257
509
  executeCurrentBranchFlow,
510
+ executeTestBranchFlow,
258
511
  restoreBranch
259
512
  };
package/src/index.js CHANGED
@@ -17,8 +17,12 @@ export {
17
17
  pullBranch,
18
18
  mergeBranch,
19
19
  pushBranch,
20
+ stashChanges,
21
+ stashPop,
22
+ autoCommit,
20
23
  executeMainBranchFlow,
21
24
  executeCurrentBranchFlow,
25
+ executeTestBranchFlow,
22
26
  restoreBranch
23
27
  } from './git-branch.js';
24
28