@lazycatcloud/lzc-cli 2.0.0-pre.5 → 2.0.0-pre.6
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 +7 -0
- package/lib/app/index.js +23 -0
- package/lib/app/lpk_build.js +30 -2
- package/lib/app/lpk_build_images.js +1 -0
- package/lib/app/lpk_installer.js +13 -1
- package/lib/app/manifest_lint.js +464 -0
- package/lib/app/project_runtime.js +1 -0
- package/lib/appstore/prePublish.js +1 -1
- package/lib/appstore/publish.js +63 -8
- package/lib/debug_bridge.js +30 -3
- package/lib/i18n/locales/en/translation.json +1 -0
- package/lib/i18n/locales/zh/translation.json +1 -0
- package/lib/utils.js +8 -6
- package/package.json +1 -1
package/changelog.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.0.0-pre.6](https://gitee.com/linakesi/lzc-cli/compare/v2.0.0-pre.5...v2.0.0-pre.6) (2026-03-25)
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- add manifest lint command and build-time warnings for deprecated fields, unknown fields, and app store requirements
|
|
8
|
+
- reject appstore publish and pre-publish when the packaged LPK does not satisfy store requirements
|
|
9
|
+
|
|
3
10
|
## [2.0.0-pre.5](https://gitee.com/linakesi/lzc-cli/compare/v2.0.0-pre.4...v2.0.0-pre.5) (2026-03-19)
|
|
4
11
|
|
|
5
12
|
### Features
|
package/lib/app/index.js
CHANGED
|
@@ -181,6 +181,29 @@ export function lpkProjectCommand(program) {
|
|
|
181
181
|
await lpk.exec();
|
|
182
182
|
},
|
|
183
183
|
},
|
|
184
|
+
{
|
|
185
|
+
command: 'lint [context]',
|
|
186
|
+
desc: 'Lint manifest compatibility and App Store hints',
|
|
187
|
+
builder: (args) => {
|
|
188
|
+
args.option('f', {
|
|
189
|
+
alias: 'file',
|
|
190
|
+
describe: t('lzc_cli.lib.app.index.lpk_cmd_build_args_file_desc', '指定构建的lzc-build.yml文件'),
|
|
191
|
+
default: 'lzc-build.yml',
|
|
192
|
+
type: 'string',
|
|
193
|
+
});
|
|
194
|
+
},
|
|
195
|
+
handler: async ({ context, file }) => {
|
|
196
|
+
const cwd = context ? path.resolve(context) : process.cwd();
|
|
197
|
+
const lpk = await new LpkBuild(cwd, file).init();
|
|
198
|
+
logger.info(`Build config: ${lpk.optionsFilePath}`);
|
|
199
|
+
const warnings = await lpk.lint();
|
|
200
|
+
if (warnings.length === 0) {
|
|
201
|
+
logger.info('No manifest lint warnings found.');
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
logger.warn(`Found ${warnings.length} manifest lint warning(s).`);
|
|
205
|
+
},
|
|
206
|
+
},
|
|
184
207
|
{
|
|
185
208
|
command: 'devshell [context]',
|
|
186
209
|
desc: false,
|
package/lib/app/lpk_build.js
CHANGED
|
@@ -2,10 +2,11 @@ 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 { isDirExist, isDirSync, isFileExist, dumpToYaml, envTemplateFile, isValidPackageName, tarContentDir, isPngWithFile, isMacOs, isWindows, isLinux } from '../utils.js';
|
|
5
|
+
import { isDirExist, isDirSync, isFileExist, dumpToYaml, envTemplateFile, isValidPackageName, tarContentDir, isPngWithFile, isMacOs, isWindows, isLinux, REPRODUCIBLE_ARCHIVE_DATE } from '../utils.js';
|
|
6
6
|
import spawn from 'cross-spawn';
|
|
7
7
|
import { loadEffectiveManifest, splitManifestAndPackageInfo, findManifestStaticFields, PACKAGE_FILE_NAME } from '../package_info.js';
|
|
8
8
|
import { buildVarsFromEnvEntries, normalizeBuildEnvEntries, preprocessManifestFile } from './manifest_build.js';
|
|
9
|
+
import { collectManifestLintWarnings, logManifestLintWarnings } from './manifest_lint.js';
|
|
9
10
|
import archiver from 'archiver';
|
|
10
11
|
import yaml from 'js-yaml';
|
|
11
12
|
import { buildConfiguredImagesToTempDir } from './lpk_build_images.js';
|
|
@@ -32,7 +33,10 @@ async function archiveFolderTo(appDir, out, format = 'zip') {
|
|
|
32
33
|
});
|
|
33
34
|
archive.pipe(output);
|
|
34
35
|
|
|
35
|
-
archive.directory(appDir, false)
|
|
36
|
+
archive.directory(appDir, false, (entryData) => ({
|
|
37
|
+
...entryData,
|
|
38
|
+
date: REPRODUCIBLE_ARCHIVE_DATE,
|
|
39
|
+
}));
|
|
36
40
|
archive.finalize();
|
|
37
41
|
});
|
|
38
42
|
}
|
|
@@ -432,6 +436,7 @@ export class LpkBuild {
|
|
|
432
436
|
this.hasPackageMetadataOverrides = false;
|
|
433
437
|
this.buildVars = null;
|
|
434
438
|
this.preprocessedManifestText = '';
|
|
439
|
+
this.lintWarnings = [];
|
|
435
440
|
|
|
436
441
|
this.beforeBuildPackageFn = [];
|
|
437
442
|
this.beforeDumpYamlFn = [];
|
|
@@ -529,6 +534,27 @@ export class LpkBuild {
|
|
|
529
534
|
return this.manifest;
|
|
530
535
|
}
|
|
531
536
|
|
|
537
|
+
async collectLintWarnings() {
|
|
538
|
+
await this.getManifest();
|
|
539
|
+
this.lintWarnings = await collectManifestLintWarnings({
|
|
540
|
+
cwd: this.pwd,
|
|
541
|
+
options: this.options,
|
|
542
|
+
manifestPath: this.manifestFilePath,
|
|
543
|
+
sourceManifest: this.sourceManifest,
|
|
544
|
+
manifest: this.manifest,
|
|
545
|
+
packageInfo: this.packageInfo,
|
|
546
|
+
});
|
|
547
|
+
return this.lintWarnings;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async lint({ silent = false } = {}) {
|
|
551
|
+
const warnings = await this.collectLintWarnings();
|
|
552
|
+
if (!silent) {
|
|
553
|
+
logManifestLintWarnings(warnings, logger);
|
|
554
|
+
}
|
|
555
|
+
return warnings;
|
|
556
|
+
}
|
|
557
|
+
|
|
532
558
|
async exec() {
|
|
533
559
|
await this.getManifest();
|
|
534
560
|
|
|
@@ -538,6 +564,8 @@ export class LpkBuild {
|
|
|
538
564
|
}, this.options);
|
|
539
565
|
}
|
|
540
566
|
|
|
567
|
+
await this.lint();
|
|
568
|
+
|
|
541
569
|
if (this.options['buildscript']) {
|
|
542
570
|
const cmd = isWindows ? 'cmd' : 'sh';
|
|
543
571
|
const cmdArgs = isWindows ? '/c' : '-c';
|
|
@@ -633,6 +633,7 @@ export async function buildConfiguredImagesToTempDir(rawConfig, manifest, cwd, t
|
|
|
633
633
|
}
|
|
634
634
|
bridge = new DebugBridge(cwd, buildRemote);
|
|
635
635
|
await bridge.init();
|
|
636
|
+
await bridge.ensureLpkV2Supported();
|
|
636
637
|
}
|
|
637
638
|
if (hasLocalBuilder) {
|
|
638
639
|
targetPlatform = bridge ? await bridge.platform() : DEFAULT_LOCAL_TARGET_PLATFORM;
|
package/lib/app/lpk_installer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logger from 'loglevel';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { Downloader, extractLpkSync } from '../utils.js';
|
|
4
|
+
import { Downloader, detectLpkArchiveFormat, extractLpkSync } from '../utils.js';
|
|
5
5
|
import { loadEffectiveManifestFromFiles } from '../package_info.js';
|
|
6
6
|
import { DebugBridge } from '../debug_bridge.js';
|
|
7
7
|
import shellapi from '../shellapi.js';
|
|
@@ -10,6 +10,11 @@ import { triggerApk } from './apkshell.js';
|
|
|
10
10
|
import { resolveBuildRemoteFromFile } from '../build_remote.js';
|
|
11
11
|
|
|
12
12
|
export const installConfig = { apk: true };
|
|
13
|
+
|
|
14
|
+
function isLpkV2Package(pkgPath) {
|
|
15
|
+
return detectLpkArchiveFormat(pkgPath) === 'tar';
|
|
16
|
+
}
|
|
17
|
+
|
|
13
18
|
// 从一个目录中找出修改时间最新的包
|
|
14
19
|
function findOnceLpkByDir(dir = process.cwd()) {
|
|
15
20
|
const pkg = fs
|
|
@@ -123,6 +128,9 @@ export class LpkInstaller {
|
|
|
123
128
|
}
|
|
124
129
|
const bridge = new DebugBridge(process.cwd(), buildRemote);
|
|
125
130
|
await bridge.init();
|
|
131
|
+
if (isLpkV2Package(pkgPath)) {
|
|
132
|
+
await bridge.ensureLpkV2Supported();
|
|
133
|
+
}
|
|
126
134
|
logger.info(t('lzc_cli.lib.app.lpk_installer.install_from_file_start_tips', '开始安装应用'));
|
|
127
135
|
await bridge.install(pkgPath, manifest ? manifest['package'] : '');
|
|
128
136
|
logger.info('\n');
|
|
@@ -144,3 +152,7 @@ export class LpkInstaller {
|
|
|
144
152
|
}
|
|
145
153
|
}
|
|
146
154
|
}
|
|
155
|
+
|
|
156
|
+
export const __test__ = {
|
|
157
|
+
isLpkV2Package,
|
|
158
|
+
};
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import semver from 'semver';
|
|
4
|
+
import { findManifestStaticFields } from '../package_info.js';
|
|
5
|
+
|
|
6
|
+
export const STORE_ICON_MAX_BYTES = 200 * 1024;
|
|
7
|
+
export const STORE_IMAGE_REGISTRY_PREFIX = 'registry.lazycat.cloud';
|
|
8
|
+
|
|
9
|
+
function hasOwn(obj, key) {
|
|
10
|
+
return Object.prototype.hasOwnProperty.call(obj ?? {}, key);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function hasPath(obj, pathList) {
|
|
14
|
+
let current = obj;
|
|
15
|
+
for (const key of pathList) {
|
|
16
|
+
if (!current || typeof current !== 'object' || !hasOwn(current, key)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
current = current[key];
|
|
20
|
+
}
|
|
21
|
+
return true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function createWarning(code, message) {
|
|
25
|
+
return { code, message };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function quoteFieldList(fields) {
|
|
29
|
+
return fields.map((field) => `\`${field}\``).join(', ');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizeString(value) {
|
|
33
|
+
return String(value ?? '').trim();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ANY = Symbol('any');
|
|
37
|
+
|
|
38
|
+
function arrayOf(value) {
|
|
39
|
+
return { kind: 'array', value };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function mapOf(value) {
|
|
43
|
+
return { kind: 'map', value };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const APP_HEALTHCHECK_SCHEMA = {
|
|
47
|
+
test_url: ANY,
|
|
48
|
+
disable: ANY,
|
|
49
|
+
start_period: ANY,
|
|
50
|
+
timeout: ANY,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const SERVICE_HEALTHCHECK_SCHEMA = {
|
|
54
|
+
test: ANY,
|
|
55
|
+
timeout: ANY,
|
|
56
|
+
interval: ANY,
|
|
57
|
+
retries: ANY,
|
|
58
|
+
start_period: ANY,
|
|
59
|
+
start_interval: ANY,
|
|
60
|
+
disable: ANY,
|
|
61
|
+
test_url: ANY,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const MANIFEST_SCHEMA = {
|
|
65
|
+
package: ANY,
|
|
66
|
+
version: ANY,
|
|
67
|
+
name: ANY,
|
|
68
|
+
description: ANY,
|
|
69
|
+
usage: ANY,
|
|
70
|
+
license: ANY,
|
|
71
|
+
homepage: ANY,
|
|
72
|
+
author: ANY,
|
|
73
|
+
min_os_version: ANY,
|
|
74
|
+
unsupported_platforms: ANY,
|
|
75
|
+
locales: ANY,
|
|
76
|
+
ext_config: {
|
|
77
|
+
permissions: ANY,
|
|
78
|
+
enable_document_access: ANY,
|
|
79
|
+
enable_media_access: ANY,
|
|
80
|
+
enable_clientfs_access: ANY,
|
|
81
|
+
disable_grpc_web_on_root: ANY,
|
|
82
|
+
default_prefix_domain: ANY,
|
|
83
|
+
enable_bind_mime_globs: ANY,
|
|
84
|
+
disable_url_raw_path: ANY,
|
|
85
|
+
remove_this_request_headers: ANY,
|
|
86
|
+
fix_websocket_header: ANY,
|
|
87
|
+
},
|
|
88
|
+
application: {
|
|
89
|
+
image: ANY,
|
|
90
|
+
background_task: ANY,
|
|
91
|
+
subdomain: ANY,
|
|
92
|
+
secondary_domains: ANY,
|
|
93
|
+
multi_instance: ANY,
|
|
94
|
+
usb_accel: ANY,
|
|
95
|
+
gpu_accel: ANY,
|
|
96
|
+
kvm_accel: ANY,
|
|
97
|
+
file_handler: {
|
|
98
|
+
mime: ANY,
|
|
99
|
+
actions: ANY,
|
|
100
|
+
},
|
|
101
|
+
entries: arrayOf({
|
|
102
|
+
id: ANY,
|
|
103
|
+
title: ANY,
|
|
104
|
+
path: ANY,
|
|
105
|
+
prefix_domain: ANY,
|
|
106
|
+
}),
|
|
107
|
+
routes: ANY,
|
|
108
|
+
upstreams: arrayOf({
|
|
109
|
+
location: ANY,
|
|
110
|
+
disable_trim_location: ANY,
|
|
111
|
+
domain_prefix: ANY,
|
|
112
|
+
backend: ANY,
|
|
113
|
+
use_backend_host: ANY,
|
|
114
|
+
backend_launch_command: ANY,
|
|
115
|
+
trim_url_suffix: ANY,
|
|
116
|
+
disable_backend_ssl_verify: ANY,
|
|
117
|
+
disable_auto_health_checking: ANY,
|
|
118
|
+
disable_url_raw_path: ANY,
|
|
119
|
+
remove_this_request_headers: ANY,
|
|
120
|
+
fix_websocket_header: ANY,
|
|
121
|
+
dump_http_headers_when_5xx: ANY,
|
|
122
|
+
dump_http_headers_when_paths: ANY,
|
|
123
|
+
}),
|
|
124
|
+
injects: arrayOf({
|
|
125
|
+
id: ANY,
|
|
126
|
+
on: ANY,
|
|
127
|
+
auth_required: ANY,
|
|
128
|
+
prefix_domain: ANY,
|
|
129
|
+
when: ANY,
|
|
130
|
+
unless: ANY,
|
|
131
|
+
do: arrayOf({
|
|
132
|
+
src: ANY,
|
|
133
|
+
params: ANY,
|
|
134
|
+
}),
|
|
135
|
+
}),
|
|
136
|
+
public_path: ANY,
|
|
137
|
+
workdir: ANY,
|
|
138
|
+
ingress: arrayOf({
|
|
139
|
+
protocol: ANY,
|
|
140
|
+
port: ANY,
|
|
141
|
+
service: ANY,
|
|
142
|
+
description: ANY,
|
|
143
|
+
publish_port: ANY,
|
|
144
|
+
send_port_info: ANY,
|
|
145
|
+
yes_i_want_80_443: ANY,
|
|
146
|
+
}),
|
|
147
|
+
environment: ANY,
|
|
148
|
+
health_check: APP_HEALTHCHECK_SCHEMA,
|
|
149
|
+
oidc_redirect_path: ANY,
|
|
150
|
+
handlers: {
|
|
151
|
+
acl_handler: ANY,
|
|
152
|
+
error_page_templates: ANY,
|
|
153
|
+
},
|
|
154
|
+
user_app: ANY,
|
|
155
|
+
depends_on: ANY,
|
|
156
|
+
},
|
|
157
|
+
services: mapOf({
|
|
158
|
+
init: ANY,
|
|
159
|
+
image: ANY,
|
|
160
|
+
environment: ANY,
|
|
161
|
+
entrypoint: ANY,
|
|
162
|
+
command: ANY,
|
|
163
|
+
tmpfs: ANY,
|
|
164
|
+
depends_on: ANY,
|
|
165
|
+
healthcheck: SERVICE_HEALTHCHECK_SCHEMA,
|
|
166
|
+
health_check: SERVICE_HEALTHCHECK_SCHEMA,
|
|
167
|
+
user: ANY,
|
|
168
|
+
cpu_shares: ANY,
|
|
169
|
+
cpus: ANY,
|
|
170
|
+
mem_limit: ANY,
|
|
171
|
+
shm_size: ANY,
|
|
172
|
+
network_mode: ANY,
|
|
173
|
+
netadmin: ANY,
|
|
174
|
+
setup_script: ANY,
|
|
175
|
+
binds: ANY,
|
|
176
|
+
runtime: ANY,
|
|
177
|
+
}),
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
function collectUnknownFieldPaths(value, schema, currentPath = '') {
|
|
181
|
+
if (schema === ANY || !value || typeof value !== 'object') {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (Array.isArray(value)) {
|
|
186
|
+
if (!schema || schema.kind !== 'array') {
|
|
187
|
+
return [];
|
|
188
|
+
}
|
|
189
|
+
return value.flatMap((item, index) => collectUnknownFieldPaths(item, schema.value, `${currentPath}[${index}]`));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (schema?.kind === 'map') {
|
|
193
|
+
return Object.entries(value).flatMap(([key, item]) => collectUnknownFieldPaths(item, schema.value, currentPath ? `${currentPath}.${key}` : key));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const unknownPaths = [];
|
|
197
|
+
for (const [key, item] of Object.entries(value)) {
|
|
198
|
+
const nextPath = currentPath ? `${currentPath}.${key}` : key;
|
|
199
|
+
if (!hasOwn(schema, key)) {
|
|
200
|
+
unknownPaths.push(nextPath);
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
unknownPaths.push(...collectUnknownFieldPaths(item, schema[key], nextPath));
|
|
204
|
+
}
|
|
205
|
+
return unknownPaths;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function collectUnknownFieldWarnings(sourceManifest) {
|
|
209
|
+
const unknownFields = collectUnknownFieldPaths(sourceManifest ?? {}, MANIFEST_SCHEMA);
|
|
210
|
+
if (unknownFields.length === 0) {
|
|
211
|
+
return [];
|
|
212
|
+
}
|
|
213
|
+
return [createWarning('unknown-manifest-fields', `Unknown manifest fields detected: ${quoteFieldList(unknownFields)}. Please confirm whether they take effect.`)];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function collectStaticFieldWarnings(sourceManifest) {
|
|
217
|
+
const staticFields = findManifestStaticFields(sourceManifest ?? {});
|
|
218
|
+
if (staticFields.length === 0) {
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
return [
|
|
222
|
+
createWarning(
|
|
223
|
+
'legacy-static-package-fields',
|
|
224
|
+
`Top-level static package fields in lzc-manifest.yml are a legacy LPK v1 layout. Move ${quoteFieldList(staticFields)} to package.yml for LPK v2.`,
|
|
225
|
+
),
|
|
226
|
+
];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function collectApplicationWarnings(sourceManifest) {
|
|
230
|
+
const warnings = [];
|
|
231
|
+
if (hasPath(sourceManifest, ['application', 'handlers'])) {
|
|
232
|
+
warnings.push(
|
|
233
|
+
createWarning(
|
|
234
|
+
'application-handlers-deprecated',
|
|
235
|
+
'`application.handlers` is deprecated and kept for compatibility only. Avoid introducing new projects that depend on it.',
|
|
236
|
+
),
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
if (hasPath(sourceManifest, ['application', 'user_app'])) {
|
|
240
|
+
warnings.push(
|
|
241
|
+
createWarning(
|
|
242
|
+
'application-user-app-deprecated',
|
|
243
|
+
'`application.user_app` is deprecated. Use `application.multi_instance` when you need multi-instance deployment semantics.',
|
|
244
|
+
),
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
if (hasPath(sourceManifest, ['application', 'depends_on'])) {
|
|
248
|
+
warnings.push(
|
|
249
|
+
createWarning(
|
|
250
|
+
'application-depends-on-deprecated',
|
|
251
|
+
'`application.depends_on` is deprecated. Use `services.<name>.depends_on`, or rely on automatic health checking from `application.routes` and `application.upstreams`.',
|
|
252
|
+
),
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
return warnings;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function collectServiceWarnings(sourceManifest) {
|
|
259
|
+
const deprecatedServices = Object.entries(sourceManifest?.services ?? {})
|
|
260
|
+
.filter(([, serviceConfig]) => hasOwn(serviceConfig, 'health_check'))
|
|
261
|
+
.map(([serviceName]) => `services.${serviceName}.health_check`);
|
|
262
|
+
if (deprecatedServices.length === 0) {
|
|
263
|
+
return [];
|
|
264
|
+
}
|
|
265
|
+
return [
|
|
266
|
+
createWarning(
|
|
267
|
+
'service-health-check-deprecated',
|
|
268
|
+
`Legacy service health check fields detected: ${quoteFieldList(deprecatedServices)}. Rename them to \`healthcheck\`.`,
|
|
269
|
+
),
|
|
270
|
+
];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function collectExtConfigWarnings(sourceManifest) {
|
|
274
|
+
const legacyFields = ['disable_url_raw_path', 'remove_this_request_headers', 'fix_websocket_header'].filter((field) =>
|
|
275
|
+
hasPath(sourceManifest, ['ext_config', field]),
|
|
276
|
+
);
|
|
277
|
+
if (legacyFields.length === 0) {
|
|
278
|
+
return [];
|
|
279
|
+
}
|
|
280
|
+
return [
|
|
281
|
+
createWarning(
|
|
282
|
+
'ext-config-http-routing-deprecated',
|
|
283
|
+
`Legacy HTTP routing fields in \`ext_config\` are deprecated: ${quoteFieldList(legacyFields)}. Move them to \`application.upstreams[*]\` with the same field names.`,
|
|
284
|
+
),
|
|
285
|
+
];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function hasLocales(manifest, sourceManifest, packageInfo) {
|
|
289
|
+
return hasOwn(packageInfo, 'locales') || hasOwn(manifest, 'locales') || hasOwn(sourceManifest, 'locales');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function resolvePackageVersion(manifest, sourceManifest, packageInfo) {
|
|
293
|
+
const candidates = [packageInfo?.version, manifest?.version, sourceManifest?.version];
|
|
294
|
+
for (const candidate of candidates) {
|
|
295
|
+
const value = normalizeString(candidate);
|
|
296
|
+
if (value) {
|
|
297
|
+
return value;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return '';
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function parseEmbedAlias(imageRef) {
|
|
304
|
+
const value = normalizeString(imageRef);
|
|
305
|
+
if (!value.startsWith('embed:')) {
|
|
306
|
+
return '';
|
|
307
|
+
}
|
|
308
|
+
const rest = value.slice('embed:'.length).trim();
|
|
309
|
+
const at = rest.indexOf('@');
|
|
310
|
+
return normalizeString(at >= 0 ? rest.slice(0, at) : rest);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function collectImageRefs(sourceManifest) {
|
|
314
|
+
const refs = [];
|
|
315
|
+
const applicationImage = normalizeString(sourceManifest?.application?.image);
|
|
316
|
+
if (applicationImage) {
|
|
317
|
+
refs.push({
|
|
318
|
+
path: 'application.image',
|
|
319
|
+
value: applicationImage,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
for (const [serviceName, serviceConfig] of Object.entries(sourceManifest?.services ?? {})) {
|
|
323
|
+
const imageValue = normalizeString(serviceConfig?.image);
|
|
324
|
+
if (!imageValue) {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
refs.push({
|
|
328
|
+
path: `services.${serviceName}.image`,
|
|
329
|
+
value: imageValue,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
return refs;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
function collectStoreVersionWarnings(manifest, sourceManifest, packageInfo) {
|
|
336
|
+
const version = resolvePackageVersion(manifest, sourceManifest, packageInfo);
|
|
337
|
+
if (!version) {
|
|
338
|
+
return [];
|
|
339
|
+
}
|
|
340
|
+
if (semver.valid(version)) {
|
|
341
|
+
return [];
|
|
342
|
+
}
|
|
343
|
+
return [createWarning('store-version-invalid-semver', `App Store submission requires a valid semver version. Current version is \`${version}\`.`)];
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function collectStoreImageWarnings(sourceManifest, options, embeddedImageUpstreams) {
|
|
347
|
+
const warnings = [];
|
|
348
|
+
const imageBuildOptions = options?.images ?? {};
|
|
349
|
+
for (const imageRef of collectImageRefs(sourceManifest)) {
|
|
350
|
+
const embedAlias = parseEmbedAlias(imageRef.value);
|
|
351
|
+
if (embedAlias) {
|
|
352
|
+
const actualUpstream = normalizeString(embeddedImageUpstreams?.[embedAlias]);
|
|
353
|
+
if (actualUpstream) {
|
|
354
|
+
if (!actualUpstream.startsWith(STORE_IMAGE_REGISTRY_PREFIX)) {
|
|
355
|
+
warnings.push(
|
|
356
|
+
createWarning(
|
|
357
|
+
'store-image-embed-upstream-invalid',
|
|
358
|
+
`App Store submission requires \`${imageRef.path}\` to use upstream images from ${STORE_IMAGE_REGISTRY_PREFIX}. Current upstream for \`${embedAlias}\` is \`${actualUpstream}\`.`,
|
|
359
|
+
),
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
continue;
|
|
363
|
+
}
|
|
364
|
+
const buildConfig = imageBuildOptions?.[embedAlias];
|
|
365
|
+
if (!buildConfig || typeof buildConfig !== 'object') {
|
|
366
|
+
warnings.push(
|
|
367
|
+
createWarning(
|
|
368
|
+
'store-image-embed-alias-missing',
|
|
369
|
+
`App Store submission requires \`${imageRef.path}\` to resolve to ${STORE_IMAGE_REGISTRY_PREFIX}. Embed alias \`${embedAlias}\` has no matching \`images.${embedAlias}\` config; please confirm whether it takes effect.`,
|
|
370
|
+
),
|
|
371
|
+
);
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
const upstreamMatch = normalizeString(buildConfig['upstream-match'] ?? STORE_IMAGE_REGISTRY_PREFIX);
|
|
375
|
+
if (!upstreamMatch.startsWith(STORE_IMAGE_REGISTRY_PREFIX)) {
|
|
376
|
+
warnings.push(
|
|
377
|
+
createWarning(
|
|
378
|
+
'store-image-embed-upstream-invalid',
|
|
379
|
+
`App Store submission requires \`${imageRef.path}\` to use upstream images from ${STORE_IMAGE_REGISTRY_PREFIX}. Current \`images.${embedAlias}.upstream-match\` is \`${upstreamMatch}\`.`,
|
|
380
|
+
),
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
continue;
|
|
384
|
+
}
|
|
385
|
+
if (!imageRef.value.startsWith(STORE_IMAGE_REGISTRY_PREFIX)) {
|
|
386
|
+
warnings.push(
|
|
387
|
+
createWarning(
|
|
388
|
+
'store-image-registry-invalid',
|
|
389
|
+
`App Store submission requires \`${imageRef.path}\` to start with ${STORE_IMAGE_REGISTRY_PREFIX}. Current value is \`${imageRef.value}\`.`,
|
|
390
|
+
),
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return warnings;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function collectStoreWarnings({ cwd, options, manifest, sourceManifest, packageInfo, embeddedImageUpstreams }) {
|
|
398
|
+
const warnings = [];
|
|
399
|
+
warnings.push(...collectStoreVersionWarnings(manifest, sourceManifest, packageInfo));
|
|
400
|
+
if (!hasLocales(manifest, sourceManifest, packageInfo)) {
|
|
401
|
+
warnings.push(
|
|
402
|
+
createWarning(
|
|
403
|
+
'store-locales-required',
|
|
404
|
+
'App Store submission requires `locales`. For LPK v2, define it in package.yml.',
|
|
405
|
+
),
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
warnings.push(...collectStoreImageWarnings(sourceManifest, options, embeddedImageUpstreams));
|
|
409
|
+
|
|
410
|
+
const rawIconPath = String(options?.icon ?? '').trim();
|
|
411
|
+
if (!rawIconPath) {
|
|
412
|
+
return warnings;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const iconPath = path.isAbsolute(rawIconPath) ? rawIconPath : path.resolve(cwd || process.cwd(), rawIconPath);
|
|
416
|
+
if (!fs.existsSync(iconPath)) {
|
|
417
|
+
return warnings;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const stats = fs.statSync(iconPath);
|
|
421
|
+
if (!stats.isFile()) {
|
|
422
|
+
return warnings;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (stats.size > STORE_ICON_MAX_BYTES) {
|
|
426
|
+
warnings.push(
|
|
427
|
+
createWarning(
|
|
428
|
+
'store-icon-too-large',
|
|
429
|
+
`App Store submission requires icon.png smaller than 200 KiB. Current icon source is ${stats.size} bytes: ${iconPath}`,
|
|
430
|
+
),
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
return warnings;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
export async function collectManifestLintWarnings({ cwd, options, manifestPath, sourceManifest, manifest, packageInfo, embeddedImageUpstreams } = {}) {
|
|
438
|
+
return [
|
|
439
|
+
...collectUnknownFieldWarnings(sourceManifest),
|
|
440
|
+
...collectStaticFieldWarnings(sourceManifest),
|
|
441
|
+
...collectApplicationWarnings(sourceManifest),
|
|
442
|
+
...collectServiceWarnings(sourceManifest),
|
|
443
|
+
...collectExtConfigWarnings(sourceManifest),
|
|
444
|
+
...collectStoreWarnings({
|
|
445
|
+
cwd,
|
|
446
|
+
options,
|
|
447
|
+
manifestPath,
|
|
448
|
+
sourceManifest,
|
|
449
|
+
manifest,
|
|
450
|
+
packageInfo,
|
|
451
|
+
embeddedImageUpstreams,
|
|
452
|
+
}),
|
|
453
|
+
];
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export function isStoreLintWarning(warning) {
|
|
457
|
+
return String(warning?.code ?? '').startsWith('store-');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
export function logManifestLintWarnings(warnings, loggerLike) {
|
|
461
|
+
for (const warning of warnings ?? []) {
|
|
462
|
+
loggerLike.warn(`[lint] ${warning.message}`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
@@ -191,6 +191,7 @@ export async function resolveProjectRuntime(startDir = process.cwd(), selection
|
|
|
191
191
|
|
|
192
192
|
const bridge = new DebugBridge(projectCwd, buildRemote);
|
|
193
193
|
await bridge.init();
|
|
194
|
+
await bridge.ensureLpkV2Supported();
|
|
194
195
|
|
|
195
196
|
const userApp = isUserApp(manifest);
|
|
196
197
|
const composeProjectName = pkgId.replaceAll('.', '');
|
package/lib/appstore/publish.js
CHANGED
|
@@ -4,9 +4,12 @@ import FormData from 'form-data';
|
|
|
4
4
|
import fs from 'node:fs';
|
|
5
5
|
import inquirer from 'inquirer';
|
|
6
6
|
import path from 'node:path';
|
|
7
|
-
import
|
|
7
|
+
import yaml from 'js-yaml';
|
|
8
|
+
import { isFileExist, isPngWithFile, extractLpkSync, isValidPackageName, getLanguageForLocale } from '../utils.js';
|
|
8
9
|
import { t } from '../i18n/index.js';
|
|
9
10
|
import { appStoreServerUrl } from './env.js';
|
|
11
|
+
import { loadEffectiveManifestFromFiles } from '../package_info.js';
|
|
12
|
+
import { collectManifestLintWarnings, isStoreLintWarning } from '../app/manifest_lint.js';
|
|
10
13
|
|
|
11
14
|
async function askChangeLog(locale) {
|
|
12
15
|
const noEmpty = (value) => value != '';
|
|
@@ -199,10 +202,54 @@ export class Publish {
|
|
|
199
202
|
return raw[0] == '{' && raw[ml - 1] == '}';
|
|
200
203
|
}
|
|
201
204
|
|
|
202
|
-
static
|
|
205
|
+
static loadPackageForPublish(pkgPath, tempDir) {
|
|
206
|
+
extractLpkSync(pkgPath, tempDir);
|
|
207
|
+
const manifestPath = path.join(tempDir, 'manifest.yml');
|
|
208
|
+
const loaded = loadEffectiveManifestFromFiles(manifestPath);
|
|
209
|
+
const imagesLockPath = path.join(tempDir, 'images.lock');
|
|
210
|
+
let embeddedImageUpstreams = {};
|
|
211
|
+
if (isFileExist(imagesLockPath)) {
|
|
212
|
+
const imagesLock = yaml.load(fs.readFileSync(imagesLockPath, 'utf8')) ?? {};
|
|
213
|
+
const images = imagesLock?.images ?? {};
|
|
214
|
+
embeddedImageUpstreams = Object.fromEntries(
|
|
215
|
+
Object.entries(images).map(([alias, imageInfo]) => [alias, String(imageInfo?.upstream ?? '').trim()]),
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
manifest: loaded.manifest,
|
|
220
|
+
sourceManifest: loaded.sourceManifest,
|
|
221
|
+
packageInfo: loaded.packageInfo,
|
|
222
|
+
manifestPath,
|
|
223
|
+
embeddedImageUpstreams,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
static async collectStorePublishWarnings(pkgPath) {
|
|
203
228
|
const tempDir = fs.mkdtempSync('.lzc-cli-publish');
|
|
204
229
|
try {
|
|
205
|
-
|
|
230
|
+
extractLpkSync(pkgPath, tempDir, ['devshell', 'icon.png', 'manifest.yml', 'package.yml', 'images.lock']);
|
|
231
|
+
const { manifest, sourceManifest, packageInfo, manifestPath, embeddedImageUpstreams } = Publish.loadPackageForPublish(pkgPath, tempDir);
|
|
232
|
+
const warnings = await collectManifestLintWarnings({
|
|
233
|
+
cwd: tempDir,
|
|
234
|
+
manifestPath,
|
|
235
|
+
sourceManifest,
|
|
236
|
+
manifest,
|
|
237
|
+
packageInfo,
|
|
238
|
+
embeddedImageUpstreams,
|
|
239
|
+
options: {
|
|
240
|
+
icon: path.join(tempDir, 'icon.png'),
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
return warnings.filter(isStoreLintWarning);
|
|
244
|
+
} finally {
|
|
245
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
static async preCheck(pkgPath) {
|
|
250
|
+
const tempDir = fs.mkdtempSync('.lzc-cli-publish');
|
|
251
|
+
try {
|
|
252
|
+
extractLpkSync(pkgPath, tempDir, ['devshell', 'icon.png']);
|
|
206
253
|
if (isFileExist(path.join(tempDir, 'devshell'))) {
|
|
207
254
|
logger.error(t('lzc_cli.lib.publish.pre_check_fail_tips', '不能发布一个devshell的版本,请重新使用 `lzc-cli project build` 构建'));
|
|
208
255
|
return false;
|
|
@@ -212,17 +259,25 @@ export class Publish {
|
|
|
212
259
|
logger.error(t('lzc_cli.lib.publish.pre_check_icon_not_exist_tips', 'icon 必须存在且要求是 png 格式'));
|
|
213
260
|
return false;
|
|
214
261
|
}
|
|
262
|
+
const storeWarnings = await Publish.collectStorePublishWarnings(pkgPath);
|
|
263
|
+
if (storeWarnings.length > 0) {
|
|
264
|
+
for (const warning of storeWarnings) {
|
|
265
|
+
logger.error(`[appstore] ${warning.message}`);
|
|
266
|
+
}
|
|
267
|
+
logger.error('当前 LPK 不满足商店发布要求,已拒绝发布');
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
215
270
|
return true;
|
|
216
271
|
} finally {
|
|
217
|
-
fs.rmSync(tempDir, { recursive: true });
|
|
272
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
218
273
|
}
|
|
219
274
|
}
|
|
220
275
|
|
|
221
276
|
async checkAppIdExist(pkgPath) {
|
|
222
277
|
const tempDir = fs.mkdtempSync('.lzc-cli-publish');
|
|
223
278
|
try {
|
|
224
|
-
|
|
225
|
-
const manifest =
|
|
279
|
+
extractLpkSync(pkgPath, tempDir, ['manifest.yml', 'package.yml']);
|
|
280
|
+
const { manifest } = Publish.loadPackageForPublish(pkgPath, tempDir);
|
|
226
281
|
const checkUrl = this.baseUrl + `/app/check/exist?package=${manifest['package']}`;
|
|
227
282
|
const res = await request(checkUrl, { method: 'GET' });
|
|
228
283
|
if (res.status >= 400) {
|
|
@@ -235,7 +290,7 @@ export class Publish {
|
|
|
235
290
|
return { manifest, appIdExisted: exist };
|
|
236
291
|
}
|
|
237
292
|
} finally {
|
|
238
|
-
fs.rmSync(tempDir, { recursive: true });
|
|
293
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
239
294
|
}
|
|
240
295
|
}
|
|
241
296
|
|
|
@@ -245,7 +300,7 @@ export class Publish {
|
|
|
245
300
|
* @param {string} currentLocale
|
|
246
301
|
*/
|
|
247
302
|
async publish(pkgPath, changelogs, currentLocale = 'zh') {
|
|
248
|
-
if (!Publish.preCheck(pkgPath)) return;
|
|
303
|
+
if (!(await Publish.preCheck(pkgPath))) return;
|
|
249
304
|
|
|
250
305
|
const { manifest, appIdExisted } = await this.checkAppIdExist(pkgPath);
|
|
251
306
|
if (!appIdExisted) {
|
package/lib/debug_bridge.js
CHANGED
|
@@ -13,6 +13,12 @@ const bannerfileContent = `˄=ᆽ=ᐟ \\`;
|
|
|
13
13
|
const DEBUG_BRIDGE_CONTAINER = 'cloudlazycatdevelopertools-app-1';
|
|
14
14
|
const DEBUG_BRIDGE_BINARY = '/lzcapp/pkg/content/debug.bridge';
|
|
15
15
|
const DEBUG_BRIDGE_APP_ID = 'cloud.lazycat.developer.tools';
|
|
16
|
+
export const MIN_LPK_V2_BACKEND_VERSION = '1.0.0';
|
|
17
|
+
|
|
18
|
+
export function isBackendVersionAtLeast(minimumVersion, currentVersion = '0.0.0') {
|
|
19
|
+
const normalizedCurrentVersion = String(currentVersion ?? '').trim() || '0.0.0';
|
|
20
|
+
return compareVersions(minimumVersion, normalizedCurrentVersion) >= 0;
|
|
21
|
+
}
|
|
16
22
|
|
|
17
23
|
export function sshBinary() {
|
|
18
24
|
if (isWindows) {
|
|
@@ -1233,12 +1239,11 @@ export class DebugBridge {
|
|
|
1233
1239
|
|
|
1234
1240
|
async backendVersion020() {
|
|
1235
1241
|
const backendVersion = await this.version();
|
|
1236
|
-
if (
|
|
1242
|
+
if (!isBackendVersionAtLeast('0.2.0', backendVersion)) {
|
|
1237
1243
|
logger.warn(
|
|
1238
1244
|
t(
|
|
1239
1245
|
'lzc_cli.lib.debug_bridge.backend_version_020_no_match_tips',
|
|
1240
|
-
`
|
|
1241
|
-
检测到您当前的 lzc-cli 版本较新,而 '懒猫开发者工具' 比较旧,请先到微服的商店升级 '懒猫开发者工具',点击下面的连接跳转:
|
|
1246
|
+
`Detected that your current lzc-cli version is newer, but the installed Lazycat Developer Tools backend is relatively old. Please upgrade Lazycat Developer Tools from the app store first:
|
|
1242
1247
|
|
|
1243
1248
|
-> https://appstore.{{boxname}}.heiyu.space/#/shop/detail/cloud.lazycat.developer.tools
|
|
1244
1249
|
`,
|
|
@@ -1251,4 +1256,26 @@ export class DebugBridge {
|
|
|
1251
1256
|
return;
|
|
1252
1257
|
}
|
|
1253
1258
|
}
|
|
1259
|
+
|
|
1260
|
+
async ensureLpkV2Supported() {
|
|
1261
|
+
const backendVersion = await this.version();
|
|
1262
|
+
if (isBackendVersionAtLeast(MIN_LPK_V2_BACKEND_VERSION, backendVersion)) {
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
logger.warn(
|
|
1266
|
+
t(
|
|
1267
|
+
'lzc_cli.lib.debug_bridge.backend_version_lpk_v2_no_match_tips',
|
|
1268
|
+
`LPK v2 features require Lazycat Developer Tools backend >= {{ minimumVersion }}, but the current backend version is {{ backendVersion }}. Please upgrade Lazycat Developer Tools from the app store first:
|
|
1269
|
+
|
|
1270
|
+
-> https://appstore.{{boxname}}.heiyu.space/#/shop/detail/cloud.lazycat.developer.tools
|
|
1271
|
+
`,
|
|
1272
|
+
{
|
|
1273
|
+
boxname: this.boxname,
|
|
1274
|
+
minimumVersion: MIN_LPK_V2_BACKEND_VERSION,
|
|
1275
|
+
backendVersion,
|
|
1276
|
+
},
|
|
1277
|
+
),
|
|
1278
|
+
);
|
|
1279
|
+
process.exit(1);
|
|
1280
|
+
}
|
|
1254
1281
|
}
|
|
@@ -162,6 +162,7 @@
|
|
|
162
162
|
},
|
|
163
163
|
"debug_bridge": {
|
|
164
164
|
"backend_version_020_no_match_tips": "It is detected that your current lzc-cli version is newer, and the 'Lazy Cat Developer Tools' is relatively old. Please go to the Microservice store to upgrade the 'Lazy Cat Developer Tools' first, and click the link below to jump:\n\n-> https://appstore.{{boxname}}.heiyu.space/#/shop/detail/cloud.lazycat.developer.tools",
|
|
165
|
+
"backend_version_lpk_v2_no_match_tips": "LPK v2 features require Lazycat Developer Tools backend >= {{minimumVersion}}, but the current backend version is {{backendVersion}}. Please upgrade Lazycat Developer Tools from the app store first:\n\n-> https://appstore.{{boxname}}.heiyu.space/#/shop/detail/cloud.lazycat.developer.tools",
|
|
165
166
|
"build_image_fail": "Failed to build image in LCMD",
|
|
166
167
|
"can_public_key_resolve_fail": "Domain name resolution failed, please check whether the proxy software intercepts *.heiyu.space",
|
|
167
168
|
"check_dev_tools_fail_tips": "Failed to detect the Lazycat developer tools. Please check whether your current network or the Lazycat LCMD client is started normally.",
|
|
@@ -162,6 +162,7 @@
|
|
|
162
162
|
},
|
|
163
163
|
"debug_bridge": {
|
|
164
164
|
"backend_version_020_no_match_tips": "\n检测到您当前的 lzc-cli 版本较新,而 '懒猫开发者工具' 比较旧,请先到微服的商店升级 '懒猫开发者工具',点击下面的连接跳转:\n\n-> https://appstore.{{boxname}}.heiyu.space/#/shop/detail/cloud.lazycat.developer.tools\n",
|
|
165
|
+
"backend_version_lpk_v2_no_match_tips": "\nLPK v2 功能要求 '懒猫开发者工具' backend 版本至少为 {{ minimumVersion }},当前 backend 版本为 {{ backendVersion }},请先到微服的商店升级 '懒猫开发者工具',点击下面的连接跳转:\n\n-> https://appstore.{{boxname}}.heiyu.space/#/shop/detail/cloud.lazycat.developer.tools\n",
|
|
165
166
|
"build_image_fail": "在微服中构建 image 失败",
|
|
166
167
|
"can_public_key_resolve_fail": "域名解析失败,请检查代理软件是否对 *.heiyu.space 拦截",
|
|
167
168
|
"check_dev_tools_fail_tips": "检测懒猫开发者工具失败,请检测您当前的网络或者懒猫微服客户端是否正常启动。",
|
package/lib/utils.js
CHANGED
|
@@ -78,6 +78,7 @@ export async function getLatestVersion(controller, pkgName = pkgInfo.name) {
|
|
|
78
78
|
export const isMacOs = process.platform == 'darwin';
|
|
79
79
|
export const isLinux = process.platform == 'linux';
|
|
80
80
|
export const isWindows = process.platform == 'win32';
|
|
81
|
+
export const REPRODUCIBLE_ARCHIVE_DATE = new Date('1980-01-01T00:00:00.000Z');
|
|
81
82
|
|
|
82
83
|
export const envsubstr = async (templateContents, args) => {
|
|
83
84
|
const parse = await importDefault('envsub/js/envsub-parser.js');
|
|
@@ -451,16 +452,13 @@ export async function tarContentDir(from, to, cwd = './', options = {}) {
|
|
|
451
452
|
tar.c(
|
|
452
453
|
{
|
|
453
454
|
cwd: cwd,
|
|
454
|
-
gzip: gzipEnabled,
|
|
455
|
+
gzip: gzipEnabled ? { mtime: 0 } : false,
|
|
455
456
|
filter: (filePath) => {
|
|
456
457
|
logger.debug(`tar gz ${filePath}`);
|
|
457
458
|
return true;
|
|
458
459
|
},
|
|
459
460
|
sync: true,
|
|
460
|
-
portable:
|
|
461
|
-
uid: 0,
|
|
462
|
-
gid: 0,
|
|
463
|
-
},
|
|
461
|
+
portable: true,
|
|
464
462
|
},
|
|
465
463
|
from,
|
|
466
464
|
)
|
|
@@ -561,6 +559,10 @@ function detectLpkArchiveFormatSync(filePath) {
|
|
|
561
559
|
}
|
|
562
560
|
}
|
|
563
561
|
|
|
562
|
+
export function detectLpkArchiveFormat(filePath) {
|
|
563
|
+
return detectLpkArchiveFormatSync(filePath);
|
|
564
|
+
}
|
|
565
|
+
|
|
564
566
|
function normalizeArchiveEntryPath(entryPath) {
|
|
565
567
|
return String(entryPath ?? '').replace(/^\.\//, '').replace(/\\/g, '/');
|
|
566
568
|
}
|
|
@@ -611,7 +613,7 @@ export function extractLpkSync(lpkPath, destPath, entries = []) {
|
|
|
611
613
|
}
|
|
612
614
|
|
|
613
615
|
fs.mkdirSync(destPath, { recursive: true });
|
|
614
|
-
const format =
|
|
616
|
+
const format = detectLpkArchiveFormat(lpkPath);
|
|
615
617
|
if (format === 'zip') {
|
|
616
618
|
extractZipEntriesSync(lpkPath, destPath, entries);
|
|
617
619
|
return;
|