@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
@@ -0,0 +1,261 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import shellApi from '../shellapi.js';
4
+ import { DebugBridge } from '../debug_bridge.js';
5
+ import { LpkBuild } from './lpk_build.js';
6
+ import { isUserApp } from '../utils.js';
7
+ import { resolveBuildRemoteFromFile, DEFAULT_BUILD_CONFIG_FILE } from '../build_remote.js';
8
+
9
+ export const DEFAULT_DEPLOY_BUILD_CONFIG_FILE = 'lzc-build.dev.yml';
10
+ export const DEFAULT_RELEASE_BUILD_CONFIG_FILE = 'lzc-build.release.yml';
11
+
12
+ function isFile(pathname) {
13
+ return fs.existsSync(pathname) && fs.statSync(pathname).isFile();
14
+ }
15
+
16
+ export function resolveBuildConfigPath(startDir = process.cwd(), buildConfigFile = DEFAULT_BUILD_CONFIG_FILE) {
17
+ const configName = String(buildConfigFile ?? DEFAULT_BUILD_CONFIG_FILE).trim() || DEFAULT_BUILD_CONFIG_FILE;
18
+ if (path.isAbsolute(configName)) {
19
+ return isFile(configName) ? configName : '';
20
+ }
21
+
22
+ const direct = path.resolve(startDir, configName);
23
+ if (isFile(direct)) {
24
+ return direct;
25
+ }
26
+
27
+ if (configName.includes('/') || configName.includes('\\')) {
28
+ return '';
29
+ }
30
+
31
+ let current = path.resolve(startDir);
32
+ while (true) {
33
+ const candidate = path.join(current, configName);
34
+ if (isFile(candidate)) {
35
+ return candidate;
36
+ }
37
+ const parent = path.dirname(current);
38
+ if (parent === current) {
39
+ break;
40
+ }
41
+ current = parent;
42
+ }
43
+ return '';
44
+ }
45
+
46
+ export function resolveProjectDeployConfigPath(startDir = process.cwd(), buildConfigFile = '') {
47
+ return resolvePreferredBuildConfigPath(startDir, buildConfigFile, DEFAULT_DEPLOY_BUILD_CONFIG_FILE);
48
+ }
49
+
50
+ export function resolveProjectReleaseConfigPath(startDir = process.cwd(), buildConfigFile = '') {
51
+ return resolvePreferredBuildConfigPath(startDir, buildConfigFile, DEFAULT_RELEASE_BUILD_CONFIG_FILE);
52
+ }
53
+
54
+ function resolvePreferredBuildConfigPath(startDir, buildConfigFile, preferredConfigFile) {
55
+ const explicitConfig = String(buildConfigFile ?? '').trim();
56
+ if (explicitConfig) {
57
+ const explicitPath = resolveBuildConfigPath(startDir, explicitConfig);
58
+ if (!explicitPath) {
59
+ throw new Error(`Build config file not found: ${explicitConfig}`);
60
+ }
61
+ return explicitPath;
62
+ }
63
+
64
+ const preferredConfigPath = resolveBuildConfigPath(startDir, preferredConfigFile);
65
+ if (preferredConfigPath) {
66
+ return preferredConfigPath;
67
+ }
68
+
69
+ const defaultConfigPath = resolveBuildConfigPath(startDir, DEFAULT_BUILD_CONFIG_FILE);
70
+ if (!defaultConfigPath) {
71
+ throw new Error(`Build config file not found: ${preferredConfigFile} or ${DEFAULT_BUILD_CONFIG_FILE}`);
72
+ }
73
+ return defaultConfigPath;
74
+ }
75
+
76
+ export async function resolveProjectRuntime(startDir = process.cwd(), buildConfigFile = DEFAULT_BUILD_CONFIG_FILE) {
77
+ const configPath = resolveBuildConfigPath(startDir, buildConfigFile);
78
+ if (!configPath) {
79
+ throw new Error(`Build config file not found: ${buildConfigFile}`);
80
+ }
81
+
82
+ const projectCwd = path.dirname(configPath);
83
+ const configName = path.basename(configPath);
84
+
85
+ const buildRemote = resolveBuildRemoteFromFile(projectCwd, configName);
86
+ if (!buildRemote) {
87
+ await shellApi.init();
88
+ }
89
+
90
+ const lpkBuild = await new LpkBuild(projectCwd, configName).init();
91
+ const manifest = await lpkBuild.getManifest();
92
+ const pkgId = String(manifest?.package ?? '').trim();
93
+ if (!pkgId) {
94
+ throw new Error('Manifest package is empty');
95
+ }
96
+
97
+ const bridge = new DebugBridge(projectCwd, buildRemote);
98
+ await bridge.init();
99
+
100
+ const userApp = isUserApp(manifest);
101
+ const composeProjectName = pkgId.replaceAll('.', '');
102
+ const localVersion = String(manifest?.version ?? '').trim();
103
+
104
+ return {
105
+ projectCwd,
106
+ configName,
107
+ configPath,
108
+ manifest,
109
+ pkgId,
110
+ userApp,
111
+ bridge,
112
+ composeProjectName,
113
+ localVersion,
114
+ };
115
+ }
116
+
117
+ function firstNonEmptyLine(text) {
118
+ return String(text ?? '')
119
+ .split(/\r?\n/)
120
+ .map((line) => line.trim())
121
+ .find((line) => line.length > 0);
122
+ }
123
+
124
+ function parseComposeProjectList(raw) {
125
+ const text = String(raw ?? '').trim();
126
+ if (!text) {
127
+ return [];
128
+ }
129
+ try {
130
+ const parsed = JSON.parse(text);
131
+ if (!Array.isArray(parsed)) {
132
+ return [];
133
+ }
134
+ return parsed
135
+ .map((item) => ({
136
+ name: String(item?.Name ?? '').trim(),
137
+ status: String(item?.Status ?? '').trim(),
138
+ configFiles: String(item?.ConfigFiles ?? '').trim(),
139
+ }))
140
+ .filter((item) => item.name.length > 0);
141
+ } catch {
142
+ return [];
143
+ }
144
+ }
145
+
146
+ export async function listComposeProjects(runtime) {
147
+ try {
148
+ const output = await runtime.bridge.lzcDockerComposeCapture(['ls', '--format', 'json']);
149
+ return parseComposeProjectList(output);
150
+ } catch {
151
+ return [];
152
+ }
153
+ }
154
+
155
+ export async function getComposeProject(runtime) {
156
+ const projects = await listComposeProjects(runtime);
157
+ return projects.find((item) => item.name === runtime.composeProjectName) || null;
158
+ }
159
+
160
+ export async function getProjectDeployInfo(runtime) {
161
+ const remoteInfo = await runtime.bridge.info(runtime.pkgId);
162
+ const deployId = String(remoteInfo?.deploy_id ?? '').trim();
163
+ const deployedVersion = String(remoteInfo?.version ?? '').trim();
164
+ const domain = String(remoteInfo?.domain ?? '').trim();
165
+ const appStatus = String(remoteInfo?.status ?? '').trim();
166
+ const instanceStatus = String(remoteInfo?.instance_status ?? '').trim();
167
+ const errorReason = String(remoteInfo?.error_reason ?? '').trim();
168
+ const deployed = appStatus !== '' && appStatus !== 'NotInstalled';
169
+ const currentVersionDeployed = deployed && runtime.localVersion !== '' && deployedVersion === runtime.localVersion;
170
+ return {
171
+ ...remoteInfo,
172
+ deployId,
173
+ localVersion: runtime.localVersion,
174
+ deployedVersion,
175
+ domain,
176
+ appStatus,
177
+ instanceStatus,
178
+ errorReason,
179
+ deployed,
180
+ currentVersionDeployed,
181
+ isRunning: instanceStatus === 'Status_Running',
182
+ };
183
+ }
184
+
185
+ export async function getProjectErrmsgByDeployId(runtime, deployId) {
186
+ const targetDeployId = String(deployId ?? '').trim();
187
+ if (!targetDeployId) {
188
+ return '';
189
+ }
190
+ try {
191
+ const errmsgPath = `/data/system/pkgm/run/${targetDeployId}/errmsg`;
192
+ const raw = await runtime.bridge.hostReadFile(errmsgPath);
193
+ return String(raw ?? '').trim();
194
+ } catch {
195
+ return '';
196
+ }
197
+ }
198
+
199
+ export async function ensureProjectRunning(runtime) {
200
+ const deploy = await getProjectDeployInfo(runtime);
201
+ if (!deploy.isRunning) {
202
+ throw new Error('Project app is not running. Run "lzc-cli project start" first.');
203
+ }
204
+ return deploy;
205
+ }
206
+
207
+ export async function getProjectComposePs(runtime) {
208
+ try {
209
+ return await runtime.bridge.lzcDockerComposeCapture(['-p', runtime.composeProjectName, 'ps']);
210
+ } catch {
211
+ return '';
212
+ }
213
+ }
214
+
215
+ export async function findProjectServiceContainer(runtime, service = 'app') {
216
+ const targetService = String(service || 'app');
217
+ try {
218
+ const output = await runtime.bridge.lzcDockerComposeCapture(['-p', runtime.composeProjectName, 'ps', '--status', 'running', '-q', targetService]);
219
+ return firstNonEmptyLine(output) || '';
220
+ } catch {
221
+ return '';
222
+ }
223
+ }
224
+
225
+ export async function ensureProjectServiceRunning(runtime, service = 'app') {
226
+ const targetService = String(service || 'app');
227
+ const composeProject = await getComposeProject(runtime);
228
+ if (!composeProject || !composeProject.status.startsWith('running(')) {
229
+ throw new Error('Project app is not running. Run "lzc-cli project start" first.');
230
+ }
231
+ const containerId = await findProjectServiceContainer(runtime, targetService);
232
+ if (!containerId) {
233
+ throw new Error(`Service \"${targetService}\" is not running. Run \"lzc-cli project start\" first.`);
234
+ }
235
+ return containerId;
236
+ }
237
+
238
+ export async function getProjectServiceEnv(runtime, service = 'app') {
239
+ const containerId = await ensureProjectServiceRunning(runtime, service);
240
+ const output = await runtime.bridge.lzcDockerCapture(['inspect', containerId]);
241
+ const inspectList = JSON.parse(String(output ?? '').trim());
242
+ if (!Array.isArray(inspectList) || inspectList.length === 0 || typeof inspectList[0] !== 'object' || !inspectList[0]) {
243
+ throw new Error(`Invalid inspect output for service "${String(service || 'app')}"`);
244
+ }
245
+ const envList = inspectList[0]?.Config?.Env;
246
+ if (!Array.isArray(envList)) {
247
+ throw new Error(`Invalid env list for service "${String(service || 'app')}"`);
248
+ }
249
+ const envMap = {};
250
+ for (const item of envList) {
251
+ const pair = String(item ?? '');
252
+ const idx = pair.indexOf('=');
253
+ if (idx <= 0) {
254
+ continue;
255
+ }
256
+ const key = pair.slice(0, idx);
257
+ const value = pair.slice(idx + 1);
258
+ envMap[key] = value;
259
+ }
260
+ return envMap;
261
+ }
@@ -0,0 +1,100 @@
1
+ import logger from 'loglevel';
2
+ import { DEFAULT_BUILD_CONFIG_FILE } from '../build_remote.js';
3
+ import { LpkBuild } from './lpk_build.js';
4
+ import { resolveProjectRuntime, getProjectDeployInfo, getProjectComposePs } from './project_runtime.js';
5
+
6
+ function sleep(ms) {
7
+ return new Promise((resolve) => setTimeout(resolve, ms));
8
+ }
9
+
10
+ async function waitProjectRunning(runtime, timeoutMs = 90000) {
11
+ const startedAt = Date.now();
12
+ while (Date.now() - startedAt <= timeoutMs) {
13
+ const deploy = await getProjectDeployInfo(runtime);
14
+ if (deploy.isRunning) {
15
+ return deploy;
16
+ }
17
+ await sleep(1000);
18
+ }
19
+ return null;
20
+ }
21
+
22
+ async function waitProjectNotRunning(runtime, timeoutMs = 30000) {
23
+ const startedAt = Date.now();
24
+ while (Date.now() - startedAt <= timeoutMs) {
25
+ const deploy = await getProjectDeployInfo(runtime);
26
+ if (!deploy.isRunning) {
27
+ return deploy;
28
+ }
29
+ await sleep(1000);
30
+ }
31
+ return null;
32
+ }
33
+
34
+ async function deployCurrentVersion(runtime) {
35
+ const lpkBuild = await new LpkBuild(runtime.projectCwd, runtime.configName).init();
36
+ lpkBuild.onBeforeBuildPackage(async (options) => {
37
+ delete options['devshell'];
38
+ return options;
39
+ });
40
+ const pkgPath = await lpkBuild.exec();
41
+ logger.info(`Install package: ${pkgPath}`);
42
+ await runtime.bridge.install(pkgPath, runtime.pkgId);
43
+ }
44
+
45
+ export function projectStartCommand() {
46
+ return {
47
+ command: 'start',
48
+ desc: 'Start project app',
49
+ builder: (args) => {
50
+ args.option('c', {
51
+ alias: 'config',
52
+ describe: 'Build config file name',
53
+ type: 'string',
54
+ default: DEFAULT_BUILD_CONFIG_FILE,
55
+ });
56
+ args.option('restart', {
57
+ describe: 'Force restart app instance',
58
+ type: 'boolean',
59
+ default: false,
60
+ });
61
+ },
62
+ handler: async ({ config, restart }) => {
63
+ const runtime = await resolveProjectRuntime(process.cwd(), config);
64
+ let deploy = await getProjectDeployInfo(runtime);
65
+
66
+ if (!deploy.currentVersionDeployed) {
67
+ logger.info('Current project version is not deployed. Build and install current version.');
68
+ await deployCurrentVersion(runtime);
69
+ deploy = await getProjectDeployInfo(runtime);
70
+ }
71
+
72
+ if (restart) {
73
+ if (deploy.isRunning) {
74
+ logger.info('Restart requested. Pause app instance first.');
75
+ await runtime.bridge.pause(runtime.pkgId);
76
+ deploy = await waitProjectNotRunning(runtime);
77
+ if (!deploy) {
78
+ throw new Error('Project app is still running after pause. Please check app status and try again.');
79
+ }
80
+ }
81
+ logger.info('Restart requested. Resume app instance.');
82
+ await runtime.bridge.resume(runtime.pkgId);
83
+ } else if (!deploy.isRunning) {
84
+ logger.info('App is not running. Resume app instance.');
85
+ await runtime.bridge.resume(runtime.pkgId);
86
+ }
87
+
88
+ deploy = await waitProjectRunning(runtime);
89
+ if (!deploy) {
90
+ throw new Error('Project app is still not running. Please check app status and try again.');
91
+ }
92
+
93
+ logger.info('Project app is running.');
94
+ const psOutput = await getProjectComposePs(runtime);
95
+ if (psOutput) {
96
+ console.log(psOutput);
97
+ }
98
+ },
99
+ };
100
+ }
@@ -1,10 +1,12 @@
1
- import { Publish } from './publish.js';
2
- import logger from 'loglevel';
3
- import { PrePublish } from './prePublish.js';
4
- import { reLogin, request } from './login.js';
5
1
  import fs from 'node:fs';
2
+ import logger from 'loglevel';
3
+
6
4
  import { sleep } from '../utils.js';
7
5
  import { t } from '../i18n/index.js';
6
+
7
+ import { Publish } from './publish.js';
8
+ import { PrePublish } from './prePublish.js';
9
+ import { reLogin, request } from './login.js';
8
10
  import { appStoreServerUrl } from './env.js';
9
11
 
10
12
  export function appstoreCommand(program) {
@@ -19,18 +21,18 @@ export function appstoreCommand(program) {
19
21
  {
20
22
  command: 'pre-publish <pkgPath>',
21
23
  desc: t('lzc_cli.lib.appstore.index.pre_publish_cmd_desc', '发布到内测'),
22
- builder: (args) => {
23
- args.option('c', {
24
+ builder: (yargs) => {
25
+ yargs.option('c', {
24
26
  alias: 'changelog',
25
27
  describe: t('lzc_cli.lib.appstore.index.pre_publish_cmd_changelog_desc', '更改日志'),
26
28
  type: 'string',
27
29
  });
28
- args.option('F', {
30
+ yargs.option('F', {
29
31
  alias: 'file',
30
32
  describe: t('lzc_cli.lib.appstore.index.pre_publish_cmd_changelog_file_desc', '更改日志文件'),
31
33
  type: 'string',
32
34
  });
33
- args.option('G', {
35
+ yargs.option('G', {
34
36
  alias: 'gid',
35
37
  describe: t('lzc_cli.lib.appstore.index.pre_publish_cmd_gid_desc', '内测组ID'),
36
38
  type: 'string',
@@ -47,30 +49,68 @@ export function appstoreCommand(program) {
47
49
  {
48
50
  command: 'publish <pkgPath>',
49
51
  desc: t('lzc_cli.lib.appstore.index.publish_cmd_desc', '发布到商店'),
50
- builder: (args) => {
51
- args.option('c', {
52
+ builder: (yargs) => {
53
+ yargs.option('c', {
52
54
  alias: 'changelog',
53
55
  describe: t('lzc_cli.lib.appstore.index.publish_cmd_changelog_desc', '更新日志'),
54
56
  type: 'string',
55
57
  });
56
- args.option('clang', {
58
+ yargs.option('clang', {
57
59
  alias: 'changelog-locale',
58
60
  describe: t('lzc_cli.lib.appstore.index.publish_cmd_changelog_lang_desc', '更新日志语言标识,默认通过当前 shell 语言环境识别'),
59
61
  type: 'string',
60
62
  });
61
- args.option('F', {
63
+ yargs.option('clangs', {
64
+ alias: 'changelog-files',
65
+ type: 'string',
66
+ array: true,
67
+ describe: t('lzc_cli.lib.appstore.index.publish_cmd_changelog_files_desc', '更新日志本地化语言文件 lang:path 例如 en:changelog.en.md'),
68
+ });
69
+ yargs.option('F', {
62
70
  alias: 'file',
71
+ deprecate: 'use --clangs=en:clog_en.txt',
63
72
  describe: t('lzc_cli.lib.appstore.index.publish_cmd_changelog_file_desc', '更新日志文件'),
64
73
  type: 'string',
65
74
  });
66
75
  },
67
- handler: async ({ pkgPath, changelog, changelogLocale, file }) => {
68
- const locale = changelogLocale ?? program.locale();
76
+ handler: async ({ pkgPath, changelog, changelogLocale, file, changelogFiles }) => {
77
+ const currentLocale = changelogLocale ?? program.locale();
69
78
  const p = new Publish();
79
+
80
+ async function readChangelogFiles(files, changelogs = {}) {
81
+ const result = {}
82
+ if (!files || !Array.isArray(files) || files.length < 1) {
83
+ return { ...result, ...changelogs }
84
+ }
85
+ for (const item of files) {
86
+ const [lang, path] = item.split(':');
87
+ if (!lang || !path) {
88
+ throw new Error('changelog files resolution error')
89
+ }
90
+ if (!fs.existsSync(path)) {
91
+ throw new Error(`changelog ${lang} file ${path} does not exist`)
92
+ }
93
+ const content = fs.readFileSync(path, 'utf8');
94
+ if (!content) {
95
+ throw new Error(`changelog ${lang} file ${path} content is empty`)
96
+ }
97
+ result[lang] = content.trim()
98
+ }
99
+ return { ...result, ...changelogs }
100
+ }
101
+
102
+ // deprecate: 移除 `--file` 参数时删除该逻辑
70
103
  if (!changelog && file) {
71
104
  changelog = fs.readFileSync(file, 'utf8');
72
105
  }
73
- await p.publish(pkgPath, changelog, locale);
106
+
107
+ const changelogInitial = {}
108
+ if (changelog) {
109
+ changelogInitial[currentLocale] = changelog
110
+ }
111
+ const changelogs = await readChangelogFiles(changelogFiles, changelogInitial)
112
+
113
+ await p.publish(pkgPath, changelogs, currentLocale);
74
114
  },
75
115
  },
76
116
  {
@@ -138,7 +178,7 @@ export function appstoreCommand(program) {
138
178
  process.stdout.write('\x1B[?25h'); // 恢复光标显示
139
179
  };
140
180
 
141
- for (;;) {
181
+ for (; ;) {
142
182
  const pgsResp = await request(progressUrl);
143
183
  if (pgsResp.ok) {
144
184
  const pgs = JSON.parse(await pgsResp.text());
@@ -14,7 +14,7 @@ async function askChangeLog(locale) {
14
14
  {
15
15
  name: 'changelog',
16
16
  type: 'editor',
17
- message: t('lzc_cli.lib.publish.ask_changelog_prompt', `填写 changelog 内容 ({{locale}})`, { locale }),
17
+ message: t('lzc_cli.lib.publish.ask_changelog_prompt', `填写 changelog 内容 ({{ locale }})`, { locale }),
18
18
  validate: noEmpty,
19
19
  },
20
20
  ]);
@@ -128,7 +128,7 @@ async function askWhetherCreateLPK(baseUrl, manifest, locale) {
128
128
  }),
129
129
  });
130
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'] }));
131
+ logger.info(t('lzc_cli.lib.publish.ask_whether_create_lpk_success_tips', `创建 {{ package }} 应用成功!`, { package: manifest['package'], interpolation: { escapeValue: false } }));
132
132
  } else {
133
133
  logger.info(
134
134
  t(
@@ -205,9 +205,9 @@ export class Publish {
205
205
  /**
206
206
  * @param {string} pkgPath
207
207
  * @param {string} changelog
208
- * @param {string} locale
208
+ * @param {string} currentLocale
209
209
  */
210
- async publish(pkgPath, changelog, locale = 'zh') {
210
+ async publish(pkgPath, changelogs, currentLocale = 'zh') {
211
211
  if (!Publish.preCheck(pkgPath)) return;
212
212
 
213
213
  const { manifest, appIdExisted } = await this.checkAppIdExist(pkgPath);
@@ -237,19 +237,25 @@ export class Publish {
237
237
  logger.debug('upload lpk response', lpkInfo);
238
238
  if (res.status >= 400) {
239
239
  logger.error(
240
- t('lzc_cli.lib.publish.publish_lpk_fail_tips', `LPK 文件上传失败,err: {{message}}`, {
240
+ t('lzc_cli.lib.publish.publish_lpk_fail_tips', `LPK 文件上传失败,err: {{ message }}`, {
241
241
  message: lpkInfo?.message ?? lpkInfo,
242
+ interpolation: { escapeValue: false }
242
243
  }),
243
244
  );
244
245
  throw new Error(lpkInfo?.message ?? lpkInfo);
245
246
  }
246
247
 
247
248
  // 填写更新日志
248
- if (!changelog) {
249
- const answer = await askChangeLog(locale);
250
- changelog = answer.changelog;
249
+ if (!changelogs || Object.keys(changelogs).length < 1) {
250
+ const answer = await askChangeLog(currentLocale);
251
+ let changelog = answer.changelog;
252
+ changelog = changelog.trim(); // clean space ^:)
253
+
254
+ // changelogs 本地化
255
+ const langKey = getLanguageForLocale(currentLocale);
256
+ changelogs[langKey] = changelog
251
257
  }
252
- changelog = changelog.trim(); // clean space ^:)
258
+ logger.debug('publish changelogs is', changelogs);
253
259
 
254
260
  const sendURL = this.baseUrl + `/app/${lpkInfo.package}/review/create`;
255
261
  logger.debug('publish url is', sendURL);
@@ -264,12 +270,9 @@ export class Publish {
264
270
  pkg_path: lpkInfo.url,
265
271
  unsupported_platforms: lpkInfo.unsupportedPlatforms,
266
272
  min_os_version: lpkInfo.minOsVersion,
267
- changelogs: {},
273
+ changelogs,
268
274
  },
269
275
  };
270
- // changelogs 本地化
271
- const langKey = getLanguageForLocale(locale);
272
- formData.version.changelogs[langKey] = changelog;
273
276
 
274
277
  logger.debug('form data is', formData);
275
278