fe-build-cli 1.1.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/README.md +430 -0
- package/package.json +41 -0
- package/src/cli.js +492 -0
- package/src/config-template.js +83 -0
- package/src/deploy-core.js +326 -0
- package/src/dingtalk.js +238 -0
- package/src/git-branch.js +259 -0
- package/src/index.d.ts +330 -0
- package/src/index.js +47 -0
- package/src/ssh-client.js +153 -0
package/src/cli.js
ADDED
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
|
+
import { deployToServer, rollbackDeployment } from './deploy-core.js';
|
|
9
|
+
import {
|
|
10
|
+
getCurrentBranch,
|
|
11
|
+
getGitSha,
|
|
12
|
+
executeMainBranchFlow,
|
|
13
|
+
executeCurrentBranchFlow,
|
|
14
|
+
restoreBranch
|
|
15
|
+
} from './git-branch.js';
|
|
16
|
+
import {
|
|
17
|
+
sendDeploySuccessNotification,
|
|
18
|
+
sendDeployFailureNotification,
|
|
19
|
+
sendRollbackNotification
|
|
20
|
+
} from './dingtalk.js';
|
|
21
|
+
|
|
22
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 交互式提示
|
|
26
|
+
*/
|
|
27
|
+
function prompt(question) {
|
|
28
|
+
const rl = createInterface({
|
|
29
|
+
input: process.stdin,
|
|
30
|
+
output: process.stdout
|
|
31
|
+
});
|
|
32
|
+
return new Promise(resolve => {
|
|
33
|
+
rl.question(question, answer => {
|
|
34
|
+
rl.close();
|
|
35
|
+
resolve(answer.trim());
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 获取配置文件路径
|
|
42
|
+
* 优先级:命令行参数 > 当前目录 > 项目根目录
|
|
43
|
+
*/
|
|
44
|
+
function getConfigPath() {
|
|
45
|
+
// 命令行参数 --config 指定的路径
|
|
46
|
+
const configIndex = process.argv.indexOf('--config');
|
|
47
|
+
if (configIndex !== -1 && process.argv[configIndex + 1]) {
|
|
48
|
+
return path.resolve(process.argv[configIndex + 1]);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 当前目录下的 fe-build.config.js
|
|
52
|
+
const localConfig = path.join(process.cwd(), 'fe-build.config.js');
|
|
53
|
+
if (fs.existsSync(localConfig)) {
|
|
54
|
+
return localConfig;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// scripts 目录下的 deploy.config.js(兼容旧项目)
|
|
58
|
+
const scriptsConfig = path.join(process.cwd(), 'scripts', 'deploy.config.js');
|
|
59
|
+
if (fs.existsSync(scriptsConfig)) {
|
|
60
|
+
return scriptsConfig;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 加载配置文件
|
|
68
|
+
*/
|
|
69
|
+
async function loadConfig() {
|
|
70
|
+
const configPath = getConfigPath();
|
|
71
|
+
|
|
72
|
+
if (!configPath) {
|
|
73
|
+
console.error('❌ 未找到配置文件!');
|
|
74
|
+
console.error('请创建 fe-build.config.js 或使用 --config 参数指定配置文件路径');
|
|
75
|
+
console.error('\n配置文件示例:');
|
|
76
|
+
console.error(`
|
|
77
|
+
export default {
|
|
78
|
+
// 分支配置(可选,用于主分支发布模式)
|
|
79
|
+
branches: {
|
|
80
|
+
test: 'test', // 测试分支名
|
|
81
|
+
main: 'main' // 主分支名
|
|
82
|
+
},
|
|
83
|
+
// 发布模式: 'main' (主分支发布) 或 'current' (当前分支发布)
|
|
84
|
+
deployMode: 'main',
|
|
85
|
+
// 服务器配置
|
|
86
|
+
servers: {
|
|
87
|
+
production: {
|
|
88
|
+
sshHost: 'your-server.com',
|
|
89
|
+
sshUser: 'deployer',
|
|
90
|
+
sshKeyPath: '~/.ssh/id_rsa',
|
|
91
|
+
deployUrl: 'https://your-domain.com',
|
|
92
|
+
backupDir: '/www/backups/your-app',
|
|
93
|
+
deployDir: '/www/your-app',
|
|
94
|
+
backupPrefix: 'backup',
|
|
95
|
+
buildMode: 'production',
|
|
96
|
+
protectedDirs: ['webgl']
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
`);
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log(`📄 使用配置文件: ${configPath}`);
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
// Windows 下需要将路径转换为 file:// URL
|
|
108
|
+
const configUrl = pathToFileURL(configPath).href;
|
|
109
|
+
const config = (await import(configUrl)).default;
|
|
110
|
+
return config;
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error(`❌ 加载配置文件失败: ${error.message}`);
|
|
113
|
+
process.exit(1);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 从配置中获取服务器列表
|
|
119
|
+
*/
|
|
120
|
+
function getServerNames(config) {
|
|
121
|
+
// 新格式:servers 对象
|
|
122
|
+
if (config.servers) {
|
|
123
|
+
return Object.keys(config.servers).filter(k => config.servers[k].sshHost !== undefined);
|
|
124
|
+
}
|
|
125
|
+
// 旧格式兼容:直接在根对象配置服务器
|
|
126
|
+
return Object.keys(config).filter(k => config[k].sshHost !== undefined);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 获取服务器配置
|
|
131
|
+
*/
|
|
132
|
+
function getServerConfig(config, serverName) {
|
|
133
|
+
if (config.servers) {
|
|
134
|
+
return config.servers[serverName];
|
|
135
|
+
}
|
|
136
|
+
return config[serverName];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 生成构建版本号
|
|
141
|
+
*/
|
|
142
|
+
function generateBuildVersion() {
|
|
143
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '').slice(0, 15);
|
|
144
|
+
let gitSha = 'local';
|
|
145
|
+
try {
|
|
146
|
+
gitSha = execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim();
|
|
147
|
+
} catch (e) {
|
|
148
|
+
// 忽略
|
|
149
|
+
}
|
|
150
|
+
return `build-${timestamp}-${gitSha}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 显示帮助信息
|
|
155
|
+
*/
|
|
156
|
+
function showHelp() {
|
|
157
|
+
console.log(`
|
|
158
|
+
fe-build-cli - 前端项目打包部署工具
|
|
159
|
+
|
|
160
|
+
用法:
|
|
161
|
+
fe-build [命令] [选项]
|
|
162
|
+
|
|
163
|
+
命令:
|
|
164
|
+
deploy [环境] 部署到指定环境(默认命令)
|
|
165
|
+
rollback [环境] 回滚到上一版本
|
|
166
|
+
help 显示帮助信息
|
|
167
|
+
|
|
168
|
+
选项:
|
|
169
|
+
--config <路径> 指定配置文件路径
|
|
170
|
+
--current-branch 使用当前分支发布(不切换分支)
|
|
171
|
+
--main-branch 使用主分支发布流程(合并到测试分支再合并到主分支)
|
|
172
|
+
--skip-build 跳过构建步骤
|
|
173
|
+
--no-push 主分支发布时不推送到远程
|
|
174
|
+
|
|
175
|
+
示例:
|
|
176
|
+
fe-build # 交互式选择环境部署
|
|
177
|
+
fe-build deploy production # 部署到生产环境
|
|
178
|
+
fe-build deploy all # 部署到所有环境
|
|
179
|
+
fe-build --current-branch # 当前分支发布
|
|
180
|
+
fe-build --main-branch # 主分支发布流程
|
|
181
|
+
fe-build rollback production # 回滚生产环境
|
|
182
|
+
|
|
183
|
+
配置文件 (fe-build.config.js):
|
|
184
|
+
export default {
|
|
185
|
+
// 分支配置
|
|
186
|
+
branches: {
|
|
187
|
+
test: 'test', // 测试分支名
|
|
188
|
+
main: 'main' // 主分支名
|
|
189
|
+
},
|
|
190
|
+
// 发布模式: 'main' 或 'current'
|
|
191
|
+
deployMode: 'main',
|
|
192
|
+
// 服务器配置
|
|
193
|
+
servers: {
|
|
194
|
+
production: {
|
|
195
|
+
sshHost: 'server.com',
|
|
196
|
+
sshUser: 'deployer',
|
|
197
|
+
sshKeyPath: '~/.ssh/id_rsa',
|
|
198
|
+
deployUrl: 'https://domain.com',
|
|
199
|
+
backupDir: '/www/backups/app',
|
|
200
|
+
deployDir: '/www/app',
|
|
201
|
+
backupPrefix: 'backup',
|
|
202
|
+
buildMode: 'production',
|
|
203
|
+
protectedDirs: ['webgl']
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* 主部署命令
|
|
212
|
+
*/
|
|
213
|
+
async function deployCommand(config) {
|
|
214
|
+
const serverNames = getServerNames(config);
|
|
215
|
+
|
|
216
|
+
if (serverNames.length === 0) {
|
|
217
|
+
console.error('❌ 配置文件中没有找到服务器配置');
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// 解析命令行参数
|
|
222
|
+
const args = process.argv.slice(2);
|
|
223
|
+
const useCurrentBranch = args.includes('--current-branch');
|
|
224
|
+
const useMainBranch = args.includes('--main-branch');
|
|
225
|
+
const skipBuild = args.includes('--skip-build');
|
|
226
|
+
const noPush = args.includes('--no-push');
|
|
227
|
+
|
|
228
|
+
// 确定发布模式
|
|
229
|
+
let deployMode = config.deployMode || 'main'; // 默认主分支发布
|
|
230
|
+
|
|
231
|
+
if (useCurrentBranch) {
|
|
232
|
+
deployMode = 'current';
|
|
233
|
+
} else if (useMainBranch) {
|
|
234
|
+
deployMode = 'main';
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// 获取目标环境(排除 deploy 命令本身)
|
|
238
|
+
const argEnv = args.find(arg => arg !== 'deploy' && !arg.startsWith('--'));
|
|
239
|
+
let selectedServers = [];
|
|
240
|
+
|
|
241
|
+
const validArgs = [...serverNames, 'all'];
|
|
242
|
+
if (argEnv && validArgs.includes(argEnv)) {
|
|
243
|
+
selectedServers = argEnv === 'all' ? serverNames : [argEnv];
|
|
244
|
+
} else {
|
|
245
|
+
// 交互式选择
|
|
246
|
+
console.log('\n========================================');
|
|
247
|
+
console.log(' 🚀 请选择部署目标');
|
|
248
|
+
console.log('========================================');
|
|
249
|
+
serverNames.forEach((name, i) => {
|
|
250
|
+
const envConfig = getServerConfig(config, name);
|
|
251
|
+
const label = envConfig ? `${name} - ${envConfig.deployUrl || envConfig.sshHost}` : name;
|
|
252
|
+
console.log(` ${i + 1}. ${label}`);
|
|
253
|
+
});
|
|
254
|
+
console.log(` ${serverNames.length + 1}. 全部服务器`);
|
|
255
|
+
console.log('========================================');
|
|
256
|
+
|
|
257
|
+
const answer = await prompt(`请输入选项 (1-${serverNames.length + 1}): `);
|
|
258
|
+
const choiceIdx = parseInt(answer, 10);
|
|
259
|
+
if (choiceIdx >= 1 && choiceIdx <= serverNames.length) {
|
|
260
|
+
selectedServers = [serverNames[choiceIdx - 1]];
|
|
261
|
+
} else if (choiceIdx === serverNames.length + 1) {
|
|
262
|
+
selectedServers = serverNames;
|
|
263
|
+
} else {
|
|
264
|
+
console.error('❌ 无效选项');
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// 执行分支发布流程
|
|
270
|
+
let branchResult = null;
|
|
271
|
+
let originalBranch = null;
|
|
272
|
+
|
|
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('========================================');
|
|
282
|
+
|
|
283
|
+
const confirmAnswer = await prompt('确认执行主分支发布流程? (y/n): ');
|
|
284
|
+
if (confirmAnswer.toLowerCase() !== 'y') {
|
|
285
|
+
console.log('已取消发布');
|
|
286
|
+
process.exit(0);
|
|
287
|
+
}
|
|
288
|
+
|
|
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('📌 当前分支发布模式:不切换分支');
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// 生成构建版本
|
|
303
|
+
const buildVersion = generateBuildVersion();
|
|
304
|
+
const startTime = Date.now();
|
|
305
|
+
|
|
306
|
+
// 部署到选中的服务器
|
|
307
|
+
for (let i = 0; i < selectedServers.length; i++) {
|
|
308
|
+
const serverName = selectedServers[i];
|
|
309
|
+
const envConfig = getServerConfig(config, serverName);
|
|
310
|
+
|
|
311
|
+
// 校验配置是否完整
|
|
312
|
+
if (!envConfig || !envConfig.sshHost) {
|
|
313
|
+
console.error(`❌ ${serverName} 配置不完整,请检查配置文件`);
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const isFirst = i === 0;
|
|
318
|
+
|
|
319
|
+
console.log('\n========================================');
|
|
320
|
+
console.log(`开始部署到 ${serverName}`);
|
|
321
|
+
console.log(`服务器: ${envConfig.sshHost}`);
|
|
322
|
+
console.log(`构建版本: ${buildVersion}`);
|
|
323
|
+
if (selectedServers.length > 1) {
|
|
324
|
+
console.log(`进度: ${i + 1}/${selectedServers.length}`);
|
|
325
|
+
}
|
|
326
|
+
console.log('========================================');
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
await deployToServer({
|
|
330
|
+
environment: serverName,
|
|
331
|
+
envConfig,
|
|
332
|
+
buildVersion,
|
|
333
|
+
skipBuild: skipBuild || !isFirst,
|
|
334
|
+
skipLocalCleanup: i < selectedServers.length - 1
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// 部署成功,发送钉钉通知
|
|
338
|
+
const duration = Math.round((Date.now() - startTime) / 1000);
|
|
339
|
+
const currentBranch = getCurrentBranch();
|
|
340
|
+
|
|
341
|
+
if (config.dingtalk && config.dingtalk.enabled && config.dingtalk.webhook) {
|
|
342
|
+
console.log('\n发送钉钉通知...');
|
|
343
|
+
await sendDeploySuccessNotification(config.dingtalk.webhook, {
|
|
344
|
+
environment: serverName,
|
|
345
|
+
buildVersion,
|
|
346
|
+
serverHost: envConfig.sshHost,
|
|
347
|
+
deployUrl: envConfig.deployUrl,
|
|
348
|
+
branch: currentBranch,
|
|
349
|
+
deployMode,
|
|
350
|
+
duration: `${duration}秒`
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
} catch (error) {
|
|
354
|
+
console.error(`❌ 部署到 ${serverName} 失败:`, error.message);
|
|
355
|
+
|
|
356
|
+
// 部署失败,发送钉钉通知
|
|
357
|
+
const currentBranch = getCurrentBranch();
|
|
358
|
+
if (config.dingtalk && config.dingtalk.enabled && config.dingtalk.webhook) {
|
|
359
|
+
console.log('\n发送钉钉失败通知...');
|
|
360
|
+
await sendDeployFailureNotification(config.dingtalk.webhook, {
|
|
361
|
+
environment: serverName,
|
|
362
|
+
buildVersion,
|
|
363
|
+
serverHost: envConfig.sshHost,
|
|
364
|
+
branch: currentBranch,
|
|
365
|
+
error: error.message
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// 如果是主分支发布模式,尝试切回原分支
|
|
370
|
+
if (originalBranch) {
|
|
371
|
+
restoreBranch(originalBranch);
|
|
372
|
+
}
|
|
373
|
+
process.exit(1);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// 主分支发布模式:部署完成后切回原分支
|
|
378
|
+
if (deployMode === 'main' && originalBranch) {
|
|
379
|
+
const returnAnswer = await prompt('\n是否切回原分支? (y/n): ');
|
|
380
|
+
if (returnAnswer.toLowerCase() === 'y') {
|
|
381
|
+
restoreBranch(originalBranch);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* 回滚命令
|
|
388
|
+
*/
|
|
389
|
+
async function rollbackCommand(config) {
|
|
390
|
+
const serverNames = getServerNames(config);
|
|
391
|
+
|
|
392
|
+
if (serverNames.length === 0) {
|
|
393
|
+
console.error('❌ 配置文件中没有找到服务器配置');
|
|
394
|
+
process.exit(1);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// 获取目标环境(排除 rollback 命令本身)
|
|
398
|
+
const args = process.argv.slice(2);
|
|
399
|
+
const environment = args.find(arg => arg !== 'rollback' && !arg.startsWith('--'));
|
|
400
|
+
const versionIndex = args.indexOf('--version');
|
|
401
|
+
const specifiedVersion = versionIndex !== -1 ? args[versionIndex + 1] : undefined;
|
|
402
|
+
|
|
403
|
+
if (!environment || !serverNames.includes(environment)) {
|
|
404
|
+
console.error(`❌ 请指定服务器: ${serverNames.join(' 或 ')}`);
|
|
405
|
+
console.error(`用法: fe-build rollback [${serverNames.join('|')}] [--version <版本号>]`);
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const envConfig = getServerConfig(config, environment);
|
|
410
|
+
|
|
411
|
+
if (!envConfig || !envConfig.sshHost) {
|
|
412
|
+
console.error(`❌ ${environment} 配置不完整`);
|
|
413
|
+
process.exit(1);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
let backupFile = '';
|
|
417
|
+
let success = false;
|
|
418
|
+
|
|
419
|
+
try {
|
|
420
|
+
// 执行回滚,获取备份文件信息
|
|
421
|
+
backupFile = specifiedVersion
|
|
422
|
+
? `${envConfig.backupDir}/${envConfig.backupPrefix}-${specifiedVersion}.tar.gz`
|
|
423
|
+
: '';
|
|
424
|
+
|
|
425
|
+
await rollbackDeployment({
|
|
426
|
+
environment,
|
|
427
|
+
envConfig,
|
|
428
|
+
specifiedVersion
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
success = true;
|
|
432
|
+
|
|
433
|
+
// 回滚成功,发送钉钉通知
|
|
434
|
+
if (config.dingtalk && config.dingtalk.enabled && config.dingtalk.webhook) {
|
|
435
|
+
console.log('\n发送钉钉通知...');
|
|
436
|
+
await sendRollbackNotification(config.dingtalk.webhook, {
|
|
437
|
+
environment,
|
|
438
|
+
backupFile: backupFile || '最新备份',
|
|
439
|
+
serverHost: envConfig.sshHost,
|
|
440
|
+
deployUrl: envConfig.deployUrl,
|
|
441
|
+
success: true
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
} catch (error) {
|
|
445
|
+
console.error('❌ 回滚失败:', error.message);
|
|
446
|
+
success = false;
|
|
447
|
+
|
|
448
|
+
// 回滚失败,发送钉钉通知
|
|
449
|
+
if (config.dingtalk && config.dingtalk.enabled && config.dingtalk.webhook) {
|
|
450
|
+
console.log('\n发送钉钉通知...');
|
|
451
|
+
await sendRollbackNotification(config.dingtalk.webhook, {
|
|
452
|
+
environment,
|
|
453
|
+
backupFile: backupFile || '未知',
|
|
454
|
+
serverHost: envConfig.sshHost,
|
|
455
|
+
deployUrl: envConfig.deployUrl,
|
|
456
|
+
success: false
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
process.exit(1);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* 主入口
|
|
466
|
+
*/
|
|
467
|
+
async function main() {
|
|
468
|
+
const args = process.argv.slice(2);
|
|
469
|
+
const command = args[0];
|
|
470
|
+
|
|
471
|
+
// 显示帮助
|
|
472
|
+
if (command === 'help' || args.includes('--help') || args.includes('-h')) {
|
|
473
|
+
showHelp();
|
|
474
|
+
process.exit(0);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// 加载配置
|
|
478
|
+
const config = await loadConfig();
|
|
479
|
+
|
|
480
|
+
// 执行命令
|
|
481
|
+
if (command === 'rollback') {
|
|
482
|
+
await rollbackCommand(config);
|
|
483
|
+
} else {
|
|
484
|
+
// 默认执行 deploy
|
|
485
|
+
await deployCommand(config);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
main().catch(error => {
|
|
490
|
+
console.error('执行失败:', error);
|
|
491
|
+
process.exit(1);
|
|
492
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* fe-build-cli 配置文件模板
|
|
5
|
+
* 将此文件复制到项目根目录并重命名为 fe-build.config.js
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export default {
|
|
9
|
+
/**
|
|
10
|
+
* 分支配置(用于主分支发布模式)
|
|
11
|
+
* 当 deployMode 为 'main' 时,会执行以下流程:
|
|
12
|
+
* 1. 当前分支 -> testBranch(测试分支)
|
|
13
|
+
* 2. testBranch -> mainBranch(主分支)
|
|
14
|
+
*/
|
|
15
|
+
branches: {
|
|
16
|
+
test: 'test', // 测试分支名,根据项目实际情况修改
|
|
17
|
+
main: 'main' // 主分支名,根据项目实际情况修改
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 发布模式
|
|
22
|
+
* - 'main': 主分支发布模式(默认)
|
|
23
|
+
* 流程:当前分支 -> 测试分支 -> 主分支,然后从主分支发布
|
|
24
|
+
* - 'current': 当前分支发布模式
|
|
25
|
+
* 直接从当前分支发布,不切换分支
|
|
26
|
+
*/
|
|
27
|
+
deployMode: 'main',
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 服务器配置
|
|
31
|
+
* 可以配置多个服务器环境
|
|
32
|
+
*/
|
|
33
|
+
servers: {
|
|
34
|
+
// 生产环境示例
|
|
35
|
+
production: {
|
|
36
|
+
// SSH 连接配置
|
|
37
|
+
sshHost: 'your-server-ip-or-domain', // 服务器 IP 或域名
|
|
38
|
+
sshUser: 'deployer', // SSH 用户名
|
|
39
|
+
sshKeyPath: `${process.env.USERPROFILE || process.env.HOME}/.ssh/id_rsa`, // SSH 私钥路径
|
|
40
|
+
|
|
41
|
+
// 部署配置
|
|
42
|
+
deployUrl: 'https://your-domain.com', // 部署后的访问地址
|
|
43
|
+
backupDir: '/www/wwwroot/backups/your-app', // 备份目录
|
|
44
|
+
deployDir: '/www/wwwroot/your-app', // 部署目录
|
|
45
|
+
backupPrefix: 'backup-production', // 备份文件前缀
|
|
46
|
+
|
|
47
|
+
// 构建配置
|
|
48
|
+
buildMode: 'production', // 构建模式:production 或 test
|
|
49
|
+
buildCommand: 'yarn build', // 自定义构建命令(可选,默认根据 buildMode 自动选择)
|
|
50
|
+
|
|
51
|
+
// 需要保护的目录(部署时不会被删除)
|
|
52
|
+
protectedDirs: ['webgl', 'uploads'] // 例如:webgl、uploads 等静态资源目录
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// 测试环境示例
|
|
56
|
+
test: {
|
|
57
|
+
sshHost: 'test-server-ip',
|
|
58
|
+
sshUser: 'deployer',
|
|
59
|
+
sshKeyPath: `${process.env.USERPROFILE || process.env.HOME}/.ssh/id_rsa`,
|
|
60
|
+
deployUrl: 'https://test.your-domain.com',
|
|
61
|
+
backupDir: '/www/wwwroot/backups/test-app',
|
|
62
|
+
deployDir: '/www/wwwroot/test-app',
|
|
63
|
+
backupPrefix: 'backup-test',
|
|
64
|
+
buildMode: 'test',
|
|
65
|
+
protectedDirs: ['webgl']
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 备份保留数量(可选)
|
|
71
|
+
* 默认保留最新的 1 个备份
|
|
72
|
+
*/
|
|
73
|
+
backupRetentionCount: 1,
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 钉钉通知配置(可选)
|
|
77
|
+
* 部署完成后自动发送钉钉消息通知
|
|
78
|
+
*/
|
|
79
|
+
dingtalk: {
|
|
80
|
+
webhook: 'https://oapi.dingtalk.com/robot/send?access_token=your-token', // 钉钉机器人 webhook URL
|
|
81
|
+
enabled: true // 是否启用钉钉通知,默认 true
|
|
82
|
+
}
|
|
83
|
+
};
|