fe-build-cli 1.2.5 → 1.5.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 +248 -11
- package/package.json +1 -1
- package/src/cli.js +188 -20
- package/src/config-template.js +14 -1
- package/src/deploy-core.js +310 -64
- package/src/dingtalk.js +31 -17
- package/src/git-branch.js +191 -34
- package/src/index.js +13 -1
- package/src/logger.js +381 -0
- package/src/ssh-client.js +69 -0
package/src/cli.js
CHANGED
|
@@ -5,10 +5,12 @@ import { execSync } from 'node:child_process';
|
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
|
-
import { deployToServer, rollbackDeployment } from './deploy-core.js';
|
|
8
|
+
import { deployToServer, rollbackDeployment, getServerBackupList, getLocalBackupList, rollbackFromLocal } from './deploy-core.js';
|
|
9
|
+
import SSHClient from './ssh-client.js';
|
|
9
10
|
import {
|
|
10
11
|
getCurrentBranch,
|
|
11
12
|
getGitSha,
|
|
13
|
+
getGitCommitMessage,
|
|
12
14
|
executeMainBranchFlow,
|
|
13
15
|
executeCurrentBranchFlow,
|
|
14
16
|
executeTestBranchFlow,
|
|
@@ -20,6 +22,7 @@ import {
|
|
|
20
22
|
sendDeployFailureNotification,
|
|
21
23
|
sendRollbackNotification
|
|
22
24
|
} from './dingtalk.js';
|
|
25
|
+
import { DeployLogger } from './logger.js';
|
|
23
26
|
|
|
24
27
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
25
28
|
|
|
@@ -164,7 +167,7 @@ fe-build-cli - 前端项目打包部署工具
|
|
|
164
167
|
|
|
165
168
|
命令:
|
|
166
169
|
deploy [环境] 部署到指定环境(默认命令)
|
|
167
|
-
rollback [环境]
|
|
170
|
+
rollback [环境] 回滚到指定版本(交互选择备份来源)
|
|
168
171
|
help 显示帮助信息
|
|
169
172
|
|
|
170
173
|
选项:
|
|
@@ -176,6 +179,9 @@ fe-build-cli - 前端项目打包部署工具
|
|
|
176
179
|
--no-merge test 发布时不合并,使用 stash 储藏本地改动
|
|
177
180
|
--skip-build 跳过构建步骤
|
|
178
181
|
--no-push 发布时不推送到远程
|
|
182
|
+
--server 回滚时使用服务器备份(默认)
|
|
183
|
+
--local 回滚时使用本地备份
|
|
184
|
+
--version <版本号> 回滚到指定版本
|
|
179
185
|
|
|
180
186
|
示例:
|
|
181
187
|
fe-build # 交互式选择环境部署
|
|
@@ -186,7 +192,9 @@ fe-build-cli - 前端项目打包部署工具
|
|
|
186
192
|
fe-build --test-branch --no-merge # test 发布,stash 储藏改动
|
|
187
193
|
fe-build --current-branch # 当前分支发布
|
|
188
194
|
fe-build --main-branch # 主分支发布流程
|
|
189
|
-
fe-build rollback production
|
|
195
|
+
fe-build rollback production # 回滚生产环境(交互选择)
|
|
196
|
+
fe-build rollback production --server # 回滚生产环境(服务器备份)
|
|
197
|
+
fe-build rollback production --local # 回滚生产环境(本地备份)
|
|
190
198
|
|
|
191
199
|
配置文件 (fe-build.config.js):
|
|
192
200
|
export default {
|
|
@@ -295,6 +303,12 @@ async function deployCommand(config) {
|
|
|
295
303
|
}
|
|
296
304
|
}
|
|
297
305
|
|
|
306
|
+
// 创建日志记录器(在分支操作之前)
|
|
307
|
+
const logDir = config.logDir || 'logs';
|
|
308
|
+
const localBackupDir = config.localBackupDir || 'D:\\备份';
|
|
309
|
+
const logger = new DeployLogger({ logDir, localBackupDir });
|
|
310
|
+
logger.start();
|
|
311
|
+
|
|
298
312
|
// 执行分支发布流程
|
|
299
313
|
let branchResult = null;
|
|
300
314
|
let originalBranch = getCurrentBranch(); // 记录原始分支
|
|
@@ -320,7 +334,8 @@ async function deployCommand(config) {
|
|
|
320
334
|
testBranch: branches.test,
|
|
321
335
|
mergeChanges,
|
|
322
336
|
pushToRemote: !noPush,
|
|
323
|
-
prompt
|
|
337
|
+
prompt,
|
|
338
|
+
logger // 传递 logger
|
|
324
339
|
});
|
|
325
340
|
originalBranch = branchResult.originalBranch;
|
|
326
341
|
needRestore = branchResult.needRestore;
|
|
@@ -339,19 +354,21 @@ async function deployCommand(config) {
|
|
|
339
354
|
const confirmAnswer = await prompt('确认执行主分支发布流程? (y/n): ');
|
|
340
355
|
if (confirmAnswer.toLowerCase() !== 'y') {
|
|
341
356
|
console.log('已取消发布');
|
|
357
|
+
logger.end('cancelled');
|
|
342
358
|
process.exit(0);
|
|
343
359
|
}
|
|
344
360
|
|
|
345
361
|
branchResult = executeMainBranchFlow({
|
|
346
362
|
testBranch: branches.test,
|
|
347
363
|
mainBranch: branches.main,
|
|
348
|
-
pushToRemote: !noPush
|
|
364
|
+
pushToRemote: !noPush,
|
|
365
|
+
logger // 传递 logger
|
|
349
366
|
});
|
|
350
367
|
originalBranch = branchResult.originalBranch;
|
|
351
368
|
needRestore = true;
|
|
352
369
|
} else if (deployMode === 'current') {
|
|
353
370
|
// 当前分支发布模式
|
|
354
|
-
branchResult = executeCurrentBranchFlow();
|
|
371
|
+
branchResult = executeCurrentBranchFlow(logger);
|
|
355
372
|
originalBranch = branchResult.currentBranch;
|
|
356
373
|
needRestore = false;
|
|
357
374
|
console.log('📌 当前分支发布模式:不切换分支');
|
|
@@ -359,7 +376,10 @@ async function deployCommand(config) {
|
|
|
359
376
|
} catch (branchError) {
|
|
360
377
|
// 分支流程失败,发送钉钉通知
|
|
361
378
|
console.error(`❌ 分支流程失败:`, branchError.message);
|
|
379
|
+
logger.log('ERROR', '分支流程失败', branchError.message);
|
|
380
|
+
logger.end('failed');
|
|
362
381
|
|
|
382
|
+
const commitMessage = getGitCommitMessage();
|
|
363
383
|
if (config.dingtalk && config.dingtalk.enabled && config.dingtalk.webhook) {
|
|
364
384
|
console.log('\n发送钉钉失败通知...');
|
|
365
385
|
const envConfig = getServerConfig(config, selectedServers[0] || serverNames[0]);
|
|
@@ -368,13 +388,14 @@ async function deployCommand(config) {
|
|
|
368
388
|
buildVersion: '未完成',
|
|
369
389
|
serverHost: envConfig?.sshHost || '未知',
|
|
370
390
|
branch: originalBranch,
|
|
391
|
+
commitMessage,
|
|
371
392
|
error: `分支流程失败: ${branchError.message}`,
|
|
372
393
|
keyword: config.dingtalk.keyword || '部署'
|
|
373
394
|
});
|
|
374
395
|
}
|
|
375
396
|
|
|
376
397
|
// 切回原分支
|
|
377
|
-
restoreBranch(originalBranch, hasStash);
|
|
398
|
+
restoreBranch(originalBranch, hasStash, logger);
|
|
378
399
|
process.exit(1);
|
|
379
400
|
}
|
|
380
401
|
|
|
@@ -404,18 +425,25 @@ async function deployCommand(config) {
|
|
|
404
425
|
}
|
|
405
426
|
console.log('========================================');
|
|
406
427
|
|
|
428
|
+
// 部署到服务器(使用前面创建的 logger)
|
|
407
429
|
try {
|
|
408
430
|
await deployToServer({
|
|
409
431
|
environment: serverName,
|
|
410
432
|
envConfig,
|
|
411
433
|
buildVersion,
|
|
412
434
|
skipBuild: skipBuild || !isFirst,
|
|
413
|
-
skipLocalCleanup: i < selectedServers.length - 1
|
|
435
|
+
skipLocalCleanup: i < selectedServers.length - 1,
|
|
436
|
+
logger,
|
|
437
|
+
localBackupDir
|
|
414
438
|
});
|
|
415
439
|
|
|
440
|
+
// 部署成功,结束日志记录
|
|
441
|
+
logger.end('success');
|
|
442
|
+
|
|
416
443
|
// 部署成功,发送钉钉通知
|
|
417
444
|
const duration = Math.round((Date.now() - startTime) / 1000);
|
|
418
445
|
const currentBranch = getCurrentBranch();
|
|
446
|
+
const commitMessage = getGitCommitMessage();
|
|
419
447
|
|
|
420
448
|
if (config.dingtalk && config.dingtalk.enabled && config.dingtalk.webhook) {
|
|
421
449
|
console.log('\n发送钉钉通知...');
|
|
@@ -426,15 +454,21 @@ async function deployCommand(config) {
|
|
|
426
454
|
deployUrl: envConfig.deployUrl,
|
|
427
455
|
branch: currentBranch,
|
|
428
456
|
deployMode,
|
|
457
|
+
commitMessage,
|
|
429
458
|
duration: `${duration}秒`,
|
|
430
459
|
keyword: config.dingtalk.keyword || '部署'
|
|
431
460
|
});
|
|
461
|
+
logger.logDingTalk(true);
|
|
432
462
|
}
|
|
433
463
|
} catch (error) {
|
|
434
464
|
console.error(`❌ 部署到 ${serverName} 失败:`, error.message);
|
|
435
465
|
|
|
466
|
+
// 部署失败,结束日志记录
|
|
467
|
+
logger.end('failed');
|
|
468
|
+
|
|
436
469
|
// 部署失败,发送钉钉通知
|
|
437
470
|
const currentBranch = getCurrentBranch();
|
|
471
|
+
const commitMessage = getGitCommitMessage();
|
|
438
472
|
if (config.dingtalk && config.dingtalk.enabled && config.dingtalk.webhook) {
|
|
439
473
|
console.log('\n发送钉钉失败通知...');
|
|
440
474
|
await sendDeployFailureNotification(config.dingtalk.webhook, {
|
|
@@ -442,14 +476,16 @@ async function deployCommand(config) {
|
|
|
442
476
|
buildVersion,
|
|
443
477
|
serverHost: envConfig.sshHost,
|
|
444
478
|
branch: currentBranch,
|
|
479
|
+
commitMessage,
|
|
445
480
|
error: error.message,
|
|
446
481
|
keyword: config.dingtalk.keyword || '部署'
|
|
447
482
|
});
|
|
483
|
+
logger.logDingTalk(false, error.message);
|
|
448
484
|
}
|
|
449
485
|
|
|
450
486
|
// 出错时切回原分支
|
|
451
487
|
if (needRestore && originalBranch) {
|
|
452
|
-
restoreBranch(originalBranch, hasStash);
|
|
488
|
+
restoreBranch(originalBranch, hasStash, logger);
|
|
453
489
|
}
|
|
454
490
|
process.exit(1);
|
|
455
491
|
}
|
|
@@ -460,13 +496,13 @@ async function deployCommand(config) {
|
|
|
460
496
|
// 合并模式:自动切回原分支
|
|
461
497
|
if (autoRestore) {
|
|
462
498
|
console.log('\n📌 自动切回原分支...');
|
|
463
|
-
restoreBranch(originalBranch, false);
|
|
499
|
+
restoreBranch(originalBranch, false, logger);
|
|
464
500
|
console.log(`✅ 已切回 ${originalBranch},可继续开发`);
|
|
465
501
|
} else {
|
|
466
502
|
// stash 模式:询问是否切回
|
|
467
503
|
const returnAnswer = await prompt('\n是否切回原分支? (y/n): ');
|
|
468
504
|
if (returnAnswer.toLowerCase() === 'y') {
|
|
469
|
-
restoreBranch(originalBranch, hasStash);
|
|
505
|
+
restoreBranch(originalBranch, hasStash, logger);
|
|
470
506
|
} else if (hasStash) {
|
|
471
507
|
console.log('\n💡 提示: 本地改动已储藏,执行以下命令恢复:');
|
|
472
508
|
console.log(' git stash pop');
|
|
@@ -491,10 +527,12 @@ async function rollbackCommand(config) {
|
|
|
491
527
|
const environment = args.find(arg => arg !== 'rollback' && !arg.startsWith('--'));
|
|
492
528
|
const versionIndex = args.indexOf('--version');
|
|
493
529
|
const specifiedVersion = versionIndex !== -1 ? args[versionIndex + 1] : undefined;
|
|
530
|
+
const useLocalBackup = args.includes('--local'); // 是否使用本地备份
|
|
531
|
+
const useServerBackup = args.includes('--server'); // 是否使用服务器备份
|
|
494
532
|
|
|
495
533
|
if (!environment || !serverNames.includes(environment)) {
|
|
496
534
|
console.error(`❌ 请指定服务器: ${serverNames.join(' 或 ')}`);
|
|
497
|
-
console.error(`用法: fe-build rollback [${serverNames.join('|')}] [--version <版本号>]`);
|
|
535
|
+
console.error(`用法: fe-build rollback [${serverNames.join('|')}] [--server|--local] [--version <版本号>]`);
|
|
498
536
|
process.exit(1);
|
|
499
537
|
}
|
|
500
538
|
|
|
@@ -505,22 +543,134 @@ async function rollbackCommand(config) {
|
|
|
505
543
|
process.exit(1);
|
|
506
544
|
}
|
|
507
545
|
|
|
546
|
+
// 创建日志记录器
|
|
547
|
+
const logDir = config.logDir || 'logs';
|
|
548
|
+
const localBackupDir = config.localBackupDir || 'D:\\备份';
|
|
549
|
+
const logger = new DeployLogger({ logDir, localBackupDir });
|
|
550
|
+
logger.start();
|
|
551
|
+
|
|
552
|
+
console.log('========================================');
|
|
553
|
+
console.log(`开始回滚 ${environment} 环境`);
|
|
554
|
+
console.log(`服务器: ${envConfig.sshHost}`);
|
|
555
|
+
console.log('========================================');
|
|
556
|
+
|
|
557
|
+
// 连接服务器
|
|
558
|
+
const ssh = new SSHClient(envConfig);
|
|
559
|
+
await ssh.connect();
|
|
560
|
+
logger.logSSHConnect(envConfig.sshHost, true);
|
|
561
|
+
|
|
508
562
|
let backupFile = '';
|
|
509
|
-
let
|
|
563
|
+
let selectedBackup = null;
|
|
564
|
+
let backupSource = 'server'; // 默认服务器备份
|
|
510
565
|
|
|
511
566
|
try {
|
|
512
|
-
//
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
567
|
+
// 如果指定了版本号,直接使用
|
|
568
|
+
if (specifiedVersion) {
|
|
569
|
+
backupFile = `${envConfig.backupDir}/${envConfig.backupPrefix}-${specifiedVersion}.tar.gz`;
|
|
570
|
+
console.log(`\n使用指定版本: ${specifiedVersion}`);
|
|
571
|
+
logger.log('INFO', '回滚版本', `指定版本: ${specifiedVersion}`);
|
|
572
|
+
} else {
|
|
573
|
+
// 获取备份列表
|
|
574
|
+
let serverBackups = [];
|
|
575
|
+
let localBackups = [];
|
|
576
|
+
|
|
577
|
+
// 获取服务器备份列表
|
|
578
|
+
console.log('\n[步骤 1] 获取服务器备份列表...');
|
|
579
|
+
serverBackups = await getServerBackupList(ssh, envConfig);
|
|
580
|
+
console.log(`找到 ${serverBackups.length} 个服务器备份`);
|
|
581
|
+
|
|
582
|
+
// 获取本地备份列表
|
|
583
|
+
console.log('\n[步骤 2] 获取本地备份列表...');
|
|
584
|
+
localBackups = getLocalBackupList(localBackupDir, envConfig.backupPrefix);
|
|
585
|
+
console.log(`找到 ${localBackups.length} 个本地备份`);
|
|
586
|
+
|
|
587
|
+
// 如果没有备份
|
|
588
|
+
if (serverBackups.length === 0 && localBackups.length === 0) {
|
|
589
|
+
logger.log('ERROR', '获取备份', '未找到任何备份文件');
|
|
590
|
+
console.error('❌ 未找到任何备份文件!');
|
|
591
|
+
await ssh.disconnect();
|
|
592
|
+
logger.end('failed');
|
|
593
|
+
process.exit(1);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// 确定备份来源
|
|
597
|
+
if (useLocalBackup && localBackups.length > 0) {
|
|
598
|
+
backupSource = 'local';
|
|
599
|
+
} else if (useServerBackup && serverBackups.length > 0) {
|
|
600
|
+
backupSource = 'server';
|
|
601
|
+
} else if (!useLocalBackup && !useServerBackup) {
|
|
602
|
+
// 交互选择备份来源(默认服务器)
|
|
603
|
+
console.log('\n========================================');
|
|
604
|
+
console.log(' 📦 选择备份来源');
|
|
605
|
+
console.log('========================================');
|
|
606
|
+
console.log(` 1. 服务器备份 (${serverBackups.length} 个) - 默认`);
|
|
607
|
+
if (localBackups.length > 0) {
|
|
608
|
+
console.log(` 2. 本地备份 (${localBackups.length} 个)`);
|
|
609
|
+
}
|
|
610
|
+
console.log('========================================');
|
|
611
|
+
|
|
612
|
+
const sourceAnswer = await prompt(`请选择备份来源 (1${localBackups.length > 0 ? '/2' : ''}): `);
|
|
613
|
+
if (sourceAnswer === '2' && localBackups.length > 0) {
|
|
614
|
+
backupSource = 'local';
|
|
615
|
+
} else {
|
|
616
|
+
backupSource = 'server';
|
|
617
|
+
}
|
|
618
|
+
}
|
|
516
619
|
|
|
620
|
+
// 显示备份列表供选择
|
|
621
|
+
const backups = backupSource === 'server' ? serverBackups : localBackups;
|
|
622
|
+
|
|
623
|
+
console.log(`\n========================================`);
|
|
624
|
+
console.log(` 📦 ${backupSource === 'server' ? '服务器' : '本地'}备份列表`);
|
|
625
|
+
console.log(`========================================`);
|
|
626
|
+
|
|
627
|
+
backups.forEach((backup, index) => {
|
|
628
|
+
const sizeStr = backup.size ? ` (${formatFileSize(backup.size)})` : '';
|
|
629
|
+
const timeStr = backup.mtime ? ` - ${backup.mtime.toLocaleDateString('zh-CN')}` : '';
|
|
630
|
+
console.log(` ${index + 1}. ${backup.version}${sizeStr}${timeStr}`);
|
|
631
|
+
});
|
|
632
|
+
console.log(`========================================`);
|
|
633
|
+
|
|
634
|
+
const backupAnswer = await prompt(`请选择要回滚的备份 (1-${backups.length}): `);
|
|
635
|
+
const selectedIndex = parseInt(backupAnswer, 10) - 1;
|
|
636
|
+
|
|
637
|
+
if (selectedIndex < 0 || selectedIndex >= backups.length) {
|
|
638
|
+
console.error('❌ 无效选择');
|
|
639
|
+
await ssh.disconnect();
|
|
640
|
+
logger.end('failed');
|
|
641
|
+
process.exit(1);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
selectedBackup = backups[selectedIndex];
|
|
645
|
+
backupFile = selectedBackup.file;
|
|
646
|
+
|
|
647
|
+
console.log(`\n已选择: ${selectedBackup.version}`);
|
|
648
|
+
logger.log('INFO', '选择备份', `来源: ${backupSource}, 版本: ${selectedBackup.version}`);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// 如果是本地备份,需要先上传到服务器
|
|
652
|
+
if (backupSource === 'local' && selectedBackup) {
|
|
653
|
+
const remoteFile = await rollbackFromLocal({
|
|
654
|
+
ssh,
|
|
655
|
+
envConfig,
|
|
656
|
+
localBackupFile: backupFile,
|
|
657
|
+
logger
|
|
658
|
+
});
|
|
659
|
+
backupFile = remoteFile;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// 执行回滚
|
|
517
663
|
await rollbackDeployment({
|
|
518
664
|
environment,
|
|
519
665
|
envConfig,
|
|
520
|
-
specifiedVersion
|
|
666
|
+
specifiedVersion: specifiedVersion || (selectedBackup ? selectedBackup.version : undefined),
|
|
667
|
+
backupFile,
|
|
668
|
+
logger,
|
|
669
|
+
ssh // 传递已连接的 ssh
|
|
521
670
|
});
|
|
522
671
|
|
|
523
|
-
|
|
672
|
+
await ssh.disconnect();
|
|
673
|
+
logger.end('success');
|
|
524
674
|
|
|
525
675
|
// 回滚成功,发送钉钉通知
|
|
526
676
|
if (config.dingtalk && config.dingtalk.enabled && config.dingtalk.webhook) {
|
|
@@ -533,10 +683,18 @@ async function rollbackCommand(config) {
|
|
|
533
683
|
success: true,
|
|
534
684
|
keyword: config.dingtalk.keyword || '部署'
|
|
535
685
|
});
|
|
686
|
+
logger.logDingTalk(true);
|
|
536
687
|
}
|
|
537
688
|
} catch (error) {
|
|
538
689
|
console.error('❌ 回滚失败:', error.message);
|
|
539
|
-
|
|
690
|
+
logger.log('ERROR', '回滚失败', error.message);
|
|
691
|
+
logger.end('failed');
|
|
692
|
+
|
|
693
|
+
try {
|
|
694
|
+
await ssh.disconnect();
|
|
695
|
+
} catch (e) {
|
|
696
|
+
// 忽略
|
|
697
|
+
}
|
|
540
698
|
|
|
541
699
|
// 回滚失败,发送钉钉通知
|
|
542
700
|
if (config.dingtalk && config.dingtalk.enabled && config.dingtalk.webhook) {
|
|
@@ -549,12 +707,22 @@ async function rollbackCommand(config) {
|
|
|
549
707
|
success: false,
|
|
550
708
|
keyword: config.dingtalk.keyword || '部署'
|
|
551
709
|
});
|
|
710
|
+
logger.logDingTalk(false, error.message);
|
|
552
711
|
}
|
|
553
712
|
|
|
554
713
|
process.exit(1);
|
|
555
714
|
}
|
|
556
715
|
}
|
|
557
716
|
|
|
717
|
+
/**
|
|
718
|
+
* 格式化文件大小
|
|
719
|
+
*/
|
|
720
|
+
function formatFileSize(bytes) {
|
|
721
|
+
if (bytes < 1024) return bytes + ' B';
|
|
722
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
723
|
+
return (bytes / (1024 * 1024)).toFixed(2) + ' MB';
|
|
724
|
+
}
|
|
725
|
+
|
|
558
726
|
/**
|
|
559
727
|
* 主入口
|
|
560
728
|
*/
|
package/src/config-template.js
CHANGED
|
@@ -80,5 +80,18 @@ export default {
|
|
|
80
80
|
webhook: 'https://oapi.dingtalk.com/robot/send?access_token=your-token', // 钉钉机器人 webhook URL
|
|
81
81
|
enabled: true, // 是否启用钉钉通知,默认 true
|
|
82
82
|
keyword: '部署' // 安全设置关键词(如果机器人设置了关键词,必须配置此项)
|
|
83
|
-
}
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* 日志配置(可选)
|
|
87
|
+
* 部署日志存储目录,默认项目根目录下的 logs 目录
|
|
88
|
+
*/
|
|
89
|
+
logDir: 'logs',
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 本地备份目录(可选)
|
|
93
|
+
* 线上备份下载到本地的存储目录,默认 D:\备份
|
|
94
|
+
* 保留 7 天内的备份,自动清理旧备份
|
|
95
|
+
*/
|
|
96
|
+
localBackupDir: 'D:\\备份'
|
|
84
97
|
};
|