@lazycatcloud/lzc-cli 1.2.16 → 1.2.18
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_build.js +26 -16
- package/lib/app/lpk_debug_bridge.js +4 -1
- package/lib/app/lpk_devshell.js +15 -12
- package/lib/appstore/publish.js +53 -13
- package/lib/shellapi.js +20 -0
- package/lib/utils.js +26 -0
- package/package.json +1 -1
package/lib/app/lpk_build.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
envTemplateFile,
|
|
10
10
|
isValidPackageName,
|
|
11
11
|
tarContentDir,
|
|
12
|
+
isPngWithFile,
|
|
12
13
|
} from "../utils.js";
|
|
13
14
|
import { spawnSync } from "child_process";
|
|
14
15
|
import { LpkManifest } from "./lpk_create.js";
|
|
@@ -63,6 +64,13 @@ async function fetchIconTo(options, cwd, destDir) {
|
|
|
63
64
|
return;
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
if (!isPngWithFile(iconPath)) {
|
|
68
|
+
logger.warn(`图标icon ${iconPath} 验证失败(不是一个png格式)`);
|
|
69
|
+
return;
|
|
70
|
+
} else {
|
|
71
|
+
logger.debug(`图标icon ${iconPath} 验证成功(png格式)`);
|
|
72
|
+
}
|
|
73
|
+
|
|
66
74
|
fs.copyFileSync(iconPath, path.join(destDir, "icon.png"));
|
|
67
75
|
return;
|
|
68
76
|
}
|
|
@@ -70,27 +78,29 @@ async function fetchIconTo(options, cwd, destDir) {
|
|
|
70
78
|
// 提供一些方便的环境变量,可以在 lzc-build.yml 中直接使用
|
|
71
79
|
// - LocalIP 本地局域网ip
|
|
72
80
|
function localIp() {
|
|
81
|
+
const regex = /inet6 (fc03:1136:[0-9a-fA-F:]+)\/\d+ scope global/;
|
|
82
|
+
|
|
83
|
+
let output = "";
|
|
73
84
|
if (isMacos) {
|
|
74
|
-
const result = spawnSync("sh", [
|
|
75
|
-
"-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (result.error) {
|
|
85
|
+
const result = spawnSync("sh", ["-c", "ifconfig", "heiyu-0"], {
|
|
86
|
+
encoding: "utf-8",
|
|
87
|
+
});
|
|
88
|
+
if (result.status != 0 || result.error) {
|
|
79
89
|
logger.debug("macos get current ip is error", result.error);
|
|
80
90
|
return "";
|
|
81
|
-
} else {
|
|
82
|
-
const ip = result.stdout.toString().trim();
|
|
83
|
-
return ip;
|
|
84
91
|
}
|
|
92
|
+
output = result.stdout;
|
|
93
|
+
} else {
|
|
94
|
+
const result = spawnSync("ip", ["addr", "show", "heiyu-0"], {
|
|
95
|
+
encoding: "utf-8",
|
|
96
|
+
});
|
|
97
|
+
if (result.status !== 0 || result.error) {
|
|
98
|
+
logger.debug("run ip addr show heiyu-0 failed", result.error);
|
|
99
|
+
return "";
|
|
100
|
+
}
|
|
101
|
+
output = result.stdout;
|
|
85
102
|
}
|
|
86
|
-
const
|
|
87
|
-
encoding: "utf-8",
|
|
88
|
-
});
|
|
89
|
-
if (output.status !== 0) {
|
|
90
|
-
logger.debug("run ip route error", output.status, output.stderr);
|
|
91
|
-
return "";
|
|
92
|
-
}
|
|
93
|
-
const match = output.stdout.match(/src\ (.+)\ uid/i);
|
|
103
|
+
const match = output.match(regex);
|
|
94
104
|
if (match) {
|
|
95
105
|
return match[1];
|
|
96
106
|
} else {
|
|
@@ -90,7 +90,7 @@ export class DebugBridge {
|
|
|
90
90
|
return this.common(this.sshCmd, [`uninstall --uid ${this.uid}`, appId]);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
async devshell(appId, isUserApp) {
|
|
93
|
+
async devshell(appId, isUserApp, onconnect = null) {
|
|
94
94
|
const stream = spawn(
|
|
95
95
|
this.sshCmd,
|
|
96
96
|
[
|
|
@@ -110,6 +110,9 @@ export class DebugBridge {
|
|
|
110
110
|
stream.on("close", (code) => {
|
|
111
111
|
code == 0 ? resolve() : reject();
|
|
112
112
|
});
|
|
113
|
+
if (onconnect) {
|
|
114
|
+
stream.on("spawn", onconnect);
|
|
115
|
+
}
|
|
113
116
|
});
|
|
114
117
|
}
|
|
115
118
|
|
package/lib/app/lpk_devshell.js
CHANGED
|
@@ -217,7 +217,7 @@ export class AppDevShell {
|
|
|
217
217
|
|
|
218
218
|
// 添加 devshell 必要路由
|
|
219
219
|
routes.push(
|
|
220
|
-
"/__debug.bridge=exec://
|
|
220
|
+
"/__debug.bridge=exec://80,/lzcapp/pkg/content/devshell/init_debug_bridge.sh"
|
|
221
221
|
);
|
|
222
222
|
routes.push("/__isdevshell=file:///lzcapp/pkg/devshell");
|
|
223
223
|
|
|
@@ -244,14 +244,17 @@ export class AppDevShell {
|
|
|
244
244
|
});
|
|
245
245
|
|
|
246
246
|
// 在生成 manifest.yml 之前合并 lzc-build.yml devshell 字段的值
|
|
247
|
-
// 并加上 health_check
|
|
247
|
+
// 并加上 health_check 字段, 当处于 devshell 的情况时,禁用 health_check
|
|
248
|
+
// 避免应用永远处于 unhealth 导致状态卡在 starting
|
|
248
249
|
this.lpkBuild.onBeforeDumpYaml(async (manifest, options) => {
|
|
249
250
|
logger.debug("merge lzc-build.yml devshell services\n", options);
|
|
251
|
+
const userapp = this.isUserApp ? shellApi.uid + "." : "";
|
|
250
252
|
const devshell = {
|
|
251
253
|
application: {
|
|
252
254
|
devshell: options["devshell"],
|
|
253
255
|
health_check: {
|
|
254
|
-
test_url: `http
|
|
256
|
+
test_url: `http://${userapp}app.${manifest["package"]}.lzcapp/__isdevshell`,
|
|
257
|
+
disable: true,
|
|
255
258
|
},
|
|
256
259
|
},
|
|
257
260
|
};
|
|
@@ -497,26 +500,26 @@ class DevShell {
|
|
|
497
500
|
"all",
|
|
498
501
|
debounce(() => {
|
|
499
502
|
this.syncProject(appId);
|
|
500
|
-
})
|
|
501
|
-
1000
|
|
503
|
+
}, 1000)
|
|
502
504
|
);
|
|
503
505
|
}
|
|
506
|
+
|
|
504
507
|
async shell() {
|
|
505
508
|
try {
|
|
506
|
-
//
|
|
507
|
-
await this.syncProject(this.appId);
|
|
508
|
-
// 注册watch函数
|
|
509
|
+
// 监听文件
|
|
509
510
|
await this.watchFile(this.appId);
|
|
510
|
-
|
|
511
|
-
|
|
511
|
+
await this.connectShell(async () => {
|
|
512
|
+
// 在连接成功的时候,同步一次文件
|
|
513
|
+
await this.syncProject(this.appId);
|
|
514
|
+
});
|
|
512
515
|
} catch (e) {
|
|
513
516
|
console.log(e);
|
|
514
517
|
return Promise.reject(e);
|
|
515
518
|
}
|
|
516
519
|
}
|
|
517
520
|
|
|
518
|
-
async connectShell() {
|
|
521
|
+
async connectShell(onconnect = null) {
|
|
519
522
|
const bridge = new DebugBridge();
|
|
520
|
-
await bridge.devshell(this.appId, this.isUserApp);
|
|
523
|
+
await bridge.devshell(this.appId, this.isUserApp, onconnect);
|
|
521
524
|
}
|
|
522
525
|
}
|
package/lib/appstore/publish.js
CHANGED
|
@@ -5,7 +5,7 @@ import fs from "node:fs";
|
|
|
5
5
|
import inquirer from "inquirer";
|
|
6
6
|
import { spawnSync } from "node:child_process";
|
|
7
7
|
import path from "node:path";
|
|
8
|
-
import { isFileExist } from "../utils.js";
|
|
8
|
+
import { isFileExist, isPngWithFile } from "../utils.js";
|
|
9
9
|
|
|
10
10
|
async function askChangeLog() {
|
|
11
11
|
const noEmpty = (value) => value != "";
|
|
@@ -24,16 +24,40 @@ export class Publish {
|
|
|
24
24
|
this.baseUrl = baseUrl;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
/**
|
|
28
|
+
* @param {string} raw
|
|
29
|
+
*/
|
|
30
|
+
isJSON(raw) {
|
|
31
|
+
const ml = raw.length
|
|
32
|
+
if (ml <= 1) return false
|
|
33
|
+
return raw[0] == '{' && raw[ml - 1] == '}'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
preCheck(pkgPath) {
|
|
28
37
|
const tempDir = fs.mkdtempSync(".lzc-cli-publish");
|
|
29
38
|
try {
|
|
30
39
|
spawnSync("unzip", ["-d", tempDir, pkgPath]);
|
|
31
40
|
if (isFileExist(path.join(tempDir, "devshell"))) {
|
|
32
|
-
|
|
41
|
+
logger.error("不能发布一个devshell的版本,请重新使用 `lzc-cli project build` 构建")
|
|
42
|
+
return false
|
|
43
|
+
}
|
|
44
|
+
const tempIcon = path.join(tempDir, "icon.png")
|
|
45
|
+
if (!isFileExist(tempIcon) || !isPngWithFile(tempIcon)) {
|
|
46
|
+
logger.error("icon 必须是 png 格式")
|
|
47
|
+
return false
|
|
33
48
|
}
|
|
49
|
+
return true
|
|
34
50
|
} finally {
|
|
35
51
|
fs.rmSync(tempDir, { recursive: true });
|
|
36
52
|
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {string} pkgPath
|
|
57
|
+
* @param {string} changelog
|
|
58
|
+
*/
|
|
59
|
+
async publish(pkgPath, changelog) {
|
|
60
|
+
if (!this.preCheck(pkgPath)) return
|
|
37
61
|
|
|
38
62
|
await autoLogin();
|
|
39
63
|
|
|
@@ -41,27 +65,43 @@ export class Publish {
|
|
|
41
65
|
const answer = await askChangeLog();
|
|
42
66
|
changelog = answer.changelog;
|
|
43
67
|
}
|
|
68
|
+
changelog = changelog.trim() // clean space ^:)
|
|
44
69
|
|
|
45
70
|
logger.info("正在提交审核...");
|
|
46
71
|
const form = new FormData();
|
|
47
72
|
form.append("file", fs.createReadStream(pkgPath));
|
|
48
73
|
|
|
49
|
-
const
|
|
74
|
+
const uploadURL = this.baseUrl + "/upload_lpk"
|
|
75
|
+
logger.debug("upload url is", uploadURL)
|
|
76
|
+
|
|
77
|
+
const res = await request(uploadURL, {
|
|
50
78
|
method: "POST",
|
|
51
79
|
body: form,
|
|
52
80
|
});
|
|
53
|
-
const
|
|
81
|
+
const text = (await res.text()).trim()
|
|
82
|
+
if (!this.isJSON(text)) {
|
|
83
|
+
logger.info("upload lpk fail", text);
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
const lpkInfo = await JSON.parse(text)
|
|
54
87
|
logger.debug("upload lpk response", lpkInfo);
|
|
55
88
|
|
|
56
|
-
|
|
89
|
+
const sendURL = this.baseUrl + `/apps/${lpkInfo.package}/reviews`
|
|
90
|
+
logger.debug("publish url is", sendURL)
|
|
91
|
+
|
|
92
|
+
const formData = {
|
|
93
|
+
changelog,
|
|
94
|
+
iconPath: lpkInfo.iconPath,
|
|
95
|
+
pkgPath: lpkInfo.url,
|
|
96
|
+
supportPC: lpkInfo.supportPC,
|
|
97
|
+
supportMobile: lpkInfo.supportMobile,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
logger.debug("form data is", formData)
|
|
101
|
+
|
|
102
|
+
return request(sendURL, {
|
|
57
103
|
method: "POST",
|
|
58
|
-
body: JSON.stringify(
|
|
59
|
-
changelog,
|
|
60
|
-
iconPath: lpkInfo.iconPath,
|
|
61
|
-
pkgPath: lpkInfo.url,
|
|
62
|
-
supportPC: lpkInfo.supportPC,
|
|
63
|
-
supportMobile: lpkInfo.supportMobile,
|
|
64
|
-
}),
|
|
104
|
+
body: JSON.stringify(formData),
|
|
65
105
|
}).then(async (res) => {
|
|
66
106
|
if (res.status >= 400) {
|
|
67
107
|
logger.error("发布应用出错,错误状态码为: ", res.status);
|
package/lib/shellapi.js
CHANGED
|
@@ -5,6 +5,9 @@ import path from "node:path";
|
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import os from "node:os";
|
|
7
7
|
import logger from "loglevel";
|
|
8
|
+
import fetch from "node-fetch";
|
|
9
|
+
|
|
10
|
+
const bannerfileContent = `˄=ᆽ=ᐟ \\`;
|
|
8
11
|
|
|
9
12
|
/**
|
|
10
13
|
* @link {https://www.npmjs.com/package/appdirs}
|
|
@@ -35,6 +38,7 @@ class ShellApi {
|
|
|
35
38
|
md.add("lzc-shellapi-cred", cred);
|
|
36
39
|
this.metadata = md;
|
|
37
40
|
this.info = await this.initBoxInfo();
|
|
41
|
+
this.checkDevTools();
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
get boxname() {
|
|
@@ -97,6 +101,22 @@ class ShellApi {
|
|
|
97
101
|
throw "没有默认盒子信息, 请先使用 lzc-cli box switch 设置默认的盒子信息";
|
|
98
102
|
}
|
|
99
103
|
|
|
104
|
+
async checkDevTools() {
|
|
105
|
+
const url = `https://dev.${this.info.boxname}.heiyu.space/bannerfile`;
|
|
106
|
+
fetch(url, { redirect: "error" })
|
|
107
|
+
.then(async (res) => {
|
|
108
|
+
const content = await res.text();
|
|
109
|
+
if (res.status != 200 || content != bannerfileContent) {
|
|
110
|
+
logger.warn(
|
|
111
|
+
`检测到你还没有安装懒猫微服开发者工具,请先到商店中搜索安装`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
.catch(() => {
|
|
116
|
+
logger.warn(`你的懒猫微服开发者工具版本较低,请从商店中更新`);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
100
120
|
async setDefaultBox(boxname) {
|
|
101
121
|
const boxes = await this.boxList();
|
|
102
122
|
const box = boxes.find((b) => b.box_name === boxname);
|
package/lib/utils.js
CHANGED
|
@@ -341,3 +341,29 @@ export async function tarContentDir(from, to, cwd = "./") {
|
|
|
341
341
|
});
|
|
342
342
|
});
|
|
343
343
|
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* check buffer is png(magic header)
|
|
347
|
+
* refer: https://github.com/sindresorhus/is-png/blob/main/index.js
|
|
348
|
+
* @param {Buffer} buffer
|
|
349
|
+
* @returns {boolean}
|
|
350
|
+
*/
|
|
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;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export function isPngWithFile(filepath) {
|
|
366
|
+
if (!isFileExist(filepath)) return false
|
|
367
|
+
const buf = fs.readFileSync(filepath)
|
|
368
|
+
return isPngWithBuffer(buf)
|
|
369
|
+
}
|