fe-build-cli 1.1.0 → 1.2.3
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 +4 -3
- package/src/cli.js +119 -41
- package/src/config-template.js +2 -1
- package/src/dingtalk.js +29 -14
- package/src/git-branch.js +254 -1
- package/src/index.d.ts +2 -0
- package/src/index.js +4 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fe-build-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "前端项目打包部署 CLI
|
|
3
|
+
"version": "1.2.3",
|
|
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
|
-
|
|
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
|
|
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 (
|
|
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,85 @@ async function deployCommand(config) {
|
|
|
268
281
|
|
|
269
282
|
// 执行分支发布流程
|
|
270
283
|
let branchResult = null;
|
|
271
|
-
let originalBranch =
|
|
284
|
+
let originalBranch = getCurrentBranch(); // 记录原始分支
|
|
285
|
+
let needRestore = false;
|
|
286
|
+
let hasStash = false;
|
|
287
|
+
let autoRestore = false; // 是否自动切回(合并模式)
|
|
272
288
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
+
keyword: config.dingtalk.keyword || '部署'
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// 切回原分支
|
|
361
|
+
restoreBranch(originalBranch, hasStash);
|
|
362
|
+
process.exit(1);
|
|
300
363
|
}
|
|
301
364
|
|
|
302
365
|
// 生成构建版本
|
|
@@ -347,7 +410,8 @@ async function deployCommand(config) {
|
|
|
347
410
|
deployUrl: envConfig.deployUrl,
|
|
348
411
|
branch: currentBranch,
|
|
349
412
|
deployMode,
|
|
350
|
-
duration: `${duration}
|
|
413
|
+
duration: `${duration}秒`,
|
|
414
|
+
keyword: config.dingtalk.keyword || '部署'
|
|
351
415
|
});
|
|
352
416
|
}
|
|
353
417
|
} catch (error) {
|
|
@@ -362,23 +426,35 @@ async function deployCommand(config) {
|
|
|
362
426
|
buildVersion,
|
|
363
427
|
serverHost: envConfig.sshHost,
|
|
364
428
|
branch: currentBranch,
|
|
365
|
-
error: error.message
|
|
429
|
+
error: error.message,
|
|
430
|
+
keyword: config.dingtalk.keyword || '部署'
|
|
366
431
|
});
|
|
367
432
|
}
|
|
368
433
|
|
|
369
|
-
//
|
|
370
|
-
if (originalBranch) {
|
|
371
|
-
restoreBranch(originalBranch);
|
|
434
|
+
// 出错时切回原分支
|
|
435
|
+
if (needRestore && originalBranch) {
|
|
436
|
+
restoreBranch(originalBranch, hasStash);
|
|
372
437
|
}
|
|
373
438
|
process.exit(1);
|
|
374
439
|
}
|
|
375
440
|
}
|
|
376
441
|
|
|
377
|
-
//
|
|
378
|
-
if (
|
|
379
|
-
|
|
380
|
-
if (
|
|
381
|
-
|
|
442
|
+
// 部署完成后切回原分支
|
|
443
|
+
if (needRestore && originalBranch) {
|
|
444
|
+
// 合并模式:自动切回原分支
|
|
445
|
+
if (autoRestore) {
|
|
446
|
+
console.log('\n📌 自动切回原分支...');
|
|
447
|
+
restoreBranch(originalBranch, false);
|
|
448
|
+
console.log(`✅ 已切回 ${originalBranch},可继续开发`);
|
|
449
|
+
} else {
|
|
450
|
+
// stash 模式:询问是否切回
|
|
451
|
+
const returnAnswer = await prompt('\n是否切回原分支? (y/n): ');
|
|
452
|
+
if (returnAnswer.toLowerCase() === 'y') {
|
|
453
|
+
restoreBranch(originalBranch, hasStash);
|
|
454
|
+
} else if (hasStash) {
|
|
455
|
+
console.log('\n💡 提示: 本地改动已储藏,执行以下命令恢复:');
|
|
456
|
+
console.log(' git stash pop');
|
|
457
|
+
}
|
|
382
458
|
}
|
|
383
459
|
}
|
|
384
460
|
}
|
|
@@ -438,7 +514,8 @@ async function rollbackCommand(config) {
|
|
|
438
514
|
backupFile: backupFile || '最新备份',
|
|
439
515
|
serverHost: envConfig.sshHost,
|
|
440
516
|
deployUrl: envConfig.deployUrl,
|
|
441
|
-
success: true
|
|
517
|
+
success: true,
|
|
518
|
+
keyword: config.dingtalk.keyword || '部署'
|
|
442
519
|
});
|
|
443
520
|
}
|
|
444
521
|
} catch (error) {
|
|
@@ -453,7 +530,8 @@ async function rollbackCommand(config) {
|
|
|
453
530
|
backupFile: backupFile || '未知',
|
|
454
531
|
serverHost: envConfig.sshHost,
|
|
455
532
|
deployUrl: envConfig.deployUrl,
|
|
456
|
-
success: false
|
|
533
|
+
success: false,
|
|
534
|
+
keyword: config.dingtalk.keyword || '部署'
|
|
457
535
|
});
|
|
458
536
|
}
|
|
459
537
|
|
package/src/config-template.js
CHANGED
|
@@ -78,6 +78,7 @@ export default {
|
|
|
78
78
|
*/
|
|
79
79
|
dingtalk: {
|
|
80
80
|
webhook: 'https://oapi.dingtalk.com/robot/send?access_token=your-token', // 钉钉机器人 webhook URL
|
|
81
|
-
enabled: true // 是否启用钉钉通知,默认 true
|
|
81
|
+
enabled: true, // 是否启用钉钉通知,默认 true
|
|
82
|
+
keyword: '部署' // 安全设置关键词(如果机器人设置了关键词,必须配置此项)
|
|
82
83
|
}
|
|
83
84
|
};
|
package/src/dingtalk.js
CHANGED
|
@@ -44,6 +44,7 @@ export async function sendDingTalkMessage(webhookUrl, message) {
|
|
|
44
44
|
* @param {string} options.branch - 分支名称
|
|
45
45
|
* @param {string} options.deployMode - 发布模式
|
|
46
46
|
* @param {string} options.duration - 部署耗时(可选)
|
|
47
|
+
* @param {string} options.keyword - 安全关键词(可选)
|
|
47
48
|
*/
|
|
48
49
|
export async function sendDeploySuccessNotification(webhookUrl, options) {
|
|
49
50
|
const {
|
|
@@ -53,7 +54,8 @@ export async function sendDeploySuccessNotification(webhookUrl, options) {
|
|
|
53
54
|
deployUrl,
|
|
54
55
|
branch,
|
|
55
56
|
deployMode,
|
|
56
|
-
duration
|
|
57
|
+
duration,
|
|
58
|
+
keyword = '部署'
|
|
57
59
|
} = options;
|
|
58
60
|
|
|
59
61
|
const now = new Date();
|
|
@@ -66,12 +68,15 @@ export async function sendDeploySuccessNotification(webhookUrl, options) {
|
|
|
66
68
|
second: '2-digit'
|
|
67
69
|
});
|
|
68
70
|
|
|
71
|
+
// 标题必须包含关键词,否则钉钉会拒绝
|
|
72
|
+
const title = `${keyword}成功 - ${environment}`;
|
|
73
|
+
|
|
69
74
|
const message = {
|
|
70
75
|
msgtype: 'markdown',
|
|
71
76
|
markdown: {
|
|
72
|
-
title
|
|
77
|
+
title,
|
|
73
78
|
text: `
|
|
74
|
-
## 🚀
|
|
79
|
+
## 🚀 ${keyword}成功通知
|
|
75
80
|
|
|
76
81
|
**环境**: ${environment}
|
|
77
82
|
**状态**: ✅ 成功
|
|
@@ -79,15 +84,15 @@ export async function sendDeploySuccessNotification(webhookUrl, options) {
|
|
|
79
84
|
|
|
80
85
|
---
|
|
81
86
|
|
|
82
|
-
###
|
|
87
|
+
### ${keyword}详情
|
|
83
88
|
|
|
84
89
|
| 项目 | 内容 |
|
|
85
90
|
|:---:|:---|
|
|
86
91
|
| 构建版本 | ${buildVersion} |
|
|
87
92
|
| 发布分支 | ${branch} |
|
|
88
|
-
| 发布模式 | ${deployMode === 'main' ? '主分支发布' : '当前分支发布'} |
|
|
93
|
+
| 发布模式 | ${deployMode === 'main' ? '主分支发布' : deployMode === 'test' ? 'Test环境发布' : '当前分支发布'} |
|
|
89
94
|
| 服务器 | ${serverHost} |
|
|
90
|
-
${duration ? `|
|
|
95
|
+
${duration ? `| ${keyword}耗时 | ${duration} |` : ''}
|
|
91
96
|
|
|
92
97
|
---
|
|
93
98
|
|
|
@@ -95,7 +100,7 @@ ${duration ? `| 部署耗时 | ${duration} |` : ''}
|
|
|
95
100
|
|
|
96
101
|
[${deployUrl}](${deployUrl})
|
|
97
102
|
|
|
98
|
-
>
|
|
103
|
+
> ${keyword}完成,请及时验证功能是否正常。
|
|
99
104
|
`.trim()
|
|
100
105
|
}
|
|
101
106
|
};
|
|
@@ -112,6 +117,7 @@ ${duration ? `| 部署耗时 | ${duration} |` : ''}
|
|
|
112
117
|
* @param {string} options.serverHost - 服务器地址
|
|
113
118
|
* @param {string} options.branch - 分支名称
|
|
114
119
|
* @param {string} options.error - 错误信息
|
|
120
|
+
* @param {string} options.keyword - 安全关键词(可选)
|
|
115
121
|
*/
|
|
116
122
|
export async function sendDeployFailureNotification(webhookUrl, options) {
|
|
117
123
|
const {
|
|
@@ -119,7 +125,8 @@ export async function sendDeployFailureNotification(webhookUrl, options) {
|
|
|
119
125
|
buildVersion,
|
|
120
126
|
serverHost,
|
|
121
127
|
branch,
|
|
122
|
-
error
|
|
128
|
+
error,
|
|
129
|
+
keyword = '部署'
|
|
123
130
|
} = options;
|
|
124
131
|
|
|
125
132
|
const now = new Date();
|
|
@@ -132,12 +139,15 @@ export async function sendDeployFailureNotification(webhookUrl, options) {
|
|
|
132
139
|
second: '2-digit'
|
|
133
140
|
});
|
|
134
141
|
|
|
142
|
+
// 标题必须包含关键词
|
|
143
|
+
const title = `${keyword}失败 - ${environment}`;
|
|
144
|
+
|
|
135
145
|
const message = {
|
|
136
146
|
msgtype: 'markdown',
|
|
137
147
|
markdown: {
|
|
138
|
-
title
|
|
148
|
+
title,
|
|
139
149
|
text: `
|
|
140
|
-
## ❌
|
|
150
|
+
## ❌ ${keyword}失败通知
|
|
141
151
|
|
|
142
152
|
**环境**: ${environment}
|
|
143
153
|
**状态**: ❌ 失败
|
|
@@ -159,7 +169,7 @@ export async function sendDeployFailureNotification(webhookUrl, options) {
|
|
|
159
169
|
|
|
160
170
|
${error}
|
|
161
171
|
|
|
162
|
-
>
|
|
172
|
+
> 请及时排查问题并重新${keyword}。
|
|
163
173
|
`.trim()
|
|
164
174
|
}
|
|
165
175
|
};
|
|
@@ -176,6 +186,7 @@ ${error}
|
|
|
176
186
|
* @param {string} options.serverHost - 服务器地址
|
|
177
187
|
* @param {string} options.deployUrl - 部署后的访问地址
|
|
178
188
|
* @param {boolean} options.success - 是否成功
|
|
189
|
+
* @param {string} options.keyword - 安全关键词(可选)
|
|
179
190
|
*/
|
|
180
191
|
export async function sendRollbackNotification(webhookUrl, options) {
|
|
181
192
|
const {
|
|
@@ -183,7 +194,8 @@ export async function sendRollbackNotification(webhookUrl, options) {
|
|
|
183
194
|
backupFile,
|
|
184
195
|
serverHost,
|
|
185
196
|
deployUrl,
|
|
186
|
-
success
|
|
197
|
+
success,
|
|
198
|
+
keyword = '部署'
|
|
187
199
|
} = options;
|
|
188
200
|
|
|
189
201
|
const now = new Date();
|
|
@@ -196,10 +208,13 @@ export async function sendRollbackNotification(webhookUrl, options) {
|
|
|
196
208
|
second: '2-digit'
|
|
197
209
|
});
|
|
198
210
|
|
|
211
|
+
// 标题必须包含关键词
|
|
212
|
+
const title = `回滚${success ? '成功' : '失败'} - ${environment}`;
|
|
213
|
+
|
|
199
214
|
const message = {
|
|
200
215
|
msgtype: 'markdown',
|
|
201
216
|
markdown: {
|
|
202
|
-
title
|
|
217
|
+
title,
|
|
203
218
|
text: `
|
|
204
219
|
## ${success ? '🔄' : '❌'} 回滚${success ? '成功' : '失败'}通知
|
|
205
220
|
|
|
@@ -222,7 +237,7 @@ ${success ? `### 访问地址
|
|
|
222
237
|
|
|
223
238
|
[${deployUrl}](${deployUrl})` : ''}
|
|
224
239
|
|
|
225
|
-
> ${success ? '回滚完成,请验证功能是否正常。' : '回滚失败,请手动处理。'}
|
|
240
|
+
> ${success ? '回滚完成,请验证功能是否正常。' : '回滚失败,请手动处理。'}(${keyword}系统)
|
|
226
241
|
`.trim()
|
|
227
242
|
}
|
|
228
243
|
};
|
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.d.ts
CHANGED
package/src/index.js
CHANGED