@noahyu/cd-cli 1.2.1 → 1.3.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 +106 -104
- package/dist/src/index.js +51 -4
- package/dist/src/templates/config.ejs +8 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -38,6 +38,26 @@ pcli-cd init
|
|
|
38
38
|
> [!WARNING]
|
|
39
39
|
> `pcli-cd.config.js` 配置文件存在敏感信息,不应该提交到 Git
|
|
40
40
|
|
|
41
|
+
#### 配置选项
|
|
42
|
+
|
|
43
|
+
| 选项 | 类型 | 必填 | 说明 |
|
|
44
|
+
| ----------------------- | -------- | ---- | -------------------------------- |
|
|
45
|
+
| `name` | string | 是 | 环境名称 |
|
|
46
|
+
| `buildCommand` | string | 否 | 构建命令,如 "npm run build" |
|
|
47
|
+
| `buildDir` | string | 是 | 构建输出目录,如 ".output" |
|
|
48
|
+
| `server.host` | string | 是 | 服务器地址 |
|
|
49
|
+
| `server.port` | number | 否 | 端口,默认 22 |
|
|
50
|
+
| `server.username` | string | 是 | 用户名 |
|
|
51
|
+
| `server.privateKey` | string | 否 | 私钥内容,优先级最高 |
|
|
52
|
+
| `server.privateKeyPath` | string | 否 | 私钥文件路径 |
|
|
53
|
+
| `server.password` | string | 否 | 密码 |
|
|
54
|
+
| `server.deployPath` | string | 是 | 服务器部署路径 |
|
|
55
|
+
| `pm2.appName` | string | 否 | PM2 应用名称 |
|
|
56
|
+
| `pm2.restart` | boolean | 否 | 是否重启 PM2 应用 |
|
|
57
|
+
| `excludeFiles` | string[] | 否 | 排除的文件模式(相对于构建目录) |
|
|
58
|
+
| `beforeDeploy` | string[] | 否 | 部署前执行的命令 |
|
|
59
|
+
| `afterDeploy` | string[] | 否 | 部署后执行的命令 |
|
|
60
|
+
|
|
41
61
|
### 2. 部署项目
|
|
42
62
|
|
|
43
63
|
```bash
|
|
@@ -94,49 +114,54 @@ PM2 配置始终指向软链接 `.output/server/index.mjs`,这样切换版本
|
|
|
94
114
|
```javascript
|
|
95
115
|
// pcli-cd 部署配置文件
|
|
96
116
|
export default {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
117
|
+
apps: [
|
|
118
|
+
{
|
|
119
|
+
name: 'prod',
|
|
120
|
+
/** 构建命令 (可选):构建项目 */
|
|
121
|
+
buildCommand: 'npm run build',
|
|
122
|
+
/** 构建输出目录 */
|
|
123
|
+
buildDir: '.output',
|
|
124
|
+
/** 版本号 (可选,不指定会在部署时询问) */
|
|
125
|
+
version: 'v1.0.0',
|
|
126
|
+
/** 服务器配置 */
|
|
127
|
+
server: {
|
|
128
|
+
/** 服务器地址 */
|
|
129
|
+
host: '192.168.1.100',
|
|
130
|
+
/** 端口号 */
|
|
131
|
+
port: 22,
|
|
132
|
+
/** 用户名 */
|
|
133
|
+
username: 'root',
|
|
134
|
+
/** SSH 认证方式(优先级:privateKey > privateKeyPath > password) */
|
|
135
|
+
privateKey: '-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----', // 私钥
|
|
136
|
+
privateKeyPath: '/home/user/.ssh/id_rsa', // 私钥文件路径
|
|
137
|
+
password: 'your-password', // 密码
|
|
138
|
+
/** 部署目录 */
|
|
139
|
+
deployPath: '/var/www/your-app',
|
|
140
|
+
},
|
|
141
|
+
/** PM2 配置 (可选) */
|
|
142
|
+
pm2: {
|
|
143
|
+
/** 进程名称 */
|
|
144
|
+
appName: 'your-app-name',
|
|
145
|
+
/** 是否立即重启 */
|
|
146
|
+
restart: true,
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* 排除的文件 (可选) - 作用于构建产物目录
|
|
151
|
+
*
|
|
152
|
+
* 请根据构建输出的实际内容谨慎配置
|
|
153
|
+
*/
|
|
154
|
+
excludeFiles: [
|
|
155
|
+
// '**/*.map', // Source Map 文件
|
|
156
|
+
// '**/.DS_Store', // macOS 系统文件
|
|
157
|
+
// '**/Thumbs.db' // Windows 缩略图缓存
|
|
158
|
+
],
|
|
159
|
+
/** 部署前命令 (可选) */
|
|
160
|
+
beforeDeploy: ['npm run test'],
|
|
161
|
+
/** 部署后命令 (可选) */
|
|
162
|
+
afterDeploy: ['npm install --production'],
|
|
163
|
+
},
|
|
135
164
|
],
|
|
136
|
-
/** 部署前命令 (可选) */
|
|
137
|
-
beforeDeploy: ['npm run test'],
|
|
138
|
-
/** 部署后命令 (可选) */
|
|
139
|
-
afterDeploy: ['npm install --production'],
|
|
140
165
|
}
|
|
141
166
|
```
|
|
142
167
|
|
|
@@ -150,12 +175,18 @@ export default {
|
|
|
150
175
|
|
|
151
176
|
```javascript
|
|
152
177
|
export default {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
178
|
+
apps: [
|
|
179
|
+
{
|
|
180
|
+
name: 'prod',
|
|
181
|
+
server: {
|
|
182
|
+
host: '192.168.1.100',
|
|
183
|
+
username: 'root',
|
|
184
|
+
privateKey: process.env.SSH_PRIVATE_KEY, // 从环境变量读取
|
|
185
|
+
deployPath: '/var/www/app',
|
|
186
|
+
},
|
|
187
|
+
// ···
|
|
188
|
+
},
|
|
189
|
+
],
|
|
159
190
|
}
|
|
160
191
|
```
|
|
161
192
|
|
|
@@ -165,12 +196,18 @@ export default {
|
|
|
165
196
|
|
|
166
197
|
```javascript
|
|
167
198
|
export default {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
199
|
+
apps: [
|
|
200
|
+
{
|
|
201
|
+
name: 'dev',
|
|
202
|
+
server: {
|
|
203
|
+
host: '192.168.1.100',
|
|
204
|
+
username: 'root',
|
|
205
|
+
privateKeyPath: '/home/user/.ssh/id_rsa',
|
|
206
|
+
deployPath: '/var/www/app',
|
|
207
|
+
},
|
|
208
|
+
// ···
|
|
209
|
+
},
|
|
210
|
+
],
|
|
174
211
|
}
|
|
175
212
|
```
|
|
176
213
|
|
|
@@ -180,12 +217,18 @@ export default {
|
|
|
180
217
|
|
|
181
218
|
```javascript
|
|
182
219
|
export default {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
220
|
+
apps: [
|
|
221
|
+
{
|
|
222
|
+
name: 'test',
|
|
223
|
+
server: {
|
|
224
|
+
host: '192.168.1.100',
|
|
225
|
+
username: 'root',
|
|
226
|
+
password: 'your-password',
|
|
227
|
+
deployPath: '/var/www/app',
|
|
228
|
+
},
|
|
229
|
+
// ···
|
|
230
|
+
},
|
|
231
|
+
],
|
|
189
232
|
}
|
|
190
233
|
```
|
|
191
234
|
|
|
@@ -205,26 +248,6 @@ export default {
|
|
|
205
248
|
- ⚠️ **测试环境**:可以使用密码认证
|
|
206
249
|
- ❌ **避免**:在配置文件中硬编码密码或私钥内容
|
|
207
250
|
|
|
208
|
-
## 配置选项
|
|
209
|
-
|
|
210
|
-
| 选项 | 类型 | 必填 | 说明 |
|
|
211
|
-
| ----------------------- | -------- | ---- | -------------------------------- |
|
|
212
|
-
| `buildCommand` | string | 否 | 构建命令,如 "npm run build" |
|
|
213
|
-
| `buildDir` | string | 是 | 构建输出目录,如 ".output" |
|
|
214
|
-
| `version` | string | 否 | 版本号,不指定会在部署时询问 |
|
|
215
|
-
| `server.host` | string | 是 | 服务器地址 |
|
|
216
|
-
| `server.port` | number | 否 | SSH 端口,默认 22 |
|
|
217
|
-
| `server.username` | string | 是 | 用户名 |
|
|
218
|
-
| `server.password` | string | 否 | SSH 密码 |
|
|
219
|
-
| `server.privateKey` | string | 否 | SSH 私钥内容,优先级最高 |
|
|
220
|
-
| `server.privateKeyPath` | string | 否 | SSH 私钥文件路径 |
|
|
221
|
-
| `server.deployPath` | string | 是 | 服务器部署路径 |
|
|
222
|
-
| `pm2.appName` | string | 否 | PM2 应用名称 |
|
|
223
|
-
| `pm2.restart` | boolean | 否 | 是否重启 PM2 应用 |
|
|
224
|
-
| `excludeFiles` | string[] | 否 | 排除的文件模式(相对于构建目录) |
|
|
225
|
-
| `beforeDeploy` | string[] | 否 | 部署前执行的命令 |
|
|
226
|
-
| `afterDeploy` | string[] | 否 | 部署后执行的命令 |
|
|
227
|
-
|
|
228
251
|
## 命令详解
|
|
229
252
|
|
|
230
253
|
### deploy (cd)
|
|
@@ -236,6 +259,7 @@ pcli-cd deploy [options]
|
|
|
236
259
|
|
|
237
260
|
Options:
|
|
238
261
|
-c, --config <config> 配置文件路径 (默认: ./pcli-cd.config.js)
|
|
262
|
+
-n, --name <name> 指定环境名称
|
|
239
263
|
-v, --version <version> 指定版本号
|
|
240
264
|
-h, --help 显示帮助信息
|
|
241
265
|
```
|
|
@@ -249,6 +273,7 @@ pcli-cd list [options]
|
|
|
249
273
|
|
|
250
274
|
Options:
|
|
251
275
|
-c, --config <config> 配置文件路径 (默认: ./pcli-cd.config.js)
|
|
276
|
+
-n, --name <name> 指定环境名称
|
|
252
277
|
-h, --help 显示帮助信息
|
|
253
278
|
```
|
|
254
279
|
|
|
@@ -261,6 +286,7 @@ pcli-cd rollback [options]
|
|
|
261
286
|
|
|
262
287
|
Options:
|
|
263
288
|
-c, --config <config> 配置文件路径 (默认: ./pcli-cd.config.js)
|
|
289
|
+
-n, --name <name> 指定环境名称
|
|
264
290
|
-v, --version <version> 回滚到的版本号
|
|
265
291
|
-h, --help 显示帮助信息
|
|
266
292
|
```
|
|
@@ -283,30 +309,6 @@ pcli-cd init
|
|
|
283
309
|
2. 然后对 `buildDir` 目录进行压缩,此时应用 `excludeFiles` 规则
|
|
284
310
|
3. 将压缩包上传到服务器
|
|
285
311
|
|
|
286
|
-
### 配置建议
|
|
287
|
-
|
|
288
|
-
```javascript
|
|
289
|
-
// ❌ 错误理解:认为排除的是项目根目录文件
|
|
290
|
-
export default {
|
|
291
|
-
excludeFiles: ['src/**', 'node_modules/**']
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// ✅ 正确理解:排除的是构建目录内的文件
|
|
295
|
-
export default {
|
|
296
|
-
excludeFiles: [
|
|
297
|
-
// 只有当这些文件确实出现在构建目录中,且确认不需要时才排除
|
|
298
|
-
'**/*.map', // Source Map 文件(通常不需要)
|
|
299
|
-
'**/.DS_Store', // macOS 系统文件
|
|
300
|
-
'**/Thumbs.db' // Windows 缩略图缓存
|
|
301
|
-
]
|
|
302
|
-
}
|
|
303
|
-
```
|
|
304
|
-
|
|
305
|
-
### 注意事项
|
|
306
|
-
|
|
307
|
-
- **运行时依赖**:某些框架(如 Nuxt、Next.js)的构建产物可能包含必需的 `node_modules`
|
|
308
|
-
- **建议**:初次使用时保持 `excludeFiles: []`,观察构建产物内容后再配置
|
|
309
|
-
|
|
310
312
|
## 版本管理
|
|
311
313
|
|
|
312
314
|
### 零停机部署流程
|
package/dist/src/index.js
CHANGED
|
@@ -63,7 +63,19 @@ async function deploy(config, version) {
|
|
|
63
63
|
throw new Error(`构建目录不存在: ${buildPath}`);
|
|
64
64
|
}
|
|
65
65
|
spinner.start('正在压缩文件...');
|
|
66
|
-
|
|
66
|
+
const extraFilesForZip = [];
|
|
67
|
+
if (config.files && config.files.length > 0) {
|
|
68
|
+
for (const file of config.files) {
|
|
69
|
+
const localFilePath = resolve(process.cwd(), file);
|
|
70
|
+
if (existsSync(localFilePath)) {
|
|
71
|
+
extraFilesForZip.push({ localPath: localFilePath, name: file });
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
console.warn(chalk.yellow(`⚠️ 额外文件不存在,已跳过: ${file}`));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
await createZip(buildPath, zipPath, config.excludeFiles, extraFilesForZip);
|
|
67
79
|
spinner.succeed('文件压缩完成');
|
|
68
80
|
spinner.start('正在连接服务器...');
|
|
69
81
|
const ssh = await createSSHConnection(config.server);
|
|
@@ -96,6 +108,22 @@ async function deploy(config, version) {
|
|
|
96
108
|
fi
|
|
97
109
|
`);
|
|
98
110
|
spinner.succeed('文件解压完成');
|
|
111
|
+
if (config.files && config.files.length > 0) {
|
|
112
|
+
spinner.start('正在部署额外文件...');
|
|
113
|
+
for (const file of config.files) {
|
|
114
|
+
const remoteVersionFilePath = join(versionPath, file);
|
|
115
|
+
const remoteRootFilePath = join(config.server.deployPath, file);
|
|
116
|
+
const checkFile = await ssh.execCommand(`test -f ${remoteVersionFilePath}`);
|
|
117
|
+
if (checkFile.code !== 0) {
|
|
118
|
+
spinner.warn(`额外文件不在版本目录中,已跳过: ${file}`);
|
|
119
|
+
spinner.start('正在部署额外文件...');
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
await ssh.execCommand(`mkdir -p ${join(config.server.deployPath, file, '..')}`);
|
|
123
|
+
await ssh.execCommand(`cp -f ${remoteVersionFilePath} ${remoteRootFilePath}`);
|
|
124
|
+
}
|
|
125
|
+
spinner.succeed(`额外文件部署完成 (${config.files.length} 个)`);
|
|
126
|
+
}
|
|
99
127
|
if (config.afterDeploy) {
|
|
100
128
|
spinner.start('执行部署后命令...');
|
|
101
129
|
for (const cmd of config.afterDeploy) {
|
|
@@ -110,7 +138,7 @@ async function deploy(config, version) {
|
|
|
110
138
|
if (linkResult.code !== 0) {
|
|
111
139
|
throw new Error(`创建临时软链接失败: ${linkResult.stderr}`);
|
|
112
140
|
}
|
|
113
|
-
const moveResult = await ssh.execCommand(`mv ${tempLinkPath} ${currentLinkPath}`);
|
|
141
|
+
const moveResult = await ssh.execCommand(`mv -T ${tempLinkPath} ${currentLinkPath}`);
|
|
114
142
|
if (moveResult.code !== 0) {
|
|
115
143
|
await ssh.execCommand(`rm -f ${tempLinkPath}`);
|
|
116
144
|
throw new Error(`切换软链接失败: ${moveResult.stderr}`);
|
|
@@ -163,7 +191,7 @@ async function deploy(config, version) {
|
|
|
163
191
|
process.exit(1);
|
|
164
192
|
}
|
|
165
193
|
}
|
|
166
|
-
async function createZip(sourcePath, outputPath, excludeFiles = []) {
|
|
194
|
+
async function createZip(sourcePath, outputPath, excludeFiles = [], extraFiles = []) {
|
|
167
195
|
return new Promise((resolve, reject) => {
|
|
168
196
|
const output = fse.createWriteStream(outputPath);
|
|
169
197
|
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
@@ -174,6 +202,9 @@ async function createZip(sourcePath, outputPath, excludeFiles = []) {
|
|
|
174
202
|
cwd: sourcePath,
|
|
175
203
|
ignore: excludeFiles,
|
|
176
204
|
});
|
|
205
|
+
for (const { localPath, name } of extraFiles) {
|
|
206
|
+
archive.file(localPath, { name });
|
|
207
|
+
}
|
|
177
208
|
archive.finalize();
|
|
178
209
|
});
|
|
179
210
|
}
|
|
@@ -405,7 +436,7 @@ async function performRollback(config, targetVersion, buildDirName) {
|
|
|
405
436
|
if (linkResult.code !== 0) {
|
|
406
437
|
throw new Error(`创建临时软链接失败: ${linkResult.stderr}`);
|
|
407
438
|
}
|
|
408
|
-
const moveResult = await ssh.execCommand(`mv ${tempLinkPath} ${currentLinkPath}`);
|
|
439
|
+
const moveResult = await ssh.execCommand(`mv -T ${tempLinkPath} ${currentLinkPath}`);
|
|
409
440
|
if (moveResult.code !== 0) {
|
|
410
441
|
await ssh.execCommand(`rm -f ${tempLinkPath}`);
|
|
411
442
|
throw new Error(`切换软链接失败: ${moveResult.stderr}`);
|
|
@@ -416,6 +447,22 @@ async function performRollback(config, targetVersion, buildDirName) {
|
|
|
416
447
|
await ssh.execCommand(`rm -f ${tempLinkPath}`);
|
|
417
448
|
throw error;
|
|
418
449
|
}
|
|
450
|
+
if (config.files && config.files.length > 0) {
|
|
451
|
+
spinner.start('正在还原额外文件...');
|
|
452
|
+
for (const file of config.files) {
|
|
453
|
+
const remoteVersionFilePath = join(versionPath, file);
|
|
454
|
+
const remoteRootFilePath = join(config.server.deployPath, file);
|
|
455
|
+
const checkFile = await ssh.execCommand(`test -f ${remoteVersionFilePath}`);
|
|
456
|
+
if (checkFile.code !== 0) {
|
|
457
|
+
spinner.warn(`版本 ${targetVersion} 中不存在文件 ${file},已跳过`);
|
|
458
|
+
spinner.start('正在还原额外文件...');
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
await ssh.execCommand(`mkdir -p ${join(config.server.deployPath, file, '..')}`);
|
|
462
|
+
await ssh.execCommand(`cp -f ${remoteVersionFilePath} ${remoteRootFilePath}`);
|
|
463
|
+
}
|
|
464
|
+
spinner.succeed('额外文件还原完成');
|
|
465
|
+
}
|
|
419
466
|
if (config.pm2) {
|
|
420
467
|
spinner.start('正在重启 PM2 应用...');
|
|
421
468
|
const { appName } = config.pm2;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noahyu/cd-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Global CLI tool for simple project deployment with version management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/bin/cli.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"author": "Noah Yu",
|
|
22
22
|
"repository": {
|
|
23
23
|
"type": "git",
|
|
24
|
-
"url": "git+https://github.com/
|
|
24
|
+
"url": "git+https://github.com/noahyu4882/project-cli.git"
|
|
25
25
|
},
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"engines": {
|