yj-deploy 0.0.4 → 0.0.6

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
@@ -86,7 +86,9 @@ const deploy = new yjDeploy({
86
86
 
87
87
  parallelFile: 50, // 文件并行上传数量,如果在上传文件阶段报错,可以减少此配置
88
88
 
89
- allLog: false // 是否打印所有log信息,主要是包含推送镜像阶段的日志,如果报错可以打开此开关
89
+ allLog: false, // 是否打印所有log信息,主要是包含推送镜像阶段的日志,如果报错可以打开此开关
90
+
91
+ lazyUpload: false // 是否开启懒上传,针对小程序图片等仅需要部署新增部分文件的项目有奇效
90
92
  }
91
93
  ```
92
94
 
@@ -236,6 +238,16 @@ node uploader.js --tmpName=test
236
238
  ### --allLog
237
239
  `--allLog` :是否打印所有log信息,主要是包含推送镜像阶段的日志,如果报错可以打开此开关
238
240
 
241
+ <br />
242
+
243
+ ### --lazyUpload
244
+ `--lazyUpload` :是否开启懒上传,如果开启,则不会上传全部文件,仅上传新增的文件。针对小程序图片等仅需要部署新增部分文件的项目有奇效
245
+
246
+ ::: warning
247
+ 如果小程序修改了相同路径和文件名的文件,会存在无法判断的情况(使用hash值能判断,但是成本过高),这种情况请关掉此选项,或使用 --reset 重置项目
248
+ :::
249
+
250
+
239
251
  <br />
240
252
 
241
253
  # 最后
@@ -243,4 +255,18 @@ node uploader.js --tmpName=test
243
255
 
244
256
  <br />
245
257
 
246
- 项目已提交到git:https://gitlab.yunjingtech.cn:10010/frontend/yj-deploy
258
+ 项目已提交到git:https://gitlab.yunjingtech.cn:10010/frontend/yj-deploy
259
+
260
+ ## 升级日志
261
+
262
+ ### v0.0.6
263
+ 支持懒上传文件,大幅提升上传速度
264
+
265
+ ### v0.0.5
266
+ 兼容根据运维文档手动创建的脚本推送镜像
267
+
268
+ ### v0.0.4
269
+ 解决因为并行上传文件太多导致上传失败的问题,支持动态配置并行上传数据量
270
+
271
+ ### v0.0.3
272
+ 解决文件夹嵌套太深,可能会导致上传文件时文件夹还没有创建导致报错的问题
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yj-deploy",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "ssh sftp",
5
5
  "scripts": {
6
6
  "build": "vite build"
@@ -1,304 +0,0 @@
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
- host: "",
9
- port: "",
10
- username: "",
11
- password: "",
12
- namespace: "",
13
- // 项目命名空间,等同于镜像地址目录名称
14
- imageStore: "dev-images",
15
- // 镜像仓库
16
- tmpName: "dev",
17
- // 镜像环境
18
- delay: 0,
19
- // 延迟上传时间
20
- // 本地文件目录(必填)
21
- fileDir: "",
22
- // 远程sftp服务根目录
23
- rootDir: "/home/yjweb",
24
- // dockerfile文件信息
25
- dockerfile: {
26
- name: "Dockerfile",
27
- content: [
28
- "FROM harbor.yunjingtech.cn:30002/yj-base/nginx:latest",
29
- "RUN rm -rf /usr/share/nginx/html/*",
30
- "COPY dist /usr/share/nginx/html/"
31
- ]
32
- },
33
- // upload.sh文件信息
34
- upload: {
35
- name: "upload.sh",
36
- content: [
37
- "#!/bin/sh",
38
- "tag=`basename \\`pwd\\``:$2-`date +%Y%m%d`",
39
- 'echo "- 镜像仓库: $1"',
40
- 'echo "- 镜像环境: $2"',
41
- "docker build -t harbor.yunjingtech.cn:30002/$1/$tag .",
42
- "docker push harbor.yunjingtech.cn:30002/$1/$tag",
43
- "echo - 镜像地址: harbor.yunjingtech.cn:30002/$1/$tag"
44
- ]
45
- },
46
- // 上传dist目录信息
47
- dist: {
48
- name: "dist"
49
- },
50
- parallelDir: 20,
51
- // 文件夹并行创建数量,如果报错,可以减少此配置
52
- parallelFile: 50,
53
- // 文件并行上传数量,如果报错,可以减少此配置
54
- allLog: !1
55
- // 是否显示全部日志
56
- });
57
- y(this, "ssh2Conn", null);
58
- // 上传状态
59
- y(this, "uploading", !1);
60
- // 定时器
61
- y(this, "trim", null);
62
- return this.config = Object.assign(this.config, e), {
63
- name: "yj-deploy",
64
- isPut: this.isPut.bind(this),
65
- upload: this.upload.bind(this),
66
- // webpack钩子
67
- apply: this.apply.bind(this),
68
- // vite上传钩子
69
- closeBundle: this.isPut.bind(this)
70
- };
71
- }
72
- // webpack钩子
73
- apply(e) {
74
- return e && e.hooks && e.hooks.done && e.hooks.done.tap("yj-deploy", () => {
75
- this.isPut();
76
- }), "build";
77
- }
78
- /**
79
- * 判断是否需要上传,
80
- * vite和 webpack环境在每次编译完都会触发当前方法
81
- * 需要通过是否有命令行参数 --deploy
82
- */
83
- isPut() {
84
- this.getArgv("--deploy") != null && (clearTimeout(this.trim), this.trim = setTimeout(() => {
85
- this.upload();
86
- }, this.config.delay || 0));
87
- }
88
- /**
89
- * 连接跳板机并开始上传
90
- */
91
- upload() {
92
- if (console.log("-------------- deploy-start --------------"), !this.config.host) {
93
- console.log("- 请配置跳板机地址 host");
94
- return;
95
- }
96
- if (!this.config.port) {
97
- console.log("- 请配置跳板机端口 port");
98
- return;
99
- }
100
- if (!this.config.username) {
101
- console.log("- 请配置跳板机账号 username");
102
- return;
103
- }
104
- if (!this.config.password) {
105
- console.log("- 请配置跳板机密码 password");
106
- return;
107
- }
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}`);
111
- });
112
- }).connect({
113
- host: this.config.host,
114
- port: this.config.port,
115
- username: this.config.username,
116
- password: this.config.password
117
- }), this.ssh2Conn.on("error", (e) => {
118
- console.log(`- 连接失败: ${e}`), this.breakConnect();
119
- }), this.ssh2Conn.on("end", () => {
120
- this.uploading = !1;
121
- }));
122
- }
123
- /**
124
- * 断开连接
125
- */
126
- breakConnect() {
127
- this.uploading = !1, console.log("- 已断开连接"), console.log("-------------- deploy-end --------------"), this.ssh2Conn.end();
128
- }
129
- /**
130
- * 运行shell命令
131
- * @param {*} shell
132
- * @returns Promise
133
- */
134
- onShell(e) {
135
- const t = this.getArgv("--allLog") !== void 0;
136
- return new Promise((n, s) => {
137
- this.ssh2Conn.shell((i, a) => {
138
- if (i) {
139
- console.log("- 远程命令错误:" + i, e), this.breakConnect(), s(i);
140
- return;
141
- }
142
- let l = [];
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) => {
150
- console.log(`- 远程命令错误:
151
- ` + c), this.breakConnect(), s(c);
152
- }), a.end(e + `
153
- exit
154
- `);
155
- });
156
- });
157
- }
158
- /**
159
- * 初始化项目
160
- * @param {*} sftp
161
- * @param {*} dir
162
- */
163
- projectInit(e, t) {
164
- return new Promise(async (n, s) => {
165
- e.mkdir(t, async (i) => {
166
- if (i)
167
- s(i);
168
- else {
169
- console.log("- 正在创建dockerfile");
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();
176
- }
177
- });
178
- });
179
- }
180
- /**
181
- * 上传项目
182
- * @param {*} sftp
183
- * @param {*} dir
184
- */
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();
192
- });
193
- return;
194
- }
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) => {
207
- try {
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);
216
- }
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) => {
235
- setTimeout(() => {
236
- o();
237
- }, 500);
238
- });
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();
248
- });
249
- }
250
- /**
251
- * 进度条
252
- * @param description 命令行开头的文字信息
253
- * @param bar_length 进度条的长度(单位:字符),默认设为 25
254
- */
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("");
264
- }
265
- /**
266
- * 获取目录下所有文件并分类
267
- * @param {*} dir
268
- */
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: $,
279
- // 本地路径
280
- remotePath: `${c}${d}`,
281
- // 远程路径
282
- level: r
283
- // 文件或目录层级
284
- }), p && a($, `${c}${d}/`, r + 1);
285
- }), m || n.push(c);
286
- };
287
- a(e, "/", 0), s({
288
- localFileList: t,
289
- dirList: n
290
- });
291
- });
292
- }
293
- /**
294
- * 获取命令行参数
295
- * @param { string } name
296
- */
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;
303
- }
304
- };
@@ -1,7 +0,0 @@
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
- exit
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}}});