@lazycatcloud/lzc-cli 2.0.0-pre.0 → 2.0.0-pre.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.
Files changed (88) hide show
  1. package/README.md +22 -7
  2. package/changelog.md +50 -19
  3. package/lib/app/apkshell.js +7 -44
  4. package/lib/app/index.js +4 -0
  5. package/lib/app/lpk_build.js +266 -56
  6. package/lib/app/lpk_build_images.js +37 -16
  7. package/lib/app/lpk_create.js +158 -83
  8. package/lib/app/lpk_create_generator.js +35 -42
  9. package/lib/app/lpk_installer.js +4 -3
  10. package/lib/app/manifest_build.js +259 -0
  11. package/lib/app/project_cp.js +5 -10
  12. package/lib/app/project_deploy.js +36 -11
  13. package/lib/app/project_exec.js +48 -11
  14. package/lib/app/project_info.js +59 -59
  15. package/lib/app/project_log.js +5 -10
  16. package/lib/app/project_runtime.js +113 -18
  17. package/lib/app/project_start.js +6 -11
  18. package/lib/app/project_sync.js +499 -0
  19. package/lib/appstore/apkshell.js +50 -0
  20. package/lib/appstore/publish.js +54 -15
  21. package/lib/build_remote.js +0 -1
  22. package/lib/config/index.js +1 -1
  23. package/lib/debug_bridge.js +81 -21
  24. package/lib/i18n/locales/en/translation.json +262 -262
  25. package/lib/i18n/locales/zh/translation.json +262 -262
  26. package/lib/lpk/core.js +2 -1
  27. package/lib/migrate/index.js +52 -0
  28. package/lib/package_info.js +135 -0
  29. package/lib/shellapi.js +35 -1
  30. package/lib/sig/core.js +2 -2
  31. package/lib/utils.js +91 -14
  32. package/package.json +89 -89
  33. package/scripts/cli.js +2 -0
  34. package/scripts/smoke/frontend-dev-entry.mjs +104 -0
  35. package/scripts/smoke/template-project.mjs +311 -0
  36. package/template/_lpk/README.md +6 -3
  37. package/template/_lpk/gui-vnc.manifest.yml.in +0 -9
  38. package/template/_lpk/hello-vue.manifest.yml.in +38 -0
  39. package/template/_lpk/manifest.yml.in +0 -9
  40. package/template/_lpk/package.yml.in +7 -0
  41. package/template/_lpk/todolist-golang.manifest.yml.in +23 -9
  42. package/template/_lpk/todolist-java.manifest.yml.in +23 -9
  43. package/template/_lpk/todolist-python.manifest.yml.in +31 -9
  44. package/template/_lpk/todolist-serverless.manifest.yml.in +38 -0
  45. package/template/blank/lzc-build.dev.yml +4 -0
  46. package/template/blank/lzc-build.yml +0 -2
  47. package/template/blank/lzc-manifest.yml +3 -12
  48. package/template/blank/package.yml +7 -0
  49. package/template/golang/Dockerfile +1 -1
  50. package/template/golang/Dockerfile.dev +20 -0
  51. package/template/golang/README.md +22 -11
  52. package/template/golang/_lzcdevignore +21 -0
  53. package/template/golang/lzc-build.dev.yml +12 -0
  54. package/template/golang/lzc-build.yml +0 -5
  55. package/template/golang/main.go +1 -1
  56. package/template/golang/manifest.dev.page.js +24 -0
  57. package/template/golang/run.sh +7 -0
  58. package/template/gui-vnc/README.md +5 -1
  59. package/template/gui-vnc/lzc-build.dev.yml +4 -0
  60. package/template/gui-vnc/lzc-build.yml +0 -5
  61. package/template/python/Dockerfile +2 -2
  62. package/template/python/Dockerfile.dev +18 -0
  63. package/template/python/README.md +28 -11
  64. package/template/python/_lzcdevignore +21 -0
  65. package/template/python/app.py +1 -1
  66. package/template/python/lzc-build.dev.yml +12 -0
  67. package/template/python/lzc-build.yml +0 -5
  68. package/template/python/manifest.dev.page.js +25 -0
  69. package/template/python/run.sh +12 -1
  70. package/template/springboot/Dockerfile +1 -1
  71. package/template/springboot/Dockerfile.dev +20 -0
  72. package/template/springboot/README.md +22 -11
  73. package/template/springboot/_lzcdevignore +21 -0
  74. package/template/springboot/lzc-build.dev.yml +12 -0
  75. package/template/springboot/lzc-build.yml +0 -5
  76. package/template/springboot/manifest.dev.page.js +24 -0
  77. package/template/springboot/run.sh +7 -0
  78. package/template/vue/README.md +14 -27
  79. package/template/vue/_gitignore +0 -1
  80. package/template/vue/lzc-build.dev.yml +7 -0
  81. package/template/vue/lzc-build.yml +0 -2
  82. package/template/vue/manifest.dev.page.js +50 -0
  83. package/template/vue-minidb/README.md +11 -19
  84. package/template/vue-minidb/_gitignore +0 -1
  85. package/template/vue-minidb/lzc-build.dev.yml +7 -0
  86. package/template/vue-minidb/lzc-build.yml +0 -2
  87. package/template/vue-minidb/manifest.dev.page.js +50 -0
  88. package/template/blank/_gitignore +0 -1
@@ -0,0 +1,50 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { Blob } from 'node:buffer';
4
+ import logger from 'loglevel';
5
+ import fetch, { FormData } from 'node-fetch';
6
+ import { t } from '../i18n/index.js';
7
+ import { appStoreServerUrl } from './env.js';
8
+
9
+ // axios@1.7.7 依赖的 form-data 使用了 util.isArray 导致 node 会输出弃用警告 (所以将逻辑迁移避免依赖)
10
+ export async function triggerApk(id, name, iconPath) {
11
+ if (!id) {
12
+ logger.error(t('lzc_cli.lib.appstore.apkshell.trigger_apk_empty_appid', 'Appid 为必填项!'));
13
+ return;
14
+ }
15
+
16
+ const appName = name || t('lzc_cli.lib.appstore.apkshell.trigger_apk_default_app_name', '懒猫应用');
17
+ const form = new FormData();
18
+ form.append('app_id', id);
19
+ form.append('app_name', appName);
20
+
21
+ if (iconPath && fs.existsSync(iconPath)) {
22
+ const iconBuffer = fs.readFileSync(iconPath);
23
+ form.append('app_icon', new Blob([iconBuffer]), path.basename(iconPath));
24
+ }
25
+
26
+ const controller = new AbortController();
27
+ const timer = setTimeout(() => controller.abort(), 5000);
28
+
29
+ try {
30
+ const resp = await fetch(`${appStoreServerUrl}/api/trigger_latest_for_app`, {
31
+ method: 'POST',
32
+ body: form,
33
+ signal: controller.signal,
34
+ });
35
+
36
+ logger.debug('triggerApk resp:', resp);
37
+ if (resp.status == 304) {
38
+ logger.debug(t('lzc_cli.lib.appstore.apkshell.trigger_apk_build_tips', `APK构建任务已创建成功,如需使用安卓端,请耐心等待1分钟左右`));
39
+ } else if (resp.status <= 201) {
40
+ logger.info(t('lzc_cli.lib.appstore.apkshell.trigger_apk_build_ok_tips', `APK构建任务已创建成功,如需使用安卓端,请耐心等待1分钟左右`));
41
+ } else if (resp.status >= 400) {
42
+ logger.debug(t('lzc_cli.lib.appstore.apkshell.trigger_apk_build_failed', '请求按钮应用出错:'), await resp.text());
43
+ throw t('lzc_cli.lib.appstore.apkshell.trigger_apk_build_failed_tips', `请求生成应用出错! 使用 --apk=n 停止生成APK`);
44
+ }
45
+ } catch (error) {
46
+ logger.debug(error);
47
+ } finally {
48
+ clearTimeout(timer);
49
+ }
50
+ }
@@ -107,15 +107,33 @@ async function askPublishAppInfo(baseUrl, manifest, locale) {
107
107
  }
108
108
 
109
109
  async function askWhetherCreateLPK(baseUrl, manifest, locale) {
110
- const answers = await inquirer.prompt([
111
- {
112
- name: 'continue',
113
- type: 'input',
114
- message: t('lzc_cli.lib.publish.ask_whether_create_lpk_continue_prompt', '检测到您当前的应用,还没有在懒猫微服中创建,是否使用当前的安装包中的信息进行创建? [y/n]'),
115
- default: 'y',
116
- },
117
- ]);
118
- if (answers.continue.toLowerCase() === 'y') {
110
+ try {
111
+ // 是否允许创建应用预检请求
112
+ const _options = await request(`${baseUrl}/app/create`, {
113
+ method: 'OPTIONS',
114
+ });
115
+ logger.debug('create app preflight:', { url: _options.url, ok: _options.ok, status: _options.status });
116
+ if (!_options.ok || _options.status < 199 || _options.status > 299) {
117
+ throw undefined;
118
+ }
119
+
120
+ // 要求输入应用信息
121
+ const answers = await inquirer.prompt([
122
+ {
123
+ name: 'continue',
124
+ type: 'input',
125
+ message: t(
126
+ 'lzc_cli.lib.publish.ask_whether_create_lpk_continue_prompt',
127
+ '检测到您当前的应用,还没有在懒猫微服中创建,是否使用当前的安装包中的信息进行创建? [y/n]',
128
+ ),
129
+ default: 'y',
130
+ },
131
+ ]);
132
+ if (answers.continue.toLowerCase() != 'y') {
133
+ throw undefined;
134
+ }
135
+
136
+ // 发送创建应用请求
119
137
  const appInfo = await askPublishAppInfo(baseUrl, manifest, locale);
120
138
  const crateAppRes = await request(`${baseUrl}/app/create`, {
121
139
  method: 'POST',
@@ -127,9 +145,28 @@ async function askWhetherCreateLPK(baseUrl, manifest, locale) {
127
145
  source_author: appInfo.author,
128
146
  }),
129
147
  });
130
- logger.debug('create app res: ', await crateAppRes.text());
131
- logger.info(t('lzc_cli.lib.publish.ask_whether_create_lpk_success_tips', `创建 {{ package }} 应用成功!`, { package: manifest['package'], interpolation: { escapeValue: false } }));
132
- } else {
148
+ logger.debug('create app res:', await crateAppRes.text());
149
+ if (!crateAppRes.ok) {
150
+ const data = await crateAppRes.json();
151
+ throw new Error(data?.message || `status:${crateAppRes.status} statusText:${crateAppRes.statusText}`);
152
+ }
153
+ logger.info(
154
+ t('lzc_cli.lib.publish.ask_whether_create_lpk_success_tips', `创建 {{ package }} 应用成功!`, {
155
+ package: manifest['package'],
156
+ interpolation: { escapeValue: false },
157
+ }),
158
+ );
159
+ } catch (error) {
160
+ if (error) {
161
+ logger.debug('create app failed: ', error.message ?? 'unknown');
162
+ logger.error(
163
+ t('lzc_cli.lib.publish.ask_whether_create_lpk_failed_tips', `创建 {{ package }} 应用失败!`, {
164
+ package: manifest['package'],
165
+ interpolation: { escapeValue: false },
166
+ }),
167
+ error.message || '',
168
+ );
169
+ }
133
170
  logger.info(
134
171
  t(
135
172
  'lzc_cli.lib.publish.ask_whether_create_lpk_fail_tips',
@@ -212,7 +249,7 @@ export class Publish {
212
249
 
213
250
  const { manifest, appIdExisted } = await this.checkAppIdExist(pkgPath);
214
251
  if (!appIdExisted) {
215
- await askWhetherCreateLPK(this.baseUrl, manifest, locale);
252
+ await askWhetherCreateLPK(this.baseUrl, manifest, currentLocale);
216
253
  }
217
254
 
218
255
  await autoLogin();
@@ -239,7 +276,7 @@ export class Publish {
239
276
  logger.error(
240
277
  t('lzc_cli.lib.publish.publish_lpk_fail_tips', `LPK 文件上传失败,err: {{ message }}`, {
241
278
  message: lpkInfo?.message ?? lpkInfo,
242
- interpolation: { escapeValue: false }
279
+ interpolation: { escapeValue: false },
243
280
  }),
244
281
  );
245
282
  throw new Error(lpkInfo?.message ?? lpkInfo);
@@ -253,7 +290,7 @@ export class Publish {
253
290
 
254
291
  // changelogs 本地化
255
292
  const langKey = getLanguageForLocale(currentLocale);
256
- changelogs[langKey] = changelog
293
+ changelogs[langKey] = changelog;
257
294
  }
258
295
  logger.debug('publish changelogs is', changelogs);
259
296
 
@@ -270,6 +307,8 @@ export class Publish {
270
307
  pkg_path: lpkInfo.url,
271
308
  unsupported_platforms: lpkInfo.unsupportedPlatforms,
272
309
  min_os_version: lpkInfo.minOsVersion,
310
+ lpk_size: lpkInfo.lpkSize,
311
+ image_size: lpkInfo.imageSize,
273
312
  changelogs,
274
313
  },
275
314
  };
@@ -1,7 +1,6 @@
1
1
  import { loadBuildRemoteFromConfig } from './box/ssh_remote.js';
2
2
 
3
3
  export const DEFAULT_BUILD_CONFIG_FILE = 'lzc-build.yml';
4
- export const DEFAULT_BUILD_BASE_FILE = 'lzc-build.base.yml';
5
4
 
6
5
  export function resolveBuildRemoteFromOptions(options, source = DEFAULT_BUILD_CONFIG_FILE) {
7
6
  void options;
@@ -51,7 +51,7 @@ export function configCommand(program) {
51
51
  ];
52
52
  program.command({
53
53
  command: 'config',
54
- desc: false,
54
+ desc: t('lzc_cli.lib.config.index.config_cmd_desc', '配置管理'),
55
55
  builder: (args) => {
56
56
  args.command(subCommands);
57
57
  args.example('$0 config get', t('lzc_cli.lib.config.index.config_cmd_list_tips', '列出所有配置'))
@@ -108,6 +108,21 @@ function normalizeErrorMessage(error) {
108
108
  return String(error).trim();
109
109
  }
110
110
 
111
+ export function shellEscapeArg(value) {
112
+ const text = String(value ?? '');
113
+ if (text === '') {
114
+ return "''";
115
+ }
116
+ if (/^[A-Za-z0-9_/.:=,@+-]+$/.test(text)) {
117
+ return text;
118
+ }
119
+ return "'" + text.replace(/'/g, "'\\''") + "'";
120
+ }
121
+
122
+ export function buildSSHRemoteCommand(args = []) {
123
+ return args.map((arg) => shellEscapeArg(arg)).join(' ');
124
+ }
125
+
111
126
  export class DebugBridge {
112
127
  constructor(baseDir = process.cwd(), buildRemote = undefined) {
113
128
  this.buildRemote = buildRemote === undefined ? resolveBuildRemoteFromFile(baseDir) : buildRemote;
@@ -275,6 +290,13 @@ export class DebugBridge {
275
290
  return args;
276
291
  }
277
292
 
293
+ async resolveDevId() {
294
+ if (this.isBuildRemoteMode()) {
295
+ return '';
296
+ }
297
+ return shellApi.resolveClientId();
298
+ }
299
+
278
300
  async init() {
279
301
  await this.checkDevTools();
280
302
  if (!(await this.canPublicKey())) {
@@ -484,6 +506,29 @@ export class DebugBridge {
484
506
  }
485
507
  }
486
508
 
509
+ async syncDevID(appId, devId = '', userApp = false) {
510
+ const normalizedDevId = String(devId ?? '').trim();
511
+ if (this.isBuildRemoteMode()) {
512
+ const commandArgs = await this.remoteCommandWithUID(['sync-dev-id']);
513
+ if (normalizedDevId) {
514
+ commandArgs.push('--dev-id', normalizedDevId);
515
+ }
516
+ if (userApp) {
517
+ commandArgs.push('--userapp');
518
+ }
519
+ commandArgs.push(appId);
520
+ return this.remoteCommon(commandArgs);
521
+ }
522
+ const rawArgs = ['sync-dev-id', `--uid ${this.uid}`];
523
+ if (normalizedDevId) {
524
+ rawArgs.push(`--dev-id ${normalizedDevId}`);
525
+ }
526
+ if (userApp) {
527
+ rawArgs.push('--userapp');
528
+ }
529
+ rawArgs.push(appId);
530
+ return this.common(sshBinary(), [...sshCmdArgs(`box@${this.domain}`), rawArgs.join(' ')]);
531
+ }
487
532
  async isDevshell(appId) {
488
533
  await this.backendVersion020();
489
534
  if (this.isBuildRemoteMode()) {
@@ -698,15 +743,20 @@ export class DebugBridge {
698
743
 
699
744
  async lzcDocker(argv) {
700
745
  await this.backendVersion020();
701
- const stream = this.isBuildRemoteMode()
702
- ? spawn(sshBinary(), this.remoteBridgeArgs(['lzc-docker', ...argv], { tty: true }), {
703
- shell: false,
704
- stdio: 'inherit',
705
- })
706
- : spawn(sshBinary(), [...sshCmdArgs(`box@${await resolveDomain(this.domain)}`), '-t', 'lzc-docker', ...argv], {
707
- shell: true,
708
- stdio: 'inherit',
709
- });
746
+ let stream;
747
+ if (this.isBuildRemoteMode()) {
748
+ stream = spawn(sshBinary(), this.remoteBridgeArgs(['lzc-docker', ...argv], { tty: true }), {
749
+ shell: false,
750
+ stdio: 'inherit',
751
+ });
752
+ } else {
753
+ const resolvedIp = await resolveDomain(this.domain);
754
+ const remoteCommand = buildSSHRemoteCommand(['lzc-docker', ...argv]);
755
+ stream = spawn(sshBinary(), [...sshCmdArgsRaw(`box@${resolvedIp}`), '-t', remoteCommand], {
756
+ shell: false,
757
+ stdio: 'inherit',
758
+ });
759
+ }
710
760
  return new Promise((resolve, reject) => {
711
761
  stream.on('close', (code) => {
712
762
  code == 0 ? resolve() : reject();
@@ -911,24 +961,29 @@ export class DebugBridge {
911
961
  });
912
962
  }
913
963
 
914
- async lzcDockerCapture(argv) {
964
+ async lzcDockerCapture(argv, options = {}) {
915
965
  await this.backendVersion020();
966
+ const stdinText = options && Object.prototype.hasOwnProperty.call(options, 'stdinText') ? String(options.stdinText ?? '') : '';
916
967
  let stream;
917
968
  if (this.isBuildRemoteMode()) {
918
969
  stream = spawn(sshBinary(), this.remoteBridgeArgs(['lzc-docker', ...argv]), {
919
970
  shell: false,
920
- stdio: ['ignore', 'pipe', 'inherit'],
971
+ stdio: ['pipe', 'pipe', 'inherit'],
921
972
  });
922
973
  } else {
923
974
  const resolvedIp = await resolveDomain(this.domain);
924
975
  stream = spawn(sshBinary(), [...sshCmdArgsRaw(`box@${resolvedIp}`), 'lzc-docker', ...argv], {
925
976
  shell: false,
926
- stdio: ['ignore', 'pipe', 'inherit'],
977
+ stdio: ['pipe', 'pipe', 'inherit'],
927
978
  });
928
979
  }
929
980
 
930
981
  return await new Promise((resolve, reject) => {
931
982
  let output = '';
983
+ if (stdinText) {
984
+ stream.stdin.write(stdinText);
985
+ }
986
+ stream.stdin.end();
932
987
  stream.stdout.on('data', (chunk) => {
933
988
  output += chunk.toString();
934
989
  });
@@ -1045,15 +1100,20 @@ export class DebugBridge {
1045
1100
 
1046
1101
  async lzcDockerCompose(argv) {
1047
1102
  await this.backendVersion020();
1048
- const stream = this.isBuildRemoteMode()
1049
- ? spawn(sshBinary(), this.remoteBridgeArgs(['lzc-docker-compose', ...argv], { tty: true }), {
1050
- shell: false,
1051
- stdio: 'inherit',
1052
- })
1053
- : spawn(sshBinary(), [...sshCmdArgs(`box@${await resolveDomain(this.domain)}`), '-t', 'lzc-docker-compose', ...argv], {
1054
- shell: true,
1055
- stdio: 'inherit',
1056
- });
1103
+ let stream;
1104
+ if (this.isBuildRemoteMode()) {
1105
+ stream = spawn(sshBinary(), this.remoteBridgeArgs(['lzc-docker-compose', ...argv], { tty: true }), {
1106
+ shell: false,
1107
+ stdio: 'inherit',
1108
+ });
1109
+ } else {
1110
+ const resolvedIp = await resolveDomain(this.domain);
1111
+ const remoteCommand = buildSSHRemoteCommand(['lzc-docker-compose', ...argv]);
1112
+ stream = spawn(sshBinary(), [...sshCmdArgsRaw(`box@${resolvedIp}`), '-t', remoteCommand], {
1113
+ shell: false,
1114
+ stdio: 'inherit',
1115
+ });
1116
+ }
1057
1117
  return new Promise((resolve, reject) => {
1058
1118
  stream.on('close', (code) => {
1059
1119
  code == 0 ? resolve() : reject();