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

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 (92) hide show
  1. package/README.md +46 -7
  2. package/changelog.md +56 -19
  3. package/lib/app/apkshell.js +7 -44
  4. package/lib/app/index.js +5 -1
  5. package/lib/app/lpk_build.js +266 -56
  6. package/lib/app/lpk_build_images.js +424 -229
  7. package/lib/app/lpk_build_images_local.js +425 -0
  8. package/lib/app/lpk_build_images_pack_local.js +409 -0
  9. package/lib/app/lpk_create.js +158 -83
  10. package/lib/app/lpk_create_generator.js +35 -42
  11. package/lib/app/lpk_devshell.js +6 -2
  12. package/lib/app/lpk_installer.js +4 -3
  13. package/lib/app/manifest_build.js +259 -0
  14. package/lib/app/project_cp.js +5 -10
  15. package/lib/app/project_deploy.js +80 -11
  16. package/lib/app/project_exec.js +48 -11
  17. package/lib/app/project_info.js +59 -59
  18. package/lib/app/project_log.js +5 -10
  19. package/lib/app/project_runtime.js +113 -18
  20. package/lib/app/project_start.js +6 -11
  21. package/lib/app/project_sync.js +499 -0
  22. package/lib/appstore/apkshell.js +50 -0
  23. package/lib/appstore/publish.js +54 -15
  24. package/lib/build_remote.js +0 -1
  25. package/lib/config/index.js +1 -1
  26. package/lib/debug_bridge.js +217 -47
  27. package/lib/i18n/locales/en/translation.json +262 -262
  28. package/lib/i18n/locales/zh/translation.json +262 -262
  29. package/lib/lpk/core.js +2 -1
  30. package/lib/migrate/index.js +52 -0
  31. package/lib/package_info.js +135 -0
  32. package/lib/shellapi.js +35 -1
  33. package/lib/sig/core.js +2 -2
  34. package/lib/utils.js +92 -15
  35. package/package.json +89 -89
  36. package/scripts/cli.js +2 -0
  37. package/scripts/smoke/frontend-dev-entry.mjs +104 -0
  38. package/scripts/smoke/template-project.mjs +311 -0
  39. package/template/_lpk/README.md +6 -3
  40. package/template/_lpk/gui-vnc.manifest.yml.in +0 -9
  41. package/template/_lpk/hello-vue.manifest.yml.in +38 -0
  42. package/template/_lpk/manifest.yml.in +0 -9
  43. package/template/_lpk/package.yml.in +7 -0
  44. package/template/_lpk/todolist-golang.manifest.yml.in +23 -9
  45. package/template/_lpk/todolist-java.manifest.yml.in +23 -9
  46. package/template/_lpk/todolist-python.manifest.yml.in +31 -9
  47. package/template/_lpk/todolist-serverless.manifest.yml.in +38 -0
  48. package/template/blank/lzc-build.dev.yml +4 -0
  49. package/template/blank/lzc-build.yml +0 -2
  50. package/template/blank/lzc-manifest.yml +3 -12
  51. package/template/blank/package.yml +7 -0
  52. package/template/golang/Dockerfile +1 -1
  53. package/template/golang/Dockerfile.dev +20 -0
  54. package/template/golang/README.md +22 -11
  55. package/template/golang/_lzcdevignore +21 -0
  56. package/template/golang/lzc-build.dev.yml +12 -0
  57. package/template/golang/lzc-build.yml +0 -5
  58. package/template/golang/main.go +1 -1
  59. package/template/golang/manifest.dev.page.js +24 -0
  60. package/template/golang/run.sh +7 -0
  61. package/template/gui-vnc/README.md +5 -1
  62. package/template/gui-vnc/lzc-build.dev.yml +4 -0
  63. package/template/gui-vnc/lzc-build.yml +0 -5
  64. package/template/python/Dockerfile +2 -2
  65. package/template/python/Dockerfile.dev +18 -0
  66. package/template/python/README.md +28 -11
  67. package/template/python/_lzcdevignore +21 -0
  68. package/template/python/app.py +1 -1
  69. package/template/python/lzc-build.dev.yml +12 -0
  70. package/template/python/lzc-build.yml +0 -5
  71. package/template/python/manifest.dev.page.js +25 -0
  72. package/template/python/run.sh +12 -1
  73. package/template/springboot/Dockerfile +1 -1
  74. package/template/springboot/Dockerfile.dev +20 -0
  75. package/template/springboot/README.md +22 -11
  76. package/template/springboot/_lzcdevignore +21 -0
  77. package/template/springboot/lzc-build.dev.yml +12 -0
  78. package/template/springboot/lzc-build.yml +0 -5
  79. package/template/springboot/manifest.dev.page.js +24 -0
  80. package/template/springboot/run.sh +7 -0
  81. package/template/vue/README.md +14 -27
  82. package/template/vue/_gitignore +0 -1
  83. package/template/vue/lzc-build.dev.yml +7 -0
  84. package/template/vue/lzc-build.yml +0 -2
  85. package/template/vue/manifest.dev.page.js +50 -0
  86. package/template/vue/src/App.vue +1 -1
  87. package/template/vue-minidb/README.md +11 -19
  88. package/template/vue-minidb/_gitignore +0 -1
  89. package/template/vue-minidb/lzc-build.dev.yml +7 -0
  90. package/template/vue-minidb/lzc-build.yml +0 -2
  91. package/template/vue-minidb/manifest.dev.page.js +50 -0
  92. package/template/blank/_gitignore +0 -1
@@ -2,28 +2,16 @@ import path from 'node:path';
2
2
  import fs from 'node:fs';
3
3
  import zlib from 'node:zlib';
4
4
  import logger from 'loglevel';
5
- import {
6
- isDirExist,
7
- isDirSync,
8
- isFileExist,
9
- dumpToYaml,
10
- envTemplateFile,
11
- isValidPackageName,
12
- tarContentDir,
13
- isPngWithFile,
14
- isMacOs,
15
- isWindows,
16
- isLinux,
17
- } from '../utils.js';
5
+ import { isDirExist, isDirSync, isFileExist, dumpToYaml, envTemplateFile, isValidPackageName, tarContentDir, isPngWithFile, isMacOs, isWindows, isLinux } from '../utils.js';
18
6
  import spawn from 'cross-spawn';
19
- import { LpkManifest } from './lpk_create.js';
7
+ import { loadEffectiveManifest, splitManifestAndPackageInfo, findManifestStaticFields, PACKAGE_FILE_NAME } from '../package_info.js';
8
+ import { buildVarsFromEnvEntries, normalizeBuildEnvEntries, preprocessManifestFile } from './manifest_build.js';
20
9
  import archiver from 'archiver';
21
10
  import yaml from 'js-yaml';
22
11
  import { buildConfiguredImagesToTempDir } from './lpk_build_images.js';
23
12
  import { pipeline } from 'node:stream/promises';
24
13
  import { t } from '../i18n/index.js';
25
14
  import mergeWith from 'lodash.mergewith';
26
- import { DEFAULT_BUILD_BASE_FILE } from '../build_remote.js';
27
15
 
28
16
  async function archiveFolderTo(appDir, out, format = 'zip') {
29
17
  return new Promise(async (resolve, reject) => {
@@ -110,6 +98,74 @@ function rewriteManifestEmbeddedImages(manifest, resolvedImageByAlias) {
110
98
  return walk(manifest);
111
99
  }
112
100
 
101
+ function rewriteManifestEmbeddedImagesInText(rawText, resolvedImageByAlias) {
102
+ if (!resolvedImageByAlias || typeof resolvedImageByAlias !== 'object') {
103
+ return rawText;
104
+ }
105
+ let output = String(rawText ?? '');
106
+ for (const [alias, resolved] of Object.entries(resolvedImageByAlias)) {
107
+ if (!alias || !resolved) {
108
+ continue;
109
+ }
110
+ const escapedAlias = alias.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
111
+ const pattern = new RegExp(`embed:${escapedAlias}(?!@sha256:[0-9a-fA-F]{64})`, 'g');
112
+ output = output.replace(pattern, `embed:${alias}@${resolved}`);
113
+ }
114
+ return output;
115
+ }
116
+
117
+ function rewriteTopLevelManifestScalarInText(rawText, key, value) {
118
+ const normalizedValue = String(value ?? '').trim();
119
+ if (!normalizedValue) {
120
+ return rawText;
121
+ }
122
+ const lines = String(rawText ?? '').split('\n');
123
+ const pattern = new RegExp(`^(\\s*)${key}:(\\s*)([^#]*?)(\\s*(#.*))?$`);
124
+ let replaced = false;
125
+ const output = lines.map((line) => {
126
+ if (replaced) {
127
+ return line;
128
+ }
129
+ const match = line.match(pattern);
130
+ if (!match) {
131
+ return line;
132
+ }
133
+ replaced = true;
134
+ const indent = match[1] ?? '';
135
+ const spacing = match[2] ?? ' ';
136
+ const comment = match[4] ?? '';
137
+ return `${indent}${key}:${spacing}${normalizedValue}${comment}`;
138
+ });
139
+ return output.join('\n');
140
+ }
141
+
142
+ function removeTopLevelManifestFieldsInText(rawText, keys) {
143
+ const wanted = new Set((keys ?? []).map((key) => String(key ?? '').trim()).filter(Boolean));
144
+ if (wanted.size === 0) {
145
+ return String(rawText ?? '');
146
+ }
147
+ const lines = String(rawText ?? '').split('\n');
148
+ const output = [];
149
+ let skipping = false;
150
+
151
+ for (const line of lines) {
152
+ const trimmed = line.trim();
153
+ const indent = line.length - line.trimStart().length;
154
+ if (indent === 0 && !trimmed.startsWith('#')) {
155
+ const match = trimmed.match(/^([A-Za-z0-9_]+):/);
156
+ if (match && wanted.has(match[1])) {
157
+ skipping = true;
158
+ continue;
159
+ }
160
+ skipping = false;
161
+ }
162
+ if (!skipping) {
163
+ output.push(line);
164
+ }
165
+ }
166
+ return output.join('\n');
167
+ }
168
+
113
169
  async function fetchIconTo(options, cwd, destDir) {
114
170
  if (!options['icon']) {
115
171
  logger.warn(t('lzc_cli.lib.app.lpk_build.fetch_icon_to_icon_empty_fail', '图标icon 没有指定'));
@@ -250,6 +306,50 @@ function mergeBuildOptions(baseOptions, topOptions) {
250
306
  });
251
307
  }
252
308
 
309
+ function applyPkgIdSuffix(packageId, suffix) {
310
+ const normalizedPackageId = String(packageId ?? '').trim();
311
+ const normalizedSuffix = String(suffix ?? '').trim();
312
+ if (!normalizedSuffix) {
313
+ return normalizedPackageId;
314
+ }
315
+ if (!normalizedPackageId) {
316
+ throw new Error('package is empty, cannot apply pkg_id_suffix');
317
+ }
318
+ return `${normalizedPackageId}.${normalizedSuffix}`;
319
+ }
320
+
321
+ export function validatePackageFileForArchiveFormat(hasPackageFile, archiveFormat) {
322
+ const format = String(archiveFormat ?? '')
323
+ .trim()
324
+ .toLowerCase();
325
+ if (format === 'tar' && !hasPackageFile) {
326
+ throw new Error('package.yml is required for tar-based LPK v2 packages');
327
+ }
328
+ }
329
+
330
+ function hasBuildEnvEntries(envEntries) {
331
+ return Array.isArray(envEntries) && envEntries.length > 0;
332
+ }
333
+
334
+ export function shouldUseV2PackageLayout({ forceV2 = false, hasPackageFile = false, hasImagesConfig = false, buildEnvEntries = [] } = {}) {
335
+ return !!forceV2 || !!hasPackageFile || !!hasImagesConfig || hasBuildEnvEntries(buildEnvEntries);
336
+ }
337
+
338
+ function logDeprecatedStaticFieldWarning(manifestPath, fields) {
339
+ if (!Array.isArray(fields) || fields.length === 0) {
340
+ return;
341
+ }
342
+ logger.warn(`Building LPK v2. Static package fields in ${manifestPath} are deprecated and will be written to package.yml: ${fields.join(', ')}`);
343
+ }
344
+
345
+ function normalizeBuildOptions(options) {
346
+ const normalized = { ...(options ?? {}) };
347
+ if (Object.prototype.hasOwnProperty.call(normalized, 'envs')) {
348
+ normalized['envs'] = normalizeBuildEnvEntries(normalized['envs']);
349
+ }
350
+ return normalized;
351
+ }
352
+
253
353
  function loadYamlIfExists(filePath) {
254
354
  if (!filePath || !isFileExist(filePath)) {
255
355
  return {};
@@ -264,16 +364,43 @@ async function loadTemplatedYamlIfExists(filePath, env) {
264
364
  return yaml.load(await envTemplateFile(filePath, env)) ?? {};
265
365
  }
266
366
 
367
+ function isDevBuildConfigName(buildConfigFile) {
368
+ return path.basename(String(buildConfigFile ?? '').trim()) === 'lzc-build.dev.yml';
369
+ }
370
+
371
+ function resolveParentBuildConfigPath(optionsFilePath) {
372
+ if (!isDevBuildConfigName(optionsFilePath)) {
373
+ return '';
374
+ }
375
+ const parentPath = path.join(path.dirname(optionsFilePath), 'lzc-build.yml');
376
+ if (!isFileExist(parentPath)) {
377
+ return '';
378
+ }
379
+ return parentPath;
380
+ }
381
+
382
+ function detectBuildProfile(buildConfigFile) {
383
+ return isDevBuildConfigName(buildConfigFile) ? 'dev' : 'release';
384
+ }
385
+
267
386
  export class LpkBuild {
268
- constructor(cwd, buildConfigFile) {
387
+ constructor(cwd, buildConfigFile, options = {}) {
269
388
  this.pwd = cwd ?? process.cwd();
389
+ this.forceV2 = !!options.forceV2;
270
390
 
271
391
  this.optionsFilePath = path.join(this.pwd, buildConfigFile);
272
- this.optionsBaseFilePath = path.join(path.dirname(this.optionsFilePath), DEFAULT_BUILD_BASE_FILE);
273
- this.options = mergeBuildOptions(loadYamlIfExists(this.optionsBaseFilePath), loadYamlIfExists(this.optionsFilePath));
392
+ this.parentOptionsFilePath = resolveParentBuildConfigPath(this.optionsFilePath);
393
+ this.options = normalizeBuildOptions(mergeBuildOptions(loadYamlIfExists(this.parentOptionsFilePath), loadYamlIfExists(this.optionsFilePath)));
394
+ this.buildProfile = detectBuildProfile(this.optionsFilePath);
274
395
 
275
396
  this.manifestFilePath = this.options['manifest'] ? path.join(this.pwd, this.options['manifest']) : path.join(this.pwd, 'lzc-manifest.yml');
397
+ this.packageFilePath = path.join(this.pwd, PACKAGE_FILE_NAME);
276
398
  this.manifest = null;
399
+ this.sourceManifest = null;
400
+ this.packageInfo = null;
401
+ this.hasPackageFile = false;
402
+ this.buildVars = null;
403
+ this.preprocessedManifestText = '';
277
404
 
278
405
  this.beforeBuildPackageFn = [];
279
406
  this.beforeDumpYamlFn = [];
@@ -283,12 +410,25 @@ export class LpkBuild {
283
410
 
284
411
  // init 时替换 lzc-build.yml 中的模板字段
285
412
  async init() {
286
- const manifest = await this.getManifest();
413
+ const sourceLoaded = loadEffectiveManifest(this.manifestFilePath, {
414
+ packageFilePath: this.packageFilePath,
415
+ });
287
416
  const primitive = convenientEnv();
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);
417
+ const baseTemplateEnv = Object.assign({}, primitive, sourceLoaded.manifest);
418
+ const firstParentOptions = await loadTemplatedYamlIfExists(this.parentOptionsFilePath, baseTemplateEnv);
419
+ const firstTopOptions = await loadTemplatedYamlIfExists(this.optionsFilePath, baseTemplateEnv);
420
+ const firstOptions = normalizeBuildOptions(mergeBuildOptions(firstParentOptions, firstTopOptions));
421
+ const buildVars = buildVarsFromEnvEntries(firstOptions['envs']);
422
+ const finalTemplateEnv = Object.assign({}, baseTemplateEnv, buildVars);
423
+ const parentOptions = await loadTemplatedYamlIfExists(this.parentOptionsFilePath, finalTemplateEnv);
424
+ const topOptions = await loadTemplatedYamlIfExists(this.optionsFilePath, finalTemplateEnv);
425
+ this.options = normalizeBuildOptions(mergeBuildOptions(parentOptions, topOptions));
426
+ this.manifest = null;
427
+ this.sourceManifest = null;
428
+ this.packageInfo = null;
429
+ this.hasPackageFile = false;
430
+ this.buildVars = buildVarsFromEnvEntries(this.options['envs']);
431
+ this.preprocessedManifestText = '';
292
432
  return this;
293
433
  }
294
434
 
@@ -314,17 +454,40 @@ export class LpkBuild {
314
454
  this.beforeDumpLpkFn.push(fn);
315
455
  }
316
456
 
457
+ getBuildVars() {
458
+ if (!this.buildVars) {
459
+ this.buildVars = buildVarsFromEnvEntries(this.options['envs']);
460
+ }
461
+ return this.buildVars;
462
+ }
463
+
317
464
  async getManifest() {
318
465
  if (this.manifest) {
319
466
  return this.manifest;
320
467
  }
321
468
 
322
- let lpkM = new LpkManifest();
323
- await lpkM.init(this.manifestFilePath);
324
- this.manifest = lpkM.manifest;
325
- // manifest使用 text/template模板时,标记为异常manifest
326
- // 创建lpk时,直接将源文件拷入 lpk
327
- this.excpManifest = lpkM.excpManifest;
469
+ this.preprocessedManifestText = preprocessManifestFile(this.manifestFilePath, {
470
+ profile: this.buildProfile,
471
+ envs: this.getBuildVars(),
472
+ });
473
+
474
+ const loaded = loadEffectiveManifest(this.manifestFilePath, {
475
+ manifestText: this.preprocessedManifestText,
476
+ packageFilePath: this.packageFilePath,
477
+ });
478
+ this.excpManifest = loaded.excpManifest;
479
+ this.sourceManifest = loaded.sourceManifest;
480
+ this.packageInfo = loaded.packageInfo;
481
+ this.hasPackageFile = loaded.hasPackageFile;
482
+ this.manifest = loaded.manifest;
483
+
484
+ const packageWithSuffix = applyPkgIdSuffix(this.manifest['package'], this.options['pkg_id_suffix']);
485
+ if (packageWithSuffix !== this.manifest['package']) {
486
+ this.manifest = { ...this.manifest, package: packageWithSuffix };
487
+ if (this.hasPackageFile) {
488
+ this.packageInfo = { ...(this.packageInfo ?? {}), package: packageWithSuffix };
489
+ }
490
+ }
328
491
 
329
492
  if (!isValidPackageName(this.manifest['package'])) {
330
493
  throw t('lzc_cli.lib.app.lpk_build.get_manifest_package_name_fail', `{{ package }} 含有非法字符,请使用正确的包名格式(java的包名格式),如:cloud.lazycat.apps.video`, {
@@ -332,7 +495,7 @@ export class LpkBuild {
332
495
  });
333
496
  }
334
497
 
335
- if (!this.manifest['application']['subdomain']) {
498
+ if (!this.manifest?.application?.subdomain) {
336
499
  throw t('lzc_cli.lib.app.lpk_build.get_manifest_subdomain_empty', `application 模块下的 subdomain 字段不能为空`);
337
500
  }
338
501
 
@@ -340,6 +503,8 @@ export class LpkBuild {
340
503
  }
341
504
 
342
505
  async exec() {
506
+ await this.getManifest();
507
+
343
508
  if (this.beforeBuildPackageFn.length > 0) {
344
509
  this.options = await this.beforeBuildPackageFn.reduce(async (prev, curr) => {
345
510
  return await curr(await prev);
@@ -352,6 +517,7 @@ export class LpkBuild {
352
517
  let p = spawn.sync(cmd, [cmdArgs, this.options['buildscript']], {
353
518
  cwd: this.pwd,
354
519
  stdio: 'inherit',
520
+ env: { ...process.env, ...this.getBuildVars() },
355
521
  });
356
522
  if (p.status != 0) {
357
523
  throw t('lzc_cli.lib.app.lpk_build.exec_build_fail', `构建失败`);
@@ -379,31 +545,37 @@ export class LpkBuild {
379
545
  let browserExtension = this.options['browser-extension'];
380
546
  let aiPodService = this.options['ai-pod-service'];
381
547
  try {
382
- if (contentdir) {
548
+ const hasContentDir = !!contentdir;
549
+ const needsContentArchive = hasContentDir || this.beforeTarContentFn.length > 0;
550
+ let tempContentDir = '';
551
+ if (hasContentDir) {
383
552
  contentdir = path.resolve(this.pwd, contentdir);
384
553
  if (!isDirExist(contentdir)) {
385
554
  throw `${contentdir} 不存在`;
386
555
  }
387
556
  } else {
388
557
  logger.warn(t('lzc_cli.lib.app.lpk_build.exec_skip_copy_contentdir', '跳过拷贝 contentdir 内容'));
389
- // 当没有指定的 contentdir 的时候,也生成一个空的文件夹
390
- // 原因是:可能其他地方会复制内容进来,像 devshell 中的会在打包的时候,将ssh key拷贝进来
391
- contentdir = fs.mkdtempSync(path.join(tempDir, 'fake-contentdir'));
392
558
  }
393
559
 
394
- // 开始打包 contentdir
395
- if (this.beforeTarContentFn.length > 0) {
396
- await this.beforeTarContentFn.reduce(async (prev, curr) => {
397
- let _prev = await prev;
398
- await curr(_prev, this.options);
399
- return _prev;
400
- }, contentdir);
560
+ if (needsContentArchive) {
561
+ if (!hasContentDir) {
562
+ // 没有显式 contentdir 时,仅在 hook 需要写入内容时创建临时目录。
563
+ tempContentDir = fs.mkdtempSync(path.join(tempDir, 'fake-contentdir'));
564
+ contentdir = tempContentDir;
565
+ }
566
+
567
+ if (this.beforeTarContentFn.length > 0) {
568
+ await this.beforeTarContentFn.reduce(async (prev, curr) => {
569
+ let _prev = await prev;
570
+ await curr(_prev, this.options);
571
+ return _prev;
572
+ }, contentdir);
573
+ }
574
+ await tarContentDir(['./'], path.join(tempDir, 'content.tar'), contentdir);
401
575
  }
402
- await tarContentDir(['./'], path.join(tempDir, 'content.tar'), contentdir);
403
576
 
404
- // 如果是临时的 contentdir, 目录在打包完成后删除
405
- if (!this.options['contentdir']) {
406
- fs.rmSync(contentdir, { recursive: true });
577
+ if (tempContentDir) {
578
+ fs.rmSync(tempContentDir, { recursive: true });
407
579
  }
408
580
 
409
581
  if (browserExtension) {
@@ -439,6 +611,9 @@ export class LpkBuild {
439
611
  let manifest = await this.getManifest();
440
612
  if (process.env.LZC_VERSION) {
441
613
  manifest.version = process.env.LZC_VERSION;
614
+ if (this.hasPackageFile) {
615
+ this.packageInfo = { ...(this.packageInfo ?? {}), version: process.env.LZC_VERSION };
616
+ }
442
617
  }
443
618
  if (this.beforeDumpYamlFn.length > 0) {
444
619
  manifest = await this.beforeDumpYamlFn.reduce(async (prev, curr) => {
@@ -461,19 +636,41 @@ export class LpkBuild {
461
636
 
462
637
  const hasImagesConfig = !!this.options['images'];
463
638
 
464
- let useTarPackage = false;
465
639
  let embeddedImageSummary = null;
466
640
  if (hasImagesConfig) {
467
641
  const buildResult = await buildConfiguredImagesToTempDir(this.options['images'], manifest, this.pwd, tempDir, {
468
642
  remote: this.options['remote'],
469
643
  });
470
644
  if (buildResult.imageCount > 0) {
471
- useTarPackage = true;
472
645
  embeddedImageSummary = buildResult;
473
646
  manifest = rewriteManifestEmbeddedImages(manifest, buildResult.resolvedImageByAlias);
474
647
  }
475
648
  }
476
- if (useTarPackage) {
649
+
650
+ const useV2PackageLayout = shouldUseV2PackageLayout({
651
+ forceV2: this.forceV2,
652
+ hasPackageFile: this.hasPackageFile,
653
+ hasImagesConfig,
654
+ buildEnvEntries: this.options['envs'],
655
+ });
656
+ if (useV2PackageLayout) {
657
+ logDeprecatedStaticFieldWarning(this.manifestFilePath, findManifestStaticFields(this.sourceManifest));
658
+ }
659
+
660
+ let manifestOutput = manifest;
661
+ let packageInfoOutput = null;
662
+ if (useV2PackageLayout) {
663
+ const split = splitManifestAndPackageInfo(manifest, this.packageInfo);
664
+ manifestOutput = split.manifest;
665
+ packageInfoOutput = split.packageInfo;
666
+ }
667
+
668
+ const archiveFormat = useV2PackageLayout ? 'tar' : 'zip';
669
+ validatePackageFileForArchiveFormat(!!packageInfoOutput, archiveFormat);
670
+ if (!useV2PackageLayout) {
671
+ logger.warn('Building legacy LPK v1 metadata layout. Run "lzc-cli migrate" to move static package fields into package.yml.');
672
+ }
673
+ if (embeddedImageSummary && embeddedImageSummary.imageCount > 0) {
477
674
  const contentTar = path.join(tempDir, 'content.tar');
478
675
  const contentTarGz = path.join(tempDir, 'content.tar.gz');
479
676
  if (isFileExist(contentTar)) {
@@ -482,19 +679,32 @@ export class LpkBuild {
482
679
  }
483
680
  }
484
681
 
485
- if (process.env.LZC_MANIFEST_TEMPLATE && !useTarPackage) {
486
- logger.debug('copy origin manifest\n', this.manifestFilePath);
487
- fs.copyFileSync(this.manifestFilePath, path.join(tempDir, 'manifest.yml'));
682
+ if (process.env.LZC_MANIFEST_TEMPLATE && archiveFormat === 'zip') {
683
+ logger.debug('copy processed manifest\n', this.manifestFilePath);
684
+ fs.writeFileSync(path.join(tempDir, 'manifest.yml'), this.preprocessedManifestText);
488
685
  } else {
489
- logger.debug('manifest\n', manifest);
490
- // 异常的manifest,就将源文件给转到lpk内
686
+ logger.debug('manifest\n', manifestOutput);
491
687
  if (this.excpManifest) {
492
- fs.writeFileSync(path.join(tempDir, 'manifest.yml'), fs.readFileSync(this.manifestFilePath, 'utf8'));
688
+ let rawManifest = this.preprocessedManifestText || fs.readFileSync(this.manifestFilePath, 'utf8');
689
+ if (archiveFormat === 'zip') {
690
+ rawManifest = rewriteTopLevelManifestScalarInText(rawManifest, 'package', manifest.package);
691
+ rawManifest = rewriteTopLevelManifestScalarInText(rawManifest, 'version', manifest.version);
692
+ } else {
693
+ rawManifest = removeTopLevelManifestFieldsInText(rawManifest, Object.keys(packageInfoOutput ?? {}));
694
+ }
695
+ if (embeddedImageSummary?.resolvedImageByAlias) {
696
+ rawManifest = rewriteManifestEmbeddedImagesInText(rawManifest, embeddedImageSummary.resolvedImageByAlias);
697
+ }
698
+ fs.writeFileSync(path.join(tempDir, 'manifest.yml'), rawManifest);
493
699
  } else {
494
- dumpToYaml(manifest, path.join(tempDir, 'manifest.yml'));
700
+ dumpToYaml(manifestOutput, path.join(tempDir, 'manifest.yml'));
495
701
  }
496
702
  }
497
703
 
704
+ if (packageInfoOutput) {
705
+ dumpToYaml(packageInfoOutput, path.join(tempDir, PACKAGE_FILE_NAME));
706
+ }
707
+
498
708
  // compose.override.yml
499
709
  if (this.options['compose_override']) {
500
710
  dumpToYaml(this.options['compose_override'], path.join(tempDir, 'compose.override.yml'));
@@ -513,7 +723,7 @@ export class LpkBuild {
513
723
  packName = path.resolve(pkgout, `${manifest.package}-v${manifest.version}${ext}`);
514
724
  }
515
725
 
516
- const lpkPath = await archiveFolderTo(tempDir, packName, useTarPackage ? 'tar' : 'zip');
726
+ const lpkPath = await archiveFolderTo(tempDir, packName, archiveFormat);
517
727
  logger.info(
518
728
  `${t('lzc_cli.lib.app.lpk_build.exec_output_lpk_path', '输出lpk包 {{ path }}', {
519
729
  path: lpkPath.path,