@lazycatcloud/lzc-cli 1.1.8 → 1.1.10

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 (105) hide show
  1. package/README.md +69 -11
  2. package/lib/api.js +71 -39
  3. package/lib/app/index.js +76 -21
  4. package/lib/app/lpk_build.js +95 -63
  5. package/lib/app/lpk_create.js +63 -41
  6. package/lib/app/lpk_create_generator.js +202 -0
  7. package/lib/app/lpk_devshell.js +393 -328
  8. package/lib/app/lpk_devshell_docker.js +211 -0
  9. package/lib/app/lpk_installer.js +63 -26
  10. package/lib/app/lpk_log.js +68 -0
  11. package/lib/app/lpk_status.js +18 -0
  12. package/lib/app/lpk_uninstall.js +19 -0
  13. package/lib/appstore/index.js +37 -0
  14. package/lib/appstore/login.js +96 -93
  15. package/lib/appstore/publish.js +62 -0
  16. package/lib/autologin.js +0 -78
  17. package/lib/box/api/clientapi.js +1322 -0
  18. package/lib/box/api/empty.js +35 -0
  19. package/lib/box/check_qemu.js +1 -0
  20. package/lib/box/index.js +41 -94
  21. package/lib/box/qemu_vm_mgr.js +208 -239
  22. package/lib/box/schemes/vm_box_system_debian.json +1 -1
  23. package/lib/docker-compose.js +1 -2
  24. package/lib/env.js +19 -101
  25. package/lib/key.js +1 -0
  26. package/lib/sdk.js +10 -25
  27. package/lib/utils.js +156 -132
  28. package/package.json +18 -10
  29. package/scripts/cli.js +14 -135
  30. package/template/_lpk/README.md +31 -0
  31. package/template/_lpk/exec.sh +19 -0
  32. package/template/_lpk/golang.manifest.yml.in +16 -0
  33. package/template/_lpk/lazycat.png +0 -0
  34. package/template/_lpk/lite.manifest.yml.in +19 -0
  35. package/template/_lpk/local_devshell/Dockerfile +16 -0
  36. package/template/_lpk/local_devshell/build.sh +5 -0
  37. package/template/_lpk/local_devshell/entrypoint.sh +87 -0
  38. package/template/{_lazycat/debug/shell → _lpk/local_devshell}/sshd_config +8 -8
  39. package/template/_lpk/manifest.yml.in +0 -1
  40. package/template/{vue/lzc-build.yml → _lpk/vue.lzc-build.yml.in} +9 -1
  41. package/template/golang/README.md +0 -2
  42. package/template/golang/_gitignore +2 -0
  43. package/template/golang/build.sh +6 -0
  44. package/template/golang/lazycat.png +0 -0
  45. package/template/golang/lzc-build.yml +9 -1
  46. package/template/golang/rego.go +15 -16
  47. package/template/golang/rego_test.go +13 -0
  48. package/template/ionic_vue3/lazycat.png +0 -0
  49. package/template/ionic_vue3/lzc-build.yml +9 -1
  50. package/template/lite/error_pages/502.html.tpl +13 -0
  51. package/template/lite/lazycat.png +0 -0
  52. package/template/lite/lzc-build.yml +60 -0
  53. package/cmds/app.js +0 -133
  54. package/cmds/config.js +0 -55
  55. package/cmds/create.js +0 -55
  56. package/cmds/dev.js +0 -130
  57. package/cmds/init.js +0 -125
  58. package/cmds/log.js +0 -103
  59. package/cmds/publish.js +0 -116
  60. package/lib/archiver.js +0 -105
  61. package/lib/box/hportal.js +0 -120
  62. package/lib/builder.js +0 -313
  63. package/lib/dev.js +0 -314
  64. package/lib/generator.js +0 -146
  65. package/template/_lazycat/_gitignore +0 -1
  66. package/template/_lazycat/app-config +0 -1
  67. package/template/_lazycat/debug/devforward/50x.html +0 -30
  68. package/template/_lazycat/debug/devforward/Dockerfile +0 -16
  69. package/template/_lazycat/debug/devforward/docker-compose.override.yml.in +0 -11
  70. package/template/_lazycat/debug/devforward/entrypoint.sh +0 -10
  71. package/template/_lazycat/debug/devforward/nginx.conf.template +0 -56
  72. package/template/_lazycat/debug/devforward/sshd_config +0 -116
  73. package/template/_lazycat/debug/shell/50x.html +0 -32
  74. package/template/_lazycat/debug/shell/Dockerfile +0 -18
  75. package/template/_lazycat/debug/shell/build.sh +0 -15
  76. package/template/_lazycat/debug/shell/docker-compose.override.yml.in +0 -13
  77. package/template/_lazycat/debug/shell/entrypoint.sh +0 -12
  78. package/template/_lazycat/docker-compose.yml.in +0 -15
  79. package/template/_lazycat/icon.svg +0 -1
  80. package/template/_lazycat/screenshot.png +0 -0
  81. package/template/_lpk/sync/Dockerfile +0 -16
  82. package/template/_lpk/sync/build.sh +0 -5
  83. package/template/_lpk/sync/entrypoint.sh +0 -8
  84. package/template/_lpk/sync/sshd_config +0 -117
  85. package/template/_lpk/sync.manifest.yml.in +0 -3
  86. package/template/release/golang/Dockerfile +0 -18
  87. package/template/release/golang/build.sh +0 -13
  88. package/template/release/ionic_vue3/Dockerfile +0 -10
  89. package/template/release/ionic_vue3/build.sh +0 -7
  90. package/template/release/ionic_vue3/docker-compose.yml.in +0 -3
  91. package/template/release/vue/Dockerfile +0 -10
  92. package/template/release/vue/build.sh +0 -10
  93. package/template/release/vue/docker-compose.yml.in +0 -3
  94. package/template/vue/README.md +0 -29
  95. package/template/vue/_dockerignore +0 -1
  96. package/template/vue/babel.config.js +0 -3
  97. package/template/vue/package.json +0 -43
  98. package/template/vue/public/favicon.ico +0 -0
  99. package/template/vue/public/index.html +0 -33
  100. package/template/vue/src/App.vue +0 -39
  101. package/template/vue/src/main.js +0 -8
  102. package/template/vue/src/todo.vue +0 -640
  103. package/template/vue/src/top-bar.vue +0 -100
  104. package/template/vue/src/webdav.vue +0 -183
  105. package/template/vue/vue.config.js +0 -21
@@ -0,0 +1,211 @@
1
+ // build strategies based on lzc-build.yml devshell field
2
+ import path from "node:path";
3
+ import fs from "node:fs";
4
+ import logger from "loglevel";
5
+
6
+ import { DockerClient, SSHClient, connectOptions } from "../sdk.js";
7
+ import { DockerfileParser } from "dockerfile-ast";
8
+ import glob from "fast-glob";
9
+ import ignore from "@balena/dockerignore";
10
+ import { createLogUpdate } from "log-update";
11
+ import { sdkEnv } from "../env.js";
12
+
13
+ export class SdkDocker {
14
+ constructor() {}
15
+
16
+ // builder 需要在app 的场景下使用, 不过有两个路径要注意
17
+ // 一个是 docker build 时的context. 这个一般是运行cli时候的路径
18
+ async buildImage(tag, buildDir, contextDir) {
19
+ const dockerfilePath = path.join(buildDir, "Dockerfile");
20
+ await this.dockerRemoteBuildV2(contextDir, dockerfilePath, tag);
21
+ }
22
+
23
+ async collectContextFromDockerFile(contextDir, dockerfilePath) {
24
+ if (!fs.existsSync(dockerfilePath)) {
25
+ throw "未发现 Dockerfile";
26
+ }
27
+
28
+ let src = [path.relative(contextDir, dockerfilePath)];
29
+
30
+ // 通过 COPY 和 ADD 获取所有context中的文件
31
+ const ast = DockerfileParser.parse(fs.readFileSync(dockerfilePath, "utf8"));
32
+ for (let a of ast.getInstructions()) {
33
+ if (["COPY", "ADD"].includes(a.getInstruction())) {
34
+ const from = a.getArguments()[0].getValue().replace(/^\//, "");
35
+
36
+ const fromFullPath = path.join(contextDir, from);
37
+
38
+ if (fs.existsSync(fromFullPath)) {
39
+ const stat = fs.statSync(path.join(contextDir, from));
40
+ if (stat.isDirectory()) {
41
+ let files = await glob(path.join(from, "**"), {
42
+ cwd: contextDir,
43
+ });
44
+ src = src.concat(files);
45
+ } else if (stat.isFile()) {
46
+ src.push(from);
47
+ }
48
+ } else {
49
+ // try use glob
50
+ let files = await glob(from, {
51
+ cwd: contextDir,
52
+ });
53
+ src = src.concat(files);
54
+ }
55
+ }
56
+ }
57
+ // filter by dockerignore
58
+ const dockerIgnoreFile = path.join(contextDir, ".dockerignore");
59
+ //
60
+ if (fs.existsSync(dockerIgnoreFile)) {
61
+ let ig = ignore();
62
+ let data = fs.readFileSync(dockerIgnoreFile, "utf8");
63
+ ig.add(data.split("\n"));
64
+ src = ig.filter(src);
65
+ }
66
+ return src;
67
+ }
68
+
69
+ /**
70
+ * v2 版本会收集所有需要的context 文件,将其传入buildImage 方法中
71
+ * @param contextDir
72
+ * @param dockerfile
73
+ * @param tag
74
+ */
75
+ async dockerRemoteBuildV2(contextDir, dockerfile, tag) {
76
+ const src = await this.collectContextFromDockerFile(contextDir, dockerfile);
77
+ const host = new URL(sdkEnv.sdkUrl).host;
78
+ console.log(host);
79
+ const docker = await new DockerClient(host).init();
80
+ try {
81
+ return new Promise(async (resolve, reject) => {
82
+ let refresher = new StatusRefresher();
83
+
84
+ logger.info("开始在设备中构建镜像");
85
+ const stream = await docker.buildImage(
86
+ {
87
+ context: contextDir,
88
+ src: src,
89
+ },
90
+ {
91
+ dockerfile: src[0],
92
+ t: tag,
93
+ networkmode: "home-cloud",
94
+ }
95
+ );
96
+ docker.modem.followProgress(
97
+ stream,
98
+ (err, res) => {
99
+ err ? reject(err) : resolve(res);
100
+ },
101
+ (res) => {
102
+ if (res.status) {
103
+ if (res.id) {
104
+ if (["Downloading", "Extracting"].includes(res.status)) {
105
+ refresher.updateStatus({
106
+ id: res.id,
107
+ content: `${res.status} ${res.progress}`,
108
+ });
109
+ } else {
110
+ refresher.updateStatus({
111
+ id: res.id,
112
+ content: res.status,
113
+ });
114
+ }
115
+ } else {
116
+ process.stdout.write(res.status);
117
+ process.stdout.write("\n");
118
+ }
119
+ }
120
+
121
+ if (res.stream) {
122
+ if (res.stream.startsWith("Step")) {
123
+ refresher.reset();
124
+ }
125
+ process.stdout.write(res.stream);
126
+ }
127
+ if (res.error) {
128
+ reject(res.error);
129
+ }
130
+ }
131
+ );
132
+ });
133
+ } catch (err) {
134
+ throw err;
135
+ }
136
+ }
137
+
138
+ async interactiveShell(appId, shell) {
139
+ const host = new URL(sdkEnv.sdkUrl).hostname;
140
+ const opts = await connectOptions(host);
141
+ const client = new SSHClient(opts);
142
+ await client.connect();
143
+ try {
144
+ const replacedAppId = appId.replaceAll(".", "_").replaceAll("-", "__");
145
+ const containerName = `lzc--${replacedAppId}-app-1`;
146
+ const stream = await client.exec(
147
+ `docker exec -e SHELL=${shell} -e PKGID=${appId} -ti ${containerName} /lzcapp/pkg/content/devshell/exec.sh`,
148
+ {
149
+ pty: true,
150
+ }
151
+ );
152
+
153
+ stream.setWindow(process.stdout.rows, process.stdout.columns, 0, 0);
154
+ process.stdin.setRawMode(true);
155
+ process.stdin.pipe(stream);
156
+
157
+ stream.stdout.pipe(process.stdout);
158
+ stream.stderr.pipe(process.stderr);
159
+ process.stdout.on("resize", () => {
160
+ // Let the remote end know when the local terminal has been resized
161
+ stream.setWindow(process.stdout.rows, process.stdout.columns, 0, 0);
162
+ });
163
+
164
+ await new Promise((resolve, reject) => {
165
+ stream.on("close", () => {
166
+ client.close();
167
+ resolve();
168
+ });
169
+ stream.on("error", (e) => {
170
+ logger.error("interactiveShell error", e);
171
+ reject(e);
172
+ });
173
+ });
174
+ } catch (e) {
175
+ logger.error(e);
176
+ } finally {
177
+ process.stdin.setRawMode(false);
178
+ client.close();
179
+ }
180
+ }
181
+ }
182
+
183
+ class StatusRefresher {
184
+ constructor() {
185
+ this.reset();
186
+ }
187
+ refresh() {
188
+ this.logUpdate(this.pulls.map((p) => `${p.id}: ${p.content}`).join("\n"));
189
+ }
190
+
191
+ reset() {
192
+ this.pulls = [];
193
+ this.logUpdate = createLogUpdate(process.stdout);
194
+ }
195
+
196
+ updateStatus({ id, content }) {
197
+ let index = this.pulls.findIndex((p) => p.id == id);
198
+ if (index > -1) {
199
+ this.pulls[index] = {
200
+ id: id,
201
+ content: content,
202
+ };
203
+ } else {
204
+ this.pulls.push({
205
+ id: id,
206
+ content: content,
207
+ });
208
+ }
209
+ this.refresh();
210
+ }
211
+ }
@@ -3,8 +3,9 @@ import { sdkEnv } from "../env.js";
3
3
  import logger from "loglevel";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
- import { dockerPullLzcAppsImage } from "../sdk.js";
7
6
  import Key from "../key.js";
7
+ import { loadFromYaml, Downloader, sleep } from "../utils.js";
8
+ import { spawnSync } from "node:child_process";
8
9
 
9
10
  export class LpkInstaller {
10
11
  constructor() {}
@@ -16,9 +17,6 @@ export class LpkInstaller {
16
17
  // 2. pull 镜像需要ssh key
17
18
  const k = new Key();
18
19
  await k.ensure(sdkEnv.sdkUrl);
19
-
20
- const host = new URL(sdkEnv.sdkUrl).hostname;
21
- await dockerPullLzcAppsImage(host);
22
20
  }
23
21
 
24
22
  async deploy(builder) {
@@ -29,39 +27,78 @@ export class LpkInstaller {
29
27
  let manifest = await builder.getManifest();
30
28
  let api = new BoxAPI(manifest["package"], sdkEnv.sdkUrl);
31
29
 
30
+ let pkgPath = await builder.exec("");
31
+ logger.info("开始部署应用");
32
+ await api.install(pkgPath);
33
+
34
+ // 新安装的应用默认为休眠的状态,需要先等容器创建成功,等成为 paused 后 resume 下
35
+ await api.checkStatus(true);
36
+
37
+ await sleep(2000);
38
+ logger.info(
39
+ `👉 请在浏览器中访问 ${sdkEnv.sdkUrl.replace(
40
+ /sdk/,
41
+ manifest["application"]["subdomain"]
42
+ )}`
43
+ );
44
+ }
45
+
46
+ async install(pkgUrl) {
47
+ if (pkgUrl.startsWith("http")) {
48
+ return this.installFromUrl(pkgUrl);
49
+ } else {
50
+ return this.installFromFile(pkgUrl);
51
+ }
52
+ }
53
+
54
+ async installFromUrl(url) {
55
+ let downloader = new Downloader();
56
+ const tempDir = fs.mkdtempSync(".lzc-cli-install");
32
57
  try {
33
- let pkgPath = await builder.exec("");
34
- logger.info("开始部署应用");
35
- await api.install(pkgPath);
36
- await api.checkStatus();
37
- logger.info(
38
- `请在浏览器中访问 ${sdkEnv.sdkUrl.replace(
39
- /sdk/,
40
- manifest["application"]["subdomain"]
41
- )}`
42
- );
58
+ let pkgPath = path.join(tempDir, "a.lpk");
59
+ logger.debug("开始下载lpk");
60
+ await downloader.download(url, pkgPath);
61
+ logger.debug("下载完成", pkgPath);
62
+ await this.installFromFile(pkgPath);
43
63
  } catch (e) {
44
64
  throw e;
65
+ } finally {
66
+ fs.rmSync(tempDir, { recursive: true });
45
67
  }
46
68
  }
47
69
 
48
- async install(pkgPath) {
70
+ async installFromFile(pkgPath) {
49
71
  if (!pkgPath) {
50
72
  throw "install 必须指定一个 pkg 路径";
51
73
  }
52
- let pkgNameParts = path.basename(pkgPath).split("-v");
53
- if (pkgNameParts.length < 2) {
54
- throw "lpk 文件名不合法:应该使用 $appid-$version.lpk 的格式(如 xxx-v0.0.1.lpk)"
74
+
75
+ if (!fs.existsSync(pkgPath)) {
76
+ throw `${pkgPath} 不存在`;
55
77
  }
56
- let pkgName = pkgNameParts[0]
57
- let api = new BoxAPI(pkgName, sdkEnv.sdkUrl);
78
+
79
+ const tempDir = fs.mkdtempSync(".lzc-cli-install");
80
+ let pkgName;
81
+ let manifest;
58
82
  try {
59
- logger.info("开始安装应用");
60
- await api.install(pkgPath);
61
- await api.checkStatus();
62
- logger.info(`安装成功!`);
63
- } catch (e) {
64
- throw e;
83
+ spawnSync("unzip", ["-d", tempDir, pkgPath]);
84
+
85
+ manifest = loadFromYaml(path.join(tempDir, "manifest.yml"));
86
+ logger.debug("lpk manifest", manifest);
87
+
88
+ pkgName = manifest.package;
89
+ } finally {
90
+ fs.rmSync(tempDir, { recursive: true });
65
91
  }
92
+
93
+ let api = new BoxAPI(pkgName, sdkEnv.sdkUrl);
94
+ logger.info("开始安装应用");
95
+ await api.install(pkgPath);
96
+ logger.info(`安装成功!`);
97
+ logger.info(
98
+ `👉 请在浏览器中访问 ${sdkEnv.sdkUrl.replace(
99
+ /sdk/,
100
+ manifest["application"]["subdomain"]
101
+ )}`
102
+ );
66
103
  }
67
104
  }
@@ -0,0 +1,68 @@
1
+ import { readFileSync } from "fs";
2
+ import { Client } from "ssh2";
3
+ import os from "os";
4
+ import path from "path";
5
+ import process from "process";
6
+ import { sdkEnv } from "../env.js";
7
+ import { SSHClient, connectOptions } from "../sdk.js";
8
+ import logger from "loglevel";
9
+
10
+ function StreamPromise(stream) {
11
+ let data = "";
12
+ return new Promise((resolve, reject) => {
13
+ stream
14
+ .on("data", (d) => {
15
+ data = d;
16
+ })
17
+ .on("close", (code, signal) => {
18
+ resolve(String(data));
19
+ })
20
+ .stderr.on("data", (data) => {
21
+ reject(String(data));
22
+ });
23
+ });
24
+ }
25
+
26
+ export class LpkLogger {
27
+ constructor() {}
28
+
29
+ async init() {
30
+ await sdkEnv.ensure();
31
+ }
32
+
33
+ async start(project, follow = false) {
34
+ const host = new URL(sdkEnv.sdkUrl).hostname;
35
+ const opts = await connectOptions(host);
36
+ const client = new SSHClient(opts);
37
+ await client.connect();
38
+ try {
39
+ const replacedAppId = project.replaceAll(".", "_").replaceAll("-", "__");
40
+ const findStream = await client.exec(
41
+ `docker-compose ls | grep ${replacedAppId} | head -n 1`
42
+ );
43
+ const result = await StreamPromise(findStream);
44
+ if (result == "") {
45
+ throw `${project} 服务没有找到`;
46
+ }
47
+
48
+ const name = result.split(" ")[0];
49
+ const _f = follow ? "-f " : " ";
50
+ const stream = await client.exec(`docker-compose -p ${name} logs ${_f}`, {
51
+ pty: true,
52
+ });
53
+ await new Promise((resolve, reject) => {
54
+ stream.stdout.pipe(process.stdout);
55
+ stream.stderr.pipe(process.stdout);
56
+ stream.on("close", () => {
57
+ client.close();
58
+ resolve();
59
+ });
60
+ stream.on("error", (e) => reject(e));
61
+ });
62
+ } catch (e) {
63
+ logger.error(e);
64
+ } finally {
65
+ client.close();
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,18 @@
1
+ import BoxAPI from "../api.js";
2
+ import { sdkEnv } from "../env.js";
3
+ import logger from "loglevel";
4
+
5
+ export class LpkStatuser {
6
+ constructor() {}
7
+
8
+ async init() {
9
+ // 1. 确保 sdk 已经安装
10
+ await sdkEnv.ensure();
11
+ }
12
+
13
+ async status(pkgId) {
14
+ let api = new BoxAPI(pkgId, sdkEnv.sdkUrl);
15
+ let { status } = await api.status();
16
+ logger.info(status);
17
+ }
18
+ }
@@ -0,0 +1,19 @@
1
+ import BoxAPI from "../api.js";
2
+ import { sdkEnv } from "../env.js";
3
+ import logger from "loglevel";
4
+
5
+ export class LpkUninstaller {
6
+ constructor() {}
7
+
8
+ async init() {
9
+ // 1. 确保 sdk 已经安装
10
+ await sdkEnv.ensure();
11
+ }
12
+
13
+ async uninstall(pkgId) {
14
+ logger.info(`开始卸载${pkgId}`);
15
+ let api = new BoxAPI(pkgId, sdkEnv.sdkUrl);
16
+ await api.uninstall();
17
+ logger.info(`卸载成功!`);
18
+ }
19
+ }
@@ -0,0 +1,37 @@
1
+ import { Publish } from "./publish.js";
2
+ import { reLogin } from "./login.js";
3
+
4
+ export function appstoreCommand(program) {
5
+ let subCommands = [
6
+ {
7
+ command: "login",
8
+ desc: "登录",
9
+ handler: async () => {
10
+ await reLogin();
11
+ },
12
+ },
13
+ {
14
+ command: "publish <pkgPath>",
15
+ desc: "发布到商店",
16
+ builder: (args) => {
17
+ args.option("c", {
18
+ alias: "changelog",
19
+ describe: "更改日志",
20
+ type: "string",
21
+ });
22
+ },
23
+ handler: async ({ pkgPath, changelog }) => {
24
+ const p = new Publish();
25
+ await p.publish(pkgPath, changelog);
26
+ },
27
+ },
28
+ ];
29
+
30
+ program.command({
31
+ command: "appstore",
32
+ desc: "应用商店",
33
+ builder: (args) => {
34
+ args.command(subCommands);
35
+ },
36
+ });
37
+ }