@lazycatcloud/lzc-cli 1.3.13 → 2.0.0-pre.0

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 (104) hide show
  1. package/README.md +30 -5
  2. package/changelog.md +16 -0
  3. package/lib/app/index.js +174 -58
  4. package/lib/app/lpk_build.js +197 -18
  5. package/lib/app/lpk_build_images.js +728 -0
  6. package/lib/app/lpk_create.js +96 -23
  7. package/lib/app/lpk_create_generator.js +150 -12
  8. package/lib/app/lpk_devshell.js +35 -21
  9. package/lib/app/lpk_embed_images.js +257 -0
  10. package/lib/app/lpk_installer.js +15 -7
  11. package/lib/app/project_cp.js +64 -0
  12. package/lib/app/project_deploy.js +33 -0
  13. package/lib/app/project_exec.js +45 -0
  14. package/lib/app/project_info.js +106 -0
  15. package/lib/app/project_log.js +67 -0
  16. package/lib/app/project_runtime.js +261 -0
  17. package/lib/app/project_start.js +100 -0
  18. package/lib/appstore/index.js +56 -16
  19. package/lib/appstore/publish.js +16 -13
  20. package/lib/box/index.js +103 -6
  21. package/lib/box/ssh_remote.js +259 -0
  22. package/lib/build_remote.js +22 -0
  23. package/lib/config/index.js +4 -3
  24. package/lib/debug_bridge.js +837 -44
  25. package/lib/docker/index.js +30 -10
  26. package/lib/i18n/index.js +1 -0
  27. package/lib/i18n/locales/en/translation.json +263 -250
  28. package/lib/i18n/locales/zh/translation.json +57 -44
  29. package/lib/lpk/core.js +487 -0
  30. package/lib/lpk/index.js +210 -0
  31. package/lib/shellapi.js +5 -5
  32. package/lib/sig/core.js +254 -0
  33. package/lib/sig/index.js +88 -0
  34. package/lib/utils.js +17 -12
  35. package/package.json +4 -3
  36. package/scripts/cli.js +4 -0
  37. package/template/_lpk/README.md +11 -3
  38. package/template/_lpk/gui-vnc.manifest.yml.in +27 -0
  39. package/template/_lpk/manifest.yml.in +4 -2
  40. package/template/_lpk/todolist-golang.manifest.yml.in +16 -0
  41. package/template/_lpk/todolist-java.manifest.yml.in +15 -0
  42. package/template/_lpk/todolist-python.manifest.yml.in +15 -0
  43. package/template/_lpk/vue.lzc-build.yml.in +0 -44
  44. package/template/blank/_gitignore +1 -0
  45. package/template/blank/lzc-build.yml +25 -40
  46. package/template/blank/lzc-manifest.yml +14 -7
  47. package/template/golang/Dockerfile +19 -0
  48. package/template/golang/README.md +33 -0
  49. package/template/golang/_gitignore +3 -0
  50. package/template/golang/go.mod +3 -0
  51. package/template/golang/lzc-build.yml +21 -0
  52. package/template/golang/lzc-icon.png +0 -0
  53. package/template/golang/main.go +252 -0
  54. package/template/golang/run.sh +3 -0
  55. package/template/golang/web/index.html +238 -0
  56. package/template/gui-vnc/README.md +19 -0
  57. package/template/gui-vnc/_gitignore +2 -0
  58. package/template/gui-vnc/images/Dockerfile +30 -0
  59. package/template/gui-vnc/images/kasmvnc.yaml +33 -0
  60. package/template/gui-vnc/images/startup-script.desktop +9 -0
  61. package/template/gui-vnc/images/startup-script.sh +6 -0
  62. package/template/gui-vnc/lzc-build.yml +23 -0
  63. package/template/gui-vnc/lzc-icon.png +0 -0
  64. package/template/python/Dockerfile +15 -0
  65. package/template/python/README.md +33 -0
  66. package/template/python/_gitignore +3 -0
  67. package/template/python/app.py +110 -0
  68. package/template/python/lzc-build.yml +21 -0
  69. package/template/python/lzc-icon.png +0 -0
  70. package/template/python/requirements.txt +1 -0
  71. package/template/python/run.sh +3 -0
  72. package/template/python/web/index.html +238 -0
  73. package/template/springboot/Dockerfile +20 -0
  74. package/template/springboot/README.md +33 -0
  75. package/template/springboot/_gitignore +3 -0
  76. package/template/springboot/lzc-build.yml +21 -0
  77. package/template/springboot/lzc-icon.png +0 -0
  78. package/template/springboot/pom.xml +38 -0
  79. package/template/springboot/run.sh +3 -0
  80. package/template/springboot/src/main/java/cloud/lazycat/app/Application.java +132 -0
  81. package/template/springboot/src/main/resources/application.properties +1 -0
  82. package/template/springboot/src/main/resources/static/index.html +238 -0
  83. package/template/vue/README.md +17 -7
  84. package/template/vue/_gitignore +1 -0
  85. package/template/vue/lzc-build.yml +31 -42
  86. package/template/vue/src/App.vue +36 -25
  87. package/template/vue/src/style.css +106 -49
  88. package/template/vue-minidb/README.md +34 -0
  89. package/template/vue-minidb/_gitignore +26 -0
  90. package/template/vue-minidb/index.html +13 -0
  91. package/template/vue-minidb/lzc-build.yml +48 -0
  92. package/template/vue-minidb/lzc-icon.png +0 -0
  93. package/template/vue-minidb/package.json +21 -0
  94. package/template/vue-minidb/public/vite.svg +1 -0
  95. package/template/vue-minidb/src/App.vue +206 -0
  96. package/template/vue-minidb/src/assets/vue.svg +1 -0
  97. package/template/vue-minidb/src/main.ts +5 -0
  98. package/template/vue-minidb/src/style.css +136 -0
  99. package/template/vue-minidb/src/vite-env.d.ts +1 -0
  100. package/template/vue-minidb/tsconfig.app.json +24 -0
  101. package/template/vue-minidb/tsconfig.json +7 -0
  102. package/template/vue-minidb/tsconfig.node.json +22 -0
  103. package/template/vue-minidb/vite.config.ts +10 -0
  104. /package/template/{vue → vue-minidb}/src/components/HelloWorld.vue +0 -0
@@ -1,8 +1,8 @@
1
1
  import path from 'node:path';
2
2
  import fs from 'node:fs';
3
+ import zlib from 'node:zlib';
3
4
  import logger from 'loglevel';
4
5
  import {
5
- loadFromYaml,
6
6
  isDirExist,
7
7
  isDirSync,
8
8
  isFileExist,
@@ -19,12 +19,16 @@ import spawn from 'cross-spawn';
19
19
  import { LpkManifest } from './lpk_create.js';
20
20
  import archiver from 'archiver';
21
21
  import yaml from 'js-yaml';
22
+ import { buildConfiguredImagesToTempDir } from './lpk_build_images.js';
23
+ import { pipeline } from 'node:stream/promises';
22
24
  import { t } from '../i18n/index.js';
25
+ import mergeWith from 'lodash.mergewith';
26
+ import { DEFAULT_BUILD_BASE_FILE } from '../build_remote.js';
23
27
 
24
28
  async function archiveFolderTo(appDir, out, format = 'zip') {
25
29
  return new Promise(async (resolve, reject) => {
26
30
  if (!fs.existsSync(appDir)) {
27
- reject(new Error(t('lzc_cli.lib.app.lpk_build.archive_folder_to_exist_fail', `{{appDir}} 文件夹不存在`, { appDir })));
31
+ reject(new Error(t('lzc_cli.lib.app.lpk_build.archive_folder_to_exist_fail', `{{appDir}} 文件夹不存在`, { appDir, interpolation: { escapeValue: false } })));
28
32
  return;
29
33
  }
30
34
 
@@ -46,6 +50,66 @@ async function archiveFolderTo(appDir, out, format = 'zip') {
46
50
  });
47
51
  }
48
52
 
53
+ async function gzipFileTo(inputPath, outputPath) {
54
+ const source = fs.createReadStream(inputPath);
55
+ const gzip = zlib.createGzip({ mtime: 0 });
56
+ const target = fs.createWriteStream(outputPath);
57
+ await pipeline(source, gzip, target);
58
+ }
59
+
60
+ function rewriteManifestEmbeddedImages(manifest, resolvedImageByAlias) {
61
+ const EMBED_PREFIX = 'embed:';
62
+ if (!resolvedImageByAlias || typeof resolvedImageByAlias !== 'object') {
63
+ return manifest;
64
+ }
65
+
66
+ const parseAlias = (rawValue) => {
67
+ const trimmed = String(rawValue ?? '').trim();
68
+ if (!trimmed.startsWith(EMBED_PREFIX)) {
69
+ return '';
70
+ }
71
+ const rest = trimmed.slice(EMBED_PREFIX.length).trim();
72
+ if (!rest) {
73
+ throw new Error(`Invalid image reference "${rawValue}", alias is required after "${EMBED_PREFIX}"`);
74
+ }
75
+ const at = rest.indexOf('@');
76
+ const alias = (at >= 0 ? rest.slice(0, at) : rest).trim();
77
+ if (!alias) {
78
+ throw new Error(`Invalid image reference "${rawValue}", alias is required after "${EMBED_PREFIX}"`);
79
+ }
80
+ return alias;
81
+ };
82
+
83
+ const walk = (value) => {
84
+ if (typeof value === 'string') {
85
+ const alias = parseAlias(value);
86
+ if (alias) {
87
+ const resolved = resolvedImageByAlias[alias];
88
+ if (!resolved) {
89
+ throw new Error(`Cannot resolve embedded image alias "${alias}" to final image reference`);
90
+ }
91
+ return `${EMBED_PREFIX}${alias}@${resolved}`;
92
+ }
93
+ return value;
94
+ }
95
+ if (Array.isArray(value)) {
96
+ for (let index = 0; index < value.length; index += 1) {
97
+ value[index] = walk(value[index]);
98
+ }
99
+ return value;
100
+ }
101
+ if (value && typeof value === 'object') {
102
+ for (const [key, item] of Object.entries(value)) {
103
+ value[key] = walk(item);
104
+ }
105
+ return value;
106
+ }
107
+ return value;
108
+ };
109
+
110
+ return walk(manifest);
111
+ }
112
+
49
113
  async function fetchIconTo(options, cwd, destDir) {
50
114
  if (!options['icon']) {
51
115
  logger.warn(t('lzc_cli.lib.app.lpk_build.fetch_icon_to_icon_empty_fail', '图标icon 没有指定'));
@@ -63,15 +127,20 @@ async function fetchIconTo(options, cwd, destDir) {
63
127
  }
64
128
 
65
129
  if (!isFileExist(iconPath)) {
66
- logger.warn(t('lzc_cli.lib.app.lpk_build.fetch_icon_to_icon_file_not_exist_fail', `图标icon {{iconPath}} 不存在`, { iconPath }));
130
+ logger.warn(t('lzc_cli.lib.app.lpk_build.fetch_icon_to_icon_file_not_exist_fail', `图标icon {{ iconPath }} 不存在`, { iconPath, interpolation: { escapeValue: false } }));
67
131
  return;
68
132
  }
69
133
 
70
134
  if (!isPngWithFile(iconPath)) {
71
- logger.warn(t('lzc_cli.lib.app.lpk_build.fetch_icon_to_icon_not_is_png_fail', `图标icon {{iconPath}} 验证失败(不是一个png格式)`, { iconPath }));
135
+ logger.warn(
136
+ t('lzc_cli.lib.app.lpk_build.fetch_icon_to_icon_not_is_png_fail', `图标icon {{ iconPath }} 验证失败(不是一个png格式)`, {
137
+ iconPath,
138
+ interpolation: { escapeValue: false },
139
+ }),
140
+ );
72
141
  return;
73
142
  } else {
74
- logger.debug(t('lzc_cli.lib.app.lpk_build.fetch_icon_to_icon_is_png', `图标icon {{iconPath}} 验证成功(png格式)`, { iconPath }));
143
+ logger.debug(t('lzc_cli.lib.app.lpk_build.fetch_icon_to_icon_is_png', `图标icon {{ iconPath }} 验证成功(png格式)`, { iconPath, interpolation: { escapeValue: false } }));
75
144
  }
76
145
 
77
146
  fs.copyFileSync(iconPath, path.join(destDir, 'icon.png'));
@@ -97,7 +166,12 @@ async function fetchLzcDeployParamTo(options, cwd, destDir) {
97
166
  }
98
167
 
99
168
  if (!isFileExist(deployParamsPath)) {
100
- logger.warn(t('lzc_cli.lib.app.lpk_build.fetch_lzc_deploy_param_to_not_exist', `deploy_params {{deployParamsPath}} 不存在`, { deployParamsPath }));
169
+ logger.warn(
170
+ t('lzc_cli.lib.app.lpk_build.fetch_lzc_deploy_param_to_not_exist', `deploy_params {{ deployParamsPath }} 不存在`, {
171
+ deployParamsPath,
172
+ interpolation: { escapeValue: false },
173
+ }),
174
+ );
101
175
  return;
102
176
  }
103
177
 
@@ -151,12 +225,52 @@ function convenientEnv() {
151
225
  );
152
226
  }
153
227
 
228
+ function formatBytes(bytes) {
229
+ const value = Number(bytes ?? 0);
230
+ if (!Number.isFinite(value) || value <= 0) {
231
+ return '0 B';
232
+ }
233
+ const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
234
+ let size = value;
235
+ let unitIndex = 0;
236
+ while (size >= 1024 && unitIndex < units.length - 1) {
237
+ size /= 1024;
238
+ unitIndex += 1;
239
+ }
240
+ const digits = unitIndex === 0 ? 0 : 2;
241
+ return `${size.toFixed(digits)} ${units[unitIndex]}`;
242
+ }
243
+
244
+ function mergeBuildOptions(baseOptions, topOptions) {
245
+ return mergeWith({}, baseOptions ?? {}, topOptions ?? {}, (objValue, srcValue) => {
246
+ if (Array.isArray(srcValue)) {
247
+ return srcValue;
248
+ }
249
+ return undefined;
250
+ });
251
+ }
252
+
253
+ function loadYamlIfExists(filePath) {
254
+ if (!filePath || !isFileExist(filePath)) {
255
+ return {};
256
+ }
257
+ return yaml.load(fs.readFileSync(filePath, 'utf8')) ?? {};
258
+ }
259
+
260
+ async function loadTemplatedYamlIfExists(filePath, env) {
261
+ if (!filePath || !isFileExist(filePath)) {
262
+ return {};
263
+ }
264
+ return yaml.load(await envTemplateFile(filePath, env)) ?? {};
265
+ }
266
+
154
267
  export class LpkBuild {
155
268
  constructor(cwd, buildConfigFile) {
156
269
  this.pwd = cwd ?? process.cwd();
157
270
 
158
271
  this.optionsFilePath = path.join(this.pwd, buildConfigFile);
159
- this.options = loadFromYaml(this.optionsFilePath);
272
+ this.optionsBaseFilePath = path.join(path.dirname(this.optionsFilePath), DEFAULT_BUILD_BASE_FILE);
273
+ this.options = mergeBuildOptions(loadYamlIfExists(this.optionsBaseFilePath), loadYamlIfExists(this.optionsFilePath));
160
274
 
161
275
  this.manifestFilePath = this.options['manifest'] ? path.join(this.pwd, this.options['manifest']) : path.join(this.pwd, 'lzc-manifest.yml');
162
276
  this.manifest = null;
@@ -171,7 +285,10 @@ export class LpkBuild {
171
285
  async init() {
172
286
  const manifest = await this.getManifest();
173
287
  const primitive = convenientEnv();
174
- this.options = yaml.load(await envTemplateFile(this.optionsFilePath, Object.assign({}, primitive, manifest)));
288
+ const env = Object.assign({}, primitive, manifest);
289
+ const baseOptions = await loadTemplatedYamlIfExists(this.optionsBaseFilePath, env);
290
+ const topOptions = await loadTemplatedYamlIfExists(this.optionsFilePath, env);
291
+ this.options = mergeBuildOptions(baseOptions, topOptions);
175
292
  return this;
176
293
  }
177
294
 
@@ -210,7 +327,7 @@ export class LpkBuild {
210
327
  this.excpManifest = lpkM.excpManifest;
211
328
 
212
329
  if (!isValidPackageName(this.manifest['package'])) {
213
- throw t('lzc_cli.lib.app.lpk_build.get_manifest_package_name_fail', `{{package}} 含有非法字符,请使用正确的包名格式(java的包名格式),如:cloud.lazycat.apps.video`, {
330
+ throw t('lzc_cli.lib.app.lpk_build.get_manifest_package_name_fail', `{{ package }} 含有非法字符,请使用正确的包名格式(java的包名格式),如:cloud.lazycat.apps.video`, {
214
331
  package: this.manifest['package'],
215
332
  });
216
333
  }
@@ -245,9 +362,16 @@ export class LpkBuild {
245
362
 
246
363
  // 输出路径
247
364
  let packName = this.options['lpkPath'];
248
- const pkgout = path.resolve(this.pwd, this.options['pkgout']);
249
- if (!packName && !isDirExist(pkgout)) {
250
- throw t('lzc_cli.lib.app.lpk_build.exec_pkgout_not_exist', `{{pkgout}} 不存在`, { pkgout });
365
+ let pkgout = '';
366
+ if (!packName) {
367
+ const rawPkgout = this.options['pkgout'];
368
+ if (rawPkgout !== undefined && rawPkgout !== null && typeof rawPkgout !== 'string') {
369
+ throw t('lzc_cli.lib.app.lpk_build.exec_pkgout_invalid_type', 'pkgout must be a string when specified');
370
+ }
371
+ pkgout = path.resolve(this.pwd, typeof rawPkgout === 'string' && rawPkgout.trim() !== '' ? rawPkgout : './');
372
+ if (!isDirExist(pkgout)) {
373
+ throw t('lzc_cli.lib.app.lpk_build.exec_pkgout_not_exist', `{{ pkgout }} 不存在`, { pkgout, interpolation: { escapeValue: false } });
374
+ }
251
375
  }
252
376
 
253
377
  const tempDir = fs.mkdtempSync('.lzc-cli-build');
@@ -291,15 +415,19 @@ export class LpkBuild {
291
415
  } else if (isFileExist(browserExtension)) {
292
416
  fs.copyFileSync(browserExtension, path.join(tempDir, 'extension.zip'));
293
417
  } else {
294
- throw t('lzc_cli.lib.app.lpk_build.exec_browser_extension_not_exist', `{{browserExtension}} 不存在`, { browserExtension });
418
+ throw t('lzc_cli.lib.app.lpk_build.exec_browser_extension_not_exist', `{{ browserExtension }} 不存在`, {
419
+ browserExtension,
420
+ interpolation: { escapeValue: false },
421
+ });
295
422
  }
296
423
  }
297
424
 
298
425
  if (aiPodService) {
299
426
  aiPodService = path.resolve(this.pwd, aiPodService);
300
427
  if (!isDirExist(aiPodService)) {
301
- throw t('lzc_cli.lib.app.lpk_build.exec_ai_pos_service_not_exist', `{{aiPodService}} 不存在`, {
428
+ throw t('lzc_cli.lib.app.lpk_build.exec_ai_pos_service_not_exist', `{{ aiPodService }} 不存在`, {
302
429
  aiPodService,
430
+ interpolation: { escapeValue: false },
303
431
  });
304
432
  }
305
433
  fs.cpSync(aiPodService, path.join(tempDir, 'ai-pod-service'), {
@@ -318,7 +446,43 @@ export class LpkBuild {
318
446
  }, manifest);
319
447
  }
320
448
 
321
- if (process.env.LZC_MANIFEST_TEMPLATE) {
449
+ if (Object.prototype.hasOwnProperty.call(this.options, 'embed_images')) {
450
+ throw new Error('embed_images is removed, please use lzc-build.yml images and manifest embed:alias');
451
+ }
452
+ if (Object.prototype.hasOwnProperty.call(this.options, 'embed_all_images')) {
453
+ throw new Error('embed_all_images is removed');
454
+ }
455
+ if (Object.prototype.hasOwnProperty.call(this.options, 'upstream_registry')) {
456
+ throw new Error('upstream_registry is renamed to upstream_match');
457
+ }
458
+ if (Object.prototype.hasOwnProperty.call(this.options, 'upstream_match') || Object.prototype.hasOwnProperty.call(this.options, 'upstream-match')) {
459
+ throw new Error('upstream_match is moved to lzc-build.yml images.<alias>.upstream-match');
460
+ }
461
+
462
+ const hasImagesConfig = !!this.options['images'];
463
+
464
+ let useTarPackage = false;
465
+ let embeddedImageSummary = null;
466
+ if (hasImagesConfig) {
467
+ const buildResult = await buildConfiguredImagesToTempDir(this.options['images'], manifest, this.pwd, tempDir, {
468
+ remote: this.options['remote'],
469
+ });
470
+ if (buildResult.imageCount > 0) {
471
+ useTarPackage = true;
472
+ embeddedImageSummary = buildResult;
473
+ manifest = rewriteManifestEmbeddedImages(manifest, buildResult.resolvedImageByAlias);
474
+ }
475
+ }
476
+ if (useTarPackage) {
477
+ const contentTar = path.join(tempDir, 'content.tar');
478
+ const contentTarGz = path.join(tempDir, 'content.tar.gz');
479
+ if (isFileExist(contentTar)) {
480
+ await gzipFileTo(contentTar, contentTarGz);
481
+ fs.rmSync(contentTar, { force: true });
482
+ }
483
+ }
484
+
485
+ if (process.env.LZC_MANIFEST_TEMPLATE && !useTarPackage) {
322
486
  logger.debug('copy origin manifest\n', this.manifestFilePath);
323
487
  fs.copyFileSync(this.manifestFilePath, path.join(tempDir, 'manifest.yml'));
324
488
  } else {
@@ -345,11 +509,26 @@ export class LpkBuild {
345
509
  }
346
510
 
347
511
  if (!packName) {
348
- packName = path.resolve(pkgout, `${manifest.package}-v${manifest.version}.lpk`);
512
+ const ext = '.lpk';
513
+ packName = path.resolve(pkgout, `${manifest.package}-v${manifest.version}${ext}`);
349
514
  }
350
515
 
351
- const lpkPath = await archiveFolderTo(tempDir, packName);
352
- logger.info(t('lzc_cli.lib.app.lpk_build.exec_output_lpk_path', `输出lpk包 {{lpkPathStr}}`, { lpkPathStr: lpkPath.path }));
516
+ const lpkPath = await archiveFolderTo(tempDir, packName, useTarPackage ? 'tar' : 'zip');
517
+ logger.info(
518
+ `${t('lzc_cli.lib.app.lpk_build.exec_output_lpk_path', '输出lpk包 {{ path }}', {
519
+ path: lpkPath.path,
520
+ interpolation: { escapeValue: false }, // https://www.i18next.com/translation-function/interpolation#unescape
521
+ })}`,
522
+ );
523
+ if (embeddedImageSummary && embeddedImageSummary.imageCount > 0) {
524
+ logger.info('Embedded image upstream summary:');
525
+ for (const [alias, upstream] of Object.entries(embeddedImageSummary.upstreamByAlias ?? {})) {
526
+ logger.info(`- ${alias}: ${upstream || '(none, full embed)'}`);
527
+ }
528
+ logger.info(
529
+ `Embedded image layer size: ${formatBytes(embeddedImageSummary.embeddedLayerBytes)} (${embeddedImageSummary.embeddedLayerBytes} bytes, ${embeddedImageSummary.embeddedLayerCount} unique layers)`,
530
+ );
531
+ }
353
532
 
354
533
  return lpkPath.path;
355
534
  } finally {