@taole/deploy-helper 0.5.3 → 1.0.0-beta.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/deploy.mjs ADDED
@@ -0,0 +1,204 @@
1
+ import { hasChangeMe } from './util.mjs';
2
+ import { runPipeline, checkYunxiaoToken } from './lib/pipelineApi.mjs';
3
+ import { log } from './lib/util.mjs';
4
+ import { simpleGit } from 'simple-git';
5
+ import { getScpClient, TEST_SERVER_HOST } from './util.mjs';
6
+
7
+
8
+ class Deployer {
9
+ async deploy(workDir, mode, config) {
10
+ // 检查配置中是否存在默认值(CHANGE_ME)
11
+ const result = hasChangeMe(JSON.stringify(config));
12
+ if (result) {
13
+ throw new Error(`配置中存在默认值(CHANGE_ME), 请先修改配置`);
14
+ }
15
+
16
+ const needHandleAssets = config.assets && config.assets.dest;
17
+ const needHandleEntry = config.entry && config.entry.dest && (mode === 'prod' || (mode === 'test' && !config.entry.onlyProd));
18
+ const entryTestBranch = (config.entry && config.entry.testBranch) || "test";
19
+ const entryProdBranch = (config.entry && config.entry.prodBranch) || "master";
20
+ const entryTargetBranch = mode === 'prod' ? entryProdBranch : entryTestBranch;
21
+
22
+ // 检查流水线配置
23
+ checkYunxiaoToken(config, mode);
24
+
25
+
26
+ // 检查项目
27
+ // 1. 检查项目是否存在
28
+ let assetsDest = null;
29
+ let entryDest = null;
30
+ if (needHandleAssets) {
31
+ assetsDest = join(workDir, config.assets.dest);
32
+ if (!fs.existsSync(assetsDest)) {
33
+ throw new Error(`assets.dest路径不存在: ${assetsDest}, 请先创建`);
34
+ }
35
+ }
36
+ // entry.dest
37
+ if (needHandleEntry) {
38
+ entryDest = join(workDir, config.entry.dest);
39
+ if (!fs.existsSync(entryDest)) {
40
+ throw new Error(`entry.dest路径不存在: ${entryDest}, 请先创建`);
41
+ }
42
+ }
43
+
44
+ // 2. 检查项目是否clean
45
+ let assetsGit = null;
46
+ let assetsStatus = null;
47
+ if (needHandleAssets) {
48
+ assetsGit = simpleGit(assetsDest);
49
+ assetsStatus = await assetsGit.status();
50
+ if (!assetsStatus.isClean()) {
51
+ throw new Error(`${assetsDest}目前有未提交内容, 请先处理`);
52
+ }
53
+ }
54
+
55
+ let entryGit;
56
+ let entryStatus;
57
+ if (needHandleEntry) {
58
+ entryGit = simpleGit(entryDest);
59
+ entryStatus = await entryGit.status();
60
+ if (!entryStatus.isClean()) {
61
+ throw new Error(`${entryDest}目前有未提交内容, 请先处理`);
62
+ }
63
+ }
64
+
65
+
66
+ // 3. 检查assets项目分支,并切换至master分支
67
+ // assets项目固定使用master分支
68
+ if (needHandleAssets && assetsStatus.current !== "master") {
69
+ log(`${assetsDest}当前分支不是master, 切换至master分支`);
70
+ await assetsGit.checkout("master");
71
+ log(`${assetsDest}切换至master分支成功`);
72
+ }
73
+ if (needHandleEntry && entryStatus.current !== entryTargetBranch) {
74
+ log(`${entryDest}当前分支不是${entryTargetBranch}, 切换至${entryTargetBranch}分支`);
75
+ await entryGit.checkout(entryTargetBranch);
76
+ log(`${entryDest}切换至${entryTargetBranch}分支成功`);
77
+ }
78
+
79
+ // 4. 执行git pull
80
+ if (assetsGit) {
81
+ log(`${assetsDest}执行git pull`);
82
+ await assetsGit.pull();
83
+ log(`${assetsDest} git pull done`);
84
+ }
85
+ if (entryGit) {
86
+ log(`${entryDest}执行git pull`);
87
+ await entryGit.pull();
88
+ log(`${entryDest} git pull done`);
89
+ }
90
+
91
+ // 5. 复制构建产物
92
+ // 5.1 复制assets
93
+ if (needHandleAssets) {
94
+ let assetsFilesCount = 0;
95
+ const assetsFiles = config.assets.files;
96
+ for (const [src, dest] of Object.entries(assetsFiles)) {
97
+ const srcPath = join(workDir, src);
98
+ if (!fs.existsSync(srcPath)) {
99
+ throw new Error(`${srcPath}不存在,请确认构建产物是否正确`);
100
+ }
101
+ const destPath = join(assetsDest, dest);
102
+ if (fs.statSync(srcPath).isDirectory()) {
103
+ fs.cpSync(srcPath, destPath, { recursive: true });
104
+ } else {
105
+ fs.copyFileSync(srcPath, destPath);
106
+ }
107
+ log(`assets copy: ${srcPath} -> ${destPath}`);
108
+ assetsFilesCount++;
109
+ }
110
+ // log(`assets copy done.`);
111
+ }
112
+ // 5.2 复制entry
113
+ if (needHandleEntry) {
114
+ const entryFiles = config.entry.files;
115
+ for (const [src, dest] of Object.entries(entryFiles)) {
116
+ const srcPath = join(workDir, src);
117
+ if (!fs.existsSync(srcPath)) {
118
+ throw new Error(`${srcPath}不存在,请确认构建产物是否正确`);
119
+ }
120
+ if (fs.statSync(srcPath).isDirectory()) {
121
+ throw new Error(`entry: ${srcPath}是目录,不支持entry为目录的部署`);
122
+ }
123
+ const destPath = join(entryDest, dest);
124
+ fs.copyFileSync(srcPath, destPath);
125
+ log(`entry copy: ${srcPath} -> ${destPath}`);
126
+ }
127
+ // log(`entry copy done.`);
128
+ }
129
+
130
+ // 6. 提交
131
+ let canPushAssets = false;
132
+ let canPushEntry = false;
133
+ if (needHandleAssets) {
134
+ assetsStatus = await assetsGit.status();
135
+ if (!assetsStatus.isClean()) {
136
+ canPushAssets = true;
137
+ await assetsGit.add(".");
138
+ const assetsCommit = `${config.assets.commit || "feat:部署资源文件"} by deploy-helper`;
139
+ const assetsCommitResult = await assetsGit.commit(assetsCommit);
140
+ log(`assets commit: ${assetsCommit}`);
141
+ log(`assets commit: ${JSON.stringify(assetsCommitResult.summary)}`);
142
+ } else {
143
+ log(`${assetsDest} 未发现修改内容,跳过提交`);
144
+ }
145
+ }
146
+ if (needHandleEntry) {
147
+ entryStatus = await entryGit.status();
148
+ if (!entryStatus.isClean()) {
149
+ canPushEntry = true;
150
+ await entryGit.add(".");
151
+ const entryCommit = `${config.entry.commit || "feat:部署入口文件"} by deploy-helper`;
152
+ const entryCommitResult = await entryGit.commit(entryCommit);
153
+ log(`entry commit: ${entryCommit}`);
154
+ log(`entry commit: ${JSON.stringify(entryCommitResult.summary)}`);
155
+ } else {
156
+ log(`${entryDest} 未发现修改内容,跳过提交`);
157
+ }
158
+ }
159
+
160
+ // 7. 推送
161
+ if (canPushAssets) {
162
+ log(`assets push start`);
163
+ try {
164
+ await assetsGit.push();
165
+ } catch (error) {
166
+ await assetsGit.push();
167
+ }
168
+ log(`assets push done.`);
169
+ }
170
+ if (canPushEntry) {
171
+ log(`entry push start`);
172
+ try {
173
+ await entryGit.push();
174
+ } catch (error) {
175
+ await entryGit.push();
176
+ }
177
+ log(`entry push done.`);
178
+ }
179
+
180
+ try {
181
+ // 8 测试环境将entry scp至测试环境
182
+ if (mode === 'test' && config.testSync) {
183
+ const syncTestFiles = config.testSync;
184
+ const scpClient = await getScpClient();
185
+ for (const [src, dest] of Object.entries(syncTestFiles)) {
186
+ const srcPath = join(workDir, src);
187
+ const destPath = "/home/web/website/tuwan_www/templets/static/play/" + dest;
188
+ log(`scp: ${srcPath} -> ${TEST_SERVER_HOST}:${destPath}`);
189
+ await scpClient.uploadFile(srcPath, destPath)
190
+ }
191
+ }
192
+ } catch (error) {
193
+ log(`scp error: ${error}`);
194
+ if (assetsFilesCount > 0) {
195
+ log(`不过请放心,构建产物的assets已经处理完成,只要手动处理index.html文件即可。`);
196
+ }
197
+ }
198
+ // 处理触发流水线的任务
199
+ await runPipeline(config, mode);
200
+ }
201
+ }
202
+
203
+
204
+ export default new Deployer();
package/deploy2.mjs ADDED
@@ -0,0 +1,9 @@
1
+ class Deployer2 {
2
+ async deploy(workDir, mode, config) {
3
+ // check
4
+ // run
5
+ }
6
+ }
7
+
8
+
9
+ export default new Deployer2();
package/index.mjs CHANGED
@@ -1,20 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { Client } from 'node-scp'
4
3
  import fs from 'fs';
5
4
  import { join, basename, dirname } from "path";
6
- import { simpleGit } from 'simple-git';
7
- import { homedir } from 'os'
8
5
  import { runPipeline, checkYunxiaoToken, triggerPipeline } from './lib/pipelineApi.mjs';
9
6
  import { setDebug, log } from './lib/util.mjs';
10
7
  import path from 'path';
11
8
  import { fileURLToPath } from 'url';
9
+ import Deployer from './deploy.mjs';
10
+ import { getScpClient, TEST_SERVER_HOST } from './util.mjs';
11
+
12
12
 
13
13
  const __filename = fileURLToPath(import.meta.url);
14
14
  const __dirname = path.dirname(__filename);
15
15
 
16
16
 
17
- const TEST_SERVER_HOST = "192.168.0.35";
18
17
  /**
19
18
  * 加载配置
20
19
  * @returns 配置对象
@@ -56,6 +55,14 @@ async function initConfigJson() {
56
55
  },
57
56
  "testSync": {
58
57
  "dist/index.html": "events/CHANGE_ME.htm"
58
+ },
59
+ "prodPipeline":{
60
+ "pipelines": [
61
+ {
62
+ "name": "OfficialSite OR SOMEOTHER",
63
+ "waitResult": true
64
+ }
65
+ ]
59
66
  }
60
67
  }`;
61
68
  fs.writeFileSync(configJsonPath, content, { encoding: "utf-8" });
@@ -63,27 +70,6 @@ async function initConfigJson() {
63
70
  process.exit(0);
64
71
  }
65
72
 
66
- function hasChangeMe(content) {
67
- return content.includes("CHANGE_ME");
68
- }
69
-
70
- async function getScpClient() {
71
- let scpClient = null;
72
- // 不能放密码。。残念
73
- const scpClientConfig = {
74
- host: TEST_SERVER_HOST,
75
- username: 'root',
76
- tryKeyboard: true,
77
- }
78
- const sshKeyPath = join(homedir(), ".ssh/id_rsa");
79
- if (fs.existsSync(sshKeyPath)) {
80
- scpClientConfig.privateKey = fs.readFileSync(sshKeyPath, "utf-8");
81
- } else {
82
- log(`${sshKeyPath}不存在, 建议配置本机ssh免密登录, 参考地址:https://foochane.cn/article/2019061601.html`);
83
- }
84
- scpClient = await Client(scpClientConfig);
85
- return scpClient;
86
- }
87
73
 
88
74
  async function getVersion() {
89
75
  const packageJsonPath = join(__dirname, "package.json");
@@ -92,6 +78,11 @@ async function getVersion() {
92
78
  }
93
79
 
94
80
 
81
+ function getDeployer(){
82
+ return new Deployer();
83
+ }
84
+
85
+
95
86
  async function main() {
96
87
  const version = await getVersion();
97
88
 
@@ -189,233 +180,11 @@ async function main() {
189
180
  log(`deploy mode: ${mode}`);
190
181
  log(`workDir: ${workDir}`);
191
182
 
192
- // 读取配置
193
183
  const config = await loadConfig();
194
- // log(`config`, config);
195
-
196
- const result = hasChangeMe(JSON.stringify(config));
197
- if (result) {
198
- log(`配置中存在默认值(CHANGE_ME), 请先修改配置`);
199
- process.exit(1);
200
- }
201
-
202
-
203
- const needHandleAssets = config.assets && config.assets.dest;
204
-
205
- // 需要处理entry
206
- const needHandleEntry = config.entry && config.entry.dest && (mode === 'prod' || (mode === 'test' && !config.entry.onlyProd));
207
- const entryTestBranch = (config.entry && config.entry.testBranch) || "test";
208
- const entryProdBranch = (config.entry && config.entry.prodBranch) || "master";
209
- const entryTargetBranch = mode === 'prod' ? entryProdBranch : entryTestBranch;
210
- // const currentProdBranch = config.prodBranch || "master";
211
-
212
-
213
- // 检查流水线配置
214
- checkYunxiaoToken(config, mode);
215
-
216
-
217
- // 检查项目
218
- // 1. 检查项目是否存在
219
- let assetsDest = null;
220
- let entryDest = null;
221
- if (needHandleAssets) {
222
- assetsDest = join(workDir, config.assets.dest);
223
- if (!fs.existsSync(assetsDest)) {
224
- log(`assets.dest路径不存在: ${assetsDest}, 请先创建`);
225
- process.exit(1);
226
- }
227
- }
228
- // entry.dest
229
- if (needHandleEntry) {
230
- entryDest = join(workDir, config.entry.dest);
231
- if (!fs.existsSync(entryDest)) {
232
- log(`entry.dest路径不存在: ${entryDest}, 请先创建`);
233
- process.exit(1);
234
- }
235
- }
236
-
237
- // 2. 检查项目是否clean
238
- let assetsGit = null;
239
- let assetsStatus = null;
240
- if (needHandleAssets) {
241
- assetsGit = simpleGit(assetsDest);
242
- assetsStatus = await assetsGit.status();
243
- if (!assetsStatus.isClean()) {
244
- log(`${assetsDest}目前有未提交内容, 请先处理`);
245
- return;
246
- }
247
- }
248
-
249
- let entryGit;
250
- let entryStatus;
251
- if (needHandleEntry) {
252
- entryGit = simpleGit(entryDest);
253
- entryStatus = await entryGit.status();
254
- if (!entryStatus.isClean()) {
255
- log(`${entryDest}目前有未提交内容, 请先处理`);
256
- return;
257
- }
258
- }
259
-
260
- // 2.1如果是prod模式,检查当前项目是不是处于主分支
261
- // if (mode === 'prod') {
262
- // const currentGit = simpleGit(workDir);
263
- // const currentStatus = await currentGit.status();
264
- // if (!currentStatus.isClean()) {
265
- // log(`当前项目有未提交内容, 请先处理`);
266
- // process.exit(1);
267
- // }
268
- // if (currentStatus.branch !== currentProdBranch) {
269
- // log(`当前项目分支${currentStatus.branch}不是${currentProdBranch}分支, 请切换至${currentProdBranch}分支再执行prod命令`);
270
- // process.exit(1);
271
- // }
272
- // }
273
-
274
- // 3. 检查assets项目分支,并切换至master分支
275
- // assets项目固定使用master分支
276
- if (needHandleAssets && assetsStatus.current !== "master") {
277
- log(`${assetsDest}当前分支不是master, 切换至master分支`);
278
- await assetsGit.checkout("master");
279
- log(`${assetsDest}切换至master分支成功`);
280
- }
281
- if (needHandleEntry && entryStatus.current !== entryTargetBranch) {
282
- log(`${entryDest}当前分支不是${entryTargetBranch}, 切换至${entryTargetBranch}分支`);
283
- await entryGit.checkout(entryTargetBranch);
284
- log(`${entryDest}切换至${entryTargetBranch}分支成功`);
285
- }
286
-
287
- // 4. 执行git pull
288
- if (assetsGit) {
289
- log(`${assetsDest}执行git pull`);
290
- await assetsGit.pull();
291
- log(`${assetsDest} git pull done`);
292
- }
293
- if (entryGit) {
294
- log(`${entryDest}执行git pull`);
295
- await entryGit.pull();
296
- log(`${entryDest} git pull done`);
297
- }
298
-
299
- // 5. 复制构建产物
300
- // 5.1 复制assets
301
- if (needHandleAssets) {
302
- let assetsFilesCount = 0;
303
- const assetsFiles = config.assets.files;
304
- for (const [src, dest] of Object.entries(assetsFiles)) {
305
- const srcPath = join(workDir, src);
306
- if (!fs.existsSync(srcPath)) {
307
- log(`${srcPath}不存在,请确认构建产物是否正确`);
308
- return;
309
- }
310
- const destPath = join(assetsDest, dest);
311
- if (fs.statSync(srcPath).isDirectory()) {
312
- fs.cpSync(srcPath, destPath, { recursive: true });
313
- } else {
314
- fs.copyFileSync(srcPath, destPath);
315
- }
316
- log(`assets copy: ${srcPath} -> ${destPath}`);
317
- assetsFilesCount++;
318
- }
319
- // log(`assets copy done.`);
320
- }
321
- // 5.2 复制entry
322
- if (needHandleEntry) {
323
- const entryFiles = config.entry.files;
324
- for (const [src, dest] of Object.entries(entryFiles)) {
325
- const srcPath = join(workDir, src);
326
- if (!fs.existsSync(srcPath)) {
327
- log(`${srcPath}不存在,请确认构建产物是否正确`);
328
- return;
329
- }
330
- if (fs.statSync(srcPath).isDirectory()) {
331
- log(`entry: ${srcPath}是目录,不支持entry为目录的部署`);
332
- return;
333
- }
334
- const destPath = join(entryDest, dest);
335
- fs.copyFileSync(srcPath, destPath);
336
- log(`entry copy: ${srcPath} -> ${destPath}`);
337
- }
338
- // log(`entry copy done.`);
339
- }
340
-
341
- // 6. 提交
342
- let canPushAssets = false;
343
- let canPushEntry = false;
344
- if (needHandleAssets) {
345
- assetsStatus = await assetsGit.status();
346
- if (!assetsStatus.isClean()) {
347
- canPushAssets = true;
348
- await assetsGit.add(".");
349
- const assetsCommit = `${config.assets.commit || "feat:部署资源文件"} by deploy-helper`;
350
- const assetsCommitResult = await assetsGit.commit(assetsCommit);
351
- log(`assets commit: ${assetsCommit}`);
352
- log(`assets commit: ${JSON.stringify(assetsCommitResult.summary)}`);
353
- } else {
354
- log(`${assetsDest} 未发现修改内容,跳过提交`);
355
- }
356
- }
357
- if (needHandleEntry) {
358
- entryStatus = await entryGit.status();
359
- if (!entryStatus.isClean()) {
360
- canPushEntry = true;
361
- await entryGit.add(".");
362
- const entryCommit = `${config.entry.commit || "feat:部署入口文件"} by deploy-helper`;
363
- const entryCommitResult = await entryGit.commit(entryCommit);
364
- log(`entry commit: ${entryCommit}`);
365
- log(`entry commit: ${JSON.stringify(entryCommitResult.summary)}`);
366
- } else {
367
- log(`${entryDest} 未发现修改内容,跳过提交`);
368
- }
369
- }
370
-
371
- // 7. 推送
372
- if (canPushAssets) {
373
- log(`assets push start`);
374
- try {
375
- await assetsGit.push();
376
- } catch (error) {
377
- await assetsGit.push();
378
- }
379
- log(`assets push done.`);
380
- }
381
- if (canPushEntry) {
382
- log(`entry push start`);
383
- try {
384
- await entryGit.push();
385
- } catch (error) {
386
- await entryGit.push();
387
- }
388
- log(`entry push done.`);
389
- }
390
-
391
- try {
392
- // 8 测试环境将entry scp至测试环境
393
- if (mode === 'test' && config.testSync) {
394
- const syncTestFiles = config.testSync;
395
- const scpClient = await getScpClient();
396
- for (const [src, dest] of Object.entries(syncTestFiles)) {
397
- const srcPath = join(workDir, src);
398
- const destPath = "/home/web/website/tuwan_www/templets/static/play/" + dest;
399
- log(`scp: ${srcPath} -> ${TEST_SERVER_HOST}:${destPath}`);
400
- await scpClient.uploadFile(srcPath, destPath)
401
- }
402
- }
403
- } catch (error) {
404
- log(`scp error: ${error}`);
405
- log(`复制文件到测试环境服务器失败, 建议配置本机ssh免密登录, 参考地址:https://foochane.cn/article/2019061601.html`);
406
- if (assetsFilesCount > 0) {
407
- log(`不过请放心,构建产物的assets已经处理完成,只要手动处理index.html文件即可。`);
408
- }
409
- process.exit(1);
410
- }
411
-
412
-
413
- // 处理触发流水线的任务
414
- await runPipeline(config, mode);
415
-
184
+ const deployer = getDeployer();
185
+ await deployer.deploy(workDir, mode, config);
416
186
  log(`deploy-helper deploy done.`);
417
187
  process.exit(0);
418
-
419
188
  } catch (error) {
420
189
  log(`error occurred in deploy-helper`, error);
421
190
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taole/deploy-helper",
3
- "version": "0.5.3",
3
+ "version": "1.0.0-beta.0",
4
4
  "description": "脚本部署工具,用于将项目部署到测试环境或生产环境",
5
5
  "main": "index.mjs",
6
6
  "type": "module",
@@ -17,6 +17,9 @@
17
17
  },
18
18
  "files": [
19
19
  "index.mjs",
20
+ "deploy.mjs",
21
+ "deploy2.mjs",
22
+ "util.mjs",
20
23
  "lib",
21
24
  "modules/alibabacloud-devops-mcp-server/dist"
22
25
  ],
package/util.mjs ADDED
@@ -0,0 +1,22 @@
1
+ import { Client } from 'node-scp'
2
+
3
+
4
+ export function hasChangeMe(content) {
5
+ return content.includes("CHANGE_ME");
6
+ }
7
+
8
+ export const TEST_SERVER_HOST = "192.168.0.35";
9
+
10
+
11
+
12
+ export async function getScpClient() {
13
+ let scpClient = null;
14
+ const scpClientConfig = {
15
+ host: TEST_SERVER_HOST,
16
+ username: 'root',
17
+ tryKeyboard: false,
18
+ password: 'tuwan123!@#', // u cant see me
19
+ }
20
+ scpClient = await Client(scpClientConfig);
21
+ return scpClient;
22
+ }