@noahyu/cd-cli 1.1.0 → 1.2.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 +12 -16
- package/dist/bin/cli.js +5 -1
- package/dist/src/index.js +130 -60
- package/dist/src/templates/config.ejs +66 -0
- package/dist/src/utils/get-version.js +31 -0
- package/dist/src/utils/template.js +20 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 简易的项目部署工具
|
|
2
2
|
|
|
3
|
-
一个简易的部署工具,支持版本管理和零停机部署。
|
|
3
|
+
> 一个简易的部署工具,支持版本管理和零停机部署。
|
|
4
4
|
|
|
5
5
|
## 功能特性
|
|
6
6
|
|
|
@@ -35,7 +35,8 @@ pcli-cd init
|
|
|
35
35
|
|
|
36
36
|
这会在项目根目录创建 `pcli-cd.config.js` 配置文件。
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
> [!WARNING]
|
|
39
|
+
> `pcli-cd.config.js` 配置文件存在敏感信息,不应该提交到 Git
|
|
39
40
|
|
|
40
41
|
### 2. 部署项目
|
|
41
42
|
|
|
@@ -87,7 +88,8 @@ PM2 配置始终指向软链接 `.output/server/index.mjs`,这样切换版本
|
|
|
87
88
|
|
|
88
89
|
配置文件 `pcli-cd.config.js` 示例:
|
|
89
90
|
|
|
90
|
-
>
|
|
91
|
+
> [!WARNING]
|
|
92
|
+
> `pcli-cd.config.js` 配置文件存在敏感信息,不应该提交到 Git
|
|
91
93
|
|
|
92
94
|
```javascript
|
|
93
95
|
// pcli-cd 部署配置文件
|
|
@@ -106,13 +108,10 @@ export default {
|
|
|
106
108
|
port: 22,
|
|
107
109
|
/** 用户名 */
|
|
108
110
|
username: 'root',
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
privateKeyPath: '/home/user/.ssh/id_rsa',
|
|
114
|
-
/** 密码 */
|
|
115
|
-
password: 'your-password',
|
|
111
|
+
/** SSH 认证方式(优先级:privateKey > privateKeyPath > password) */
|
|
112
|
+
privateKey: '-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----', // 私钥
|
|
113
|
+
privateKeyPath: '/home/user/.ssh/id_rsa', // 私钥文件路径
|
|
114
|
+
password: 'your-password', // 密码
|
|
116
115
|
/** 部署目录 */
|
|
117
116
|
deployPath: '/var/www/your-app',
|
|
118
117
|
},
|
|
@@ -344,7 +343,8 @@ pcli-cd rollback
|
|
|
344
343
|
|
|
345
344
|
每个项目只需要一个 `pcli-cd.config.js` 配置文件,工具会自动读取当前目录下的配置。
|
|
346
345
|
|
|
347
|
-
|
|
346
|
+
> [!WARNING]
|
|
347
|
+
> `pcli-cd.config.js` 配置文件存在敏感信息,不应该提交到 Git
|
|
348
348
|
|
|
349
349
|
## 注意事项
|
|
350
350
|
|
|
@@ -420,7 +420,3 @@ ssh -p 22 root@your-server.com
|
|
|
420
420
|
# 测试指定私钥连接
|
|
421
421
|
ssh -i /home/user/.ssh/id_rsa -p 22 root@your-server.com
|
|
422
422
|
```
|
|
423
|
-
|
|
424
|
-
## License
|
|
425
|
-
|
|
426
|
-
MIT
|
package/dist/bin/cli.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { program } from 'commander';
|
|
3
3
|
import { deployCommand, initConfig, listVersions, rollbackVersion } from '../src/index.js';
|
|
4
|
+
import { getVersion } from '../src/utils/get-version.js';
|
|
4
5
|
|
|
5
|
-
program.version('
|
|
6
|
+
program.version(getVersion(), '-v, --version').description('Simple CI/CD deployment tool');
|
|
6
7
|
program
|
|
7
8
|
.command('deploy')
|
|
8
9
|
.alias('cd')
|
|
9
10
|
.description('Build and deploy project to server')
|
|
10
11
|
.option('-c, --config <config>', 'Configuration file path', './pcli-cd.config.js')
|
|
11
12
|
.option('-v, --version <version>', 'Specify version number')
|
|
13
|
+
.option('-n, --name <name>', 'Specify deployment environment name (dev, prod, staging, etc.). If not specified, will prompt to select interactively')
|
|
12
14
|
.action(deployCommand);
|
|
13
15
|
program.command('init').description('Initialize CD configuration file').action(initConfig);
|
|
14
16
|
program
|
|
@@ -16,6 +18,7 @@ program
|
|
|
16
18
|
.alias('ls')
|
|
17
19
|
.description('List deployed versions on server')
|
|
18
20
|
.option('-c, --config <config>', 'Configuration file path', './pcli-cd.config.js')
|
|
21
|
+
.option('-n, --name <name>', 'Specify environment name to list (dev, prod, staging, etc.). If not specified, will prompt to select interactively')
|
|
19
22
|
.action(listVersions);
|
|
20
23
|
program
|
|
21
24
|
.command('rollback')
|
|
@@ -23,5 +26,6 @@ program
|
|
|
23
26
|
.description('Rollback to a previous version')
|
|
24
27
|
.option('-c, --config <config>', 'Configuration file path', './pcli-cd.config.js')
|
|
25
28
|
.option('-v, --version <version>', 'Version to rollback to')
|
|
29
|
+
.option('-n, --name <name>', 'Specify environment name to rollback (dev, prod, staging, etc.). If not specified, will prompt to select interactively')
|
|
26
30
|
.action(rollbackVersion);
|
|
27
31
|
program.parse(process.argv);
|
package/dist/src/index.js
CHANGED
|
@@ -8,24 +8,23 @@ import { NodeSSH } from 'node-ssh';
|
|
|
8
8
|
import ora from 'ora';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
10
|
import inquirer from 'inquirer';
|
|
11
|
+
import { renderConfigTemplate } from './utils/template.js';
|
|
11
12
|
|
|
12
13
|
async function deployCommand(options) {
|
|
13
14
|
const configPath = resolve(process.cwd(), options.config);
|
|
14
|
-
|
|
15
|
-
console.log(chalk.red(`❌ 配置文件不存在: ${configPath}`));
|
|
16
|
-
console.log(chalk.yellow('💡 请创建 pcli-cd.config.js 配置文件'));
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
15
|
+
const configResult = await resolveEnvConfig(configPath, options.name);
|
|
19
16
|
let config;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
config = configModule.default || configModule;
|
|
17
|
+
if (configResult.targetConfig) {
|
|
18
|
+
config = configResult.targetConfig;
|
|
23
19
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
else {
|
|
21
|
+
const selected = await selectEnvironmentFromConfigs(configResult.allConfigs);
|
|
22
|
+
config = selected.targetConfig;
|
|
27
23
|
}
|
|
28
|
-
|
|
24
|
+
console.log(chalk.blue(`🚀 部署环境: ${chalk.bold(config.name)}`));
|
|
25
|
+
console.log(chalk.gray(`📍 部署路径: ${config.server.deployPath}`));
|
|
26
|
+
let version = options.version;
|
|
27
|
+
if (!version) {
|
|
29
28
|
const answers = await inquirer.prompt([
|
|
30
29
|
{
|
|
31
30
|
type: 'input',
|
|
@@ -35,18 +34,14 @@ async function deployCommand(options) {
|
|
|
35
34
|
validate: (input) => input.trim() !== '' || '版本号不能为空',
|
|
36
35
|
},
|
|
37
36
|
]);
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
config.version = options.version || config.version;
|
|
37
|
+
version = answers.version;
|
|
42
38
|
}
|
|
43
|
-
await deploy(config);
|
|
39
|
+
await deploy(config, version);
|
|
44
40
|
}
|
|
45
|
-
async function deploy(config) {
|
|
41
|
+
async function deploy(config, version) {
|
|
46
42
|
const spinner = ora();
|
|
47
43
|
const tempDir = join(process.cwd(), '.deploy-temp');
|
|
48
44
|
const zipPath = join(tempDir, 'build.zip');
|
|
49
|
-
const version = config.version || `v${Date.now()}`;
|
|
50
45
|
const buildDirName = config.buildDir.split('/').pop() || 'build';
|
|
51
46
|
try {
|
|
52
47
|
await fse.remove(tempDir);
|
|
@@ -75,8 +70,10 @@ async function deploy(config) {
|
|
|
75
70
|
spinner.succeed('服务器连接成功');
|
|
76
71
|
await cleanTempLinks(ssh, config.server.deployPath, buildDirName);
|
|
77
72
|
spinner.start('检查部署环境...');
|
|
78
|
-
await handleExistingDeployDir(ssh, config.server.deployPath, buildDirName);
|
|
79
|
-
spinner.
|
|
73
|
+
await handleExistingDeployDir(ssh, config.server.deployPath, buildDirName, spinner);
|
|
74
|
+
if (spinner.isSpinning) {
|
|
75
|
+
spinner.succeed('部署环境检查完成');
|
|
76
|
+
}
|
|
80
77
|
const versionDirName = `${buildDirName}-${version}`;
|
|
81
78
|
const versionPath = join(config.server.deployPath, versionDirName);
|
|
82
79
|
const currentLinkPath = join(config.server.deployPath, buildDirName);
|
|
@@ -204,6 +201,13 @@ async function cleanOldVersions(ssh, deployPath, buildDirName, keepCount) {
|
|
|
204
201
|
async function initConfig() {
|
|
205
202
|
console.log(chalk.blue('🚀 初始化配置文件'));
|
|
206
203
|
const answers = await inquirer.prompt([
|
|
204
|
+
{
|
|
205
|
+
type: 'input',
|
|
206
|
+
name: 'envName',
|
|
207
|
+
message: '环境名称 (如: dev, prod, staging):',
|
|
208
|
+
default: 'dev',
|
|
209
|
+
validate: (input) => input.trim() !== '' || '请输入环境名称',
|
|
210
|
+
},
|
|
207
211
|
{
|
|
208
212
|
type: 'input',
|
|
209
213
|
name: 'buildCommand',
|
|
@@ -251,44 +255,35 @@ async function initConfig() {
|
|
|
251
255
|
message: 'PM2 应用名称 (可选):',
|
|
252
256
|
},
|
|
253
257
|
]);
|
|
254
|
-
const
|
|
258
|
+
const configContent = renderConfigTemplate({
|
|
259
|
+
envName: answers.envName,
|
|
255
260
|
buildCommand: answers.buildCommand,
|
|
256
261
|
buildDir: answers.buildDir,
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
excludeFiles: [],
|
|
265
|
-
};
|
|
266
|
-
if (answers.pm2AppName) {
|
|
267
|
-
config.pm2 = {
|
|
268
|
-
appName: answers.pm2AppName,
|
|
269
|
-
restart: true,
|
|
270
|
-
};
|
|
271
|
-
}
|
|
272
|
-
const configContent = `// pcli-cd 部署配置文件
|
|
273
|
-
export default ${JSON.stringify(config, null, 2)}`;
|
|
262
|
+
host: answers.host,
|
|
263
|
+
port: answers.port,
|
|
264
|
+
username: answers.username,
|
|
265
|
+
privateKeyPath: answers.privateKeyPath || undefined,
|
|
266
|
+
deployPath: answers.deployPath,
|
|
267
|
+
pm2AppName: answers.pm2AppName || undefined,
|
|
268
|
+
});
|
|
274
269
|
await fse.writeFile('pcli-cd.config.js', configContent);
|
|
275
270
|
console.log(chalk.green('✅ 配置文件已创建: pcli-cd.config.js'));
|
|
271
|
+
console.log(chalk.blue(`📝 默认环境: ${answers.envName}`));
|
|
272
|
+
console.log(chalk.gray('💡 可以在 apps 数组中添加更多环境配置'));
|
|
273
|
+
console.log(chalk.gray('💡 配置文件包含详细的注释说明'));
|
|
276
274
|
}
|
|
277
275
|
async function listVersions(options) {
|
|
278
276
|
const configPath = resolve(process.cwd(), options.config);
|
|
279
|
-
|
|
280
|
-
console.log(chalk.red(`❌ 配置文件不存在: ${configPath}`));
|
|
281
|
-
process.exit(1);
|
|
282
|
-
}
|
|
277
|
+
const configResult = await resolveEnvConfig(configPath, options.name);
|
|
283
278
|
let config;
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
config = configModule.default || configModule;
|
|
279
|
+
if (configResult.targetConfig) {
|
|
280
|
+
config = configResult.targetConfig;
|
|
287
281
|
}
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
282
|
+
else {
|
|
283
|
+
const selected = await selectEnvironmentFromConfigs(configResult.allConfigs);
|
|
284
|
+
config = selected.targetConfig;
|
|
291
285
|
}
|
|
286
|
+
console.log(chalk.blue(`🔍 查看环境: ${chalk.bold(config.name)}`));
|
|
292
287
|
const spinner = ora('正在获取版本列表...');
|
|
293
288
|
spinner.start();
|
|
294
289
|
try {
|
|
@@ -342,19 +337,16 @@ async function listVersions(options) {
|
|
|
342
337
|
}
|
|
343
338
|
async function rollbackVersion(options) {
|
|
344
339
|
const configPath = resolve(process.cwd(), options.config);
|
|
345
|
-
|
|
346
|
-
console.log(chalk.red(`❌ 配置文件不存在: ${configPath}`));
|
|
347
|
-
process.exit(1);
|
|
348
|
-
}
|
|
340
|
+
const configResult = await resolveEnvConfig(configPath, options.name);
|
|
349
341
|
let config;
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
config = configModule.default || configModule;
|
|
342
|
+
if (configResult.targetConfig) {
|
|
343
|
+
config = configResult.targetConfig;
|
|
353
344
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
345
|
+
else {
|
|
346
|
+
const selected = await selectEnvironmentFromConfigs(configResult.allConfigs);
|
|
347
|
+
config = selected.targetConfig;
|
|
357
348
|
}
|
|
349
|
+
console.log(chalk.blue(`⏪ 回滚环境: ${chalk.bold(config.name)}`));
|
|
358
350
|
const buildDirName = config.buildDir.split('/').pop() || 'build';
|
|
359
351
|
let targetVersion = options.version;
|
|
360
352
|
if (!targetVersion) {
|
|
@@ -451,6 +443,76 @@ async function performRollback(config, targetVersion, buildDirName) {
|
|
|
451
443
|
process.exit(1);
|
|
452
444
|
}
|
|
453
445
|
}
|
|
446
|
+
async function resolveEnvConfig(configPath, envName) {
|
|
447
|
+
if (!existsSync(configPath)) {
|
|
448
|
+
console.log(chalk.red(`❌ 配置文件不存在: ${configPath}`));
|
|
449
|
+
console.log(chalk.yellow('💡 请创建 pcli-cd.config.js 配置文件'));
|
|
450
|
+
process.exit(1);
|
|
451
|
+
}
|
|
452
|
+
let rawConfig;
|
|
453
|
+
try {
|
|
454
|
+
const configModule = await import(configPath);
|
|
455
|
+
rawConfig = configModule.default || configModule;
|
|
456
|
+
}
|
|
457
|
+
catch (error) {
|
|
458
|
+
console.log(chalk.red(`❌ 配置文件读取失败: ${error}`));
|
|
459
|
+
process.exit(1);
|
|
460
|
+
}
|
|
461
|
+
if (!rawConfig.apps || !Array.isArray(rawConfig.apps)) {
|
|
462
|
+
console.log(chalk.red('❌ 配置文件格式错误:缺少 apps 数组'));
|
|
463
|
+
console.log(chalk.yellow('💡 配置文件应该包含一个 apps 数组,每个元素都是一个环境配置'));
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
if (rawConfig.apps.length === 0) {
|
|
467
|
+
console.log(chalk.red('❌ 配置文件中没有任何环境配置'));
|
|
468
|
+
console.log(chalk.yellow('💡 请在 apps 数组中添加至少一个环境配置'));
|
|
469
|
+
process.exit(1);
|
|
470
|
+
}
|
|
471
|
+
if (!envName) {
|
|
472
|
+
return {
|
|
473
|
+
targetConfig: null,
|
|
474
|
+
allConfigs: rawConfig.apps,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
const envConfig = rawConfig.apps.find((app) => app.name === envName);
|
|
478
|
+
if (!envConfig) {
|
|
479
|
+
console.log(chalk.red(`❌ 环境配置 "${envName}" 不存在`));
|
|
480
|
+
console.log(chalk.yellow('💡 可用的环境配置:'));
|
|
481
|
+
rawConfig.apps.forEach((app) => {
|
|
482
|
+
console.log(chalk.gray(` - ${app.name}`));
|
|
483
|
+
});
|
|
484
|
+
process.exit(1);
|
|
485
|
+
}
|
|
486
|
+
return {
|
|
487
|
+
targetConfig: envConfig,
|
|
488
|
+
allConfigs: rawConfig.apps,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
async function selectEnvironmentFromConfigs(allConfigs) {
|
|
492
|
+
if (allConfigs.length === 1) {
|
|
493
|
+
return {
|
|
494
|
+
envName: allConfigs[0].name,
|
|
495
|
+
targetConfig: allConfigs[0],
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
const envChoices = allConfigs.map((app) => ({
|
|
499
|
+
name: `${app.name} (${app.server.host})`,
|
|
500
|
+
value: app.name,
|
|
501
|
+
}));
|
|
502
|
+
const answers = await inquirer.prompt([
|
|
503
|
+
{
|
|
504
|
+
type: 'list',
|
|
505
|
+
name: 'envName',
|
|
506
|
+
message: '请选择环境:',
|
|
507
|
+
choices: envChoices,
|
|
508
|
+
},
|
|
509
|
+
]);
|
|
510
|
+
const selectedConfig = allConfigs.find((app) => app.name === answers.envName);
|
|
511
|
+
return {
|
|
512
|
+
envName: answers.envName,
|
|
513
|
+
targetConfig: selectedConfig,
|
|
514
|
+
};
|
|
515
|
+
}
|
|
454
516
|
async function createSSHConnection(server) {
|
|
455
517
|
const ssh = new NodeSSH();
|
|
456
518
|
const connectConfig = {
|
|
@@ -486,7 +548,7 @@ async function cleanTempLinks(ssh, deployPath, buildDirName) {
|
|
|
486
548
|
console.warn(chalk.yellow(`⚠️ 清理临时链接时出现警告: ${error}`));
|
|
487
549
|
}
|
|
488
550
|
}
|
|
489
|
-
async function handleExistingDeployDir(ssh, deployPath, buildDirName) {
|
|
551
|
+
async function handleExistingDeployDir(ssh, deployPath, buildDirName, spinner) {
|
|
490
552
|
const currentLinkPath = join(deployPath, buildDirName);
|
|
491
553
|
const checkResult = await ssh.execCommand(`test -e ${currentLinkPath}`);
|
|
492
554
|
if (checkResult.code !== 0) {
|
|
@@ -500,6 +562,7 @@ async function handleExistingDeployDir(ssh, deployPath, buildDirName) {
|
|
|
500
562
|
const fileType = typeResult.stdout.trim();
|
|
501
563
|
if (fileType.includes('directory') || fileType === 'directory') {
|
|
502
564
|
const backupPath = `${currentLinkPath}.backup.${Date.now()}`;
|
|
565
|
+
spinner.stop();
|
|
503
566
|
console.log(chalk.yellow(`⚠️ 检测到已存在的目录: ${currentLinkPath}`));
|
|
504
567
|
console.log(chalk.blue(`📁 将备份到: ${backupPath}`));
|
|
505
568
|
const answers = await inquirer.prompt([
|
|
@@ -513,18 +576,25 @@ async function handleExistingDeployDir(ssh, deployPath, buildDirName) {
|
|
|
513
576
|
if (!answers.proceed) {
|
|
514
577
|
throw new Error('用户取消部署');
|
|
515
578
|
}
|
|
579
|
+
spinner.start('正在备份已存在的目录...');
|
|
516
580
|
const backupResult = await ssh.execCommand(`mv ${currentLinkPath} ${backupPath}`);
|
|
517
581
|
if (backupResult.code !== 0) {
|
|
518
582
|
throw new Error(`备份目录失败: ${backupResult.stderr}`);
|
|
519
583
|
}
|
|
584
|
+
spinner.stop();
|
|
520
585
|
console.log(chalk.green(`✅ 目录已备份到: ${backupPath}`));
|
|
521
586
|
}
|
|
522
587
|
else {
|
|
523
588
|
const backupPath = `${currentLinkPath}.backup.${Date.now()}`;
|
|
589
|
+
spinner.stop();
|
|
590
|
+
console.log(chalk.yellow(`⚠️ 检测到已存在的文件: ${currentLinkPath}`));
|
|
591
|
+
console.log(chalk.blue(`📁 将备份到: ${backupPath}`));
|
|
592
|
+
spinner.start('正在备份已存在的文件...');
|
|
524
593
|
const backupResult = await ssh.execCommand(`mv ${currentLinkPath} ${backupPath}`);
|
|
525
594
|
if (backupResult.code !== 0) {
|
|
526
595
|
throw new Error(`备份文件失败: ${backupResult.stderr}`);
|
|
527
596
|
}
|
|
597
|
+
spinner.stop();
|
|
528
598
|
console.log(chalk.green(`✅ 文件已备份到: ${backupPath}`));
|
|
529
599
|
}
|
|
530
600
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// pcli-cd.config.js
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
apps: [
|
|
5
|
+
{
|
|
6
|
+
// 环境名称
|
|
7
|
+
name: '<%= envName %>',
|
|
8
|
+
|
|
9
|
+
// 构建命令,部署前执行
|
|
10
|
+
buildCommand: '<%= buildCommand %>',
|
|
11
|
+
|
|
12
|
+
// 构建输出目录
|
|
13
|
+
buildDir: '<%= buildDir %>',
|
|
14
|
+
|
|
15
|
+
// 服务器配置
|
|
16
|
+
server: {
|
|
17
|
+
host: '<%= host %>',
|
|
18
|
+
port: <%= port %>,
|
|
19
|
+
username: '<%= username %>',<% if (privateKeyPath) { %>
|
|
20
|
+
privateKeyPath: '<%= privateKeyPath %>',<% } else { %>
|
|
21
|
+
// 私钥路径
|
|
22
|
+
// privateKeyPath: '~/.ssh/id_rsa',
|
|
23
|
+
|
|
24
|
+
// 或者使用私钥内容
|
|
25
|
+
// privateKey: `-----BEGIN OPENSSH PRIVATE KEY-----
|
|
26
|
+
// ...
|
|
27
|
+
// -----END OPENSSH PRIVATE KEY-----`,
|
|
28
|
+
|
|
29
|
+
// 或者使用密码
|
|
30
|
+
// password: 'your-password',<% } %>
|
|
31
|
+
deployPath: '<%= deployPath %>'
|
|
32
|
+
},<% if (pm2AppName) { %>
|
|
33
|
+
|
|
34
|
+
// PM2 进程管理配置
|
|
35
|
+
pm2: {
|
|
36
|
+
appName: '<%= pm2AppName %>',
|
|
37
|
+
restart: true
|
|
38
|
+
},<% } else { %>
|
|
39
|
+
|
|
40
|
+
// PM2 进程管理配置(可选)
|
|
41
|
+
// pm2: {
|
|
42
|
+
// appName: 'my-app',
|
|
43
|
+
// restart: true
|
|
44
|
+
// },<% } %>
|
|
45
|
+
|
|
46
|
+
// 排除文件列表(可选)
|
|
47
|
+
excludeFiles: [
|
|
48
|
+
// 'node_modules/**',
|
|
49
|
+
// '*.log',
|
|
50
|
+
// '.env*'
|
|
51
|
+
],
|
|
52
|
+
|
|
53
|
+
// 部署前命令(可选)
|
|
54
|
+
// beforeDeploy: [
|
|
55
|
+
// 'npm test',
|
|
56
|
+
// 'npm run lint'
|
|
57
|
+
// ],
|
|
58
|
+
|
|
59
|
+
// 部署后命令(可选)
|
|
60
|
+
// afterDeploy: [
|
|
61
|
+
// 'npm install --production',
|
|
62
|
+
// 'npm run migrate'
|
|
63
|
+
// ]
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fse from 'fs-extra';
|
|
3
|
+
import parseJson from 'parse-json';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
|
|
7
|
+
function getVersion() {
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
const packageJsonPath = join(__dirname, '../../package.json');
|
|
11
|
+
let packageJsonContent;
|
|
12
|
+
try {
|
|
13
|
+
packageJsonContent = fse.readFileSync(packageJsonPath, 'utf-8');
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
throw new Error(`无法找到 package.json 文件:${packageJsonPath}`);
|
|
17
|
+
}
|
|
18
|
+
let packageData;
|
|
19
|
+
try {
|
|
20
|
+
packageData = parseJson(packageJsonContent);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new Error('package.json 文件格式错误,请检查 JSON 语法');
|
|
24
|
+
}
|
|
25
|
+
if (!packageData.version) {
|
|
26
|
+
throw new Error('package.json 中缺少 version 字段');
|
|
27
|
+
}
|
|
28
|
+
return packageData.version || '0.0.0';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { getVersion };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'fs';
|
|
3
|
+
import { dirname, join } from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import ejs from 'ejs';
|
|
6
|
+
|
|
7
|
+
function renderConfigTemplate(templateData) {
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = dirname(__filename);
|
|
10
|
+
const templatePath = join(__dirname, '../templates/config.ejs');
|
|
11
|
+
try {
|
|
12
|
+
const templateContent = readFileSync(templatePath, 'utf-8');
|
|
13
|
+
return ejs.render(templateContent, templateData);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
throw new Error(`模板渲染失败: ${error}`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { renderConfigTemplate };
|