@lazycatcloud/lzc-cli 1.1.1 → 1.1.2

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 +38 -30
  9. package/lib/env.js +276 -57
  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 -11
  14. package/lib/sdk.js +7 -10
  15. package/lib/utils.js +149 -53
  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/env.js CHANGED
@@ -1,46 +1,182 @@
1
1
  import path from "path";
2
2
  import fs from "fs";
3
- import os from "os";
4
- import { ensureDir, APP_CONFIG_FILE, GLOBAL_CONFIG_DIR } from "./utils.js";
3
+ import {
4
+ ensureDir,
5
+ APP_CONFIG_FILE,
6
+ GLOBAL_CONFIG_DIR,
7
+ APP_FOLDER,
8
+ findAppIsInstalled,
9
+ checkSDKInstallStatus,
10
+ InstallSDK,
11
+ } from "./utils.js";
12
+ import { debuglog } from "util";
13
+ import inquirer from "inquirer";
14
+ import chalk from "chalk";
15
+ import fetch from "node-fetch";
16
+ const debug = debuglog("lib/env");
5
17
 
6
18
  const GLOBAL_CONFIG_NAME = "box-config.json";
19
+ const permitGlobalEnv = [
20
+ {
21
+ name: "DEFAULT_BOXNAME",
22
+ type: "input",
23
+ message: "默认的懒猫云盒子名称",
24
+ validate: (input) => {
25
+ return input != "";
26
+ },
27
+ },
28
+ ];
7
29
 
8
- const permitVars = [
9
- "APP_ID",
10
- "APP_NAME",
11
- "APP_IMAGE_NAME",
12
- "BUILD_CONTEXT",
13
- "HTTP_SERVICE_PORT",
14
- "DEV_PORT",
15
- "DEV_CMD",
16
- "SDK_URL",
30
+ const _permitLocalEnv = [
31
+ {
32
+ name: "APP_ID",
33
+ type: "input",
34
+ message: "应用名称",
35
+ },
36
+ {
37
+ name: "APP_DESCRIPTION",
38
+ type: "input",
39
+ message: "应用描述",
40
+ },
41
+ {
42
+ name: "BUILD_CONTEXT",
43
+ message: "",
44
+ type: "input",
45
+ },
46
+ {
47
+ name: "DEV_CONTEXT",
48
+ message: "",
49
+ type: "input",
50
+ },
51
+ {
52
+ name: "APP_VERSION",
53
+ message: "应用版本号",
54
+ type: "input",
55
+ },
56
+ {
57
+ name: "HTTP_SERVICE_PORT",
58
+ message: "服务暴露端口",
59
+ type: "input",
60
+ validate: (input) => {
61
+ if (!/^[^0]\d+$/.test(input)) {
62
+ return "端口必须为数字";
63
+ }
64
+ return true;
65
+ },
66
+ },
17
67
  ];
18
68
 
19
- const permitGlobalVars = ["SDK_URL"];
69
+ const permitLocalEnv = _permitLocalEnv.concat(permitGlobalEnv);
20
70
 
21
- class Env {
22
- constructor(cwd) {
23
- this.envPath = path.join(cwd, APP_CONFIG_FILE);
24
- this.globalEnvPath = path.join(
25
- GLOBAL_CONFIG_DIR,
26
- GLOBAL_CONFIG_NAME
27
- );
28
- this.load();
71
+ const allPermits = _permitLocalEnv.concat(permitGlobalEnv);
72
+
73
+ const controller = new AbortController();
74
+
75
+ /**
76
+ * 检查URL是否能被ping通
77
+ * @param url 要检查的URL
78
+ * @param suffix 可选,与URL拼接的后缀
79
+ **/
80
+ async function checkURL(url, suffix = "") {
81
+ const timeout = setTimeout(() => {
82
+ controller.abort();
83
+ }, 5000);
84
+
85
+ try {
86
+ const resp = await fetch(url + suffix, { signal: controller.signal });
87
+ if (resp.status != 200) {
88
+ // 设备可以访问 (client 有正常连接), 但是sdk 这个服务无法访问
89
+ throw new Error(
90
+ chalk.red(
91
+ `无法连接 sdk 服务, 请确保 ${chalk.yellow(
92
+ new URL(url).origin
93
+ )} 可以访问或者在应用商店安装 sdk`
94
+ )
95
+ );
96
+ }
97
+ } catch (error) {
98
+ switch (error.name) {
99
+ case "FetchError":
100
+ console.log(chalk.red("盒子入口地址有误,请核对后再试"));
101
+ process.exit();
102
+ case "AbortError":
103
+ console.log(chalk.red(`请求 ${url} 超时, 请确保懒猫云客户端已连接`));
104
+ process.exit();
105
+ default:
106
+ throw error;
107
+ }
108
+ } finally {
109
+ clearTimeout(timeout);
29
110
  }
111
+ }
112
+
113
+ const allPermitEnv = permitGlobalEnv
114
+ .concat(permitLocalEnv)
115
+ .map((e) => e.name)
116
+ .filter((value, index, self) => self.indexOf(value) == index);
30
117
 
31
- load() {
118
+ class Env {
119
+ constructor(cwd) {
120
+ // 只加载
121
+ this.existance = {
122
+ global: false,
123
+ local: false,
124
+ };
32
125
  this.allEnv = {};
33
- this.env = {};
34
- this.globalEnv = {};
35
126
 
36
- if (fs.existsSync(this.envPath)) {
37
- this.env = JSON.parse(fs.readFileSync(this.envPath));
38
- }
127
+ this._loadGlobalEnv();
128
+ this._updateAllEnv();
129
+ }
39
130
 
40
- if (fs.existsSync(this.globalEnvPath)) {
41
- this.globalEnv = JSON.parse(fs.readFileSync(this.globalEnvPath));
131
+ /**
132
+ * 加载应用环境变量.
133
+ * @param {string} cwd 当前路径
134
+ */
135
+ load(cwd) {
136
+ this.localEnvPath = path.join(cwd, APP_FOLDER, APP_CONFIG_FILE);
137
+ try {
138
+ this.localEnv = JSON.parse(fs.readFileSync(this.localEnvPath));
139
+ this.existance.local = true;
140
+ } catch (e) {
141
+ debug("local env fail to fetch %s", e.message);
142
+ this.localEnv = {};
42
143
  }
43
- this.updateAllEnv();
144
+
145
+ this._updateAllEnv();
146
+ }
147
+
148
+ /**
149
+ * 确保字段存在, 如果不存在会要求用户输入
150
+ * @param pairs {array} 需要询问的环境变量, 同 inquirer.prompt 参数
151
+ * @param global {boolean} 是否设置为全局变量, 默认为 false
152
+ */
153
+ async ensure(pairs, global = false) {
154
+ let willAskPairs = [];
155
+
156
+ pairs.forEach((pair) => {
157
+ if (this.allEnv.hasOwnProperty(pair.name)) return;
158
+ let found = allPermits.find((p) => p.name == pair.name);
159
+ if (found) {
160
+ willAskPairs.push(Object.assign({}, found, pair));
161
+ }
162
+ });
163
+
164
+ const pair = await inquirer.prompt(willAskPairs);
165
+ let pairNames = pairs.map((a) => a.name);
166
+ this.set(pair, global);
167
+ return Object.fromEntries(
168
+ Object.entries(this.allEnv).filter(([name, _]) => {
169
+ return pairNames.includes(name);
170
+ })
171
+ );
172
+ }
173
+
174
+ stringify() {
175
+ let content = "";
176
+ Object.keys(this.all).forEach((key) => {
177
+ content += `${key}="${this.allEnv[key]}"\n`;
178
+ });
179
+ return content.trimEnd();
44
180
  }
45
181
 
46
182
  get isEnvExist() {
@@ -49,12 +185,20 @@ class Env {
49
185
 
50
186
  get all() {
51
187
  return Object.keys(this.allEnv).reduce((p, c) => {
52
- if (permitVars.includes(c)) p[c] = this.allEnv[c];
188
+ if (allPermitEnv.includes(c)) p[c] = this.allEnv[c];
53
189
  return p;
54
190
  }, {});
55
191
  }
56
192
 
57
- hasKey(key) {
193
+ get localEnvExist() {
194
+ return this.existance.local;
195
+ }
196
+
197
+ get globalEnvExist() {
198
+ return this.existance.global;
199
+ }
200
+
201
+ has(key) {
58
202
  return Object.keys(this.allEnv).indexOf(key) > -1;
59
203
  }
60
204
 
@@ -62,42 +206,117 @@ class Env {
62
206
  return this.allEnv[key];
63
207
  }
64
208
 
65
- updateAllEnv() {
66
- this.allEnv = Object.assign({}, this.globalEnv, this.env);
209
+ set(pairs, global = false) {
210
+ const configPath = global ? this.globalEnvPath : this.localEnvPath;
211
+ let envs = global ? this.globalEnv : this.localEnv;
212
+ ensureDir(configPath);
213
+
214
+ Object.assign(envs, pairs);
215
+ fs.writeFileSync(configPath, JSON.stringify(envs, null, " "));
216
+ return this._updateAllEnv();
67
217
  }
68
218
 
69
- setGlobal(pairs = {}) {
70
- const configPath = this.globalEnvPath;
71
- if (Object.keys(pairs).some((k) => permitGlobalVars.indexOf(k) < 0)) {
72
- return;
219
+ _updateAllEnv() {
220
+ return (this.allEnv = Object.assign({}, this.globalEnv, this.localEnv));
221
+ }
222
+
223
+ _loadGlobalEnv() {
224
+ this.globalEnvPath = path.join(GLOBAL_CONFIG_DIR, GLOBAL_CONFIG_NAME);
225
+
226
+ try {
227
+ this.globalEnv = JSON.parse(fs.readFileSync(this.globalEnvPath));
228
+ this.existance.global = true;
229
+ } catch (e) {
230
+ debug("global env fail to fetch %s", e.message);
231
+ this.globalEnv = {};
73
232
  }
233
+ }
234
+ }
74
235
 
75
- ensureDir(configPath);
76
- Object.assign(this.globalEnv, pairs);
77
- fs.writeFileSync(configPath, JSON.stringify(this.globalEnv, null, " "));
78
- this.updateAllEnv();
236
+ const env = new Env();
237
+
238
+ // 用于处理 SDK_URL 这个特殊的环境变量
239
+ // SDK_URL 根据默认的盒子名称生成,如果没有指定默认的盒子名称将会
240
+ // 询问用户输入对应的盒子名称。
241
+ class SDKEnv {
242
+ get sdkUrl() {
243
+ if (env.has("DEFAULT_BOXNAME")) {
244
+ return this.buildSdkUrl(env.get("DEFAULT_BOXNAME"));
245
+ }
246
+ return "";
247
+ }
248
+
249
+ async ensure() {
250
+ if (this.sdkUrl) {
251
+ try {
252
+ // 另一种是SDK尚未安装,不过会被catch捕获掉,并安装sdk
253
+ await this.checkConnection(this.sdkUrl);
254
+ } catch (e) {
255
+ // sdk未安装
256
+ await this.installSDK(this.sdkUrl);
257
+ }
258
+ } else {
259
+ await this.setDefaultBoxName();
260
+ }
79
261
  }
80
262
 
81
- set(pairs = {}) {
82
- if (Object.keys(pairs).some((k) => permitVars.indexOf(k) < 0)) {
83
- return;
263
+ async setDefaultBoxName(boxname = "", ensureSDK = true) {
264
+ if (!boxname) {
265
+ boxname = await this._promptAnswers();
84
266
  }
267
+ let url = this.buildSdkUrl(boxname);
85
268
 
86
- ensureDir(this.envPath);
87
- Object.assign(this.env, pairs);
88
- fs.writeFileSync(this.envPath, JSON.stringify(this.env, null, " "));
89
- this.updateAllEnv();
269
+ if (ensureSDK) {
270
+ await this.installSDK(url);
271
+ }
272
+
273
+ env.set({ DEFAULT_BOXNAME: boxname, SDK_URL: url }, true);
90
274
  }
91
275
 
92
- stringify() {
93
- let content = "";
94
- Object.keys(this.all).forEach((key) => {
95
- content += `${key}="${this.allEnv[key]}"\n`;
96
- });
97
- return content.trimEnd();
276
+ buildSdkUrl(boxname) {
277
+ return `https://sdk.${boxname}.heiyu.space`;
98
278
  }
99
- }
100
279
 
101
- export default function (cwd) {
102
- return new Env(cwd);
280
+ async checkConnection(host) {
281
+ return await checkURL(host, "/api/v1/ping");
282
+ }
283
+
284
+ /**
285
+ * 确保sdk已安装
286
+ * @param need_answer {boolean} 是否需要询问用户地址
287
+ */
288
+ async installSDK(sdkURL) {
289
+ // 无法直接从环境变量拿到SDK_URL,就让用户输入
290
+ debug(sdkURL);
291
+
292
+ let boxURL = sdkURL.replace("sdk.", "");
293
+
294
+ try {
295
+ if (await findAppIsInstalled(boxURL, "sdk")) {
296
+ return;
297
+ }
298
+
299
+ console.log(chalk.yellow("检测到SDK尚未安装,开始安装SDK"));
300
+ await InstallSDK(boxURL);
301
+ console.log(chalk.green("SDK安装完成,开始部署SDK"));
302
+
303
+ await checkSDKInstallStatus(boxURL);
304
+ } catch (e) {
305
+ throw e;
306
+ }
307
+ }
308
+
309
+ /**
310
+ * 提示用户回答问题
311
+ *
312
+ */
313
+ async _promptAnswers() {
314
+ const answers = await inquirer.prompt(
315
+ permitGlobalEnv.filter((a) => a.name == "DEFAULT_BOXNAME")
316
+ );
317
+ return answers["DEFAULT_BOXNAME"];
318
+ }
103
319
  }
320
+
321
+ export const sdkEnv = new SDKEnv();
322
+ export default env;
package/lib/generator.js CHANGED
@@ -9,6 +9,37 @@ import chalk from "chalk";
9
9
  const __dirname = contextDirname();
10
10
 
11
11
  export const TemplateConfig = {
12
+ ionic_vue3: {
13
+ template: "ionic_vue3",
14
+ defaultEnvs: {
15
+ HTTP_SERVICE_PORT: "8080",
16
+ BUILD_CONTEXT: ".lazycat/release",
17
+ },
18
+ after: async function (name) {
19
+ console.log(chalk.green("开始安装依赖"));
20
+
21
+ const subprocess = execa("npm", ["install"], {
22
+ cwd: path.join(process.cwd(), name),
23
+ stdio: "inherit",
24
+ });
25
+ await subprocess;
26
+ console.log(
27
+ chalk.green(
28
+ `
29
+ ✨ 懒猫云应用 ${name} 已创建:
30
+ ${chalk.yellow(`cd ${name}`)}
31
+ ${chalk.yellow(`npm run dev`)}
32
+ 将应用部署至设备中:
33
+ ${chalk.yellow(`lzc-cli deploy`)}
34
+ 生成android代码:
35
+ ${chalk.yellow(`npm run build_android`)}
36
+ 生成ios代码:
37
+ ${chalk.yellow(`npm run build_ios`)}
38
+ `.trim()
39
+ )
40
+ );
41
+ },
42
+ },
12
43
  vue: {
13
44
  template: "vue",
14
45
  defaultEnvs: {
@@ -0,0 +1,7 @@
1
+ # executed under appdb
2
+
3
+ git fetch origin
4
+ git reset --hard
5
+
6
+ git add ${APP_ID}
7
+ git commit -m "[lzc-cli publish] bump ${APP_ID} version to ${APP_VERSION}"
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+
3
+ FOLDER="$APPDB_DIR"
4
+
5
+ if [ ! -d "$FOLDER" ] ; then
6
+ git clone --depth 1 git@gitee.com:lazycatcloud/appdb.git $FOLDER
7
+ fi
8
+ #
9
+ cd $FOLDER
10
+ #
11
+ git fetch && git reset --hard origin/v0.1
12
+ #
13
+ #
14
+
15
+
package/lib/key.js CHANGED
@@ -6,9 +6,11 @@ import process from "process";
6
6
  import fs from "fs";
7
7
  import execa from "execa";
8
8
  import API from "./api.js";
9
- import { GLOBAL_CONFIG_DIR } from "./utils.js";
9
+ import { ensureDir } from "./utils.js";
10
+ import os from "os";
10
11
 
11
- const KEY_NAME = "lzc_box_key";
12
+ const KEY_NAME = "id_ed25519";
13
+ const SSH_CONFIG_DIR = path.join(os.homedir(), ".ssh");
12
14
 
13
15
  export default class Key {
14
16
  // 确保该秘钥可以工作
@@ -18,7 +20,8 @@ export default class Key {
18
20
  }
19
21
 
20
22
  async generate() {
21
- const privateKeyFile = path.join(GLOBAL_CONFIG_DIR, KEY_NAME);
23
+ const privateKeyFile = path.join(SSH_CONFIG_DIR, KEY_NAME);
24
+ ensureDir(privateKeyFile);
22
25
  await execa(
23
26
  "ssh-keygen",
24
27
  ["-t", "ed25519", "-P", "", "-f", privateKeyFile],
@@ -85,14 +88,14 @@ export default class Key {
85
88
  }
86
89
 
87
90
  async getKeyPair() {
88
- const results = await glob(path.join(GLOBAL_CONFIG_DIR, `/${KEY_NAME}*`));
89
- if (results.length != 2) {
90
- return await this.generate();
91
+ const privateKey = path.join(SSH_CONFIG_DIR, KEY_NAME);
92
+ const publicKey = path.join(SSH_CONFIG_DIR, `${KEY_NAME}.pub`);
93
+ if (fs.existsSync(privateKey) && fs.existsSync(publicKey)) {
94
+ return {
95
+ private: privateKey,
96
+ public: publicKey,
97
+ };
91
98
  }
92
-
93
- return {
94
- private: path.join(GLOBAL_CONFIG_DIR, KEY_NAME),
95
- public: path.join(GLOBAL_CONFIG_DIR, `${KEY_NAME}.pub`),
96
- };
99
+ return await this.generate();
97
100
  }
98
101
  }
package/lib/sdk.js CHANGED
@@ -1,18 +1,13 @@
1
1
  import ssh from "docker-modem/lib/ssh.js";
2
2
  // import Docker from "./docker/promise.js";
3
3
  import Docker from "dockerode";
4
- import path from "path";
5
- import os from "os";
6
4
  import process from "process";
7
5
  import fs from "fs";
8
6
  import { Client } from "ssh2";
9
7
  import chalk from "chalk";
10
- import glob from "fast-glob";
11
- import execa from "execa";
12
- import API from "./api.js";
13
8
  import Key from "./key.js";
14
9
 
15
- async function connectOptions(host) {
10
+ export async function connectOptions(host) {
16
11
  const pairs = await new Key().getKeyPair();
17
12
  return {
18
13
  host: host,
@@ -41,10 +36,12 @@ export class DockerClient {
41
36
  }
42
37
 
43
38
  async init() {
44
- this.docker = new Docker({
45
- protocal: "http",
46
- agent: ssh(await connectOptions(this.host)),
47
- });
39
+ this.docker = !!this.host
40
+ ? new Docker({
41
+ protocal: "http",
42
+ agent: ssh(await connectOptions(this.host)),
43
+ })
44
+ : new Docker();
48
45
 
49
46
  const host = this.host;
50
47