@lazycatcloud/lzc-cli 1.1.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 +21 -0
- package/lib/api.js +123 -0
- package/lib/archiver.js +128 -0
- package/lib/builder.js +183 -0
- package/lib/dev.js +300 -0
- package/lib/docker/promise.js +91 -0
- package/lib/docker-compose.js +51 -0
- package/lib/env.js +104 -0
- package/lib/generator.js +115 -0
- package/lib/key.js +112 -0
- package/lib/sdk.js +129 -0
- package/lib/utils.js +349 -0
- package/package.json +53 -0
- package/scripts/cli.js +98 -0
- package/template/_lazycat/_gitignore +1 -0
- package/template/_lazycat/debug/devforward/50x.html +30 -0
- package/template/_lazycat/debug/devforward/Dockerfile +16 -0
- package/template/_lazycat/debug/devforward/docker-compose.override.yml.in +11 -0
- package/template/_lazycat/debug/devforward/entrypoint.sh +10 -0
- package/template/_lazycat/debug/devforward/nginx.conf.template +56 -0
- package/template/_lazycat/debug/devforward/sshd_config +116 -0
- package/template/_lazycat/debug/shell/50x.html +32 -0
- package/template/_lazycat/debug/shell/Dockerfile +16 -0
- package/template/_lazycat/debug/shell/build.sh +15 -0
- package/template/_lazycat/debug/shell/docker-compose.override.yml.in +23 -0
- package/template/_lazycat/debug/shell/entrypoint.sh +10 -0
- package/template/_lazycat/debug/shell/nginx.conf.template +64 -0
- package/template/_lazycat/debug/shell/sshd_config +117 -0
- package/template/_lazycat/docker-compose.yml.in +17 -0
- package/template/_lazycat/icon.svg +1 -0
- package/template/_lazycat/screenshot.png +0 -0
- package/template/golang/.godir +1 -0
- package/template/golang/README.md +13 -0
- package/template/golang/assets/css/bootstrap-responsive.css +1088 -0
- package/template/golang/assets/css/bootstrap-responsive.min.css +9 -0
- package/template/golang/assets/css/bootstrap.css +5893 -0
- package/template/golang/assets/css/bootstrap.min.css +9 -0
- package/template/golang/assets/css/rego.css +45 -0
- package/template/golang/assets/img/glyphicons-halflings-white.png +0 -0
- package/template/golang/assets/img/glyphicons-halflings.png +0 -0
- package/template/golang/assets/js/bootstrap.js +2025 -0
- package/template/golang/assets/js/bootstrap.min.js +6 -0
- package/template/golang/assets/js/rego.js +121 -0
- package/template/golang/go.mod +3 -0
- package/template/golang/index.html +267 -0
- package/template/golang/rego.go +83 -0
- package/template/release/golang/Dockerfile +18 -0
- package/template/release/golang/build.sh +14 -0
- package/template/release/vue/Dockerfile +9 -0
- package/template/release/vue/build.sh +9 -0
- package/template/release/vue/docker-compose.yml.in +8 -0
- package/template/vue/README.md +24 -0
- package/template/vue/_dockerignore +1 -0
- package/template/vue/babel.config.js +5 -0
- package/template/vue/package.json +43 -0
- package/template/vue/public/favicon.ico +0 -0
- package/template/vue/public/index.html +33 -0
- package/template/vue/src/App.vue +39 -0
- package/template/vue/src/lzc.js +110 -0
- package/template/vue/src/main.js +19 -0
- package/template/vue/src/todo.vue +640 -0
- package/template/vue/src/top-bar.vue +100 -0
- package/template/vue/src/webdav.vue +183 -0
- package/template/vue/vue.config.js +5 -0
package/lib/key.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import glob from "fast-glob";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import { Client } from "ssh2";
|
|
6
|
+
import process from "process";
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import execa from "execa";
|
|
9
|
+
import API from "./api.js";
|
|
10
|
+
|
|
11
|
+
const KEY_NAME = "lzc_box_key";
|
|
12
|
+
|
|
13
|
+
export default class Key {
|
|
14
|
+
// 确保该秘钥可以工作
|
|
15
|
+
async ensure(host) {
|
|
16
|
+
const pairs = await this.getKeyPair();
|
|
17
|
+
await this.validationCheck(host, pairs);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async generate() {
|
|
21
|
+
const privateKeyFile = path.join(os.homedir(), "/.ssh/", KEY_NAME);
|
|
22
|
+
await execa(
|
|
23
|
+
"ssh-keygen",
|
|
24
|
+
["-t", "ed25519", "-P", "", "-f", privateKeyFile],
|
|
25
|
+
{
|
|
26
|
+
stdio: "inherit",
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
return {
|
|
30
|
+
private: privateKeyFile,
|
|
31
|
+
public: privateKeyFile + ".pub",
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async validationCheck(host, pair) {
|
|
36
|
+
const hostname = new URL(host).hostname;
|
|
37
|
+
const client = new Client();
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
client
|
|
40
|
+
.on("ready", () => {
|
|
41
|
+
resolve(true);
|
|
42
|
+
client.end();
|
|
43
|
+
})
|
|
44
|
+
.on("error", (err) => {
|
|
45
|
+
if (err.level == "client-authentication") {
|
|
46
|
+
inquirer
|
|
47
|
+
.prompt([
|
|
48
|
+
{
|
|
49
|
+
name: "key",
|
|
50
|
+
type: "confirm",
|
|
51
|
+
message: "发布应用需要上传公钥, 确定上传?",
|
|
52
|
+
},
|
|
53
|
+
])
|
|
54
|
+
.then(({ key }) => {
|
|
55
|
+
if (key) {
|
|
56
|
+
const api = new API("", host);
|
|
57
|
+
api
|
|
58
|
+
.postPublicKey(fs.readFileSync(pair["public"]))
|
|
59
|
+
.then(() => {
|
|
60
|
+
resolve(true);
|
|
61
|
+
});
|
|
62
|
+
} else {
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
} else {
|
|
67
|
+
reject(err);
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
.connect({
|
|
71
|
+
host: hostname,
|
|
72
|
+
port: 2222,
|
|
73
|
+
username: "box",
|
|
74
|
+
retry: 3,
|
|
75
|
+
wait: 500,
|
|
76
|
+
authHandler: [
|
|
77
|
+
{
|
|
78
|
+
type: "publickey",
|
|
79
|
+
username: "box",
|
|
80
|
+
key: fs.readFileSync(pair["private"]),
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async getKeyPair() {
|
|
88
|
+
const results = await glob(path.join(os.homedir(), `/.ssh/${KEY_NAME}*`));
|
|
89
|
+
if (results.length != 2) {
|
|
90
|
+
// create key pair
|
|
91
|
+
const answers = await inquirer.prompt([
|
|
92
|
+
{
|
|
93
|
+
name: "generate",
|
|
94
|
+
message: "未发现ssh秘钥, 是否生成?",
|
|
95
|
+
type: "confirm",
|
|
96
|
+
default: true,
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
if (answers.generate) {
|
|
101
|
+
return await this.generate();
|
|
102
|
+
} else {
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
private: path.join(os.homedir(), "/.ssh/", KEY_NAME),
|
|
109
|
+
public: path.join(os.homedir(), `/.ssh/${KEY_NAME}.pub`),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
package/lib/sdk.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import ssh from "docker-modem/lib/ssh.js";
|
|
2
|
+
// import Docker from "./docker/promise.js";
|
|
3
|
+
import Docker from "dockerode";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import process from "process";
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import { Client } from "ssh2";
|
|
9
|
+
import chalk from "chalk";
|
|
10
|
+
import glob from "fast-glob";
|
|
11
|
+
import execa from "execa";
|
|
12
|
+
import API from "./api.js";
|
|
13
|
+
import Key from "./key.js";
|
|
14
|
+
|
|
15
|
+
async function connectOptions(host) {
|
|
16
|
+
const pairs = await new Key().getKeyPair();
|
|
17
|
+
return {
|
|
18
|
+
host: host,
|
|
19
|
+
port: 2222,
|
|
20
|
+
username: "box",
|
|
21
|
+
retry: 3,
|
|
22
|
+
wait: 500,
|
|
23
|
+
authHandler: [
|
|
24
|
+
{
|
|
25
|
+
type: "publickey",
|
|
26
|
+
username: "box",
|
|
27
|
+
key: fs.readFileSync(pairs["private"]),
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
type: "agent",
|
|
31
|
+
username: "box",
|
|
32
|
+
agent: process.env.SSH_AUTH_SOCK,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class DockerClient {
|
|
39
|
+
constructor(host) {
|
|
40
|
+
this.host = host;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async init() {
|
|
44
|
+
this.docker = new Docker({
|
|
45
|
+
protocal: "http",
|
|
46
|
+
agent: ssh(await connectOptions(this.host)),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const host = this.host;
|
|
50
|
+
|
|
51
|
+
async function onUncaughtException(e) {
|
|
52
|
+
if (e.code == "ECONNREFUSED") {
|
|
53
|
+
console.log(
|
|
54
|
+
chalk.red(`无法连接 sdk 服务, 请确保 ${chalk.yellow(host)} 可以访问`)
|
|
55
|
+
);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
} else {
|
|
58
|
+
throw e;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
process.on("uncaughtException", onUncaughtException);
|
|
62
|
+
await this.docker.ping();
|
|
63
|
+
process.off("uncaughtException", onUncaughtException);
|
|
64
|
+
return this.docker;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class SSHClient {
|
|
69
|
+
// arg can be a simple host name or a ssh connectOptions
|
|
70
|
+
constructor(arg) {
|
|
71
|
+
if (typeof arg == "string") {
|
|
72
|
+
this.opts = connectOptions(arg);
|
|
73
|
+
} else {
|
|
74
|
+
this.opts = arg;
|
|
75
|
+
}
|
|
76
|
+
this.con = new Client();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
connect() {
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
this.con
|
|
82
|
+
.on("ready", () => resolve(this.con))
|
|
83
|
+
.on("error", (err) => {
|
|
84
|
+
reject(err);
|
|
85
|
+
})
|
|
86
|
+
.on("close", () => resolve(null))
|
|
87
|
+
.connect(this.opts);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
close() {
|
|
92
|
+
this.con.end();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
exec(command, options = {}) {
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
this.con.exec(command, options, (err, chan) => {
|
|
98
|
+
if (err) return reject(err);
|
|
99
|
+
resolve(chan);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async shell() {
|
|
105
|
+
this.con.shell(
|
|
106
|
+
{
|
|
107
|
+
term: process.env.TERM,
|
|
108
|
+
rows: process.stdout.rows,
|
|
109
|
+
cols: process.stdout.columns,
|
|
110
|
+
},
|
|
111
|
+
(err, stream) => {
|
|
112
|
+
if (err) throw err;
|
|
113
|
+
|
|
114
|
+
stream.on("close", () => {
|
|
115
|
+
process.exit();
|
|
116
|
+
});
|
|
117
|
+
process.stdin.setRawMode(true);
|
|
118
|
+
process.stdin.pipe(stream);
|
|
119
|
+
|
|
120
|
+
// Connect remote output to local stdout
|
|
121
|
+
stream.pipe(process.stdout);
|
|
122
|
+
process.stdout.on("resize", () => {
|
|
123
|
+
// Let the remote end know when the local terminal has been resized
|
|
124
|
+
stream.setWindow(process.stdout.rows, process.stdout.columns, 0, 0);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import path from "path";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import { mkdtemp } from "fs/promises";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
import archiver from "archiver";
|
|
7
|
+
import glob from "fast-glob";
|
|
8
|
+
import envsub from "envsub";
|
|
9
|
+
import yaml from "js-yaml";
|
|
10
|
+
import mergeWith from "lodash.mergewith";
|
|
11
|
+
import isArray from "lodash.isarray";
|
|
12
|
+
import fetch from "node-fetch";
|
|
13
|
+
import { dirname } from "path";
|
|
14
|
+
import { fileURLToPath } from "url";
|
|
15
|
+
import ignore from "ignore";
|
|
16
|
+
|
|
17
|
+
const controller = new AbortController();
|
|
18
|
+
const META_MARK = "x-lazycat-app";
|
|
19
|
+
const APP_FOLDER = ".lazycat";
|
|
20
|
+
const APP_CONFIG_FILE = "app-config";
|
|
21
|
+
const APP_SDK_HOSTNAME = "box";
|
|
22
|
+
|
|
23
|
+
export const envsubstr = async (templateContents, args) => {
|
|
24
|
+
const parse = await importDefault("envsub/js/envsub-parser.js");
|
|
25
|
+
return parse(templateContents, args);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// commander passes the Command object itself as options,
|
|
29
|
+
// extract only actual options into a fresh object.
|
|
30
|
+
async function checkURL(url) {
|
|
31
|
+
const timeout = setTimeout(() => {
|
|
32
|
+
controller.abort();
|
|
33
|
+
}, 5000);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const resp = await fetch(url, { signal: controller.signal });
|
|
37
|
+
if (resp.status != 200) {
|
|
38
|
+
throw new Error(chalk.red(
|
|
39
|
+
`无法连接 sdk 服务, 请确保 ${chalk.yellow(
|
|
40
|
+
new URL(url).origin
|
|
41
|
+
)} 可以访问或者在应用商店安装 sdk`
|
|
42
|
+
));
|
|
43
|
+
}
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw error;
|
|
46
|
+
} finally {
|
|
47
|
+
clearTimeout(timeout);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function ensureDir(filePath) {
|
|
52
|
+
const dirPath = path.dirname(filePath);
|
|
53
|
+
if (!fs.existsSync(dirPath)) {
|
|
54
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function loadFromYaml(file) {
|
|
59
|
+
return yaml.load(fs.readFileSync(file, "utf8"));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function dumpToYaml(template, target) {
|
|
63
|
+
fs.writeFileSync(
|
|
64
|
+
target,
|
|
65
|
+
yaml.dump(template, {
|
|
66
|
+
styles: {
|
|
67
|
+
"!!null": "empty", // dump null as ""
|
|
68
|
+
},
|
|
69
|
+
})
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isValidApp(appDir) {
|
|
74
|
+
function doCheck(next = true) {
|
|
75
|
+
const files = ["docker-compose.yml", "docker-compose.yml.in"];
|
|
76
|
+
for (let f of files) {
|
|
77
|
+
const composeFile = path.join(appDir, f);
|
|
78
|
+
if (fs.existsSync(composeFile)) {
|
|
79
|
+
const doc = yaml.load(fs.readFileSync(composeFile, "utf8"));
|
|
80
|
+
if (doc[META_MARK]) {
|
|
81
|
+
return { appDir, isTemplate: f == "docker-compose.yml.in" };
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (next) {
|
|
86
|
+
appDir = path.join(appDir, APP_FOLDER);
|
|
87
|
+
return doCheck(false);
|
|
88
|
+
}
|
|
89
|
+
return { appDir: false, isTemplate: false };
|
|
90
|
+
}
|
|
91
|
+
return doCheck();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// find any valid app path till root folder
|
|
95
|
+
function findAppRootPath(aPath) {
|
|
96
|
+
const sep = "/";
|
|
97
|
+
const folders = aPath.split(sep);
|
|
98
|
+
for (let i = folders.length; i >= 0; i--) {
|
|
99
|
+
if (folders[i] == APP_FOLDER) {
|
|
100
|
+
return folders.slice(0, i + 1).join(sep);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function toPair(object) {
|
|
107
|
+
return Object.keys(object).map((key) => {
|
|
108
|
+
return {
|
|
109
|
+
name: key,
|
|
110
|
+
value: object[key].toString(),
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getMetaInfo(composeFile) {
|
|
116
|
+
const doc = yaml.load(fs.readFileSync(composeFile, "utf8"));
|
|
117
|
+
return doc[META_MARK];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function convertTemplateFile(templateFile, outputFile, env) {
|
|
121
|
+
const template = yaml.load(fs.readFileSync(templateFile, "utf8"));
|
|
122
|
+
// const meta = template[META_MARK];
|
|
123
|
+
// if (
|
|
124
|
+
// meta &&
|
|
125
|
+
// Array.isArray(meta["permissions"]) &&
|
|
126
|
+
// meta["permissions"].includes("lzcapis")
|
|
127
|
+
// ) {
|
|
128
|
+
// template[META_MARK]["ingress"].push({
|
|
129
|
+
// service: "lazycat-apis-sidecar",
|
|
130
|
+
// port: 8888,
|
|
131
|
+
// subdomain: "${APP_NAME}",
|
|
132
|
+
// path: "/lzcapis/",
|
|
133
|
+
// auth: "oidc",
|
|
134
|
+
// authcallback: "/lzcapis/oidc-callback",
|
|
135
|
+
// });
|
|
136
|
+
// template["services"]["lazycat-apis-sidecar"] = {
|
|
137
|
+
// image: "registry.linakesi.com/lazycat-apis-sidecar",
|
|
138
|
+
// // volumes_from: ["${APP_NAME}:rw"],
|
|
139
|
+
// volumes: ["lzcapis-lzcapp:/lzcapp"],
|
|
140
|
+
// command: [
|
|
141
|
+
// "--client-id=${LAZYCAT_AUTH_OIDC_CLIENT_ID}",
|
|
142
|
+
// "--client-secret=${LAZYCAT_AUTH_OIDC_CLIENT_SECRET}",
|
|
143
|
+
// "--client-url=https://${LAZYCAT_APP_ORIGIN}/lzcapis/",
|
|
144
|
+
// "--issuer=${LAZYCAT_AUTH_OIDC_ISSUER_URL}",
|
|
145
|
+
// "--prefix=lzcapis",
|
|
146
|
+
// "--fs-root=/lzcapp/documents",
|
|
147
|
+
// ],
|
|
148
|
+
// };
|
|
149
|
+
// }
|
|
150
|
+
//
|
|
151
|
+
const options = {
|
|
152
|
+
envs: toPair(env),
|
|
153
|
+
syntax: "default",
|
|
154
|
+
protect: false,
|
|
155
|
+
};
|
|
156
|
+
const output = await envsubstr(
|
|
157
|
+
yaml.dump(template, {
|
|
158
|
+
styles: {
|
|
159
|
+
"!!null": "empty", // dump null as ""
|
|
160
|
+
},
|
|
161
|
+
}),
|
|
162
|
+
{ options }
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
fs.writeFileSync(outputFile, output);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// this will copy current app to a tmp dir
|
|
169
|
+
async function copyDotAppDir(from, to, opts = {}) {
|
|
170
|
+
const {
|
|
171
|
+
include = [],
|
|
172
|
+
ignore = ["app-config", "output", "box-config.json"],
|
|
173
|
+
env = {},
|
|
174
|
+
} = opts;
|
|
175
|
+
try {
|
|
176
|
+
if (fs.existsSync(to)) {
|
|
177
|
+
fs.rmSync(to, { recursive: true });
|
|
178
|
+
}
|
|
179
|
+
const _files = await glob(["*"].concat(include), {
|
|
180
|
+
cwd: from,
|
|
181
|
+
dot: true,
|
|
182
|
+
ignore: ignore,
|
|
183
|
+
});
|
|
184
|
+
for (let f of _files) {
|
|
185
|
+
let needConvert = false;
|
|
186
|
+
let templateFile = path.join(from, f);
|
|
187
|
+
let outputFile = path.join(to, f);
|
|
188
|
+
if (f.endsWith(".in")) {
|
|
189
|
+
outputFile = outputFile.replace(/.in$/, "");
|
|
190
|
+
needConvert = true;
|
|
191
|
+
}
|
|
192
|
+
ensureDir(outputFile);
|
|
193
|
+
if (needConvert) {
|
|
194
|
+
await convertTemplateFile(templateFile, outputFile, env);
|
|
195
|
+
} else {
|
|
196
|
+
fs.copyFileSync(templateFile, outputFile);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return to;
|
|
200
|
+
} catch (e) {
|
|
201
|
+
console.log(e);
|
|
202
|
+
return "";
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// override yaml, notice this will change target file
|
|
207
|
+
function mergeYaml(target, source) {
|
|
208
|
+
const targetContent = yaml.load(fs.readFileSync(target, "utf8"));
|
|
209
|
+
const sourceContent = yaml.load(fs.readFileSync(source, "utf8"));
|
|
210
|
+
const merged = mergeWith(
|
|
211
|
+
targetContent,
|
|
212
|
+
sourceContent,
|
|
213
|
+
(objValue, srcValue) => {
|
|
214
|
+
if (isArray(objValue)) {
|
|
215
|
+
return objValue.concat(srcValue);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
fs.writeFileSync(
|
|
221
|
+
target,
|
|
222
|
+
yaml.dump(merged, {
|
|
223
|
+
styles: {
|
|
224
|
+
"!!null": "empty", // dump null as ""
|
|
225
|
+
},
|
|
226
|
+
})
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function archiveFolder(appDir, format = "zip") {
|
|
231
|
+
return new Promise(async (resolve, reject) => {
|
|
232
|
+
if (!fs.existsSync(appDir)) {
|
|
233
|
+
reject(new Error("folder does not exist"));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const tempDir = await mkdtemp(path.join(os.tmpdir(), "hc-"));
|
|
238
|
+
const out = path.join(tempDir, "app." + format);
|
|
239
|
+
|
|
240
|
+
// console.log(chalk.green("start archive app ..."));
|
|
241
|
+
const output = fs.createWriteStream(out);
|
|
242
|
+
const archive = archiver(format);
|
|
243
|
+
|
|
244
|
+
archive.on("error", (e) => {
|
|
245
|
+
reject(e);
|
|
246
|
+
// console.log(e);
|
|
247
|
+
});
|
|
248
|
+
archive.on("end", () => {
|
|
249
|
+
resolve(output);
|
|
250
|
+
});
|
|
251
|
+
archive.pipe(output);
|
|
252
|
+
|
|
253
|
+
archive.directory(appDir, false);
|
|
254
|
+
archive.finalize();
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function contextDirname() {
|
|
259
|
+
return dirname(fileURLToPath(import.meta.url));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function importDefault(pkgPath) {
|
|
263
|
+
let mod = await import(pkgPath);
|
|
264
|
+
return mod.default;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
class GitIgnore {
|
|
268
|
+
constructor() {
|
|
269
|
+
this.ig = ignore({ allowRelativePaths: true });
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// 扫面一个指定的文件夹, 将.gitignore中的规则添加到this.gitignore
|
|
273
|
+
scan(dir) {
|
|
274
|
+
try {
|
|
275
|
+
let data = fs.readFileSync(path.join(dir, ".gitignore"), "utf8");
|
|
276
|
+
this.ig.add(data.split("\n"));
|
|
277
|
+
} catch {
|
|
278
|
+
// if not exist .gitignore
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
contain(filepath) {
|
|
283
|
+
return this.ig.ignores(filepath);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// node.js fs.readdir的封装,会自动扫描.gitignore并过滤
|
|
287
|
+
async readdir(dir, callback) {
|
|
288
|
+
await this.scan(dir);
|
|
289
|
+
|
|
290
|
+
if (this.ig.ignores(dir)) {
|
|
291
|
+
return callback("", []);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return fs.readdir(dir, { withFileTypes: true }, (err, files) => {
|
|
295
|
+
if (err) {
|
|
296
|
+
return callback(err, []);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
let validFiles = files.filter((f) => !this.ig.ignores(f.name));
|
|
300
|
+
return callback(err, validFiles);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function isDirSync(path) {
|
|
306
|
+
let stat = fs.statSync(path);
|
|
307
|
+
return stat.isDirectory();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function isFileExist(path) {
|
|
311
|
+
try {
|
|
312
|
+
fs.accessSync(
|
|
313
|
+
path,
|
|
314
|
+
fs.constants.W_OK | fs.constants.R_OK | fs.constants.F_OK
|
|
315
|
+
);
|
|
316
|
+
return true;
|
|
317
|
+
} catch {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
function urlHostname(url) {
|
|
323
|
+
let u = new URL(url);
|
|
324
|
+
return u.hostname;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export {
|
|
328
|
+
ensureDir,
|
|
329
|
+
copyDotAppDir,
|
|
330
|
+
isValidApp,
|
|
331
|
+
getMetaInfo,
|
|
332
|
+
findAppRootPath,
|
|
333
|
+
archiveFolder,
|
|
334
|
+
mergeYaml,
|
|
335
|
+
checkURL,
|
|
336
|
+
loadFromYaml,
|
|
337
|
+
dumpToYaml,
|
|
338
|
+
importDefault,
|
|
339
|
+
APP_FOLDER,
|
|
340
|
+
APP_CONFIG_FILE,
|
|
341
|
+
APP_SDK_HOSTNAME,
|
|
342
|
+
META_MARK,
|
|
343
|
+
toPair,
|
|
344
|
+
contextDirname,
|
|
345
|
+
isDirSync,
|
|
346
|
+
GitIgnore,
|
|
347
|
+
isFileExist,
|
|
348
|
+
urlHostname,
|
|
349
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lazycatcloud/lzc-cli",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "lazycat cloud developer kit",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"template",
|
|
10
|
+
"scripts",
|
|
11
|
+
"lib"
|
|
12
|
+
],
|
|
13
|
+
"bin": {
|
|
14
|
+
"lzc-cli": "./scripts/cli.js"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"keywords": [
|
|
18
|
+
"lazycat cloud sdk"
|
|
19
|
+
],
|
|
20
|
+
"author": "zac zeng",
|
|
21
|
+
"license": "ISC",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@balena/dockerignore": "^1.0.2",
|
|
24
|
+
"archiver": "^5.3.0",
|
|
25
|
+
"chalk": "^4.1.2",
|
|
26
|
+
"commander": "^8.3.0",
|
|
27
|
+
"dockerfile-ast": "^0.4.1",
|
|
28
|
+
"dockerode": "^3.3.1",
|
|
29
|
+
"ejs": "^3.1.6",
|
|
30
|
+
"envsub": "^4.0.7",
|
|
31
|
+
"execa": "^5.1.1",
|
|
32
|
+
"fast-glob": "^3.2.7",
|
|
33
|
+
"form-data": "^4.0.0",
|
|
34
|
+
"ignore": "^5.2.0",
|
|
35
|
+
"inquirer": "^8.2.0",
|
|
36
|
+
"isbinaryfile": "^4.0.8",
|
|
37
|
+
"js-yaml": "^4.1.0",
|
|
38
|
+
"lodash.get": "^4.4.2",
|
|
39
|
+
"lodash.isarray": "^4.0.0",
|
|
40
|
+
"lodash.merge": "^4.6.2",
|
|
41
|
+
"lodash.mergewith": "^4.6.2",
|
|
42
|
+
"log-update": "^5.0.0",
|
|
43
|
+
"minimist": "^1.2.5",
|
|
44
|
+
"node-fetch": "^2.6.6",
|
|
45
|
+
"ora": "^6.0.1",
|
|
46
|
+
"semver": "^7.3.5",
|
|
47
|
+
"ssh2": "^1.5.0",
|
|
48
|
+
"ssh2-promise": "^1.0.2"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"prettier": "^2.5.0"
|
|
52
|
+
}
|
|
53
|
+
}
|