@lazycatcloud/lzc-cli 1.1.4 → 1.1.7
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/cmds/app.js +1 -5
- package/cmds/dev.js +12 -4
- package/lib/api.js +21 -7
- package/lib/app/index.js +92 -0
- package/lib/app/lpk_build.js +229 -0
- package/lib/app/lpk_create.js +201 -0
- package/lib/app/lpk_devshell.js +621 -0
- package/lib/app/lpk_installer.js +67 -0
- package/lib/archiver.js +0 -42
- package/lib/autologin.js +84 -0
- package/lib/box/check_qemu.js +39 -16
- package/lib/box/hportal.js +7 -1
- package/lib/box/index.js +9 -6
- package/lib/box/qemu_vm_mgr.js +37 -23
- package/lib/builder.js +12 -1
- package/lib/dev.js +4 -9
- package/lib/env.js +9 -5
- package/lib/generator.js +2 -2
- package/lib/sdk.js +29 -5
- package/lib/utils.js +69 -46
- package/package.json +8 -2
- package/scripts/cli.js +91 -12
- package/template/_lazycat/debug/shell/Dockerfile +5 -3
- package/template/_lazycat/debug/shell/docker-compose.override.yml.in +2 -12
- package/template/_lazycat/debug/shell/entrypoint.sh +3 -1
- package/template/_lpk/Dockerfile.in +8 -0
- package/template/_lpk/devshell/Dockerfile +18 -0
- package/template/_lpk/devshell/build.sh +5 -0
- package/template/_lpk/devshell/entrypoint.sh +8 -0
- package/template/_lpk/devshell/sshd_config +117 -0
- package/template/_lpk/manifest.yml.in +17 -0
- package/template/_lpk/sync/Dockerfile +16 -0
- package/template/_lpk/sync/build.sh +5 -0
- package/template/_lpk/sync/entrypoint.sh +8 -0
- package/template/_lpk/sync/sshd_config +117 -0
- package/template/_lpk/sync.manifest.yml.in +3 -0
- package/template/golang/build.sh +6 -0
- package/template/golang/lzc-build.yml +52 -0
- package/template/ionic_vue3/lzc-build.yml +53 -0
- package/template/ionic_vue3/vite.config.ts +1 -1
- package/template/release/golang/build.sh +1 -1
- package/template/release/ionic_vue3/Dockerfile +1 -1
- package/template/release/ionic_vue3/build.sh +1 -3
- package/template/release/ionic_vue3/docker-compose.yml.in +0 -5
- package/template/release/vue/Dockerfile +1 -1
- package/template/release/vue/build.sh +2 -3
- package/template/release/vue/docker-compose.yml.in +0 -5
- package/template/vue/lzc-build.yml +53 -0
- package/template/vue/src/main.js +3 -14
- package/template/vue/vue.config.js +16 -0
- package/template/_lazycat/debug/shell/nginx.conf.template +0 -64
- package/template/vue/src/lzc.js +0 -110
package/cmds/app.js
CHANGED
|
@@ -14,10 +14,6 @@ import BoxAPI from "../lib/api.js";
|
|
|
14
14
|
import Archiver from "../lib/archiver.js";
|
|
15
15
|
import Key from "../lib/key.js";
|
|
16
16
|
|
|
17
|
-
import { debuglog } from "util";
|
|
18
|
-
|
|
19
|
-
const debug = debuglog("cmd/app");
|
|
20
|
-
|
|
21
17
|
// this set of api target box
|
|
22
18
|
class App {
|
|
23
19
|
constructor(context) {
|
|
@@ -113,7 +109,7 @@ class App {
|
|
|
113
109
|
try {
|
|
114
110
|
const out = await archiveFolder(dir);
|
|
115
111
|
console.log(chalk.green("开始部署应用"));
|
|
116
|
-
await api.
|
|
112
|
+
await api.applyZip(out.path);
|
|
117
113
|
await api.checkStatus();
|
|
118
114
|
|
|
119
115
|
console.log(
|
package/cmds/dev.js
CHANGED
|
@@ -4,6 +4,9 @@ import chalk from "chalk";
|
|
|
4
4
|
import Dev from "../lib/dev.js";
|
|
5
5
|
import env, { sdkEnv } from "../lib/env.js";
|
|
6
6
|
import BoxAPI from "../lib/api.js";
|
|
7
|
+
import { dockerPullLzcAppsImage } from "../lib/sdk.js";
|
|
8
|
+
import Key from "../lib/key.js";
|
|
9
|
+
import logger from "loglevel";
|
|
7
10
|
|
|
8
11
|
// this set of api target box
|
|
9
12
|
class Develop {
|
|
@@ -24,8 +27,14 @@ class Develop {
|
|
|
24
27
|
async devShell({ build }) {
|
|
25
28
|
let appdev = new Dev(env.get("APP_ID"), this.appPath);
|
|
26
29
|
|
|
30
|
+
await sdkEnv.ensure();
|
|
31
|
+
|
|
32
|
+
// 确保 sdk 能通过公钥正常访问
|
|
33
|
+
await new Key().ensure(sdkEnv.sdkUrl);
|
|
34
|
+
|
|
27
35
|
if (build) {
|
|
28
36
|
appdev.reset();
|
|
37
|
+
await dockerPullLzcAppsImage(sdkEnv.sdkHostName);
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
// 当如果已经连接。直接连接ssh
|
|
@@ -40,16 +49,15 @@ class Develop {
|
|
|
40
49
|
|
|
41
50
|
// 重新部署dev app container
|
|
42
51
|
try {
|
|
43
|
-
await sdkEnv.ensure();
|
|
44
52
|
const { APP_ID: appId } = await env.ensure([{ name: "APP_ID" }]);
|
|
45
53
|
|
|
46
|
-
|
|
54
|
+
logger.debug(sdkEnv.sdkUrl);
|
|
47
55
|
const api = new BoxAPI(appId, sdkEnv.sdkUrl);
|
|
48
56
|
|
|
49
57
|
let out = await appdev.fakeApp("shell");
|
|
50
58
|
|
|
51
|
-
|
|
52
|
-
await api.
|
|
59
|
+
logger.debug("设置端口转发");
|
|
60
|
+
await api.applyZip(out.path, { build: true });
|
|
53
61
|
await api.checkStatus();
|
|
54
62
|
|
|
55
63
|
await appdev.shell();
|
package/lib/api.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import fetch from "node-fetch";
|
|
2
1
|
import ora from "ora";
|
|
3
2
|
import chalk from "chalk";
|
|
4
3
|
import fs from "fs";
|
|
4
|
+
import { request } from "./autologin.js";
|
|
5
|
+
|
|
5
6
|
//
|
|
6
7
|
// sdk 提供的API
|
|
7
8
|
export default class API {
|
|
@@ -9,13 +10,26 @@ export default class API {
|
|
|
9
10
|
this.appId = appId;
|
|
10
11
|
this.host = host;
|
|
11
12
|
}
|
|
13
|
+
async applyZip(zipPath, params) {
|
|
14
|
+
let searchParams = new URLSearchParams(params);
|
|
15
|
+
const resp = await request(
|
|
16
|
+
`${this.host}/api/v1/app/applyZip?${searchParams.toString()}`,
|
|
17
|
+
{
|
|
18
|
+
method: "POST",
|
|
19
|
+
body: fs.createReadStream(zipPath),
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
if (resp.status != 200) {
|
|
23
|
+
throw chalk.red(await resp.text());
|
|
24
|
+
}
|
|
25
|
+
// update
|
|
26
|
+
}
|
|
12
27
|
// applyZip api 支持以下query
|
|
13
28
|
// build boolean 是否构建app容器
|
|
14
29
|
// pull boolean 是否从docker.io拉取image
|
|
15
30
|
async install(zipPath, params) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
`${this.host}/api/v1/app/applyZip?${searchParams.toString()}`,
|
|
31
|
+
const resp = await request(
|
|
32
|
+
`${this.host}/api/v1/app/install`,
|
|
19
33
|
{
|
|
20
34
|
method: "POST",
|
|
21
35
|
body: fs.createReadStream(zipPath),
|
|
@@ -28,13 +42,13 @@ export default class API {
|
|
|
28
42
|
}
|
|
29
43
|
|
|
30
44
|
async uninstall() {
|
|
31
|
-
return await
|
|
45
|
+
return await request(`${this.host}/api/v1/app/uninstall?id=${this.appId}`, {
|
|
32
46
|
method: "DELETE",
|
|
33
47
|
});
|
|
34
48
|
}
|
|
35
49
|
|
|
36
50
|
async status() {
|
|
37
|
-
const resp = await
|
|
51
|
+
const resp = await request(`${this.host}/api/v1/app/status?id=${this.appId}`);
|
|
38
52
|
if (resp.status == 200) {
|
|
39
53
|
const status = await resp.json();
|
|
40
54
|
return status;
|
|
@@ -46,7 +60,7 @@ export default class API {
|
|
|
46
60
|
}
|
|
47
61
|
|
|
48
62
|
async postPublicKey(key) {
|
|
49
|
-
const resp = await
|
|
63
|
+
const resp = await request(`${this.host}/api/v1/register`, {
|
|
50
64
|
method: "POST",
|
|
51
65
|
body: key,
|
|
52
66
|
});
|
package/lib/app/index.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import lpkCreate, { LpkManifest } from "./lpk_create.js";
|
|
3
|
+
import { LpkBuild } from "./lpk_build.js";
|
|
4
|
+
import { AppDevShell } from "./lpk_devshell.js";
|
|
5
|
+
import { LpkInstaller } from "./lpk_installer.js";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import { APP_FOLDER } from "../utils.js";
|
|
8
|
+
|
|
9
|
+
export function lpkProjectCommand(program) {
|
|
10
|
+
let subCommands = [
|
|
11
|
+
{
|
|
12
|
+
command: "create <name>",
|
|
13
|
+
desc: "创建懒猫云应用",
|
|
14
|
+
handler: async ({ name }) => {
|
|
15
|
+
await lpkCreate({ name });
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
command: "build [context]",
|
|
20
|
+
desc: "构建",
|
|
21
|
+
builder: (args) => {
|
|
22
|
+
args.option("o", {
|
|
23
|
+
alias: "output",
|
|
24
|
+
describe: "输出文件夹",
|
|
25
|
+
type: "string",
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
handler: async ({ context, output }) => {
|
|
29
|
+
const lpk = new LpkBuild();
|
|
30
|
+
await lpk.init();
|
|
31
|
+
// 正常的打包逻辑不需要 devshell
|
|
32
|
+
lpk.onBeforeBuildPackage(async (options) => {
|
|
33
|
+
delete options["devshell"];
|
|
34
|
+
if (output) {
|
|
35
|
+
options["pkgout"] = output;
|
|
36
|
+
}
|
|
37
|
+
return options;
|
|
38
|
+
});
|
|
39
|
+
await lpk.exec();
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
command: "devshell [context]",
|
|
44
|
+
desc: "进入盒子的开发环境",
|
|
45
|
+
builder: (args) => {
|
|
46
|
+
args.option("b", {
|
|
47
|
+
alias: "build",
|
|
48
|
+
describe: "重新构建环境",
|
|
49
|
+
type: "boolean",
|
|
50
|
+
default: false,
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
handler: async ({ context, build }) => {
|
|
54
|
+
const app = new AppDevShell(context);
|
|
55
|
+
await app.init();
|
|
56
|
+
if (build) {
|
|
57
|
+
await app.shellWithBuild();
|
|
58
|
+
} else {
|
|
59
|
+
await app.shell();
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
program.command({
|
|
65
|
+
command: "project",
|
|
66
|
+
desc: "项目管理",
|
|
67
|
+
builder: (args) => {
|
|
68
|
+
args.command(subCommands);
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function lpkAppCommand(program) {
|
|
74
|
+
let subCommands = [
|
|
75
|
+
{
|
|
76
|
+
command: "install <pkgPath>",
|
|
77
|
+
desc: "部署应用至设备",
|
|
78
|
+
handler: async ({ pkgPath }) => {
|
|
79
|
+
const installer = new LpkInstaller();
|
|
80
|
+
await installer.init();
|
|
81
|
+
await installer.install(pkgPath);
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
];
|
|
85
|
+
program.command({
|
|
86
|
+
command: "app",
|
|
87
|
+
desc: "应用管理",
|
|
88
|
+
builder: (args) => {
|
|
89
|
+
args.command(subCommands);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import tar from "tar";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import logger from "loglevel";
|
|
5
|
+
import {
|
|
6
|
+
loadFromYaml,
|
|
7
|
+
isDirExist,
|
|
8
|
+
isFileExist,
|
|
9
|
+
dumpToYaml,
|
|
10
|
+
envTemplateFile,
|
|
11
|
+
} from "../utils.js";
|
|
12
|
+
import { spawnSync } from "child_process";
|
|
13
|
+
import { LpkManifest } from "./lpk_create.js";
|
|
14
|
+
import archiver from "archiver";
|
|
15
|
+
import yaml from "js-yaml";
|
|
16
|
+
|
|
17
|
+
async function tarContentDir(from, to, cwd = "./") {
|
|
18
|
+
const dest = fs.createWriteStream(to);
|
|
19
|
+
tar
|
|
20
|
+
.c(
|
|
21
|
+
{
|
|
22
|
+
cwd: cwd,
|
|
23
|
+
filter: (filePath, stat) => {
|
|
24
|
+
logger.debug(`tar gz ${filePath}`);
|
|
25
|
+
return true;
|
|
26
|
+
},
|
|
27
|
+
sync: true,
|
|
28
|
+
portable: {
|
|
29
|
+
uid: 0,
|
|
30
|
+
gid: 0,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
[from]
|
|
34
|
+
)
|
|
35
|
+
.pipe(dest);
|
|
36
|
+
logger.debug(`pack: ${dest.path}`);
|
|
37
|
+
return dest.path;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function archiveFolderTo(appDir, out, format = "zip") {
|
|
41
|
+
return new Promise(async (resolve, reject) => {
|
|
42
|
+
if (!fs.existsSync(appDir)) {
|
|
43
|
+
reject(new Error(`${appDir} 文件夹不存在`));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
logger.debug("start archive app ...");
|
|
48
|
+
const output = fs.createWriteStream(out);
|
|
49
|
+
const archive = archiver(format);
|
|
50
|
+
|
|
51
|
+
archive.on("error", (e) => {
|
|
52
|
+
reject(e);
|
|
53
|
+
logger.error(e);
|
|
54
|
+
});
|
|
55
|
+
archive.on("end", () => {
|
|
56
|
+
resolve(output);
|
|
57
|
+
});
|
|
58
|
+
archive.pipe(output);
|
|
59
|
+
|
|
60
|
+
archive.directory(appDir, false);
|
|
61
|
+
archive.finalize();
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function fetchIconTo(options, cwd, destDir) {
|
|
66
|
+
if (!options["icon"]) {
|
|
67
|
+
logger.warn("图标icon 没有指定");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
let iconPath = options["icon"];
|
|
72
|
+
if (path.extname(iconPath) !== ".png") {
|
|
73
|
+
logger.warn("图标icon 不是一个 .png 文件");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!path.isAbsolute(iconPath)) {
|
|
78
|
+
iconPath = path.resolve(cwd, iconPath);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!isFileExist(iconPath)) {
|
|
82
|
+
logger.warn(`图标icon ${iconPath} 不存在`);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fs.copyFileSync(iconPath, path.join(destDir, "icon.png"));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export class LpkBuild {
|
|
91
|
+
constructor(cwd) {
|
|
92
|
+
this.pwd = cwd ?? process.cwd();
|
|
93
|
+
|
|
94
|
+
this.optionsFilePath = path.join(this.pwd, "lzc-build.yml");
|
|
95
|
+
this.options = loadFromYaml(this.optionsFilePath);
|
|
96
|
+
|
|
97
|
+
this.manifestFilePath = this.options["manifest"]
|
|
98
|
+
? path.join(this.pwd, this.options["manifest"])
|
|
99
|
+
: path.join(this.pwd, "lzc-manifest.yml");
|
|
100
|
+
this.manifest = null;
|
|
101
|
+
|
|
102
|
+
this.beforeBuildPackageFn = [];
|
|
103
|
+
this.beforeDumpYamlFn = [];
|
|
104
|
+
this.beforeTarContentFn = [];
|
|
105
|
+
this.beforeDumpLpkFn = [fetchIconTo];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// init 时替换 lzc-build.yml 中的模板字段
|
|
109
|
+
async init() {
|
|
110
|
+
const manifest = await this.getManifest();
|
|
111
|
+
this.options = yaml.load(
|
|
112
|
+
await envTemplateFile(this.optionsFilePath, manifest)
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// onBeforeDumpYaml
|
|
117
|
+
// fn: function(manifest, options) => manifest
|
|
118
|
+
onBeforeDumpYaml(fn) {
|
|
119
|
+
this.beforeDumpYamlFn.push(fn);
|
|
120
|
+
}
|
|
121
|
+
// onBeforeTarContent
|
|
122
|
+
// fn: function(contentdir) => void
|
|
123
|
+
onBeforeTarContent(fn) {
|
|
124
|
+
this.beforeTarContentFn.push(fn);
|
|
125
|
+
}
|
|
126
|
+
// onBeforeBuildPackage
|
|
127
|
+
// fn: function(options) => options
|
|
128
|
+
onBeforeBuildPackage(fn) {
|
|
129
|
+
this.beforeBuildPackageFn.push(fn);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// onBeforeDumpLpk
|
|
133
|
+
// fn: function(options, cwd, dir) => void
|
|
134
|
+
onBeforeDumpLpk(fn) {
|
|
135
|
+
this.beforeDumpLpkFn.push(fn);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async getManifest() {
|
|
139
|
+
if (this.manifest) {
|
|
140
|
+
return this.manifest;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let lpkM = new LpkManifest();
|
|
144
|
+
await lpkM.init(this.manifestFilePath);
|
|
145
|
+
this.manifest = lpkM.manifest;
|
|
146
|
+
return this.manifest;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async exec(prefix = "") {
|
|
150
|
+
if (this.beforeBuildPackageFn.length > 0) {
|
|
151
|
+
this.options = await this.beforeBuildPackageFn.reduce(
|
|
152
|
+
async (prev, curr) => {
|
|
153
|
+
return await curr(await prev);
|
|
154
|
+
},
|
|
155
|
+
this.options
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (this.options["buildscript"]) {
|
|
160
|
+
let p = spawnSync("sh", ["-c", this.options["buildscript"]], {
|
|
161
|
+
cwd: this.pwd,
|
|
162
|
+
stdio: "inherit",
|
|
163
|
+
});
|
|
164
|
+
if (p.status != 0) {
|
|
165
|
+
throw `构建失败`;
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
logger.warn("跳过执行 buildscript");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const pkgout = path.resolve(this.pwd, this.options["pkgout"]);
|
|
172
|
+
if (!isDirExist(pkgout)) {
|
|
173
|
+
throw `${pkgout} 不存在`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const tempDir = fs.mkdtempSync(".lzc-cli-build");
|
|
177
|
+
|
|
178
|
+
let contentdir = this.options["contentdir"];
|
|
179
|
+
if (contentdir) {
|
|
180
|
+
contentdir = path.isAbsolute(contentdir)
|
|
181
|
+
? contentdir
|
|
182
|
+
: path.join(this.pwd, contentdir);
|
|
183
|
+
if (!isDirExist(contentdir)) {
|
|
184
|
+
throw `${contentdir} 不存在`;
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
logger.warn("跳过拷贝 contentdir 内容");
|
|
188
|
+
// 当没有指定的 contentdir 的时候,也生成一个空的文件夹
|
|
189
|
+
// 原因是:可能其他地方会复制内容进来,像 devshell 中的会在打包的时候,将ssh key拷贝进来
|
|
190
|
+
contentdir = fs.mkdtempSync(path.join(tempDir, "fake-contentdir"));
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// 开始打包 contentdir
|
|
194
|
+
if (this.beforeTarContentFn.length > 0) {
|
|
195
|
+
await this.beforeTarContentFn.reduce(async (prev, curr) => {
|
|
196
|
+
let _prev = await prev;
|
|
197
|
+
await curr(_prev);
|
|
198
|
+
return _prev;
|
|
199
|
+
}, contentdir);
|
|
200
|
+
}
|
|
201
|
+
tarContentDir("./", path.join(tempDir, "content.tar"), contentdir);
|
|
202
|
+
|
|
203
|
+
// 开始生成 manifest.yml
|
|
204
|
+
let manifest = await this.getManifest();
|
|
205
|
+
if (this.beforeDumpYamlFn.length > 0) {
|
|
206
|
+
manifest = await this.beforeDumpYamlFn.reduce(async (prev, curr) => {
|
|
207
|
+
return await curr(await prev, this.options);
|
|
208
|
+
}, manifest);
|
|
209
|
+
}
|
|
210
|
+
logger.debug("manifest\n", manifest);
|
|
211
|
+
dumpToYaml(manifest, path.join(tempDir, "manifest.yml"));
|
|
212
|
+
|
|
213
|
+
// 打包 lpk
|
|
214
|
+
if (this.beforeDumpLpkFn.length > 0) {
|
|
215
|
+
await this.beforeDumpLpkFn.reduce(async (prev, curr) => {
|
|
216
|
+
await curr(this.options, this.pwd, tempDir);
|
|
217
|
+
return prev;
|
|
218
|
+
}, {});
|
|
219
|
+
}
|
|
220
|
+
const packName = `${prefix}${manifest.package}-v${manifest.version}.lpk`;
|
|
221
|
+
const lpkPath = await archiveFolderTo(tempDir, path.join(pkgout, packName));
|
|
222
|
+
logger.info(`输出lpk包 ${lpkPath.path}`);
|
|
223
|
+
|
|
224
|
+
// remove temp file
|
|
225
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
226
|
+
|
|
227
|
+
return lpkPath.path;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import logger from "loglevel";
|
|
2
|
+
import inquirer from "inquirer";
|
|
3
|
+
import {
|
|
4
|
+
contextDirname,
|
|
5
|
+
createTemplateFile,
|
|
6
|
+
loadFromYaml,
|
|
7
|
+
envTemplateFile,
|
|
8
|
+
ensureDir,
|
|
9
|
+
dumpToYaml,
|
|
10
|
+
} from "../utils.js";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import Generator, { TemplateConfig } from "../generator.js";
|
|
13
|
+
import fs from "fs";
|
|
14
|
+
import yaml from "js-yaml";
|
|
15
|
+
let fsPromises = fs.promises;
|
|
16
|
+
import { parse2CorrectName } from "../utils.js";
|
|
17
|
+
|
|
18
|
+
export class LpkManifest {
|
|
19
|
+
constructor(defaultAppID) {
|
|
20
|
+
this.pwd = contextDirname(import.meta.url);
|
|
21
|
+
this.manifest = {};
|
|
22
|
+
this.defaultAppID = defaultAppID;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async init(manifestFilePath) {
|
|
26
|
+
if (manifestFilePath) {
|
|
27
|
+
this.manifest = loadFromYaml(manifestFilePath);
|
|
28
|
+
} else {
|
|
29
|
+
let templatePath = path.join(
|
|
30
|
+
this.pwd,
|
|
31
|
+
"../../template/_lpk/manifest.yml.in"
|
|
32
|
+
);
|
|
33
|
+
let answer = await this.askLpkInfo();
|
|
34
|
+
this.manifest = yaml.load(await envTemplateFile(templatePath, answer));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async create(outputDir) {
|
|
39
|
+
let outputFilePath = path.join(outputDir, "manifest.yml");
|
|
40
|
+
logger.info(`创建文件 ${outputFilePath}`);
|
|
41
|
+
ensureDir(outputFilePath);
|
|
42
|
+
return fsPromises
|
|
43
|
+
.access(outputFilePath, fs.constants.F_OK | fs.constants.W_OK)
|
|
44
|
+
.then(
|
|
45
|
+
async () => {
|
|
46
|
+
const questions = [
|
|
47
|
+
{
|
|
48
|
+
name: "override",
|
|
49
|
+
type: "input",
|
|
50
|
+
default: "n",
|
|
51
|
+
message: "manifest.yml已经存在,是否覆盖(y/n): ",
|
|
52
|
+
},
|
|
53
|
+
];
|
|
54
|
+
const answers = await inquirer.prompt(questions);
|
|
55
|
+
if (answers.override.toLowerCase() === "y") {
|
|
56
|
+
return dumpToYaml(this.manifest, outputFilePath);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
() => {
|
|
60
|
+
return dumpToYaml(this.manifest, outputFilePath);
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async askLpkInfo() {
|
|
66
|
+
const noEmpty = (value) => value != "";
|
|
67
|
+
return await inquirer.prompt([
|
|
68
|
+
{
|
|
69
|
+
type: "input",
|
|
70
|
+
name: "package",
|
|
71
|
+
message: "请输入应用ID",
|
|
72
|
+
default: this.defaultAppID,
|
|
73
|
+
validate: noEmpty,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: "input",
|
|
77
|
+
name: "version",
|
|
78
|
+
message: "请输入应用版本信息",
|
|
79
|
+
default: "0.0.1",
|
|
80
|
+
validate: noEmpty,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: "input",
|
|
84
|
+
name: "subdomain",
|
|
85
|
+
message: "请输入应用子域名",
|
|
86
|
+
default: (answers) => {
|
|
87
|
+
return answers["package"].trim();
|
|
88
|
+
},
|
|
89
|
+
validate: (input) => {
|
|
90
|
+
if (!/^([a-z]|[0-9]|[\-])+$/.test(input)) {
|
|
91
|
+
return "应用名称只能包含(减号,小写字母,数字),请重新输入";
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
type: "input",
|
|
98
|
+
name: "name",
|
|
99
|
+
message: "请输入应用名称",
|
|
100
|
+
default: (answers) => {
|
|
101
|
+
const p = answers["package"];
|
|
102
|
+
return `${p}-懒猫云`;
|
|
103
|
+
},
|
|
104
|
+
validate: noEmpty,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
type: "input",
|
|
108
|
+
name: "description",
|
|
109
|
+
message: "应用简单的描述信息",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
type: "input",
|
|
113
|
+
name: "homepage",
|
|
114
|
+
message: "应用主页地址",
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
type: "input",
|
|
118
|
+
name: "author",
|
|
119
|
+
message: "应用作者",
|
|
120
|
+
},
|
|
121
|
+
]);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export class TemplateInit {
|
|
126
|
+
constructor(context) {
|
|
127
|
+
this.context = context;
|
|
128
|
+
this.type = context.type;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async create() {
|
|
132
|
+
if (!this.type) {
|
|
133
|
+
this.type = await chooseTemplate();
|
|
134
|
+
}
|
|
135
|
+
let lpkManifest = new LpkManifest(path.basename(this.context.cwd));
|
|
136
|
+
await lpkManifest.init();
|
|
137
|
+
await lpkManifest.create(this.context.cwd);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function chooseTemplate() {
|
|
142
|
+
return (
|
|
143
|
+
await inquirer.prompt([
|
|
144
|
+
{
|
|
145
|
+
name: "type",
|
|
146
|
+
message: "选择项目构建模板",
|
|
147
|
+
type: "list",
|
|
148
|
+
choices: ["vue", "golang", "ionic_vue3"],
|
|
149
|
+
},
|
|
150
|
+
])
|
|
151
|
+
)["type"];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 创建一个应用
|
|
155
|
+
// - 先选择模板信息(vue, golang, vue3)
|
|
156
|
+
// - 根据 lpk manifest 模板中所需要的字段填充manifest
|
|
157
|
+
// - 将 .lazycat 生成(包含 manifest, 项目文件)
|
|
158
|
+
// - 调用模板的hook
|
|
159
|
+
class LpkCreate {
|
|
160
|
+
constructor({ name }) {
|
|
161
|
+
this.name = name;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async exec() {
|
|
165
|
+
const type = await chooseTemplate();
|
|
166
|
+
const template = new TemplateInit({
|
|
167
|
+
cwd: path.join(process.cwd(), this.name),
|
|
168
|
+
type: type,
|
|
169
|
+
});
|
|
170
|
+
if (TemplateConfig[type]) {
|
|
171
|
+
await template.create();
|
|
172
|
+
logger.info(`初始化项目 ${this.name}`);
|
|
173
|
+
await Generator().generate(type, this.name);
|
|
174
|
+
logger.info("设置懒猫云应用");
|
|
175
|
+
await TemplateConfig[type].after(this.name);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export default async (context) => {
|
|
181
|
+
context.name = parse2CorrectName(context.name);
|
|
182
|
+
await fsPromises.access(process.cwd() + "/" + context.name).then(
|
|
183
|
+
async () => {
|
|
184
|
+
const questions = [
|
|
185
|
+
{
|
|
186
|
+
name: "override",
|
|
187
|
+
type: "input",
|
|
188
|
+
default: "n",
|
|
189
|
+
message: "项目已存在,是否覆盖(y/n): ",
|
|
190
|
+
},
|
|
191
|
+
];
|
|
192
|
+
const answers = await inquirer.prompt(questions);
|
|
193
|
+
if (answers.override.toLowerCase() === "y") {
|
|
194
|
+
return new LpkCreate(context).exec();
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
() => {
|
|
198
|
+
return new LpkCreate(context).exec();
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
};
|