@lazycatcloud/lzc-cli 1.1.12 → 1.1.13

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/lib/api.js CHANGED
@@ -2,7 +2,7 @@ import ora from "ora";
2
2
  import chalk from "chalk";
3
3
  import fs from "fs";
4
4
  import logger from "loglevel";
5
- import fetch from "node-fetch";
5
+ import fetch from "./fetch.js";
6
6
 
7
7
  //
8
8
  // sdk 提供的API
@@ -12,12 +12,12 @@ export default class API {
12
12
  this.host = host;
13
13
  this.uid = uid;
14
14
  }
15
- async install(zipPath) {
15
+ async install(zipPath, isDevshell = false) {
16
16
  if (!fs.existsSync(zipPath)) {
17
17
  throw `${zipPath} 不存在`;
18
18
  }
19
19
  const resp = await fetch(
20
- `${this.host}/api/v1/app/install?id=${this.appId}`,
20
+ `${this.host}/api/v1/app/install?id=${this.appId}&devshell=${isDevshell}`,
21
21
  {
22
22
  method: "POST",
23
23
  body: fs.createReadStream(zipPath),
package/lib/app/index.js CHANGED
@@ -6,6 +6,9 @@ import { LpkInstaller } from "./lpk_installer.js";
6
6
  import { LpkUninstaller } from "./lpk_uninstall.js";
7
7
  import { LpkStatuser } from "./lpk_status.js";
8
8
  import { LpkLogger } from "./lpk_log.js";
9
+ import logger from "loglevel";
10
+ import fetch from "../fetch.js";
11
+ import { sdkEnv } from "../env.js";
9
12
 
10
13
  export function lpkProjectCommand(program) {
11
14
  let subCommands = [
@@ -72,9 +75,27 @@ export function lpkProjectCommand(program) {
72
75
  type: "string",
73
76
  default: "lzc-build.yml",
74
77
  });
78
+ args.option("contentdir", {
79
+ describe: "同时打包 lzc-build.yml 中指定的 contentdir 目录",
80
+ type: "boolean",
81
+ });
75
82
  },
76
- handler: async ({ context, shell, force, config }) => {
77
- const app = new AppDevShell(context, config, force);
83
+ handler: async ({ context, shell, force, config, contentdir }) => {
84
+ const cwd = context ? path.resolve(context) : process.cwd();
85
+ const lpkBuild = await new LpkBuild(cwd, config).init();
86
+ lpkBuild.onBeforeBuildPackage(async (options) => {
87
+ // devshell 正常情况下,不需要执行 buildscript 和 contentdir 字段
88
+ logger.debug("devshell delete 'buildscript' field");
89
+ delete options["buildscript"];
90
+
91
+ if (!contentdir) {
92
+ logger.debug("devshell delete 'contentdir' field");
93
+ delete options["contentdir"];
94
+ }
95
+ return options;
96
+ });
97
+ await fetch(`${sdkEnv.sdkUrl}/api/v1/ping`);
98
+ const app = new AppDevShell(cwd, lpkBuild, force);
78
99
  await app.init();
79
100
  await app.build();
80
101
  await app.rsyncShell(shell);
@@ -8,6 +8,7 @@ import {
8
8
  isFileExist,
9
9
  dumpToYaml,
10
10
  envTemplateFile,
11
+ isValidPackageName,
11
12
  } from "../utils.js";
12
13
  import { spawnSync } from "child_process";
13
14
  import { LpkManifest } from "./lpk_create.js";
@@ -130,11 +131,6 @@ function convenientEnv() {
130
131
  );
131
132
  }
132
133
 
133
- function isValidPackageName(packageName) {
134
- const regex = /^([a-zA-Z_][a-zA-Z0-9_]*\.)*[a-zA-Z_][a-zA-Z0-9_]*$/;
135
- return regex.test(packageName);
136
- }
137
-
138
134
  export class LpkBuild {
139
135
  constructor(cwd, lzcBuild = "lzc-build.yml") {
140
136
  this.pwd = cwd ?? process.cwd();
@@ -7,6 +7,7 @@ import {
7
7
  ensureDir,
8
8
  dumpToYaml,
9
9
  isFileExist,
10
+ isValidPackageName,
10
11
  } from "../utils.js";
11
12
  import path from "node:path";
12
13
  import { TemplateConfig } from "./lpk_create_generator.js";
@@ -74,9 +75,14 @@ export class LpkManifest {
74
75
  {
75
76
  type: "input",
76
77
  name: "package",
77
- message: "请输入应用ID",
78
- default: this.defaultAppID,
79
- validate: noEmpty,
78
+ message: "请输入应用ID, 如 cloud.lazycat.app.video",
79
+ validate: (input) => {
80
+ if (isValidPackageName(input)) {
81
+ return true;
82
+ } else {
83
+ return "应用ID错误,请输入正确的格式, 如 cloud.lazycat.app.video";
84
+ }
85
+ },
80
86
  },
81
87
  {
82
88
  type: "input",
@@ -129,19 +129,14 @@ class AppDevShellMonitor {
129
129
  }
130
130
 
131
131
  export class AppDevShell {
132
- constructor(cwd, config, forceBuild = false) {
133
- this.cwd = cwd ? path.resolve(cwd) : process.cwd();
134
- this.lpkBuild = undefined;
132
+ constructor(cwd, lpkBuild, forceBuild = false) {
133
+ this.cwd = cwd;
134
+ this.lpkBuild = lpkBuild;
135
135
  this.monitor = undefined;
136
136
  this.forceBuild = forceBuild;
137
- this.lpkBuildConfig = config;
138
137
  }
139
138
 
140
139
  async init() {
141
- if (!this.lpkBuild) {
142
- this.lpkBuild = new LpkBuild(this.cwd, this.lpkBuildConfig);
143
- await this.lpkBuild.init();
144
- }
145
140
  const manifest = await this.lpkBuild.getManifest();
146
141
  const uid = await getUidByManifest(manifest);
147
142
  this.monitor = await new AppDevShellMonitor(
@@ -165,13 +160,7 @@ export class AppDevShell {
165
160
  await k.ensure(sdkEnv.sdkUrl);
166
161
  const pairs = await k.getKeyPair();
167
162
 
168
- // devshell 不需要执行 buildscript 和 contentdir 也不需要
169
163
  this.lpkBuild.onBeforeBuildPackage(async (options) => {
170
- logger.debug("devshell delete 'buildscript' field");
171
- logger.debug("devshell delete 'contentdir' field");
172
- delete options["buildscript"];
173
- delete options["contentdir"];
174
-
175
164
  const devshell = options["devshell"];
176
165
  if (!devshell) {
177
166
  throw "devshell 模式下,devshell 字段必须要指定";
@@ -371,7 +360,7 @@ export class AppDevShell {
371
360
  // 在构建生成 lpk 包后,调用 deploy 进行部署
372
361
  let installer = new LpkInstaller();
373
362
  await installer.init();
374
- await installer.deploy(this.lpkBuild);
363
+ await installer.deploy(this.lpkBuild, true);
375
364
 
376
365
  await sleep(2000);
377
366
  }
@@ -444,11 +433,11 @@ class DevShell {
444
433
 
445
434
  let rsyncDebug = process.env.RSYNCDEBUG ? "-P" : "";
446
435
  const userId = this.uid ? `/${this.uid}` : "";
447
- let storePath = `/run/lzc_boot/data/cache/${appId}${userId}/devshell`;
436
+ let storePath = `/lzcsys/run/data/app/cache/${appId}${userId}/devshell`;
448
437
  // FIXME: 下方执行命令不确定是否有兼容性问题
449
438
  try {
450
439
  execSync(
451
- `rsync ${rsyncDebug} --rsh='${rsh}' --recursive --relative --perms --update . ${host}:${storePath} --filter=':- .gitignore' --exclude='node_modules' --exclude='.git' --ignore-errors --usermap=:nobody --groupmap=*:nobody`,
440
+ `rsync ${rsyncDebug} --rsh='${rsh}' --recursive --relative --perms --update . ${host}:${storePath} --filter=':- .gitignore' --ignore-errors --usermap=:nobody --groupmap=*:nobody`,
452
441
  { stdio: ["ignore", "inherit", "inherit"] }
453
442
  );
454
443
  } catch (err) {
@@ -4,10 +4,9 @@ import logger from "loglevel";
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
6
  import Key from "../key.js";
7
- import { loadFromYaml, Downloader, sleep } from "../utils.js";
7
+ import { loadFromYaml, Downloader } from "../utils.js";
8
8
  import { spawnSync } from "node:child_process";
9
9
  import { getUidByManifest } from "../lzc_sdk.js";
10
- import fetch from "node-fetch";
11
10
 
12
11
  export class LpkInstaller {
13
12
  constructor() {}
@@ -21,7 +20,8 @@ export class LpkInstaller {
21
20
  await k.ensure(sdkEnv.sdkUrl);
22
21
  }
23
22
 
24
- async deploy(builder) {
23
+ // TODO: 将 devshell 的判断逻辑放在 builder 中判断
24
+ async deploy(builder, isDevshell = false) {
25
25
  if (!builder) {
26
26
  throw "deploy 必须传递一个 builder";
27
27
  }
@@ -32,24 +32,14 @@ export class LpkInstaller {
32
32
 
33
33
  let pkgPath = await builder.exec("");
34
34
  logger.info("开始部署应用");
35
- await api.install(pkgPath);
35
+ await api.install(pkgPath, isDevshell);
36
36
 
37
37
  const appUrl = sdkEnv.sdkUrl.replace(
38
38
  /sdk/,
39
39
  manifest["application"]["subdomain"]
40
40
  );
41
- // 如果是多实例的应用,需要先访问下,才会创建对应的容器
42
- if (manifest["application"]["user_app"]) {
43
- const res = await fetch(appUrl);
44
- if (res.status >= 400) {
45
- throw `访问 ${appUrl} 失败`;
46
- }
47
- }
48
-
49
41
  // 新安装的应用默认为休眠的状态,需要先等容器创建成功,等成为 paused 后 resume 下
50
42
  await api.checkStatus(true);
51
-
52
- await sleep(2000);
53
43
  logger.info(`👉 请在浏览器中访问 ${appUrl}`);
54
44
  }
55
45
 
@@ -1,4 +1,4 @@
1
- import fetch from "node-fetch";
1
+ import fetch from "../fetch.js";
2
2
  import path from "path";
3
3
  import logger from "loglevel";
4
4
  import env from "../env.js";
@@ -5,7 +5,7 @@ import path from "node:path";
5
5
  import inquirer from "inquirer";
6
6
  import process from "node:process";
7
7
  import net from "node:net";
8
- import fetch from "node-fetch";
8
+ import fetch from "../fetch.js";
9
9
  import os from "node:os";
10
10
  import logger from "loglevel";
11
11
  import glob from "fast-glob";
package/lib/core.proto ADDED
@@ -0,0 +1,118 @@
1
+ syntax = "proto3";
2
+
3
+ package space.heiyu.hportal.shell;
4
+
5
+ import "google/protobuf/empty.proto";
6
+
7
+ // 此接口为hclient&hserver曝露给管理界面、命令行客户端等使用的API
8
+ service ShellCore {
9
+ // 查询当前盒子列表的状态
10
+ rpc QueryBoxList(google.protobuf.Empty) returns(BoxList) {}
11
+ // 添加、修改盒子的登陆信息、开启、关闭盒子等
12
+ rpc ModifyBox(BoxSetting) returns (google.protobuf.Empty);
13
+ }
14
+
15
+ // 盒子只会处于以下四种状态之一
16
+ enum BoxStatus {
17
+ // 此盒子当前未激活
18
+ DISABLED = 0;
19
+
20
+ // 正在连接中,此过程持续时间无法评估,
21
+ // 可能瞬间结束也可能持续一两分钟(在确实可以连接上的前提下)
22
+ CONNECTING = 1;
23
+
24
+ // 成功连接上了
25
+ CONNECTED = 2;
26
+
27
+ // 确定无法连接,此时不会继续重试,需要客户端
28
+ // 修正环境后,重新调用ModifyBox后继续尝试。
29
+ FAILED = 3;
30
+ }
31
+
32
+ enum ConnectingStatus {
33
+ AWAIT_NEW_DEVICE_AUTH = 0;
34
+ }
35
+
36
+ enum FailedStatus {
37
+ // 未知原因失败
38
+ FAILED_UNKNOWN = 0;
39
+
40
+ // 当前盒子无管理员帐号,需要调用CreateUser创建管理员帐号
41
+ FAILED_NO_ADMINUSER = 1;
42
+
43
+ // 当前盒子未注册盒子名称,需要调用SetupNewBox向horigin申请盒子名称以及配套资源
44
+ FAILED_NO_BOXNAME = 2;
45
+
46
+ // 当前登陆信息不正确,需要调用ModifyBox设置新的auth信息
47
+ FAILED_INVALID_PASSWORD = 3;
48
+
49
+ // 当前客户端与服务器版本不匹配,无法连接,需要升级盒子系统或客户端软件
50
+ FAILED_MISMATCH_BOX_VERSION = 4;
51
+
52
+ // 当前设备被拒绝登录
53
+ FAILED_DEVICE_AUTH_REJECT = 5;
54
+
55
+
56
+ //------ 100+的失败状态一般为本机特有错误状态
57
+
58
+ //调用LoginBox的过程中,此盒子的状态被其他API请求转换为disabled了
59
+ FAILED_LOCAL_DISABLED_BOX = 100;
60
+
61
+ //调用LoginBox时使用了一个不存在的盒子名称。(若只是本地不盒子列表不存在此盒子,会自动添加到盒子列表)
62
+ FAILED_LOCAL_NO_SUCH_BOX_IN_THE_WORLD = 101;
63
+
64
+ //使用LoginBox时,登陆超时
65
+ FAILED_LOCAL_CONNECTING_TIMEOUT = 102;
66
+ }
67
+
68
+ enum ExceptionStatus {
69
+ OK = 0;
70
+ Error = 1;
71
+ Booting = 2;
72
+ }
73
+
74
+ message BoxInfo {
75
+ string box_id = 1;
76
+ string box_name = 2;
77
+ string box_home_url = 3 [deprecated = true];
78
+ string box_domain = 14;
79
+ string box_virtual_ip = 10;
80
+
81
+ BoxStatus status = 4;
82
+ string status_reason = 5;
83
+ optional FailedStatus failed_status = 6;
84
+ optional ConnectingStatus connecting_status = 12; // deprecated
85
+ ExceptionStatus exception_status = 11;
86
+
87
+ bool is_relay_connection = 13;
88
+
89
+ string login_user = 7;
90
+ bool is_admin_login = 8;
91
+ string auth_token = 9;
92
+
93
+ bool is_default_box = 15;
94
+ }
95
+
96
+ message BoxList {
97
+ repeated BoxInfo boxes = 1;
98
+ }
99
+
100
+ message BoxSetting {
101
+ string id = 1;
102
+
103
+ optional string name = 2;
104
+
105
+ // 若不设置则使用当前值
106
+ optional bool enabled = 3;
107
+
108
+ message AuthInfo {
109
+ string user = 3;
110
+ string password = 4;
111
+ optional string verification_code = 5;
112
+ }
113
+ optional AuthInfo auth = 4;
114
+
115
+ //设置此盒子为默认盒子,会覆盖其他(如果有)默认盒子的配置
116
+ //第一个添加的盒子会自动成为默认盒子
117
+ optional bool set_as_default_box = 5;
118
+ }
package/lib/env.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  import inquirer from "inquirer";
10
10
  import chalk from "chalk";
11
11
  import logger from "loglevel";
12
- import fetch from "node-fetch";
12
+ import fetch from "./fetch.js";
13
13
 
14
14
  const GLOBAL_CONFIG_NAME = "box-config.json";
15
15
  const allPermitEnv = [
@@ -188,7 +188,7 @@ class SDKEnv {
188
188
  }
189
189
 
190
190
  buildSdkUrl(boxname) {
191
- return `http://sdk.${boxname}.heiyu.space`;
191
+ return `https://sdk.${boxname}.heiyu.space`;
192
192
  }
193
193
 
194
194
  /**
package/lib/fetch.js ADDED
@@ -0,0 +1,50 @@
1
+ import nodeFetch from "node-fetch";
2
+ import makeFetchCookie from "fetch-cookie";
3
+ import { FileCookieStore } from "tough-cookie-file-store";
4
+ import inquirer from "inquirer";
5
+ import env from "./env.js";
6
+ import path from "node:path";
7
+ import { GLOBAL_CONFIG_DIR } from "./utils.js";
8
+
9
+ const cookieFetch = makeFetchCookie(
10
+ nodeFetch,
11
+ new makeFetchCookie.toughCookie.CookieJar(
12
+ new FileCookieStore(path.resolve(GLOBAL_CONFIG_DIR, "cookie.json"))
13
+ )
14
+ );
15
+ const fetch = async (url, options) => {
16
+ const resp = await cookieFetch(url, options);
17
+ if (resp.status == 401) {
18
+ await login();
19
+ }
20
+ return resp;
21
+ };
22
+
23
+ export async function login() {
24
+ const boxname = env.get("DEFAULT_BOXNAME");
25
+ const boxUrl = `https://${boxname}.heiyu.space`;
26
+
27
+ const questions = [
28
+ {
29
+ name: "username",
30
+ type: "input",
31
+ message: "请输入帐号名",
32
+ },
33
+ {
34
+ type: "password",
35
+ mask: "*",
36
+ name: "password",
37
+ message: "请输入登录密码",
38
+ },
39
+ ];
40
+ const answers = await inquirer.prompt(questions);
41
+ await fetch(`${boxUrl}/sys/login?type=plain`, {
42
+ headers: {
43
+ "content-type": "application/x-www-form-urlencoded",
44
+ },
45
+ body: `username=${answers.username}&password=${answers.password}`,
46
+ method: "POST",
47
+ });
48
+ }
49
+
50
+ export default fetch;
package/lib/lzc_sdk.js CHANGED
@@ -1,25 +1,69 @@
1
- import os from "node:os";
1
+ import grpc from "@grpc/grpc-js";
2
+ import protoLoader from "@grpc/proto-loader";
3
+ import { contextDirname } from "./utils.js";
2
4
  import path from "node:path";
3
- import fs from "node:fs";
5
+ import env from "./env.js";
4
6
  import logger from "loglevel";
7
+ import os from "node:os";
8
+ import fs from "node:fs";
5
9
 
6
- const lzcConfigFile = path.resolve(
7
- os.homedir(),
8
- ".config",
9
- "lzc-client-desktop",
10
- "defaultBox.json"
10
+ const SHELLAPI_CONFIG_DIR = path.join(os.homedir(), "/.config/hportal-client");
11
+ const coreDefinition = protoLoader.loadSync(
12
+ path.join(contextDirname(), "./core.proto"),
13
+ {
14
+ keepCase: true,
15
+ longs: String,
16
+ enums: String,
17
+ defaults: true,
18
+ oneofs: true,
19
+ }
11
20
  );
21
+ const core =
22
+ grpc.loadPackageDefinition(coreDefinition).space.heiyu.hportal.shell;
23
+
24
+ let client;
25
+
26
+ function readShellApiInfo() {
27
+ const addr = fs.readFileSync(
28
+ path.resolve(SHELLAPI_CONFIG_DIR, "shellapi_addr"),
29
+ { encoding: "utf-8" }
30
+ );
31
+ const cred = fs.readFileSync(
32
+ path.resolve(SHELLAPI_CONFIG_DIR, "shellapi_cred"),
33
+ { encoding: "utf-8" }
34
+ );
35
+ return { addr, cred };
36
+ }
12
37
 
13
- let lzcConfig = undefined;
14
38
  export async function getUidByManifest(manifest) {
15
- if (manifest["application"]["user_app"]) {
16
- if (!lzcConfig) {
17
- lzcConfig = JSON.parse(
18
- fs.readFileSync(lzcConfigFile, { encoding: "utf8" })
19
- );
20
- logger.debug("loginUser: ", lzcConfig["loginUser"]);
21
- }
22
- return lzcConfig["loginUser"];
39
+ if (!manifest["application"]["user_app"]) {
40
+ return "";
41
+ }
42
+
43
+ const { addr, cred } = readShellApiInfo();
44
+
45
+ if (!client) {
46
+ client = new core.ShellCore(addr, grpc.credentials.createInsecure());
23
47
  }
24
- return "";
48
+
49
+ const metadata = new grpc.Metadata();
50
+ metadata.add("lzc-shellapi-cred", cred);
51
+
52
+ const boxName = env.get("DEFAULT_BOXNAME");
53
+ return new Promise((resolve, reject) => {
54
+ client.queryBoxList({}, metadata, function (err, response) {
55
+ if (err) {
56
+ reject(err);
57
+ return;
58
+ }
59
+ for (let box of response.boxes) {
60
+ if (box.box_name == boxName) {
61
+ logger.debug("当前登录用户: ", box.login_user);
62
+ resolve(box.login_user);
63
+ return;
64
+ }
65
+ }
66
+ reject("没有默认盒子信息");
67
+ });
68
+ });
25
69
  }
package/lib/utils.js CHANGED
@@ -390,3 +390,8 @@ export class FileLocker {
390
390
  }
391
391
  }
392
392
  }
393
+
394
+ export function isValidPackageName(packageName) {
395
+ const regex = new RegExp("^(?:[a-z][a-z0-9_]*\\.)+[a-z][a-z0-9_]*$");
396
+ return regex.test(packageName);
397
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazycatcloud/lzc-cli",
3
- "version": "1.1.12",
3
+ "version": "1.1.13",
4
4
  "description": "lazycat cloud developer kit",
5
5
  "scripts": {
6
6
  "test": "tap",
@@ -29,9 +29,10 @@
29
29
  "license": "ISC",
30
30
  "dependencies": {
31
31
  "@balena/dockerignore": "^1.0.2",
32
+ "@grpc/grpc-js": "^1.8.12",
33
+ "@grpc/proto-loader": "^0.7.6",
32
34
  "@improbable-eng/grpc-web": "^0.15.0",
33
35
  "@improbable-eng/grpc-web-node-http-transport": "^0.15.0",
34
- "@lazycatcloud/sdk": "^0.1.120",
35
36
  "archiver": "^5.3.0",
36
37
  "browser-headers": "^0.4.1",
37
38
  "chalk": "^4.1.2",
@@ -43,6 +44,7 @@
43
44
  "envsub": "^4.0.7",
44
45
  "execa": "^5.1.1",
45
46
  "fast-glob": "^3.2.7",
47
+ "fetch-cookie": "^2.1.0",
46
48
  "form-data": "^4.0.0",
47
49
  "ignore": "^5.2.0",
48
50
  "inquirer": "^8.2.0",
@@ -64,6 +66,7 @@
64
66
  "ssh2": "^1.5.0",
65
67
  "ssh2-promise": "^1.0.2",
66
68
  "tar": "^6.1.11",
69
+ "tough-cookie-file-store": "^2.0.3",
67
70
  "urllib": "^3.1.0",
68
71
  "yargs": "^17.5.1"
69
72
  },
package/scripts/cli.js CHANGED
@@ -96,14 +96,14 @@ lpkAppCommand(program);
96
96
  lpkProjectCommand(program);
97
97
  appstoreCommand(program);
98
98
 
99
- const parser = program
100
- .strict()
101
- .showHelpOnFail(false, "使用 lzc-cli help 查看更多帮助")
102
- .middleware([setLoggerLevel])
103
- .parse();
104
-
105
99
  // 当没有参数的时候,默认显示帮助。
106
100
  (async () => {
101
+ const parser = program
102
+ .strict()
103
+ .showHelpOnFail(false, "使用 lzc-cli help 查看更多帮助")
104
+ .middleware([setLoggerLevel])
105
+ .parse();
106
+
107
107
  const argv = await parser;
108
108
  if (argv._.length == 1) {
109
109
  switch (argv._[0]) {