@lazycatcloud/lzc-cli 1.3.13 → 2.0.0-pre.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -5
- package/changelog.md +16 -0
- package/lib/app/index.js +174 -58
- package/lib/app/lpk_build.js +197 -18
- package/lib/app/lpk_build_images.js +728 -0
- package/lib/app/lpk_create.js +96 -23
- package/lib/app/lpk_create_generator.js +150 -12
- package/lib/app/lpk_devshell.js +35 -21
- package/lib/app/lpk_embed_images.js +257 -0
- package/lib/app/lpk_installer.js +15 -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/appstore/index.js +56 -16
- package/lib/appstore/publish.js +16 -13
- package/lib/box/index.js +103 -6
- package/lib/box/ssh_remote.js +259 -0
- package/lib/build_remote.js +22 -0
- package/lib/config/index.js +4 -3
- package/lib/debug_bridge.js +837 -44
- package/lib/docker/index.js +30 -10
- package/lib/i18n/index.js +1 -0
- package/lib/i18n/locales/en/translation.json +263 -250
- package/lib/i18n/locales/zh/translation.json +57 -44
- package/lib/lpk/core.js +487 -0
- package/lib/lpk/index.js +210 -0
- package/lib/shellapi.js +5 -5
- package/lib/sig/core.js +254 -0
- package/lib/sig/index.js +88 -0
- package/lib/utils.js +17 -12
- package/package.json +4 -3
- 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/lib/shellapi.js
CHANGED
|
@@ -23,7 +23,7 @@ function getShellAPIConfigDir() {
|
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
class ShellApi {
|
|
26
|
-
constructor() {}
|
|
26
|
+
constructor() { }
|
|
27
27
|
|
|
28
28
|
async init() {
|
|
29
29
|
// 检查当前 shell 环境上下文是否配置 HTTP_PROXY
|
|
@@ -40,15 +40,15 @@ class ShellApi {
|
|
|
40
40
|
'lzc_cli.lib.shellapi.init.conf_file_not_exist_warn',
|
|
41
41
|
`WARN:: 读取不到以下路径中的文件,无法连接客户端自动判读盒子名称和盒子用户,
|
|
42
42
|
|
|
43
|
-
{{addrFile}} 盒子的连接地址
|
|
44
|
-
{{credFile}} 盒子的连接凭证
|
|
43
|
+
{{ addrFile }} 盒子的连接地址
|
|
44
|
+
{{ credFile }} 盒子的连接凭证
|
|
45
45
|
|
|
46
46
|
但可使用环境变量指定:
|
|
47
47
|
- BOX_NAME=foo 指定盒子的名称
|
|
48
48
|
- BOX_UID=bar 指定盒子的用户
|
|
49
49
|
|
|
50
50
|
NOTE:在指定环境变量的模式下,有些接口依旧不能访问,为了更好的开发体验,建议尽量在同主机的环境下启动客户端`,
|
|
51
|
-
{ addrFile, credFile },
|
|
51
|
+
{ addrFile, credFile, interpolation: { escapeValue: false } },
|
|
52
52
|
),
|
|
53
53
|
);
|
|
54
54
|
const md = new grpc.Metadata();
|
|
@@ -134,7 +134,7 @@ NOTE:在指定环境变量的模式下,有些接口依旧不能访问,为
|
|
|
134
134
|
const boxes = await this.boxList();
|
|
135
135
|
const box = boxes.find((b) => b.box_name === boxname);
|
|
136
136
|
if (!box) {
|
|
137
|
-
throw t('lzc_cli.lib.shellapi.set_default_box.box_not_exists_fail', '{{boxname}} 盒子不存在', { boxname });
|
|
137
|
+
throw t('lzc_cli.lib.shellapi.set_default_box.box_not_exists_fail', '{{ boxname }} 盒子不存在', { boxname });
|
|
138
138
|
}
|
|
139
139
|
return new Promise((resolve, reject) => {
|
|
140
140
|
this.client.modifyBoxConfig({ id: box.id, name: box.box_name, set_as_default_box: true }, this.metadata, function (err) {
|
package/lib/sig/core.js
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import archiver from 'archiver';
|
|
6
|
+
import AdmZip from 'adm-zip';
|
|
7
|
+
import * as tar from 'tar';
|
|
8
|
+
import yaml from 'js-yaml';
|
|
9
|
+
import { t } from '../i18n/index.js';
|
|
10
|
+
|
|
11
|
+
function toPosixPath(filePath) {
|
|
12
|
+
return filePath.split(path.sep).join('/');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function detectPackageFormat(pkgPath) {
|
|
16
|
+
const ext = path.basename(pkgPath).toLowerCase();
|
|
17
|
+
if (ext.endsWith('.lpk.tar') || ext.endsWith('.tar')) {
|
|
18
|
+
return 'tar';
|
|
19
|
+
}
|
|
20
|
+
const fd = fs.openSync(pkgPath, 'r');
|
|
21
|
+
try {
|
|
22
|
+
const header = Buffer.alloc(4);
|
|
23
|
+
fs.readSync(fd, header, 0, 4, 0);
|
|
24
|
+
if (header[0] === 0x50 && header[1] === 0x4b) {
|
|
25
|
+
return 'zip';
|
|
26
|
+
}
|
|
27
|
+
} finally {
|
|
28
|
+
fs.closeSync(fd);
|
|
29
|
+
}
|
|
30
|
+
return 'tar';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function extractPackage(pkgPath, format, destDir) {
|
|
34
|
+
if (format === 'zip') {
|
|
35
|
+
const zip = new AdmZip(pkgPath);
|
|
36
|
+
zip.extractAllTo(destDir, true);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
await tar.x({
|
|
40
|
+
file: pkgPath,
|
|
41
|
+
cwd: destDir,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function packAsZip(srcDir, outPath) {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
const output = fs.createWriteStream(outPath);
|
|
48
|
+
const archive = archiver('zip');
|
|
49
|
+
archive.on('error', reject);
|
|
50
|
+
output.on('error', reject);
|
|
51
|
+
output.on('close', resolve);
|
|
52
|
+
archive.pipe(output);
|
|
53
|
+
archive.directory(srcDir, false);
|
|
54
|
+
archive.finalize();
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function packAsTar(srcDir, outPath) {
|
|
59
|
+
const entries = fs.readdirSync(srcDir).sort();
|
|
60
|
+
await tar.c(
|
|
61
|
+
{
|
|
62
|
+
cwd: srcDir,
|
|
63
|
+
file: outPath,
|
|
64
|
+
portable: true,
|
|
65
|
+
},
|
|
66
|
+
entries,
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function packPackage(srcDir, format, outPath) {
|
|
71
|
+
if (format === 'zip') {
|
|
72
|
+
return packAsZip(srcDir, outPath);
|
|
73
|
+
}
|
|
74
|
+
return packAsTar(srcDir, outPath);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function walkFilesRecursive(dir, baseDir = dir) {
|
|
78
|
+
const result = [];
|
|
79
|
+
if (!fs.existsSync(dir)) {
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
83
|
+
const absPath = path.join(dir, entry);
|
|
84
|
+
const stat = fs.statSync(absPath);
|
|
85
|
+
if (stat.isDirectory()) {
|
|
86
|
+
result.push(...walkFilesRecursive(absPath, baseDir));
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (stat.isFile()) {
|
|
90
|
+
result.push(toPosixPath(path.relative(baseDir, absPath)));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function sha256File(filePath) {
|
|
97
|
+
return new Promise((resolve, reject) => {
|
|
98
|
+
const hash = crypto.createHash('sha256');
|
|
99
|
+
let size = 0;
|
|
100
|
+
const stream = fs.createReadStream(filePath);
|
|
101
|
+
stream.on('data', (chunk) => {
|
|
102
|
+
size += chunk.length;
|
|
103
|
+
hash.update(chunk);
|
|
104
|
+
});
|
|
105
|
+
stream.on('error', reject);
|
|
106
|
+
stream.on('end', () => {
|
|
107
|
+
resolve({
|
|
108
|
+
digest: hash.digest('hex'),
|
|
109
|
+
size,
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function loadManifestInfo(workDir) {
|
|
116
|
+
const manifestPath = path.join(workDir, 'manifest.yml');
|
|
117
|
+
if (!fs.existsSync(manifestPath)) {
|
|
118
|
+
return { appid: '', version: '' };
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
const manifest = yaml.load(fs.readFileSync(manifestPath, 'utf-8'));
|
|
122
|
+
return {
|
|
123
|
+
appid: manifest?.package ?? '',
|
|
124
|
+
version: manifest?.version ? String(manifest.version) : '',
|
|
125
|
+
};
|
|
126
|
+
} catch {
|
|
127
|
+
return { appid: '', version: '' };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function hasMetaSignData(workDir) {
|
|
132
|
+
const releaseLock = path.join(workDir, 'META', 'release.lock');
|
|
133
|
+
const sigDir = path.join(workDir, 'META', 'signatures');
|
|
134
|
+
if (fs.existsSync(releaseLock)) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
if (!fs.existsSync(sigDir) || !fs.statSync(sigDir).isDirectory()) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
return fs.readdirSync(sigDir).some((f) => f.endsWith('.sig'));
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function writeMetaSignatureFiles(workDir, privateKeyPath, publicKeyPath, keyId) {
|
|
144
|
+
const filePaths = walkFilesRecursive(workDir)
|
|
145
|
+
.filter((p) => !p.startsWith('META/'))
|
|
146
|
+
.sort();
|
|
147
|
+
const objects = [];
|
|
148
|
+
for (const relPath of filePaths) {
|
|
149
|
+
const absPath = path.join(workDir, relPath);
|
|
150
|
+
const { digest, size } = await sha256File(absPath);
|
|
151
|
+
objects.push({
|
|
152
|
+
path: relPath,
|
|
153
|
+
digest: `sha256:${digest}`,
|
|
154
|
+
size,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const manifestInfo = loadManifestInfo(workDir);
|
|
159
|
+
const releaseLock = {
|
|
160
|
+
schema: 'lazycat.lpk.release-lock/v1',
|
|
161
|
+
appid: manifestInfo.appid,
|
|
162
|
+
version: manifestInfo.version,
|
|
163
|
+
objects,
|
|
164
|
+
};
|
|
165
|
+
const releaseLockContent = Buffer.from(JSON.stringify(releaseLock, null, 2));
|
|
166
|
+
|
|
167
|
+
const privateKeyPem = fs.readFileSync(privateKeyPath, 'utf-8');
|
|
168
|
+
const publicKeyPem = fs.readFileSync(publicKeyPath, 'utf-8');
|
|
169
|
+
const privateKey = crypto.createPrivateKey(privateKeyPem);
|
|
170
|
+
const signature = crypto.sign(null, releaseLockContent, privateKey).toString('base64');
|
|
171
|
+
|
|
172
|
+
const metaDir = path.join(workDir, 'META');
|
|
173
|
+
const keyDir = path.join(metaDir, 'keys');
|
|
174
|
+
const sigDir = path.join(metaDir, 'signatures');
|
|
175
|
+
fs.mkdirSync(keyDir, { recursive: true });
|
|
176
|
+
fs.mkdirSync(sigDir, { recursive: true });
|
|
177
|
+
fs.writeFileSync(path.join(metaDir, 'release.lock'), releaseLockContent);
|
|
178
|
+
fs.writeFileSync(path.join(keyDir, `${keyId}.pub`), publicKeyPem);
|
|
179
|
+
fs.writeFileSync(
|
|
180
|
+
path.join(sigDir, `${keyId}.sig`),
|
|
181
|
+
JSON.stringify(
|
|
182
|
+
{
|
|
183
|
+
schema: 'lazycat.lpk.signature/v1',
|
|
184
|
+
algorithm: 'ed25519',
|
|
185
|
+
key_id: keyId,
|
|
186
|
+
signed_file: 'META/release.lock',
|
|
187
|
+
signature,
|
|
188
|
+
},
|
|
189
|
+
null,
|
|
190
|
+
2,
|
|
191
|
+
),
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function signPackageFile(pkgPath, options = {}) {
|
|
196
|
+
const { privateKey, publicKey, keyId = 'dev', output, resign = false } = options;
|
|
197
|
+
|
|
198
|
+
if (!pkgPath || !fs.existsSync(pkgPath)) {
|
|
199
|
+
throw new Error(t('lzc_cli.lib.sig.index.pkg_not_found_fail', `Package not found: ${pkgPath}`));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const resolvedPrivateKey = privateKey ? path.resolve(privateKey) : path.resolve('.sig', `${keyId}.ed25519.private.pem`);
|
|
203
|
+
const resolvedPublicKey = publicKey ? path.resolve(publicKey) : path.resolve('.sig', `${keyId}.ed25519.public.pem`);
|
|
204
|
+
if (!fs.existsSync(resolvedPrivateKey)) {
|
|
205
|
+
throw new Error(t('lzc_cli.lib.sig.index.private_key_not_found_fail', `Private key not found: ${resolvedPrivateKey}`));
|
|
206
|
+
}
|
|
207
|
+
if (!fs.existsSync(resolvedPublicKey)) {
|
|
208
|
+
throw new Error(t('lzc_cli.lib.sig.index.public_key_not_found_fail', `Public key not found: ${resolvedPublicKey}`));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const format = detectPackageFormat(pkgPath);
|
|
212
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'lzc-cli-sign-'));
|
|
213
|
+
const outTemp = path.join(tempDir, format === 'zip' ? 'signed.lpk' : 'signed.lpk.tar');
|
|
214
|
+
const workDir = path.join(tempDir, 'work');
|
|
215
|
+
fs.mkdirSync(workDir, { recursive: true });
|
|
216
|
+
try {
|
|
217
|
+
await extractPackage(pkgPath, format, workDir);
|
|
218
|
+
if (resign) {
|
|
219
|
+
fs.rmSync(path.join(workDir, 'META'), { recursive: true, force: true });
|
|
220
|
+
} else if (hasMetaSignData(workDir)) {
|
|
221
|
+
throw new Error(t('lzc_cli.lib.sig.index.package_already_signed_fail', 'Package already signed, use `lzc-cli lpk sign --resign`'));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
await writeMetaSignatureFiles(workDir, resolvedPrivateKey, resolvedPublicKey, keyId);
|
|
225
|
+
await packPackage(workDir, format, outTemp);
|
|
226
|
+
|
|
227
|
+
const finalPath = output ? path.resolve(output) : path.resolve(pkgPath);
|
|
228
|
+
fs.mkdirSync(path.dirname(finalPath), { recursive: true });
|
|
229
|
+
fs.copyFileSync(outTemp, finalPath);
|
|
230
|
+
return finalPath;
|
|
231
|
+
} finally {
|
|
232
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function generateKeyPair(outputDir, name, force = false) {
|
|
237
|
+
const targetDir = path.resolve(outputDir || process.cwd());
|
|
238
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
239
|
+
|
|
240
|
+
const privateKeyPath = path.join(targetDir, `${name}.ed25519.private.pem`);
|
|
241
|
+
const publicKeyPath = path.join(targetDir, `${name}.ed25519.public.pem`);
|
|
242
|
+
if (!force && (fs.existsSync(privateKeyPath) || fs.existsSync(publicKeyPath))) {
|
|
243
|
+
throw new Error(t('lzc_cli.lib.sig.index.key_exists_fail', `Key files already exist: ${privateKeyPath}, ${publicKeyPath}`));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync('ed25519');
|
|
247
|
+
const privateKeyPem = privateKey.export({ type: 'pkcs8', format: 'pem' });
|
|
248
|
+
const publicKeyPem = publicKey.export({ type: 'spki', format: 'pem' });
|
|
249
|
+
|
|
250
|
+
fs.writeFileSync(privateKeyPath, privateKeyPem);
|
|
251
|
+
fs.chmodSync(privateKeyPath, 0o600);
|
|
252
|
+
fs.writeFileSync(publicKeyPath, publicKeyPem);
|
|
253
|
+
return { privateKeyPath, publicKeyPath };
|
|
254
|
+
}
|
package/lib/sig/index.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import logger from 'loglevel';
|
|
2
|
+
import { t } from '../i18n/index.js';
|
|
3
|
+
import { generateKeyPair, signPackageFile } from './core.js';
|
|
4
|
+
|
|
5
|
+
function signBuilder(args) {
|
|
6
|
+
args.option('private-key', {
|
|
7
|
+
alias: 'k',
|
|
8
|
+
describe: t('lzc_cli.lib.sig.index.sign_private_key_desc', 'Private key path'),
|
|
9
|
+
type: 'string',
|
|
10
|
+
});
|
|
11
|
+
args.option('public-key', {
|
|
12
|
+
alias: 'p',
|
|
13
|
+
describe: t('lzc_cli.lib.sig.index.sign_public_key_desc', 'Public key path'),
|
|
14
|
+
type: 'string',
|
|
15
|
+
});
|
|
16
|
+
args.option('key-id', {
|
|
17
|
+
describe: t('lzc_cli.lib.sig.index.sign_key_id_desc', 'Signature key id'),
|
|
18
|
+
type: 'string',
|
|
19
|
+
default: 'dev',
|
|
20
|
+
});
|
|
21
|
+
args.option('o', {
|
|
22
|
+
alias: 'output',
|
|
23
|
+
describe: t('lzc_cli.lib.sig.index.sign_output_desc', 'Output package path'),
|
|
24
|
+
type: 'string',
|
|
25
|
+
});
|
|
26
|
+
args.option('resign', {
|
|
27
|
+
describe: t('lzc_cli.lib.sig.index.sign_resign_desc', 'Force replace existing META signature'),
|
|
28
|
+
type: 'boolean',
|
|
29
|
+
default: false,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function signHandler({ pkgPath, privateKey, publicKey, keyId, output, resign }) {
|
|
34
|
+
const out = await signPackageFile(pkgPath, {
|
|
35
|
+
privateKey,
|
|
36
|
+
publicKey,
|
|
37
|
+
keyId,
|
|
38
|
+
output,
|
|
39
|
+
resign: !!resign,
|
|
40
|
+
});
|
|
41
|
+
logger.info(`${resign ? 're-signed' : 'signed'} package: ${out}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function sigCommand(program) {
|
|
45
|
+
const subCommands = [
|
|
46
|
+
{
|
|
47
|
+
command: '$0 <pkgPath>',
|
|
48
|
+
desc: t('lzc_cli.lib.sig.index.sign_desc', 'Sign lpk package'),
|
|
49
|
+
builder: signBuilder,
|
|
50
|
+
handler: signHandler,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
command: 'gen-key [outputDir]',
|
|
54
|
+
desc: t('lzc_cli.lib.sig.index.gen_key_desc', 'Generate developer key pair'),
|
|
55
|
+
builder: (args) => {
|
|
56
|
+
args.option('name', {
|
|
57
|
+
describe: t('lzc_cli.lib.sig.index.gen_key_name_desc', 'Key name'),
|
|
58
|
+
type: 'string',
|
|
59
|
+
default: 'dev',
|
|
60
|
+
});
|
|
61
|
+
args.option('force', {
|
|
62
|
+
describe: t('lzc_cli.lib.sig.index.gen_key_force_desc', 'Overwrite existing key files'),
|
|
63
|
+
type: 'boolean',
|
|
64
|
+
default: false,
|
|
65
|
+
});
|
|
66
|
+
},
|
|
67
|
+
handler: async ({ outputDir, name, force }) => {
|
|
68
|
+
const { privateKeyPath, publicKeyPath } = generateKeyPair(outputDir || '.sig', name, force);
|
|
69
|
+
logger.info(`private key: ${privateKeyPath}`);
|
|
70
|
+
logger.info(`public key: ${publicKeyPath}`);
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
command: 'sign <pkgPath>',
|
|
75
|
+
desc: t('lzc_cli.lib.sig.index.sign_desc', 'Sign lpk package'),
|
|
76
|
+
builder: signBuilder,
|
|
77
|
+
handler: signHandler,
|
|
78
|
+
},
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
program.command({
|
|
82
|
+
command: 'sig',
|
|
83
|
+
desc: t('lzc_cli.lib.sig.index.sig_cmd_desc', 'LPK signature tools'),
|
|
84
|
+
builder: (args) => {
|
|
85
|
+
args.command(subCommands);
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
}
|
package/lib/utils.js
CHANGED
|
@@ -30,7 +30,7 @@ export function checkNodejsVersion() {
|
|
|
30
30
|
const requiredVersion = pkgInfo.engines.node;
|
|
31
31
|
if (!semver.satisfies(process.version, requiredVersion)) {
|
|
32
32
|
logger.error(
|
|
33
|
-
t('lzc_cli.lib.utils.check_nodejs_version_no_match_tips', `检测到您当前的 nodejs 版本 {{version}} 不匹配,lzc-cli 最低要求 nodejs 版本为 {{requiredVersion}}, 请升级`, {
|
|
33
|
+
t('lzc_cli.lib.utils.check_nodejs_version_no_match_tips', `检测到您当前的 nodejs 版本 {{ version }} 不匹配,lzc-cli 最低要求 nodejs 版本为 {{ requiredVersion }}, 请升级`, {
|
|
34
34
|
version: process.version,
|
|
35
35
|
requiredVersion,
|
|
36
36
|
}),
|
|
@@ -55,19 +55,21 @@ export async function getLatestVersion(controller, pkgName = pkgInfo.name) {
|
|
|
55
55
|
const latestVersion = content['tags']['latest'];
|
|
56
56
|
if (semver.lt(pkgInfo.version, latestVersion)) {
|
|
57
57
|
logger.warn(
|
|
58
|
-
t('lzc_cli.lib.utils.get_latest_version_tips', `检测到 {{pkgName}} 最新版本为 {{latestVersion}},使用 'npm i -g {{pkgName}}@{{latestVersion}}' 升级到最新版本!`, {
|
|
58
|
+
t('lzc_cli.lib.utils.get_latest_version_tips', `检测到 {{ pkgName }} 最新版本为 {{ latestVersion }},使用 'npm i -g {{ pkgName }}@{{ latestVersion }}' 升级到最新版本!`, {
|
|
59
59
|
pkgName,
|
|
60
60
|
latestVersion,
|
|
61
|
+
interpolation: { escapeValue: false }
|
|
61
62
|
}),
|
|
62
63
|
);
|
|
63
64
|
} else {
|
|
64
|
-
logger.debug(t('lzc_cli.lib.utils.get_latest_version_success_tips', `已经在最新版本: {{latestVersion}}`, { latestVersion }));
|
|
65
|
+
logger.debug(t('lzc_cli.lib.utils.get_latest_version_success_tips', `已经在最新版本: {{ latestVersion }}`, { latestVersion }));
|
|
65
66
|
}
|
|
66
67
|
} catch (err) {
|
|
67
68
|
logger.debug(
|
|
68
|
-
t('lzc_cli.lib.utils.get_latest_version_fail_tips', `请求 {{url}} 失败, error: {{err}}`, {
|
|
69
|
+
t('lzc_cli.lib.utils.get_latest_version_fail_tips', `请求 {{ url }} 失败, error: {{ err }}`, {
|
|
69
70
|
url,
|
|
70
71
|
err,
|
|
72
|
+
interpolation: { escapeValue: false }
|
|
71
73
|
}),
|
|
72
74
|
);
|
|
73
75
|
}
|
|
@@ -139,9 +141,10 @@ export function ensureDirectoryExists(filePath, isRenameOrClear) {
|
|
|
139
141
|
if (err.code === 'ENOENT') {
|
|
140
142
|
fs.mkdirSync(filePath, { recursive: true });
|
|
141
143
|
} else {
|
|
142
|
-
throw t('lzc_cli.lib.utils.ensure_directory_exists_fail', `创建文件夹{{filePath}}错误: {{err}}`, {
|
|
144
|
+
throw t('lzc_cli.lib.utils.ensure_directory_exists_fail', `创建文件夹{{ filePath }}错误: {{ err }}`, {
|
|
143
145
|
filePath,
|
|
144
146
|
err,
|
|
147
|
+
interpolation: { escapeValue: false }
|
|
145
148
|
});
|
|
146
149
|
}
|
|
147
150
|
}
|
|
@@ -361,7 +364,7 @@ export async function md5File(filepath) {
|
|
|
361
364
|
}
|
|
362
365
|
|
|
363
366
|
export class Downloader {
|
|
364
|
-
constructor() {}
|
|
367
|
+
constructor() { }
|
|
365
368
|
|
|
366
369
|
showDownloadingProgress(received, total) {
|
|
367
370
|
let percentage = ((received * 100) / total).toFixed(2);
|
|
@@ -423,12 +426,14 @@ export function isUserApp(manifest) {
|
|
|
423
426
|
return !!manifest['application']['user_app'];
|
|
424
427
|
}
|
|
425
428
|
|
|
426
|
-
export async function tarContentDir(from, to, cwd = './') {
|
|
429
|
+
export async function tarContentDir(from, to, cwd = './', options = {}) {
|
|
430
|
+
const gzipEnabled = !!options.gzip;
|
|
427
431
|
return new Promise((resolve, reject) => {
|
|
428
432
|
const dest = fs.createWriteStream(to);
|
|
429
433
|
tar.c(
|
|
430
434
|
{
|
|
431
435
|
cwd: cwd,
|
|
436
|
+
gzip: gzipEnabled,
|
|
432
437
|
filter: (filePath) => {
|
|
433
438
|
logger.debug(`tar gz ${filePath}`);
|
|
434
439
|
return true;
|
|
@@ -515,7 +520,7 @@ export async function resolveDomain(domain, quiet = false) {
|
|
|
515
520
|
}
|
|
516
521
|
} catch (error) {
|
|
517
522
|
if (!quiet) {
|
|
518
|
-
logger.error(t('lzc_cli.lib.utils.resolve_domain_fail_tips', `无法解析域名 {{domain}}: `, { domain }), error);
|
|
523
|
+
logger.error(t('lzc_cli.lib.utils.resolve_domain_fail_tips', `无法解析域名 {{ domain }}: `, { domain, interpolation: { escapeValue: false } }), error);
|
|
519
524
|
}
|
|
520
525
|
throw error;
|
|
521
526
|
}
|
|
@@ -523,7 +528,7 @@ export async function resolveDomain(domain, quiet = false) {
|
|
|
523
528
|
|
|
524
529
|
export function unzipSync(zipPath, destPath, entries = []) {
|
|
525
530
|
if (!isFileExist(zipPath)) {
|
|
526
|
-
throw t('lzc_cli.lib.utils.unzip_sync_not_exist_fail', `{{zipPath}} 找不到该文件`, { zipPath });
|
|
531
|
+
throw t('lzc_cli.lib.utils.unzip_sync_not_exist_fail', `{{ zipPath }} 找不到该文件`, { zipPath, interpolation: { escapeValue: false } });
|
|
527
532
|
}
|
|
528
533
|
|
|
529
534
|
// 确保目标目录存在
|
|
@@ -536,7 +541,7 @@ export function unzipSync(zipPath, destPath, entries = []) {
|
|
|
536
541
|
try {
|
|
537
542
|
zip.extractEntryTo(entry, destPath, false, true);
|
|
538
543
|
} catch {
|
|
539
|
-
logger.debug(t('lzc_cli.lib.utils.unzip_sync_not_exist_file_log', `压缩包中没有找到 {{entry}} 文件`, { entry }));
|
|
544
|
+
logger.debug(t('lzc_cli.lib.utils.unzip_sync_not_exist_file_log', `压缩包中没有找到 {{ entry }} 文件`, { entry, interpolation: { escapeValue: false } }));
|
|
540
545
|
}
|
|
541
546
|
});
|
|
542
547
|
} else {
|
|
@@ -647,12 +652,12 @@ export function checkRsync() {
|
|
|
647
652
|
const versionMatch = check.stdout.match(/rsync\s+version\s+(\d+\.\d+\.\d+)/i);
|
|
648
653
|
if (!versionMatch || compareVersions('3.2.0', versionMatch[1]) < 0) {
|
|
649
654
|
reject(
|
|
650
|
-
t('lzc_cli.lib.utils.check_rsync_version_no_match_fail', `当前 rsync 版本为:{{versionMatch}}, 要求 rsync 版本为: 3.2.0+`, {
|
|
655
|
+
t('lzc_cli.lib.utils.check_rsync_version_no_match_fail', `当前 rsync 版本为:{{ versionMatch }}, 要求 rsync 版本为: 3.2.0+`, {
|
|
651
656
|
versionMatch: versionMatch[1],
|
|
652
657
|
}),
|
|
653
658
|
);
|
|
654
659
|
}
|
|
655
|
-
logger.debug(t('lzc_cli.lib.utils.check_rsync_version_print_log', `当前 rsync 版本为:{{versionMatch}}`, { versionMatch: versionMatch[1] }));
|
|
660
|
+
logger.debug(t('lzc_cli.lib.utils.check_rsync_version_print_log', `当前 rsync 版本为:{{ versionMatch }}`, { versionMatch: versionMatch[1] }));
|
|
656
661
|
} else {
|
|
657
662
|
reject(t('lzc_cli.lib.utils.check_rsync_version_fail', '请检查 rsync 安装是否正确,指定 rsync --version 错误'));
|
|
658
663
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazycatcloud/lzc-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-pre.0",
|
|
4
4
|
"description": "lazycat cloud developer kit",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"release": "release-it patch",
|
|
7
7
|
"prepublishOnly": "node check-changelog.js",
|
|
8
|
-
"i18n:parser": "i18next-cli extract -c i18next.config.js && prettier -w ./lib/i18n/locales"
|
|
8
|
+
"i18n:parser": "i18next-cli extract -c i18next.config.js --sync-all && prettier -w ./lib/i18n/locales"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"template",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"inquirer": "^10.1.8",
|
|
50
50
|
"isbinaryfile": "^5.0.2",
|
|
51
51
|
"js-yaml": "^4.1.0",
|
|
52
|
+
"lodash": "^4.17.23",
|
|
52
53
|
"lodash.debounce": "^4.0.8",
|
|
53
54
|
"lodash.merge": "^4.6.2",
|
|
54
55
|
"lodash.mergewith": "^4.6.2",
|
|
@@ -61,7 +62,7 @@
|
|
|
61
62
|
"devDependencies": {
|
|
62
63
|
"@release-it/conventional-changelog": "^10.0.1",
|
|
63
64
|
"@types/command-exists": "^1.2.3",
|
|
64
|
-
"i18next-cli": "^1.
|
|
65
|
+
"i18next-cli": "^1.32.0",
|
|
65
66
|
"prettier": "^3.3.3",
|
|
66
67
|
"release-it": "^19.0.4"
|
|
67
68
|
},
|
package/scripts/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { appstoreCommand } from '../lib/appstore/index.js';
|
|
|
14
14
|
import { lpkAppCommand, lpkProjectCommand } from '../lib/app/index.js';
|
|
15
15
|
import { configCommand } from '../lib/config/index.js';
|
|
16
16
|
import { lzcDockerCommand } from '../lib/docker/index.js';
|
|
17
|
+
import { lpkCommand } from '../lib/lpk/index.js';
|
|
17
18
|
|
|
18
19
|
function setLoggerLevel({ log }) {
|
|
19
20
|
logger.setLevel(log, false);
|
|
@@ -26,6 +27,7 @@ function checkLatestVersion(controller) {
|
|
|
26
27
|
case 'project':
|
|
27
28
|
case 'app':
|
|
28
29
|
case 'appstore':
|
|
30
|
+
case 'lpk':
|
|
29
31
|
getLatestVersion(controller);
|
|
30
32
|
}
|
|
31
33
|
}
|
|
@@ -63,6 +65,7 @@ lpkAppCommand(program);
|
|
|
63
65
|
lpkProjectCommand(program);
|
|
64
66
|
appstoreCommand(program);
|
|
65
67
|
lzcDockerCommand(program);
|
|
68
|
+
lpkCommand(program);
|
|
66
69
|
|
|
67
70
|
// 当没有参数的时候,默认显示帮助。
|
|
68
71
|
(async () => {
|
|
@@ -84,6 +87,7 @@ lzcDockerCommand(program);
|
|
|
84
87
|
case 'project':
|
|
85
88
|
case 'appstore':
|
|
86
89
|
case 'config':
|
|
90
|
+
case 'lpk':
|
|
87
91
|
program.showHelp();
|
|
88
92
|
return;
|
|
89
93
|
}
|
package/template/_lpk/README.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
|
|
2
2
|
# 懒猫云应用
|
|
3
3
|
|
|
4
|
+
## 第一次部署(先看到结果)
|
|
5
|
+
```bash
|
|
6
|
+
npm install
|
|
7
|
+
lzc-cli project deploy
|
|
8
|
+
lzc-cli project info
|
|
9
|
+
```
|
|
10
|
+
|
|
4
11
|
## 构建
|
|
5
12
|
```
|
|
6
13
|
lzc-cli project build -o you-awesome.lpk
|
|
@@ -9,12 +16,13 @@ lzc-cli project build -o you-awesome.lpk
|
|
|
9
16
|
|
|
10
17
|
## 安装
|
|
11
18
|
```
|
|
12
|
-
lzc-cli
|
|
19
|
+
lzc-cli lpk install you-awesome.lpk
|
|
13
20
|
```
|
|
14
21
|
|
|
15
|
-
##
|
|
22
|
+
## 修改源码后再次部署
|
|
16
23
|
```
|
|
17
|
-
lzc-cli project
|
|
24
|
+
lzc-cli project deploy
|
|
25
|
+
lzc-cli project log -f
|
|
18
26
|
```
|
|
19
27
|
|
|
20
28
|
## 成为懒猫云应用开发者
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: ${name} # app名称
|
|
2
|
+
package: ${package} # app的唯一标识符
|
|
3
|
+
version: 0.0.1 # app的版本
|
|
4
|
+
description: # app描述
|
|
5
|
+
|
|
6
|
+
license: https://choosealicense.com/licenses/mit/
|
|
7
|
+
homepage: # 项目主页或文档地址
|
|
8
|
+
author: # app author
|
|
9
|
+
|
|
10
|
+
# application 是默认前台容器,对应固定 service 名 app
|
|
11
|
+
application:
|
|
12
|
+
subdomain: ${subdomain} # 默认访问子域名前缀
|
|
13
|
+
routes:
|
|
14
|
+
# 将入口流量转发到 desktop service 的 6901 端口(VNC Web)
|
|
15
|
+
- /=http://desktop:6901/
|
|
16
|
+
# app 在启动时依赖 desktop 就绪
|
|
17
|
+
depends_on:
|
|
18
|
+
- desktop
|
|
19
|
+
# 多实例:每个用户部署自己的实例
|
|
20
|
+
multi_instance: true
|
|
21
|
+
|
|
22
|
+
services:
|
|
23
|
+
desktop:
|
|
24
|
+
# 使用容器内桌面用户启动
|
|
25
|
+
user: lazycat:kasm-user
|
|
26
|
+
# 引用 lzc-build.yml 里的 images.app-runtime
|
|
27
|
+
image: embed:app-runtime
|
|
@@ -7,8 +7,10 @@ license: https://choosealicense.com/licenses/mit/
|
|
|
7
7
|
homepage: # 出现bug时候提交反馈的地方
|
|
8
8
|
author: # app author
|
|
9
9
|
|
|
10
|
-
#application
|
|
10
|
+
# application 作为默认前台容器运行,对应固定 service 名 app
|
|
11
11
|
application:
|
|
12
|
-
subdomain: ${subdomain}
|
|
12
|
+
subdomain: ${subdomain} # 默认访问子域名前缀
|
|
13
13
|
routes:
|
|
14
|
+
# file:// 路由:直接返回静态文件(前端构建产物)
|
|
15
|
+
# 该示例默认读取 /lzcapp/pkg/content/dist 下的内容
|
|
14
16
|
- /=file:///lzcapp/pkg/content/dist
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
name: ${name} # app名称
|
|
2
|
+
package: ${package} # app的唯一标识符
|
|
3
|
+
version: 0.0.1 # app的版本
|
|
4
|
+
description: # app描述
|
|
5
|
+
|
|
6
|
+
license: https://choosealicense.com/licenses/mit/
|
|
7
|
+
homepage: # 出现bug时候提交反馈的地方
|
|
8
|
+
author: # app author
|
|
9
|
+
|
|
10
|
+
application:
|
|
11
|
+
subdomain: ${subdomain} # 默认访问子域名前缀
|
|
12
|
+
image: embed:app-runtime # 引用 lzc-build.yml 里的 images.app-runtime
|
|
13
|
+
routes:
|
|
14
|
+
# exec:// 路由会执行 /app/run.sh,并把请求转发到 3000 端口
|
|
15
|
+
# 适合「一个容器同时承载前端+后端」的入门场景
|
|
16
|
+
- /=exec://3000,/app/run.sh
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
name: ${name} # app名称
|
|
2
|
+
package: ${package} # app的唯一标识符
|
|
3
|
+
version: 0.0.1 # app的版本
|
|
4
|
+
description: # app描述
|
|
5
|
+
|
|
6
|
+
license: https://choosealicense.com/licenses/mit/
|
|
7
|
+
homepage: # 出现bug时候提交反馈的地方
|
|
8
|
+
author: # app author
|
|
9
|
+
|
|
10
|
+
application:
|
|
11
|
+
subdomain: ${subdomain} # 默认访问子域名前缀
|
|
12
|
+
image: embed:app-runtime # 引用 lzc-build.yml 里的 images.app-runtime
|
|
13
|
+
routes:
|
|
14
|
+
# exec:// 路由会执行 /app/run.sh,并把请求转发到 8080 端口
|
|
15
|
+
- /=exec://8080,/app/run.sh
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
name: ${name} # app名称
|
|
2
|
+
package: ${package} # app的唯一标识符
|
|
3
|
+
version: 0.0.1 # app的版本
|
|
4
|
+
description: # app描述
|
|
5
|
+
|
|
6
|
+
license: https://choosealicense.com/licenses/mit/
|
|
7
|
+
homepage: # 出现bug时候提交反馈的地方
|
|
8
|
+
author: # app author
|
|
9
|
+
|
|
10
|
+
application:
|
|
11
|
+
subdomain: ${subdomain} # 默认访问子域名前缀
|
|
12
|
+
image: embed:app-runtime # 引用 lzc-build.yml 里的 images.app-runtime
|
|
13
|
+
routes:
|
|
14
|
+
# exec:// 路由会执行 /app/run.sh,并把请求转发到 3000 端口
|
|
15
|
+
- /=exec://3000,/app/run.sh
|