@lazycatcloud/lzc-cli 1.2.3 → 1.2.5

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
@@ -5,6 +5,7 @@
5
5
  1. `ssh`
6
6
  2. `ssh-copy-id`
7
7
  3. `rsync`
8
+ 4. 盒子中安装开发者工具应用
8
9
 
9
10
  #### 快速上手
10
11
 
@@ -37,11 +38,3 @@ lzc-cli appstore publish
37
38
  #### 1. 开发者工具 ssh 的帐号密码是多少?
38
39
 
39
40
  现在帐号密码设置为 box:box,后面会使用 pam 模块对接盒子帐号体系。
40
-
41
- #### 2. 如何切换默认的开发的盒子?
42
-
43
- 目前不支持通过 lzc-cli 工具切换默认的盒子,所以你需要在 lzc-client-desktop 客户端上点击你要使用的盒子来切换。
44
-
45
- #### 3. 如果在盒子中构建镜像? 不想在本地构建
46
-
47
- 现在已经把在盒子中构建镜像的功能去掉了,只能在本地或者其他地方构建好,然后指定 image 字段来选择你需要的开发环境。
@@ -1,4 +1,3 @@
1
- import tar from "tar";
2
1
  import path from "node:path";
3
2
  import fs from "node:fs";
4
3
  import logger from "loglevel";
@@ -9,6 +8,7 @@ import {
9
8
  dumpToYaml,
10
9
  envTemplateFile,
11
10
  isValidPackageName,
11
+ tarContentDir,
12
12
  } from "../utils.js";
13
13
  import { spawnSync } from "child_process";
14
14
  import { LpkManifest } from "./lpk_create.js";
@@ -17,29 +17,6 @@ import yaml from "js-yaml";
17
17
 
18
18
  const isMacos = process.platform == "darwin";
19
19
 
20
- async function tarContentDir(from, to, cwd = "./") {
21
- const dest = fs.createWriteStream(to);
22
- tar
23
- .c(
24
- {
25
- cwd: cwd,
26
- filter: (filePath, stat) => {
27
- logger.debug(`tar gz ${filePath}`);
28
- return true;
29
- },
30
- sync: true,
31
- portable: {
32
- uid: 0,
33
- gid: 0,
34
- },
35
- },
36
- [from]
37
- )
38
- .pipe(dest);
39
- logger.debug(`pack: ${dest.path}`);
40
- return dest.path;
41
- }
42
-
43
20
  async function archiveFolderTo(appDir, out, format = "zip") {
44
21
  return new Promise(async (resolve, reject) => {
45
22
  if (!fs.existsSync(appDir)) {
@@ -251,7 +228,11 @@ export class LpkBuild {
251
228
  return _prev;
252
229
  }, contentdir);
253
230
  }
254
- tarContentDir("./", path.join(tempDir, "content.tar"), contentdir);
231
+ await tarContentDir(
232
+ ["./"],
233
+ path.join(tempDir, "content.tar"),
234
+ contentdir
235
+ );
255
236
 
256
237
  // 如果是临时的 contentdir, 目录在打包完成后删除
257
238
  if (!this.options["contentdir"]) {
@@ -108,4 +108,23 @@ export class DebugBridge {
108
108
  });
109
109
  });
110
110
  }
111
+
112
+ async buildImage(label, contextTar) {
113
+ const tag = `debug.bridge/${label}`;
114
+ const stream = fs.createReadStream(contextTar);
115
+ const ssh = spawn(this.sshCmd, [`build --tag ${tag}`], {
116
+ shell: true,
117
+ stdio: ["pipe", "inherit", "inherit"],
118
+ });
119
+ stream.pipe(ssh.stdin);
120
+ return new Promise((resolve, reject) => {
121
+ ssh.on("close", (code) => {
122
+ code == 0
123
+ ? resolve(`dev.${this.boxname}.heiyu.space/${tag}`)
124
+ : reject("在盒子中构建 image 失败");
125
+ });
126
+ }).finally(() => {
127
+ fs.rmSync(contextTar);
128
+ });
129
+ }
111
130
  }
@@ -16,6 +16,7 @@ import {
16
16
  loadFromYaml,
17
17
  FileLocker,
18
18
  isUserApp,
19
+ createTemplateFileCommon,
19
20
  } from "../utils.js";
20
21
  import os from "node:os";
21
22
  import commandExists from "command-exists";
@@ -23,6 +24,7 @@ import chokidar from "chokidar";
23
24
  import _ from "lodash";
24
25
  import { DebugBridge } from "./lpk_debug_bridge.js";
25
26
  import shellApi from "../shellapi.js";
27
+ import { collectContextFromDockerFile } from "./lpk_devshell_docker.js";
26
28
 
27
29
  // 判断是否需要重新构建
28
30
  // - 先判断 lzc-build.yml 是否发生改变
@@ -264,7 +266,36 @@ export class AppDevShell {
264
266
  return manifest;
265
267
  }
266
268
 
267
- throw "lzc-cli 不支持在盒子中构建 docker 镜像,请从其他地方构建,然后指定image字段";
269
+ const depsStr = deps.sort().join(" ");
270
+ logger.debug("开始创建 Dockerfile 文件");
271
+
272
+ const tempDir = fs.mkdtempSync(".lzc-cli-build-dependencies");
273
+ try {
274
+ const dockerfilePath = path.join(
275
+ contextDirname(import.meta.url),
276
+ "../../template/_lpk/Dockerfile.in"
277
+ );
278
+ await createTemplateFileCommon(
279
+ dockerfilePath,
280
+ path.join(tempDir, "Dockerfile"),
281
+ { dependencies: depsStr }
282
+ );
283
+
284
+ const label = `${await md5String(depsStr)}:latest`;
285
+ logger.debug(`开始在盒子中构建 ${label} 镜像 from ${tempDir}`);
286
+
287
+ const contextTar = await collectContextFromDockerFile(
288
+ tempDir,
289
+ path.resolve(tempDir, "Dockerfile")
290
+ );
291
+ const bridge = new DebugBridge();
292
+ const tag = await bridge.buildImage(label, contextTar);
293
+ delete manifest["application"]["devshell"];
294
+ manifest["application"]["image"] = tag;
295
+ } finally {
296
+ fs.rmSync(tempDir, { recursive: true });
297
+ }
298
+ return manifest;
268
299
  });
269
300
 
270
301
  // 如果 services 中有 devshell 的字段,需要检测是否需要提前构建
@@ -279,7 +310,19 @@ export class AppDevShell {
279
310
  return manifest;
280
311
  }
281
312
 
282
- throw "lzc-cli 不支持在盒子中构建 docker 镜像,请从其他地方构建,然后指定image字段";
313
+ const label = `${manifest["package"]}-devshell:${manifest["version"]}`;
314
+ logger.debug(`开始在盒子中构建 ${label} 镜像`);
315
+
316
+ const contextTar = await collectContextFromDockerFile(
317
+ process.cwd(),
318
+ path.resolve(process.cwd(), config["build"], "Dockerfile")
319
+ );
320
+
321
+ const bridge = new DebugBridge();
322
+ const tag = await bridge.buildImage(label, contextTar);
323
+ delete manifest["application"]["devshell"];
324
+ manifest["application"]["image"] = tag;
325
+ return manifest;
283
326
  });
284
327
 
285
328
  // 如果 devshell 中指定了 image 字段将使用 image 字段
@@ -0,0 +1,55 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs";
3
+ import { DockerfileParser } from "dockerfile-ast";
4
+ import glob from "fast-glob";
5
+ import ignore from "@balena/dockerignore";
6
+ import { tarContentDir } from "../utils.js";
7
+
8
+ export async function collectContextFromDockerFile(contextDir, dockerfilePath) {
9
+ if (!fs.existsSync(dockerfilePath)) {
10
+ throw "未发现 Dockerfile";
11
+ }
12
+
13
+ let src = [path.relative(contextDir, dockerfilePath)];
14
+
15
+ // 通过 COPY 和 ADD 获取所有context中的文件
16
+ const ast = DockerfileParser.parse(fs.readFileSync(dockerfilePath, "utf8"));
17
+ for (let a of ast.getInstructions()) {
18
+ if (["COPY", "ADD"].includes(a.getInstruction())) {
19
+ const from = a.getArguments()[0].getValue().replace(/^\//, "");
20
+ const fromFullPath = path.resolve(contextDir, from);
21
+
22
+ if (fs.existsSync(fromFullPath)) {
23
+ const stat = fs.statSync(fromFullPath);
24
+ if (stat.isDirectory()) {
25
+ let files = await glob(path.join(from, "**"), {
26
+ cwd: contextDir,
27
+ });
28
+ src = src.concat(files);
29
+ } else if (stat.isFile()) {
30
+ src.push(from);
31
+ }
32
+ } else {
33
+ // try use glob
34
+ let files = await glob(from, {
35
+ cwd: contextDir,
36
+ });
37
+ src = src.concat(files);
38
+ }
39
+ }
40
+ }
41
+ // filter by dockerignore
42
+ const dockerIgnoreFile = path.join(contextDir, ".dockerignore");
43
+ if (fs.existsSync(dockerIgnoreFile)) {
44
+ let ig = ignore();
45
+ let data = fs.readFileSync(dockerIgnoreFile, "utf8");
46
+ ig.add(data.split("\n"));
47
+ src = ig.filter(src);
48
+ }
49
+
50
+ return await tarContentDir(
51
+ src,
52
+ path.join(contextDir, "lzc-build-image-context.tar"),
53
+ contextDir
54
+ );
55
+ }
@@ -1,5 +1,6 @@
1
1
  import { Publish } from "./publish.js";
2
2
  import { reLogin } from "./login.js";
3
+ import fs from "node:fs";
3
4
 
4
5
  export function appstoreCommand(program) {
5
6
  let subCommands = [
@@ -19,9 +20,17 @@ export function appstoreCommand(program) {
19
20
  describe: "更改日志",
20
21
  type: "string",
21
22
  });
23
+ args.option("F", {
24
+ alias: "file",
25
+ describe: "更改日志文件",
26
+ type: "string",
27
+ });
22
28
  },
23
- handler: async ({ pkgPath, changelog }) => {
29
+ handler: async ({ pkgPath, changelog, file }) => {
24
30
  const p = new Publish();
31
+ if (!changelog && file) {
32
+ changelog = fs.readFileSync(file, "utf8");
33
+ }
25
34
  await p.publish(pkgPath, changelog);
26
35
  },
27
36
  },
@@ -61,7 +61,8 @@ export class Publish {
61
61
  pkgPath: lpkInfo.url,
62
62
  }),
63
63
  }).then(async (res) => {
64
- if (res.status != 201) {
64
+ if (res.status >= 400) {
65
+ logger.error("发布应用出错,错误状态码为: ", res.status);
65
66
  logger.error(await res.text());
66
67
  return;
67
68
  }
@@ -143,9 +143,9 @@ message BoxSetting {
143
143
  optional bool enabled = 3;
144
144
 
145
145
  message AuthInfo {
146
- string user = 3;
147
- string password = 4;
148
- optional string password_hash = 5;
146
+ string user = 1;
147
+ string password = 2;
148
+ optional string password_hash = 3;
149
149
  }
150
150
  optional AuthInfo auth = 4;
151
151
 
@@ -227,27 +227,31 @@ message BoxInfo {
227
227
 
228
228
  string box_home_url = 3;
229
229
 
230
- string box_domain = 14;
230
+ string box_domain = 4;
231
231
 
232
- string box_virtual_ip = 10;
232
+ string box_virtual_ip = 5;
233
233
 
234
- BoxStatus status = 4;
234
+ BoxStatus status = 6;
235
235
 
236
- string status_reason = 5;
237
- optional FailedStatus failed_status = 6;
236
+ string status_reason = 7;
237
+ optional FailedStatus failed_status = 8;
238
238
 
239
- string login_user = 7;
240
- bool is_admin_login = 8;
241
-
242
- string auth_token = 9;
239
+ string login_user = 9;
240
+ bool is_admin_login = 10;
243
241
 
244
242
  // 此盒子属于当前客户端的默认盒子
245
- bool is_default_box = 15;
243
+ bool is_default_box = 11;
244
+
245
+ // 底层网络的连接类型,如果是Relay/BLE这类则客户端一般需要进行提示并且限制访问流量要求大的应用
246
+ LowNetworkConnType conn_type = 12;
247
+ }
246
248
 
247
- // 与此盒子的底层网络是否连通
248
- bool low_network_connected = 16;
249
- // 与此盒子的底层网络连通是通过中转的低速网络
250
- bool is_relay_connection = 13;
249
+ enum LowNetworkConnType {
250
+ NOT_CONNECTED = 0; //未建立连接
251
+ BLE = 100; //通过蓝牙建立的慢速连接
252
+ RELAY = 200; //通过中继建立的慢速连接
253
+ NORMAL = 300; //正常连接,可能是通过多层或一层局域网也可能是互联网等方式建立的连接
254
+ DIRECT = 400; //通过局域网等高速通道建立的连接
251
255
  }
252
256
 
253
257
  message BoxList {
@@ -275,12 +279,6 @@ message ShellCoreInfo {
275
279
 
276
280
  string device_os = 9;
277
281
 
278
- // // 设备 DNS 服务器地址
279
- // string dns_ip = 10;
280
-
281
- // // 内存占用
282
- // int32 memory_usage = 11;
283
-
284
282
  // 额外的调试信息
285
283
  map<string, string> debug_extras = 101;
286
284
  }
package/lib/utils.js CHANGED
@@ -14,6 +14,7 @@ import zlib from "node:zlib";
14
14
  import process from "node:process";
15
15
  import { spawnSync } from "node:child_process";
16
16
  import logger from "loglevel";
17
+ import tar from "tar";
17
18
 
18
19
  export const envsubstr = async (templateContents, args) => {
19
20
  const parse = await importDefault("envsub/js/envsub-parser.js");
@@ -37,6 +38,18 @@ export function ensureDir(filePath) {
37
38
  }
38
39
  }
39
40
 
41
+ export async function createTemplateFileCommon(templateFile, outputFile, env) {
42
+ const options = {
43
+ envs: toPair(env),
44
+ syntax: "default",
45
+ protect: false,
46
+ };
47
+ const output = await envsubstr(fs.readFileSync(templateFile, "utf-8"), {
48
+ options,
49
+ });
50
+ fs.writeFileSync(outputFile, output);
51
+ }
52
+
40
53
  export function loadFromYaml(file) {
41
54
  return yaml.load(fs.readFileSync(file, "utf8"));
42
55
  }
@@ -298,3 +311,33 @@ export function isValidPackageName(packageName) {
298
311
  export function isUserApp(manifest) {
299
312
  return !!manifest["application"]["user_app"];
300
313
  }
314
+
315
+ export async function tarContentDir(from, to, cwd = "./") {
316
+ return new Promise((resolve, reject) => {
317
+ const dest = fs.createWriteStream(to);
318
+ tar
319
+ .c(
320
+ {
321
+ cwd: cwd,
322
+ filter: (filePath) => {
323
+ logger.debug(`tar gz ${filePath}`);
324
+ return true;
325
+ },
326
+ sync: true,
327
+ portable: {
328
+ uid: 0,
329
+ gid: 0,
330
+ },
331
+ },
332
+ from
333
+ )
334
+ .pipe(dest)
335
+ .on("close", () => {
336
+ logger.debug(`pack: ${dest.path}`);
337
+ resolve(dest.path);
338
+ })
339
+ .on("error", (err) => {
340
+ reject(err);
341
+ });
342
+ });
343
+ }
package/package.json CHANGED
@@ -1,17 +1,7 @@
1
1
  {
2
2
  "name": "@lazycatcloud/lzc-cli",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "lazycat cloud developer kit",
5
- "scripts": {
6
- "test": "tap",
7
- "snap": "tap"
8
- },
9
- "tap": {
10
- "node-arg": [
11
- "--loader=esmock",
12
- "--experimental-vm-modules"
13
- ]
14
- },
15
5
  "files": [
16
6
  "template",
17
7
  "scripts",
@@ -27,12 +17,14 @@
27
17
  "author": "zac zeng",
28
18
  "license": "ISC",
29
19
  "dependencies": {
20
+ "@balena/dockerignore": "^1.0.2",
30
21
  "@grpc/grpc-js": "^1.8.18",
31
22
  "@grpc/proto-loader": "^0.7.8",
32
23
  "archiver": "^5.3.0",
33
24
  "chalk": "^4.1.2",
34
25
  "chokidar": "^3.5.3",
35
26
  "command-exists": "^1.2.9",
27
+ "dockerfile-ast": "^0.5.0",
36
28
  "envsub": "^4.0.7",
37
29
  "fast-glob": "^3.2.7",
38
30
  "form-data": "^4.0.0",
@@ -50,9 +42,7 @@
50
42
  },
51
43
  "devDependencies": {
52
44
  "@types/command-exists": "^1.2.0",
53
- "esmock": "^2.0.0",
54
- "prettier": "^2.5.0",
55
- "tap": "^16.3.0"
45
+ "prettier": "^2.5.0"
56
46
  },
57
47
  "publishConfig": {
58
48
  "registry": "https://registry.npmjs.org",
@@ -32,9 +32,25 @@ icon: ./lazycat.png
32
32
  # - /=http://127.0.0.1:3000
33
33
  # image: registry.lazycat.cloud/lzc-cli/devshell:0.0.4
34
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
35
47
  devshell:
36
48
  routes:
37
49
  - /=http://127.0.0.1:3000
50
+ dependencies:
51
+ - nodejs
52
+ - vim
53
+ - npm
38
54
  # setupscript 每次进入到app container后都会执行的配置脚本
39
55
  # - 可以为脚本的路径地址
40
56
  # - 如果构建命令简单,也可以直接写 sh 的命令
@@ -32,9 +32,24 @@ icon: ./lazycat.png
32
32
  # - /=http://127.0.0.1:3000
33
33
  # image: registry.lazycat.cloud/lzc-cli/devshell:0.0.4
34
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
35
47
  devshell:
36
48
  routes:
37
49
  - /=http://127.0.0.1:3000
50
+ dependencies:
51
+ - go
52
+ - vim
38
53
  # setupscript 每次进入到app container后都会执行的配置脚本
39
54
  # - 可以为脚本的路径地址
40
55
  # - 如果构建命令简单,也可以直接写 sh 的命令
@@ -32,9 +32,25 @@ icon: ./lazycat.png
32
32
  # - /=http://127.0.0.1:3000
33
33
  # image: registry.lazycat.cloud/lzc-cli/devshell:0.0.4
34
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
35
47
  devshell:
36
48
  routes:
37
49
  - /=http://127.0.0.1:3000
50
+ dependencies:
51
+ - nodejs
52
+ - vim
53
+ - npm
38
54
  # setupscript 每次进入到app container后都会执行的配置脚本
39
55
  # - 可以为脚本的路径地址
40
56
  # - 如果构建命令简单,也可以直接写 sh 的命令
@@ -17,6 +17,7 @@ pkgout: ./
17
17
  # icon 指定 lpk 包 icon 的路径路径,如果不指定将会警告
18
18
  # icon 仅仅允许 png 后缀的文件
19
19
  icon: ./lazycat.png
20
+
20
21
  # devshell 自定义应用的开发容器环境
21
22
  # - routers 指定应用容器的访问路由
22
23
 
@@ -30,3 +31,30 @@ icon: ./lazycat.png
30
31
  # routes:
31
32
  # - /=http://127.0.0.1:3000
32
33
  # 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