@lazycatcloud/lzc-cli 1.2.20 → 1.2.22

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.
@@ -78,7 +78,7 @@ async function fetchIconTo(options, cwd, destDir) {
78
78
  // 提供一些方便的环境变量,可以在 lzc-build.yml 中直接使用
79
79
  // - LocalIP 本地局域网ip
80
80
  function localIp() {
81
- const regex = /inet6 (fc03:1136:[0-9a-fA-F:]+)\/\d+ scope global/;
81
+ const regex = /inet6 (fc03:1136:[0-9a-fA-F:]+)[?:\/ ]/;
82
82
 
83
83
  let output = "";
84
84
  if (isMacos) {
@@ -2,22 +2,27 @@ import { spawn, spawnSync } from "child_process";
2
2
  import fs from "node:fs";
3
3
  import shellApi from "../shellapi.js";
4
4
  import inquirer from "inquirer";
5
+ import { isDebugMode } from "../utils.js";
5
6
 
6
7
  export class DebugBridge {
7
8
  constructor() {
8
9
  this.uid = shellApi.uid;
9
10
  this.boxname = shellApi.boxname;
10
- this.sshCmd = `ssh -p 22222 box@dev.${this.boxname}.heiyu.space`;
11
+ this.sshCmd = `ssh ${isDebugMode() ? "-vv" : ""} -p 22222 box@dev.${
12
+ this.boxname
13
+ }.heiyu.space`;
11
14
  }
12
15
 
13
16
  common(cmd, args) {
14
17
  const ssh = spawnSync(cmd, args, {
15
18
  shell: true,
16
19
  encoding: "utf-8",
17
- stdio: "pipe",
20
+ stdio: ["pipe", "pipe", "inherit"],
18
21
  });
19
22
  return new Promise((resolve, reject) => {
20
- ssh.status == 0 ? resolve(ssh.stdout) : reject(ssh.stderr);
23
+ ssh.status == 0
24
+ ? resolve(ssh.stdout)
25
+ : reject(`执行命令 ${cmd} ${args.join(" ")} 出错\n${ssh.stderr ?? ""}`);
21
26
  });
22
27
  }
23
28
 
@@ -17,6 +17,7 @@ import {
17
17
  FileLocker,
18
18
  isUserApp,
19
19
  createTemplateFileCommon,
20
+ isDebugMode,
20
21
  } from "../utils.js";
21
22
  import os from "node:os";
22
23
  import commandExists from "command-exists";
@@ -418,7 +419,7 @@ class DevShell {
418
419
  "-o", `"UserKnownHostsFile=/dev/null"`,
419
420
  "-o", `"ConnectionAttempts=3"`,
420
421
  "-o", `"ConnectTimeout=30"`,
421
- "-o", `"LogLevel=ERROR"`,
422
+ "-o", `${isDebugMode() ? '"LogLevel=DEBUG"' :'"LogLevel=ERROR"'}`,
422
423
  ].join(" ");
423
424
  // 检查rsync工具是否存在:提示用户
424
425
  const rsyncExisted = commandExists.sync("rsync");
@@ -427,14 +428,14 @@ class DevShell {
427
428
  process.exit(1);
428
429
  }
429
430
 
430
- const rsyncDebug = process.env.RSYNCDEBUG ? "-P" : "";
431
+ const rsyncDebug = isDebugMode() ? "-P" : "";
431
432
  const host = this.isUserApp
432
433
  ? `${shellApi.uid}.app.${appId}.lzcapp`
433
434
  : `app.${appId}.lzcapp`;
434
435
  const dest = `root@${host}:/lzcapp/cache/devshell`;
435
436
  try {
436
437
  execSync(
437
- `rsync ${rsyncDebug} --rsh='${rsh}' --recursive --relative --perms --update --filter=':- .gitignore' --ignore-errors . ${dest}`,
438
+ `rsync ${rsyncDebug} --rsh='${rsh}' --recursive --relative --perms --update -F --filter=':- .gitignore' --ignore-errors . ${dest}`,
438
439
  { stdio: ["ignore", "inherit", "inherit"] }
439
440
  );
440
441
  } catch (err) {
@@ -10,7 +10,7 @@ export async function collectContextFromDockerFile(contextDir, dockerfilePath) {
10
10
  throw "未发现 Dockerfile";
11
11
  }
12
12
 
13
- let src = [path.relative(contextDir, dockerfilePath)];
13
+ let src = [];
14
14
 
15
15
  // 通过 COPY 和 ADD 获取所有context中的文件
16
16
  const ast = DockerfileParser.parse(fs.readFileSync(dockerfilePath, "utf8"));
@@ -47,9 +47,8 @@ export async function collectContextFromDockerFile(contextDir, dockerfilePath) {
47
47
  src = ig.filter(src);
48
48
  }
49
49
 
50
- return await tarContentDir(
51
- src,
52
- path.join(contextDir, "lzc-build-image-context.tar"),
53
- contextDir
54
- );
50
+ const dockerfile = path.relative(contextDir, dockerfilePath);
51
+ src.push(dockerfile); // DON'T IGNORE DOCKERFILE
52
+ const output = path.join(contextDir, "lzc-build-image-context.tar");
53
+ return await tarContentDir(src, output, contextDir);
55
54
  }
@@ -1,4 +1,5 @@
1
1
  import { Publish } from "./publish.js";
2
+ import { PrePublish } from "./prePublish.js";
2
3
  import { reLogin } from "./login.js";
3
4
  import fs from "node:fs";
4
5
 
@@ -11,6 +12,34 @@ export function appstoreCommand(program) {
11
12
  await reLogin();
12
13
  },
13
14
  },
15
+ {
16
+ command: "pre-publish <pkgPath>",
17
+ desc: "发布到内测",
18
+ builder: (args) => {
19
+ args.option("c", {
20
+ alias: "changelog",
21
+ describe: "更改日志",
22
+ type: "string",
23
+ });
24
+ args.option("F", {
25
+ alias: "file",
26
+ describe: "更改日志文件",
27
+ type: "string",
28
+ });
29
+ args.option("G", {
30
+ alias: "gid",
31
+ describe: "内测组ID",
32
+ type: "string",
33
+ });
34
+ },
35
+ handler: async ({ pkgPath, changelog, file, gid }) => {
36
+ const p = new PrePublish();
37
+ if (!changelog && file) {
38
+ changelog = fs.readFileSync(file, "utf8");
39
+ }
40
+ await p.publish(pkgPath, changelog, gid);
41
+ },
42
+ },
14
43
  {
15
44
  command: "publish <pkgPath>",
16
45
  desc: "发布到商店",
@@ -0,0 +1,145 @@
1
+ import { request, autoLogin } from "./login.js";
2
+ import logger from "loglevel";
3
+ import FormData from "form-data";
4
+ import fs from "node:fs";
5
+ import inquirer from "inquirer";
6
+ import { spawnSync } from "node:child_process";
7
+ import path from "node:path";
8
+ import { isFileExist, isPngWithFile } from "../utils.js";
9
+ import env from "../env.js";
10
+
11
+ async function askChangeLog() {
12
+ const noEmpty = (value) => value != "";
13
+ return await inquirer.prompt([
14
+ {
15
+ name: "changelog",
16
+ type: "editor",
17
+ message: "填写 changelog 内容",
18
+ validate: noEmpty,
19
+ },
20
+ ]);
21
+ }
22
+
23
+ async function askGroup(choices) {
24
+ return (
25
+ await inquirer.prompt([
26
+ {
27
+ name: "type",
28
+ message: "选择内测组",
29
+ type: "list",
30
+ choices,
31
+ },
32
+ ])
33
+ )["type"];
34
+ }
35
+
36
+ export class PrePublish {
37
+ constructor(baseUrl = "https://testflight.lazycat.cloud/api") {
38
+ this.baseUrl = baseUrl;
39
+ }
40
+
41
+ /**
42
+ * @param {string} raw
43
+ */
44
+ isJSON(raw) {
45
+ const ml = raw.length;
46
+ if (ml <= 1) return false;
47
+ return raw[0] == "{" && raw[ml - 1] == "}";
48
+ }
49
+
50
+ preCheck(pkgPath) {
51
+ const tempDir = fs.mkdtempSync(".lzc-cli-publish");
52
+ try {
53
+ spawnSync("unzip", ["-d", tempDir, pkgPath]);
54
+ if (isFileExist(path.join(tempDir, "devshell"))) {
55
+ logger.error(
56
+ "不能发布一个devshell的版本,请重新使用 `lzc-cli project build` 构建"
57
+ );
58
+ return false;
59
+ }
60
+ const tempIcon = path.join(tempDir, "icon.png");
61
+ if (!isFileExist(tempIcon) || !isPngWithFile(tempIcon)) {
62
+ logger.error("icon 必须是 png 格式");
63
+ return false;
64
+ }
65
+ return true;
66
+ } finally {
67
+ fs.rmSync(tempDir, { recursive: true });
68
+ }
69
+ }
70
+
71
+ async getDict() {
72
+ const url = this.baseUrl + "/groups/dict";
73
+ const token = env.get("token");
74
+ const res = await request(url, {
75
+ method: "GET",
76
+ headers: {
77
+ Authorization: `Bearer ${token}`,
78
+ },
79
+ });
80
+ const text = (await res.text()).trim();
81
+ if (!this.isJSON(text)) {
82
+ logger.error(`parse error: dict resp text not is json`);
83
+ return;
84
+ }
85
+ const respJson = await JSON.parse(text);
86
+ return respJson.data || [];
87
+ }
88
+
89
+ async upload(groupId, changelog, pkgPath) {
90
+ const form = new FormData();
91
+ form.append("type", "Lpk");
92
+ form.append("changelog", changelog);
93
+ form.append("file", fs.createReadStream(pkgPath));
94
+
95
+ const url = this.baseUrl + `/group/${groupId}/upload`;
96
+ const token = env.get("token");
97
+ const res = await request(url, {
98
+ method: "POST",
99
+ body: form,
100
+ headers: {
101
+ Authorization: `Bearer ${token}`,
102
+ },
103
+ });
104
+ const text = (await res.text()).trim();
105
+ if (!this.isJSON(text)) {
106
+ logger.error(`parse error: upload resp text not is json`);
107
+ return;
108
+ }
109
+ const respJson = await JSON.parse(text);
110
+ logger.debug("upload lpk response", respJson);
111
+ return respJson;
112
+ }
113
+
114
+ /**
115
+ * @param {string} pkgPath
116
+ * @param {string} changelog
117
+ */
118
+ async publish(pkgPath, changelog, gid) {
119
+ if (!this.preCheck(pkgPath)) return;
120
+
121
+ await autoLogin();
122
+
123
+ if (!gid) {
124
+ const groups = await this.getDict();
125
+ const groupName = await askGroup(groups.map((it) => it.name));
126
+ gid = groups.find((it) => it.name == groupName)?.id;
127
+ }
128
+ if (!gid) {
129
+ logger.error("请选择内测组");
130
+ return;
131
+ }
132
+ if (!changelog) {
133
+ const answer = await askChangeLog();
134
+ changelog = answer.changelog;
135
+ }
136
+ changelog = changelog.trim(); // clean space ^:)
137
+ logger.info("正在提交内测...");
138
+ const resp = await this.upload(gid, changelog, pkgPath);
139
+ if (resp.success) {
140
+ logger.info("应用提交成功! 请在内测工具中查看");
141
+ } else {
142
+ logger.error(`应用提交失败: ${resp.msg}`);
143
+ }
144
+ }
145
+ }
@@ -25,12 +25,12 @@ export class Publish {
25
25
  }
26
26
 
27
27
  /**
28
- * @param {string} raw
28
+ * @param {string} raw
29
29
  */
30
30
  isJSON(raw) {
31
- const ml = raw.length
32
- if (ml <= 1) return false
33
- return raw[0] == '{' && raw[ml - 1] == '}'
31
+ const ml = raw.length;
32
+ if (ml <= 1) return false;
33
+ return raw[0] == "{" && raw[ml - 1] == "}";
34
34
  }
35
35
 
36
36
  preCheck(pkgPath) {
@@ -38,26 +38,28 @@ export class Publish {
38
38
  try {
39
39
  spawnSync("unzip", ["-d", tempDir, pkgPath]);
40
40
  if (isFileExist(path.join(tempDir, "devshell"))) {
41
- logger.error("不能发布一个devshell的版本,请重新使用 `lzc-cli project build` 构建")
42
- return false
41
+ logger.error(
42
+ "不能发布一个devshell的版本,请重新使用 `lzc-cli project build` 构建"
43
+ );
44
+ return false;
43
45
  }
44
- const tempIcon = path.join(tempDir, "icon.png")
46
+ const tempIcon = path.join(tempDir, "icon.png");
45
47
  if (!isFileExist(tempIcon) || !isPngWithFile(tempIcon)) {
46
- logger.error("icon 必须是 png 格式")
47
- return false
48
+ logger.error("icon 必须是 png 格式");
49
+ return false;
48
50
  }
49
- return true
51
+ return true;
50
52
  } finally {
51
53
  fs.rmSync(tempDir, { recursive: true });
52
54
  }
53
55
  }
54
56
 
55
57
  /**
56
- * @param {string} pkgPath
57
- * @param {string} changelog
58
+ * @param {string} pkgPath
59
+ * @param {string} changelog
58
60
  */
59
61
  async publish(pkgPath, changelog) {
60
- if (!this.preCheck(pkgPath)) return
62
+ if (!this.preCheck(pkgPath)) return;
61
63
 
62
64
  await autoLogin();
63
65
 
@@ -65,39 +67,40 @@ export class Publish {
65
67
  const answer = await askChangeLog();
66
68
  changelog = answer.changelog;
67
69
  }
68
- changelog = changelog.trim() // clean space ^:)
70
+ changelog = changelog.trim(); // clean space ^:)
69
71
 
70
72
  logger.info("正在提交审核...");
71
73
  const form = new FormData();
72
74
  form.append("file", fs.createReadStream(pkgPath));
73
75
 
74
- const uploadURL = this.baseUrl + "/upload_lpk"
75
- logger.debug("upload url is", uploadURL)
76
+ const uploadURL = this.baseUrl + "/upload_lpk";
77
+ logger.debug("upload url is", uploadURL);
76
78
 
77
79
  const res = await request(uploadURL, {
78
80
  method: "POST",
79
81
  body: form,
80
82
  });
81
- const text = (await res.text()).trim()
83
+ const text = (await res.text()).trim();
82
84
  if (!this.isJSON(text)) {
83
85
  logger.info("upload lpk fail", text);
84
- return
86
+ return;
85
87
  }
86
- const lpkInfo = await JSON.parse(text)
88
+ const lpkInfo = await JSON.parse(text);
87
89
  logger.debug("upload lpk response", lpkInfo);
88
90
 
89
- const sendURL = this.baseUrl + `/apps/${lpkInfo.package}/reviews`
90
- logger.debug("publish url is", sendURL)
91
+ const sendURL = this.baseUrl + `/apps/${lpkInfo.package}/reviews`;
92
+ logger.debug("publish url is", sendURL);
91
93
 
92
94
  const formData = {
93
95
  changelog,
96
+ name: lpkInfo.name,
94
97
  iconPath: lpkInfo.iconPath,
95
98
  pkgPath: lpkInfo.url,
96
99
  supportPC: lpkInfo.supportPC,
97
100
  supportMobile: lpkInfo.supportMobile,
98
- }
101
+ };
99
102
 
100
- logger.debug("form data is", formData)
103
+ logger.debug("form data is", formData);
101
104
 
102
105
  return request(sendURL, {
103
106
  method: "POST",
package/lib/shellapi.js CHANGED
@@ -113,7 +113,7 @@ class ShellApi {
113
113
  }
114
114
  })
115
115
  .catch(() => {
116
- logger.warn(`你的懒猫微服开发者工具版本较低,请从商店中更新`);
116
+ logger.debug(`你的懒猫微服开发者工具版本较低,请从商店中更新`);
117
117
  });
118
118
  }
119
119
 
package/lib/utils.js CHANGED
@@ -345,25 +345,31 @@ export async function tarContentDir(from, to, cwd = "./") {
345
345
  /**
346
346
  * check buffer is png(magic header)
347
347
  * refer: https://github.com/sindresorhus/is-png/blob/main/index.js
348
- * @param {Buffer} buffer
348
+ * @param {Buffer} buffer
349
349
  * @returns {boolean}
350
350
  */
351
351
  function isPngWithBuffer(buffer) {
352
- if (!buffer || buffer.length < 8) {
353
- return false;
354
- }
355
- return buffer[0] === 0x89
356
- && buffer[1] === 0x50
357
- && buffer[2] === 0x4E
358
- && buffer[3] === 0x47
359
- && buffer[4] === 0x0D
360
- && buffer[5] === 0x0A
361
- && buffer[6] === 0x1A
362
- && buffer[7] === 0x0A;
352
+ if (!buffer || buffer.length < 8) {
353
+ return false;
354
+ }
355
+ return (
356
+ buffer[0] === 0x89 &&
357
+ buffer[1] === 0x50 &&
358
+ buffer[2] === 0x4e &&
359
+ buffer[3] === 0x47 &&
360
+ buffer[4] === 0x0d &&
361
+ buffer[5] === 0x0a &&
362
+ buffer[6] === 0x1a &&
363
+ buffer[7] === 0x0a
364
+ );
363
365
  }
364
366
 
365
367
  export function isPngWithFile(filepath) {
366
- if (!isFileExist(filepath)) return false
367
- const buf = fs.readFileSync(filepath)
368
- return isPngWithBuffer(buf)
369
- }
368
+ if (!isFileExist(filepath)) return false;
369
+ const buf = fs.readFileSync(filepath);
370
+ return isPngWithBuffer(buf);
371
+ }
372
+
373
+ export function isDebugMode() {
374
+ return logger.getLevel() <= logger.levels.DEBUG;
375
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazycatcloud/lzc-cli",
3
- "version": "1.2.20",
3
+ "version": "1.2.22",
4
4
  "description": "lazycat cloud developer kit",
5
5
  "files": [
6
6
  "template",