@taole/deploy-helper 0.0.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 +2 -0
- package/index.js +225 -0
- package/package.json +26 -0
package/README.md
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Client } from 'node-scp'
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { simpleGit } from 'simple-git';
|
|
7
|
+
import { homedir } from 'os'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 加载配置
|
|
11
|
+
* @returns 配置对象
|
|
12
|
+
*/
|
|
13
|
+
async function loadConfig() {
|
|
14
|
+
const configPath = join(process.cwd(), "deploy.config.js");
|
|
15
|
+
if (fs.existsSync(configPath)) {
|
|
16
|
+
const config = await import(`file://${configPath}`);
|
|
17
|
+
return config.default;
|
|
18
|
+
}
|
|
19
|
+
const configJsonPath = join(process.cwd(), "deploy.config.json");
|
|
20
|
+
if (fs.existsSync(configJsonPath)) {
|
|
21
|
+
const configJson = JSON.parse(fs.readFileSync(configJsonPath, "utf-8"));
|
|
22
|
+
return configJson;
|
|
23
|
+
}
|
|
24
|
+
console.log(`deploy.config.js或deploy.config.json不存在, 请先创建`);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
async function main() {
|
|
30
|
+
|
|
31
|
+
// other commands
|
|
32
|
+
|
|
33
|
+
if(process.argv[2] === "help"){
|
|
34
|
+
console.log(`deploy-helper 部署脚本`);
|
|
35
|
+
console.log(`usage: deploy-helper [mode]`);
|
|
36
|
+
console.log(`mode: prod 生产环境部署`);
|
|
37
|
+
console.log(`mode: test 测试环境部署`);
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if(!["prod", "test"].includes(process.argv[2])){
|
|
42
|
+
console.log("不支持的命令");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
const workDir = process.cwd(); // 当前项目根目录
|
|
47
|
+
const mode = process.argv[2] === 'prod' ? 'prod' : 'test'; // 部署环境
|
|
48
|
+
console.log(`deploy mode: ${mode}`);
|
|
49
|
+
|
|
50
|
+
// 读取配置
|
|
51
|
+
const config = await loadConfig();
|
|
52
|
+
// console.log(`config`, config);
|
|
53
|
+
|
|
54
|
+
// 检查配置格式
|
|
55
|
+
if (!config.assets || !config.assets.dest || !config.entry || !config.entry.dest) {
|
|
56
|
+
console.log(`配置格式错误, 请检查deploy.config.js或deploy.config.json`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 检查项目
|
|
61
|
+
// 1. 检查项目是否存在
|
|
62
|
+
const assetsDest = join(workDir, config.assets.dest);
|
|
63
|
+
const entryDest = join(workDir, config.entry.dest);
|
|
64
|
+
if (!fs.existsSync(assetsDest)) {
|
|
65
|
+
console.log(`assets.dest路径不存在: ${assetsDest}, 请先创建`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// test mode下不需要检查entry.dest
|
|
69
|
+
if (mode === 'prod' && !fs.existsSync(entryDest)) {
|
|
70
|
+
console.log(`entry.dest路径不存在: ${entryDest}, 请先创建`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 2. 检查项目是否clean
|
|
75
|
+
const assetsGit = simpleGit(assetsDest);
|
|
76
|
+
const assetsStatus = await assetsGit.status();
|
|
77
|
+
if (!assetsStatus.isClean()) {
|
|
78
|
+
console.log(`${assetsDest}目前有未提交内容, 请先处理`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let entryGit;
|
|
83
|
+
if (mode === 'prod') {
|
|
84
|
+
entryGit = simpleGit(entryDest);
|
|
85
|
+
const entryStatus = await entryGit.status();
|
|
86
|
+
if (!entryStatus.isClean()) {
|
|
87
|
+
console.log(`${entryDest}目前有未提交内容, 请先处理`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 2.1如果是prod模式,检查当前项目是不是处于master分支
|
|
93
|
+
if(mode === 'prod'){
|
|
94
|
+
const currentGit = simpleGit(workDir);
|
|
95
|
+
const currentStatus = await currentGit.status();
|
|
96
|
+
if(!currentStatus.isClean()){
|
|
97
|
+
console.log(`当前项目有未提交内容, 请先处理`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
if(currentStatus.branch !== "master"){
|
|
101
|
+
console.log(`当前项目不是master分支, 请切换至master分支`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 3. 检查项目分支,并切换至master分支
|
|
107
|
+
if (assetsStatus.current !== "master") {
|
|
108
|
+
console.log(`${assetsDest}当前分支不是master, 切换至master分支`);
|
|
109
|
+
await assetsGit.checkout("master");
|
|
110
|
+
console.log(`${assetsDest}切换至master分支成功`);
|
|
111
|
+
}
|
|
112
|
+
if (entryGit && entryStatus.current !== "master") {
|
|
113
|
+
console.log(`${entryDest}当前分支不是master, 切换至master分支`);
|
|
114
|
+
await entryGit.checkout("master");
|
|
115
|
+
console.log(`${entryDest}切换至master分支成功`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 4. 执行git pull
|
|
119
|
+
await assetsGit.pull();
|
|
120
|
+
if (entryGit) {
|
|
121
|
+
await entryGit.pull();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 5. 复制构建产物
|
|
125
|
+
// 5.1 复制assets
|
|
126
|
+
let assetsFilesCount = 0;
|
|
127
|
+
const assetsFiles = config.assets.files;
|
|
128
|
+
for (const [src, dest] of Object.entries(assetsFiles)) {
|
|
129
|
+
const srcPath = join(workDir, src);
|
|
130
|
+
if (!fs.existsSync(srcPath)) {
|
|
131
|
+
console.log(`${srcPath}不存在,请确认构建产物是否正确`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const destPath = join(assetsDest, dest);
|
|
135
|
+
if (fs.statSync(srcPath).isDirectory()) {
|
|
136
|
+
fs.cpSync(srcPath, destPath, { recursive: true });
|
|
137
|
+
} else {
|
|
138
|
+
fs.copyFileSync(srcPath, destPath);
|
|
139
|
+
}
|
|
140
|
+
console.log(`assets copy: ${srcPath} -> ${destPath}`);
|
|
141
|
+
assetsFilesCount++;
|
|
142
|
+
}
|
|
143
|
+
console.log(`assets copy done.`);
|
|
144
|
+
// 5.2 复制entry
|
|
145
|
+
if (mode === 'prod') {
|
|
146
|
+
const entryFiles = config.entry.files;
|
|
147
|
+
for (const [src, dest] of Object.entries(entryFiles)) {
|
|
148
|
+
const srcPath = join(workDir, src);
|
|
149
|
+
if (!fs.existsSync(srcPath)) {
|
|
150
|
+
console.log(`${srcPath}不存在,请确认构建产物是否正确`);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (fs.statSync(srcPath).isDirectory()) {
|
|
154
|
+
console.log(`entry: ${srcPath}是目录,不支持entry为目录的部署`);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const destPath = join(entryDest, dest);
|
|
158
|
+
fs.copyFileSync(srcPath, destPath);
|
|
159
|
+
console.log(`entry copy: ${srcPath} -> ${destPath}`);
|
|
160
|
+
}
|
|
161
|
+
console.log(`entry copy done.`);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 6. 提交
|
|
165
|
+
await assetsGit.add(".");
|
|
166
|
+
const assetsCommit = `${config.assets.commit || "feat:部署资源文件"} by deploy-helper`;
|
|
167
|
+
await assetsGit.commit(assetsCommit);
|
|
168
|
+
console.log(`assets commit: ${assetsCommit}`);
|
|
169
|
+
if (entryGit) {
|
|
170
|
+
await entryGit.add(".");
|
|
171
|
+
const entryCommit = `${config.entry.commit || "feat:部署入口文件"} by deploy-helper`;
|
|
172
|
+
await entryGit.commit(entryCommit);
|
|
173
|
+
console.log(`entry commit: ${entryCommit}`);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// 7. 推送
|
|
177
|
+
await assetsGit.push();
|
|
178
|
+
console.log(`assets push done.`);
|
|
179
|
+
if(entryGit){
|
|
180
|
+
await entryGit.push();
|
|
181
|
+
console.log(`entry push done.`);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
// 8 测试环境将entry scp至测试环境
|
|
186
|
+
if (mode === 'test' && config.entry.syncTestFiles) {
|
|
187
|
+
const syncTestFiles = config.entry.syncTestFiles;
|
|
188
|
+
// 不能放密码。。残念
|
|
189
|
+
const scpClientConfig = {
|
|
190
|
+
host: '192.168.0.35',
|
|
191
|
+
username: 'root',
|
|
192
|
+
tryKeyboard: true,
|
|
193
|
+
}
|
|
194
|
+
const sshKeyPath = join(homedir(), ".ssh/id_rsa");
|
|
195
|
+
if (fs.existsSync(sshKeyPath)) {
|
|
196
|
+
scpClientConfig.privateKey = fs.readFileSync(sshKeyPath, "utf-8");
|
|
197
|
+
}
|
|
198
|
+
const scpClient = await Client(scpClientConfig);
|
|
199
|
+
for (const [src, dest] of Object.entries(syncTestFiles)) {
|
|
200
|
+
const srcPath = join(workDir, src);
|
|
201
|
+
const destPath = "/home/web/website/tuwan_www/templets/static/play/" + dest;
|
|
202
|
+
console.log(`scp: ${srcPath} -> ${destPath}`);
|
|
203
|
+
await scpClient.uploadFile(srcPath, destPath)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
console.log(`scp error: ${error}`);
|
|
208
|
+
console.log(`复制文件到测试环境服务器失败, 建议配置本机ssh免密登录, 参考地址:https://foochane.cn/article/2019061601.html`);
|
|
209
|
+
if(assetsFilesCount > 0){
|
|
210
|
+
console.log(`不过请放心,构建产物的assets已经处理完成,只要手动处理index.html文件即可。`);
|
|
211
|
+
}
|
|
212
|
+
process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
console.log(`deploy-helper deploy done.`);
|
|
215
|
+
process.exit(0);
|
|
216
|
+
|
|
217
|
+
} catch (error) {
|
|
218
|
+
console.log(`error occurred in deploy-helper`, error);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
main();
|
|
225
|
+
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@taole/deploy-helper",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "脚本部署工具,用于将项目部署到测试环境或生产环境",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"deploy-helper": "index.js",
|
|
12
|
+
"dh": "index.js"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"access": "public",
|
|
16
|
+
"registry": "https://registry.npmjs.org/"
|
|
17
|
+
},
|
|
18
|
+
"files": ["index.js"],
|
|
19
|
+
"keywords": [],
|
|
20
|
+
"author": "",
|
|
21
|
+
"license": "ISC",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"node-scp": "^0.0.25",
|
|
24
|
+
"simple-git": "^3.28.0"
|
|
25
|
+
}
|
|
26
|
+
}
|