@taole/deploy-helper 1.0.0-beta.6 → 1.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/deploy.mjs DELETED
@@ -1,203 +0,0 @@
1
- import { join } from "path";
2
- import { simpleGit } from 'simple-git';
3
- import fs from 'fs';
4
- import { hasChangeMe } from './util.mjs';
5
- import { runPipeline, checkYunxiaoToken } from './lib/pipelineApi.mjs';
6
- import { log } from './lib/util.mjs';
7
- import { getScpClient, TEST_SERVER_HOST } from './util.mjs';
8
-
9
-
10
- export default class Deployer {
11
- async deploy(workDir, mode, config) {
12
- // 检查配置中是否存在默认值(CHANGE_ME)
13
- const result = hasChangeMe(JSON.stringify(config));
14
- if (result) {
15
- throw new Error(`配置中存在默认值(CHANGE_ME), 请先修改配置`);
16
- }
17
-
18
- const needHandleAssets = config.assets && config.assets.dest;
19
- const needHandleEntry = config.entry && config.entry.dest && (mode === 'prod' || (mode === 'test' && !config.entry.onlyProd));
20
- const entryTestBranch = (config.entry && config.entry.testBranch) || "test";
21
- const entryProdBranch = (config.entry && config.entry.prodBranch) || "master";
22
- const entryTargetBranch = mode === 'prod' ? entryProdBranch : entryTestBranch;
23
-
24
- // 检查流水线配置
25
- checkYunxiaoToken(config, mode);
26
-
27
-
28
- // 检查项目
29
- // 1. 检查项目是否存在
30
- let assetsDest = null;
31
- let entryDest = null;
32
- if (needHandleAssets) {
33
- assetsDest = join(workDir, config.assets.dest);
34
- if (!fs.existsSync(assetsDest)) {
35
- throw new Error(`assets.dest路径不存在: ${assetsDest}, 请先创建`);
36
- }
37
- }
38
- // entry.dest
39
- if (needHandleEntry) {
40
- entryDest = join(workDir, config.entry.dest);
41
- if (!fs.existsSync(entryDest)) {
42
- throw new Error(`entry.dest路径不存在: ${entryDest}, 请先创建`);
43
- }
44
- }
45
-
46
- // 2. 检查项目是否clean
47
- let assetsGit = null;
48
- let assetsStatus = null;
49
- if (needHandleAssets) {
50
- assetsGit = simpleGit(assetsDest);
51
- assetsStatus = await assetsGit.status();
52
- if (!assetsStatus.isClean()) {
53
- throw new Error(`${assetsDest}目前有未提交内容, 请先处理`);
54
- }
55
- }
56
-
57
- let entryGit;
58
- let entryStatus;
59
- if (needHandleEntry) {
60
- entryGit = simpleGit(entryDest);
61
- entryStatus = await entryGit.status();
62
- if (!entryStatus.isClean()) {
63
- throw new Error(`${entryDest}目前有未提交内容, 请先处理`);
64
- }
65
- }
66
-
67
-
68
- // 3. 检查assets项目分支,并切换至master分支
69
- // assets项目固定使用master分支
70
- if (needHandleAssets && assetsStatus.current !== "master") {
71
- log(`${assetsDest}当前分支不是master, 切换至master分支`);
72
- await assetsGit.checkout("master");
73
- log(`${assetsDest}切换至master分支成功`);
74
- }
75
- if (needHandleEntry && entryStatus.current !== entryTargetBranch) {
76
- log(`${entryDest}当前分支不是${entryTargetBranch}, 切换至${entryTargetBranch}分支`);
77
- await entryGit.checkout(entryTargetBranch);
78
- log(`${entryDest}切换至${entryTargetBranch}分支成功`);
79
- }
80
-
81
- // 4. 执行git pull
82
- if (assetsGit) {
83
- log(`${assetsDest}执行git pull`);
84
- await assetsGit.pull();
85
- log(`${assetsDest} git pull done`);
86
- }
87
- if (entryGit) {
88
- log(`${entryDest}执行git pull`);
89
- await entryGit.pull();
90
- log(`${entryDest} git pull done`);
91
- }
92
-
93
- // 5. 复制构建产物
94
- // 5.1 复制assets
95
- if (needHandleAssets) {
96
- let assetsFilesCount = 0;
97
- const assetsFiles = config.assets.files;
98
- for (const [src, dest] of Object.entries(assetsFiles)) {
99
- const srcPath = join(workDir, src);
100
- if (!fs.existsSync(srcPath)) {
101
- throw new Error(`${srcPath}不存在,请确认构建产物是否正确`);
102
- }
103
- const destPath = join(assetsDest, dest);
104
- if (fs.statSync(srcPath).isDirectory()) {
105
- fs.cpSync(srcPath, destPath, { recursive: true });
106
- } else {
107
- fs.copyFileSync(srcPath, destPath);
108
- }
109
- log(`assets copy: ${srcPath} -> ${destPath}`);
110
- assetsFilesCount++;
111
- }
112
- // log(`assets copy done.`);
113
- }
114
- // 5.2 复制entry
115
- if (needHandleEntry) {
116
- const entryFiles = config.entry.files;
117
- for (const [src, dest] of Object.entries(entryFiles)) {
118
- const srcPath = join(workDir, src);
119
- if (!fs.existsSync(srcPath)) {
120
- throw new Error(`${srcPath}不存在,请确认构建产物是否正确`);
121
- }
122
- if (fs.statSync(srcPath).isDirectory()) {
123
- throw new Error(`entry: ${srcPath}是目录,不支持entry为目录的部署`);
124
- }
125
- const destPath = join(entryDest, dest);
126
- fs.copyFileSync(srcPath, destPath);
127
- log(`entry copy: ${srcPath} -> ${destPath}`);
128
- }
129
- // log(`entry copy done.`);
130
- }
131
-
132
- // 6. 提交
133
- let canPushAssets = false;
134
- let canPushEntry = false;
135
- if (needHandleAssets) {
136
- assetsStatus = await assetsGit.status();
137
- if (!assetsStatus.isClean()) {
138
- canPushAssets = true;
139
- await assetsGit.add(".");
140
- const assetsCommit = `${config.assets.commit || "feat:部署资源文件"} by deploy-helper`;
141
- const assetsCommitResult = await assetsGit.commit(assetsCommit);
142
- log(`assets commit: ${assetsCommit}`);
143
- log(`assets commit: ${JSON.stringify(assetsCommitResult.summary)}`);
144
- } else {
145
- log(`${assetsDest} 未发现修改内容,跳过提交`);
146
- }
147
- }
148
- if (needHandleEntry) {
149
- entryStatus = await entryGit.status();
150
- if (!entryStatus.isClean()) {
151
- canPushEntry = true;
152
- await entryGit.add(".");
153
- const entryCommit = `${config.entry.commit || "feat:部署入口文件"} by deploy-helper`;
154
- const entryCommitResult = await entryGit.commit(entryCommit);
155
- log(`entry commit: ${entryCommit}`);
156
- log(`entry commit: ${JSON.stringify(entryCommitResult.summary)}`);
157
- } else {
158
- log(`${entryDest} 未发现修改内容,跳过提交`);
159
- }
160
- }
161
-
162
- // 7. 推送
163
- if (canPushAssets) {
164
- log(`assets push start`);
165
- try {
166
- await assetsGit.push();
167
- } catch (error) {
168
- await assetsGit.push();
169
- }
170
- log(`assets push done.`);
171
- }
172
- if (canPushEntry) {
173
- log(`entry push start`);
174
- try {
175
- await entryGit.push();
176
- } catch (error) {
177
- await entryGit.push();
178
- }
179
- log(`entry push done.`);
180
- }
181
-
182
- try {
183
- // 8 测试环境将entry scp至测试环境
184
- if (mode === 'test' && config.testSync) {
185
- const syncTestFiles = config.testSync;
186
- const scpClient = await getScpClient();
187
- for (const [src, dest] of Object.entries(syncTestFiles)) {
188
- const srcPath = join(workDir, src);
189
- const destPath = "/home/web/website/tuwan_www/templets/static/play/" + dest;
190
- log(`scp: ${srcPath} -> ${TEST_SERVER_HOST}:${destPath}`);
191
- await scpClient.uploadFile(srcPath, destPath)
192
- }
193
- }
194
- } catch (error) {
195
- log(`scp error: ${error}`);
196
- if (assetsFilesCount > 0) {
197
- log(`不过请放心,构建产物的assets已经处理完成,只要手动处理index.html文件即可。`);
198
- }
199
- }
200
- // 处理触发流水线的任务
201
- await runPipeline(config, mode);
202
- }
203
- }
package/deploy2.mjs DELETED
@@ -1,275 +0,0 @@
1
- import { join, dirname } from "path";
2
- import { simpleGit } from 'simple-git';
3
- import { homedir } from 'node:os';
4
- import fs from 'fs';
5
- import { hasChangeMe } from './util.mjs';
6
- import { runPipeline, checkYunxiaoToken } from './lib/pipelineApi.mjs';
7
- import { log } from './lib/util.mjs';
8
- import { getScpClient, TEST_SERVER_HOST } from './util.mjs';
9
-
10
-
11
- function repoUrl2https(url) {
12
- return url.replace("git@codeup.aliyun.com:", "https://codeup.aliyun.com/");
13
- }
14
-
15
- // https://codeup.aliyun.com/5ec8bb7bd1d1abe63b55cd33/PHP/Static.git
16
- const repoMap = {
17
- "Static": "git@codeup.aliyun.com:5ec8bb7bd1d1abe63b55cd33/PHP/Static.git",
18
- "Static_2025": "git@codeup.aliyun.com:5ec8bb7bd1d1abe63b55cd33/PC_Web/Static_2025.git",
19
- "events": "git@codeup.aliyun.com:5ec8bb7bd1d1abe63b55cd33/PHP/events.git",
20
- "OfficialSite": "git@codeup.aliyun.com:5ec8bb7bd1d1abe63b55cd33/PHP/OfficialSite.git",
21
- "TXC_OfficialSite": "git@codeup.aliyun.com:5ec8bb7bd1d1abe63b55cd33/PC_Web/TXC_OfficialSite.git",
22
- }
23
-
24
- function getDeployWorkDir() {
25
- const workDir = join(homedir(), ".deploy-helper-workspace");
26
- if (!fs.existsSync(workDir)) {
27
- fs.mkdirSync(workDir, { recursive: true });
28
- }
29
- return workDir;
30
- }
31
-
32
-
33
-
34
- /**
35
- * @typedef {Object} PubProjectConfig
36
- * @property {string} dest 项目名称
37
- * @property {{[src: string]: string}} files 项目文件路径映射
38
- * @property {string} [branch] 项目分支
39
- * @property {string} [commit] 项目提交信息
40
- */
41
-
42
-
43
- async function initPubProject(workDir, config, packageJson, isAssets = false) {
44
- const deployWorkDir = getDeployWorkDir();
45
-
46
- // 获取目标项目
47
- let dest = config.dest;
48
- if (dest.indexOf("/") !== -1) {
49
- dest = dest.split("/").pop();
50
- }
51
- const supportedRepoNames = Object.keys(repoMap);
52
- if (!supportedRepoNames.includes(dest)) {
53
- throw new Error(`${dest}不是支持的项目, 请确认. 目前支持的项目有: ${supportedRepoNames.join(", ")}`);
54
- }
55
- let repoUrl = repoMap[dest];
56
- let repoUrls = [repoUrl, repoUrl2https(repoUrl)];
57
- // 默认使用ssh, 如果从某处获知当前用户使用的是https, 那么先尝试https方式的拉取
58
- const useSSH = false;
59
- if (useSSH) {
60
- repoUrls = repoUrls.reverse();
61
- }
62
-
63
- const projectName = `${dest}_${packageJson.name}_${packageJson.version}_${Date.now()}`;
64
- const projectDir = join(deployWorkDir, projectName);
65
-
66
-
67
- // 检查实际的构建产物是否存在, 并且获取需要sparse-checkout的目录
68
- let dests = Object.keys(config.files).map(src => {
69
- const i = config.files[src];
70
- const fullPath = join(workDir, src);
71
- if (fs.existsSync(fullPath)) {
72
- const isDir = fs.statSync(fullPath).isDirectory();
73
- if (isDir) {
74
- return i;
75
- } else {
76
- const subs = i.split("/");
77
- if (subs.length === 1) {
78
- return "";
79
- }
80
- subs.pop();
81
- return subs.join("/");
82
- }
83
- } else {
84
- throw new Error(`${fullPath}不存在,请确认构建产物是否正确`);
85
- }
86
- }).filter(Boolean);
87
- dests = [...new Set(dests)];
88
-
89
-
90
- let git = simpleGit(deployWorkDir);
91
-
92
- if (isAssets && dests.length === 0) {
93
- throw new Error(`【警告】构建产物assets部分一般需要指定子目录, 请检查部署配置是否有误`);
94
- }
95
-
96
- for (let i = 0; i < repoUrls.length; i++) {
97
- try {
98
- log(`try clone ${repoUrls[i]}`);
99
- const gitCmdStr = `clone --branch master --depth=1 --filter=blob:none --no-checkout ${repoUrls[i]} ${projectDir}`;
100
- await git.raw(gitCmdStr.split(' '));
101
- break;
102
- } catch (error) {
103
- log(`clone ${repoUrls[i]} failed: ${error}`);
104
- }
105
- }
106
- if (!fs.existsSync(projectDir)) {
107
- throw new Error(`clone ${repoUrls[i]} failed, please find the author for help`);
108
- }
109
- git = simpleGit(projectDir);
110
- if (dests.length > 0) {
111
- log(`git sparse-checkout init --cone`);
112
- await git.raw("sparse-checkout init --cone".split(' '));
113
- //TODO: 进一步容错, 如果有包含关系的, 去掉被包含的
114
- for (const dest of dests) {
115
- const cmds = ['sparse-checkout', 'set', dest];
116
- log(`git ${cmds.join(' ')}`);
117
- await git.raw(cmds);
118
- }
119
- log(`git checkout master`);
120
- await git.checkout('master');
121
- log(`${dest} partial clone done`)
122
- } else {
123
- log(`git checkout master`);
124
- await git.checkout('master');
125
- log(`${dest} clone done`)
126
- }
127
- return { git, projectDir, destReopName: dest };
128
- }
129
-
130
- /**
131
- * 将构建产物复制到项目的对应位置
132
- * @param {string} workDir
133
- * @param {PubProjectConfig} config
134
- * @param {string} targetProjectDir
135
- */
136
- function cpFiles(workDir, config, targetProjectDir) {
137
- // log(`cpFiles: ${workDir} -> ${targetProjectDir}`);
138
- let assetsFilesCount = 0;
139
- const assetsFiles = config.files;
140
- for (const [src, dest] of Object.entries(assetsFiles)) {
141
- const srcPath = join(workDir, src);
142
- if (!fs.existsSync(srcPath)) {
143
- throw new Error(`${srcPath}不存在,请确认构建产物是否正确`);
144
- }
145
- const destPath = join(targetProjectDir, dest);
146
- if (fs.statSync(srcPath).isDirectory()) {
147
- fs.cpSync(srcPath, destPath, { recursive: true });
148
- } else {
149
- fs.copyFileSync(srcPath, destPath);
150
- }
151
- log(`assets copy: ${srcPath} -> ${destPath}`);
152
- assetsFilesCount++;
153
- }
154
- log(`assets copy done. ${assetsFilesCount} files copied.`);
155
- }
156
-
157
-
158
- /**
159
- * 提交和推送
160
- * @param {*} git
161
- * @param {PubProjectConfig} config
162
- * @param {string} testReopName
163
- */
164
- async function commitAndPush(git, config, testReopName, isAssets = false) {
165
- await git.add(".");
166
- const status = await git.status();
167
- if (!status.isClean()) {
168
- const assetsCommit = `${config.commit || "feat:部署资源文件"} by deploy-helper`;
169
- const assetsCommitResult = await git.commit(assetsCommit);
170
- log(`${testReopName} commit: ${assetsCommit}`);
171
- log(`${testReopName} commit: ${JSON.stringify(assetsCommitResult.summary)}`);
172
- return true;
173
- } else {
174
- log(`${testReopName} ${isAssets ? "静态资源" : "入口文件"}未发现修改内容,跳过提交`);
175
- return false;
176
- }
177
- }
178
-
179
- export default class Deployer2 {
180
- async deploy(workDir, mode, config) {
181
-
182
- log(`将使用新版部署方式`);
183
-
184
- // check
185
- // 检查配置中是否存在默认值(CHANGE_ME)
186
- const result = hasChangeMe(JSON.stringify(config));
187
- if (result) {
188
- throw new Error(`配置中存在默认值(CHANGE_ME), 请先修改配置`);
189
- }
190
-
191
- const needHandleAssets = config.assets && config.assets.dest && Object.keys(config.assets.files || {}).length > 0;
192
- const needHandleEntry = config.entry && config.entry.dest && (mode === 'prod' || (mode === 'test' && !config.entry.onlyProd));
193
- const entryTestBranch = (config.entry && config.entry.testBranch) || "test";
194
- const entryProdBranch = (config.entry && config.entry.prodBranch) || "master";
195
- const entryTargetBranch = mode === 'prod' ? entryProdBranch : entryTestBranch;
196
-
197
- // 检查流水线配置
198
- checkYunxiaoToken(config, mode);
199
- const gitsNeedPush = [];
200
- const gitsToHandle = [];
201
- const dirsToDelete = [];
202
- if (needHandleAssets) {
203
- gitsToHandle.push({
204
- subConfig: config.assets,
205
- isAssets: true,
206
- branch: "master",
207
- });
208
- }
209
- if (needHandleEntry) {
210
- gitsToHandle.push({
211
- subConfig: config.entry,
212
- isAssets: false,
213
- branch: entryTargetBranch,
214
- });
215
- }
216
- for (const { subConfig, isAssets } of gitsToHandle) {
217
- const { git, projectDir, destReopName } = await initPubProject(workDir, subConfig, config.packageJson, isAssets);
218
- cpFiles(workDir, subConfig, projectDir);
219
- const canPush = await commitAndPush(git, subConfig, destReopName, isAssets);
220
- if (canPush) {
221
- gitsNeedPush.push({ git, destReopName, projectDir });
222
- }
223
- dirsToDelete.push(projectDir);
224
- }
225
- for (const { git, destReopName } of gitsNeedPush) {
226
- let isPushed = false;
227
- try {
228
- await git.push();
229
- isPushed = true;
230
- } catch (error) {
231
- log(`${destReopName} push failed, try again.`);
232
- }
233
- if(!isPushed) {
234
- try {
235
- await git.push();
236
- } catch (error) {
237
- log(`${destReopName} push failed, please find the author for help.`);
238
- throw error;
239
- }
240
- }
241
- log(`${destReopName} push done.`);
242
- }
243
- for (const dir of dirsToDelete) {
244
- try {
245
- fs.rmSync(dir, { recursive: true });
246
- } catch (error) {
247
- // ignore
248
- log(`${dir} delete failed, 请稍后手动删除`);
249
- console.error(error);
250
- console.error(error.stack);
251
- }
252
- }
253
-
254
- try {
255
- // 8 测试环境将entry scp至测试环境
256
- if (mode === 'test' && config.testSync) {
257
- const syncTestFiles = config.testSync;
258
- const scpClient = await getScpClient();
259
- for (const [src, dest] of Object.entries(syncTestFiles)) {
260
- const srcPath = join(workDir, src);
261
- const destPath = "/home/web/website/tuwan_www/templets/static/play/" + dest;
262
- log(`scp: ${srcPath} -> ${TEST_SERVER_HOST}:${destPath}`);
263
- await scpClient.uploadFile(srcPath, destPath)
264
- }
265
- }
266
- } catch (error) {
267
- log(`scp error: ${error}`);
268
- if (assetsFilesCount > 0) {
269
- log(`不过请放心,构建产物的assets已经处理完成,只要手动处理index.html文件即可。`);
270
- }
271
- }
272
- // 处理触发流水线的任务
273
- await runPipeline(config, mode);
274
- }
275
- }
package/util.mjs DELETED
@@ -1,22 +0,0 @@
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
- }