yj-deploy 0.0.2 → 0.0.4

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
@@ -1,19 +1,18 @@
1
1
  # yj-deploy
2
2
 
3
- > `yj-deploy` 是为解决公司内部项目部署到镜像服务器开发的工具,可解决每次开发完需通过繁琐的步骤才能获得镜像地址的问题。
4
-
5
- <br />
6
- <br />
3
+ `yj-deploy` 是为解决公司内部项目部署到镜像服务器开发的工具,可帮助开发者自动构建镜像,并自动部署到镜像服务器上。
7
4
 
8
5
  ## 目前已实现如下功能
9
6
  - &#x2714; 项目打包完成后自动上传到跳板机
10
- - &#x2714; 自动生成目录,自动创建dockerfile,upload.sh脚本文件
7
+ - &#x2714; 自动生成项目目录,自动创建dockerfile,upload.sh脚本文件
11
8
  - &#x2714; 自动运行脚本,自动打包镜像,自动推送到镜像服务器(全程不用手动操作)
12
9
  - &#x2714; 直接集成到vite,webpack项目
13
10
  - &#x2714; 支持手动上传其他任何类型项目
14
11
  - &#x2714; 支持灵活配置dockerfile,upload.sh等脚本内容
15
12
  - &#x2714; 支持多种方式配置 `镜像仓库` `镜像环境`等参数
13
+ > 全程不需要连接跳板机做任何操作即可完成镜像推送
16
14
 
15
+ ## 运行情况
17
16
  ![预览](./img.png)
18
17
 
19
18
  ## 安装
@@ -28,27 +27,27 @@ $ npm i yj-deploy -D
28
27
 
29
28
  ```javascript
30
29
  import path from 'path'
31
- import yjDeploy from './lib/main.js'
30
+ import yjDeploy from 'yj-deploy'
32
31
 
33
32
  const deploy = new yjDeploy({
34
33
  host: '', // ip
35
34
  port: '', // 端口
36
35
  username: '', // 账号
37
36
  password: '', // 密码
38
- namespace: '', // 项目命名空间,等同于镜像地址目录名称
37
+ namespace: '', // 项目名称,等同git地址目录名称
39
38
  // 本地文件目录
40
39
  fileDir: path.resolve('./dist'),
41
40
  })
42
41
  ```
43
42
 
44
43
  ## 完整配置(包含默认配置)
45
- ```json
44
+ ```javascript
46
45
  {
47
46
  host: '', // ip (必填)
48
47
  port: '', // 端口 (必填)
49
48
  username: '', // 账号 (必填)
50
49
  password: '', // 密码 (必填)
51
- namespace: '', // 项目命名空间,等同于镜像地址目录名称 (必填)
50
+ namespace: '', // 项目名称,等同git地址目录名称(必填)
52
51
  imageStore: 'dev-images', // 镜像仓库, 默认为 dev-images
53
52
  tmpName: 'dev', // 镜像环境, 默认 dev
54
53
  delay: 0, // 延迟上传时间 (部分项目构建需要时间,延迟上传可以解决)
@@ -64,7 +63,7 @@ const deploy = new yjDeploy({
64
63
  ]
65
64
  },
66
65
 
67
- // upload.sh文件信息(可覆盖,单需要注意 $1 和 $2参数的顺序)
66
+ // upload.sh文件信息(可覆盖,但需要注意 $1 和 $2参数的顺序)
68
67
  upload: {
69
68
  name: 'upload.sh',
70
69
  content: [
@@ -81,14 +80,25 @@ const deploy = new yjDeploy({
81
80
  // 上传dist目录信息 (除非运维改变,否则不要配置)
82
81
  dist: {
83
82
  name: 'dist'
84
- }
83
+ },
84
+
85
+ parallelDir: 20, // 文件夹并行创建数量,如果在创建文件夹阶段报错,可以尝试减少此配置
86
+
87
+ parallelFile: 50, // 文件并行上传数量,如果在上传文件阶段报错,可以减少此配置
88
+
89
+ allLog: false // 是否打印所有log信息,主要是包含推送镜像阶段的日志,如果报错可以打开此开关
85
90
  }
86
91
  ```
87
92
 
93
+ <br />
94
+
88
95
  # 使用
96
+
89
97
  ## 配合打包命令使用
98
+ 可以配合项目脚手架使用
99
+
100
+ ### webpack 中使用
90
101
  ```javascript
91
- // webpack 中使用
92
102
  //vue.config.js
93
103
  const path = require('path')
94
104
  const yjDeploy = require('yj-deploy')
@@ -101,7 +111,7 @@ module.exports = {
101
111
  port: '',
102
112
  username: '',
103
113
  password: '',
104
- namespace: '', // 项目命名空间,等同于镜像地址目录名称
114
+ namespace: '', // 项目名称,等同git地址目录名称
105
115
  // 本地文件目录
106
116
  fileDir: path.resolve('./dist'),
107
117
  })
@@ -117,8 +127,8 @@ module.exports = {
117
127
  // 使用 yarn deploy 或 npm run deploy
118
128
  ```
119
129
 
130
+ ### vite 项目中使用
120
131
  ```javascript
121
- // vite 项目中使用
122
132
  //vite.config.js
123
133
  import path from 'path'
124
134
  import yjDeploy from 'yj-deploy'
@@ -130,7 +140,7 @@ export default defineConfig({
130
140
  port: '',
131
141
  username: '',
132
142
  password: '',
133
- namespace: '', // 项目命名空间,等同于镜像地址目录名称
143
+ namespace: '', // 项目名称,等同git地址目录名称
134
144
  // 本地文件目录
135
145
  fileDir: path.resolve('./dist'),
136
146
  })
@@ -139,20 +149,44 @@ export default defineConfig({
139
149
 
140
150
  // package.json
141
151
  "scripts": {
142
- "deploy": "vue-cli-service build --mode development -- --deploy"
152
+ "deploy": "vite build --mode development -- --deploy"
143
153
  }
144
154
  // 使用 yarn deploy 或 npm run deploy
145
155
  ```
146
156
 
157
+ ### 上传任意项目
158
+ ```javascript
159
+ // 1、在项目中创建uploader.js
160
+ // 2、配置和webpack插件模式相同
161
+ const deploy = new yjDeploy({
162
+ host: '',
163
+ port: '',
164
+ username: '',
165
+ password: '',
166
+ namespace: '', // 项目命名空间,等同于镜像地址目录名称
167
+ // 本地文件目录
168
+ fileDir: path.resolve('./dist'),
169
+ })
170
+
171
+ deploy.upload() // 开始上传
172
+ // 然后在项目根目录终端下运行如下命令(node直接上传不需要加--deploy动态指令)
173
+ node uploader.js
174
+
175
+ // 增加动态配置
176
+ node uploader.js --tmpName=test
177
+ ```
178
+
179
+
147
180
  ## 动态配置项
148
181
  由于部分项目可能需要区分环境,所以需要动态配置项,目前支持动态配置项如下:
149
182
  <br />
150
183
 
151
- `--deploy` :是否启用自动部署功能,如果启用,在命令后跟上 --deploy即可
184
+ ### --deploy
185
+ `--deploy` :是否启用自动部署功能,在配合vite或webpack时需要使用,如果启用,在命令后跟上 --deploy即可
152
186
  ```javascript
153
187
  // vite项目 package.json
154
188
  "scripts": {
155
- "deploy": "vue-cli-service build --mode development -- --deploy"
189
+ "deploy": "vite build --mode development -- --deploy"
156
190
  }
157
191
 
158
192
  // webpack项目 package.json
@@ -166,53 +200,47 @@ export default defineConfig({
166
200
 
167
201
  <br />
168
202
 
169
- `--reset` :是否重置项目,如果配置了此选项,则每次执行命令时会删除`dockerfile``upload.sh`配置文件,然后重新初始化项目,默认为不重置
203
+ ### --reset
204
+ `--reset` :是否重置项目(默认为不重置),如果配置了此选项,则每次执行命令时会删除 `dockerfile` `upload.sh` 配置文件,然后重新初始化项目
170
205
 
171
206
  <br />
172
207
 
208
+ ### --imageStore
173
209
  `--imageStore` :镜像仓库名称(默认为dev-images),在测试或者正式环境可能需要配置,需要推送到指定的镜像仓库时配置,增加此动态配置可以使用更灵活,同一套主配置可以满足多个环境推送到不同的镜像仓库,如:
174
210
  ```javascript
175
211
  "scripts": {
176
212
  // 开发环境
177
- "deploy": "vue-cli-service build --mode development -- --deploy"
213
+ "deploy": "vite build --mode development -- --deploy"
178
214
  // 测试环境
179
- "deploy-test": "vue-cli-service build --mode development -- --deploy --imageStore=sot-admin"
215
+ "deploy-test": "vite build --mode test -- --deploy --imageStore=sot-admin"
180
216
  }
181
217
  ```
182
218
 
183
219
  <br />
184
220
 
221
+ ### --tmpName
185
222
  `--tmpName` :镜像环境名称(默认为 dev),在测试或者正式环境可能需要配置,需要推送到指定的镜像环境时配置,防止镜像名重复导致覆盖,同一套主配置可以满足多个环境推送到不同的镜像地址,如:
186
223
  ```javascript
187
224
  "scripts": {
188
225
  // 开发环境
189
- "deploy": "vue-cli-service build --mode development -- --deploy --tmpName=dev"
226
+ "deploy": "vite build --mode development -- --deploy --tmpName=dev"
190
227
  // 测试环境
191
- "deploy-test": "vue-cli-service build --mode development -- --deploy --tmpName=test"
192
- // 测试环境
193
- "deploy-prod": "vue-cli-service build --mode development -- --deploy --tmpName=prod"
228
+ "deploy-test": "vite build --mode test -- --deploy --tmpName=test"
229
+ // 正式环境
230
+ "deploy-prod": "vite build --mode production -- --deploy --tmpName=prod"
194
231
  }
195
232
  ```
196
233
 
234
+ <br />
197
235
 
198
- ## 上传任意项目
199
- ```javascript
200
- // 1、在项目中创建uploader.js
201
- // 2、配置和webpack插件模式相同
202
- const deploy = new yjDeploy({
203
- host: '',
204
- port: '',
205
- username: '',
206
- password: '',
207
- namespace: '', // 项目命名空间,等同于镜像地址目录名称
208
- // 本地文件目录
209
- fileDir: path.resolve('./dist'),
210
- })
236
+ ### --allLog
237
+ `--allLog` :是否打印所有log信息,主要是包含推送镜像阶段的日志,如果报错可以打开此开关
211
238
 
212
- deploy.upload() // 开始上传
213
- // 然后在项目根目录终端下运行如下命令(node直接上传不需要加--deploy动态指令)
214
- node uploader.js
239
+ <br />
215
240
 
216
- // 增加动态配置
217
- node uploader.js --tmpName=test
218
- ```
241
+ # 最后
242
+ 如有意见或建议,或bug或文档不清晰等问题,可随时向我反馈,或直接修改后提交,共同完善这个工具。
243
+
244
+ <br />
245
+
246
+ 项目已提交到git:https://gitlab.yunjingtech.cn:10010/frontend/yj-deploy
@@ -1,10 +1,10 @@
1
- var S = Object.defineProperty;
2
- var C = (h, o, e) => o in h ? S(h, o, { enumerable: !0, configurable: !0, writable: !0, value: e }) : h[o] = e;
3
- var g = (h, o, e) => (C(h, typeof o != "symbol" ? o + "" : o, e), e);
4
- const { stdout: j } = require("single-line-log"), D = require("path"), m = require("fs"), { Client: v } = require("ssh2");
5
- class F {
6
- constructor(o) {
7
- g(this, "config", {
1
+ var L = Object.defineProperty;
2
+ var v = (f, e, t) => e in f ? L(f, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : f[e] = t;
3
+ var y = (f, e, t) => (v(f, typeof e != "symbol" ? e + "" : e, t), t);
4
+ const { stdout: x } = require("single-line-log"), F = require("path"), b = require("fs"), { Client: A } = require("ssh2");
5
+ module.exports = class {
6
+ constructor(e) {
7
+ y(this, "config", {
8
8
  host: "",
9
9
  port: "",
10
10
  username: "",
@@ -46,14 +46,20 @@ class F {
46
46
  // 上传dist目录信息
47
47
  dist: {
48
48
  name: "dist"
49
- }
49
+ },
50
+ parallelDir: 20,
51
+ // 文件夹并行创建数量,如果报错,可以减少此配置
52
+ parallelFile: 50,
53
+ // 文件并行上传数量,如果报错,可以减少此配置
54
+ allLog: !1
55
+ // 是否显示全部日志
50
56
  });
51
- g(this, "ssh2Conn", null);
57
+ y(this, "ssh2Conn", null);
52
58
  // 上传状态
53
- g(this, "uploading", !1);
59
+ y(this, "uploading", !1);
54
60
  // 定时器
55
- g(this, "trim", null);
56
- return this.config = Object.assign(this.config, o), {
61
+ y(this, "trim", null);
62
+ return this.config = Object.assign(this.config, e), {
57
63
  name: "yj-deploy",
58
64
  isPut: this.isPut.bind(this),
59
65
  upload: this.upload.bind(this),
@@ -64,8 +70,8 @@ class F {
64
70
  };
65
71
  }
66
72
  // webpack钩子
67
- apply(o) {
68
- return o && o.hooks && o.hooks.done && o.hooks.done.tap("yj-deploy", () => {
73
+ apply(e) {
74
+ return e && e.hooks && e.hooks.done && e.hooks.done.tap("yj-deploy", () => {
69
75
  this.isPut();
70
76
  }), "build";
71
77
  }
@@ -99,21 +105,24 @@ class F {
99
105
  console.log("- 请配置跳板机密码 password");
100
106
  return;
101
107
  }
102
- this.uploading || (this.ssh2Conn = new v(), this.ssh2Conn.on("ready", () => {
103
- console.log("- 跳板机连接成功!"), this.ssh2Conn.sftp(async (o, e) => {
104
- o && (console.log("- sftp连接失败"), this.breakConnect()), this.config.namespace || (console.log("- 请配置项目命名空间 namespace"), this.breakConnect()), this.uploading = !0, this.getArgv("--reset") !== void 0 && (console.log("- 重置项目"), await this.onShell(`rm -r ${this.config.rootDir}/${this.config.namespace}`)), this.upLoadProject(e, `${this.config.rootDir}/${this.config.namespace}`);
108
+ this.uploading || (this.ssh2Conn = new A(), this.ssh2Conn.on("ready", () => {
109
+ console.log("- 跳板机连接成功!"), this.ssh2Conn.sftp(async (e, t) => {
110
+ e && (console.log("- sftp连接失败"), this.breakConnect()), this.config.namespace || (console.log("- 请配置项目命名空间 namespace"), this.breakConnect()), this.uploading = !0, this.getArgv("--reset") !== void 0 && (console.log("- 重置项目"), await this.onShell(`rm -r ${this.config.rootDir}/${this.config.namespace}`)), this.upLoadProject(t, `${this.config.rootDir}/${this.config.namespace}`);
105
111
  });
106
112
  }).connect({
107
113
  host: this.config.host,
108
114
  port: this.config.port,
109
115
  username: this.config.username,
110
116
  password: this.config.password
111
- }), this.ssh2Conn.on("error", (o) => {
112
- console.log(`- 连接失败: ${o}`), this.breakConnect();
117
+ }), this.ssh2Conn.on("error", (e) => {
118
+ console.log(`- 连接失败: ${e}`), this.breakConnect();
113
119
  }), this.ssh2Conn.on("end", () => {
114
120
  this.uploading = !1;
115
121
  }));
116
122
  }
123
+ /**
124
+ * 断开连接
125
+ */
117
126
  breakConnect() {
118
127
  this.uploading = !1, console.log("- 已断开连接"), console.log("-------------- deploy-end --------------"), this.ssh2Conn.end();
119
128
  }
@@ -122,23 +131,25 @@ class F {
122
131
  * @param {*} shell
123
132
  * @returns Promise
124
133
  */
125
- onShell(o) {
126
- return new Promise((e, r) => {
127
- this.ssh2Conn.shell((i, s) => {
134
+ onShell(e) {
135
+ const t = this.getArgv("--allLog") !== void 0;
136
+ return new Promise((n, s) => {
137
+ this.ssh2Conn.shell((i, a) => {
128
138
  if (i) {
129
- this.breakConnect();
139
+ console.log("- 远程命令错误:" + i, e), this.breakConnect(), s(i);
130
140
  return;
131
141
  }
132
142
  let l = [];
133
- s.on("close", () => {
134
- e(l.toString());
135
- }).on("data", (t) => {
136
- l.push(`
137
- ` + t);
138
- }).stderr.on("data", (t) => {
143
+ a.on("close", () => {
144
+ n(l.toString());
145
+ }).on("data", (c) => {
146
+ let r = c.toString();
147
+ (t || this.config.allLog || r.startsWith("- ")) && l.push(`
148
+ ` + r);
149
+ }).stderr.on("data", (c) => {
139
150
  console.log(`- 远程命令错误:
140
- ` + t), this.ssh2Conn.end(), r(t);
141
- }), s.end(o + `
151
+ ` + c), this.breakConnect(), s(c);
152
+ }), a.end(e + `
142
153
  exit
143
154
  `);
144
155
  });
@@ -149,19 +160,19 @@ exit
149
160
  * @param {*} sftp
150
161
  * @param {*} dir
151
162
  */
152
- projectInit(o, e) {
153
- return new Promise(async (r, i) => {
154
- o.mkdir(e, async (s) => {
155
- if (s)
156
- console.log(s), i();
163
+ projectInit(e, t) {
164
+ return new Promise(async (n, s) => {
165
+ e.mkdir(t, async (i) => {
166
+ if (i)
167
+ s(i);
157
168
  else {
158
169
  console.log("- 正在创建dockerfile");
159
- const l = `${e}/${this.config.dockerfile.name}`;
160
- await this.onShell(`touch ${l}`), await this.onShell(`echo '${this.config.dockerfile.content.join(`
161
- `)}' > ${l}`), console.log("- 正在创建upload.sh");
162
- const t = `${e}/${this.config.upload.name}`;
163
- await this.onShell(`touch ${t}`), await this.onShell(`echo '${this.config.upload.content.join(`
164
- `)}' > ${t}`), r();
170
+ const a = `${t}/${this.config.dockerfile.name}`;
171
+ await this.onShell(`touch ${a}`), await this.onShell(`echo '${this.config.dockerfile.content.join(`
172
+ `)}' > ${a}`), console.log("- 正在创建upload.sh");
173
+ const l = `${t}/${this.config.upload.name}`;
174
+ await this.onShell(`touch ${l}`), await this.onShell(`echo '${this.config.upload.content.join(`
175
+ `)}' > ${l}`), n();
165
176
  }
166
177
  });
167
178
  });
@@ -171,48 +182,69 @@ exit
171
182
  * @param {*} sftp
172
183
  * @param {*} dir
173
184
  */
174
- upLoadProject(o, e) {
175
- o.readdir(e, async (r) => {
176
- if (r) {
177
- console.log("- 跳板机不存在项目, 开始创建项目"), this.projectInit(o, e).then(() => {
178
- console.log(`- ${this.config.namespace}项目创建成功`), this.upLoadProject(o, e);
179
- }).catch(() => {
180
- console.log("- 创建项目失败"), this.breakConnect();
185
+ upLoadProject(e, t) {
186
+ e.readdir(t, async (n) => {
187
+ if (n) {
188
+ console.log("- 跳板机不存在项目, 开始创建项目"), this.projectInit(e, t).then(() => {
189
+ console.log(`- ${this.config.namespace}项目创建成功`), this.upLoadProject(e, t);
190
+ }).catch((o) => {
191
+ console.log(`- 创建项目失败: ${o}`), this.breakConnect();
181
192
  });
182
193
  return;
183
194
  }
184
- console.log("- 开始上传文件");
185
- const i = `${e}/${this.config.dist.name}`;
186
- await this.onShell(`rm -rf ${i}`), await this.onShell(`mkdir ${i}`), this.config.fileDir || (console.log("- 请配置本地文件目录 fileDir"), this.breakConnect());
187
- const s = await this.getFilesInDirectory(this.config.fileDir);
188
- s.length === 0 && (console.log("- 待上传目录为空,请检查"), this.breakConnect());
189
- let l = 0;
190
- const t = (n, k = 0) => n.isDirectory ? this.onShell(`mkdir ${i}${n.remotePath}`) : new Promise((P, y) => {
195
+ const s = `${t}/${this.config.dist.name}`;
196
+ if (await this.onShell(`rm -rf ${s}`), await this.onShell(`mkdir ${s}`), !this.config.fileDir) {
197
+ console.log("- 请配置待上传文件目录 fileDir"), this.breakConnect();
198
+ return;
199
+ }
200
+ const { localFileList: i, dirList: a } = await this.getFilesInDirectory(this.config.fileDir);
201
+ if (i.length === 0) {
202
+ console.log("- 待上传目录没有获取到文件,请检查"), this.breakConnect();
203
+ return;
204
+ }
205
+ let l = 0, c = i.length;
206
+ const r = (o) => new Promise((u, g) => {
191
207
  try {
192
- const f = m.createReadStream(n.localPath), p = o.createWriteStream(`${e}/dist${n.remotePath}`);
193
- p.on("close", () => {
194
- this.progressBar(++l, k), P();
195
- }), p.on("error", (w) => {
196
- console.log(`- 文件 ${n.remotePath} 上传失败:${w}`), y(w), this.breakConnect();
197
- }), f.pipe(p);
198
- } catch (f) {
199
- y(f);
208
+ const h = b.createReadStream(o.localPath), w = e.createWriteStream(`${s}${o.remotePath}`);
209
+ w.on("close", () => {
210
+ this.progressBar(++l, c), u();
211
+ }), w.on("error", (S) => {
212
+ console.log(`- 文件 ${o.remotePath} 上传失败:${S}`), g(S), this.breakConnect();
213
+ }), h.pipe(w);
214
+ } catch (h) {
215
+ g(h);
200
216
  }
201
- }), d = s.filter((n) => n.isDirectory);
202
- await Promise.all(d.map((n) => t(n))), await new Promise((n) => {
217
+ }), P = (o, u) => {
218
+ let g = [];
219
+ for (let h = 0; h < o.length; h += u)
220
+ g.push(o.slice(h, h + u));
221
+ return g;
222
+ }, m = (o) => {
223
+ if (o.length === 0)
224
+ return Promise.resolve();
225
+ const u = o.shift();
226
+ return Promise.all(u.map((g) => r(g))).then(() => m(o));
227
+ }, d = (o) => {
228
+ if (o.length === 0)
229
+ return Promise.resolve();
230
+ let g = o.shift().map((h) => `${s}${h}`).join(" ");
231
+ return this.onShell(`mkdir -p ${g}`).then(() => d(o));
232
+ };
233
+ let $ = [...P(a, this.config.parallelDir || 20)];
234
+ console.log("- 开始创建目录"), await d($), console.log("- 创建目录完成"), await new Promise((o) => {
203
235
  setTimeout(() => {
204
- n();
236
+ o();
205
237
  }, 500);
206
238
  });
207
- const a = s.filter((n) => !n.isDirectory);
208
- await Promise.all(a.map((n) => t(n, a.length))), console.log("- 文件上传成功"), console.log("- 开始推送镜像");
209
- let c = `cd ${e} && sh ${this.config.upload.name}`;
210
- const $ = this.getArgv("--imageStore") || this.config.imageStore;
211
- c += ` ${$}`;
212
- const u = this.getArgv("--tmpName") || this.config.tmpName;
213
- c += ` ${u}`;
214
- const b = await this.onShell(c);
215
- console.log(b), console.log("- 镜像推送完成"), this.ssh2Conn.end();
239
+ let k = [...P(i, this.config.parallelFile || 50)];
240
+ console.log("- 开始上传文件"), await m(k), console.log("- 文件上传成功"), console.log("- 开始推送镜像");
241
+ let p = `cd ${t} && sh ${this.config.upload.name}`;
242
+ const C = this.getArgv("--imageStore") || this.config.imageStore;
243
+ p += ` ${C}`;
244
+ const j = this.getArgv("--tmpName") || this.config.tmpName;
245
+ p += ` ${j}`;
246
+ const D = await this.onShell(p);
247
+ console.log(D), console.log("- 镜像推送完成"), this.breakConnect();
216
248
  });
217
249
  }
218
250
  /**
@@ -220,50 +252,53 @@ exit
220
252
  * @param description 命令行开头的文字信息
221
253
  * @param bar_length 进度条的长度(单位:字符),默认设为 25
222
254
  */
223
- progressBar(o, e, r = 25) {
224
- let i = (o / e).toFixed(4), s = Math.floor(i * r), l = "";
225
- for (let a = 0; a < s; a++)
226
- l += "█";
227
- let t = "";
228
- for (let a = 0; a < r - s; a++)
229
- t += "░";
230
- let d = `- 上传进度: ${l}${t} ${(100 * i).toFixed(2)}% (${o}/${e})`;
231
- j(d), o == e && console.log("");
255
+ progressBar(e, t, n = 25) {
256
+ let s = (e / t).toFixed(4), i = Math.floor(s * n), a = "";
257
+ for (let r = 0; r < i; r++)
258
+ a += "█";
259
+ let l = "";
260
+ for (let r = 0; r < n - i; r++)
261
+ l += "░";
262
+ let c = `- 文件上传进度: ${a}${l} (${e}/${t}) ${(100 * s).toFixed(2)}%`;
263
+ x(c), e == t && console.log("");
232
264
  }
233
265
  /**
234
- * 获取目录下所有文件
266
+ * 获取目录下所有文件并分类
235
267
  * @param {*} dir
236
268
  */
237
- getFilesInDirectory(o) {
238
- const e = [];
239
- return new Promise((r, i) => {
240
- const s = (l, t) => {
241
- m.readdirSync(l).forEach((a) => {
242
- const c = D.join(l, a), u = m.statSync(c).isDirectory();
243
- e.push({
244
- isDirectory: u,
245
- localPath: c,
269
+ getFilesInDirectory(e) {
270
+ const t = [], n = [];
271
+ return new Promise((s, i) => {
272
+ const a = (l, c, r) => {
273
+ const P = b.readdirSync(l);
274
+ let m = !1;
275
+ P.forEach((d) => {
276
+ const $ = F.join(l, d), p = b.statSync($).isDirectory();
277
+ p ? m = !0 : t.push({
278
+ localPath: $,
246
279
  // 本地路径
247
- remotePath: `${t}${a}`
280
+ remotePath: `${c}${d}`,
248
281
  // 远程路径
249
- }), u && s(c, `${t}${a}/`);
250
- });
282
+ level: r
283
+ // 文件或目录层级
284
+ }), p && a($, `${c}${d}/`, r + 1);
285
+ }), m || n.push(c);
251
286
  };
252
- s(o, "/"), r(e);
287
+ a(e, "/", 0), s({
288
+ localFileList: t,
289
+ dirList: n
290
+ });
253
291
  });
254
292
  }
255
293
  /**
256
294
  * 获取命令行参数
257
295
  * @param { string } name
258
296
  */
259
- getArgv(o) {
260
- const e = process.argv;
261
- let r;
262
- return e.forEach((i) => {
263
- i.indexOf(o) > -1 && (r = i.split("=")[1] || "");
264
- }), r;
297
+ getArgv(e) {
298
+ const t = process.argv;
299
+ let n;
300
+ return t.forEach((s) => {
301
+ s.indexOf(e) > -1 && (n = s.split("=")[1] || "");
302
+ }), n;
265
303
  }
266
- }
267
- export {
268
- F as default
269
304
  };
@@ -1,7 +1,7 @@
1
- (function(h,r){typeof exports=="object"&&typeof module<"u"?module.exports=r():typeof define=="function"&&define.amd?define(r):(h=typeof globalThis<"u"?globalThis:h||self,h["yj-deploy"]=r())})(this,function(){"use strict";var D=Object.defineProperty;var v=(h,r,g)=>r in h?D(h,r,{enumerable:!0,configurable:!0,writable:!0,value:g}):h[r]=g;var u=(h,r,g)=>(v(h,typeof r!="symbol"?r+"":r,g),g);const{stdout:h}=require("single-line-log"),r=require("path"),g=require("fs"),{Client:k}=require("ssh2");class P{constructor(e){u(this,"config",{host:"",port:"",username:"",password:"",namespace:"",imageStore:"dev-images",tmpName:"dev",delay:0,fileDir:"",rootDir:"/home/yjweb",dockerfile:{name:"Dockerfile",content:["FROM harbor.yunjingtech.cn:30002/yj-base/nginx:latest","RUN rm -rf /usr/share/nginx/html/*","COPY dist /usr/share/nginx/html/"]},upload:{name:"upload.sh",content:["#!/bin/sh","tag=`basename \\`pwd\\``:$2-`date +%Y%m%d`",'echo "- 镜像仓库: $1"','echo "- 镜像环境: $2"',"docker build -t harbor.yunjingtech.cn:30002/$1/$tag .","docker push harbor.yunjingtech.cn:30002/$1/$tag","echo - 镜像地址: harbor.yunjingtech.cn:30002/$1/$tag"]},dist:{name:"dist"}});u(this,"ssh2Conn",null);u(this,"uploading",!1);u(this,"trim",null);return this.config=Object.assign(this.config,e),{name:"yj-deploy",isPut:this.isPut.bind(this),upload:this.upload.bind(this),apply:this.apply.bind(this),closeBundle:this.isPut.bind(this)}}apply(e){return e&&e.hooks&&e.hooks.done&&e.hooks.done.tap("yj-deploy",()=>{this.isPut()}),"build"}isPut(){this.getArgv("--deploy")!=null&&(clearTimeout(this.trim),this.trim=setTimeout(()=>{this.upload()},this.config.delay||0))}upload(){if(console.log("-------------- deploy-start --------------"),!this.config.host){console.log("- 请配置跳板机地址 host");return}if(!this.config.port){console.log("- 请配置跳板机端口 port");return}if(!this.config.username){console.log("- 请配置跳板机账号 username");return}if(!this.config.password){console.log("- 请配置跳板机密码 password");return}this.uploading||(this.ssh2Conn=new k,this.ssh2Conn.on("ready",()=>{console.log("- 跳板机连接成功!"),this.ssh2Conn.sftp(async(e,o)=>{e&&(console.log("- sftp连接失败"),this.breakConnect()),this.config.namespace||(console.log("- 请配置项目命名空间 namespace"),this.breakConnect()),this.uploading=!0,this.getArgv("--reset")!==void 0&&(console.log("- 重置项目"),await this.onShell(`rm -r ${this.config.rootDir}/${this.config.namespace}`)),this.upLoadProject(o,`${this.config.rootDir}/${this.config.namespace}`)})}).connect({host:this.config.host,port:this.config.port,username:this.config.username,password:this.config.password}),this.ssh2Conn.on("error",e=>{console.log(`- 连接失败: ${e}`),this.breakConnect()}),this.ssh2Conn.on("end",()=>{this.uploading=!1}))}breakConnect(){this.uploading=!1,console.log("- 已断开连接"),console.log("-------------- deploy-end --------------"),this.ssh2Conn.end()}onShell(e){return new Promise((o,l)=>{this.ssh2Conn.shell((i,n)=>{if(i){this.breakConnect();return}let c=[];n.on("close",()=>{o(c.toString())}).on("data",t=>{c.push(`
2
- `+t)}).stderr.on("data",t=>{console.log(`- 远程命令错误:
3
- `+t),this.ssh2Conn.end(),l(t)}),n.end(e+`
1
+ (function(h){typeof define=="function"&&define.amd?define(h):h()})(function(){"use strict";var x=Object.defineProperty;var F=(h,u,f)=>u in h?x(h,u,{enumerable:!0,configurable:!0,writable:!0,value:f}):h[u]=f;var w=(h,u,f)=>(F(h,typeof u!="symbol"?u+"":u,f),f);const{stdout:h}=require("single-line-log"),u=require("path"),f=require("fs"),{Client:j}=require("ssh2");module.exports=class{constructor(e){w(this,"config",{host:"",port:"",username:"",password:"",namespace:"",imageStore:"dev-images",tmpName:"dev",delay:0,fileDir:"",rootDir:"/home/yjweb",dockerfile:{name:"Dockerfile",content:["FROM harbor.yunjingtech.cn:30002/yj-base/nginx:latest","RUN rm -rf /usr/share/nginx/html/*","COPY dist /usr/share/nginx/html/"]},upload:{name:"upload.sh",content:["#!/bin/sh","tag=`basename \\`pwd\\``:$2-`date +%Y%m%d`",'echo "- 镜像仓库: $1"','echo "- 镜像环境: $2"',"docker build -t harbor.yunjingtech.cn:30002/$1/$tag .","docker push harbor.yunjingtech.cn:30002/$1/$tag","echo - 镜像地址: harbor.yunjingtech.cn:30002/$1/$tag"]},dist:{name:"dist"},parallelDir:20,parallelFile:50,allLog:!1});w(this,"ssh2Conn",null);w(this,"uploading",!1);w(this,"trim",null);return this.config=Object.assign(this.config,e),{name:"yj-deploy",isPut:this.isPut.bind(this),upload:this.upload.bind(this),apply:this.apply.bind(this),closeBundle:this.isPut.bind(this)}}apply(e){return e&&e.hooks&&e.hooks.done&&e.hooks.done.tap("yj-deploy",()=>{this.isPut()}),"build"}isPut(){this.getArgv("--deploy")!=null&&(clearTimeout(this.trim),this.trim=setTimeout(()=>{this.upload()},this.config.delay||0))}upload(){if(console.log("-------------- deploy-start --------------"),!this.config.host){console.log("- 请配置跳板机地址 host");return}if(!this.config.port){console.log("- 请配置跳板机端口 port");return}if(!this.config.username){console.log("- 请配置跳板机账号 username");return}if(!this.config.password){console.log("- 请配置跳板机密码 password");return}this.uploading||(this.ssh2Conn=new j,this.ssh2Conn.on("ready",()=>{console.log("- 跳板机连接成功!"),this.ssh2Conn.sftp(async(e,t)=>{e&&(console.log("- sftp连接失败"),this.breakConnect()),this.config.namespace||(console.log("- 请配置项目命名空间 namespace"),this.breakConnect()),this.uploading=!0,this.getArgv("--reset")!==void 0&&(console.log("- 重置项目"),await this.onShell(`rm -r ${this.config.rootDir}/${this.config.namespace}`)),this.upLoadProject(t,`${this.config.rootDir}/${this.config.namespace}`)})}).connect({host:this.config.host,port:this.config.port,username:this.config.username,password:this.config.password}),this.ssh2Conn.on("error",e=>{console.log(`- 连接失败: ${e}`),this.breakConnect()}),this.ssh2Conn.on("end",()=>{this.uploading=!1}))}breakConnect(){this.uploading=!1,console.log("- 已断开连接"),console.log("-------------- deploy-end --------------"),this.ssh2Conn.end()}onShell(e){const t=this.getArgv("--allLog")!==void 0;return new Promise((s,n)=>{this.ssh2Conn.shell((i,a)=>{if(i){console.log("- 远程命令错误:"+i,e),this.breakConnect(),n(i);return}let l=[];a.on("close",()=>{s(l.toString())}).on("data",c=>{let r=c.toString();(t||this.config.allLog||r.startsWith("- "))&&l.push(`
2
+ `+r)}).stderr.on("data",c=>{console.log(`- 远程命令错误:
3
+ `+c),this.breakConnect(),n(c)}),a.end(e+`
4
4
  exit
5
- `)})})}projectInit(e,o){return new Promise(async(l,i)=>{e.mkdir(o,async n=>{if(n)console.log(n),i();else{console.log("- 正在创建dockerfile");const c=`${o}/${this.config.dockerfile.name}`;await this.onShell(`touch ${c}`),await this.onShell(`echo '${this.config.dockerfile.content.join(`
6
- `)}' > ${c}`),console.log("- 正在创建upload.sh");const t=`${o}/${this.config.upload.name}`;await this.onShell(`touch ${t}`),await this.onShell(`echo '${this.config.upload.content.join(`
7
- `)}' > ${t}`),l()}})})}upLoadProject(e,o){e.readdir(o,async l=>{if(l){console.log("- 跳板机不存在项目, 开始创建项目"),this.projectInit(e,o).then(()=>{console.log(`- ${this.config.namespace}项目创建成功`),this.upLoadProject(e,o)}).catch(()=>{console.log("- 创建项目失败"),this.breakConnect()});return}console.log("- 开始上传文件");const i=`${o}/${this.config.dist.name}`;await this.onShell(`rm -rf ${i}`),await this.onShell(`mkdir ${i}`),this.config.fileDir||(console.log("- 请配置本地文件目录 fileDir"),this.breakConnect());const n=await this.getFilesInDirectory(this.config.fileDir);n.length===0&&(console.log("- 待上传目录为空,请检查"),this.breakConnect());let c=0;const t=(s,C=0)=>s.isDirectory?this.onShell(`mkdir ${i}${s.remotePath}`):new Promise((j,w)=>{try{const m=g.createReadStream(s.localPath),$=e.createWriteStream(`${o}/dist${s.remotePath}`);$.on("close",()=>{this.progressBar(++c,C),j()}),$.on("error",b=>{console.log(`- 文件 ${s.remotePath} 上传失败:${b}`),w(b),this.breakConnect()}),m.pipe($)}catch(m){w(m)}}),f=n.filter(s=>s.isDirectory);await Promise.all(f.map(s=>t(s))),await new Promise(s=>{setTimeout(()=>{s()},500)});const a=n.filter(s=>!s.isDirectory);await Promise.all(a.map(s=>t(s,a.length))),console.log("- 文件上传成功"),console.log("- 开始推送镜像");let d=`cd ${o} && sh ${this.config.upload.name}`;const y=this.getArgv("--imageStore")||this.config.imageStore;d+=` ${y}`;const p=this.getArgv("--tmpName")||this.config.tmpName;d+=` ${p}`;const S=await this.onShell(d);console.log(S),console.log("- 镜像推送完成"),this.ssh2Conn.end()})}progressBar(e,o,l=25){let i=(e/o).toFixed(4),n=Math.floor(i*l),c="";for(let a=0;a<n;a++)c+="█";let t="";for(let a=0;a<l-n;a++)t+="░";let f=`- 上传进度: ${c}${t} ${(100*i).toFixed(2)}% (${e}/${o})`;h(f),e==o&&console.log("")}getFilesInDirectory(e){const o=[];return new Promise((l,i)=>{const n=(c,t)=>{g.readdirSync(c).forEach(a=>{const d=r.join(c,a),p=g.statSync(d).isDirectory();o.push({isDirectory:p,localPath:d,remotePath:`${t}${a}`}),p&&n(d,`${t}${a}/`)})};n(e,"/"),l(o)})}getArgv(e){const o=process.argv;let l;return o.forEach(i=>{i.indexOf(e)>-1&&(l=i.split("=")[1]||"")}),l}}return P});
5
+ `)})})}projectInit(e,t){return new Promise(async(s,n)=>{e.mkdir(t,async i=>{if(i)n(i);else{console.log("- 正在创建dockerfile");const a=`${t}/${this.config.dockerfile.name}`;await this.onShell(`touch ${a}`),await this.onShell(`echo '${this.config.dockerfile.content.join(`
6
+ `)}' > ${a}`),console.log("- 正在创建upload.sh");const l=`${t}/${this.config.upload.name}`;await this.onShell(`touch ${l}`),await this.onShell(`echo '${this.config.upload.content.join(`
7
+ `)}' > ${l}`),s()}})})}upLoadProject(e,t){e.readdir(t,async s=>{if(s){console.log("- 跳板机不存在项目, 开始创建项目"),this.projectInit(e,t).then(()=>{console.log(`- ${this.config.namespace}项目创建成功`),this.upLoadProject(e,t)}).catch(o=>{console.log(`- 创建项目失败: ${o}`),this.breakConnect()});return}const n=`${t}/${this.config.dist.name}`;if(await this.onShell(`rm -rf ${n}`),await this.onShell(`mkdir ${n}`),!this.config.fileDir){console.log("- 请配置待上传文件目录 fileDir"),this.breakConnect();return}const{localFileList:i,dirList:a}=await this.getFilesInDirectory(this.config.fileDir);if(i.length===0){console.log("- 待上传目录没有获取到文件,请检查"),this.breakConnect();return}let l=0,c=i.length;const r=o=>new Promise((p,d)=>{try{const g=f.createReadStream(o.localPath),k=e.createWriteStream(`${n}${o.remotePath}`);k.on("close",()=>{this.progressBar(++l,c),p()}),k.on("error",C=>{console.log(`- 文件 ${o.remotePath} 上传失败:${C}`),d(C),this.breakConnect()}),g.pipe(k)}catch(g){d(g)}}),b=(o,p)=>{let d=[];for(let g=0;g<o.length;g+=p)d.push(o.slice(g,g+p));return d},y=o=>{if(o.length===0)return Promise.resolve();const p=o.shift();return Promise.all(p.map(d=>r(d))).then(()=>y(o))},m=o=>{if(o.length===0)return Promise.resolve();let d=o.shift().map(g=>`${n}${g}`).join(" ");return this.onShell(`mkdir -p ${d}`).then(()=>m(o))};let P=[...b(a,this.config.parallelDir||20)];console.log("- 开始创建目录"),await m(P),console.log("- 创建目录完成"),await new Promise(o=>{setTimeout(()=>{o()},500)});let S=[...b(i,this.config.parallelFile||50)];console.log("- 开始上传文件"),await y(S),console.log("- 文件上传成功"),console.log("- 开始推送镜像");let $=`cd ${t} && sh ${this.config.upload.name}`;const D=this.getArgv("--imageStore")||this.config.imageStore;$+=` ${D}`;const L=this.getArgv("--tmpName")||this.config.tmpName;$+=` ${L}`;const v=await this.onShell($);console.log(v),console.log("- 镜像推送完成"),this.breakConnect()})}progressBar(e,t,s=25){let n=(e/t).toFixed(4),i=Math.floor(n*s),a="";for(let r=0;r<i;r++)a+="█";let l="";for(let r=0;r<s-i;r++)l+="░";let c=`- 文件上传进度: ${a}${l} (${e}/${t}) ${(100*n).toFixed(2)}%`;h(c),e==t&&console.log("")}getFilesInDirectory(e){const t=[],s=[];return new Promise((n,i)=>{const a=(l,c,r)=>{const b=f.readdirSync(l);let y=!1;b.forEach(m=>{const P=u.join(l,m),$=f.statSync(P).isDirectory();$?y=!0:t.push({localPath:P,remotePath:`${c}${m}`,level:r}),$&&a(P,`${c}${m}/`,r+1)}),y||s.push(c)};a(e,"/",0),n({localFileList:t,dirList:s})})}getArgv(e){const t=process.argv;let s;return t.forEach(n=>{n.indexOf(e)>-1&&(s=n.split("=")[1]||"")}),s}}});
package/package.json CHANGED
@@ -1,33 +1,33 @@
1
- {
2
- "name": "yj-deploy",
3
- "version": "0.0.2",
4
- "description": "ssh sftp",
5
- "scripts": {
6
- "build": "vite build"
7
- },
8
- "files": [
9
- "dist",
10
- "package.json",
11
- "README.md"
12
- ],
13
- "main": "./dist/yj-deploy.umd.js",
14
- "module": "./dist/yj-deploy.mjs",
15
- "exports": {
16
- ".": {
17
- "import": "./dist/yj-deploy.mjs",
18
- "require": "./dist/yj-deploy.umd.js"
19
- }
20
- },
21
- "author": "",
22
- "license": "ISC",
23
- "dependencies": {
24
- "single-line-log": "^1.1.2",
25
- "ssh2": "^1.15.0"
26
- },
27
- "devDependencies": {
28
- "vite": "4.5"
29
- },
30
- "publishConfig": {
31
- "registry": "https://registry.npmjs.org"
32
- }
33
- }
1
+ {
2
+ "name": "yj-deploy",
3
+ "version": "0.0.4",
4
+ "description": "ssh sftp",
5
+ "scripts": {
6
+ "build": "vite build"
7
+ },
8
+ "files": [
9
+ "dist",
10
+ "package.json",
11
+ "README.md"
12
+ ],
13
+ "main": "./dist/yj-deploy.umd.js",
14
+ "module": "./dist/yj-deploy.mjs",
15
+ "exports": {
16
+ ".": {
17
+ "import": "./dist/yj-deploy.mjs",
18
+ "require": "./dist/yj-deploy.umd.js"
19
+ }
20
+ },
21
+ "author": "",
22
+ "license": "ISC",
23
+ "dependencies": {
24
+ "single-line-log": "^1.1.2",
25
+ "ssh2": "^1.15.0"
26
+ },
27
+ "devDependencies": {
28
+ "vite": "4.5"
29
+ },
30
+ "publishConfig": {
31
+ "registry": "https://registry.npmjs.org"
32
+ }
33
+ }