@lazycatcloud/lzc-cli 1.1.13 → 1.2.0

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 (39) hide show
  1. package/lib/app/index.js +33 -35
  2. package/lib/app/lpk_debug_bridge.js +101 -0
  3. package/lib/app/lpk_devshell.js +55 -114
  4. package/lib/app/lpk_installer.js +43 -40
  5. package/lib/appstore/login.js +1 -1
  6. package/lib/appstore/publish.js +18 -8
  7. package/lib/box/index.js +2 -86
  8. package/lib/env.js +18 -178
  9. package/lib/lzc_sdk.js +16 -2
  10. package/lib/shellapi.js +95 -0
  11. package/lib/shellapi.proto +301 -0
  12. package/lib/utils.js +2 -103
  13. package/package.json +3 -24
  14. package/template/_lpk/busybox-1.35.0 +0 -0
  15. package/template/_lpk/exec.sh +1 -10
  16. package/template/_lpk/init_debug_bridge.sh +24 -0
  17. package/template/_lpk/vue.lzc-build.yml.in +0 -16
  18. package/template/golang/lzc-build.yml +0 -15
  19. package/template/ionic_vue3/lzc-build.yml +0 -16
  20. package/template/ionic_vue3/package-lock.json +8100 -0
  21. package/template/lite/lzc-build.yml +0 -28
  22. package/lib/api.js +0 -155
  23. package/lib/app/lpk_devshell_docker.js +0 -211
  24. package/lib/app/lpk_log.js +0 -68
  25. package/lib/app/lpk_status.js +0 -18
  26. package/lib/app/lpk_uninstall.js +0 -19
  27. package/lib/box/api/clientapi.js +0 -1322
  28. package/lib/box/api/empty.js +0 -35
  29. package/lib/box/check_qemu.js +0 -51
  30. package/lib/box/qemu_vm_mgr.js +0 -608
  31. package/lib/box/schemes/vm_box_system_debian.json +0 -47
  32. package/lib/core.proto +0 -118
  33. package/lib/docker/promise.js +0 -91
  34. package/lib/docker-compose.js +0 -50
  35. package/lib/fetch.js +0 -50
  36. package/lib/git/git-commit.sh +0 -7
  37. package/lib/git/git-reset.sh +0 -15
  38. package/lib/key.js +0 -102
  39. package/lib/sdk.js +0 -135
@@ -17,7 +17,6 @@ pkgout: ./
17
17
  # icon 指定 lpk 包 icon 的路径路径,如果不指定将会警告
18
18
  # icon 仅仅允许 png 后缀的文件
19
19
  icon: ./lazycat.png
20
-
21
20
  # devshell 自定义应用的开发容器环境
22
21
  # - routers 指定应用容器的访问路由
23
22
 
@@ -31,30 +30,3 @@ icon: ./lazycat.png
31
30
  # routes:
32
31
  # - /=http://127.0.0.1:3000
33
32
  # image: registry.lazycat.cloud/lzc-cli/devshell:0.0.4
34
-
35
- # devshell 指定构建Dockerfile
36
- # image 字段如果没有定义,将默认使用 ${package}-devshell:${version}
37
- # devshell:
38
- # routes:
39
- # - /=http://127.0.0.1:3000
40
- # image: ${package}-devshell:${version}
41
- # pull_policy: build
42
- # build: .
43
-
44
- # dvshell 指定开发依赖的情况
45
- # 这种情况下,选用 apline:3.16 作为基础镜像,在 dependencies 中添加所需要的开发依赖即可
46
- # 如果 dependencies 和 build 同时存在,将会优先使用 dependencies
47
- # devshell:
48
- # routes:
49
- # - /=http://127.0.0.1:3000
50
- # dependencies:
51
- # - go
52
- # - vim
53
- # # setupscript 每次进入到app container后都会执行的配置脚本
54
- # # - 可以为脚本的路径地址
55
- # # - 如果构建命令简单,也可以直接写 sh 的命令
56
- # # setupscript: export GOPROXY=https://goproxy.cn
57
- # # setupscript: ./setupscript.sh
58
- # setupscript: |
59
- # export GOPROXY=https://goproxy.cn
60
- # export npm_config_registry=https://registry.npmmirror.com
package/lib/api.js DELETED
@@ -1,155 +0,0 @@
1
- import ora from "ora";
2
- import chalk from "chalk";
3
- import fs from "fs";
4
- import logger from "loglevel";
5
- import fetch from "./fetch.js";
6
-
7
- //
8
- // sdk 提供的API
9
- export default class API {
10
- constructor(appId, host, uid = "") {
11
- this.appId = appId;
12
- this.host = host;
13
- this.uid = uid;
14
- }
15
- async install(zipPath, isDevshell = false) {
16
- if (!fs.existsSync(zipPath)) {
17
- throw `${zipPath} 不存在`;
18
- }
19
- const resp = await fetch(
20
- `${this.host}/api/v1/app/install?id=${this.appId}&devshell=${isDevshell}`,
21
- {
22
- method: "POST",
23
- body: fs.createReadStream(zipPath),
24
- }
25
- );
26
- if (resp.status != 200) {
27
- throw chalk.red(await resp.text());
28
- }
29
- }
30
-
31
- async uninstall() {
32
- return await fetch(`${this.host}/api/v1/app/uninstall?id=${this.appId}`, {
33
- method: "DELETE",
34
- });
35
- }
36
-
37
- async resume() {
38
- const resp = await fetch(
39
- `${this.host}/api/v1/app/resume?id=${this.appId}&userId=${this.uid}`,
40
- {
41
- method: "POST",
42
- }
43
- );
44
- if (resp.status != 200) {
45
- const text = await resp.text();
46
- throw text;
47
- }
48
- }
49
-
50
- async isDevshell() {
51
- const resp = await fetch(
52
- `${this.host}/api/v1/app/isDevshell?id=${this.appId}`,
53
- {
54
- method: "GET",
55
- }
56
- );
57
- if (resp.status == 200) {
58
- return true;
59
- } else {
60
- const text = await resp.text();
61
- if (text) {
62
- logger.debug("isDevshell request failed", text);
63
- }
64
- return false;
65
- }
66
- }
67
-
68
- async status() {
69
- const resp = await fetch(
70
- `${this.host}/api/v1/app/status?id=${this.appId}&userId=${this.uid}`
71
- );
72
- if (resp.status == 200) {
73
- const status = await resp.json();
74
- return status;
75
- } else {
76
- let text = await resp.text();
77
- // console.error(chalk.red(`Status Code: ${resp.status} ${text}`));
78
- throw text;
79
- }
80
- }
81
-
82
- async postPublicKey(key) {
83
- const resp = await fetch(`${this.host}/api/v1/register`, {
84
- method: "POST",
85
- body: key,
86
- });
87
- if (resp.status != 200) {
88
- throw new Error(await resp.text());
89
- }
90
- }
91
-
92
- async checkStatus(resume = false) {
93
- const spinner = ora().start();
94
- let { status, info } = await this.status();
95
- spinner.text = "部署进度 " + status;
96
-
97
- switch (status) {
98
- case "running":
99
- console.log(chalk.yellow("应用正在运行中"));
100
- break;
101
- case "error":
102
- spinner.stop();
103
- logger.error("应用状态错误,请在盒子中查看对应的错误信息");
104
- throw info?.msg;
105
- default:
106
- try {
107
- await desireStatusTimer(async (result) => {
108
- spinner.text = "部署进度 " + result.status;
109
-
110
- if (result.status == "paused" && resume) {
111
- await this.resume();
112
- }
113
-
114
- return result.status === "running";
115
- }, this.status.bind(this));
116
- } catch (error) {
117
- throw error;
118
- } finally {
119
- spinner.stop();
120
- }
121
- break;
122
- }
123
- spinner.stop();
124
- }
125
- }
126
-
127
- export function desireStatusTimer(desireCheckFn, getStatusFn) {
128
- let errorCount = 0;
129
- return new Promise((resolve, reject) => {
130
- function schedule(timeout) {
131
- setTimeout(async () => {
132
- const answer = await getStatusFn();
133
- try {
134
- if (answer) {
135
- if (answer.status == "error") {
136
- throw new Error(answer.error);
137
- } else if (await desireCheckFn(answer)) {
138
- resolve(answer);
139
- } else {
140
- schedule(1000);
141
- }
142
- }
143
- } catch (e) {
144
- errorCount += 1;
145
- if (errorCount >= 3) {
146
- reject(e);
147
- return;
148
- }
149
- schedule(1000);
150
- }
151
- }, timeout);
152
- }
153
- schedule(1000);
154
- });
155
- }
@@ -1,211 +0,0 @@
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, uid = "") {
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-${uid}-${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
- }
@@ -1,68 +0,0 @@
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
- }
@@ -1,18 +0,0 @@
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
- }
@@ -1,19 +0,0 @@
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
- }