@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.
- package/README.md +46 -7
- package/changelog.md +56 -19
- package/lib/app/apkshell.js +7 -44
- package/lib/app/index.js +5 -1
- package/lib/app/lpk_build.js +266 -56
- package/lib/app/lpk_build_images.js +424 -229
- package/lib/app/lpk_build_images_local.js +425 -0
- package/lib/app/lpk_build_images_pack_local.js +409 -0
- package/lib/app/lpk_create.js +158 -83
- package/lib/app/lpk_create_generator.js +35 -42
- package/lib/app/lpk_devshell.js +6 -2
- package/lib/app/lpk_installer.js +4 -3
- package/lib/app/manifest_build.js +259 -0
- package/lib/app/project_cp.js +5 -10
- package/lib/app/project_deploy.js +80 -11
- package/lib/app/project_exec.js +48 -11
- package/lib/app/project_info.js +59 -59
- package/lib/app/project_log.js +5 -10
- package/lib/app/project_runtime.js +113 -18
- package/lib/app/project_start.js +6 -11
- package/lib/app/project_sync.js +499 -0
- package/lib/appstore/apkshell.js +50 -0
- package/lib/appstore/publish.js +54 -15
- package/lib/build_remote.js +0 -1
- package/lib/config/index.js +1 -1
- package/lib/debug_bridge.js +217 -47
- package/lib/i18n/locales/en/translation.json +262 -262
- package/lib/i18n/locales/zh/translation.json +262 -262
- package/lib/lpk/core.js +2 -1
- package/lib/migrate/index.js +52 -0
- package/lib/package_info.js +135 -0
- package/lib/shellapi.js +35 -1
- package/lib/sig/core.js +2 -2
- package/lib/utils.js +92 -15
- package/package.json +89 -89
- package/scripts/cli.js +2 -0
- package/scripts/smoke/frontend-dev-entry.mjs +104 -0
- package/scripts/smoke/template-project.mjs +311 -0
- package/template/_lpk/README.md +6 -3
- package/template/_lpk/gui-vnc.manifest.yml.in +0 -9
- package/template/_lpk/hello-vue.manifest.yml.in +38 -0
- package/template/_lpk/manifest.yml.in +0 -9
- package/template/_lpk/package.yml.in +7 -0
- package/template/_lpk/todolist-golang.manifest.yml.in +23 -9
- package/template/_lpk/todolist-java.manifest.yml.in +23 -9
- package/template/_lpk/todolist-python.manifest.yml.in +31 -9
- package/template/_lpk/todolist-serverless.manifest.yml.in +38 -0
- package/template/blank/lzc-build.dev.yml +4 -0
- package/template/blank/lzc-build.yml +0 -2
- package/template/blank/lzc-manifest.yml +3 -12
- package/template/blank/package.yml +7 -0
- package/template/golang/Dockerfile +1 -1
- package/template/golang/Dockerfile.dev +20 -0
- package/template/golang/README.md +22 -11
- package/template/golang/_lzcdevignore +21 -0
- package/template/golang/lzc-build.dev.yml +12 -0
- package/template/golang/lzc-build.yml +0 -5
- package/template/golang/main.go +1 -1
- package/template/golang/manifest.dev.page.js +24 -0
- package/template/golang/run.sh +7 -0
- package/template/gui-vnc/README.md +5 -1
- package/template/gui-vnc/lzc-build.dev.yml +4 -0
- package/template/gui-vnc/lzc-build.yml +0 -5
- package/template/python/Dockerfile +2 -2
- package/template/python/Dockerfile.dev +18 -0
- package/template/python/README.md +28 -11
- package/template/python/_lzcdevignore +21 -0
- package/template/python/app.py +1 -1
- package/template/python/lzc-build.dev.yml +12 -0
- package/template/python/lzc-build.yml +0 -5
- package/template/python/manifest.dev.page.js +25 -0
- package/template/python/run.sh +12 -1
- package/template/springboot/Dockerfile +1 -1
- package/template/springboot/Dockerfile.dev +20 -0
- package/template/springboot/README.md +22 -11
- package/template/springboot/_lzcdevignore +21 -0
- package/template/springboot/lzc-build.dev.yml +12 -0
- package/template/springboot/lzc-build.yml +0 -5
- package/template/springboot/manifest.dev.page.js +24 -0
- package/template/springboot/run.sh +7 -0
- package/template/vue/README.md +14 -27
- package/template/vue/_gitignore +0 -1
- package/template/vue/lzc-build.dev.yml +7 -0
- package/template/vue/lzc-build.yml +0 -2
- package/template/vue/manifest.dev.page.js +50 -0
- package/template/vue/src/App.vue +1 -1
- package/template/vue-minidb/README.md +11 -19
- package/template/vue-minidb/_gitignore +0 -1
- package/template/vue-minidb/lzc-build.dev.yml +7 -0
- package/template/vue-minidb/lzc-build.yml +0 -2
- package/template/vue-minidb/manifest.dev.page.js +50 -0
- package/template/blank/_gitignore +0 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { Blob } from 'node:buffer';
|
|
4
|
+
import logger from 'loglevel';
|
|
5
|
+
import fetch, { FormData } from 'node-fetch';
|
|
6
|
+
import { t } from '../i18n/index.js';
|
|
7
|
+
import { appStoreServerUrl } from './env.js';
|
|
8
|
+
|
|
9
|
+
// axios@1.7.7 依赖的 form-data 使用了 util.isArray 导致 node 会输出弃用警告 (所以将逻辑迁移避免依赖)
|
|
10
|
+
export async function triggerApk(id, name, iconPath) {
|
|
11
|
+
if (!id) {
|
|
12
|
+
logger.error(t('lzc_cli.lib.appstore.apkshell.trigger_apk_empty_appid', 'Appid 为必填项!'));
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const appName = name || t('lzc_cli.lib.appstore.apkshell.trigger_apk_default_app_name', '懒猫应用');
|
|
17
|
+
const form = new FormData();
|
|
18
|
+
form.append('app_id', id);
|
|
19
|
+
form.append('app_name', appName);
|
|
20
|
+
|
|
21
|
+
if (iconPath && fs.existsSync(iconPath)) {
|
|
22
|
+
const iconBuffer = fs.readFileSync(iconPath);
|
|
23
|
+
form.append('app_icon', new Blob([iconBuffer]), path.basename(iconPath));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
const timer = setTimeout(() => controller.abort(), 5000);
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const resp = await fetch(`${appStoreServerUrl}/api/trigger_latest_for_app`, {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
body: form,
|
|
33
|
+
signal: controller.signal,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
logger.debug('triggerApk resp:', resp);
|
|
37
|
+
if (resp.status == 304) {
|
|
38
|
+
logger.debug(t('lzc_cli.lib.appstore.apkshell.trigger_apk_build_tips', `APK构建任务已创建成功,如需使用安卓端,请耐心等待1分钟左右`));
|
|
39
|
+
} else if (resp.status <= 201) {
|
|
40
|
+
logger.info(t('lzc_cli.lib.appstore.apkshell.trigger_apk_build_ok_tips', `APK构建任务已创建成功,如需使用安卓端,请耐心等待1分钟左右`));
|
|
41
|
+
} else if (resp.status >= 400) {
|
|
42
|
+
logger.debug(t('lzc_cli.lib.appstore.apkshell.trigger_apk_build_failed', '请求按钮应用出错:'), await resp.text());
|
|
43
|
+
throw t('lzc_cli.lib.appstore.apkshell.trigger_apk_build_failed_tips', `请求生成应用出错! 使用 --apk=n 停止生成APK`);
|
|
44
|
+
}
|
|
45
|
+
} catch (error) {
|
|
46
|
+
logger.debug(error);
|
|
47
|
+
} finally {
|
|
48
|
+
clearTimeout(timer);
|
|
49
|
+
}
|
|
50
|
+
}
|
package/lib/appstore/publish.js
CHANGED
|
@@ -107,15 +107,33 @@ async function askPublishAppInfo(baseUrl, manifest, locale) {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
async function askWhetherCreateLPK(baseUrl, manifest, locale) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
110
|
+
try {
|
|
111
|
+
// 是否允许创建应用预检请求
|
|
112
|
+
const _options = await request(`${baseUrl}/app/create`, {
|
|
113
|
+
method: 'OPTIONS',
|
|
114
|
+
});
|
|
115
|
+
logger.debug('create app preflight:', { url: _options.url, ok: _options.ok, status: _options.status });
|
|
116
|
+
if (!_options.ok || _options.status < 199 || _options.status > 299) {
|
|
117
|
+
throw undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 要求输入应用信息
|
|
121
|
+
const answers = await inquirer.prompt([
|
|
122
|
+
{
|
|
123
|
+
name: 'continue',
|
|
124
|
+
type: 'input',
|
|
125
|
+
message: t(
|
|
126
|
+
'lzc_cli.lib.publish.ask_whether_create_lpk_continue_prompt',
|
|
127
|
+
'检测到您当前的应用,还没有在懒猫微服中创建,是否使用当前的安装包中的信息进行创建? [y/n]',
|
|
128
|
+
),
|
|
129
|
+
default: 'y',
|
|
130
|
+
},
|
|
131
|
+
]);
|
|
132
|
+
if (answers.continue.toLowerCase() != 'y') {
|
|
133
|
+
throw undefined;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// 发送创建应用请求
|
|
119
137
|
const appInfo = await askPublishAppInfo(baseUrl, manifest, locale);
|
|
120
138
|
const crateAppRes = await request(`${baseUrl}/app/create`, {
|
|
121
139
|
method: 'POST',
|
|
@@ -127,9 +145,28 @@ async function askWhetherCreateLPK(baseUrl, manifest, locale) {
|
|
|
127
145
|
source_author: appInfo.author,
|
|
128
146
|
}),
|
|
129
147
|
});
|
|
130
|
-
logger.debug('create app res:
|
|
131
|
-
|
|
132
|
-
|
|
148
|
+
logger.debug('create app res:', await crateAppRes.text());
|
|
149
|
+
if (!crateAppRes.ok) {
|
|
150
|
+
const data = await crateAppRes.json();
|
|
151
|
+
throw new Error(data?.message || `status:${crateAppRes.status} statusText:${crateAppRes.statusText}`);
|
|
152
|
+
}
|
|
153
|
+
logger.info(
|
|
154
|
+
t('lzc_cli.lib.publish.ask_whether_create_lpk_success_tips', `创建 {{ package }} 应用成功!`, {
|
|
155
|
+
package: manifest['package'],
|
|
156
|
+
interpolation: { escapeValue: false },
|
|
157
|
+
}),
|
|
158
|
+
);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (error) {
|
|
161
|
+
logger.debug('create app failed: ', error.message ?? 'unknown');
|
|
162
|
+
logger.error(
|
|
163
|
+
t('lzc_cli.lib.publish.ask_whether_create_lpk_failed_tips', `创建 {{ package }} 应用失败!`, {
|
|
164
|
+
package: manifest['package'],
|
|
165
|
+
interpolation: { escapeValue: false },
|
|
166
|
+
}),
|
|
167
|
+
error.message || '',
|
|
168
|
+
);
|
|
169
|
+
}
|
|
133
170
|
logger.info(
|
|
134
171
|
t(
|
|
135
172
|
'lzc_cli.lib.publish.ask_whether_create_lpk_fail_tips',
|
|
@@ -212,7 +249,7 @@ export class Publish {
|
|
|
212
249
|
|
|
213
250
|
const { manifest, appIdExisted } = await this.checkAppIdExist(pkgPath);
|
|
214
251
|
if (!appIdExisted) {
|
|
215
|
-
await askWhetherCreateLPK(this.baseUrl, manifest,
|
|
252
|
+
await askWhetherCreateLPK(this.baseUrl, manifest, currentLocale);
|
|
216
253
|
}
|
|
217
254
|
|
|
218
255
|
await autoLogin();
|
|
@@ -239,7 +276,7 @@ export class Publish {
|
|
|
239
276
|
logger.error(
|
|
240
277
|
t('lzc_cli.lib.publish.publish_lpk_fail_tips', `LPK 文件上传失败,err: {{ message }}`, {
|
|
241
278
|
message: lpkInfo?.message ?? lpkInfo,
|
|
242
|
-
interpolation: { escapeValue: false }
|
|
279
|
+
interpolation: { escapeValue: false },
|
|
243
280
|
}),
|
|
244
281
|
);
|
|
245
282
|
throw new Error(lpkInfo?.message ?? lpkInfo);
|
|
@@ -253,7 +290,7 @@ export class Publish {
|
|
|
253
290
|
|
|
254
291
|
// changelogs 本地化
|
|
255
292
|
const langKey = getLanguageForLocale(currentLocale);
|
|
256
|
-
changelogs[langKey] = changelog
|
|
293
|
+
changelogs[langKey] = changelog;
|
|
257
294
|
}
|
|
258
295
|
logger.debug('publish changelogs is', changelogs);
|
|
259
296
|
|
|
@@ -270,6 +307,8 @@ export class Publish {
|
|
|
270
307
|
pkg_path: lpkInfo.url,
|
|
271
308
|
unsupported_platforms: lpkInfo.unsupportedPlatforms,
|
|
272
309
|
min_os_version: lpkInfo.minOsVersion,
|
|
310
|
+
lpk_size: lpkInfo.lpkSize,
|
|
311
|
+
image_size: lpkInfo.imageSize,
|
|
273
312
|
changelogs,
|
|
274
313
|
},
|
|
275
314
|
};
|
package/lib/build_remote.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { loadBuildRemoteFromConfig } from './box/ssh_remote.js';
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_BUILD_CONFIG_FILE = 'lzc-build.yml';
|
|
4
|
-
export const DEFAULT_BUILD_BASE_FILE = 'lzc-build.base.yml';
|
|
5
4
|
|
|
6
5
|
export function resolveBuildRemoteFromOptions(options, source = DEFAULT_BUILD_CONFIG_FILE) {
|
|
7
6
|
void options;
|
package/lib/config/index.js
CHANGED
|
@@ -51,7 +51,7 @@ export function configCommand(program) {
|
|
|
51
51
|
];
|
|
52
52
|
program.command({
|
|
53
53
|
command: 'config',
|
|
54
|
-
desc:
|
|
54
|
+
desc: t('lzc_cli.lib.config.index.config_cmd_desc', '配置管理'),
|
|
55
55
|
builder: (args) => {
|
|
56
56
|
args.command(subCommands);
|
|
57
57
|
args.example('$0 config get', t('lzc_cli.lib.config.index.config_cmd_list_tips', '列出所有配置'))
|
package/lib/debug_bridge.js
CHANGED
|
@@ -34,6 +34,18 @@ export function sshCmdArgsRaw(...args) {
|
|
|
34
34
|
return [...defaultOptions, ...args];
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
export function buildLegacySSHArgs(target, commandArgs = [], { tty = false } = {}) {
|
|
38
|
+
const args = sshCmdArgsRaw();
|
|
39
|
+
if (tty) {
|
|
40
|
+
args.push('-t');
|
|
41
|
+
}
|
|
42
|
+
args.push(target);
|
|
43
|
+
if (Array.isArray(commandArgs) && commandArgs.length > 0) {
|
|
44
|
+
args.push(buildSSHRemoteCommand(commandArgs.filter((arg) => String(arg ?? '') !== '')));
|
|
45
|
+
}
|
|
46
|
+
return args;
|
|
47
|
+
}
|
|
48
|
+
|
|
37
49
|
function stripAnsi(text = '') {
|
|
38
50
|
return String(text).replace(/\x1b\[[0-9;]*[A-Za-z]/g, '');
|
|
39
51
|
}
|
|
@@ -108,6 +120,21 @@ function normalizeErrorMessage(error) {
|
|
|
108
120
|
return String(error).trim();
|
|
109
121
|
}
|
|
110
122
|
|
|
123
|
+
export function shellEscapeArg(value) {
|
|
124
|
+
const text = String(value ?? '');
|
|
125
|
+
if (text === '') {
|
|
126
|
+
return "''";
|
|
127
|
+
}
|
|
128
|
+
if (/^[A-Za-z0-9_/.:=,@+-]+$/.test(text)) {
|
|
129
|
+
return text;
|
|
130
|
+
}
|
|
131
|
+
return "'" + text.replace(/'/g, "'\\''") + "'";
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function buildSSHRemoteCommand(args = []) {
|
|
135
|
+
return args.map((arg) => shellEscapeArg(arg)).join(' ');
|
|
136
|
+
}
|
|
137
|
+
|
|
111
138
|
export class DebugBridge {
|
|
112
139
|
constructor(baseDir = process.cwd(), buildRemote = undefined) {
|
|
113
140
|
this.buildRemote = buildRemote === undefined ? resolveBuildRemoteFromFile(baseDir) : buildRemote;
|
|
@@ -275,6 +302,13 @@ export class DebugBridge {
|
|
|
275
302
|
return args;
|
|
276
303
|
}
|
|
277
304
|
|
|
305
|
+
async resolveDevId() {
|
|
306
|
+
if (this.isBuildRemoteMode()) {
|
|
307
|
+
return '';
|
|
308
|
+
}
|
|
309
|
+
return shellApi.resolveClientId();
|
|
310
|
+
}
|
|
311
|
+
|
|
278
312
|
async init() {
|
|
279
313
|
await this.checkDevTools();
|
|
280
314
|
if (!(await this.canPublicKey())) {
|
|
@@ -340,22 +374,22 @@ export class DebugBridge {
|
|
|
340
374
|
|
|
341
375
|
async common(cmd, args) {
|
|
342
376
|
const resolvedIp = await resolveDomain(this.domain);
|
|
343
|
-
|
|
344
|
-
const ssh = spawn.sync(cmd,
|
|
345
|
-
shell:
|
|
377
|
+
const normalizedArgs = args.map((arg) => String(arg).replace(this.domain, resolvedIp));
|
|
378
|
+
const ssh = spawn.sync(cmd, normalizedArgs, {
|
|
379
|
+
shell: false,
|
|
346
380
|
encoding: 'utf-8',
|
|
347
381
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
348
382
|
});
|
|
349
|
-
logger.debug(t('lzc_cli.lib.debug_bridge.common_start_log', `执行命令 {{ cmd }} {{ args }}`, { cmd, args:
|
|
383
|
+
logger.debug(t('lzc_cli.lib.debug_bridge.common_start_log', `执行命令 {{ cmd }} {{ args }}`, { cmd, args: normalizedArgs.join(' '), interpolation: { escapeValue: false } }));
|
|
350
384
|
return new Promise((resolve, reject) => {
|
|
351
385
|
ssh.status == 0
|
|
352
386
|
? resolve(ssh.stdout)
|
|
353
387
|
: reject(
|
|
354
388
|
t('lzc_cli.lib.debug_bridge.common_exec_fail', `执行命令 {{ cmd }} {{ args }} 出错\n{{ stdout }}\n{{ stderr }}`, {
|
|
355
389
|
cmd,
|
|
356
|
-
args:
|
|
390
|
+
args: normalizedArgs.join(' '),
|
|
357
391
|
stdout: ssh.stdout ?? '',
|
|
358
|
-
|
|
392
|
+
stderr: ssh.stderr ?? '',
|
|
359
393
|
interpolation: { escapeValue: false },
|
|
360
394
|
}),
|
|
361
395
|
);
|
|
@@ -391,8 +425,12 @@ export class DebugBridge {
|
|
|
391
425
|
}
|
|
392
426
|
|
|
393
427
|
const resolvedIp = await resolveDomain(this.domain);
|
|
394
|
-
const
|
|
395
|
-
|
|
428
|
+
const commandArgs = ['install', '--uid', this.uid];
|
|
429
|
+
if (pkgId) {
|
|
430
|
+
commandArgs.push('--pkgId', pkgId);
|
|
431
|
+
}
|
|
432
|
+
const ssh = spawn(sshBinary(), buildLegacySSHArgs(`box@${resolvedIp}`, commandArgs), {
|
|
433
|
+
shell: false,
|
|
396
434
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
397
435
|
});
|
|
398
436
|
const output = streamInstallOutput(ssh, { printStdout: false, printStderr: false });
|
|
@@ -422,7 +460,7 @@ export class DebugBridge {
|
|
|
422
460
|
return ssh.status === 0;
|
|
423
461
|
}
|
|
424
462
|
try {
|
|
425
|
-
await this.common(sshBinary(),
|
|
463
|
+
await this.common(sshBinary(), buildLegacySSHArgs(`box@${this.domain}`));
|
|
426
464
|
return true;
|
|
427
465
|
} catch (err) {
|
|
428
466
|
logger.debug('canPublicKey error: ', err);
|
|
@@ -467,7 +505,7 @@ export class DebugBridge {
|
|
|
467
505
|
if (this.isBuildRemoteMode()) {
|
|
468
506
|
return this.remoteCommon(await this.remoteCommandWithUID(['status', appId]));
|
|
469
507
|
}
|
|
470
|
-
return this.common(sshBinary(),
|
|
508
|
+
return this.common(sshBinary(), buildLegacySSHArgs(`box@${this.domain}`, ['status', '--uid', this.uid, appId]));
|
|
471
509
|
}
|
|
472
510
|
|
|
473
511
|
async info(appId) {
|
|
@@ -475,7 +513,7 @@ export class DebugBridge {
|
|
|
475
513
|
if (this.isBuildRemoteMode()) {
|
|
476
514
|
stdout = await this.remoteCommon(await this.remoteCommandWithUID(['info', appId]));
|
|
477
515
|
} else {
|
|
478
|
-
stdout = await this.common(sshBinary(),
|
|
516
|
+
stdout = await this.common(sshBinary(), buildLegacySSHArgs(`box@${this.domain}`, ['info', '--uid', this.uid, appId]));
|
|
479
517
|
}
|
|
480
518
|
try {
|
|
481
519
|
return JSON.parse(stdout);
|
|
@@ -484,13 +522,36 @@ export class DebugBridge {
|
|
|
484
522
|
}
|
|
485
523
|
}
|
|
486
524
|
|
|
525
|
+
async syncDevID(appId, devId = '', userApp = false) {
|
|
526
|
+
const normalizedDevId = String(devId ?? '').trim();
|
|
527
|
+
if (this.isBuildRemoteMode()) {
|
|
528
|
+
const commandArgs = await this.remoteCommandWithUID(['sync-dev-id']);
|
|
529
|
+
if (normalizedDevId) {
|
|
530
|
+
commandArgs.push('--dev-id', normalizedDevId);
|
|
531
|
+
}
|
|
532
|
+
if (userApp) {
|
|
533
|
+
commandArgs.push('--userapp');
|
|
534
|
+
}
|
|
535
|
+
commandArgs.push(appId);
|
|
536
|
+
return this.remoteCommon(commandArgs);
|
|
537
|
+
}
|
|
538
|
+
const rawArgs = ['sync-dev-id', '--uid', this.uid];
|
|
539
|
+
if (normalizedDevId) {
|
|
540
|
+
rawArgs.push('--dev-id', normalizedDevId);
|
|
541
|
+
}
|
|
542
|
+
if (userApp) {
|
|
543
|
+
rawArgs.push('--userapp');
|
|
544
|
+
}
|
|
545
|
+
rawArgs.push(appId);
|
|
546
|
+
return this.common(sshBinary(), buildLegacySSHArgs(`box@${this.domain}`, rawArgs));
|
|
547
|
+
}
|
|
487
548
|
async isDevshell(appId) {
|
|
488
549
|
await this.backendVersion020();
|
|
489
550
|
if (this.isBuildRemoteMode()) {
|
|
490
551
|
const stdout = await this.remoteCommon(await this.remoteCommandWithUID(['isDevshellV2', appId]));
|
|
491
552
|
return stdout.trim() == 'true';
|
|
492
553
|
}
|
|
493
|
-
const stdout = await this.common(sshBinary(),
|
|
554
|
+
const stdout = await this.common(sshBinary(), buildLegacySSHArgs(`box@${this.domain}`, ['isDevshellV2', '--uid', this.uid, appId]));
|
|
494
555
|
return stdout == 'true';
|
|
495
556
|
}
|
|
496
557
|
|
|
@@ -498,18 +559,18 @@ export class DebugBridge {
|
|
|
498
559
|
if (this.isBuildRemoteMode()) {
|
|
499
560
|
return this.remoteCommon(await this.remoteCommandWithUID(['resume', appId]));
|
|
500
561
|
}
|
|
501
|
-
return this.common(sshBinary(),
|
|
562
|
+
return this.common(sshBinary(), buildLegacySSHArgs(`box@${this.domain}`, ['resume', '--uid', this.uid, appId]));
|
|
502
563
|
}
|
|
503
564
|
|
|
504
565
|
async pause(appId) {
|
|
505
566
|
if (this.isBuildRemoteMode()) {
|
|
506
567
|
return this.remoteCommon(await this.remoteCommandWithUID(['pause', appId]));
|
|
507
568
|
}
|
|
508
|
-
return this.common(sshBinary(),
|
|
569
|
+
return this.common(sshBinary(), buildLegacySSHArgs(`box@${this.domain}`, ['pause', '--uid', this.uid, appId]));
|
|
509
570
|
}
|
|
510
571
|
|
|
511
572
|
async version() {
|
|
512
|
-
const output = this.isBuildRemoteMode() ? await this.remoteCommon(['version']) : await this.common(sshBinary(),
|
|
573
|
+
const output = this.isBuildRemoteMode() ? await this.remoteCommon(['version']) : await this.common(sshBinary(), buildLegacySSHArgs(`box@${this.domain}`, ['version']));
|
|
513
574
|
logger.debug(`backend version:\n${output}`);
|
|
514
575
|
try {
|
|
515
576
|
const data = JSON.parse(output);
|
|
@@ -519,6 +580,23 @@ export class DebugBridge {
|
|
|
519
580
|
}
|
|
520
581
|
}
|
|
521
582
|
|
|
583
|
+
async platform() {
|
|
584
|
+
await this.backendVersion020();
|
|
585
|
+
const output = this.isBuildRemoteMode() ? await this.remoteCommon(['platform']) : await this.common(sshBinary(), buildLegacySSHArgs(`box@${this.domain}`, ['platform']));
|
|
586
|
+
logger.debug(`backend platform:\n${output}`);
|
|
587
|
+
let data;
|
|
588
|
+
try {
|
|
589
|
+
data = JSON.parse(output);
|
|
590
|
+
} catch (error) {
|
|
591
|
+
throw new Error(`parse platform output failed: ${error.message}`);
|
|
592
|
+
}
|
|
593
|
+
const platform = String(data?.platform ?? '').trim().toLowerCase();
|
|
594
|
+
if (!/^[a-z0-9]+\/[a-z0-9]+$/.test(platform)) {
|
|
595
|
+
throw new Error(`invalid platform output: ${output}`);
|
|
596
|
+
}
|
|
597
|
+
return platform;
|
|
598
|
+
}
|
|
599
|
+
|
|
522
600
|
async uninstall(appId, deleteAppData = false) {
|
|
523
601
|
if (this.isBuildRemoteMode()) {
|
|
524
602
|
const commandArgs = await this.remoteCommandWithUID(['uninstall']);
|
|
@@ -528,7 +606,7 @@ export class DebugBridge {
|
|
|
528
606
|
commandArgs.push(appId);
|
|
529
607
|
return this.remoteCommon(commandArgs);
|
|
530
608
|
}
|
|
531
|
-
return this.common(sshBinary(),
|
|
609
|
+
return this.common(sshBinary(), buildLegacySSHArgs(`box@${this.domain}`, ['uninstall', '--uid', this.uid, ...(deleteAppData ? ['--delete-data'] : []), appId]));
|
|
532
610
|
}
|
|
533
611
|
|
|
534
612
|
async devshell(appId, isUserApp, onconnect = null) {
|
|
@@ -557,14 +635,15 @@ export class DebugBridge {
|
|
|
557
635
|
});
|
|
558
636
|
} else {
|
|
559
637
|
const resolvedIp = await resolveDomain(this.domain);
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
638
|
+
const commandArgs = ['devshell', '--uid', this.uid];
|
|
639
|
+
if (isUserApp) {
|
|
640
|
+
commandArgs.push('--userapp');
|
|
641
|
+
}
|
|
642
|
+
commandArgs.push(appId, '/bin/sh', '/lzcapp/pkg/content/devshell/exec.sh');
|
|
643
|
+
stream = spawn(sshBinary(), buildLegacySSHArgs(`box@${resolvedIp}`, commandArgs, { tty: true }), {
|
|
644
|
+
shell: false,
|
|
645
|
+
stdio: 'inherit',
|
|
646
|
+
});
|
|
568
647
|
}
|
|
569
648
|
return new Promise((resolve, reject) => {
|
|
570
649
|
stream.on('close', (code) => {
|
|
@@ -590,8 +669,8 @@ export class DebugBridge {
|
|
|
590
669
|
});
|
|
591
670
|
} else {
|
|
592
671
|
const resolvedIp = await resolveDomain(this.domain);
|
|
593
|
-
buildStream = spawn(sshBinary(),
|
|
594
|
-
shell:
|
|
672
|
+
buildStream = spawn(sshBinary(), buildLegacySSHArgs(`box@${resolvedIp}`, buildArgs), {
|
|
673
|
+
shell: false,
|
|
595
674
|
stdio: ['pipe', 'inherit', 'inherit'],
|
|
596
675
|
});
|
|
597
676
|
}
|
|
@@ -698,15 +777,20 @@ export class DebugBridge {
|
|
|
698
777
|
|
|
699
778
|
async lzcDocker(argv) {
|
|
700
779
|
await this.backendVersion020();
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
780
|
+
let stream;
|
|
781
|
+
if (this.isBuildRemoteMode()) {
|
|
782
|
+
stream = spawn(sshBinary(), this.remoteBridgeArgs(['lzc-docker', ...argv], { tty: true }), {
|
|
783
|
+
shell: false,
|
|
784
|
+
stdio: 'inherit',
|
|
785
|
+
});
|
|
786
|
+
} else {
|
|
787
|
+
const resolvedIp = await resolveDomain(this.domain);
|
|
788
|
+
const remoteCommand = buildSSHRemoteCommand(['lzc-docker', ...argv]);
|
|
789
|
+
stream = spawn(sshBinary(), [...sshCmdArgsRaw(`box@${resolvedIp}`), '-t', remoteCommand], {
|
|
790
|
+
shell: false,
|
|
791
|
+
stdio: 'inherit',
|
|
792
|
+
});
|
|
793
|
+
}
|
|
710
794
|
return new Promise((resolve, reject) => {
|
|
711
795
|
stream.on('close', (code) => {
|
|
712
796
|
code == 0 ? resolve() : reject();
|
|
@@ -911,24 +995,105 @@ export class DebugBridge {
|
|
|
911
995
|
});
|
|
912
996
|
}
|
|
913
997
|
|
|
914
|
-
async
|
|
998
|
+
async packImagesArchive(imagesSpec, archivePath, outputPath) {
|
|
915
999
|
await this.backendVersion020();
|
|
1000
|
+
if (!Array.isArray(imagesSpec) || imagesSpec.length === 0) {
|
|
1001
|
+
throw new Error('imagesSpec cannot be empty');
|
|
1002
|
+
}
|
|
1003
|
+
const spec = Buffer.from(
|
|
1004
|
+
JSON.stringify({
|
|
1005
|
+
images: imagesSpec,
|
|
1006
|
+
}),
|
|
1007
|
+
).toString('base64');
|
|
1008
|
+
|
|
1009
|
+
const input = fs.createReadStream(archivePath);
|
|
1010
|
+
const output = fs.createWriteStream(outputPath);
|
|
1011
|
+
let stream;
|
|
1012
|
+
if (this.isBuildRemoteMode()) {
|
|
1013
|
+
stream = spawn(sshBinary(), this.remoteBridgeArgs(['pack-images-archive', '--spec', spec]), {
|
|
1014
|
+
shell: false,
|
|
1015
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
1016
|
+
});
|
|
1017
|
+
} else {
|
|
1018
|
+
const resolvedIp = await resolveDomain(this.domain);
|
|
1019
|
+
stream = spawn(sshBinary(), [...sshCmdArgsRaw(`box@${resolvedIp}`), 'pack-images-archive', '--spec', spec], {
|
|
1020
|
+
shell: false,
|
|
1021
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
input.pipe(stream.stdin);
|
|
1025
|
+
stream.stdout.pipe(output);
|
|
1026
|
+
|
|
1027
|
+
return new Promise((resolve, reject) => {
|
|
1028
|
+
let done = false;
|
|
1029
|
+
const fail = (err) => {
|
|
1030
|
+
if (done) {
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
done = true;
|
|
1034
|
+
try {
|
|
1035
|
+
stream.kill('SIGKILL');
|
|
1036
|
+
} catch {}
|
|
1037
|
+
try {
|
|
1038
|
+
input.destroy();
|
|
1039
|
+
} catch {}
|
|
1040
|
+
try {
|
|
1041
|
+
output.destroy();
|
|
1042
|
+
} catch {}
|
|
1043
|
+
try {
|
|
1044
|
+
fs.rmSync(outputPath, { force: true });
|
|
1045
|
+
} catch {}
|
|
1046
|
+
reject(err);
|
|
1047
|
+
};
|
|
1048
|
+
|
|
1049
|
+
input.on('error', (e) => {
|
|
1050
|
+
fail(e);
|
|
1051
|
+
});
|
|
1052
|
+
stream.on('error', (e) => {
|
|
1053
|
+
fail(e);
|
|
1054
|
+
});
|
|
1055
|
+
output.on('error', (e) => {
|
|
1056
|
+
fail(e);
|
|
1057
|
+
});
|
|
1058
|
+
stream.on('close', (code) => {
|
|
1059
|
+
if (done) {
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
output.end(() => {
|
|
1063
|
+
if (code == 0) {
|
|
1064
|
+
done = true;
|
|
1065
|
+
resolve();
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
fail(new Error('pack-images-archive failed'));
|
|
1069
|
+
});
|
|
1070
|
+
});
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
async lzcDockerCapture(argv, options = {}) {
|
|
1075
|
+
await this.backendVersion020();
|
|
1076
|
+
const stdinText = options && Object.prototype.hasOwnProperty.call(options, 'stdinText') ? String(options.stdinText ?? '') : '';
|
|
916
1077
|
let stream;
|
|
917
1078
|
if (this.isBuildRemoteMode()) {
|
|
918
1079
|
stream = spawn(sshBinary(), this.remoteBridgeArgs(['lzc-docker', ...argv]), {
|
|
919
1080
|
shell: false,
|
|
920
|
-
stdio: ['
|
|
1081
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
921
1082
|
});
|
|
922
1083
|
} else {
|
|
923
1084
|
const resolvedIp = await resolveDomain(this.domain);
|
|
924
1085
|
stream = spawn(sshBinary(), [...sshCmdArgsRaw(`box@${resolvedIp}`), 'lzc-docker', ...argv], {
|
|
925
1086
|
shell: false,
|
|
926
|
-
stdio: ['
|
|
1087
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
927
1088
|
});
|
|
928
1089
|
}
|
|
929
1090
|
|
|
930
1091
|
return await new Promise((resolve, reject) => {
|
|
931
1092
|
let output = '';
|
|
1093
|
+
if (stdinText) {
|
|
1094
|
+
stream.stdin.write(stdinText);
|
|
1095
|
+
}
|
|
1096
|
+
stream.stdin.end();
|
|
932
1097
|
stream.stdout.on('data', (chunk) => {
|
|
933
1098
|
output += chunk.toString();
|
|
934
1099
|
});
|
|
@@ -1045,15 +1210,20 @@ export class DebugBridge {
|
|
|
1045
1210
|
|
|
1046
1211
|
async lzcDockerCompose(argv) {
|
|
1047
1212
|
await this.backendVersion020();
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1213
|
+
let stream;
|
|
1214
|
+
if (this.isBuildRemoteMode()) {
|
|
1215
|
+
stream = spawn(sshBinary(), this.remoteBridgeArgs(['lzc-docker-compose', ...argv], { tty: true }), {
|
|
1216
|
+
shell: false,
|
|
1217
|
+
stdio: 'inherit',
|
|
1218
|
+
});
|
|
1219
|
+
} else {
|
|
1220
|
+
const resolvedIp = await resolveDomain(this.domain);
|
|
1221
|
+
const remoteCommand = buildSSHRemoteCommand(['lzc-docker-compose', ...argv]);
|
|
1222
|
+
stream = spawn(sshBinary(), [...sshCmdArgsRaw(`box@${resolvedIp}`), '-t', remoteCommand], {
|
|
1223
|
+
shell: false,
|
|
1224
|
+
stdio: 'inherit',
|
|
1225
|
+
});
|
|
1226
|
+
}
|
|
1057
1227
|
return new Promise((resolve, reject) => {
|
|
1058
1228
|
stream.on('close', (code) => {
|
|
1059
1229
|
code == 0 ? resolve() : reject();
|