yj-deploy 0.0.10 → 0.0.11

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
@@ -82,17 +82,7 @@ const deploy = new yjDeploy({
82
82
  'else',
83
83
  'echo - 镜像推送失败,请重试或联系运维人员,如需查看全部docker日志,请在命令后添加 --allLog查看更多信息',
84
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
- }
85
+ ]
96
86
  },
97
87
 
98
88
  // 上传dist目录信息 (除非运维改变,否则不要配置)
@@ -109,8 +99,46 @@ const deploy = new yjDeploy({
109
99
  lazyUpload: false, // 是否开启懒上传,针对小程序图片等仅需要部署新增部分文件的项目有奇效
110
100
 
111
101
  rePushNum: 3, // 重新推送次数,为0代表不重新推送
102
+
103
+ // 自动重启服务的rancher配置
104
+ rancherConfig: {
105
+ token: '', // Rancher API 访问令牌 (必填)
106
+ namespace: '', // 服务所在的命名空间 (必填)
107
+ workload: '', // 服务名称 (必填)
108
+
109
+ // 非必填
110
+ baseUrl: '', // Rancher API 基础地址
111
+ clusterId: '', // Rancher 集群 ID
112
+ }
113
+ }
114
+ ```
115
+
116
+
117
+ ## rancher配置说明
118
+ 需要自动重启服务时,必须配置如下内容,然后在命令后加上`--restart`即可启用自动重启功能(因为仅开发环境支持,所以启用命令只能通过加在命令行里)
119
+ ```js
120
+ rancherConfig: {
121
+ token: '', // Rancher API 访问令牌 (必填)
122
+ namespace: '', // 服务所在的命名空间 (必填)
123
+ workload: '', // 服务名称 (必填)
124
+
125
+ // 非必填
126
+ baseUrl: '', // Rancher API 基础地址
127
+ clusterId: '', // Rancher 集群 ID
112
128
  }
113
129
  ```
130
+ ### token
131
+ 需要在rancher主页 -> 个人中心 -> API & Keys 内创建永久key,也可以复制其他已有项目里的token使用
132
+
133
+ ### namespace
134
+ 服务所在的命名空间 (必填)
135
+
136
+ ### workload
137
+ 服务名称 (必填)
138
+ ![服务名称](./img2.png)
139
+
140
+ ### baseUrl clusterId
141
+ 非必填,已默认配置为开发环境地址,除非运维更换rancher地址与集群id,否则不用配置
114
142
 
115
143
  <br />
116
144
 
@@ -269,6 +297,12 @@ node uploader.js --tmpName=test
269
297
 
270
298
  ### --restart
271
299
  `--restart` :是否需要自动重启服务,加上此命令,且配置了rancherConfig,将会自动重启服务
300
+ ```javascript
301
+ "scripts": {
302
+ // 开发环境
303
+ "deploy": "vite build --mode development -- --deploy --tmpName=dev --restart"
304
+ }
305
+ ```
272
306
 
273
307
  ::: warning
274
308
  仅对开发环境生效,并且需要先在rancher创建服务后方能成功
@@ -1,10 +1,10 @@
1
- var x = Object.defineProperty;
2
- var L = (g, e, o) => e in g ? x(g, e, { enumerable: !0, configurable: !0, writable: !0, value: o }) : g[e] = o;
3
- var $ = (g, e, o) => (L(g, typeof e != "symbol" ? e + "" : e, o), o);
4
- const { stdout: F } = require("single-line-log"), N = require("path"), S = require("fs"), { Client: U } = require("ssh2"), A = require("axios"), R = require("https");
1
+ var v = Object.defineProperty;
2
+ var L = (u, e, o) => e in u ? v(u, e, { enumerable: !0, configurable: !0, writable: !0, value: o }) : u[e] = o;
3
+ var w = (u, e, o) => (L(u, typeof e != "symbol" ? e + "" : e, o), o);
4
+ const { stdout: U } = require("single-line-log"), F = require("path"), S = require("fs"), { Client: R } = require("ssh2"), N = require("axios"), q = require("https");
5
5
  module.exports = class {
6
6
  constructor(e) {
7
- $(this, "config", {
7
+ w(this, "config", {
8
8
  host: "",
9
9
  port: "",
10
10
  username: "",
@@ -74,11 +74,11 @@ module.exports = class {
74
74
  // 工作负载名称
75
75
  }
76
76
  });
77
- $(this, "ssh2Conn", null);
77
+ w(this, "ssh2Conn", null);
78
78
  // 上传状态
79
- $(this, "uploading", !1);
79
+ w(this, "uploading", !1);
80
80
  // 定时器
81
- $(this, "trim", null);
81
+ w(this, "trim", null);
82
82
  return this.config = Object.assign(this.config, e), {
83
83
  name: "yj-deploy",
84
84
  isPut: this.isPut.bind(this),
@@ -125,7 +125,7 @@ module.exports = class {
125
125
  console.log("- 请配置跳板机密码 password");
126
126
  return;
127
127
  }
128
- this.uploading || (this.ssh2Conn = new U(), this.ssh2Conn.on("ready", () => {
128
+ this.uploading || (this.ssh2Conn = new R(), this.ssh2Conn.on("ready", () => {
129
129
  console.log("- 跳板机连接成功!"), this.ssh2Conn.sftp(async (e, o) => {
130
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(o, `${this.config.rootDir}/${this.config.namespace}`);
131
131
  });
@@ -153,23 +153,23 @@ module.exports = class {
153
153
  */
154
154
  onShell(e) {
155
155
  const o = this.getArgv("--allLog") !== void 0;
156
- return new Promise((t, i) => {
157
- this.ssh2Conn.shell((l, s) => {
158
- if (l) {
159
- console.log("- 远程命令错误:" + l, e), this.breakConnect(), i(l);
156
+ return new Promise((n, i) => {
157
+ this.ssh2Conn.shell((s, t) => {
158
+ if (s) {
159
+ console.log("- 远程命令错误:" + s, e), this.breakConnect(), i(s);
160
160
  return;
161
161
  }
162
- let n = [];
163
- s.on("close", () => {
164
- t(n.toString());
165
- }).on("data", (a) => {
166
- let c = a.toString();
167
- (o || this.config.allLog || c.startsWith("- ")) && n.push(`
168
- ` + c);
169
- }).stderr.on("data", (a) => {
162
+ let r = [];
163
+ t.on("close", () => {
164
+ n(r.toString());
165
+ }).on("data", (c) => {
166
+ let a = c.toString();
167
+ (o || this.config.allLog || a.startsWith("- ")) && r.push(`
168
+ ` + a);
169
+ }).stderr.on("data", (c) => {
170
170
  console.log(`- 远程命令错误:
171
- ` + a), this.breakConnect(), i(a);
172
- }), s.end(e + `
171
+ ` + c), this.breakConnect(), i(c);
172
+ }), t.end(e + `
173
173
  exit
174
174
  `);
175
175
  });
@@ -181,18 +181,18 @@ exit
181
181
  * @param {*} dir
182
182
  */
183
183
  projectInit(e, o) {
184
- return new Promise(async (t, i) => {
185
- e.mkdir(o, async (l) => {
186
- if (l)
187
- i(l);
184
+ return new Promise(async (n, i) => {
185
+ e.mkdir(o, async (s) => {
186
+ if (s)
187
+ i(s);
188
188
  else {
189
189
  console.log("- 正在创建dockerfile");
190
- const s = `${o}/${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 n = `${o}/${this.config.upload.name}`;
194
- await this.onShell(`touch ${n}`), await this.onShell(`echo '${this.config.upload.content.join(`
195
- `)}' > ${n}`), t();
190
+ const t = `${o}/${this.config.dockerfile.name}`;
191
+ await this.onShell(`touch ${t}`), await this.onShell(`echo '${this.config.dockerfile.content.join(`
192
+ `)}' > ${t}`), console.log("- 正在创建upload.sh");
193
+ const r = `${o}/${this.config.upload.name}`;
194
+ await this.onShell(`touch ${r}`), await this.onShell(`echo '${this.config.upload.content.join(`
195
+ `)}' > ${r}`), n();
196
196
  }
197
197
  });
198
198
  });
@@ -204,9 +204,9 @@ exit
204
204
  * @returns
205
205
  */
206
206
  checkRemoteFile(e, o) {
207
- return new Promise((t) => {
207
+ return new Promise((n) => {
208
208
  e.stat(o, (i) => {
209
- t(!i);
209
+ n(!i);
210
210
  });
211
211
  });
212
212
  }
@@ -216,74 +216,74 @@ exit
216
216
  * @param {*} dir
217
217
  */
218
218
  upLoadProject(e, o) {
219
- let t = 0;
220
- typeof this.config.rePushNum == "number" && this.config.rePushNum > 0 && (t = this.config.rePushNum), e.readdir(o, async (i) => {
219
+ let n = 0;
220
+ typeof this.config.rePushNum == "number" && this.config.rePushNum > 0 && (n = this.config.rePushNum), e.readdir(o, async (i) => {
221
221
  if (i) {
222
222
  console.log("- 跳板机不存在项目, 开始创建项目"), this.projectInit(e, o).then(() => {
223
223
  console.log(`- ${this.config.namespace}项目创建成功`), this.upLoadProject(e, o);
224
- }).catch((r) => {
225
- console.log(`- 创建项目失败: ${r}`), this.breakConnect();
224
+ }).catch((l) => {
225
+ console.log(`- 创建项目失败: ${l}`), this.breakConnect();
226
226
  });
227
227
  return;
228
228
  }
229
- const l = this.getArgv("--lazyUpload") !== void 0 || this.config.lazyUpload, s = `${o}/${this.config.dist.name}`;
230
- if (l || await this.onShell(`rm -rf ${s}`), await this.onShell(`mkdir ${s}`), !this.config.fileDir) {
229
+ const s = this.getArgv("--lazyUpload") !== void 0 || this.config.lazyUpload, t = `${o}/${this.config.dist.name}`;
230
+ if (s || await this.onShell(`rm -rf ${t}`), await this.onShell(`mkdir ${t}`), !this.config.fileDir) {
231
231
  console.log("- 请配置待上传文件目录 fileDir"), this.breakConnect();
232
232
  return;
233
233
  }
234
- const { localFileList: n, dirList: a } = await this.getFilesInDirectory(this.config.fileDir);
235
- if (n.length === 0) {
234
+ const { localFileList: r, dirList: c } = await this.getFilesInDirectory(this.config.fileDir);
235
+ if (r.length === 0) {
236
236
  console.log("- 待上传目录没有获取到文件,请检查"), this.breakConnect();
237
237
  return;
238
238
  }
239
- let c = 0, m = n.length;
240
- const w = (r) => new Promise(async (f, u) => {
239
+ let a = 0, h = r.length;
240
+ const p = (l) => new Promise(async (d, f) => {
241
241
  try {
242
- if (l && await this.checkRemoteFile(e, `${s}${r.remotePath}`))
243
- return f();
244
- const h = S.createReadStream(r.localPath), b = e.createWriteStream(`${s}${r.remotePath}`);
242
+ if (s && await this.checkRemoteFile(e, `${t}${l.remotePath}`))
243
+ return d();
244
+ const g = S.createReadStream(l.localPath), b = e.createWriteStream(`${t}${l.remotePath}`);
245
245
  b.on("close", () => {
246
- this.progressBar(++c, m), f();
246
+ this.progressBar(++a, h), d();
247
247
  }), b.on("error", (C) => {
248
- console.log(`- 文件 ${r.remotePath} 上传失败:${C}`), u(C), this.breakConnect();
249
- }), h.pipe(b);
250
- } catch (h) {
251
- u(h);
248
+ console.log(`- 文件 ${l.remotePath} 上传失败:${C}`), f(C), this.breakConnect();
249
+ }), g.pipe(b);
250
+ } catch (g) {
251
+ f(g);
252
252
  }
253
- }), d = (r, f) => {
254
- let u = [];
255
- for (let h = 0; h < r.length; h += f)
256
- u.push(r.slice(h, h + f));
257
- return u;
258
- }, p = (r) => {
259
- if (r.length === 0)
253
+ }), m = (l, d) => {
254
+ let f = [];
255
+ for (let g = 0; g < l.length; g += d)
256
+ f.push(l.slice(g, g + d));
257
+ return f;
258
+ }, $ = (l) => {
259
+ if (l.length === 0)
260
260
  return Promise.resolve();
261
- const f = r.shift();
262
- return Promise.all(f.map((u) => w(u))).then(() => p(r));
263
- }, k = (r) => {
264
- if (r.length === 0)
261
+ const d = l.shift();
262
+ return Promise.all(d.map((f) => p(f))).then(() => $(l));
263
+ }, k = (l) => {
264
+ if (l.length === 0)
265
265
  return Promise.resolve();
266
- let u = r.shift().map((h) => `${s}${h}`).join(" ");
267
- return this.onShell(`mkdir -p ${u}`).then(() => k(r));
266
+ let f = l.shift().map((g) => `${t}${g}`).join(" ");
267
+ return this.onShell(`mkdir -p ${f}`).then(() => k(l));
268
268
  };
269
- let y = [...d(a, this.config.parallelDir || 20)];
270
- await k(y), console.log("- 创建目录完成"), await new Promise((r) => {
269
+ let y = [...m(c, this.config.parallelDir || 20)];
270
+ await k(y), console.log("- 创建目录完成"), await new Promise((l) => {
271
271
  setTimeout(() => {
272
- r();
272
+ l();
273
273
  }, 500);
274
274
  });
275
- let I = [...d(n, this.config.parallelFile || 50)];
276
- await p(I), l && (this.progressBar(m, m), console.log("\x1B[32m%s\x1B[0m", `- 已开启懒上传,本次共上传 ${c} 个文件`)), console.log("- 文件上传成功"), console.log("- 开始推送镜像");
275
+ let I = [...m(r, this.config.parallelFile || 50)];
276
+ await $(I), s && (this.progressBar(h, h), console.log("\x1B[32m%s\x1B[0m", `- 已开启懒上传,本次共上传 ${a} 个文件`)), console.log("- 文件上传成功"), console.log("- 开始推送镜像");
277
277
  let P = `cd ${o} && sh ${this.config.upload.name}`;
278
278
  if (await this.isNewShell(e, o)) {
279
- const r = this.getArgv("--imageStore") || this.config.imageStore;
280
- P += ` ${r}`;
279
+ const l = this.getArgv("--imageStore") || this.config.imageStore;
280
+ P += ` ${l}`;
281
281
  } else
282
282
  console.log(`
283
283
  - warning 检测到当前脚本为手动创建,不支持镜像仓库配置,imageStore将失效`), console.log("- warning 如需支持imageStore参数,请在命令行后添加 --reset 重新生成脚本");
284
- const v = this.getArgv("--tmpName") || this.config.tmpName;
285
- P += ` ${v}`;
286
- const D = Array.from(new Array(t + 1)).map(() => this.onShell(P));
284
+ const x = this.getArgv("--tmpName") || this.config.tmpName;
285
+ P += ` ${x}`;
286
+ const D = Array.from(new Array(n + 1)).map(() => this.onShell(P));
287
287
  await this.pushMirrorImage(D, D.length), this.breakConnect();
288
288
  });
289
289
  }
@@ -291,8 +291,8 @@ exit
291
291
  async pushMirrorImage(e, o) {
292
292
  if (e.length === 0)
293
293
  return console.log("- 镜像推送失败,请添加 --allLog 查看全部日志"), null;
294
- const t = await e[0];
295
- return t.includes("- 镜像推送失败") ? (console.log(t), e.length > 1 && console.log(`- 正在尝试第 ${o - e.length + 1} 次推送...`), this.pushMirrorImage(e.slice(1), o)) : (console.log(t), console.log("- 镜像推送结束"), this.getArgv("--restart") !== void 0 && await q(t, this.config.rancherConfig), t);
294
+ const n = await e[0];
295
+ return n.includes("- 镜像推送失败") ? (console.log(n), e.length > 1 && console.log(`- 正在尝试第 ${o - e.length + 1} 次推送...`), this.pushMirrorImage(e.slice(1), o)) : (console.log(n), console.log("- 镜像推送结束"), this.getArgv("--restart") !== void 0 && await z(n, this.config.rancherConfig), n);
296
296
  }
297
297
  /**
298
298
  * 获取脚本内容是不是自动创建的脚本
@@ -300,13 +300,13 @@ exit
300
300
  * 有 $2代表是自动创建的版本,否则老版本
301
301
  */
302
302
  async isNewShell(e, o) {
303
- return new Promise((t, i) => {
304
- e.readFile(`${o}/${this.config.upload.name}`, (l, s) => {
305
- if (l)
303
+ return new Promise((n, i) => {
304
+ e.readFile(`${o}/${this.config.upload.name}`, (s, t) => {
305
+ if (s)
306
306
  return i(!1);
307
- const n = s.toString();
308
- n.includes("- 镜像推送失败") || console.log(`
309
- - warning 检测到当前脚本可能不是最新版本,推送失败可能不会提示,且没有失败自动重试功能,请在命令后添加 --reset 重新生成脚本`), t(n.includes("$2"));
307
+ const r = t.toString();
308
+ r.includes("- 镜像推送失败") || console.log(`
309
+ - warning 检测到当前脚本可能不是最新版本,推送失败可能不会提示,且没有失败自动重试功能,请在命令后添加 --reset 重新生成脚本`), n(r.includes("$2"));
310
310
  });
311
311
  });
312
312
  }
@@ -315,41 +315,41 @@ exit
315
315
  * @param description 命令行开头的文字信息
316
316
  * @param bar_length 进度条的长度(单位:字符),默认设为 25
317
317
  */
318
- progressBar(e, o, t = 25) {
319
- let i = (e / o).toFixed(4), l = Math.floor(i * t), s = "";
320
- for (let c = 0; c < l; c++)
321
- s += "█";
322
- let n = "";
323
- for (let c = 0; c < t - l; c++)
324
- n += "░";
325
- let a = `- 文件上传进度: ${s}${n} (${e}/${o}) ${(100 * i).toFixed(2)}%`;
326
- F(a), e == o && console.log("");
318
+ progressBar(e, o, n = 25) {
319
+ let i = (e / o).toFixed(4), s = Math.floor(i * n), t = "";
320
+ for (let a = 0; a < s; a++)
321
+ t += "█";
322
+ let r = "";
323
+ for (let a = 0; a < n - s; a++)
324
+ r += "░";
325
+ let c = `- 文件上传进度: ${t}${r} (${e}/${o}) ${(100 * i).toFixed(2)}%`;
326
+ U(c), e == o && console.log("");
327
327
  }
328
328
  /**
329
329
  * 获取目录下所有文件并分类
330
330
  * @param {*} dir
331
331
  */
332
332
  getFilesInDirectory(e) {
333
- const o = [], t = [];
334
- return new Promise((i, l) => {
335
- const s = (n, a, c) => {
336
- const m = S.readdirSync(n);
337
- let w = !1;
338
- m.forEach((d) => {
339
- const p = N.join(n, d), y = S.statSync(p).isDirectory();
340
- y ? w = !0 : o.push({
341
- localPath: p,
333
+ const o = [], n = [];
334
+ return new Promise((i, s) => {
335
+ const t = (r, c, a) => {
336
+ const h = S.readdirSync(r);
337
+ let p = !1;
338
+ h.forEach((m) => {
339
+ const $ = F.join(r, m), y = S.statSync($).isDirectory();
340
+ y ? p = !0 : o.push({
341
+ localPath: $,
342
342
  // 本地路径
343
- remotePath: `${a}${d}`,
343
+ remotePath: `${c}${m}`,
344
344
  // 远程路径
345
- level: c
345
+ level: a
346
346
  // 文件或目录层级
347
- }), y && s(p, `${a}${d}/`, c + 1);
348
- }), w || t.push(a);
347
+ }), y && t($, `${c}${m}/`, a + 1);
348
+ }), p || n.push(c);
349
349
  };
350
- s(e, "/", 0), i({
350
+ t(e, "/", 0), i({
351
351
  localFileList: o,
352
- dirList: t
352
+ dirList: n
353
353
  });
354
354
  });
355
355
  }
@@ -359,16 +359,20 @@ exit
359
359
  */
360
360
  getArgv(e) {
361
361
  const o = process.argv;
362
- let t;
362
+ let n;
363
363
  return o.forEach((i) => {
364
- i.indexOf(e) > -1 && (t = i.split("=")[1] || "");
365
- }), t;
364
+ i.indexOf(e) > -1 && (n = i.split("=")[1] || "");
365
+ }), n;
366
366
  }
367
367
  };
368
- const j = new R.Agent({
368
+ const A = new q.Agent({
369
369
  rejectUnauthorized: !1
370
+ }), j = N.create({
371
+ proxy: !1
372
+ // 禁用代理
370
373
  });
371
- async function q(g, e) {
374
+ async function z(u, e) {
375
+ var o, n, i;
372
376
  try {
373
377
  const t = {
374
378
  // baseUrl: 'https://paas-test.ymygz.com:10443/v3', // Rancher API 基础地址
@@ -394,34 +398,36 @@ async function q(g, e) {
394
398
  return console.error("- 服务所在的命名空间未配置,请检查 rancherConfig.namespace 是否正确");
395
399
  if (!t.workload)
396
400
  return console.error("- 工作负载名称未配置,请检查 rancherConfig.workload 是否正确");
397
- if (!g)
401
+ if (!u)
398
402
  return console.error("- 内部错误");
399
- const i = g.match(/- 镜像地址:\s*(.+)/);
400
- let l = "";
401
- if (i && i[1])
402
- l = i[1].trim();
403
+ const r = u.match(/- 镜像地址:\s*(.+)/);
404
+ let c = "";
405
+ if (r && r[1])
406
+ c = r[1].trim();
403
407
  else
404
408
  return console.error("- 未提取到镜像地址");
405
409
  console.log("- 开始调用 Rancher API 更新服务镜像地址");
406
- const s = `${t.baseUrl}/projects/${t.clusterId}/workloads/deployment:${t.namespace}:${t.workload}`, n = await A.get(s, {
410
+ const a = `${t.baseUrl}/projects/${t.clusterId}/workloads/deployment:${t.namespace}:${t.workload}`;
411
+ console.log("- 请求的工作负载 URL:", a);
412
+ const h = await j.get(a, {
407
413
  headers: { Authorization: `Bearer ${t.token}` },
408
- httpsAgent: j
414
+ httpsAgent: A
409
415
  });
410
- if (!n.data || !n.data.containers || n.data.containers.length === 0)
416
+ if (!h.data || !h.data.containers || h.data.containers.length === 0)
411
417
  return console.error("- 未找到工作负载的容器信息,请检查 namespace 和 workload 是否正确");
412
- const a = n.data;
413
- a.containers[0].image = l, await A.put(s, a, {
418
+ const p = h.data;
419
+ p.containers[0].image = c, await j.put(a, p, {
414
420
  headers: { Authorization: `Bearer ${t.token}` },
415
- httpsAgent: j
416
- }), console.log("- 镜像地址更新成功,正在重启服务..."), await A.post(
417
- `${s}?action=redeploy`,
421
+ httpsAgent: A
422
+ }), console.log("- 镜像地址更新成功,正在重启服务..."), await j.post(
423
+ `${a}?action=redeploy`,
418
424
  {},
419
425
  {
420
426
  headers: { Authorization: `Bearer ${t.token}` },
421
- httpsAgent: j
427
+ httpsAgent: A
422
428
  }
423
429
  ), console.log("- 服务重启成功");
424
- } catch (o) {
425
- console.error("- 调用 Rancher API 失败, 请手动重启服务:", o.message);
430
+ } catch (s) {
431
+ console.error("- 调用 Rancher API 失败,请检查以下信息:"), console.error(" - 错误代码:", s.code), console.error(" - 错误信息:", s.message), console.error(" - 请求 URL:", (o = s.config) == null ? void 0 : o.url), console.error(" - 响应状态:", (n = s.response) == null ? void 0 : n.status), console.error(" - 响应数据:", (i = s.response) == null ? void 0 : i.data);
426
432
  }
427
433
  }
@@ -1,9 +1,9 @@
1
- (function(h){typeof define=="function"&&define.amd?define(h):h()})(function(){"use strict";var R=Object.defineProperty;var q=(h,u,f)=>u in h?R(h,u,{enumerable:!0,configurable:!0,writable:!0,value:f}):h[u]=f;var y=(h,u,f)=>(q(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"),b=require("axios"),L=require("https");module.exports=class{constructor(e){y(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:""}});y(this,"ssh2Conn",null);y(this,"uploading",!1);y(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,i)=>{this.ssh2Conn.shell((l,s)=>{if(l){console.log("- 远程命令错误:"+l,e),this.breakConnect(),i(l);return}let n=[];s.on("close",()=>{o(n.toString())}).on("data",a=>{let c=a.toString();(t||this.config.allLog||c.startsWith("- "))&&n.push(`
2
- `+c)}).stderr.on("data",a=>{console.log(`- 远程命令错误:
3
- `+a),this.breakConnect(),i(a)}),s.end(e+`
1
+ (function(h){typeof define=="function"&&define.amd?define(h):h()})(function(){"use strict";var q=Object.defineProperty;var z=(h,f,d)=>f in h?q(h,f,{enumerable:!0,configurable:!0,writable:!0,value:d}):h[f]=d;var k=(h,f,d)=>(z(h,typeof f!="symbol"?f+"":f,d),d);const{stdout:h}=require("single-line-log"),f=require("path"),d=require("fs"),{Client:v}=require("ssh2"),L=require("axios"),U=require("https");module.exports=class{constructor(e){k(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:""}});k(this,"ssh2Conn",null);k(this,"uploading",!1);k(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,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){const o=this.getArgv("--allLog")!==void 0;return new Promise((n,i)=>{this.ssh2Conn.shell((s,t)=>{if(s){console.log("- 远程命令错误:"+s,e),this.breakConnect(),i(s);return}let r=[];t.on("close",()=>{n(r.toString())}).on("data",c=>{let a=c.toString();(o||this.config.allLog||a.startsWith("- "))&&r.push(`
2
+ `+a)}).stderr.on("data",c=>{console.log(`- 远程命令错误:
3
+ `+c),this.breakConnect(),i(c)}),t.end(e+`
4
4
  exit
5
- `)})})}projectInit(e,t){return new Promise(async(o,i)=>{e.mkdir(t,async l=>{if(l)i(l);else{console.log("- 正在创建dockerfile");const s=`${t}/${this.config.dockerfile.name}`;await this.onShell(`touch ${s}`),await this.onShell(`echo '${this.config.dockerfile.content.join(`
6
- `)}' > ${s}`),console.log("- 正在创建upload.sh");const n=`${t}/${this.config.upload.name}`;await this.onShell(`touch ${n}`),await this.onShell(`echo '${this.config.upload.content.join(`
7
- `)}' > ${n}`),o()}})})}checkRemoteFile(e,t){return new Promise(o=>{e.stat(t,i=>{o(!i)})})}upLoadProject(e,t){let o=0;typeof this.config.rePushNum=="number"&&this.config.rePushNum>0&&(o=this.config.rePushNum),e.readdir(t,async i=>{if(i){console.log("- 跳板机不存在项目, 开始创建项目"),this.projectInit(e,t).then(()=>{console.log(`- ${this.config.namespace}项目创建成功`),this.upLoadProject(e,t)}).catch(r=>{console.log(`- 创建项目失败: ${r}`),this.breakConnect()});return}const l=this.getArgv("--lazyUpload")!==void 0||this.config.lazyUpload,s=`${t}/${this.config.dist.name}`;if(l||await this.onShell(`rm -rf ${s}`),await this.onShell(`mkdir ${s}`),!this.config.fileDir){console.log("- 请配置待上传文件目录 fileDir"),this.breakConnect();return}const{localFileList:n,dirList:a}=await this.getFilesInDirectory(this.config.fileDir);if(n.length===0){console.log("- 待上传目录没有获取到文件,请检查"),this.breakConnect();return}let c=0,w=n.length;const k=r=>new Promise(async(p,d)=>{try{if(l&&await this.checkRemoteFile(e,`${s}${r.remotePath}`))return p();const g=f.createReadStream(r.localPath),D=e.createWriteStream(`${s}${r.remotePath}`);D.on("close",()=>{this.progressBar(++c,w),p()}),D.on("error",I=>{console.log(`- 文件 ${r.remotePath} 上传失败:${I}`),d(I),this.breakConnect()}),g.pipe(D)}catch(g){d(g)}}),m=(r,p)=>{let d=[];for(let g=0;g<r.length;g+=p)d.push(r.slice(g,g+p));return d},$=r=>{if(r.length===0)return Promise.resolve();const p=r.shift();return Promise.all(p.map(d=>k(d))).then(()=>$(r))},A=r=>{if(r.length===0)return Promise.resolve();let d=r.shift().map(g=>`${s}${g}`).join(" ");return this.onShell(`mkdir -p ${d}`).then(()=>A(r))};let P=[...m(a,this.config.parallelDir||20)];await A(P),console.log("- 创建目录完成"),await new Promise(r=>{setTimeout(()=>{r()},500)});let N=[...m(n,this.config.parallelFile||50)];await $(N),l&&(this.progressBar(w,w),console.log("\x1B[32m%s\x1B[0m",`- 已开启懒上传,本次共上传 ${c} 个文件`)),console.log("- 文件上传成功"),console.log("- 开始推送镜像");let j=`cd ${t} && sh ${this.config.upload.name}`;if(await this.isNewShell(e,t)){const r=this.getArgv("--imageStore")||this.config.imageStore;j+=` ${r}`}else console.log(`
8
- - warning 检测到当前脚本为手动创建,不支持镜像仓库配置,imageStore将失效`),console.log("- warning 如需支持imageStore参数,请在命令行后添加 --reset 重新生成脚本");const U=this.getArgv("--tmpName")||this.config.tmpName;j+=` ${U}`;const x=Array.from(new Array(o+1)).map(()=>this.onShell(j));await this.pushMirrorImage(x,x.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 F(o,this.config.rancherConfig),o)}async isNewShell(e,t){return new Promise((o,i)=>{e.readFile(`${t}/${this.config.upload.name}`,(l,s)=>{if(l)return i(!1);const n=s.toString();n.includes("- 镜像推送失败")||console.log(`
9
- - warning 检测到当前脚本可能不是最新版本,推送失败可能不会提示,且没有失败自动重试功能,请在命令后添加 --reset 重新生成脚本`),o(n.includes("$2"))})})}progressBar(e,t,o=25){let i=(e/t).toFixed(4),l=Math.floor(i*o),s="";for(let c=0;c<l;c++)s+="█";let n="";for(let c=0;c<o-l;c++)n+="░";let a=`- 文件上传进度: ${s}${n} (${e}/${t}) ${(100*i).toFixed(2)}%`;h(a),e==t&&console.log("")}getFilesInDirectory(e){const t=[],o=[];return new Promise((i,l)=>{const s=(n,a,c)=>{const w=f.readdirSync(n);let k=!1;w.forEach(m=>{const $=u.join(n,m),P=f.statSync($).isDirectory();P?k=!0:t.push({localPath:$,remotePath:`${a}${m}`,level:c}),P&&s($,`${a}${m}/`,c+1)}),k||o.push(a)};s(e,"/",0),i({localFileList:t,dirList:o})})}getArgv(e){const t=process.argv;let o;return t.forEach(i=>{i.indexOf(e)>-1&&(o=i.split("=")[1]||"")}),o}};const C=new L.Agent({rejectUnauthorized:!1});async function F(S,e){try{const o={...{baseUrl:"https://paas-test.ymygz.com:10443/v3",clusterId:"local:p-r7j28"},...e};if(!o.baseUrl)return console.error("- Rancher API 基础地址未配置,请检查 rancherConfig.baseUrl 是否正确");if(!o.token)return console.error("- Rancher API 访问令牌未配置,请检查 rancherConfig.token 是否正确");if(!o.clusterId)return console.error("- Rancher 集群 ID 未配置,请检查 rancherConfig.clusterId 是否正确");if(!o.namespace)return console.error("- 服务所在的命名空间未配置,请检查 rancherConfig.namespace 是否正确");if(!o.workload)return console.error("- 工作负载名称未配置,请检查 rancherConfig.workload 是否正确");if(!S)return console.error("- 内部错误");const i=S.match(/- 镜像地址:\s*(.+)/);let l="";if(i&&i[1])l=i[1].trim();else return console.error("- 未提取到镜像地址");console.log("- 开始调用 Rancher API 更新服务镜像地址");const s=`${o.baseUrl}/projects/${o.clusterId}/workloads/deployment:${o.namespace}:${o.workload}`,n=await b.get(s,{headers:{Authorization:`Bearer ${o.token}`},httpsAgent:C});if(!n.data||!n.data.containers||n.data.containers.length===0)return console.error("- 未找到工作负载的容器信息,请检查 namespace 和 workload 是否正确");const a=n.data;a.containers[0].image=l,await b.put(s,a,{headers:{Authorization:`Bearer ${o.token}`},httpsAgent:C}),console.log("- 镜像地址更新成功,正在重启服务..."),await b.post(`${s}?action=redeploy`,{},{headers:{Authorization:`Bearer ${o.token}`},httpsAgent:C}),console.log("- 服务重启成功")}catch(t){console.error("- 调用 Rancher API 失败, 请手动重启服务:",t.message)}}});
5
+ `)})})}projectInit(e,o){return new Promise(async(n,i)=>{e.mkdir(o,async s=>{if(s)i(s);else{console.log("- 正在创建dockerfile");const t=`${o}/${this.config.dockerfile.name}`;await this.onShell(`touch ${t}`),await this.onShell(`echo '${this.config.dockerfile.content.join(`
6
+ `)}' > ${t}`),console.log("- 正在创建upload.sh");const r=`${o}/${this.config.upload.name}`;await this.onShell(`touch ${r}`),await this.onShell(`echo '${this.config.upload.content.join(`
7
+ `)}' > ${r}`),n()}})})}checkRemoteFile(e,o){return new Promise(n=>{e.stat(o,i=>{n(!i)})})}upLoadProject(e,o){let n=0;typeof this.config.rePushNum=="number"&&this.config.rePushNum>0&&(n=this.config.rePushNum),e.readdir(o,async i=>{if(i){console.log("- 跳板机不存在项目, 开始创建项目"),this.projectInit(e,o).then(()=>{console.log(`- ${this.config.namespace}项目创建成功`),this.upLoadProject(e,o)}).catch(l=>{console.log(`- 创建项目失败: ${l}`),this.breakConnect()});return}const s=this.getArgv("--lazyUpload")!==void 0||this.config.lazyUpload,t=`${o}/${this.config.dist.name}`;if(s||await this.onShell(`rm -rf ${t}`),await this.onShell(`mkdir ${t}`),!this.config.fileDir){console.log("- 请配置待上传文件目录 fileDir"),this.breakConnect();return}const{localFileList:r,dirList:c}=await this.getFilesInDirectory(this.config.fileDir);if(r.length===0){console.log("- 待上传目录没有获取到文件,请检查"),this.breakConnect();return}let a=0,g=r.length;const $=l=>new Promise(async(m,p)=>{try{if(s&&await this.checkRemoteFile(e,`${t}${l.remotePath}`))return m();const u=d.createReadStream(l.localPath),x=e.createWriteStream(`${t}${l.remotePath}`);x.on("close",()=>{this.progressBar(++a,g),m()}),x.on("error",D=>{console.log(`- 文件 ${l.remotePath} 上传失败:${D}`),p(D),this.breakConnect()}),u.pipe(x)}catch(u){p(u)}}),w=(l,m)=>{let p=[];for(let u=0;u<l.length;u+=m)p.push(l.slice(u,u+m));return p},y=l=>{if(l.length===0)return Promise.resolve();const m=l.shift();return Promise.all(m.map(p=>$(p))).then(()=>y(l))},A=l=>{if(l.length===0)return Promise.resolve();let p=l.shift().map(u=>`${t}${u}`).join(" ");return this.onShell(`mkdir -p ${p}`).then(()=>A(l))};let P=[...w(c,this.config.parallelDir||20)];await A(P),console.log("- 创建目录完成"),await new Promise(l=>{setTimeout(()=>{l()},500)});let R=[...w(r,this.config.parallelFile||50)];await y(R),s&&(this.progressBar(g,g),console.log("\x1B[32m%s\x1B[0m",`- 已开启懒上传,本次共上传 ${a} 个文件`)),console.log("- 文件上传成功"),console.log("- 开始推送镜像");let j=`cd ${o} && sh ${this.config.upload.name}`;if(await this.isNewShell(e,o)){const l=this.getArgv("--imageStore")||this.config.imageStore;j+=` ${l}`}else console.log(`
8
+ - warning 检测到当前脚本为手动创建,不支持镜像仓库配置,imageStore将失效`),console.log("- warning 如需支持imageStore参数,请在命令行后添加 --reset 重新生成脚本");const N=this.getArgv("--tmpName")||this.config.tmpName;j+=` ${N}`;const I=Array.from(new Array(n+1)).map(()=>this.onShell(j));await this.pushMirrorImage(I,I.length),this.breakConnect()})}async pushMirrorImage(e,o){if(e.length===0)return console.log("- 镜像推送失败,请添加 --allLog 查看全部日志"),null;const n=await e[0];return n.includes("- 镜像推送失败")?(console.log(n),e.length>1&&console.log(`- 正在尝试第 ${o-e.length+1} 次推送...`),this.pushMirrorImage(e.slice(1),o)):(console.log(n),console.log("- 镜像推送结束"),this.getArgv("--restart")!==void 0&&await F(n,this.config.rancherConfig),n)}async isNewShell(e,o){return new Promise((n,i)=>{e.readFile(`${o}/${this.config.upload.name}`,(s,t)=>{if(s)return i(!1);const r=t.toString();r.includes("- 镜像推送失败")||console.log(`
9
+ - warning 检测到当前脚本可能不是最新版本,推送失败可能不会提示,且没有失败自动重试功能,请在命令后添加 --reset 重新生成脚本`),n(r.includes("$2"))})})}progressBar(e,o,n=25){let i=(e/o).toFixed(4),s=Math.floor(i*n),t="";for(let a=0;a<s;a++)t+="█";let r="";for(let a=0;a<n-s;a++)r+="░";let c=`- 文件上传进度: ${t}${r} (${e}/${o}) ${(100*i).toFixed(2)}%`;h(c),e==o&&console.log("")}getFilesInDirectory(e){const o=[],n=[];return new Promise((i,s)=>{const t=(r,c,a)=>{const g=d.readdirSync(r);let $=!1;g.forEach(w=>{const y=f.join(r,w),P=d.statSync(y).isDirectory();P?$=!0:o.push({localPath:y,remotePath:`${c}${w}`,level:a}),P&&t(y,`${c}${w}/`,a+1)}),$||n.push(c)};t(e,"/",0),i({localFileList:o,dirList:n})})}getArgv(e){const o=process.argv;let n;return o.forEach(i=>{i.indexOf(e)>-1&&(n=i.split("=")[1]||"")}),n}};const b=new U.Agent({rejectUnauthorized:!1}),C=L.create({proxy:!1});async function F(S,e){var o,n,i;try{const t={...{baseUrl:"https://paas-test.ymygz.com:10443/v3",clusterId:"local:p-r7j28"},...e};if(!t.baseUrl)return console.error("- Rancher API 基础地址未配置,请检查 rancherConfig.baseUrl 是否正确");if(!t.token)return console.error("- Rancher API 访问令牌未配置,请检查 rancherConfig.token 是否正确");if(!t.clusterId)return console.error("- Rancher 集群 ID 未配置,请检查 rancherConfig.clusterId 是否正确");if(!t.namespace)return console.error("- 服务所在的命名空间未配置,请检查 rancherConfig.namespace 是否正确");if(!t.workload)return console.error("- 工作负载名称未配置,请检查 rancherConfig.workload 是否正确");if(!S)return console.error("- 内部错误");const r=S.match(/- 镜像地址:\s*(.+)/);let c="";if(r&&r[1])c=r[1].trim();else return console.error("- 未提取到镜像地址");console.log("- 开始调用 Rancher API 更新服务镜像地址");const a=`${t.baseUrl}/projects/${t.clusterId}/workloads/deployment:${t.namespace}:${t.workload}`;console.log("- 请求的工作负载 URL:",a);const g=await C.get(a,{headers:{Authorization:`Bearer ${t.token}`},httpsAgent:b});if(!g.data||!g.data.containers||g.data.containers.length===0)return console.error("- 未找到工作负载的容器信息,请检查 namespace 和 workload 是否正确");const $=g.data;$.containers[0].image=c,await C.put(a,$,{headers:{Authorization:`Bearer ${t.token}`},httpsAgent:b}),console.log("- 镜像地址更新成功,正在重启服务..."),await C.post(`${a}?action=redeploy`,{},{headers:{Authorization:`Bearer ${t.token}`},httpsAgent:b}),console.log("- 服务重启成功")}catch(s){console.error("- 调用 Rancher API 失败,请检查以下信息:"),console.error(" - 错误代码:",s.code),console.error(" - 错误信息:",s.message),console.error(" - 请求 URL:",(o=s.config)==null?void 0:o.url),console.error(" - 响应状态:",(n=s.response)==null?void 0:n.status),console.error(" - 响应数据:",(i=s.response)==null?void 0:i.data)}}});
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "yj-deploy",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "ssh sftp",
5
5
  "scripts": {
6
- "build": "vite build"
6
+ "build": "vite build",
7
+ "deploy": "node test.js --restart"
7
8
  },
8
9
  "files": [
9
10
  "dist",