@lazycatcloud/lzc-cli 1.1.8 → 1.1.9
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 +69 -11
- package/lib/api.js +71 -39
- package/lib/app/index.js +76 -21
- package/lib/app/lpk_build.js +95 -63
- package/lib/app/lpk_create.js +63 -41
- package/lib/app/lpk_create_generator.js +202 -0
- package/lib/app/lpk_devshell.js +393 -328
- package/lib/app/lpk_devshell_docker.js +211 -0
- package/lib/app/lpk_installer.js +63 -26
- package/lib/app/lpk_log.js +68 -0
- package/lib/app/lpk_status.js +18 -0
- package/lib/app/lpk_uninstall.js +19 -0
- package/lib/appstore/index.js +37 -0
- package/lib/appstore/login.js +96 -93
- package/lib/appstore/publish.js +62 -0
- package/lib/autologin.js +0 -78
- package/lib/box/api/clientapi.js +1322 -0
- package/lib/box/api/empty.js +35 -0
- package/lib/box/check_qemu.js +1 -0
- package/lib/box/index.js +41 -94
- package/lib/box/qemu_vm_mgr.js +208 -239
- package/lib/box/schemes/vm_box_system_debian.json +1 -1
- package/lib/docker-compose.js +1 -2
- package/lib/env.js +19 -101
- package/lib/key.js +1 -0
- package/lib/sdk.js +10 -25
- package/lib/utils.js +156 -132
- package/package.json +19 -10
- package/scripts/cli.js +14 -135
- package/template/_lpk/README.md +31 -0
- package/template/_lpk/exec.sh +19 -0
- package/template/_lpk/golang.manifest.yml.in +16 -0
- package/template/_lpk/lazycat.png +0 -0
- package/template/_lpk/lite.manifest.yml.in +19 -0
- package/template/_lpk/local_devshell/Dockerfile +16 -0
- package/template/_lpk/local_devshell/build.sh +5 -0
- package/template/_lpk/local_devshell/entrypoint.sh +87 -0
- package/template/{_lazycat/debug/shell → _lpk/local_devshell}/sshd_config +8 -8
- package/template/_lpk/manifest.yml.in +0 -1
- package/template/{vue/lzc-build.yml → _lpk/vue.lzc-build.yml.in} +9 -1
- package/template/golang/README.md +0 -2
- package/template/golang/_gitignore +2 -0
- package/template/golang/build.sh +6 -0
- package/template/golang/lazycat.png +0 -0
- package/template/golang/lzc-build.yml +9 -1
- package/template/golang/rego.go +15 -16
- package/template/golang/rego_test.go +13 -0
- package/template/ionic_vue3/lazycat.png +0 -0
- package/template/ionic_vue3/lzc-build.yml +9 -1
- package/template/lite/error_pages/502.html.tpl +13 -0
- package/template/lite/lazycat.png +0 -0
- package/template/lite/lzc-build.yml +60 -0
- package/cmds/app.js +0 -133
- package/cmds/config.js +0 -55
- package/cmds/create.js +0 -55
- package/cmds/dev.js +0 -130
- package/cmds/init.js +0 -125
- package/cmds/log.js +0 -103
- package/cmds/publish.js +0 -116
- package/lib/archiver.js +0 -105
- package/lib/box/hportal.js +0 -120
- package/lib/builder.js +0 -313
- package/lib/dev.js +0 -314
- package/lib/generator.js +0 -146
- package/template/_lazycat/_gitignore +0 -1
- package/template/_lazycat/app-config +0 -1
- package/template/_lazycat/debug/devforward/50x.html +0 -30
- package/template/_lazycat/debug/devforward/Dockerfile +0 -16
- package/template/_lazycat/debug/devforward/docker-compose.override.yml.in +0 -11
- package/template/_lazycat/debug/devforward/entrypoint.sh +0 -10
- package/template/_lazycat/debug/devforward/nginx.conf.template +0 -56
- package/template/_lazycat/debug/devforward/sshd_config +0 -116
- package/template/_lazycat/debug/shell/50x.html +0 -32
- package/template/_lazycat/debug/shell/Dockerfile +0 -18
- package/template/_lazycat/debug/shell/build.sh +0 -15
- package/template/_lazycat/debug/shell/docker-compose.override.yml.in +0 -13
- package/template/_lazycat/debug/shell/entrypoint.sh +0 -12
- package/template/_lazycat/docker-compose.yml.in +0 -15
- package/template/_lazycat/icon.svg +0 -1
- package/template/_lazycat/screenshot.png +0 -0
- package/template/_lpk/sync/Dockerfile +0 -16
- package/template/_lpk/sync/build.sh +0 -5
- package/template/_lpk/sync/entrypoint.sh +0 -8
- package/template/_lpk/sync/sshd_config +0 -117
- package/template/_lpk/sync.manifest.yml.in +0 -3
- package/template/release/golang/Dockerfile +0 -18
- package/template/release/golang/build.sh +0 -13
- package/template/release/ionic_vue3/Dockerfile +0 -10
- package/template/release/ionic_vue3/build.sh +0 -7
- package/template/release/ionic_vue3/docker-compose.yml.in +0 -3
- package/template/release/vue/Dockerfile +0 -10
- package/template/release/vue/build.sh +0 -10
- package/template/release/vue/docker-compose.yml.in +0 -3
- package/template/vue/README.md +0 -29
- package/template/vue/_dockerignore +0 -1
- package/template/vue/babel.config.js +0 -3
- package/template/vue/package.json +0 -43
- package/template/vue/public/favicon.ico +0 -0
- package/template/vue/public/index.html +0 -33
- package/template/vue/src/App.vue +0 -39
- package/template/vue/src/main.js +0 -8
- package/template/vue/src/todo.vue +0 -640
- package/template/vue/src/top-bar.vue +0 -100
- package/template/vue/src/webdav.vue +0 -183
- package/template/vue/vue.config.js +0 -21
package/cmds/publish.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import env from "../lib/env.js";
|
|
2
|
-
import Builder from "../lib/builder.js";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import Semver from "semver";
|
|
5
|
-
import inquirer from "inquirer";
|
|
6
|
-
import Archiver from "../lib/archiver.js";
|
|
7
|
-
import { isValidApp, contextDirname as getLibDir } from "../lib/utils.js";
|
|
8
|
-
import fs from "fs";
|
|
9
|
-
import execa from "execa";
|
|
10
|
-
import { GLOBAL_CONFIG_DIR } from "../lib/utils.js";
|
|
11
|
-
|
|
12
|
-
const REGISTRY_REPO = "registry.lazycat.cloud";
|
|
13
|
-
const APPDB_DIR = path.join(GLOBAL_CONFIG_DIR, "appdb");
|
|
14
|
-
|
|
15
|
-
export default class Publisher {
|
|
16
|
-
constructor() {
|
|
17
|
-
env.load(process.cwd());
|
|
18
|
-
}
|
|
19
|
-
async run() {
|
|
20
|
-
const { appDir, _ } = isValidApp(process.cwd());
|
|
21
|
-
const builder = new Builder();
|
|
22
|
-
const contextDir = process.cwd();
|
|
23
|
-
|
|
24
|
-
console.log(getLibDir());
|
|
25
|
-
//
|
|
26
|
-
const { tag, newVersion } = await this.generateTag();
|
|
27
|
-
|
|
28
|
-
const dockerfile = path.join(env.get("BUILD_CONTEXT"), "Dockerfile");
|
|
29
|
-
await builder.dockerLocalBuild(contextDir, dockerfile, tag);
|
|
30
|
-
|
|
31
|
-
// 该操作确保了 appdb 的存在
|
|
32
|
-
execa.sync(path.join(getLibDir(), "git/git-reset.sh"), {
|
|
33
|
-
cwd: appDir,
|
|
34
|
-
env: {
|
|
35
|
-
APPDB_DIR,
|
|
36
|
-
},
|
|
37
|
-
stdio: "inherit",
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
// 生成新的应用
|
|
41
|
-
const outputDir = path.join(APPDB_DIR, env.get("APP_ID"));
|
|
42
|
-
|
|
43
|
-
if (!fs.existsSync(outputDir)) {
|
|
44
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
45
|
-
}
|
|
46
|
-
const buildDir = path.join(process.cwd(), env.get("BUILD_CONTEXT"));
|
|
47
|
-
|
|
48
|
-
const archiver = new Archiver(outputDir);
|
|
49
|
-
await archiver.load(appDir);
|
|
50
|
-
await archiver.add(buildDir);
|
|
51
|
-
archiver.replace(
|
|
52
|
-
`
|
|
53
|
-
x-lazycat-app:
|
|
54
|
-
version: ${newVersion}
|
|
55
|
-
services:
|
|
56
|
-
${env.get("APP_ID")}:
|
|
57
|
-
image: ${tag}
|
|
58
|
-
`.trim()
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
execa.sync(path.join(getLibDir(), "git/git-commit.sh"), {
|
|
62
|
-
cwd: APPDB_DIR,
|
|
63
|
-
env: {
|
|
64
|
-
APP_ID: env.get("APP_ID"),
|
|
65
|
-
APP_VERSION: newVersion,
|
|
66
|
-
},
|
|
67
|
-
stdio: "inherit",
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
// confirm git push
|
|
71
|
-
const { push } = await inquirer.prompt([
|
|
72
|
-
{
|
|
73
|
-
name: "push",
|
|
74
|
-
message: `确定发布版本 ${newVersion}`,
|
|
75
|
-
type: "confirm",
|
|
76
|
-
},
|
|
77
|
-
]);
|
|
78
|
-
|
|
79
|
-
if (push) {
|
|
80
|
-
execa.sync("git", ["push"], {
|
|
81
|
-
cwd: APPDB_DIR,
|
|
82
|
-
});
|
|
83
|
-
// 更新版本
|
|
84
|
-
env.set({ APP_VERSION: newVersion });
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async generateTag() {
|
|
89
|
-
const currentVersion = Semver.parse(env.get("APP_VERSION"));
|
|
90
|
-
if (currentVersion == null) throw new Error("无法获取当前版本号");
|
|
91
|
-
|
|
92
|
-
const newVersion = Semver.inc(currentVersion, "patch");
|
|
93
|
-
const answer = await inquirer.prompt({
|
|
94
|
-
name: "version",
|
|
95
|
-
message: "请填写新的版本号",
|
|
96
|
-
type: "input",
|
|
97
|
-
default: () => newVersion,
|
|
98
|
-
validate: (input) => {
|
|
99
|
-
let v = Semver.parse(input);
|
|
100
|
-
if (v) {
|
|
101
|
-
if (Semver.lte(v, currentVersion)) {
|
|
102
|
-
return "该版本号小于当前版本";
|
|
103
|
-
}
|
|
104
|
-
return true;
|
|
105
|
-
} else {
|
|
106
|
-
return "请输入正确的版本号";
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
return {
|
|
112
|
-
tag: `${REGISTRY_REPO}/${env.get("APP_ID")}:${answer.version}`,
|
|
113
|
-
newVersion: answer.version,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
}
|
package/lib/archiver.js
DELETED
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
archiveFolder,
|
|
3
|
-
copyDotAppDir,
|
|
4
|
-
ensureDir,
|
|
5
|
-
toPair,
|
|
6
|
-
META_MARK,
|
|
7
|
-
envsubstr,
|
|
8
|
-
} from "./utils.js";
|
|
9
|
-
|
|
10
|
-
import glob from "fast-glob";
|
|
11
|
-
import path from "path";
|
|
12
|
-
import fs from "fs";
|
|
13
|
-
import env from "./env.js";
|
|
14
|
-
import DockerCompose from "./docker-compose.js";
|
|
15
|
-
|
|
16
|
-
class Archiver {
|
|
17
|
-
constructor(tmpDir) {
|
|
18
|
-
this.tmpDir = tmpDir;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async load(baseDir, e) {
|
|
22
|
-
const allEnv = e ? e : env.all;
|
|
23
|
-
// 1 拷贝 dotApp 路径至 temp
|
|
24
|
-
this.dotAppDir = baseDir;
|
|
25
|
-
await copyDotAppDir(this.dotAppDir, this.tmpDir, {
|
|
26
|
-
env: allEnv,
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// 用于替换一些docker-compose 内容
|
|
31
|
-
replace(dockerComposeContent) {
|
|
32
|
-
let base = new DockerCompose(path.join(this.tmpDir, "docker-compose.yml"));
|
|
33
|
-
base.merge({ content: dockerComposeContent });
|
|
34
|
-
base.save();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async add(cwd, e) {
|
|
38
|
-
const allEnv = e ? e : env.all;
|
|
39
|
-
Object.assign(allEnv, { APP_IMAGE_NAME: env.get("APP_ID") });
|
|
40
|
-
const target = this.tmpDir;
|
|
41
|
-
try {
|
|
42
|
-
let base = new DockerCompose(path.join(target, "docker-compose.yml"));
|
|
43
|
-
// 2 遍历当前路径, 拷贝至 temp, 同时对 docker-compose 做合并操作
|
|
44
|
-
const _files = await glob(["**"], {
|
|
45
|
-
cwd: cwd,
|
|
46
|
-
dot: true,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
for (let file of _files) {
|
|
50
|
-
let absFile = path.join(cwd, file);
|
|
51
|
-
if (path.basename(file).startsWith("docker-compose")) {
|
|
52
|
-
if (file.endsWith(".in")) {
|
|
53
|
-
base.merge({ content: await this._doEnvSubStr(absFile, allEnv) });
|
|
54
|
-
} else {
|
|
55
|
-
base.merge({ path: absFile });
|
|
56
|
-
}
|
|
57
|
-
} else {
|
|
58
|
-
// 对其他模板做转换操作, 然后直接写入目标文件
|
|
59
|
-
let targetAbsFile;
|
|
60
|
-
if (file.endsWith(".in")) {
|
|
61
|
-
targetAbsFile = path.join(target, file.replace(/\.in$/, ""));
|
|
62
|
-
ensureDir(targetAbsFile);
|
|
63
|
-
fs.writeFileSync(
|
|
64
|
-
targetAbsFile,
|
|
65
|
-
await this._doEnvSubStr(absFile, allEnv)
|
|
66
|
-
);
|
|
67
|
-
} else {
|
|
68
|
-
targetAbsFile = path.join(target, file);
|
|
69
|
-
ensureDir(targetAbsFile);
|
|
70
|
-
fs.copyFileSync(absFile, targetAbsFile);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
base.save();
|
|
76
|
-
|
|
77
|
-
// if (opts.build || false) {
|
|
78
|
-
// // new Builder()
|
|
79
|
-
// }
|
|
80
|
-
} catch (e) {
|
|
81
|
-
throw e;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async _doEnvSubStr(filepath, env = {}) {
|
|
86
|
-
const options = {
|
|
87
|
-
envs: toPair(env),
|
|
88
|
-
syntax: "default",
|
|
89
|
-
protect: false,
|
|
90
|
-
};
|
|
91
|
-
return await envsubstr(fs.readFileSync(filepath, "utf8"), {
|
|
92
|
-
options,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async finalize(zip = true) {
|
|
97
|
-
if (zip) {
|
|
98
|
-
return await archiveFolder(this.tmpDir);
|
|
99
|
-
} else {
|
|
100
|
-
return this.tmpDir;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export default Archiver;
|
package/lib/box/hportal.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import fetch from "node-fetch";
|
|
2
|
-
import logger from "loglevel";
|
|
3
|
-
|
|
4
|
-
export class HportalManager {
|
|
5
|
-
constructor(apiHost) {
|
|
6
|
-
this.apiHost = apiHost;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
async deleteBox(boxId) {
|
|
10
|
-
let url = `${this.apiHost}/admin/boxes/${boxId}`;
|
|
11
|
-
const resp = await fetch(url, { method: "DELETE" });
|
|
12
|
-
if (resp.status != 200) {
|
|
13
|
-
let text = await resp.text();
|
|
14
|
-
console.log("从 Hportal 删除盒子失败", text);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
async addBox(boxName) {
|
|
19
|
-
let url = `${this.apiHost}/admin/boxes/${boxName}`;
|
|
20
|
-
const resp = await fetch(url, {
|
|
21
|
-
method: "POST",
|
|
22
|
-
body: JSON.stringify({
|
|
23
|
-
box_name: boxName,
|
|
24
|
-
}),
|
|
25
|
-
});
|
|
26
|
-
if (resp.status != 200) {
|
|
27
|
-
let text = await resp.text();
|
|
28
|
-
console.log(`添加 ${boxName} 到 Hportal 失败`);
|
|
29
|
-
throw text;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async loginBox(boxId, user, password) {
|
|
34
|
-
let url = `${this.apiHost}/admin/loginBox`;
|
|
35
|
-
const resp = await fetch(url, {
|
|
36
|
-
method: "POST",
|
|
37
|
-
body: JSON.stringify({
|
|
38
|
-
box_id: boxId,
|
|
39
|
-
password,
|
|
40
|
-
user,
|
|
41
|
-
}),
|
|
42
|
-
});
|
|
43
|
-
if (resp.status != 200) {
|
|
44
|
-
let text = await resp.text();
|
|
45
|
-
console.log(`登录失败`, text);
|
|
46
|
-
throw text;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async getBoxId(boxName) {
|
|
51
|
-
let boxs = await this.boxs(boxName);
|
|
52
|
-
if (boxs.length == 0) {
|
|
53
|
-
return "";
|
|
54
|
-
} else {
|
|
55
|
-
return boxs[0].boxId;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
async boxs(boxName, defaultBoxName = "") {
|
|
60
|
-
let url = this.apiHost + "/admin/boxes";
|
|
61
|
-
|
|
62
|
-
let origin = [];
|
|
63
|
-
try {
|
|
64
|
-
const resp = await fetch(url);
|
|
65
|
-
if (resp.status == 200) {
|
|
66
|
-
origin = await resp.json();
|
|
67
|
-
} else {
|
|
68
|
-
logger.debug(
|
|
69
|
-
`status: ${resp.status}, url: ${resp.url}, text: ${resp.statusText}`
|
|
70
|
-
);
|
|
71
|
-
return [];
|
|
72
|
-
}
|
|
73
|
-
} catch {
|
|
74
|
-
return [];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
let all = origin.map((info) => {
|
|
78
|
-
let yesorno = info.box_name == defaultBoxName ? "yes" : "no";
|
|
79
|
-
let connect = info.status >= 200 ? "connect" : "disconnect";
|
|
80
|
-
return {
|
|
81
|
-
default: yesorno,
|
|
82
|
-
boxName: info.box_name,
|
|
83
|
-
boxId: info.box_id,
|
|
84
|
-
connect,
|
|
85
|
-
};
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
if (!boxName) {
|
|
89
|
-
return all;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
let box = all.find((a) => a.boxName == boxName);
|
|
93
|
-
if (box) {
|
|
94
|
-
return [box];
|
|
95
|
-
} else {
|
|
96
|
-
return [];
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// showBox 将 qemu 虚拟盒子 和 真实盒子 的信息合并起来展示
|
|
102
|
-
export function showBoxInfo(vmInfos, rmInfos) {
|
|
103
|
-
let boxIds = vmInfos
|
|
104
|
-
.concat(rmInfos)
|
|
105
|
-
.map((info) => info.boxId)
|
|
106
|
-
.filter((value, index, self) => self.indexOf(value) == index);
|
|
107
|
-
|
|
108
|
-
if (boxIds.length == 0) {
|
|
109
|
-
console.log("没有找到任何盒子,创建一个吧!");
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
let result = [];
|
|
114
|
-
boxIds.forEach((boxId) => {
|
|
115
|
-
let vInfo = vmInfos.find((r) => r.boxId == boxId) || {};
|
|
116
|
-
let rInfo = rmInfos.find((r) => r.boxId == boxId) || {};
|
|
117
|
-
result.push(Object.assign({}, vInfo, rInfo));
|
|
118
|
-
});
|
|
119
|
-
console.table(result);
|
|
120
|
-
}
|
package/lib/builder.js
DELETED
|
@@ -1,313 +0,0 @@
|
|
|
1
|
-
// build strategies based on build.yml
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import chalk from "chalk";
|
|
5
|
-
import execa from "execa";
|
|
6
|
-
// import { archiveFolder } from "./utils.js";
|
|
7
|
-
import { dockerPullLzcAppsImage, DockerClient } from "./sdk.js";
|
|
8
|
-
import { DockerfileParser } from "dockerfile-ast";
|
|
9
|
-
import glob from "fast-glob";
|
|
10
|
-
import ignore from "@balena/dockerignore";
|
|
11
|
-
import { createLogUpdate } from "log-update";
|
|
12
|
-
import env, { sdkEnv } from "./env.js";
|
|
13
|
-
import Dockerode from "dockerode";
|
|
14
|
-
|
|
15
|
-
export const PRE_BUILD_FILE = "build.sh";
|
|
16
|
-
|
|
17
|
-
export async function execPreBuild(targetDir, context, env = {}) {
|
|
18
|
-
const buildFile = path.join(targetDir, PRE_BUILD_FILE);
|
|
19
|
-
if (!fs.existsSync(buildFile)) return;
|
|
20
|
-
|
|
21
|
-
const subproces = execa(buildFile, {
|
|
22
|
-
cwd: context,
|
|
23
|
-
env: env,
|
|
24
|
-
stdio: ["ignore", "inherit", "inherit"],
|
|
25
|
-
});
|
|
26
|
-
return await subproces;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// builder 需要在app 的场景下使用, 不过有两个路径要注意
|
|
30
|
-
// 一个是 docker build 时的context. 这个一般是运行cli时候的路径
|
|
31
|
-
// 一个是 .lazycat 的路径
|
|
32
|
-
export default class Builder {
|
|
33
|
-
constructor(env) {
|
|
34
|
-
this.env = env;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
async local() {}
|
|
38
|
-
|
|
39
|
-
async buildImage(contextDir, buildDir) {
|
|
40
|
-
try {
|
|
41
|
-
await this.dockerRemoteBuildV2(
|
|
42
|
-
contextDir,
|
|
43
|
-
path.join(buildDir, "Dockerfile")
|
|
44
|
-
);
|
|
45
|
-
} catch (e) {
|
|
46
|
-
throw e;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// private functions
|
|
51
|
-
|
|
52
|
-
async collectContextFromDockerFile(contextDir, dockerfilePath) {
|
|
53
|
-
// const contextDir = path.join(process.cwd(), context);
|
|
54
|
-
// const dockerfilePath = path.join(process.cwd(), dockerfile);
|
|
55
|
-
|
|
56
|
-
if (!fs.existsSync(dockerfilePath)) {
|
|
57
|
-
throw "未发现 Dockerfile";
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let src = [path.relative(contextDir, dockerfilePath)];
|
|
61
|
-
|
|
62
|
-
// 通过 COPY 和 ADD 获取所有context中的文件
|
|
63
|
-
// TODO 未处理 dockerignore 情况
|
|
64
|
-
const ast = DockerfileParser.parse(fs.readFileSync(dockerfilePath, "utf8"));
|
|
65
|
-
for (let a of ast.getInstructions()) {
|
|
66
|
-
if (["COPY", "ADD"].includes(a.getInstruction())) {
|
|
67
|
-
const from = a.getArguments()[0].getValue().replace(/^\//, "");
|
|
68
|
-
|
|
69
|
-
const fromFullPath = path.join(contextDir, from);
|
|
70
|
-
|
|
71
|
-
if (fs.existsSync(fromFullPath)) {
|
|
72
|
-
const stat = fs.statSync(path.join(contextDir, from));
|
|
73
|
-
if (stat.isDirectory()) {
|
|
74
|
-
let files = await glob(path.join(from, "**"), {
|
|
75
|
-
cwd: contextDir,
|
|
76
|
-
});
|
|
77
|
-
src = src.concat(files);
|
|
78
|
-
} else if (stat.isFile()) {
|
|
79
|
-
src.push(from);
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
// try use glob
|
|
83
|
-
let files = await glob(from, {
|
|
84
|
-
cwd: contextDir,
|
|
85
|
-
});
|
|
86
|
-
src = src.concat(files);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
// filter by dockerignore
|
|
91
|
-
const dockerIgnoreFile = path.join(contextDir, ".dockerignore");
|
|
92
|
-
//
|
|
93
|
-
if (fs.existsSync(dockerIgnoreFile)) {
|
|
94
|
-
let ig = ignore();
|
|
95
|
-
let data = fs.readFileSync(dockerIgnoreFile, "utf8");
|
|
96
|
-
ig.add(data.split("\n"));
|
|
97
|
-
src = ig.filter(src);
|
|
98
|
-
}
|
|
99
|
-
return src;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async dockerLocalBuild(contextDir, dockerfile, tag = env.get("APP_ID")) {
|
|
103
|
-
const src = await this.collectContextFromDockerFile(contextDir, dockerfile);
|
|
104
|
-
|
|
105
|
-
const docker = await new DockerClient().init();
|
|
106
|
-
try {
|
|
107
|
-
console.log(chalk.green("开始在设备中构建镜像"));
|
|
108
|
-
|
|
109
|
-
const stream = await docker.buildImage(
|
|
110
|
-
{
|
|
111
|
-
context: contextDir,
|
|
112
|
-
src: src,
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
buildargs: env.all,
|
|
116
|
-
dockerfile: src[0],
|
|
117
|
-
t: tag,
|
|
118
|
-
}
|
|
119
|
-
);
|
|
120
|
-
const finished = await new Promise((resolve, reject) => {
|
|
121
|
-
let refresher = new StatusRefresher();
|
|
122
|
-
|
|
123
|
-
docker.modem.followProgress(
|
|
124
|
-
stream,
|
|
125
|
-
(err, res) => {
|
|
126
|
-
err ? reject(err) : resolve(res);
|
|
127
|
-
},
|
|
128
|
-
(res) => {
|
|
129
|
-
if (res.status) {
|
|
130
|
-
if (res.id) {
|
|
131
|
-
if (["Downloading", "Extracting"].includes(res.status)) {
|
|
132
|
-
refresher.updateStatus({
|
|
133
|
-
id: res.id,
|
|
134
|
-
content: `${res.status} ${res.progress}`,
|
|
135
|
-
});
|
|
136
|
-
} else {
|
|
137
|
-
refresher.updateStatus({ id: res.id, content: res.status });
|
|
138
|
-
}
|
|
139
|
-
} else {
|
|
140
|
-
process.stdout.write(res.status);
|
|
141
|
-
process.stdout.write("\n");
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (res.stream) {
|
|
146
|
-
if (res.stream.startsWith("Step")) {
|
|
147
|
-
refresher.reset();
|
|
148
|
-
}
|
|
149
|
-
process.stdout.write(res.stream);
|
|
150
|
-
}
|
|
151
|
-
if (res.error) {
|
|
152
|
-
reject(res.error);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
);
|
|
156
|
-
});
|
|
157
|
-
await this.pushImage(tag);
|
|
158
|
-
} catch (err) {
|
|
159
|
-
throw err;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
async pushImage(tag) {
|
|
164
|
-
console.log(chalk.green(`开始推送镜像`));
|
|
165
|
-
const docker = await new DockerClient().init();
|
|
166
|
-
|
|
167
|
-
const image = new Dockerode.Image(docker.modem, tag);
|
|
168
|
-
|
|
169
|
-
// TODO hardcode 未来开发者需要获取推送至 lazycat 的token
|
|
170
|
-
return new Promise((resolve, reject) => {
|
|
171
|
-
image
|
|
172
|
-
.push({
|
|
173
|
-
authconfig: { auth: "bG5rczpsbmtzMjAyMA==" },
|
|
174
|
-
})
|
|
175
|
-
.then((stream) => {
|
|
176
|
-
let refresher = new StatusRefresher();
|
|
177
|
-
docker.modem.followProgress(
|
|
178
|
-
stream,
|
|
179
|
-
(err, res) => {
|
|
180
|
-
err ? reject(err) : resolve(res);
|
|
181
|
-
},
|
|
182
|
-
(res) => {
|
|
183
|
-
if (res.status) {
|
|
184
|
-
if (res.id) {
|
|
185
|
-
refresher.updateStatus({ id: res.id, content: res.status });
|
|
186
|
-
} else {
|
|
187
|
-
process.stdout.write(res.status);
|
|
188
|
-
process.stdout.write("\n");
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (res.stream) {
|
|
193
|
-
process.stdout.write(res.stream);
|
|
194
|
-
}
|
|
195
|
-
if (res.error) {
|
|
196
|
-
reject(res.error);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
);
|
|
200
|
-
});
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* v2 版本会收集所有需要的context 文件,将其传入buildImage 方法中
|
|
206
|
-
* @param contextDir
|
|
207
|
-
* @param dockerfile
|
|
208
|
-
* @param tag
|
|
209
|
-
*/
|
|
210
|
-
async dockerRemoteBuildV2(contextDir, dockerfile, tag = env.get("APP_ID")) {
|
|
211
|
-
const src = await this.collectContextFromDockerFile(contextDir, dockerfile);
|
|
212
|
-
const host = new URL(sdkEnv.sdkUrl).host;
|
|
213
|
-
console.log(host);
|
|
214
|
-
const docker = await new DockerClient(host).init();
|
|
215
|
-
try {
|
|
216
|
-
// TODO, 使用 docker.pull 方法会失败, 原因未知, 使用 workaround 方案
|
|
217
|
-
// docker.pull(
|
|
218
|
-
// "hello-world",
|
|
219
|
-
// // "registry.lazycat.cloud/lzc/lzcapp:0.1",
|
|
220
|
-
// // { authconfig: { auth: "bG5rczpsbmtzMjAyMA==" } },
|
|
221
|
-
// (err, stream) => {
|
|
222
|
-
// console.log("====", err, stream);
|
|
223
|
-
// }
|
|
224
|
-
// );
|
|
225
|
-
await dockerPullLzcAppsImage(host);
|
|
226
|
-
|
|
227
|
-
return new Promise(async (resolve, reject) => {
|
|
228
|
-
let refresher = new StatusRefresher();
|
|
229
|
-
|
|
230
|
-
console.log(chalk.green("开始在设备中构建镜像"));
|
|
231
|
-
const stream = await docker.buildImage(
|
|
232
|
-
{
|
|
233
|
-
context: contextDir,
|
|
234
|
-
src: src,
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
buildargs: this.env,
|
|
238
|
-
dockerfile: src[0],
|
|
239
|
-
t: tag,
|
|
240
|
-
}
|
|
241
|
-
);
|
|
242
|
-
docker.modem.followProgress(
|
|
243
|
-
stream,
|
|
244
|
-
(err, res) => {
|
|
245
|
-
err ? reject(err) : resolve(res);
|
|
246
|
-
},
|
|
247
|
-
(res) => {
|
|
248
|
-
if (res.status) {
|
|
249
|
-
if (res.id) {
|
|
250
|
-
if (["Downloading", "Extracting"].includes(res.status)) {
|
|
251
|
-
refresher.updateStatus({
|
|
252
|
-
id: res.id,
|
|
253
|
-
content: `${res.status} ${res.progress}`,
|
|
254
|
-
});
|
|
255
|
-
} else {
|
|
256
|
-
refresher.updateStatus({
|
|
257
|
-
id: res.id,
|
|
258
|
-
content: res.status,
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
} else {
|
|
262
|
-
process.stdout.write(res.status);
|
|
263
|
-
process.stdout.write("\n");
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
if (res.stream) {
|
|
268
|
-
if (res.stream.startsWith("Step")) {
|
|
269
|
-
refresher.reset();
|
|
270
|
-
}
|
|
271
|
-
process.stdout.write(res.stream);
|
|
272
|
-
}
|
|
273
|
-
if (res.error) {
|
|
274
|
-
reject(res.error);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
);
|
|
278
|
-
});
|
|
279
|
-
} catch (err) {
|
|
280
|
-
throw err;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
class StatusRefresher {
|
|
286
|
-
constructor() {
|
|
287
|
-
this.reset();
|
|
288
|
-
}
|
|
289
|
-
refresh() {
|
|
290
|
-
this.logUpdate(this.pulls.map((p) => `${p.id}: ${p.content}`).join("\n"));
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
reset() {
|
|
294
|
-
this.pulls = [];
|
|
295
|
-
this.logUpdate = createLogUpdate(process.stdout);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
updateStatus({ id, content }) {
|
|
299
|
-
let index = this.pulls.findIndex((p) => p.id == id);
|
|
300
|
-
if (index > -1) {
|
|
301
|
-
this.pulls[index] = {
|
|
302
|
-
id: id,
|
|
303
|
-
content: content,
|
|
304
|
-
};
|
|
305
|
-
} else {
|
|
306
|
-
this.pulls.push({
|
|
307
|
-
id: id,
|
|
308
|
-
content: content,
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
this.refresh();
|
|
312
|
-
}
|
|
313
|
-
}
|