@lazycatcloud/lzc-cli 1.1.0 → 1.1.3

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.
Files changed (60) hide show
  1. package/lib/api.js +34 -36
  2. package/lib/archiver.js +50 -31
  3. package/lib/box/hportal.js +113 -0
  4. package/lib/box/index.js +135 -0
  5. package/lib/box/qemu_vm_mgr.js +553 -0
  6. package/lib/box/schemes/vm_box_system_debian.json +47 -0
  7. package/lib/builder.js +154 -35
  8. package/lib/dev.js +39 -31
  9. package/lib/env.js +276 -58
  10. package/lib/generator.js +31 -0
  11. package/lib/git/git-commit.sh +7 -0
  12. package/lib/git/git-reset.sh +15 -0
  13. package/lib/key.js +14 -25
  14. package/lib/sdk.js +7 -10
  15. package/lib/utils.js +149 -52
  16. package/package.json +14 -2
  17. package/scripts/auto-completion.sh +46 -0
  18. package/scripts/cli.js +134 -70
  19. package/template/_lazycat/app-config +1 -0
  20. package/template/_lazycat/docker-compose.yml.in +3 -5
  21. package/template/golang/README.md +3 -4
  22. package/template/golang/assets/css/bootstrap-responsive.css +26 -23
  23. package/template/golang/assets/css/bootstrap-responsive.min.css +1065 -1
  24. package/template/golang/assets/css/bootstrap.css +733 -362
  25. package/template/golang/assets/css/bootstrap.min.css +5299 -1
  26. package/template/golang/assets/css/rego.css +17 -17
  27. package/template/golang/assets/js/bootstrap.js +1340 -1311
  28. package/template/golang/assets/js/bootstrap.min.js +1240 -5
  29. package/template/golang/assets/js/rego.js +80 -69
  30. package/template/golang/index.html +61 -59
  31. package/template/ionic_vue3/README.md +46 -0
  32. package/template/ionic_vue3/_eslintrc.cjs +24 -0
  33. package/template/ionic_vue3/_gitignore +29 -0
  34. package/template/ionic_vue3/_vscode/extensions.json +6 -0
  35. package/template/ionic_vue3/capacitor.config.ts +10 -0
  36. package/template/ionic_vue3/env.d.ts +1 -0
  37. package/template/ionic_vue3/index.html +13 -0
  38. package/template/ionic_vue3/ionic.config.json +7 -0
  39. package/template/ionic_vue3/package.json +52 -0
  40. package/template/ionic_vue3/postcss.config.js +6 -0
  41. package/template/ionic_vue3/public/favicon.ico +0 -0
  42. package/template/ionic_vue3/src/App.vue +11 -0
  43. package/template/ionic_vue3/src/assets/logo.svg +1 -0
  44. package/template/ionic_vue3/src/index.css +3 -0
  45. package/template/ionic_vue3/src/main.ts +35 -0
  46. package/template/ionic_vue3/src/router/index.ts +15 -0
  47. package/template/ionic_vue3/src/theme/variables.css +231 -0
  48. package/template/ionic_vue3/src/views/Home.vue +38 -0
  49. package/template/ionic_vue3/tailwind.config.js +7 -0
  50. package/template/ionic_vue3/tsconfig.json +16 -0
  51. package/template/ionic_vue3/tsconfig.vite-config.json +8 -0
  52. package/template/ionic_vue3/vite.config.ts +28 -0
  53. package/template/release/golang/build.sh +1 -2
  54. package/template/release/ionic_vue3/Dockerfile +10 -0
  55. package/template/release/ionic_vue3/build.sh +9 -0
  56. package/template/release/ionic_vue3/docker-compose.yml.in +8 -0
  57. package/template/release/vue/Dockerfile +3 -2
  58. package/template/release/vue/build.sh +4 -2
  59. package/template/vue/README.md +5 -0
  60. package/template/vue/babel.config.js +2 -4
package/lib/api.js CHANGED
@@ -21,8 +21,7 @@ export default class API {
21
21
  body: fs.createReadStream(zipPath),
22
22
  }
23
23
  );
24
- if (resp.status == 200) {
25
- } else {
24
+ if (resp.status != 200) {
26
25
  throw chalk.red(await resp.text());
27
26
  }
28
27
  // update
@@ -71,10 +70,10 @@ export default class API {
71
70
  throw info?.msg;
72
71
  default:
73
72
  try {
74
- await this.desireStatusTimer((result) => {
73
+ await desireStatusTimer((result) => {
75
74
  spinner.text = "部署进度 " + result.status;
76
75
  return result.status === "running";
77
- });
76
+ }, this.status.bind(this));
78
77
  } catch (error) {
79
78
  throw error;
80
79
  } finally {
@@ -84,40 +83,39 @@ export default class API {
84
83
  }
85
84
  spinner.stop();
86
85
  }
86
+ }
87
87
 
88
- desireStatusTimer(desireCheckFn) {
89
- let that = this;
90
- const step = 2000;
91
- return new Promise((resolve, reject) => {
92
- function schedule(timeout) {
93
- setTimeout(async () => {
94
- try {
95
- const answer = await that.status();
96
- if (answer) {
97
- if (answer.status == "error") {
98
- timeout += step;
99
- if (timeout > 1000 * 10) {
100
- reject(new Error(answer.error));
101
- return;
102
- }
103
- schedule(timeout);
104
- } else if (desireCheckFn(answer)) {
105
- resolve(answer);
106
- } else {
107
- schedule(1000);
88
+ export function desireStatusTimer(desireCheckFn, getStatusFn) {
89
+ const step = 2000;
90
+ return new Promise((resolve, reject) => {
91
+ function schedule(timeout) {
92
+ setTimeout(async () => {
93
+ try {
94
+ const answer = await getStatusFn();
95
+ if (answer) {
96
+ if (answer.status == "error") {
97
+ timeout += step;
98
+ if (timeout > 1000 * 10) {
99
+ reject(new Error(answer.error));
100
+ return;
108
101
  }
102
+ schedule(timeout);
103
+ } else if (desireCheckFn(answer)) {
104
+ resolve(answer);
105
+ } else {
106
+ schedule(1000);
109
107
  }
110
- } catch (e) {
111
- timeout += step;
112
- if (timeout > 1000 * 10) {
113
- reject(e);
114
- return;
115
- }
116
- schedule(timeout);
117
108
  }
118
- }, timeout);
119
- }
120
- schedule(1000);
121
- });
122
- }
109
+ } catch (e) {
110
+ timeout += step;
111
+ if (timeout > 1000 * 10) {
112
+ reject(e);
113
+ return;
114
+ }
115
+ schedule(timeout);
116
+ }
117
+ }, timeout);
118
+ }
119
+ schedule(1000);
120
+ });
123
121
  }
package/lib/archiver.js CHANGED
@@ -7,14 +7,10 @@ import {
7
7
  envsubstr,
8
8
  } from "./utils.js";
9
9
 
10
- import { execPreBuild } from "../lib/builder.js";
11
-
12
- import execa from "execa";
13
-
14
10
  import glob from "fast-glob";
15
11
  import path from "path";
16
12
  import fs from "fs";
17
- import Env from "./env.js";
13
+ import env from "./env.js";
18
14
  import DockerCompose from "./docker-compose.js";
19
15
 
20
16
  class Archiver {
@@ -22,20 +18,25 @@ class Archiver {
22
18
  this.tmpDir = tmpDir;
23
19
  }
24
20
 
25
- async load(baseDir) {
21
+ async load(baseDir, e) {
22
+ const allEnv = e ? e : env.all;
26
23
  // 1 拷贝 dotApp 路径至 temp
27
24
  this.dotAppDir = baseDir;
28
- this.env = Env(baseDir);
29
25
  await copyDotAppDir(this.dotAppDir, this.tmpDir, {
30
- env: this.env.all,
26
+ env: allEnv,
31
27
  });
32
28
  }
33
29
 
34
- async add(cwd) {
35
- const allEnv = {
36
- ...this.env.all,
37
- APP_IMAGE_NAME: this.env.get("APP_ID"),
38
- };
30
+ // 用于替换一些docker-compose 内容
31
+ replace(dockerComposeContent) {
32
+ let base = new DockerCompose(path.join(this.tmpDir, "docker-compose.yml"));
33
+ base.merge({ content: dockerComposeContent });
34
+ base.save();
35
+ }
36
+
37
+ async add(cwd, e) {
38
+ const allEnv = e ? e : env.all;
39
+ Object.assign(allEnv, { APP_IMAGE_NAME: env.get("APP_ID") });
39
40
  const target = this.tmpDir;
40
41
  try {
41
42
  let base = new DockerCompose(path.join(target, "docker-compose.yml"));
@@ -49,22 +50,25 @@ class Archiver {
49
50
  let absFile = path.join(cwd, file);
50
51
  if (path.basename(file).startsWith("docker-compose")) {
51
52
  if (file.endsWith(".in")) {
52
- const options = {
53
- envs: toPair(allEnv),
54
- syntax: "default",
55
- protect: false,
56
- };
57
- const output = await envsubstr(fs.readFileSync(absFile, "utf8"), {
58
- options,
59
- });
60
- base.merge({ content: output });
53
+ base.merge({ content: await this._doEnvSubStr(absFile, allEnv) });
61
54
  } else {
62
55
  base.merge({ path: absFile });
63
56
  }
64
57
  } else {
65
- let targetAbsFile = path.join(target, file);
66
- ensureDir(targetAbsFile);
67
- fs.copyFileSync(absFile, targetAbsFile);
58
+ // 对其他模板做转换操作, 然后直接写入目标文件
59
+ let targetAbsFile;
60
+ if (file.endsWith(".in")) {
61
+ targetAbsFile = path.join(target, file.replace(/\.in$/, ""));
62
+ ensureDir(targetAbsFile);
63
+ fs.writeFileSync(
64
+ targetAbsFile,
65
+ await this._doEnvSubStr(absFile, allEnv)
66
+ );
67
+ } else {
68
+ targetAbsFile = path.join(target, file);
69
+ ensureDir(targetAbsFile);
70
+ fs.copyFileSync(absFile, targetAbsFile);
71
+ }
68
72
  }
69
73
  }
70
74
 
@@ -78,14 +82,29 @@ class Archiver {
78
82
  }
79
83
  }
80
84
 
81
- async finalize() {
82
- await this._changeContent();
83
- return await archiveFolder(this.tmpDir);
85
+ async _doEnvSubStr(filepath, env = {}) {
86
+ const options = {
87
+ envs: toPair(env),
88
+ syntax: "default",
89
+ protect: false,
90
+ };
91
+ return await envsubstr(fs.readFileSync(filepath, "utf8"), {
92
+ options,
93
+ });
94
+ }
95
+
96
+ async finalize(zip = true) {
97
+ // await this._changeContent();
98
+ if (zip) {
99
+ return await archiveFolder(this.tmpDir);
100
+ } else {
101
+ return this.tmpDir;
102
+ }
84
103
  }
85
104
  //
86
105
  // permissions:
87
106
  // - lzcapis
88
- //
107
+ // need REMOVE deprecated
89
108
  async _changeContent() {
90
109
  let base = new DockerCompose(path.join(this.tmpDir, "docker-compose.yml"));
91
110
  base
@@ -99,13 +118,13 @@ class Archiver {
99
118
  template[META_MARK]["ingress"].push({
100
119
  service: "lazycat-apis-sidecar",
101
120
  port: 8888,
102
- subdomain: this.env.get("APP_ID"),
121
+ subdomain: env.get("APP_ID"),
103
122
  path: "/lzcapis/",
104
123
  auth: "oidc",
105
124
  authcallback: "/lzcapis/oidc-callback",
106
125
  });
107
126
  template["services"]["lazycat-apis-sidecar"] = {
108
- image: "registry.linakesi.com/lazycat-apis-sidecar",
127
+ image: "registry.lazycat.cloud/lazycat-apis-sidecar",
109
128
  pull_policy: "always",
110
129
  // volumes_from: ["${APP_NAME}:rw"],
111
130
  volumes: ["lzcapis-lzcapp:/lzcapp"],
@@ -0,0 +1,113 @@
1
+ import fetch from "node-fetch";
2
+
3
+ export class HportalManager {
4
+ constructor(apiHost) {
5
+ this.apiHost = apiHost;
6
+ }
7
+
8
+ async deleteBox(boxId) {
9
+ let url = `${this.apiHost}/admin/boxes/${boxId}`;
10
+ const resp = await fetch(url, { method: "DELETE" });
11
+ if (resp.status != 200) {
12
+ let text = await resp.text();
13
+ console.log("从 Hportal 删除盒子失败", text);
14
+ }
15
+ }
16
+
17
+ async addBox(boxName) {
18
+ let url = `${this.apiHost}/admin/boxes/${boxName}`;
19
+ const resp = await fetch(url, {
20
+ method: "POST",
21
+ body: JSON.stringify({
22
+ box_name: boxName,
23
+ }),
24
+ });
25
+ if (resp.status != 200) {
26
+ let text = await resp.text();
27
+ console.log(`添加 ${boxName} 到 Hportal 失败`);
28
+ throw text;
29
+ }
30
+ }
31
+
32
+ async loginBox(boxId, user, password) {
33
+ let url = `${this.apiHost}/admin/loginBox`;
34
+ const resp = await fetch(url, {
35
+ method: "POST",
36
+ body: JSON.stringify({
37
+ box_id: boxId,
38
+ password,
39
+ user,
40
+ }),
41
+ });
42
+ if (resp.status != 200) {
43
+ let text = await resp.text();
44
+ console.log(`登录失败`, text);
45
+ throw text;
46
+ }
47
+ }
48
+
49
+ async getBoxId(boxName) {
50
+ let boxs = await this.boxs(boxName);
51
+ if (boxs.length == 0) {
52
+ return "";
53
+ } else {
54
+ return boxs[0].boxId;
55
+ }
56
+ }
57
+
58
+ async boxs(boxName, defaultBoxName = "") {
59
+ let url = this.apiHost + "/admin/boxes";
60
+
61
+ let origin;
62
+ try {
63
+ const resp = await fetch(url);
64
+ if (resp.status == 200) {
65
+ origin = await resp.json();
66
+ }
67
+ } catch {
68
+ return [];
69
+ }
70
+
71
+ let all = origin.map((info) => {
72
+ let yesorno = info.box_name == defaultBoxName ? "yes" : "no";
73
+ let connect = info.status >= 200 ? "connect" : "disconnect";
74
+ return {
75
+ default: yesorno,
76
+ boxName: info.box_name,
77
+ boxId: info.box_id,
78
+ connect,
79
+ };
80
+ });
81
+
82
+ if (!boxName) {
83
+ return all;
84
+ }
85
+
86
+ let box = all.find((a) => a.boxName == boxName);
87
+ if (box) {
88
+ return [box];
89
+ } else {
90
+ return [];
91
+ }
92
+ }
93
+ }
94
+
95
+ // showBox 将 qemu 虚拟盒子 和 真实盒子 的信息合并起来展示
96
+ export function showBoxInfo(vmInfos, rmInfos) {
97
+ let boxIds = vmInfos
98
+ .concat(rmInfos)
99
+ .map((info) => info.boxId)
100
+ .filter((value, index, self) => self.indexOf(value) == index);
101
+
102
+ if (boxIds.length == 0) {
103
+ console.log("没有找到任何盒子,创建一个吧!");
104
+ }
105
+
106
+ let result = [];
107
+ boxIds.forEach((boxId) => {
108
+ let vInfo = vmInfos.find((r) => r.boxId == boxId) || {};
109
+ let rInfo = rmInfos.find((r) => r.boxId == boxId) || {};
110
+ result.push(Object.assign({}, vInfo, rInfo));
111
+ });
112
+ console.table(result);
113
+ }
@@ -0,0 +1,135 @@
1
+ #!/bin/node
2
+
3
+ import fs from "node:fs";
4
+ import path from "node:path";
5
+ import { QemuVM, QemuResource } from "./qemu_vm_mgr.js";
6
+ import { contextDirname } from "../utils.js";
7
+ import env, { sdkEnv } from "../env.js";
8
+ import { HportalManager, showBoxInfo } from "./hportal.js";
9
+
10
+ async function initQemuVM() {
11
+ let defaultSchemeFile = path.join(
12
+ contextDirname(),
13
+ "box",
14
+ "schemes",
15
+ "vm_box_system_debian.json"
16
+ );
17
+ let scheme = JSON.parse(fs.readFileSync(defaultSchemeFile));
18
+ if (scheme.path.search(/\$HOME/) >= 0) {
19
+ let home = process.env["HOME"];
20
+ scheme.path = scheme.path.replace("$HOME", home);
21
+ }
22
+
23
+ if (scheme.path && !scheme.path.startsWith("/")) {
24
+ scheme.path = path.join(contextDirname(), "box-emulator", scheme.path);
25
+ }
26
+
27
+ // 使用本地网络 api 调试 new QemuResource(scheme.path, "http://127.0.0.1");
28
+ let resource = new QemuResource(scheme.path);
29
+ await resource.init();
30
+
31
+ let m = new QemuVM(scheme);
32
+ return m;
33
+ }
34
+
35
+ async function initHportal() {
36
+ let apiHost = "http://127.0.0.1:30553/api";
37
+ let m = new HportalManager(apiHost);
38
+ return m;
39
+ }
40
+
41
+ export function boxCommand(box) {
42
+ let subCommands = [
43
+ {
44
+ command: "create",
45
+ desc: "创建一个虚拟盒子,并注册运行",
46
+ handler: async () => {
47
+ let m = await initQemuVM();
48
+ let { boxName, boxId, user, password } = await m.createVM();
49
+ // 如果没有指定默认的盒子名称将会将第一个盒子作为默认的盒子
50
+ let defaultBoxName = env.get("DEFAULT_BOXNAME");
51
+ if (!defaultBoxName) {
52
+ // 新创建的盒子在初始化的时候,盒子里面需要安装hc的组件,需要一点时间,
53
+ // 所以这里并不对sdk url进行校验是否有效
54
+ await sdkEnv.setDefaultBoxName(boxName, false);
55
+ }
56
+
57
+ let h = await initHportal();
58
+ console.log("添加盒子到 Hportal ......");
59
+ await h.addBox(boxName);
60
+ console.log("正在使用管理员帐号密码登录中......");
61
+ await h.loginBox(boxId, user, password);
62
+ console.log("登录成功!");
63
+ },
64
+ },
65
+ {
66
+ command: "start <boxName>",
67
+ desc: "启动一个虚拟盒子",
68
+ handler: async ({ boxName }) => {
69
+ let m = await initQemuVM();
70
+ let boxid = await m.runVM(boxName);
71
+ if (boxid) {
72
+ console.log("盒子ID: ", boxid);
73
+ }
74
+ },
75
+ },
76
+ {
77
+ command: "stop <boxName>",
78
+ desc: "停止一个虚拟盒子",
79
+ handler: async ({ boxName }) => {
80
+ let m = await initQemuVM();
81
+ await m.stopVM(boxName);
82
+ },
83
+ },
84
+ {
85
+ command: "delete <boxName>",
86
+ desc: "删除一个盒子,并取消接管。如果盒子为虚拟盒子,将会删除其所具有的数据",
87
+ handler: async ({ boxName }) => {
88
+ let h = await initHportal();
89
+ let boxId = await h.getBoxId(boxName);
90
+ if (boxId) {
91
+ await h.deleteBox(boxId);
92
+ }
93
+ let m = await initQemuVM();
94
+ await m.deleteVM(boxName);
95
+ },
96
+ },
97
+ {
98
+ command: "info [boxName]",
99
+ desc: "获取一个虚拟盒子的信息",
100
+ handler: async ({ boxName }) => {
101
+ let m = await initQemuVM();
102
+ let defaultBoxName = env.get("DEFAULT_BOXNAME");
103
+
104
+ let rm = await initHportal();
105
+
106
+ let vmInfos = await m.infoVM(boxName, defaultBoxName);
107
+ let rmInfos = await rm.boxs(boxName, defaultBoxName);
108
+
109
+ // 过滤条件不满足
110
+ if (vmInfos.length == 0 && rmInfos == 0 && boxName != "") {
111
+ console.log(`${boxName} 盒子不存在`);
112
+ let allVmInfos = await m.infoVM("", defaultBoxName);
113
+ let allRmInfos = await rm.boxs("", defaultBoxName);
114
+ showBoxInfo(allVmInfos, allRmInfos);
115
+ } else {
116
+ showBoxInfo(vmInfos, rmInfos);
117
+ }
118
+ },
119
+ },
120
+ {
121
+ command: "switch <boxName>",
122
+ desc: "设置默认的盒子",
123
+ handler: async ({ boxName }) => {
124
+ await sdkEnv.setDefaultBoxName(boxName);
125
+ },
126
+ },
127
+ ];
128
+ box.command({
129
+ command: "box",
130
+ desc: "盒子管理",
131
+ builder: (args) => {
132
+ args.command(subCommands);
133
+ },
134
+ });
135
+ }