@noahyu/cd-cli 1.2.2 → 1.3.1

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 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
- buildCommand: 'npm run build',
99
- /** 构建输出目录 */
100
- buildDir: '.output',
101
- /** 版本号 (可选,不指定会在部署时询问) */
102
- version: 'v1.0.0',
103
- /** 服务器配置 */
104
- server: {
105
- /** 服务器地址 */
106
- host: '192.168.1.100',
107
- /** 端口号 */
108
- port: 22,
109
- /** 用户名 */
110
- username: 'root',
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', // 密码
115
- /** 部署目录 */
116
- deployPath: '/var/www/your-app',
117
- },
118
- /** PM2 配置 (可选) */
119
- pm2: {
120
- /** 进程名称 */
121
- appName: 'your-app-name',
122
- /** 是否立即重启 */
123
- restart: true,
124
- },
125
-
126
- /**
127
- * 排除的文件 (可选) - 作用于构建产物目录
128
- *
129
- * 请根据构建输出的实际内容谨慎配置
130
- */
131
- excludeFiles: [
132
- // '**/*.map', // Source Map 文件
133
- // '**/.DS_Store', // macOS 系统文件
134
- // '**/Thumbs.db' // Windows 缩略图缓存
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
- server: {
154
- host: '192.168.1.100',
155
- username: 'root',
156
- privateKey: process.env.SSH_PRIVATE_KEY, // 从环境变量读取
157
- deployPath: '/var/www/app',
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
- server: {
169
- host: '192.168.1.100',
170
- username: 'root',
171
- privateKeyPath: '/home/user/.ssh/id_rsa',
172
- deployPath: '/var/www/app',
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
- server: {
184
- host: '192.168.1.100',
185
- username: 'root',
186
- password: 'your-password',
187
- deployPath: '/var/www/app',
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
- await createZip(buildPath, zipPath, config.excludeFiles);
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,10 +108,26 @@ 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) {
102
- await ssh.execCommand(cmd, { cwd: versionPath });
130
+ await ssh.execCommand(cmd, { cwd: config.server.deployPath });
103
131
  }
104
132
  spinner.succeed('部署后命令执行完成');
105
133
  }
@@ -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
  }
@@ -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;
@@ -12,6 +12,14 @@ export default {
12
12
  // 构建输出目录
13
13
  buildDir: '<%= buildDir %>',
14
14
 
15
+ // 额外部署文件(可选)
16
+ // 这些文件独立于构建目录
17
+ // files: [
18
+ // 'package.json',
19
+ // '.env',
20
+ // 'ecosystem.config.js'
21
+ // ],
22
+
15
23
  // 服务器配置
16
24
  server: {
17
25
  host: '<%= host %>',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noahyu/cd-cli",
3
- "version": "1.2.2",
3
+ "version": "1.3.1",
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/Noah-Ywh/project-cli.git"
24
+ "url": "git+https://github.com/noahyu4882/project-cli.git"
25
25
  },
26
26
  "license": "MIT",
27
27
  "engines": {