@ts-max/deploy 0.0.16

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 ts-max
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.cjs ADDED
@@ -0,0 +1,370 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const process = require("process");
6
+ const xml2js = require("xml2js");
7
+ const child_process = require("child_process");
8
+ const request = require("sync-request");
9
+ const axios = require("axios");
10
+ const os = require("os");
11
+ const chalk = require("chalk");
12
+ const inquirer = require("inquirer");
13
+ function _interopNamespaceDefault(e) {
14
+ const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
15
+ if (e) {
16
+ for (const k in e) {
17
+ if (k !== "default") {
18
+ const d = Object.getOwnPropertyDescriptor(e, k);
19
+ Object.defineProperty(n, k, d.get ? d : {
20
+ enumerable: true,
21
+ get: () => e[k]
22
+ });
23
+ }
24
+ }
25
+ }
26
+ n.default = e;
27
+ return Object.freeze(n);
28
+ }
29
+ const path__namespace = /* @__PURE__ */ _interopNamespaceDefault(path);
30
+ const process__namespace = /* @__PURE__ */ _interopNamespaceDefault(process);
31
+ const __cwd = process__namespace.cwd();
32
+ const pak = require(path__namespace.join(__cwd, "package.json"));
33
+ const account = getAccount();
34
+ const defaultConfig = {
35
+ CONFIG_FILE: "deploy/config.js",
36
+ SHELL_FILE: "deploy/command.sh",
37
+ ENV_TIP: "测试环境",
38
+ DOMAIN: void 0,
39
+ APP_TITLE: pak.title || pak.name,
40
+ JENKINS_BASE_URL: "",
41
+ JENKINS_JOB_NAME: pak.name?.replace(/\s+/, "-").toLowerCase(),
42
+ JENKINS_JOB_XML_FILE: path__namespace.join(__dirname, "job.xml"),
43
+ RANCHER: false,
44
+ RANCHER_URL: void 0,
45
+ RANCHER_BEARER_TOKEN: void 0,
46
+ RANCHER_NAME_SPACE: "front-center",
47
+ GIT_URL: getGitUrl(),
48
+ GIT_BRANCH: getGitBranch$1(),
49
+ NODE_VERSION: "18",
50
+ NOTICE_USERS: void 0,
51
+ MESSAGE_URL: void 0,
52
+ MESSAGE_TOKEN: void 0
53
+ };
54
+ const customerConfig = getConfig();
55
+ const config = Object.keys(defaultConfig).reduce((prev, curr) => {
56
+ return {
57
+ ...prev,
58
+ [curr]: val(curr)
59
+ };
60
+ }, {});
61
+ function getConfig() {
62
+ const CONFIG_FILE = getProcessEnv("CONFIG_FILE") || defaultConfig.CONFIG_FILE;
63
+ let configPath = path__namespace.resolve(__cwd, CONFIG_FILE);
64
+ return fs.existsSync(configPath) ? require(configPath) : {};
65
+ }
66
+ function val(key) {
67
+ let _defaultValue = defaultConfig[key];
68
+ if (_defaultValue === void 0 || _defaultValue === "") _defaultValue = account[key];
69
+ const envValue = getProcessEnv(key);
70
+ if (envValue !== void 0) return envValue;
71
+ if (key in customerConfig) return customerConfig[key];
72
+ return _defaultValue;
73
+ }
74
+ function getProcessEnv(key) {
75
+ if (key in process__namespace.env) {
76
+ const value = process__namespace.env[key];
77
+ if (value === "false") return false;
78
+ if (value === "true") return true;
79
+ return value;
80
+ }
81
+ return void 0;
82
+ }
83
+ function getAccount() {
84
+ const res = request("GET", "http://22.50.7.25:21168/?type=rancher");
85
+ return JSON.parse(res.getBody("utf8"));
86
+ }
87
+ function getGitRoot() {
88
+ let rootPath = __cwd;
89
+ while (rootPath) {
90
+ if (fs.existsSync(path__namespace.join(rootPath, ".git"))) {
91
+ break;
92
+ }
93
+ const paths = rootPath.split(path__namespace.sep);
94
+ rootPath = paths.slice(0, paths.length - 1).join(path__namespace.sep);
95
+ }
96
+ if (rootPath) return rootPath;
97
+ throw new Error("非git仓库,或 非仓库中子文件夹");
98
+ }
99
+ function httpToSSH(url) {
100
+ if (!url) return url;
101
+ if (url.startsWith("http")) {
102
+ const { host, pathname } = new URL(url);
103
+ return `git@${host}:${pathname.replace("/", "")}`;
104
+ }
105
+ return url;
106
+ }
107
+ function getGitUrl() {
108
+ const gitRoot = getGitRoot();
109
+ const gitConfigPath = path__namespace.join(gitRoot, ".git", "config");
110
+ const configContent = fs.readFileSync(gitConfigPath, "UTF-8");
111
+ const arr = configContent.split("\n ");
112
+ const url = arr.find((item) => item.startsWith("url = ")) || "";
113
+ return httpToSSH(url.replace("url = ", ""));
114
+ }
115
+ function getGitBranch$1() {
116
+ return child_process.execSync("git rev-parse --abbrev-ref HEAD").toString().trim();
117
+ }
118
+ const jenkins = require("jenkins")({
119
+ baseUrl: config.JENKINS_BASE_URL,
120
+ crumbIssuer: false,
121
+ promisify: true
122
+ });
123
+ async function jenkinsBuildJob() {
124
+ const queueNumber = await jenkins.job.build(config.JENKINS_JOB_NAME);
125
+ return getJenkinsBuildNumber(queueNumber);
126
+ }
127
+ async function isJenkinsJobExist() {
128
+ return jenkins.job.exists(config.JENKINS_JOB_NAME);
129
+ }
130
+ async function createJenkinsJob() {
131
+ const xml = await getJenkinsConfigXml();
132
+ return new Promise((resolve, reject) => {
133
+ jenkins.job.create(
134
+ {
135
+ name: config.JENKINS_JOB_NAME,
136
+ xml
137
+ },
138
+ (err, data) => {
139
+ if (err) return reject(err);
140
+ resolve(data);
141
+ }
142
+ );
143
+ });
144
+ }
145
+ async function modifyJenkinsJobConfig() {
146
+ const xml = await getJenkinsConfigXml();
147
+ return jenkins.job.config(config.JENKINS_JOB_NAME, xml);
148
+ }
149
+ function getJenkinsAddress(path2 = "") {
150
+ const url = new URL(config.JENKINS_BASE_URL);
151
+ return `${url.origin}${url.pathname}/job/${config.JENKINS_JOB_NAME}${path2}`;
152
+ }
153
+ async function getJenkinsBuildNumber(queueNumber) {
154
+ return new Promise((resolve) => {
155
+ const si = setInterval(async () => {
156
+ const res = await jenkins.queue.item(queueNumber);
157
+ if (!res.executable) {
158
+ console.log(res.why);
159
+ } else {
160
+ resolve(res.executable.number);
161
+ clearInterval(si);
162
+ }
163
+ }, 2e3);
164
+ });
165
+ }
166
+ async function isJenkinsBuildSuccess(buildNumber) {
167
+ const res = await jenkins.build.get({ name: config.JENKINS_JOB_NAME, number: buildNumber });
168
+ return res.result === "SUCCESS";
169
+ }
170
+ function getJenkinsLogStream(buildNumber) {
171
+ return jenkins.build.logStream(config.JENKINS_JOB_NAME, buildNumber);
172
+ }
173
+ async function showJenkinsLog(buildNumber) {
174
+ return new Promise((resolve, reject) => {
175
+ const log = getJenkinsLogStream(buildNumber);
176
+ log.on("data", function(text) {
177
+ process__namespace.stdout.write(text);
178
+ });
179
+ log.on("error", function(err) {
180
+ reject(err);
181
+ });
182
+ log.on("end", async function() {
183
+ const isSuccess = await isJenkinsBuildSuccess(buildNumber);
184
+ if (isSuccess) {
185
+ resolve(isSuccess);
186
+ } else {
187
+ reject(Error("部署失败"));
188
+ }
189
+ });
190
+ });
191
+ }
192
+ async function getJenkinsConfigXml() {
193
+ const gitUrl = config.GIT_URL;
194
+ const branch = config.GIT_BRANCH;
195
+ const shell = fs.readFileSync(path.resolve(process__namespace.cwd(), config.SHELL_FILE), "UTF-8");
196
+ const nodeVersion = config.NODE_VERSION;
197
+ if (!gitUrl) throw Error("git 地址不能为空!");
198
+ let xmlTemplate = fs.readFileSync(path.resolve(process__namespace.cwd(), config.JENKINS_JOB_XML_FILE), "UTF-8") || "";
199
+ const parser = new xml2js.Parser(
200
+ /* options */
201
+ );
202
+ const result = await parser.parseStringPromise(xmlTemplate);
203
+ result.project.scm[0].userRemoteConfigs[0]["hudson.plugins.git.UserRemoteConfig"][0].url = gitUrl;
204
+ result.project.scm[0].branches[0]["hudson.plugins.git.BranchSpec"][0].name[0] = `*/${branch}`;
205
+ result.project.buildWrappers[0]["jenkins.plugins.nodejs.NodeJSBuildWrapper"][0].nodeJSInstallationName = nodeVersion;
206
+ result.project.builders[0]["hudson.tasks.Shell"][0].command = [shell];
207
+ const builder = new xml2js.Builder();
208
+ return builder.buildObject(result);
209
+ }
210
+ function getGitBranch() {
211
+ return child_process.execSync("git rev-parse --abbrev-ref HEAD").toString().trim();
212
+ }
213
+ async function checkGitOrigin() {
214
+ const result = child_process.execSync(`git fetch && git diff ${config.GIT_BRANCH} origin/${config.GIT_BRANCH}`).toString().trim();
215
+ const isSame = !result;
216
+ if (isSame) return;
217
+ const { yes } = await inquirer.prompt([
218
+ {
219
+ type: "confirm",
220
+ message: chalk.red(`您要部署的分支${config.GIT_BRANCH},本地与远程代码不同,确定进行了代码推送?`),
221
+ name: "yes"
222
+ }
223
+ ]);
224
+ if (!yes) process.exit(0);
225
+ }
226
+ async function checkGitBranch() {
227
+ const currentBranch = getGitBranch();
228
+ const envBranch = config.GIT_BRANCH;
229
+ if (!envBranch) return;
230
+ if (envBranch === currentBranch) return;
231
+ const { yes } = await inquirer.prompt([
232
+ {
233
+ type: "confirm",
234
+ message: chalk.red(`您要部署的分支${config.GIT_BRANCH},不是您本地当前分支,确认进行了代码合并?`),
235
+ name: "yes"
236
+ }
237
+ ]);
238
+ if (!yes) process.exit(0);
239
+ }
240
+ function getGitLastCommit() {
241
+ return child_process.execSync("git show -s --format=%s").toString().trim();
242
+ }
243
+ function getOperator() {
244
+ const res = request("GET", "http://22.50.7.25:21168/?type=operator");
245
+ const users = JSON.parse(res.getBody("utf8"));
246
+ const ipv4 = getNetworkIp();
247
+ const record = (users || []).find((item) => item.ip === ipv4);
248
+ return record?.name || `${os.userInfo().username}(${ipv4})`;
249
+ }
250
+ function getNetworkIp() {
251
+ const interfaces = os.networkInterfaces();
252
+ for (let value of Object.values(interfaces)) {
253
+ for (let alias of value) {
254
+ if (alias.family === "IPv4" && !alias.internal) {
255
+ if (alias.address.startsWith("172")) {
256
+ return alias.address;
257
+ }
258
+ }
259
+ }
260
+ }
261
+ }
262
+ async function check() {
263
+ await checkGitOrigin();
264
+ await checkGitBranch();
265
+ }
266
+ async function getWebAddress() {
267
+ if (!config.RANCHER) return;
268
+ if (!config.RANCHER_URL || !config.RANCHER_BEARER_TOKEN) return;
269
+ const url = `${config.RANCHER_URL}/project/c-m-spdnzth6:p-prdww/workloads/deployment:${config.RANCHER_NAME_SPACE}:${config.JENKINS_JOB_NAME}`;
270
+ const res = await axios.get(url, {
271
+ headers: {
272
+ Authorization: `Bearer ${config.RANCHER_BEARER_TOKEN}`
273
+ }
274
+ });
275
+ if (res.data && res.data.publicEndpoints && res.data.publicEndpoints.length) {
276
+ const info = res.data.publicEndpoints[0];
277
+ const { addresses = [], port } = info;
278
+ if (addresses.length && port) {
279
+ const ip = addresses[0];
280
+ return `http://${ip}:${port}`;
281
+ }
282
+ }
283
+ return;
284
+ }
285
+ async function showLog(buildNumber) {
286
+ const webAddress = await getWebAddress();
287
+ const jenkinsAddress = getJenkinsAddress(`/${buildNumber}/console`);
288
+ const event = process.env.npm_lifecycle_event;
289
+ console.log();
290
+ console.log(`## ${config.ENV_TIP}部署`);
291
+ console.log(`- 执行命令:yarn ${event}`);
292
+ console.log(`- jenkins地址: ${jenkinsAddress}`);
293
+ webAddress && console.log(`- web访问地址: ${webAddress}`);
294
+ config.DOMAIN && console.log(`- web访问域名: ${config.DOMAIN}`);
295
+ }
296
+ async function notice(buildNumber) {
297
+ const jenkinsAddress = getJenkinsAddress(`/${buildNumber}/console`);
298
+ const webAddress = await getWebAddress();
299
+ let users = config.NOTICE_USERS || [];
300
+ if (!users || !users.length) return;
301
+ if (typeof users === "string") users = users.split(",");
302
+ if (!Array.isArray(users)) return;
303
+ const operator = getOperator();
304
+ users = users.map((item) => item.trim());
305
+ const commit = getGitLastCommit();
306
+ const date = dateFormat("YYYY-mm-dd HH:MM:SS", /* @__PURE__ */ new Date());
307
+ const message = {
308
+ token: config.MESSAGE_TOKEN,
309
+ userIds: users.join("|"),
310
+ type: "markdown",
311
+ content: [
312
+ `**「${config.APP_TITLE}」${config.ENV_TIP}部署成功!**`,
313
+ `>部署人员:<font color="info">${operator}</font>`,
314
+ `>构建时间:<font color="info">${date}</font>`,
315
+ `>构建分支:<font color="info">${config.GIT_BRANCH}</font>`,
316
+ `>提交信息:<font color="info">${commit}</font>`,
317
+ `>`,
318
+ webAddress && `>访问地址:[${webAddress}](${webAddress})`,
319
+ config.DOMAIN && `>访问域名:[${config.DOMAIN}](${config.DOMAIN})`,
320
+ `>构建日志:[${jenkinsAddress}](${jenkinsAddress})`
321
+ ].filter(Boolean).join("\n")
322
+ };
323
+ await axios({
324
+ method: "post",
325
+ url: config.MESSAGE_URL,
326
+ data: message
327
+ }).catch((e) => {
328
+ console.log(e);
329
+ });
330
+ }
331
+ function dateFormat(fmt, date) {
332
+ let ret;
333
+ const opt = {
334
+ "Y+": date.getFullYear().toString(),
335
+ // 年
336
+ "m+": (date.getMonth() + 1).toString(),
337
+ // 月
338
+ "d+": date.getDate().toString(),
339
+ // 日
340
+ "H+": date.getHours().toString(),
341
+ // 时
342
+ "M+": date.getMinutes().toString(),
343
+ // 分
344
+ "S+": date.getSeconds().toString()
345
+ // 秒
346
+ // 有其他格式化字符需求可以继续添加,必须转化成字符串
347
+ };
348
+ for (let k in opt) {
349
+ ret = new RegExp("(" + k + ")").exec(fmt);
350
+ if (ret) {
351
+ fmt = fmt.replace(ret[1], ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, "0"));
352
+ }
353
+ }
354
+ return fmt;
355
+ }
356
+ (async () => {
357
+ await check();
358
+ console.log("- jenkins地址:", getJenkinsAddress());
359
+ const exist = await isJenkinsJobExist();
360
+ if (!exist) {
361
+ console.log("- 创建Jenkins任务:", config.JENKINS_JOB_NAME);
362
+ await createJenkinsJob();
363
+ } else {
364
+ await modifyJenkinsJobConfig();
365
+ }
366
+ const buildNumber = await jenkinsBuildJob();
367
+ await showJenkinsLog(buildNumber);
368
+ await showLog(buildNumber);
369
+ await notice(buildNumber);
370
+ })();
@@ -0,0 +1 @@
1
+ export { }
package/dist/job.xml ADDED
@@ -0,0 +1,72 @@
1
+ <?xml version='1.1' encoding='UTF-8'?>
2
+ <project>
3
+ <actions/>
4
+ <description></description>
5
+ <keepDependencies>false</keepDependencies>
6
+ <properties/>
7
+ <scm class="hudson.plugins.git.GitSCM" plugin="git@4.2.2">
8
+ <configVersion>2</configVersion>
9
+ <userRemoteConfigs>
10
+ <hudson.plugins.git.UserRemoteConfig>
11
+ <url>https://gitee.com/sxfad/react-admin.git</url>
12
+ </hudson.plugins.git.UserRemoteConfig>
13
+ </userRemoteConfigs>
14
+ <branches>
15
+ <hudson.plugins.git.BranchSpec>
16
+ <name>*/master</name>
17
+ </hudson.plugins.git.BranchSpec>
18
+ </branches>
19
+ <doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>
20
+ <submoduleCfg class="list"/>
21
+ <extensions/>
22
+ </scm>
23
+ <canRoam>true</canRoam>
24
+ <disabled>false</disabled>
25
+ <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
26
+ <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
27
+ <triggers/>
28
+ <concurrentBuild>false</concurrentBuild>
29
+ <builders>
30
+ <hudson.tasks.Shell>
31
+ <command>
32
+ # 进入前端目录
33
+ cd .
34
+ # 输出node版本
35
+ node -v
36
+ # 安装依赖
37
+ yarn install
38
+
39
+ # 前端构建
40
+ yarn build
41
+
42
+ # 将构建生成的静态文件copy到deploy目录,提升docker构建速度
43
+ rm -rf deploy/rancher/build &amp;&amp; rsync -rv --exclude=deploy build/ deploy/rancher/build
44
+
45
+ # 进入deploy目录
46
+ cd deploy/rancher
47
+
48
+ # 构建docker镜像
49
+ cat Dockerfile
50
+ docker build --no-cache -t test-harbor.suixingpay.com/app/${JOB_BASE_NAME}:${BUILD_ID} -f Dockerfile .
51
+ docker push test-harbor.suixingpay.com/app/${JOB_BASE_NAME}:${BUILD_ID}
52
+
53
+ # deploy发布
54
+ sed -i &quot;s/NAMESPACE_NAME/front-center/g&quot; deployment.yaml
55
+ sed -i &quot;s/JOB_BASE_NAME/${JOB_BASE_NAME}/g&quot; deployment.yaml
56
+ sed -i &quot;s/BUILD_ID/${BUILD_ID}/g&quot; deployment.yaml
57
+ cat deployment.yaml
58
+ kubectl apply -f deployment.yaml
59
+
60
+ # 删除镜像
61
+ docker rmi test-harbor.suixingpay.com/app/${JOB_BASE_NAME}:${BUILD_ID}
62
+ </command>
63
+ </hudson.tasks.Shell>
64
+ </builders>
65
+ <publishers/>
66
+ <buildWrappers>
67
+ <jenkins.plugins.nodejs.NodeJSBuildWrapper plugin="nodejs@1.3.4">
68
+ <nodeJSInstallationName>14.17.0</nodeJSInstallationName>
69
+ <cacheLocationStrategy class="jenkins.plugins.nodejs.cache.DefaultCacheLocationLocator"/>
70
+ </jenkins.plugins.nodejs.NodeJSBuildWrapper>
71
+ </buildWrappers>
72
+ </project>
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@ts-max/deploy",
3
+ "version": "0.0.16",
4
+ "description": "部署脚本",
5
+ "main": "./dist/index.cjs",
6
+ "types": "./dist/index.d.ts",
7
+ "bin": {
8
+ "rc-deploy": "./dist/index.cjs"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "dev": "vite build --watch",
21
+ "build": "vite build"
22
+ },
23
+ "dependencies": {
24
+ "axios": "^1.2.0",
25
+ "chalk": "4.1.2",
26
+ "inquirer": "8.0.0",
27
+ "jenkins": "^0.28.1",
28
+ "sync-request": "^6.1.0",
29
+ "xml2js": "^0.6.2"
30
+ },
31
+ "devDependencies": {
32
+ "@types/inquirer": "^8.2.12",
33
+ "@types/node": "^24.10.1",
34
+ "@types/xml2js": "^0.4.14",
35
+ "vite": "^7.2.6",
36
+ "vite-plugin-dts": "^4.5.4"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "keywords": [
42
+ "deploy",
43
+ "jenkins",
44
+ "rancher",
45
+ "cli"
46
+ ],
47
+ "license": "MIT",
48
+ "gitHead": "3b6508c312e7354d211bf69be8f459faa5288638"
49
+ }