@lazycatcloud/lzc-cli 1.3.10 → 1.3.12
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/changelog.md +23 -0
- package/lib/app/index.js +9 -2
- package/lib/appstore/index.js +41 -21
- package/lib/appstore/prePublish.js +1 -1
- package/lib/appstore/publish.js +14 -13
- package/lib/config/env.js +7 -0
- package/lib/debug_bridge.js +26 -16
- package/lib/utils.js +14 -12
- package/package.json +1 -1
package/changelog.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.3.12](https://gitee.com/linakesi/lzc-cli/compare/v1.3.11...v1.3.12) (2025-10-29)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* ci friendly publish command ([1e7b477](https://gitee.com/linakesi/lzc-cli/commits/1e7b47740f1d9ae15d677a16a97d7cc461814c42))
|
|
9
|
+
* pre publish print error ([55c8326](https://gitee.com/linakesi/lzc-cli/commits/55c832625aa532bab3e0fcf0d7ec09d8235db190))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
### Features
|
|
13
|
+
|
|
14
|
+
* add `LZC_CLI_CHECK_DNS_RESOLVE` set check dev tools to use built-in dns resolution ([78ba39e](https://gitee.com/linakesi/lzc-cli/commits/78ba39e6869cbed956141e7ff7812ce3718e41ce))
|
|
15
|
+
* supports config the language key for publish app update logs through `--clang` ([e06eec8](https://gitee.com/linakesi/lzc-cli/commits/e06eec88f11827816ddd4e3a7ccd43b2883a1003))
|
|
16
|
+
* uninstall app add `--delete-data` parameter ([b4378f7](https://gitee.com/linakesi/lzc-cli/commits/b4378f77b528fbb5b5a1c74771ddda9f60f4e49a))
|
|
17
|
+
|
|
18
|
+
## [1.3.11](https://gitee.com/linakesi/lzc-cli/compare/v1.3.10...v1.3.11) (2025-10-10)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
* support setting token through environment variables ([4a25f84](https://gitee.com/linakesi/lzc-cli/commits/4a25f84fe4df938cb3d85f6391e27772dae5b071))
|
|
24
|
+
* the copy-image command adds the `trace-level` parameter ([cd4d4c3](https://gitee.com/linakesi/lzc-cli/commits/cd4d4c351d4bd43bffcfe5f67087c6bffd65d3f4))
|
|
25
|
+
|
|
3
26
|
## [1.3.10](https://gitee.com/linakesi/lzc-cli/compare/v0.0.8...v1.3.10) (2025-08-15)
|
|
4
27
|
|
|
5
28
|
### Bug Fixes
|
package/lib/app/index.js
CHANGED
|
@@ -143,12 +143,19 @@ export function lpkAppCommand(program) {
|
|
|
143
143
|
{
|
|
144
144
|
command: "uninstall <pkgId>",
|
|
145
145
|
desc: "从设备中卸载某一个应用",
|
|
146
|
-
|
|
146
|
+
builder: (args) => {
|
|
147
|
+
args.option("delete-data", {
|
|
148
|
+
describe: "删除应用数据 ⚠️ 警告: 应用数据删除后无法恢复",
|
|
149
|
+
type: "boolean",
|
|
150
|
+
default: false
|
|
151
|
+
})
|
|
152
|
+
},
|
|
153
|
+
handler: async ({ pkgId, deleteData }) => {
|
|
147
154
|
await shellApi.init()
|
|
148
155
|
|
|
149
156
|
const bridge = new DebugBridge()
|
|
150
157
|
await bridge.init()
|
|
151
|
-
await bridge.uninstall(pkgId)
|
|
158
|
+
await bridge.uninstall(pkgId, deleteData)
|
|
152
159
|
console.log(`默认微服设备: ${bridge.boxname} ,卸载应用 ${pkgId} 完成`)
|
|
153
160
|
}
|
|
154
161
|
},
|
package/lib/appstore/index.js
CHANGED
|
@@ -49,17 +49,22 @@ export function appstoreCommand(program) {
|
|
|
49
49
|
builder: (args) => {
|
|
50
50
|
args.option("c", {
|
|
51
51
|
alias: "changelog",
|
|
52
|
-
describe: "
|
|
52
|
+
describe: "更新日志",
|
|
53
|
+
type: "string"
|
|
54
|
+
})
|
|
55
|
+
args.option("clang", {
|
|
56
|
+
alias: "changelog-locale",
|
|
57
|
+
describe: "更新日志语言标识,默认通过当前 shell 语言环境识别",
|
|
53
58
|
type: "string"
|
|
54
59
|
})
|
|
55
60
|
args.option("F", {
|
|
56
61
|
alias: "file",
|
|
57
|
-
describe: "
|
|
62
|
+
describe: "更新日志文件",
|
|
58
63
|
type: "string"
|
|
59
64
|
})
|
|
60
65
|
},
|
|
61
|
-
handler: async ({ pkgPath, changelog, file }) => {
|
|
62
|
-
const locale = program.locale()
|
|
66
|
+
handler: async ({ pkgPath, changelog, changelogLocale, file }) => {
|
|
67
|
+
const locale = changelogLocale ?? program.locale()
|
|
63
68
|
const p = new Publish()
|
|
64
69
|
if (!changelog && file) {
|
|
65
70
|
changelog = fs.readFileSync(file, "utf8")
|
|
@@ -76,20 +81,31 @@ export function appstoreCommand(program) {
|
|
|
76
81
|
default: "amd64",
|
|
77
82
|
type: "string"
|
|
78
83
|
})
|
|
84
|
+
// 出于 CI 目的增加控制是否打印进度选项
|
|
85
|
+
yargs.option("trace-level", {
|
|
86
|
+
describe: "trace level 'verbose', 'quiet'",
|
|
87
|
+
default: "verbose",
|
|
88
|
+
type: "string"
|
|
89
|
+
})
|
|
79
90
|
return yargs
|
|
80
91
|
},
|
|
81
|
-
handler: async ({ img: imageName, arch: platform }) => {
|
|
92
|
+
handler: async ({ img: imageName, arch: platform, traceLevel }) => {
|
|
82
93
|
const uploadUrl = `${appStoreServerUrl}/api/v3/developer/app/docker/image/push/v3/copy?image=${imageName}&platform=${platform}`
|
|
83
94
|
const progressUrl = `${appStoreServerUrl}/api/v3/developer/app/docker/image/push/v3/progress?image=${imageName}&platform=${platform}`
|
|
84
|
-
|
|
85
|
-
|
|
95
|
+
|
|
96
|
+
const useCI = traceLevel === "quiet"
|
|
97
|
+
const _logger = logger
|
|
98
|
+
_logger.setLevel(useCI ? _logger.levels.ERROR : _logger.levels.INFO)
|
|
99
|
+
|
|
100
|
+
_logger.info(
|
|
101
|
+
`Waiting ... ( copy ${imageName} (${platform}) to lazycat offical registry)`
|
|
86
102
|
)
|
|
87
103
|
try {
|
|
88
104
|
const resp = await request(uploadUrl)
|
|
89
105
|
if (resp.ok) {
|
|
90
|
-
|
|
106
|
+
_logger.info("uploading")
|
|
91
107
|
} else {
|
|
92
|
-
|
|
108
|
+
_logger.error("error: ", resp, await resp.text())
|
|
93
109
|
return
|
|
94
110
|
}
|
|
95
111
|
sleep(1000)
|
|
@@ -97,6 +113,10 @@ export function appstoreCommand(program) {
|
|
|
97
113
|
let layers = []
|
|
98
114
|
|
|
99
115
|
let refreshProgress = (lys) => {
|
|
116
|
+
if (useCI) {
|
|
117
|
+
// 日志级别为 quiet 时不输出进度(用于 CI 目的)
|
|
118
|
+
return
|
|
119
|
+
}
|
|
100
120
|
// 关键修改:动态回退多行
|
|
101
121
|
process.stdout.write("\x1B[?25l") // 隐藏光标
|
|
102
122
|
if (prevLineCount > 0) {
|
|
@@ -130,30 +150,30 @@ export function appstoreCommand(program) {
|
|
|
130
150
|
;(layers ? layers : [])?.forEach((v) => {
|
|
131
151
|
v.progress = 100
|
|
132
152
|
})
|
|
133
|
-
refreshProgress(layers)
|
|
134
|
-
process.stdout.write("\n")
|
|
135
153
|
if (pgs.errmsg) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
imageName,
|
|
139
|
-
" err:",
|
|
140
|
-
pgs.errmsg
|
|
154
|
+
throw Error(
|
|
155
|
+
`failed to copyimage ${imageName} err: ${pgs.errmsg}`
|
|
141
156
|
)
|
|
142
|
-
break
|
|
143
157
|
}
|
|
144
|
-
|
|
158
|
+
if (!useCI) {
|
|
159
|
+
refreshProgress(layers)
|
|
160
|
+
process.stdout.write("\n")
|
|
161
|
+
_logger.info("uploaded: ", pgs.lzc_image)
|
|
162
|
+
} else {
|
|
163
|
+
// 日志级别为 quiet 时仅输出结果(用于 CI 目的)
|
|
164
|
+
console.log(pgs.lzc_image)
|
|
165
|
+
}
|
|
145
166
|
break
|
|
146
167
|
}
|
|
147
168
|
layers = pgs?.layers
|
|
148
169
|
refreshProgress(layers)
|
|
149
170
|
await sleep(1000)
|
|
150
171
|
} else {
|
|
151
|
-
|
|
152
|
-
break
|
|
172
|
+
throw Error(`error: ${await resp.text()}`)
|
|
153
173
|
}
|
|
154
174
|
}
|
|
155
175
|
} catch (err) {
|
|
156
|
-
|
|
176
|
+
_logger.error(err?.message ?? "unknown exception")
|
|
157
177
|
}
|
|
158
178
|
}
|
|
159
179
|
},
|
|
@@ -73,7 +73,7 @@ export class PrePublish {
|
|
|
73
73
|
})
|
|
74
74
|
const text = (await res.text()).trim()
|
|
75
75
|
if (!Publish.isJSON(text)) {
|
|
76
|
-
logger.error(`parse error: upload resp text not is json
|
|
76
|
+
logger.error(`parse error: upload resp text not is json`, text)
|
|
77
77
|
return
|
|
78
78
|
}
|
|
79
79
|
const respJson = await JSON.parse(text)
|
package/lib/appstore/publish.js
CHANGED
|
@@ -238,13 +238,13 @@ export class Publish {
|
|
|
238
238
|
const text = (await res.text()).trim()
|
|
239
239
|
if (!Publish.isJSON(text)) {
|
|
240
240
|
logger.info("upload lpk fail", text)
|
|
241
|
-
|
|
241
|
+
throw new Error(text)
|
|
242
242
|
}
|
|
243
243
|
const lpkInfo = await JSON.parse(text)
|
|
244
244
|
logger.debug("upload lpk response", lpkInfo)
|
|
245
245
|
if (res.status >= 400) {
|
|
246
246
|
logger.error(`LPK 文件上传失败,err: ${lpkInfo?.message ?? lpkInfo}`)
|
|
247
|
-
|
|
247
|
+
throw new Error(lpkInfo?.message ?? lpkInfo)
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
// 填写更新日志
|
|
@@ -276,19 +276,20 @@ export class Publish {
|
|
|
276
276
|
|
|
277
277
|
logger.debug("form data is", formData)
|
|
278
278
|
|
|
279
|
-
|
|
279
|
+
const publishRes = await request(sendURL, {
|
|
280
280
|
method: "POST",
|
|
281
281
|
body: JSON.stringify(formData)
|
|
282
|
-
}).then(async (res) => {
|
|
283
|
-
if (res.status >= 400) {
|
|
284
|
-
logger.error("发布应用出错,错误状态码为: ", res.status)
|
|
285
|
-
logger.error(await res.text())
|
|
286
|
-
return
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
logger.info("应用提交成功! 请等待审核结果")
|
|
290
|
-
logger.debug("publish response", await res.text())
|
|
291
|
-
return
|
|
292
282
|
})
|
|
283
|
+
|
|
284
|
+
const publishResMsg = (await publishRes.text()).trim()
|
|
285
|
+
if (publishRes.status > 399) {
|
|
286
|
+
logger.error("发布应用出错,错误状态码为: ", publishRes.status)
|
|
287
|
+
logger.error(publishResMsg)
|
|
288
|
+
throw new Error(publishResMsg)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
logger.info("应用提交成功! 请等待审核结果")
|
|
292
|
+
logger.debug("publish response", publishResMsg)
|
|
293
|
+
return publishRes
|
|
293
294
|
}
|
|
294
295
|
}
|
package/lib/config/env.js
CHANGED
|
@@ -4,6 +4,8 @@ import { ensureDir } from "../utils.js"
|
|
|
4
4
|
import logger from "loglevel"
|
|
5
5
|
import os from "node:os"
|
|
6
6
|
|
|
7
|
+
export const _SYSTEM_ENV_PREFIX = "LZC_CLI"
|
|
8
|
+
|
|
7
9
|
export const GLOBAL_CONFIG_DIR = path.join(
|
|
8
10
|
os.homedir(),
|
|
9
11
|
"/.config/lazycat/box-config.json"
|
|
@@ -23,6 +25,11 @@ class Env {
|
|
|
23
25
|
}
|
|
24
26
|
|
|
25
27
|
get(key) {
|
|
28
|
+
// 优先通过环境变量获取 (支持 CI/CD)
|
|
29
|
+
const _env_key = `${_SYSTEM_ENV_PREFIX}_${(key ?? "").toUpperCase()}`
|
|
30
|
+
if (process.env[_env_key]) {
|
|
31
|
+
return process.env[_env_key]
|
|
32
|
+
}
|
|
26
33
|
return this.allEnv[key]
|
|
27
34
|
}
|
|
28
35
|
|
package/lib/debug_bridge.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import spawn from "cross-spawn"
|
|
2
1
|
import fs from "node:fs"
|
|
2
|
+
import spawn from "cross-spawn"
|
|
3
|
+
import logger from "loglevel"
|
|
4
|
+
import fetch from "node-fetch"
|
|
5
|
+
|
|
3
6
|
import shellApi from "./shellapi.js"
|
|
7
|
+
import { _SYSTEM_ENV_PREFIX } from "./config/env.js"
|
|
4
8
|
import {
|
|
5
9
|
isDebugMode,
|
|
6
10
|
isTraceMode,
|
|
@@ -12,10 +16,6 @@ import {
|
|
|
12
16
|
contextDirname,
|
|
13
17
|
compareVersions
|
|
14
18
|
} from "./utils.js"
|
|
15
|
-
import logger from "loglevel"
|
|
16
|
-
import commandExists from "command-exists"
|
|
17
|
-
import path from "node:path"
|
|
18
|
-
import fetch from "node-fetch"
|
|
19
19
|
|
|
20
20
|
const bannerfileContent = `˄=ᆽ=ᐟ \\`
|
|
21
21
|
|
|
@@ -43,6 +43,8 @@ export class DebugBridge {
|
|
|
43
43
|
this.uid = shellApi.uid
|
|
44
44
|
this.boxname = shellApi.boxname
|
|
45
45
|
this.domain = `dev.${this.boxname}.heiyu.space`
|
|
46
|
+
this.checkUseResolve =
|
|
47
|
+
!!process.env[`${_SYSTEM_ENV_PREFIX}_CHECK_DNS_RESOLVE`]
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
async init() {
|
|
@@ -54,7 +56,14 @@ export class DebugBridge {
|
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
async checkDevTools() {
|
|
57
|
-
|
|
59
|
+
let domain = this.domain
|
|
60
|
+
if (this.checkUseResolve) {
|
|
61
|
+
try {
|
|
62
|
+
const _ipv6 = await resolveDomain(this.domain, true)
|
|
63
|
+
domain = `[${_ipv6}]`
|
|
64
|
+
} catch {}
|
|
65
|
+
}
|
|
66
|
+
const url = `https://${domain}/bannerfile`
|
|
58
67
|
return new Promise((resolve, reject) => {
|
|
59
68
|
fetch(url, { redirect: "error" })
|
|
60
69
|
.then(async (res) => {
|
|
@@ -71,9 +80,10 @@ export class DebugBridge {
|
|
|
71
80
|
)
|
|
72
81
|
reject()
|
|
73
82
|
})
|
|
74
|
-
.catch(() => {
|
|
83
|
+
.catch((err) => {
|
|
75
84
|
logger.error(
|
|
76
|
-
|
|
85
|
+
`检测懒猫开发者工具失败,请检测您当前的网络或者懒猫微服客户端是否正常启动。`,
|
|
86
|
+
err
|
|
77
87
|
)
|
|
78
88
|
reject()
|
|
79
89
|
})
|
|
@@ -93,8 +103,8 @@ export class DebugBridge {
|
|
|
93
103
|
ssh.status == 0
|
|
94
104
|
? resolve(ssh.stdout)
|
|
95
105
|
: reject(
|
|
96
|
-
|
|
97
|
-
|
|
106
|
+
`执行命令 ${cmd} ${args.join(" ")} 出错\n${ssh.stdout ?? ""}\n${ssh.stderr ?? ""}`
|
|
107
|
+
)
|
|
98
108
|
})
|
|
99
109
|
}
|
|
100
110
|
|
|
@@ -150,7 +160,6 @@ export class DebugBridge {
|
|
|
150
160
|
|
|
151
161
|
-> https://${this.domain}/auth?key=${pk}
|
|
152
162
|
`
|
|
153
|
-
|
|
154
163
|
)
|
|
155
164
|
throw "请在授权完成后重试!"
|
|
156
165
|
}
|
|
@@ -195,10 +204,11 @@ export class DebugBridge {
|
|
|
195
204
|
}
|
|
196
205
|
}
|
|
197
206
|
|
|
198
|
-
async uninstall(appId) {
|
|
207
|
+
async uninstall(appId, deleteAppData = false) {
|
|
199
208
|
return this.common(sshBinary(), [
|
|
200
209
|
...sshCmdArgs(`box@${this.domain}`),
|
|
201
210
|
`uninstall --uid ${this.uid}`,
|
|
211
|
+
deleteAppData ? "--delete-data" : "",
|
|
202
212
|
appId
|
|
203
213
|
])
|
|
204
214
|
}
|
|
@@ -267,10 +277,10 @@ export class DebugBridge {
|
|
|
267
277
|
buildStream.on("close", (code) => {
|
|
268
278
|
code == 0
|
|
269
279
|
? resolve(
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
280
|
+
compareVersions("0.1.12", backendVersion) >= 0
|
|
281
|
+
? `127.0.0.1:5000/${tag}`
|
|
282
|
+
: `dev.${this.boxname}.heiyu.space/${tag}`
|
|
283
|
+
)
|
|
274
284
|
: reject(`在盒子中构建 image 失败`)
|
|
275
285
|
})
|
|
276
286
|
}).finally(() => {
|
package/lib/utils.js
CHANGED
|
@@ -175,11 +175,11 @@ export function fakeLoadManifestYml(file) {
|
|
|
175
175
|
const res = fs.readFileSync(file, "utf8")
|
|
176
176
|
let obj = {
|
|
177
177
|
application: {
|
|
178
|
-
subdomain: undefined
|
|
179
|
-
}
|
|
178
|
+
subdomain: undefined
|
|
179
|
+
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
res.split("\n").forEach(v => {
|
|
182
|
+
res.split("\n").forEach((v) => {
|
|
183
183
|
let line = v.trim()
|
|
184
184
|
const arr = line.split(":")
|
|
185
185
|
if (arr.length != 2) {
|
|
@@ -353,18 +353,18 @@ export async function md5File(filepath) {
|
|
|
353
353
|
}
|
|
354
354
|
|
|
355
355
|
export class Downloader {
|
|
356
|
-
constructor() {
|
|
356
|
+
constructor() {}
|
|
357
357
|
|
|
358
358
|
showDownloadingProgress(received, total) {
|
|
359
359
|
let percentage = ((received * 100) / total).toFixed(2)
|
|
360
360
|
process.stdout.write("\r")
|
|
361
361
|
process.stdout.write(
|
|
362
362
|
percentage +
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
363
|
+
"% | " +
|
|
364
|
+
received +
|
|
365
|
+
" bytes downloaded out of " +
|
|
366
|
+
total +
|
|
367
|
+
" bytes."
|
|
368
368
|
)
|
|
369
369
|
}
|
|
370
370
|
|
|
@@ -494,7 +494,7 @@ export function isTraceMode() {
|
|
|
494
494
|
// 会直接报错,连版本检测都到不了(包导入的太前面了)
|
|
495
495
|
async function __resolveDomain(domain, ipv6 = false) {
|
|
496
496
|
return new Promise((resolve, reject) => {
|
|
497
|
-
const callback = function(err, addresses) {
|
|
497
|
+
const callback = function (err, addresses) {
|
|
498
498
|
if (addresses && addresses.length > 0) {
|
|
499
499
|
resolve(addresses[0])
|
|
500
500
|
} else {
|
|
@@ -510,7 +510,7 @@ async function __resolveDomain(domain, ipv6 = false) {
|
|
|
510
510
|
})
|
|
511
511
|
}
|
|
512
512
|
|
|
513
|
-
export async function resolveDomain(domain) {
|
|
513
|
+
export async function resolveDomain(domain, quiet = false) {
|
|
514
514
|
try {
|
|
515
515
|
// Set machine's dns server as defalut dns server
|
|
516
516
|
dns.setServers(["[fc03:1136:3800::1]"])
|
|
@@ -526,7 +526,9 @@ export async function resolveDomain(domain) {
|
|
|
526
526
|
throw ipv6Addresses.reason
|
|
527
527
|
}
|
|
528
528
|
} catch (error) {
|
|
529
|
-
|
|
529
|
+
if (!quiet) {
|
|
530
|
+
logger.error(`无法解析域名 ${domain}: `, error)
|
|
531
|
+
}
|
|
530
532
|
throw error
|
|
531
533
|
}
|
|
532
534
|
}
|