@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 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
- handler: async ({ pkgId }) => {
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
  },
@@ -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
- logger.info(
85
- `Waiting ... ( copy ${imageName} (${platform}) to lazycat offical registry)`
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
- logger.info("uploading")
106
+ _logger.info("uploading")
91
107
  } else {
92
- logger.error("error: ", resp, await resp.text())
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
- logger.error(
137
- "failed to copyimage ",
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
- logger.info("uploaded: ", pgs.lzc_image)
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
- logger.error("error: ", await resp.text())
152
- break
172
+ throw Error(`error: ${await resp.text()}`)
153
173
  }
154
174
  }
155
175
  } catch (err) {
156
- console.error(err)
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)
@@ -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
- return
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
- return
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
- return request(sendURL, {
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
 
@@ -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
- const url = `https://dev.${this.boxname}.heiyu.space/bannerfile`
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
- `执行命令 ${cmd} ${args.join(" ")} 出错\n${ssh.stdout ?? ""}\n${ssh.stderr ?? ""}`
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
- compareVersions("0.1.12", backendVersion) >= 0
271
- ? `127.0.0.1:5000/${tag}`
272
- : `dev.${this.boxname}.heiyu.space/${tag}`
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
- received +
365
- " bytes downloaded out of " +
366
- total +
367
- " bytes."
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
- logger.error(`无法解析域名 ${domain}: `, error)
529
+ if (!quiet) {
530
+ logger.error(`无法解析域名 ${domain}: `, error)
531
+ }
530
532
  throw error
531
533
  }
532
534
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazycatcloud/lzc-cli",
3
- "version": "1.3.10",
3
+ "version": "1.3.12",
4
4
  "description": "lazycat cloud developer kit",
5
5
  "scripts": {
6
6
  "release": "release-it patch",