@lazycatcloud/lzc-cli 1.3.14 → 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.
- package/README.md +30 -5
- package/changelog.md +4 -0
- package/lib/app/index.js +174 -58
- package/lib/app/lpk_build.js +192 -17
- package/lib/app/lpk_build_images.js +728 -0
- package/lib/app/lpk_create.js +93 -21
- package/lib/app/lpk_create_generator.js +144 -9
- package/lib/app/lpk_devshell.js +33 -19
- package/lib/app/lpk_embed_images.js +257 -0
- package/lib/app/lpk_installer.js +14 -7
- package/lib/app/project_cp.js +64 -0
- package/lib/app/project_deploy.js +33 -0
- package/lib/app/project_exec.js +45 -0
- package/lib/app/project_info.js +106 -0
- package/lib/app/project_log.js +67 -0
- package/lib/app/project_runtime.js +261 -0
- package/lib/app/project_start.js +100 -0
- package/lib/box/index.js +101 -4
- package/lib/box/ssh_remote.js +259 -0
- package/lib/build_remote.js +22 -0
- package/lib/config/index.js +1 -1
- package/lib/debug_bridge.js +837 -46
- package/lib/docker/index.js +30 -10
- package/lib/i18n/index.js +1 -0
- package/lib/i18n/locales/en/translation.json +17 -5
- package/lib/i18n/locales/zh/translation.json +16 -4
- package/lib/lpk/core.js +487 -0
- package/lib/lpk/index.js +210 -0
- package/lib/sig/core.js +254 -0
- package/lib/sig/index.js +88 -0
- package/lib/utils.js +3 -1
- package/package.json +2 -1
- package/scripts/cli.js +4 -0
- package/template/_lpk/README.md +11 -3
- package/template/_lpk/gui-vnc.manifest.yml.in +27 -0
- package/template/_lpk/manifest.yml.in +4 -2
- package/template/_lpk/todolist-golang.manifest.yml.in +16 -0
- package/template/_lpk/todolist-java.manifest.yml.in +15 -0
- package/template/_lpk/todolist-python.manifest.yml.in +15 -0
- package/template/_lpk/vue.lzc-build.yml.in +0 -44
- package/template/blank/_gitignore +1 -0
- package/template/blank/lzc-build.yml +25 -40
- package/template/blank/lzc-manifest.yml +14 -7
- package/template/golang/Dockerfile +19 -0
- package/template/golang/README.md +33 -0
- package/template/golang/_gitignore +3 -0
- package/template/golang/go.mod +3 -0
- package/template/golang/lzc-build.yml +21 -0
- package/template/golang/lzc-icon.png +0 -0
- package/template/golang/main.go +252 -0
- package/template/golang/run.sh +3 -0
- package/template/golang/web/index.html +238 -0
- package/template/gui-vnc/README.md +19 -0
- package/template/gui-vnc/_gitignore +2 -0
- package/template/gui-vnc/images/Dockerfile +30 -0
- package/template/gui-vnc/images/kasmvnc.yaml +33 -0
- package/template/gui-vnc/images/startup-script.desktop +9 -0
- package/template/gui-vnc/images/startup-script.sh +6 -0
- package/template/gui-vnc/lzc-build.yml +23 -0
- package/template/gui-vnc/lzc-icon.png +0 -0
- package/template/python/Dockerfile +15 -0
- package/template/python/README.md +33 -0
- package/template/python/_gitignore +3 -0
- package/template/python/app.py +110 -0
- package/template/python/lzc-build.yml +21 -0
- package/template/python/lzc-icon.png +0 -0
- package/template/python/requirements.txt +1 -0
- package/template/python/run.sh +3 -0
- package/template/python/web/index.html +238 -0
- package/template/springboot/Dockerfile +20 -0
- package/template/springboot/README.md +33 -0
- package/template/springboot/_gitignore +3 -0
- package/template/springboot/lzc-build.yml +21 -0
- package/template/springboot/lzc-icon.png +0 -0
- package/template/springboot/pom.xml +38 -0
- package/template/springboot/run.sh +3 -0
- package/template/springboot/src/main/java/cloud/lazycat/app/Application.java +132 -0
- package/template/springboot/src/main/resources/application.properties +1 -0
- package/template/springboot/src/main/resources/static/index.html +238 -0
- package/template/vue/README.md +17 -7
- package/template/vue/_gitignore +1 -0
- package/template/vue/lzc-build.yml +31 -42
- package/template/vue/src/App.vue +36 -25
- package/template/vue/src/style.css +106 -49
- package/template/vue-minidb/README.md +34 -0
- package/template/vue-minidb/_gitignore +26 -0
- package/template/vue-minidb/index.html +13 -0
- package/template/vue-minidb/lzc-build.yml +48 -0
- package/template/vue-minidb/lzc-icon.png +0 -0
- package/template/vue-minidb/package.json +21 -0
- package/template/vue-minidb/public/vite.svg +1 -0
- package/template/vue-minidb/src/App.vue +206 -0
- package/template/vue-minidb/src/assets/vue.svg +1 -0
- package/template/vue-minidb/src/main.ts +5 -0
- package/template/vue-minidb/src/style.css +136 -0
- package/template/vue-minidb/src/vite-env.d.ts +1 -0
- package/template/vue-minidb/tsconfig.app.json +24 -0
- package/template/vue-minidb/tsconfig.json +7 -0
- package/template/vue-minidb/tsconfig.node.json +22 -0
- package/template/vue-minidb/vite.config.ts +10 -0
- /package/template/{vue → vue-minidb}/src/components/HelloWorld.vue +0 -0
package/README.md
CHANGED
|
@@ -17,19 +17,44 @@ lzc-cli completion >> ~/.zshrc
|
|
|
17
17
|
下面开始使用 `lzc-cli` 去创建一个项目吧!
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
+
# 在新目录创建项目(交互选择模板)
|
|
20
21
|
lzc-cli project create you_project
|
|
21
22
|
|
|
22
|
-
#
|
|
23
|
-
lzc-cli project
|
|
23
|
+
# 在当前目录初始化空白项目
|
|
24
|
+
lzc-cli project create --in-place
|
|
25
|
+
|
|
26
|
+
# 构建 release lpk 包(优先 lzc-build.release.yml,不存在则回退 lzc-build.yml)
|
|
27
|
+
lzc-cli project release
|
|
24
28
|
|
|
25
29
|
# 将lpk包安装到盒子中去
|
|
26
|
-
lzc-cli
|
|
30
|
+
lzc-cli lpk install
|
|
31
|
+
|
|
32
|
+
# 部署后默认自动启动
|
|
33
|
+
lzc-cli project deploy
|
|
27
34
|
|
|
28
|
-
#
|
|
29
|
-
lzc-cli project
|
|
35
|
+
# 进入运行容器调试
|
|
36
|
+
lzc-cli project exec /bin/sh
|
|
30
37
|
|
|
31
38
|
# 经过测试后,将包发布到懒猫云商店中去
|
|
32
39
|
lzc-cli appstore publish
|
|
33
40
|
```
|
|
34
41
|
|
|
35
42
|
[changelog](./changelog.md)
|
|
43
|
+
|
|
44
|
+
#### box add-by-ssh 远端直连模式
|
|
45
|
+
|
|
46
|
+
当运行环境无法使用 `hclient` 时,可通过 `box add-by-ssh` 配置远端 ssh 目标,由 `lzc-cli` 直连 `lzcos ssh` 并在远端执行 debug bridge 命令。
|
|
47
|
+
|
|
48
|
+
示例:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
lzc-cli box add-by-ssh root 192.168.31.13
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
说明:
|
|
55
|
+
|
|
56
|
+
1. 参数格式为 `loginUser address`,地址支持 `host` 或 `host:port`
|
|
57
|
+
2. 配置后会自动设为默认盒子,可通过 `box list/switch/default` 管理
|
|
58
|
+
3. `project release/deploy/start/exec/cp/log/info`、`lpk install/uninstall`、`docker/docker-compose` 都会优先使用该远端
|
|
59
|
+
4. `lzc-build.yml` 不再支持 `remote` 字段
|
|
60
|
+
5. 可选基础配置文件为 `lzc-build.base.yml`(与构建配置同目录)
|
package/changelog.md
CHANGED
package/lib/app/index.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import lpkCreate from './lpk_create.js';
|
|
2
|
+
import lpkCreate, { listProjectTemplateValues, normalizeTemplateType } from './lpk_create.js';
|
|
3
3
|
import { LpkBuild } from './lpk_build.js';
|
|
4
4
|
import { AppDevShell } from './lpk_devshell.js';
|
|
5
|
+
import { projectExecCommand } from './project_exec.js';
|
|
6
|
+
import { projectCpCommand } from './project_cp.js';
|
|
7
|
+
import { projectLogCommand } from './project_log.js';
|
|
8
|
+
import { projectInfoCommand } from './project_info.js';
|
|
9
|
+
import { projectStartCommand } from './project_start.js';
|
|
10
|
+
import { projectDeployCommand } from './project_deploy.js';
|
|
5
11
|
import { LpkInstaller, installConfig } from './lpk_installer.js';
|
|
6
12
|
import logger from 'loglevel';
|
|
7
13
|
import { sleep, checkRsync } from '../utils.js';
|
|
@@ -9,28 +15,110 @@ import { DebugBridge } from '../debug_bridge.js';
|
|
|
9
15
|
import shellApi from '../shellapi.js';
|
|
10
16
|
import { generate } from './lpk_create_generator.js';
|
|
11
17
|
import { t } from '../i18n/index.js';
|
|
18
|
+
import { resolveBuildRemoteFromFile } from '../build_remote.js';
|
|
19
|
+
import { resolveProjectReleaseConfigPath } from './project_runtime.js';
|
|
20
|
+
|
|
21
|
+
async function installLpk(pkgPath, apk) {
|
|
22
|
+
if (!resolveBuildRemoteFromFile(process.cwd())) {
|
|
23
|
+
await shellApi.init();
|
|
24
|
+
}
|
|
25
|
+
installConfig.apk = apk == 'y';
|
|
26
|
+
|
|
27
|
+
const installPath = pkgPath ?? process.cwd();
|
|
28
|
+
const installer = new LpkInstaller();
|
|
29
|
+
await installer.init();
|
|
30
|
+
await installer.install(installPath);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function uninstallLpk(pkgId, deleteData) {
|
|
34
|
+
const buildRemote = resolveBuildRemoteFromFile(process.cwd());
|
|
35
|
+
if (!buildRemote) {
|
|
36
|
+
await shellApi.init();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const bridge = new DebugBridge(process.cwd(), buildRemote);
|
|
40
|
+
await bridge.init();
|
|
41
|
+
await bridge.uninstall(pkgId, deleteData);
|
|
42
|
+
logger.debug(`default lcmd device: ${bridge.boxname} , uninstall the app ${pkgId} finish`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function lpkInstallSubCommand({ desc }) {
|
|
46
|
+
return {
|
|
47
|
+
command: 'install [pkgPath]',
|
|
48
|
+
desc,
|
|
49
|
+
builder: (args) => {
|
|
50
|
+
args.option('apk', {
|
|
51
|
+
describe: t('lzc_cli.lib.app.index.lpk_cmd_index_rags_apk_desc', '是否生成APK(y/n)'),
|
|
52
|
+
type: 'string',
|
|
53
|
+
default: 'y',
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
handler: async ({ pkgPath, apk }) => {
|
|
57
|
+
await installLpk(pkgPath, apk);
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function lpkUninstallSubCommand({ desc }) {
|
|
63
|
+
return {
|
|
64
|
+
command: 'uninstall <pkgId>',
|
|
65
|
+
desc,
|
|
66
|
+
builder: (args) => {
|
|
67
|
+
args.option('delete-data', {
|
|
68
|
+
describe: t('lzc_cli.lib.app.index.lpk_cmd_uninstall_rags_delete_data_desc', '删除应用数据 ⚠️ 警告: 应用数据删除后无法恢复'),
|
|
69
|
+
type: 'boolean',
|
|
70
|
+
default: false,
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
handler: async ({ pkgId, deleteData }) => {
|
|
74
|
+
await uninstallLpk(pkgId, deleteData);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
12
78
|
|
|
13
79
|
export function lpkProjectCommand(program) {
|
|
14
80
|
let subCommands = [
|
|
15
81
|
{
|
|
16
|
-
command: '
|
|
17
|
-
desc: t('lzc_cli.lib.app.index.lpk_cmd_init_desc', '初始化懒猫云应用(提供最基础的模板)'),
|
|
18
|
-
handler: async () => {
|
|
19
|
-
generate('blank', './');
|
|
20
|
-
logger.info(t('lzc_cli.lib.app.index.lpk_cmd_init_success', '应用初始化完成'));
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
command: 'create <name>',
|
|
82
|
+
command: 'create [name]',
|
|
25
83
|
desc: t('lzc_cli.lib.app.index.lpk_cmd_create_desc', '创建懒猫云应用'),
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
84
|
+
builder: (args) => {
|
|
85
|
+
args.option('in-place', {
|
|
86
|
+
describe: 'Initialize current directory with blank template',
|
|
87
|
+
type: 'boolean',
|
|
88
|
+
default: false,
|
|
89
|
+
});
|
|
90
|
+
args.option('t', {
|
|
91
|
+
alias: 'template',
|
|
92
|
+
describe: `Template name (${listProjectTemplateValues().join(', ')})`,
|
|
93
|
+
type: 'string',
|
|
94
|
+
});
|
|
95
|
+
},
|
|
96
|
+
handler: async ({ name, inPlace, template }) => {
|
|
97
|
+
if (name && inPlace) {
|
|
98
|
+
throw new Error('--in-place cannot be used with <name>.');
|
|
99
|
+
}
|
|
100
|
+
const normalizedTemplate = normalizeTemplateType(template);
|
|
101
|
+
if (name) {
|
|
102
|
+
name = String(name);
|
|
103
|
+
await lpkCreate(name, process.cwd(), normalizedTemplate);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (inPlace) {
|
|
108
|
+
if (normalizedTemplate) {
|
|
109
|
+
throw new Error('--template cannot be used with --in-place.');
|
|
110
|
+
}
|
|
111
|
+
generate('blank', './');
|
|
112
|
+
logger.info(t('lzc_cli.lib.app.index.lpk_cmd_init_success', '应用初始化完成'));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
throw new Error('Project name is required unless --in-place is used.');
|
|
29
117
|
},
|
|
30
118
|
},
|
|
31
119
|
{
|
|
32
120
|
command: 'build [context]',
|
|
33
|
-
desc:
|
|
121
|
+
desc: false,
|
|
34
122
|
builder: (args) => {
|
|
35
123
|
args.option('o', {
|
|
36
124
|
alias: 'output',
|
|
@@ -58,9 +146,41 @@ export function lpkProjectCommand(program) {
|
|
|
58
146
|
await lpk.exec();
|
|
59
147
|
},
|
|
60
148
|
},
|
|
149
|
+
{
|
|
150
|
+
command: 'release [context]',
|
|
151
|
+
desc: 'Build release package',
|
|
152
|
+
builder: (args) => {
|
|
153
|
+
args.option('o', {
|
|
154
|
+
alias: 'output',
|
|
155
|
+
describe: t('lzc_cli.lib.app.index.lpk_cmd_build_args_output_desc', '输出文件'),
|
|
156
|
+
type: 'string',
|
|
157
|
+
});
|
|
158
|
+
args.option('f', {
|
|
159
|
+
alias: 'file',
|
|
160
|
+
describe: t('lzc_cli.lib.app.index.lpk_cmd_build_args_file_desc', '指定构建的lzc-build.yml文件'),
|
|
161
|
+
type: 'string',
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
handler: async ({ context, output, file }) => {
|
|
165
|
+
const cwd = context ? path.resolve(context) : process.cwd();
|
|
166
|
+
const configPath = resolveProjectReleaseConfigPath(cwd, file);
|
|
167
|
+
const projectCwd = path.dirname(configPath);
|
|
168
|
+
const configName = path.basename(configPath);
|
|
169
|
+
const lpk = await new LpkBuild(projectCwd, configName).init();
|
|
170
|
+
lpk.onBeforeBuildPackage(async (options) => {
|
|
171
|
+
delete options['devshell'];
|
|
172
|
+
|
|
173
|
+
if (output) {
|
|
174
|
+
options['lpkPath'] = output;
|
|
175
|
+
}
|
|
176
|
+
return options;
|
|
177
|
+
});
|
|
178
|
+
await lpk.exec();
|
|
179
|
+
},
|
|
180
|
+
},
|
|
61
181
|
{
|
|
62
182
|
command: 'devshell [context]',
|
|
63
|
-
desc:
|
|
183
|
+
desc: false,
|
|
64
184
|
builder: (args) => {
|
|
65
185
|
args.option('f', {
|
|
66
186
|
alias: 'force',
|
|
@@ -84,12 +204,16 @@ export function lpkProjectCommand(program) {
|
|
|
84
204
|
});
|
|
85
205
|
},
|
|
86
206
|
handler: async ({ context, force, config, contentdir, apk }) => {
|
|
87
|
-
|
|
207
|
+
logger.warn('project devshell is deprecated. Use "project deploy", "project start", "project exec", "project cp", and "project log" instead.');
|
|
208
|
+
const cwd = context ? path.resolve(context) : process.cwd();
|
|
209
|
+
const buildRemote = resolveBuildRemoteFromFile(cwd, config);
|
|
210
|
+
if (!buildRemote) {
|
|
211
|
+
await shellApi.init();
|
|
212
|
+
}
|
|
88
213
|
// 检测 rsync 满足
|
|
89
214
|
await checkRsync();
|
|
90
215
|
|
|
91
216
|
installConfig.apk = apk == 'y';
|
|
92
|
-
const cwd = context ? path.resolve(context) : process.cwd();
|
|
93
217
|
const lpkBuild = await new LpkBuild(cwd, config).init();
|
|
94
218
|
lpkBuild.onBeforeBuildPackage(async (options) => {
|
|
95
219
|
// devshell 正常情况下,不需要执行 buildscript 和 contentdir 字段
|
|
@@ -109,6 +233,26 @@ export function lpkProjectCommand(program) {
|
|
|
109
233
|
await app.rsyncShell();
|
|
110
234
|
},
|
|
111
235
|
},
|
|
236
|
+
{
|
|
237
|
+
command: 'app',
|
|
238
|
+
desc: false,
|
|
239
|
+
builder: (args) => {
|
|
240
|
+
args.command([
|
|
241
|
+
lpkInstallSubCommand({
|
|
242
|
+
desc: false,
|
|
243
|
+
}),
|
|
244
|
+
lpkUninstallSubCommand({
|
|
245
|
+
desc: false,
|
|
246
|
+
}),
|
|
247
|
+
]);
|
|
248
|
+
},
|
|
249
|
+
},
|
|
250
|
+
projectExecCommand(),
|
|
251
|
+
projectCpCommand(),
|
|
252
|
+
projectLogCommand(),
|
|
253
|
+
projectInfoCommand(),
|
|
254
|
+
projectStartCommand(),
|
|
255
|
+
projectDeployCommand(),
|
|
112
256
|
];
|
|
113
257
|
program.command({
|
|
114
258
|
command: 'project',
|
|
@@ -121,52 +265,22 @@ export function lpkProjectCommand(program) {
|
|
|
121
265
|
|
|
122
266
|
export function lpkAppCommand(program) {
|
|
123
267
|
let subCommands = [
|
|
124
|
-
{
|
|
125
|
-
command: 'install [pkgPath]',
|
|
268
|
+
lpkInstallSubCommand({
|
|
126
269
|
desc: t('lzc_cli.lib.app.index.lpk_cmd_install_desc', '部署应用至设备, pkgPath 可以为路径,或者https://,http://请求地址, 如果不填写,将默认为当前目录下的lpk'),
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
describe: t('lzc_cli.lib.app.index.lpk_cmd_index_rags_apk_desc', '是否生成APK(y/n)'),
|
|
130
|
-
type: 'string',
|
|
131
|
-
default: 'y',
|
|
132
|
-
});
|
|
133
|
-
},
|
|
134
|
-
handler: async ({ pkgPath, apk }) => {
|
|
135
|
-
await shellApi.init();
|
|
136
|
-
installConfig.apk = apk == 'y';
|
|
137
|
-
|
|
138
|
-
pkgPath = pkgPath ?? process.cwd();
|
|
139
|
-
const installer = new LpkInstaller();
|
|
140
|
-
await installer.init();
|
|
141
|
-
await installer.install(pkgPath);
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
command: 'uninstall <pkgId>',
|
|
270
|
+
}),
|
|
271
|
+
lpkUninstallSubCommand({
|
|
146
272
|
desc: t('lzc_cli.lib.app.index.lpk_cmd_uninstall_desc', '从设备中卸载某一个应用'),
|
|
147
|
-
|
|
148
|
-
args.option('delete-data', {
|
|
149
|
-
describe: t('lzc_cli.lib.app.index.lpk_cmd_uninstall_rags_delete_data_desc', '删除应用数据 ⚠️ 警告: 应用数据删除后无法恢复'),
|
|
150
|
-
type: 'boolean',
|
|
151
|
-
default: false,
|
|
152
|
-
});
|
|
153
|
-
},
|
|
154
|
-
handler: async ({ pkgId, deleteData }) => {
|
|
155
|
-
await shellApi.init();
|
|
156
|
-
|
|
157
|
-
const bridge = new DebugBridge();
|
|
158
|
-
await bridge.init();
|
|
159
|
-
await bridge.uninstall(pkgId, deleteData);
|
|
160
|
-
logger.debug(`default lcmd device: ${bridge.boxname} , uninstall the app ${pkgId} finish`);
|
|
161
|
-
},
|
|
162
|
-
},
|
|
273
|
+
}),
|
|
163
274
|
{
|
|
164
275
|
command: 'status <pkgId>',
|
|
165
276
|
desc: t('lzc_cli.lib.app.index.lpk_cmd_status_desc', '获取某一个应用的状态'),
|
|
166
277
|
handler: async ({ pkgId }) => {
|
|
167
|
-
|
|
278
|
+
const buildRemote = resolveBuildRemoteFromFile(process.cwd());
|
|
279
|
+
if (!buildRemote) {
|
|
280
|
+
await shellApi.init();
|
|
281
|
+
}
|
|
168
282
|
|
|
169
|
-
const bridge = new DebugBridge();
|
|
283
|
+
const bridge = new DebugBridge(process.cwd(), buildRemote);
|
|
170
284
|
await bridge.init();
|
|
171
285
|
const status = await bridge.status(pkgId);
|
|
172
286
|
console.log(status);
|
|
@@ -184,7 +298,9 @@ export function lpkAppCommand(program) {
|
|
|
184
298
|
});
|
|
185
299
|
},
|
|
186
300
|
handler: async () => {
|
|
187
|
-
|
|
301
|
+
if (!resolveBuildRemoteFromFile(process.cwd())) {
|
|
302
|
+
await shellApi.init();
|
|
303
|
+
}
|
|
188
304
|
|
|
189
305
|
throw 'not yet realized';
|
|
190
306
|
},
|
|
@@ -192,7 +308,7 @@ export function lpkAppCommand(program) {
|
|
|
192
308
|
];
|
|
193
309
|
program.command({
|
|
194
310
|
command: 'app',
|
|
195
|
-
desc:
|
|
311
|
+
desc: false,
|
|
196
312
|
builder: async (args) => {
|
|
197
313
|
args.command(subCommands);
|
|
198
314
|
},
|
package/lib/app/lpk_build.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import fs from 'node:fs';
|
|
3
|
+
import zlib from 'node:zlib';
|
|
3
4
|
import logger from 'loglevel';
|
|
4
5
|
import {
|
|
5
|
-
loadFromYaml,
|
|
6
6
|
isDirExist,
|
|
7
7
|
isDirSync,
|
|
8
8
|
isFileExist,
|
|
@@ -19,7 +19,11 @@ import spawn from 'cross-spawn';
|
|
|
19
19
|
import { LpkManifest } from './lpk_create.js';
|
|
20
20
|
import archiver from 'archiver';
|
|
21
21
|
import yaml from 'js-yaml';
|
|
22
|
+
import { buildConfiguredImagesToTempDir } from './lpk_build_images.js';
|
|
23
|
+
import { pipeline } from 'node:stream/promises';
|
|
22
24
|
import { t } from '../i18n/index.js';
|
|
25
|
+
import mergeWith from 'lodash.mergewith';
|
|
26
|
+
import { DEFAULT_BUILD_BASE_FILE } from '../build_remote.js';
|
|
23
27
|
|
|
24
28
|
async function archiveFolderTo(appDir, out, format = 'zip') {
|
|
25
29
|
return new Promise(async (resolve, reject) => {
|
|
@@ -46,6 +50,66 @@ async function archiveFolderTo(appDir, out, format = 'zip') {
|
|
|
46
50
|
});
|
|
47
51
|
}
|
|
48
52
|
|
|
53
|
+
async function gzipFileTo(inputPath, outputPath) {
|
|
54
|
+
const source = fs.createReadStream(inputPath);
|
|
55
|
+
const gzip = zlib.createGzip({ mtime: 0 });
|
|
56
|
+
const target = fs.createWriteStream(outputPath);
|
|
57
|
+
await pipeline(source, gzip, target);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function rewriteManifestEmbeddedImages(manifest, resolvedImageByAlias) {
|
|
61
|
+
const EMBED_PREFIX = 'embed:';
|
|
62
|
+
if (!resolvedImageByAlias || typeof resolvedImageByAlias !== 'object') {
|
|
63
|
+
return manifest;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const parseAlias = (rawValue) => {
|
|
67
|
+
const trimmed = String(rawValue ?? '').trim();
|
|
68
|
+
if (!trimmed.startsWith(EMBED_PREFIX)) {
|
|
69
|
+
return '';
|
|
70
|
+
}
|
|
71
|
+
const rest = trimmed.slice(EMBED_PREFIX.length).trim();
|
|
72
|
+
if (!rest) {
|
|
73
|
+
throw new Error(`Invalid image reference "${rawValue}", alias is required after "${EMBED_PREFIX}"`);
|
|
74
|
+
}
|
|
75
|
+
const at = rest.indexOf('@');
|
|
76
|
+
const alias = (at >= 0 ? rest.slice(0, at) : rest).trim();
|
|
77
|
+
if (!alias) {
|
|
78
|
+
throw new Error(`Invalid image reference "${rawValue}", alias is required after "${EMBED_PREFIX}"`);
|
|
79
|
+
}
|
|
80
|
+
return alias;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const walk = (value) => {
|
|
84
|
+
if (typeof value === 'string') {
|
|
85
|
+
const alias = parseAlias(value);
|
|
86
|
+
if (alias) {
|
|
87
|
+
const resolved = resolvedImageByAlias[alias];
|
|
88
|
+
if (!resolved) {
|
|
89
|
+
throw new Error(`Cannot resolve embedded image alias "${alias}" to final image reference`);
|
|
90
|
+
}
|
|
91
|
+
return `${EMBED_PREFIX}${alias}@${resolved}`;
|
|
92
|
+
}
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
if (Array.isArray(value)) {
|
|
96
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
97
|
+
value[index] = walk(value[index]);
|
|
98
|
+
}
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
if (value && typeof value === 'object') {
|
|
102
|
+
for (const [key, item] of Object.entries(value)) {
|
|
103
|
+
value[key] = walk(item);
|
|
104
|
+
}
|
|
105
|
+
return value;
|
|
106
|
+
}
|
|
107
|
+
return value;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
return walk(manifest);
|
|
111
|
+
}
|
|
112
|
+
|
|
49
113
|
async function fetchIconTo(options, cwd, destDir) {
|
|
50
114
|
if (!options['icon']) {
|
|
51
115
|
logger.warn(t('lzc_cli.lib.app.lpk_build.fetch_icon_to_icon_empty_fail', '图标icon 没有指定'));
|
|
@@ -68,7 +132,12 @@ async function fetchIconTo(options, cwd, destDir) {
|
|
|
68
132
|
}
|
|
69
133
|
|
|
70
134
|
if (!isPngWithFile(iconPath)) {
|
|
71
|
-
logger.warn(
|
|
135
|
+
logger.warn(
|
|
136
|
+
t('lzc_cli.lib.app.lpk_build.fetch_icon_to_icon_not_is_png_fail', `图标icon {{ iconPath }} 验证失败(不是一个png格式)`, {
|
|
137
|
+
iconPath,
|
|
138
|
+
interpolation: { escapeValue: false },
|
|
139
|
+
}),
|
|
140
|
+
);
|
|
72
141
|
return;
|
|
73
142
|
} else {
|
|
74
143
|
logger.debug(t('lzc_cli.lib.app.lpk_build.fetch_icon_to_icon_is_png', `图标icon {{ iconPath }} 验证成功(png格式)`, { iconPath, interpolation: { escapeValue: false } }));
|
|
@@ -97,7 +166,12 @@ async function fetchLzcDeployParamTo(options, cwd, destDir) {
|
|
|
97
166
|
}
|
|
98
167
|
|
|
99
168
|
if (!isFileExist(deployParamsPath)) {
|
|
100
|
-
logger.warn(
|
|
169
|
+
logger.warn(
|
|
170
|
+
t('lzc_cli.lib.app.lpk_build.fetch_lzc_deploy_param_to_not_exist', `deploy_params {{ deployParamsPath }} 不存在`, {
|
|
171
|
+
deployParamsPath,
|
|
172
|
+
interpolation: { escapeValue: false },
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
101
175
|
return;
|
|
102
176
|
}
|
|
103
177
|
|
|
@@ -151,12 +225,52 @@ function convenientEnv() {
|
|
|
151
225
|
);
|
|
152
226
|
}
|
|
153
227
|
|
|
228
|
+
function formatBytes(bytes) {
|
|
229
|
+
const value = Number(bytes ?? 0);
|
|
230
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
231
|
+
return '0 B';
|
|
232
|
+
}
|
|
233
|
+
const units = ['B', 'KiB', 'MiB', 'GiB', 'TiB'];
|
|
234
|
+
let size = value;
|
|
235
|
+
let unitIndex = 0;
|
|
236
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
237
|
+
size /= 1024;
|
|
238
|
+
unitIndex += 1;
|
|
239
|
+
}
|
|
240
|
+
const digits = unitIndex === 0 ? 0 : 2;
|
|
241
|
+
return `${size.toFixed(digits)} ${units[unitIndex]}`;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function mergeBuildOptions(baseOptions, topOptions) {
|
|
245
|
+
return mergeWith({}, baseOptions ?? {}, topOptions ?? {}, (objValue, srcValue) => {
|
|
246
|
+
if (Array.isArray(srcValue)) {
|
|
247
|
+
return srcValue;
|
|
248
|
+
}
|
|
249
|
+
return undefined;
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function loadYamlIfExists(filePath) {
|
|
254
|
+
if (!filePath || !isFileExist(filePath)) {
|
|
255
|
+
return {};
|
|
256
|
+
}
|
|
257
|
+
return yaml.load(fs.readFileSync(filePath, 'utf8')) ?? {};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function loadTemplatedYamlIfExists(filePath, env) {
|
|
261
|
+
if (!filePath || !isFileExist(filePath)) {
|
|
262
|
+
return {};
|
|
263
|
+
}
|
|
264
|
+
return yaml.load(await envTemplateFile(filePath, env)) ?? {};
|
|
265
|
+
}
|
|
266
|
+
|
|
154
267
|
export class LpkBuild {
|
|
155
268
|
constructor(cwd, buildConfigFile) {
|
|
156
269
|
this.pwd = cwd ?? process.cwd();
|
|
157
270
|
|
|
158
271
|
this.optionsFilePath = path.join(this.pwd, buildConfigFile);
|
|
159
|
-
this.
|
|
272
|
+
this.optionsBaseFilePath = path.join(path.dirname(this.optionsFilePath), DEFAULT_BUILD_BASE_FILE);
|
|
273
|
+
this.options = mergeBuildOptions(loadYamlIfExists(this.optionsBaseFilePath), loadYamlIfExists(this.optionsFilePath));
|
|
160
274
|
|
|
161
275
|
this.manifestFilePath = this.options['manifest'] ? path.join(this.pwd, this.options['manifest']) : path.join(this.pwd, 'lzc-manifest.yml');
|
|
162
276
|
this.manifest = null;
|
|
@@ -171,7 +285,10 @@ export class LpkBuild {
|
|
|
171
285
|
async init() {
|
|
172
286
|
const manifest = await this.getManifest();
|
|
173
287
|
const primitive = convenientEnv();
|
|
174
|
-
|
|
288
|
+
const env = Object.assign({}, primitive, manifest);
|
|
289
|
+
const baseOptions = await loadTemplatedYamlIfExists(this.optionsBaseFilePath, env);
|
|
290
|
+
const topOptions = await loadTemplatedYamlIfExists(this.optionsFilePath, env);
|
|
291
|
+
this.options = mergeBuildOptions(baseOptions, topOptions);
|
|
175
292
|
return this;
|
|
176
293
|
}
|
|
177
294
|
|
|
@@ -245,9 +362,16 @@ export class LpkBuild {
|
|
|
245
362
|
|
|
246
363
|
// 输出路径
|
|
247
364
|
let packName = this.options['lpkPath'];
|
|
248
|
-
|
|
249
|
-
if (!packName
|
|
250
|
-
|
|
365
|
+
let pkgout = '';
|
|
366
|
+
if (!packName) {
|
|
367
|
+
const rawPkgout = this.options['pkgout'];
|
|
368
|
+
if (rawPkgout !== undefined && rawPkgout !== null && typeof rawPkgout !== 'string') {
|
|
369
|
+
throw t('lzc_cli.lib.app.lpk_build.exec_pkgout_invalid_type', 'pkgout must be a string when specified');
|
|
370
|
+
}
|
|
371
|
+
pkgout = path.resolve(this.pwd, typeof rawPkgout === 'string' && rawPkgout.trim() !== '' ? rawPkgout : './');
|
|
372
|
+
if (!isDirExist(pkgout)) {
|
|
373
|
+
throw t('lzc_cli.lib.app.lpk_build.exec_pkgout_not_exist', `{{ pkgout }} 不存在`, { pkgout, interpolation: { escapeValue: false } });
|
|
374
|
+
}
|
|
251
375
|
}
|
|
252
376
|
|
|
253
377
|
const tempDir = fs.mkdtempSync('.lzc-cli-build');
|
|
@@ -291,7 +415,10 @@ export class LpkBuild {
|
|
|
291
415
|
} else if (isFileExist(browserExtension)) {
|
|
292
416
|
fs.copyFileSync(browserExtension, path.join(tempDir, 'extension.zip'));
|
|
293
417
|
} else {
|
|
294
|
-
throw t('lzc_cli.lib.app.lpk_build.exec_browser_extension_not_exist', `{{ browserExtension }} 不存在`, {
|
|
418
|
+
throw t('lzc_cli.lib.app.lpk_build.exec_browser_extension_not_exist', `{{ browserExtension }} 不存在`, {
|
|
419
|
+
browserExtension,
|
|
420
|
+
interpolation: { escapeValue: false },
|
|
421
|
+
});
|
|
295
422
|
}
|
|
296
423
|
}
|
|
297
424
|
|
|
@@ -300,7 +427,7 @@ export class LpkBuild {
|
|
|
300
427
|
if (!isDirExist(aiPodService)) {
|
|
301
428
|
throw t('lzc_cli.lib.app.lpk_build.exec_ai_pos_service_not_exist', `{{ aiPodService }} 不存在`, {
|
|
302
429
|
aiPodService,
|
|
303
|
-
interpolation: { escapeValue: false }
|
|
430
|
+
interpolation: { escapeValue: false },
|
|
304
431
|
});
|
|
305
432
|
}
|
|
306
433
|
fs.cpSync(aiPodService, path.join(tempDir, 'ai-pod-service'), {
|
|
@@ -319,7 +446,43 @@ export class LpkBuild {
|
|
|
319
446
|
}, manifest);
|
|
320
447
|
}
|
|
321
448
|
|
|
322
|
-
if (
|
|
449
|
+
if (Object.prototype.hasOwnProperty.call(this.options, 'embed_images')) {
|
|
450
|
+
throw new Error('embed_images is removed, please use lzc-build.yml images and manifest embed:alias');
|
|
451
|
+
}
|
|
452
|
+
if (Object.prototype.hasOwnProperty.call(this.options, 'embed_all_images')) {
|
|
453
|
+
throw new Error('embed_all_images is removed');
|
|
454
|
+
}
|
|
455
|
+
if (Object.prototype.hasOwnProperty.call(this.options, 'upstream_registry')) {
|
|
456
|
+
throw new Error('upstream_registry is renamed to upstream_match');
|
|
457
|
+
}
|
|
458
|
+
if (Object.prototype.hasOwnProperty.call(this.options, 'upstream_match') || Object.prototype.hasOwnProperty.call(this.options, 'upstream-match')) {
|
|
459
|
+
throw new Error('upstream_match is moved to lzc-build.yml images.<alias>.upstream-match');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const hasImagesConfig = !!this.options['images'];
|
|
463
|
+
|
|
464
|
+
let useTarPackage = false;
|
|
465
|
+
let embeddedImageSummary = null;
|
|
466
|
+
if (hasImagesConfig) {
|
|
467
|
+
const buildResult = await buildConfiguredImagesToTempDir(this.options['images'], manifest, this.pwd, tempDir, {
|
|
468
|
+
remote: this.options['remote'],
|
|
469
|
+
});
|
|
470
|
+
if (buildResult.imageCount > 0) {
|
|
471
|
+
useTarPackage = true;
|
|
472
|
+
embeddedImageSummary = buildResult;
|
|
473
|
+
manifest = rewriteManifestEmbeddedImages(manifest, buildResult.resolvedImageByAlias);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
if (useTarPackage) {
|
|
477
|
+
const contentTar = path.join(tempDir, 'content.tar');
|
|
478
|
+
const contentTarGz = path.join(tempDir, 'content.tar.gz');
|
|
479
|
+
if (isFileExist(contentTar)) {
|
|
480
|
+
await gzipFileTo(contentTar, contentTarGz);
|
|
481
|
+
fs.rmSync(contentTar, { force: true });
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if (process.env.LZC_MANIFEST_TEMPLATE && !useTarPackage) {
|
|
323
486
|
logger.debug('copy origin manifest\n', this.manifestFilePath);
|
|
324
487
|
fs.copyFileSync(this.manifestFilePath, path.join(tempDir, 'manifest.yml'));
|
|
325
488
|
} else {
|
|
@@ -346,14 +509,26 @@ export class LpkBuild {
|
|
|
346
509
|
}
|
|
347
510
|
|
|
348
511
|
if (!packName) {
|
|
349
|
-
|
|
512
|
+
const ext = '.lpk';
|
|
513
|
+
packName = path.resolve(pkgout, `${manifest.package}-v${manifest.version}${ext}`);
|
|
350
514
|
}
|
|
351
515
|
|
|
352
|
-
const lpkPath = await archiveFolderTo(tempDir, packName);
|
|
353
|
-
logger.info(
|
|
354
|
-
path
|
|
355
|
-
|
|
356
|
-
|
|
516
|
+
const lpkPath = await archiveFolderTo(tempDir, packName, useTarPackage ? 'tar' : 'zip');
|
|
517
|
+
logger.info(
|
|
518
|
+
`${t('lzc_cli.lib.app.lpk_build.exec_output_lpk_path', '输出lpk包 {{ path }}', {
|
|
519
|
+
path: lpkPath.path,
|
|
520
|
+
interpolation: { escapeValue: false }, // https://www.i18next.com/translation-function/interpolation#unescape
|
|
521
|
+
})}`,
|
|
522
|
+
);
|
|
523
|
+
if (embeddedImageSummary && embeddedImageSummary.imageCount > 0) {
|
|
524
|
+
logger.info('Embedded image upstream summary:');
|
|
525
|
+
for (const [alias, upstream] of Object.entries(embeddedImageSummary.upstreamByAlias ?? {})) {
|
|
526
|
+
logger.info(`- ${alias}: ${upstream || '(none, full embed)'}`);
|
|
527
|
+
}
|
|
528
|
+
logger.info(
|
|
529
|
+
`Embedded image layer size: ${formatBytes(embeddedImageSummary.embeddedLayerBytes)} (${embeddedImageSummary.embeddedLayerBytes} bytes, ${embeddedImageSummary.embeddedLayerCount} unique layers)`,
|
|
530
|
+
);
|
|
531
|
+
}
|
|
357
532
|
|
|
358
533
|
return lpkPath.path;
|
|
359
534
|
} finally {
|