@lazycatcloud/lzc-cli 1.2.64 → 1.3.1

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,4 +1,13 @@
1
+ # 1.3.1
2
+
3
+ 1. 升级 lzc-cli 1.3.1 版本
4
+
5
+ # 1.2.65
6
+
7
+ 1. 适配新版本开发者平台接口
8
+
1
9
  # 1.2.64
10
+
2
11
  1. 升级 dozzle,支持查看所有容器日志
3
12
  2. 修复 devshell ssh 多路复用错误
4
13
 
@@ -0,0 +1,10 @@
1
+ /*
2
+ * @Author: Bin
3
+ * @Date: 2025-04-14
4
+ * @FilePath: /lzc-cli/lib/appstore/env.js
5
+ */
6
+
7
+ const accountServerUrl = "https://account.lazycat.cloud"
8
+ const appStoreServerUrl = "https://appstore.api.lazycat.cloud"
9
+
10
+ export { accountServerUrl, appStoreServerUrl }
@@ -4,6 +4,7 @@ import { PrePublish } from "./prePublish.js"
4
4
  import { reLogin, request } from "./login.js"
5
5
  import fs from "node:fs"
6
6
  import { sleep } from "../utils.js"
7
+ import { appStoreServerUrl } from "./env.js"
7
8
 
8
9
  export function appstoreCommand(program) {
9
10
  let subCommands = [
@@ -58,24 +59,27 @@ export function appstoreCommand(program) {
58
59
  })
59
60
  },
60
61
  handler: async ({ pkgPath, changelog, file }) => {
62
+ const locale = program.locale()
61
63
  const p = new Publish()
62
64
  if (!changelog && file) {
63
65
  changelog = fs.readFileSync(file, "utf8")
64
66
  }
65
- await p.publish(pkgPath, changelog)
67
+ await p.publish(pkgPath, changelog, locale)
66
68
  }
67
69
  },
68
70
  {
69
71
  command: "copy-image <imageName>",
70
72
  desc: "复制镜像至懒猫微服官方源",
71
73
  handler: async ({ imageName }) => {
72
- const baseUrl = "https://appstore.api.lazycat.cloud/api/v2/developer"
74
+ const baseUrl = `${appStoreServerUrl}/api/v3/developer`
73
75
  logger.info(
74
76
  `Waiting ... ( copy ${imageName} to lazycat offical registry)`
75
77
  )
76
78
  try {
77
- for (; ;) {
78
- const resp = await request(`${baseUrl}/push-image?image=${imageName}&v=2`)
79
+ for (;;) {
80
+ const resp = await request(
81
+ `${baseUrl}/app/docker/image/push?image=${imageName}&v=2`
82
+ )
79
83
  if (resp.ok) {
80
84
  const tag = await resp.text()
81
85
  if (tag.length == 0) {
@@ -87,7 +91,7 @@ export function appstoreCommand(program) {
87
91
  logger.error("error: ", await resp.text())
88
92
  }
89
93
 
90
- break;
94
+ break
91
95
  }
92
96
  } catch (err) {
93
97
  console.error(err)
@@ -4,160 +4,163 @@ import url from "url"
4
4
  import logger from "loglevel"
5
5
  import env from "../config/env.js"
6
6
  import inquirer from "inquirer"
7
-
8
- const accountServerUrl = "https://account.lazycat.cloud"
7
+ import { accountServerUrl } from "./env.js"
9
8
 
10
9
  function join(...params) {
11
- let x = ""
12
- try {
13
- // fix: url path join failed for windows
14
- const u = new url.URL(params[0])
15
- params.splice(0, 1)
16
- u.pathname = path.posix.join(u.pathname, ...params)
17
- x = u.toString()
18
- } catch (error) {
19
- console.warn(error)
20
- x = path.join(...params)
21
- }
22
- return x
10
+ let x = ""
11
+ try {
12
+ // fix: url path join failed for windows
13
+ const u = new url.URL(params[0])
14
+ params.splice(0, 1)
15
+ u.pathname = path.posix.join(u.pathname, ...params)
16
+ x = u.toString()
17
+ } catch (error) {
18
+ console.warn(error)
19
+ x = path.join(...params)
20
+ }
21
+ return x
23
22
  }
24
23
 
25
24
  async function login(username, password) {
26
- return fetch(join(accountServerUrl, "/api/login/signin"), {
27
- method: "POST",
28
- body: new URLSearchParams({
29
- username: username,
30
- password: password
31
- })
32
- })
33
- .then(async (res) => {
34
- let bodyText = await res.text()
35
- let body = JSON.parse(bodyText)
36
- if (body.success) {
37
- return Promise.resolve(body.data)
38
- }
39
- return Promise.reject(body.message)
40
- })
41
- .then((data) => {
42
- env.set({ token: data.token }, true)
43
- logger.info("登录成功!")
44
- })
25
+ return fetch(join(accountServerUrl, "/api/login/signin"), {
26
+ method: "POST",
27
+ body: new URLSearchParams({
28
+ username: username,
29
+ password: password
30
+ })
31
+ })
32
+ .then(async (res) => {
33
+ let bodyText = await res.text()
34
+ let body = JSON.parse(bodyText)
35
+ if (body.success) {
36
+ return Promise.resolve(body.data)
37
+ }
38
+ return Promise.reject(body.message)
39
+ })
40
+ .then((data) => {
41
+ env.set({ token: data.token }, true)
42
+ logger.info("登录成功!")
43
+ })
45
44
  }
46
45
 
47
46
  async function isLogin() {
48
- try {
49
- let token = env.get("token")
50
- if (!token) {
51
- return false
52
- }
53
- return fetch(join(accountServerUrl, "/api/user/current"), {
54
- method: "GET",
55
- headers: {
56
- "X-User-Token": token
57
- }
58
- }).then(async (res) => {
59
- let bodyText = await res.text()
60
- let body = JSON.parse(bodyText)
61
- if (body.success) {
62
- return token
63
- }
64
- logger.debug(body.message)
65
- return false
66
- })
67
- } catch (e) {
68
- logger.trace(e)
69
- return false
70
- }
47
+ try {
48
+ let token = env.get("token")
49
+ if (!token) {
50
+ return false
51
+ }
52
+ return fetch(join(accountServerUrl, "/api/user/current"), {
53
+ method: "GET",
54
+ headers: {
55
+ "X-User-Token": token
56
+ }
57
+ }).then(async (res) => {
58
+ let bodyText = await res.text()
59
+ let body = JSON.parse(bodyText)
60
+ if (body.success) {
61
+ return token
62
+ }
63
+ logger.debug(body.message)
64
+ return false
65
+ })
66
+ } catch (e) {
67
+ logger.trace(e)
68
+ return false
69
+ }
71
70
  }
72
71
 
73
72
  async function askUserInfo() {
74
- const noEmpty = (value) => value != ""
75
- return await inquirer.prompt([
76
- {
77
- type: "input",
78
- name: "username",
79
- message: "请输入登录用户名",
80
- validate: noEmpty
81
- },
82
- {
83
- type: "password",
84
- mask: "*",
85
- name: "password",
86
- message: "请输入登录密码",
87
- validate: noEmpty
88
- }
89
- ])
73
+ const noEmpty = (value) => value != ""
74
+ return await inquirer.prompt([
75
+ {
76
+ type: "input",
77
+ name: "username",
78
+ message: "请输入登录用户名",
79
+ validate: noEmpty
80
+ },
81
+ {
82
+ type: "password",
83
+ mask: "*",
84
+ name: "password",
85
+ message: "请输入登录密码",
86
+ validate: noEmpty
87
+ }
88
+ ])
90
89
  }
91
90
 
92
91
  async function interactiveLogin() {
93
- try {
94
- let info = await askUserInfo()
95
- await login(info.username, info.password)
96
- } catch (e) {
97
- logger.debug("login error: ", e)
98
- logger.error("帐号或者密码错误,请重新输入!")
99
- return interactiveLogin()
100
- }
92
+ try {
93
+ let info = await askUserInfo()
94
+ await login(info.username, info.password)
95
+ } catch (e) {
96
+ logger.debug("login error: ", e)
97
+ logger.error("帐号或者密码错误,请重新输入!")
98
+ return interactiveLogin()
99
+ }
101
100
  }
102
101
 
103
102
  function tipsFirstLogin() {
104
- let token = env.get("token")
105
- if (!token) {
106
- // 还没登录过的用户提示用户前往 https://developer.lazycat.cloud/manage 注册开发者账号
107
- logger.info("请登录懒猫微服社区账号,账号注册以及开发者权限申请地址: https://developer.lazycat.cloud/manage")
108
- }
103
+ let token = env.get("token")
104
+ if (!token) {
105
+ // 还没登录过的用户提示用户前往 https://developer.lazycat.cloud/manage 注册开发者账号
106
+ logger.info(
107
+ "请登录懒猫微服社区账号,账号注册以及开发者权限申请地址: https://developer.lazycat.cloud/manage"
108
+ )
109
+ }
109
110
  }
110
111
 
111
112
  export async function reLogin() {
112
- let token = await isLogin()
113
- if (token) {
114
- const questions = [
115
- {
116
- name: "relogin",
117
- type: "input",
118
- default: "n",
119
- message: "检测到已经登录,是否重新登录(y/n):"
120
- }
121
- ]
122
- const answers = await inquirer.prompt(questions)
123
- if (answers.relogin.toLowerCase() === "n") {
124
- logger.info(`token: ${token}`)
125
- return
126
- }
127
- } else {
128
- tipsFirstLogin()
129
- }
130
- await interactiveLogin()
113
+ let token = await isLogin()
114
+ if (token) {
115
+ const questions = [
116
+ {
117
+ name: "relogin",
118
+ type: "input",
119
+ default: "n",
120
+ message: "检测到已经登录,是否重新登录(y/n):"
121
+ }
122
+ ]
123
+ const answers = await inquirer.prompt(questions)
124
+ if (answers.relogin.toLowerCase() === "n") {
125
+ logger.info(`token: ${token}`)
126
+ return
127
+ }
128
+ } else {
129
+ tipsFirstLogin()
130
+ }
131
+ await interactiveLogin()
131
132
  }
132
133
 
133
134
  export async function autoLogin() {
134
- let token = await isLogin()
135
- if (token) {
136
- logger.debug("appstore 已经登录")
137
- return
138
- } else {
139
- tipsFirstLogin()
140
- }
135
+ let token = await isLogin()
136
+ if (token) {
137
+ logger.debug("appstore 已经登录")
138
+ return
139
+ } else {
140
+ tipsFirstLogin()
141
+ }
141
142
 
142
- logger.debug("token错误,尝试自动登录")
143
- await interactiveLogin()
143
+ logger.debug("token错误,尝试自动登录")
144
+ await interactiveLogin()
144
145
  }
145
146
 
146
147
  export async function request(url, options = {}) {
147
- await autoLogin()
148
+ await autoLogin()
149
+
150
+ const token = env.get("token")
148
151
 
149
- const token = env.get("token")
152
+ let headers = {
153
+ "X-User-Token": token,
154
+ cookie: `userToken=${token}`
155
+ }
156
+ if (options.headers) {
157
+ headers = Object.assign({}, options.headers, headers)
158
+ }
150
159
 
151
- let headers = {
152
- "X-User-Token": token,
153
- cookie: `userToken=${token}`
154
- }
155
- if (options.headers) {
156
- headers = Object.assign({}, options.headers, headers)
157
- }
160
+ // logger.debug("token", token)
158
161
 
159
- options = Object.assign({}, options, { headers })
160
- logger.trace(`fetch ${url}`, options)
162
+ options = Object.assign({}, options, { headers })
163
+ logger.trace(`fetch ${url}`, options)
161
164
 
162
- return fetch(url, options)
165
+ return fetch(url, options)
163
166
  }
@@ -9,35 +9,45 @@ import {
9
9
  isPngWithFile,
10
10
  unzipSync,
11
11
  loadFromYaml,
12
- isValidPackageName
12
+ isValidPackageName,
13
+ getLanguageForLocale
13
14
  } from "../utils.js"
15
+ import { appStoreServerUrl } from "./env.js"
14
16
 
15
- async function askChangeLog() {
17
+ async function askChangeLog(locale) {
16
18
  const noEmpty = (value) => value != ""
17
19
  return await inquirer.prompt([
18
20
  {
19
21
  name: "changelog",
20
22
  type: "editor",
21
- message: "填写 changelog 内容",
23
+ message: `填写 changelog 内容 (${locale})`,
22
24
  validate: noEmpty
23
25
  }
24
26
  ])
25
27
  }
26
28
 
27
- async function getCategories(baseUrl) {
28
- const response = await request(`${baseUrl}/categories`, { method: "GET" })
29
- const data = await response.json()
29
+ async function getCategories(baseUrl, locale = "zh") {
30
+ const response = await request(`${baseUrl}/app/category?size=100`, {
31
+ method: "GET"
32
+ })
33
+ const langKey = getLanguageForLocale(locale)
34
+ const { items: data } = await response.json()
35
+ console.log("data", data)
30
36
  return data
31
37
  .sort((a, b) => a.index - b.index)
32
- .map((cat) => ({
33
- name: cat.name,
34
- value: cat.name
35
- }))
38
+ .map((cat) => {
39
+ let localize_name = (cat?.localize_name ?? {})[langKey] ?? undefined
40
+ return {
41
+ name: localize_name ?? cat.name,
42
+ value: cat.id
43
+ }
44
+ })
36
45
  }
37
46
 
38
- async function askPublishAppInfo(baseUrl, manifest) {
47
+ async function askPublishAppInfo(baseUrl, manifest, locale) {
39
48
  // 获取分类列表
40
- const categories = await getCategories(baseUrl)
49
+ // const categories = await getCategories(baseUrl, locale)
50
+ const langKey = getLanguageForLocale(locale)
41
51
 
42
52
  const questions = [
43
53
  {
@@ -49,56 +59,62 @@ async function askPublishAppInfo(baseUrl, manifest) {
49
59
  },
50
60
  {
51
61
  type: "input",
52
- name: "appId",
53
- message: "请输入应用ID:",
62
+ name: "package",
63
+ message: "请输入应用包标识符:",
54
64
  default: manifest["package"],
55
65
  validate: (input) =>
56
- isValidPackageName(input) ? true : "应用ID不符合规范"
57
- },
58
- {
59
- type: "input",
60
- name: "description",
61
- message: "请输入应用描述:",
62
- default: manifest["description"] || ""
63
- },
64
- {
65
- type: "input",
66
- name: "brief",
67
- message: "请输入应用简介(简单的概述):",
68
- default: ""
69
- },
70
- {
71
- type: "checkbox",
72
- name: "category",
73
- message: "请选择应用分类:",
74
- choices: categories,
75
- validate: (input) => (input.length > 0 ? true : "请至少选择一个分类")
76
- },
77
- {
78
- type: "input",
79
- name: "keywords",
80
- message: "请输入关键词(用逗号分隔):",
81
- default: ""
66
+ isValidPackageName(input)
67
+ ? true
68
+ : "应用包标识符不符合规范,建议使用反向域名表示法"
82
69
  },
70
+ // {
71
+ // type: "input",
72
+ // name: "description",
73
+ // message: "请输入应用描述:",
74
+ // default: manifest["description"] || ""
75
+ // },
76
+ // {
77
+ // type: "input",
78
+ // name: "brief",
79
+ // message: "请输入应用简介(简单的概述):",
80
+ // default: ""
81
+ // },
82
+ // 开发者暂时不能配置应用分类
83
+ // {
84
+ // type: "checkbox",
85
+ // name: "category",
86
+ // message: "请选择应用分类:",
87
+ // choices: categories,
88
+ // validate: (input) => (input.length > 0 ? true : "请至少选择一个分类")
89
+ // },
90
+ // {
91
+ // type: "input",
92
+ // name: "keywords",
93
+ // message: "请输入关键词(用逗号分隔):",
94
+ // default: ""
95
+ // },
83
96
  {
84
97
  type: "input",
85
98
  name: "source",
86
- message: "请输入应用来源:",
99
+ message: "请输入应用来源(原创应用可不填):",
87
100
  default: manifest["homepage"] || ""
88
101
  },
89
102
  {
90
103
  type: "input",
91
104
  name: "author",
92
- message: "请输入作者名称:",
105
+ message: "请输入作者名称(原创应用可不填):",
93
106
  default: manifest["author"] || ""
94
107
  }
95
108
  ]
96
109
 
97
110
  const answers = await inquirer.prompt(questions)
98
- return answers
111
+ return {
112
+ language: langKey, // 应用主要语言(后续需要做成可以让用户选择)
113
+ ...answers
114
+ }
99
115
  }
100
116
 
101
- async function askWhetherCreateLPK(baseUrl, manifest) {
117
+ async function askWhetherCreateLPK(baseUrl, manifest, locale) {
102
118
  const answers = await inquirer.prompt([
103
119
  {
104
120
  name: "continue",
@@ -109,10 +125,16 @@ async function askWhetherCreateLPK(baseUrl, manifest) {
109
125
  }
110
126
  ])
111
127
  if (answers.continue.toLowerCase() === "y") {
112
- const appInfo = await askPublishAppInfo(baseUrl, manifest)
113
- const crateAppRes = await request(`${baseUrl}/apps`, {
128
+ const appInfo = await askPublishAppInfo(baseUrl, manifest, locale)
129
+ const crateAppRes = await request(`${baseUrl}/app/create`, {
114
130
  method: "POST",
115
- body: JSON.stringify(appInfo)
131
+ body: JSON.stringify({
132
+ package: appInfo.package,
133
+ language: appInfo.language,
134
+ name: appInfo.name,
135
+ source: appInfo.source,
136
+ source_author: appInfo.author
137
+ })
116
138
  })
117
139
  logger.debug("create app res: ", await crateAppRes.text())
118
140
  logger.info(`创建 ${manifest["package"]} 应用成功!`)
@@ -131,7 +153,7 @@ async function askWhetherCreateLPK(baseUrl, manifest) {
131
153
  }
132
154
 
133
155
  export class Publish {
134
- constructor(baseUrl = "https://appstore.api.lazycat.cloud/api/v2/developer") {
156
+ constructor(baseUrl = `${appStoreServerUrl}/api/v3/developer`) {
135
157
  this.baseUrl = baseUrl
136
158
  }
137
159
 
@@ -170,16 +192,17 @@ export class Publish {
170
192
  try {
171
193
  unzipSync(pkgPath, tempDir, ["manifest.yml"])
172
194
  const manifest = loadFromYaml(path.join(tempDir, "manifest.yml"))
173
- const checkUrl = this.baseUrl + `/apps/${manifest["package"]}/check_exist`
195
+ const checkUrl =
196
+ this.baseUrl + `/app/check/exist?package=${manifest["package"]}`
174
197
  const res = await request(checkUrl, { method: "GET" })
175
198
  if (res.status >= 400) {
176
199
  logger.error("检测应用是否存在出错, 错误状态码为: ", res.status)
177
- logger.error(await res.text())
200
+ logger.error(await res.json())
178
201
  return { manifest, appIdExisted: true } // 默认认为已经存在
179
202
  } else {
180
- const text = (await res.text()).trim()
181
- logger.debug(`check appId[${manifest["package"]}] exist: ${text}`)
182
- return { manifest, appIdExisted: text == "true" }
203
+ const { exist = false } = await res.json()
204
+ logger.debug(`check appId[${manifest["package"]}] exist: ${exist}`)
205
+ return { manifest, appIdExisted: exist }
183
206
  }
184
207
  } finally {
185
208
  fs.rmSync(tempDir, { recursive: true })
@@ -189,19 +212,20 @@ export class Publish {
189
212
  /**
190
213
  * @param {string} pkgPath
191
214
  * @param {string} changelog
215
+ * @param {string} locale
192
216
  */
193
- async publish(pkgPath, changelog) {
217
+ async publish(pkgPath, changelog, locale = "zh") {
194
218
  if (!Publish.preCheck(pkgPath)) return
195
219
 
196
220
  const { manifest, appIdExisted } = await this.checkAppIdExist(pkgPath)
197
221
  if (!appIdExisted) {
198
- await askWhetherCreateLPK(this.baseUrl, manifest)
222
+ await askWhetherCreateLPK(this.baseUrl, manifest, locale)
199
223
  }
200
224
 
201
225
  await autoLogin()
202
226
 
203
227
  if (!changelog) {
204
- const answer = await askChangeLog()
228
+ const answer = await askChangeLog(locale)
205
229
  changelog = answer.changelog
206
230
  }
207
231
  changelog = changelog.trim() // clean space ^:)
@@ -210,7 +234,7 @@ export class Publish {
210
234
  const form = new FormData()
211
235
  form.append("file", fs.createReadStream(pkgPath))
212
236
 
213
- const uploadURL = this.baseUrl + "/upload_lpk"
237
+ const uploadURL = this.baseUrl + "/app/lpk/upload"
214
238
  logger.debug("upload url is", uploadURL)
215
239
 
216
240
  const res = await request(uploadURL, {
@@ -225,17 +249,25 @@ export class Publish {
225
249
  const lpkInfo = await JSON.parse(text)
226
250
  logger.debug("upload lpk response", lpkInfo)
227
251
 
228
- const sendURL = this.baseUrl + `/apps/${lpkInfo.package}/reviews`
252
+ const sendURL = this.baseUrl + `/app/${lpkInfo.package}/review/create`
229
253
  logger.debug("publish url is", sendURL)
230
254
 
231
255
  const formData = {
232
- changelog,
233
- name: lpkInfo.name,
234
- iconPath: lpkInfo.iconPath,
235
- pkgPath: lpkInfo.url,
236
- supportPC: lpkInfo.supportPC,
237
- supportMobile: lpkInfo.supportMobile
256
+ version: {
257
+ // supportPC: lpkInfo.supportPC,
258
+ // supportMobile: lpkInfo.supportMobile,
259
+ package: lpkInfo.package,
260
+ name: lpkInfo.version,
261
+ icon_path: lpkInfo.iconPath,
262
+ pkg_path: lpkInfo.url,
263
+ unsupported_platforms: lpkInfo.unsupportedPlatforms,
264
+ min_os_version: lpkInfo.minOsVersion,
265
+ changelogs: {}
266
+ }
238
267
  }
268
+ // changelogs 本地化
269
+ const langKey = getLanguageForLocale(locale)
270
+ formData.version.changelogs[langKey] = changelog
239
271
 
240
272
  logger.debug("form data is", formData)
241
273
 
package/lib/utils.js CHANGED
@@ -655,3 +655,13 @@ export function checkRsync() {
655
655
  resolve()
656
656
  })
657
657
  }
658
+
659
+ export function getLanguageForLocale(locale) {
660
+ locale = locale.replace("_", "-")
661
+ try {
662
+ let l = new Intl.Locale(locale)
663
+ return l.language
664
+ } catch (error) {
665
+ return locale
666
+ }
667
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazycatcloud/lzc-cli",
3
- "version": "1.2.64",
3
+ "version": "1.3.1",
4
4
  "description": "lazycat cloud developer kit",
5
5
  "scripts": {
6
6
  "prepublishOnly": "node check-changelog.js"