yj-deploy 0.0.7 → 0.0.9

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
@@ -11,6 +11,8 @@
11
11
  - ✔ 支持灵活配置dockerfile,upload.sh等脚本内容
12
12
  - ✔ 支持多种方式配置 `镜像仓库` `镜像环境`等参数
13
13
  - ✔ 支持懒上传,仅上传新增部分文件,提升上传效率
14
+ - ✔ 支持失败自动重新上传,支持配置次数,防止网络不稳定可能导致上传失败
15
+ - ✔ 镜像推送成功支持自动重启服务,减少部署流程
14
16
  > 全程不需要连接跳板机做任何操作即可完成镜像推送
15
17
 
16
18
  ## 运行情况
@@ -74,8 +76,23 @@ const deploy = new yjDeploy({
74
76
  'echo "- 镜像环境: $2"',
75
77
  'docker build -t harbor.yunjingtech.cn:30002/$1/$tag .',
76
78
  'docker push harbor.yunjingtech.cn:30002/$1/$tag',
79
+ 'if [ $? -eq 0 ];then',
80
+ 'echo - 推送成功',
77
81
  'echo - 镜像地址: harbor.yunjingtech.cn:30002/$1/$tag',
78
- ]
82
+ 'else',
83
+ 'echo - 镜像推送失败,请重试或联系运维人员,如需查看全部docker日志,请在命令后添加 --allLog查看更多信息',
84
+ 'fi',
85
+ ],
86
+
87
+ // 自动重启服务的rancher配置
88
+ rancherConfig: {
89
+ token: '', // Rancher API 访问令牌 (必填)
90
+ namespace: '', // 服务所在的命名空间 (必填)
91
+ workload: '', // 服务名称 (必填)
92
+
93
+ // baseUrl: '', // Rancher API 基础地址
94
+ // clusterId: '', // Rancher 集群 ID
95
+ }
79
96
  },
80
97
 
81
98
  // 上传dist目录信息 (除非运维改变,否则不要配置)
@@ -89,7 +106,9 @@ const deploy = new yjDeploy({
89
106
 
90
107
  allLog: false, // 是否打印所有log信息,主要是包含推送镜像阶段的日志,如果报错可以打开此开关
91
108
 
92
- lazyUpload: false // 是否开启懒上传,针对小程序图片等仅需要部署新增部分文件的项目有奇效
109
+ lazyUpload: false, // 是否开启懒上传,针对小程序图片等仅需要部署新增部分文件的项目有奇效
110
+
111
+ rePushNum: 3, // 重新推送次数,为0代表不重新推送
93
112
  }
94
113
  ```
95
114
 
@@ -248,6 +267,15 @@ node uploader.js --tmpName=test
248
267
  如果小程序修改了相同路径和文件名的文件,会存在无法判断的情况(使用hash值能判断,但是成本过高),这种情况请关掉此选项,或使用 --reset 重置项目
249
268
  :::
250
269
 
270
+ ### --restart
271
+ `--restart` :是否需要自动重启服务,加上此命令,且配置了rancherConfig,将会自动重启服务
272
+
273
+ ::: warning
274
+ 仅对开发环境生效,并且需要先在rancher创建服务后方能成功
275
+ :::
276
+
277
+ <br />
278
+
251
279
 
252
280
  <br />
253
281
 
@@ -260,6 +288,12 @@ node uploader.js --tmpName=test
260
288
 
261
289
  ## 升级日志
262
290
 
291
+ ### v0.0.9
292
+ 支持自动重启服务,上传完后,可以根据配置自动重启rancher服务
293
+
294
+ ### v0.0.8
295
+ 支持判断是否推送成功,如果推送失败,则自动重试三次,可以手动配置重试次数
296
+
263
297
  ### v0.0.6
264
298
  支持懒上传文件,大幅提升上传速度
265
299
 
@@ -1,7 +1,7 @@
1
1
  var x = Object.defineProperty;
2
2
  var L = (f, e, t) => e in f ? x(f, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : f[e] = t;
3
3
  var $ = (f, e, t) => (L(f, typeof e != "symbol" ? e + "" : e, t), t);
4
- const { stdout: v } = require("single-line-log"), F = require("path"), k = require("fs"), { Client: A } = require("ssh2");
4
+ const { stdout: A } = require("single-line-log"), F = require("path"), C = require("fs"), { Client: N } = require("ssh2"), I = require("./rancher");
5
5
  module.exports = class {
6
6
  constructor(e) {
7
7
  $(this, "config", {
@@ -40,7 +40,12 @@ module.exports = class {
40
40
  'echo "- 镜像环境: $2"',
41
41
  "docker build -t harbor.yunjingtech.cn:30002/$1/$tag .",
42
42
  "docker push harbor.yunjingtech.cn:30002/$1/$tag",
43
- "echo - 镜像地址: harbor.yunjingtech.cn:30002/$1/$tag"
43
+ "if [ $? -eq 0 ];then",
44
+ "echo - 推送成功",
45
+ "echo - 镜像地址: harbor.yunjingtech.cn:30002/$1/$tag",
46
+ "else",
47
+ "echo - 镜像推送失败,请重试或联系运维人员,如需查看全部docker日志,请在命令后添加 --allLog查看更多信息",
48
+ "fi"
44
49
  ]
45
50
  },
46
51
  // 上传dist目录信息
@@ -53,8 +58,21 @@ module.exports = class {
53
58
  // 文件并行上传数量,如果报错,可以减少此配置
54
59
  allLog: !1,
55
60
  // 是否显示全部日志
56
- lazyUpload: !1
61
+ lazyUpload: !1,
57
62
  // 是否开启懒上传
63
+ rePushNum: 3,
64
+ // 重新推送次数,为0代表不重新推送
65
+ // 仅能通过命令启用
66
+ rancherConfig: {
67
+ // baseUrl: '', // Rancher API 基础地址
68
+ // clusterId: '', // Rancher 集群 ID
69
+ token: "",
70
+ // Rancher API 访问令牌
71
+ namespace: "",
72
+ // 服务所在的命名空间
73
+ workload: ""
74
+ // 工作负载名称
75
+ }
58
76
  });
59
77
  $(this, "ssh2Conn", null);
60
78
  // 上传状态
@@ -107,7 +125,7 @@ module.exports = class {
107
125
  console.log("- 请配置跳板机密码 password");
108
126
  return;
109
127
  }
110
- this.uploading || (this.ssh2Conn = new A(), this.ssh2Conn.on("ready", () => {
128
+ this.uploading || (this.ssh2Conn = new N(), this.ssh2Conn.on("ready", () => {
111
129
  console.log("- 跳板机连接成功!"), this.ssh2Conn.sftp(async (e, t) => {
112
130
  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}`);
113
131
  });
@@ -135,23 +153,23 @@ module.exports = class {
135
153
  */
136
154
  onShell(e) {
137
155
  const t = this.getArgv("--allLog") !== void 0;
138
- return new Promise((i, s) => {
139
- this.ssh2Conn.shell((n, l) => {
140
- if (n) {
141
- console.log("- 远程命令错误:" + n, e), this.breakConnect(), s(n);
156
+ return new Promise((o, r) => {
157
+ this.ssh2Conn.shell((l, s) => {
158
+ if (l) {
159
+ console.log("- 远程命令错误:" + l, e), this.breakConnect(), r(l);
142
160
  return;
143
161
  }
144
- let r = [];
145
- l.on("close", () => {
146
- i(r.toString());
162
+ let i = [];
163
+ s.on("close", () => {
164
+ o(i.toString());
147
165
  }).on("data", (c) => {
148
166
  let a = c.toString();
149
- (t || this.config.allLog || a.startsWith("- ")) && r.push(`
167
+ (t || this.config.allLog || a.startsWith("- ")) && i.push(`
150
168
  ` + a);
151
169
  }).stderr.on("data", (c) => {
152
170
  console.log(`- 远程命令错误:
153
- ` + c), this.breakConnect(), s(c);
154
- }), l.end(e + `
171
+ ` + c), this.breakConnect(), r(c);
172
+ }), s.end(e + `
155
173
  exit
156
174
  `);
157
175
  });
@@ -163,18 +181,18 @@ exit
163
181
  * @param {*} dir
164
182
  */
165
183
  projectInit(e, t) {
166
- return new Promise(async (i, s) => {
167
- e.mkdir(t, async (n) => {
168
- if (n)
169
- s(n);
184
+ return new Promise(async (o, r) => {
185
+ e.mkdir(t, async (l) => {
186
+ if (l)
187
+ r(l);
170
188
  else {
171
189
  console.log("- 正在创建dockerfile");
172
- const l = `${t}/${this.config.dockerfile.name}`;
173
- await this.onShell(`touch ${l}`), await this.onShell(`echo '${this.config.dockerfile.content.join(`
174
- `)}' > ${l}`), console.log("- 正在创建upload.sh");
175
- const r = `${t}/${this.config.upload.name}`;
176
- await this.onShell(`touch ${r}`), await this.onShell(`echo '${this.config.upload.content.join(`
177
- `)}' > ${r}`), i();
190
+ const s = `${t}/${this.config.dockerfile.name}`;
191
+ await this.onShell(`touch ${s}`), await this.onShell(`echo '${this.config.dockerfile.content.join(`
192
+ `)}' > ${s}`), console.log("- 正在创建upload.sh");
193
+ const i = `${t}/${this.config.upload.name}`;
194
+ await this.onShell(`touch ${i}`), await this.onShell(`echo '${this.config.upload.content.join(`
195
+ `)}' > ${i}`), o();
178
196
  }
179
197
  });
180
198
  });
@@ -186,9 +204,9 @@ exit
186
204
  * @returns
187
205
  */
188
206
  checkRemoteFile(e, t) {
189
- return new Promise((i) => {
190
- e.stat(t, (s) => {
191
- i(!s);
207
+ return new Promise((o) => {
208
+ e.stat(t, (r) => {
209
+ o(!r);
192
210
  });
193
211
  });
194
212
  }
@@ -198,88 +216,97 @@ exit
198
216
  * @param {*} dir
199
217
  */
200
218
  upLoadProject(e, t) {
201
- e.readdir(t, async (i) => {
202
- if (i) {
219
+ let o = 0;
220
+ typeof this.config.rePushNum == "number" && this.config.rePushNum > 0 && (o = this.config.rePushNum), e.readdir(t, async (r) => {
221
+ if (r) {
203
222
  console.log("- 跳板机不存在项目, 开始创建项目"), this.projectInit(e, t).then(() => {
204
223
  console.log(`- ${this.config.namespace}项目创建成功`), this.upLoadProject(e, t);
205
- }).catch((o) => {
206
- console.log(`- 创建项目失败: ${o}`), this.breakConnect();
224
+ }).catch((n) => {
225
+ console.log(`- 创建项目失败: ${n}`), this.breakConnect();
207
226
  });
208
227
  return;
209
228
  }
210
- const s = this.getArgv("--lazyUpload") !== void 0 || this.config.lazyUpload, n = `${t}/${this.config.dist.name}`;
211
- if (s || await this.onShell(`rm -rf ${n}`), await this.onShell(`mkdir ${n}`), !this.config.fileDir) {
229
+ const l = this.getArgv("--lazyUpload") !== void 0 || this.config.lazyUpload, s = `${t}/${this.config.dist.name}`;
230
+ if (l || await this.onShell(`rm -rf ${s}`), await this.onShell(`mkdir ${s}`), !this.config.fileDir) {
212
231
  console.log("- 请配置待上传文件目录 fileDir"), this.breakConnect();
213
232
  return;
214
233
  }
215
- const { localFileList: l, dirList: r } = await this.getFilesInDirectory(this.config.fileDir);
216
- if (l.length === 0) {
234
+ const { localFileList: i, dirList: c } = await this.getFilesInDirectory(this.config.fileDir);
235
+ if (i.length === 0) {
217
236
  console.log("- 待上传目录没有获取到文件,请检查"), this.breakConnect();
218
237
  return;
219
238
  }
220
- let c = 0, a = l.length;
221
- const w = (o) => new Promise(async (u, g) => {
239
+ let a = 0, p = i.length;
240
+ const y = (n) => new Promise(async (u, g) => {
222
241
  try {
223
- if (s && await this.checkRemoteFile(e, `${n}${o.remotePath}`))
242
+ if (l && await this.checkRemoteFile(e, `${s}${n.remotePath}`))
224
243
  return u();
225
- const h = k.createReadStream(o.localPath), S = e.createWriteStream(`${n}${o.remotePath}`);
226
- S.on("close", () => {
227
- this.progressBar(++c, a), u();
228
- }), S.on("error", (b) => {
229
- console.log(`- 文件 ${o.remotePath} 上传失败:${b}`), g(b), this.breakConnect();
230
- }), h.pipe(S);
244
+ const h = C.createReadStream(n.localPath), k = e.createWriteStream(`${s}${n.remotePath}`);
245
+ k.on("close", () => {
246
+ this.progressBar(++a, p), u();
247
+ }), k.on("error", (b) => {
248
+ console.log(`- 文件 ${n.remotePath} 上传失败:${b}`), g(b), this.breakConnect();
249
+ }), h.pipe(k);
231
250
  } catch (h) {
232
251
  g(h);
233
252
  }
234
- }), m = (o, u) => {
253
+ }), d = (n, u) => {
235
254
  let g = [];
236
- for (let h = 0; h < o.length; h += u)
237
- g.push(o.slice(h, h + u));
255
+ for (let h = 0; h < n.length; h += u)
256
+ g.push(n.slice(h, h + u));
238
257
  return g;
239
- }, d = (o) => {
240
- if (o.length === 0)
258
+ }, m = (n) => {
259
+ if (n.length === 0)
241
260
  return Promise.resolve();
242
- const u = o.shift();
243
- return Promise.all(u.map((g) => w(g))).then(() => d(o));
244
- }, p = (o) => {
245
- if (o.length === 0)
261
+ const u = n.shift();
262
+ return Promise.all(u.map((g) => y(g))).then(() => m(n));
263
+ }, P = (n) => {
264
+ if (n.length === 0)
246
265
  return Promise.resolve();
247
- let g = o.shift().map((h) => `${n}${h}`).join(" ");
248
- return this.onShell(`mkdir -p ${g}`).then(() => p(o));
266
+ let g = n.shift().map((h) => `${s}${h}`).join(" ");
267
+ return this.onShell(`mkdir -p ${g}`).then(() => P(n));
249
268
  };
250
- let C = [...m(r, this.config.parallelDir || 20)];
251
- await p(C), console.log("- 创建目录完成"), await new Promise((o) => {
269
+ let w = [...d(c, this.config.parallelDir || 20)];
270
+ await P(w), console.log("- 创建目录完成"), await new Promise((n) => {
252
271
  setTimeout(() => {
253
- o();
272
+ n();
254
273
  }, 500);
255
274
  });
256
- let y = [...m(l, this.config.parallelFile || 50)];
257
- await d(y), s && (this.progressBar(a, a), console.log("\x1B[32m%s\x1B[0m", `- 已开启懒上传,本次共上传 ${c} 个文件`)), console.log("- 文件上传成功"), console.log("- 开始推送镜像");
258
- let P = `cd ${t} && sh ${this.config.upload.name}`;
275
+ let D = [...d(i, this.config.parallelFile || 50)];
276
+ await m(D), l && (this.progressBar(p, p), console.log("\x1B[32m%s\x1B[0m", `- 已开启懒上传,本次共上传 ${a} 个文件`)), console.log("- 文件上传成功"), console.log("- 开始推送镜像");
277
+ let S = `cd ${t} && sh ${this.config.upload.name}`;
259
278
  if (await this.isNewShell(e, t)) {
260
- const o = this.getArgv("--imageStore") || this.config.imageStore;
261
- P += ` ${o}`;
279
+ const n = this.getArgv("--imageStore") || this.config.imageStore;
280
+ S += ` ${n}`;
262
281
  } else
263
282
  console.log(`
264
283
  - warning 检测到当前脚本为手动创建,不支持镜像仓库配置,imageStore将失效`), console.log("- warning 如需支持imageStore参数,请在命令行后添加 --reset 重新生成脚本");
265
- const j = this.getArgv("--tmpName") || this.config.tmpName;
266
- P += ` ${j}`;
267
- const D = await this.onShell(P);
268
- console.log(D), console.log("- 镜像推送完成"), this.breakConnect();
284
+ const v = this.getArgv("--tmpName") || this.config.tmpName;
285
+ S += ` ${v}`;
286
+ const j = Array.from(new Array(o + 1)).map(() => this.onShell(S));
287
+ await this.pushMirrorImage(j, j.length), this.breakConnect();
269
288
  });
270
289
  }
290
+ // 推送镜像
291
+ async pushMirrorImage(e, t) {
292
+ if (e.length === 0)
293
+ return console.log("- 镜像推送失败,请添加 --allLog 查看全部日志"), null;
294
+ const o = await e[0];
295
+ return o.includes("- 镜像推送失败") ? (console.log(o), e.length > 1 && console.log(`- 正在尝试第 ${t - e.length + 1} 次推送...`), this.pushMirrorImage(e.slice(1), t)) : (console.log(o), console.log("- 镜像推送结束"), this.getArgv("--restart") !== void 0 && await I(o, this.config.rancherConfig), o);
296
+ }
271
297
  /**
272
298
  * 获取脚本内容是不是自动创建的脚本
273
299
  * 兼容用户手动根据运维文档创建的推送脚本,因参数不同可能报错的问题
274
300
  * 有 $2代表是自动创建的版本,否则老版本
275
301
  */
276
302
  async isNewShell(e, t) {
277
- return new Promise((i, s) => {
278
- e.readFile(`${t}/${this.config.upload.name}`, (n, l) => {
279
- if (n)
280
- return s(!1);
281
- const r = l.toString();
282
- i(r.includes("$2"));
303
+ return new Promise((o, r) => {
304
+ e.readFile(`${t}/${this.config.upload.name}`, (l, s) => {
305
+ if (l)
306
+ return r(!1);
307
+ const i = s.toString();
308
+ i.includes("- 镜像推送失败") || console.log(`
309
+ - warning 检测到当前脚本可能不是最新版本,推送失败可能不会提示,且没有失败自动重试功能,请在命令后添加 --reset 重新生成脚本`), o(i.includes("$2"));
283
310
  });
284
311
  });
285
312
  }
@@ -288,41 +315,41 @@ exit
288
315
  * @param description 命令行开头的文字信息
289
316
  * @param bar_length 进度条的长度(单位:字符),默认设为 25
290
317
  */
291
- progressBar(e, t, i = 25) {
292
- let s = (e / t).toFixed(4), n = Math.floor(s * i), l = "";
293
- for (let a = 0; a < n; a++)
294
- l += "█";
295
- let r = "";
296
- for (let a = 0; a < i - n; a++)
297
- r += "░";
298
- let c = `- 文件上传进度: ${l}${r} (${e}/${t}) ${(100 * s).toFixed(2)}%`;
299
- v(c), e == t && console.log("");
318
+ progressBar(e, t, o = 25) {
319
+ let r = (e / t).toFixed(4), l = Math.floor(r * o), s = "";
320
+ for (let a = 0; a < l; a++)
321
+ s += "█";
322
+ let i = "";
323
+ for (let a = 0; a < o - l; a++)
324
+ i += "░";
325
+ let c = `- 文件上传进度: ${s}${i} (${e}/${t}) ${(100 * r).toFixed(2)}%`;
326
+ A(c), e == t && console.log("");
300
327
  }
301
328
  /**
302
329
  * 获取目录下所有文件并分类
303
330
  * @param {*} dir
304
331
  */
305
332
  getFilesInDirectory(e) {
306
- const t = [], i = [];
307
- return new Promise((s, n) => {
308
- const l = (r, c, a) => {
309
- const w = k.readdirSync(r);
310
- let m = !1;
311
- w.forEach((d) => {
312
- const p = F.join(r, d), y = k.statSync(p).isDirectory();
313
- y ? m = !0 : t.push({
314
- localPath: p,
333
+ const t = [], o = [];
334
+ return new Promise((r, l) => {
335
+ const s = (i, c, a) => {
336
+ const p = C.readdirSync(i);
337
+ let y = !1;
338
+ p.forEach((d) => {
339
+ const m = F.join(i, d), w = C.statSync(m).isDirectory();
340
+ w ? y = !0 : t.push({
341
+ localPath: m,
315
342
  // 本地路径
316
343
  remotePath: `${c}${d}`,
317
344
  // 远程路径
318
345
  level: a
319
346
  // 文件或目录层级
320
- }), y && l(p, `${c}${d}/`, a + 1);
321
- }), m || i.push(c);
347
+ }), w && s(m, `${c}${d}/`, a + 1);
348
+ }), y || o.push(c);
322
349
  };
323
- l(e, "/", 0), s({
350
+ s(e, "/", 0), r({
324
351
  localFileList: t,
325
- dirList: i
352
+ dirList: o
326
353
  });
327
354
  });
328
355
  }
@@ -332,9 +359,9 @@ exit
332
359
  */
333
360
  getArgv(e) {
334
361
  const t = process.argv;
335
- let i;
336
- return t.forEach((s) => {
337
- s.indexOf(e) > -1 && (i = s.split("=")[1] || "");
338
- }), i;
362
+ let o;
363
+ return t.forEach((r) => {
364
+ r.indexOf(e) > -1 && (o = r.split("=")[1] || "");
365
+ }), o;
339
366
  }
340
367
  };
@@ -1,8 +1,9 @@
1
- (function(h){typeof define=="function"&&define.amd?define(h):h()})(function(){"use strict";var v=Object.defineProperty;var F=(h,f,u)=>f in h?v(h,f,{enumerable:!0,configurable:!0,writable:!0,value:u}):h[f]=u;var w=(h,f,u)=>(F(h,typeof f!="symbol"?f+"":f,u),u);const{stdout:h}=require("single-line-log"),f=require("path"),u=require("fs"),{Client:D}=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,lazyUpload:!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 D,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((n,s)=>{this.ssh2Conn.shell((i,l)=>{if(i){console.log("- 远程命令错误:"+i,e),this.breakConnect(),s(i);return}let r=[];l.on("close",()=>{n(r.toString())}).on("data",c=>{let a=c.toString();(t||this.config.allLog||a.startsWith("- "))&&r.push(`
1
+ (function(h){typeof define=="function"&&define.amd?define(h):h()})(function(){"use strict";var F=Object.defineProperty;var N=(h,u,f)=>u in h?F(h,u,{enumerable:!0,configurable:!0,writable:!0,value:f}):h[u]=f;var w=(h,u,f)=>(N(h,typeof u!="symbol"?u+"":u,f),f);const{stdout:h}=require("single-line-log"),u=require("path"),f=require("fs"),{Client:v}=require("ssh2"),x=require("./rancher");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","if [ $? -eq 0 ];then","echo - 推送成功","echo - 镜像地址: harbor.yunjingtech.cn:30002/$1/$tag","else","echo - 镜像推送失败,请重试或联系运维人员,如需查看全部docker日志,请在命令后添加 --allLog查看更多信息","fi"]},dist:{name:"dist"},parallelDir:20,parallelFile:50,allLog:!1,lazyUpload:!1,rePushNum:3,rancherConfig:{token:"",namespace:"",workload:""}});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 v,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((o,r)=>{this.ssh2Conn.shell((l,i)=>{if(l){console.log("- 远程命令错误:"+l,e),this.breakConnect(),r(l);return}let s=[];i.on("close",()=>{o(s.toString())}).on("data",c=>{let a=c.toString();(t||this.config.allLog||a.startsWith("- "))&&s.push(`
2
2
  `+a)}).stderr.on("data",c=>{console.log(`- 远程命令错误:
3
- `+c),this.breakConnect(),s(c)}),l.end(e+`
3
+ `+c),this.breakConnect(),r(c)}),i.end(e+`
4
4
  exit
5
- `)})})}projectInit(e,t){return new Promise(async(n,s)=>{e.mkdir(t,async i=>{if(i)s(i);else{console.log("- 正在创建dockerfile");const l=`${t}/${this.config.dockerfile.name}`;await this.onShell(`touch ${l}`),await this.onShell(`echo '${this.config.dockerfile.content.join(`
6
- `)}' > ${l}`),console.log("- 正在创建upload.sh");const r=`${t}/${this.config.upload.name}`;await this.onShell(`touch ${r}`),await this.onShell(`echo '${this.config.upload.content.join(`
7
- `)}' > ${r}`),n()}})})}checkRemoteFile(e,t){return new Promise(n=>{e.stat(t,s=>{n(!s)})})}upLoadProject(e,t){e.readdir(t,async n=>{if(n){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 s=this.getArgv("--lazyUpload")!==void 0||this.config.lazyUpload,i=`${t}/${this.config.dist.name}`;if(s||await this.onShell(`rm -rf ${i}`),await this.onShell(`mkdir ${i}`),!this.config.fileDir){console.log("- 请配置待上传文件目录 fileDir"),this.breakConnect();return}const{localFileList:l,dirList:r}=await this.getFilesInDirectory(this.config.fileDir);if(l.length===0){console.log("- 待上传目录没有获取到文件,请检查"),this.breakConnect();return}let c=0,a=l.length;const S=o=>new Promise(async(p,d)=>{try{if(s&&await this.checkRemoteFile(e,`${i}${o.remotePath}`))return p();const g=u.createReadStream(o.localPath),k=e.createWriteStream(`${i}${o.remotePath}`);k.on("close",()=>{this.progressBar(++c,a),p()}),k.on("error",C=>{console.log(`- 文件 ${o.remotePath} 上传失败:${C}`),d(C),this.breakConnect()}),g.pipe(k)}catch(g){d(g)}}),y=(o,p)=>{let d=[];for(let g=0;g<o.length;g+=p)d.push(o.slice(g,g+p));return d},m=o=>{if(o.length===0)return Promise.resolve();const p=o.shift();return Promise.all(p.map(d=>S(d))).then(()=>m(o))},$=o=>{if(o.length===0)return Promise.resolve();let d=o.shift().map(g=>`${i}${g}`).join(" ");return this.onShell(`mkdir -p ${d}`).then(()=>$(o))};let j=[...y(r,this.config.parallelDir||20)];await $(j),console.log("- 创建目录完成"),await new Promise(o=>{setTimeout(()=>{o()},500)});let P=[...y(l,this.config.parallelFile||50)];await m(P),s&&(this.progressBar(a,a),console.log("\x1B[32m%s\x1B[0m",`- 已开启懒上传,本次共上传 ${c} 个文件`)),console.log("- 文件上传成功"),console.log("- 开始推送镜像");let b=`cd ${t} && sh ${this.config.upload.name}`;if(await this.isNewShell(e,t)){const o=this.getArgv("--imageStore")||this.config.imageStore;b+=` ${o}`}else console.log(`
8
- - warning 检测到当前脚本为手动创建,不支持镜像仓库配置,imageStore将失效`),console.log("- warning 如需支持imageStore参数,请在命令行后添加 --reset 重新生成脚本");const x=this.getArgv("--tmpName")||this.config.tmpName;b+=` ${x}`;const L=await this.onShell(b);console.log(L),console.log("- 镜像推送完成"),this.breakConnect()})}async isNewShell(e,t){return new Promise((n,s)=>{e.readFile(`${t}/${this.config.upload.name}`,(i,l)=>{if(i)return s(!1);const r=l.toString();n(r.includes("$2"))})})}progressBar(e,t,n=25){let s=(e/t).toFixed(4),i=Math.floor(s*n),l="";for(let a=0;a<i;a++)l+="█";let r="";for(let a=0;a<n-i;a++)r+="░";let c=`- 文件上传进度: ${l}${r} (${e}/${t}) ${(100*s).toFixed(2)}%`;h(c),e==t&&console.log("")}getFilesInDirectory(e){const t=[],n=[];return new Promise((s,i)=>{const l=(r,c,a)=>{const S=u.readdirSync(r);let y=!1;S.forEach(m=>{const $=f.join(r,m),P=u.statSync($).isDirectory();P?y=!0:t.push({localPath:$,remotePath:`${c}${m}`,level:a}),P&&l($,`${c}${m}/`,a+1)}),y||n.push(c)};l(e,"/",0),s({localFileList:t,dirList:n})})}getArgv(e){const t=process.argv;let n;return t.forEach(s=>{s.indexOf(e)>-1&&(n=s.split("=")[1]||"")}),n}}});
5
+ `)})})}projectInit(e,t){return new Promise(async(o,r)=>{e.mkdir(t,async l=>{if(l)r(l);else{console.log("- 正在创建dockerfile");const i=`${t}/${this.config.dockerfile.name}`;await this.onShell(`touch ${i}`),await this.onShell(`echo '${this.config.dockerfile.content.join(`
6
+ `)}' > ${i}`),console.log("- 正在创建upload.sh");const s=`${t}/${this.config.upload.name}`;await this.onShell(`touch ${s}`),await this.onShell(`echo '${this.config.upload.content.join(`
7
+ `)}' > ${s}`),o()}})})}checkRemoteFile(e,t){return new Promise(o=>{e.stat(t,r=>{o(!r)})})}upLoadProject(e,t){let o=0;typeof this.config.rePushNum=="number"&&this.config.rePushNum>0&&(o=this.config.rePushNum),e.readdir(t,async r=>{if(r){console.log("- 跳板机不存在项目, 开始创建项目"),this.projectInit(e,t).then(()=>{console.log(`- ${this.config.namespace}项目创建成功`),this.upLoadProject(e,t)}).catch(n=>{console.log(`- 创建项目失败: ${n}`),this.breakConnect()});return}const l=this.getArgv("--lazyUpload")!==void 0||this.config.lazyUpload,i=`${t}/${this.config.dist.name}`;if(l||await this.onShell(`rm -rf ${i}`),await this.onShell(`mkdir ${i}`),!this.config.fileDir){console.log("- 请配置待上传文件目录 fileDir"),this.breakConnect();return}const{localFileList:s,dirList:c}=await this.getFilesInDirectory(this.config.fileDir);if(s.length===0){console.log("- 待上传目录没有获取到文件,请检查"),this.breakConnect();return}let a=0,y=s.length;const P=n=>new Promise(async(m,d)=>{try{if(l&&await this.checkRemoteFile(e,`${i}${n.remotePath}`))return m();const g=f.createReadStream(n.localPath),C=e.createWriteStream(`${i}${n.remotePath}`);C.on("close",()=>{this.progressBar(++a,y),m()}),C.on("error",j=>{console.log(`- 文件 ${n.remotePath} 上传失败:${j}`),d(j),this.breakConnect()}),g.pipe(C)}catch(g){d(g)}}),p=(n,m)=>{let d=[];for(let g=0;g<n.length;g+=m)d.push(n.slice(g,g+m));return d},$=n=>{if(n.length===0)return Promise.resolve();const m=n.shift();return Promise.all(m.map(d=>P(d))).then(()=>$(n))},k=n=>{if(n.length===0)return Promise.resolve();let d=n.shift().map(g=>`${i}${g}`).join(" ");return this.onShell(`mkdir -p ${d}`).then(()=>k(n))};let S=[...p(c,this.config.parallelDir||20)];await k(S),console.log("- 创建目录完成"),await new Promise(n=>{setTimeout(()=>{n()},500)});let L=[...p(s,this.config.parallelFile||50)];await $(L),l&&(this.progressBar(y,y),console.log("\x1B[32m%s\x1B[0m",`- 已开启懒上传,本次共上传 ${a} 个文件`)),console.log("- 文件上传成功"),console.log("- 开始推送镜像");let b=`cd ${t} && sh ${this.config.upload.name}`;if(await this.isNewShell(e,t)){const n=this.getArgv("--imageStore")||this.config.imageStore;b+=` ${n}`}else console.log(`
8
+ - warning 检测到当前脚本为手动创建,不支持镜像仓库配置,imageStore将失效`),console.log("- warning 如需支持imageStore参数,请在命令行后添加 --reset 重新生成脚本");const A=this.getArgv("--tmpName")||this.config.tmpName;b+=` ${A}`;const D=Array.from(new Array(o+1)).map(()=>this.onShell(b));await this.pushMirrorImage(D,D.length),this.breakConnect()})}async pushMirrorImage(e,t){if(e.length===0)return console.log("- 镜像推送失败,请添加 --allLog 查看全部日志"),null;const o=await e[0];return o.includes("- 镜像推送失败")?(console.log(o),e.length>1&&console.log(`- 正在尝试第 ${t-e.length+1} 次推送...`),this.pushMirrorImage(e.slice(1),t)):(console.log(o),console.log("- 镜像推送结束"),this.getArgv("--restart")!==void 0&&await x(o,this.config.rancherConfig),o)}async isNewShell(e,t){return new Promise((o,r)=>{e.readFile(`${t}/${this.config.upload.name}`,(l,i)=>{if(l)return r(!1);const s=i.toString();s.includes("- 镜像推送失败")||console.log(`
9
+ - warning 检测到当前脚本可能不是最新版本,推送失败可能不会提示,且没有失败自动重试功能,请在命令后添加 --reset 重新生成脚本`),o(s.includes("$2"))})})}progressBar(e,t,o=25){let r=(e/t).toFixed(4),l=Math.floor(r*o),i="";for(let a=0;a<l;a++)i+="█";let s="";for(let a=0;a<o-l;a++)s+="░";let c=`- 文件上传进度: ${i}${s} (${e}/${t}) ${(100*r).toFixed(2)}%`;h(c),e==t&&console.log("")}getFilesInDirectory(e){const t=[],o=[];return new Promise((r,l)=>{const i=(s,c,a)=>{const y=f.readdirSync(s);let P=!1;y.forEach(p=>{const $=u.join(s,p),S=f.statSync($).isDirectory();S?P=!0:t.push({localPath:$,remotePath:`${c}${p}`,level:a}),S&&i($,`${c}${p}/`,a+1)}),P||o.push(c)};i(e,"/",0),r({localFileList:t,dirList:o})})}getArgv(e){const t=process.argv;let o;return t.forEach(r=>{r.indexOf(e)>-1&&(o=r.split("=")[1]||"")}),o}}});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yj-deploy",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "ssh sftp",
5
5
  "scripts": {
6
6
  "build": "vite build"
@@ -25,6 +25,7 @@
25
25
  "ssh2": "^1.15.0"
26
26
  },
27
27
  "devDependencies": {
28
+ "axios": "^1.13.5",
28
29
  "vite": "4.5"
29
30
  },
30
31
  "publishConfig": {