@lazycatcloud/lzc-cli 1.2.21 → 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.
- package/lib/app/lpk_debug_bridge.js +8 -3
- package/lib/app/lpk_devshell.js +4 -3
- package/lib/app/lpk_devshell_docker.js +5 -6
- package/lib/appstore/index.js +29 -0
- package/lib/appstore/prePublish.js +145 -0
- package/lib/appstore/publish.js +26 -23
- package/lib/shellapi.js +1 -4
- package/lib/utils.js +22 -16
- package/package.json +1 -1
|
@@ -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.${
|
|
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
|
|
23
|
+
ssh.status == 0
|
|
24
|
+
? resolve(ssh.stdout)
|
|
25
|
+
: reject(`执行命令 ${cmd} ${args.join(" ")} 出错\n${ssh.stderr ?? ""}`);
|
|
21
26
|
});
|
|
22
27
|
}
|
|
23
28
|
|
package/lib/app/lpk_devshell.js
CHANGED
|
@@ -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",
|
|
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 =
|
|
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 = [
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
}
|
package/lib/appstore/index.js
CHANGED
|
@@ -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
|
+
}
|
package/lib/appstore/publish.js
CHANGED
|
@@ -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] ==
|
|
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(
|
|
42
|
-
|
|
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
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
+
}
|