@lazycatcloud/lzc-cli 1.1.3 → 1.1.6

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.
Files changed (59) hide show
  1. package/cmds/app.js +133 -0
  2. package/cmds/config.js +55 -0
  3. package/cmds/create.js +55 -0
  4. package/cmds/dev.js +130 -0
  5. package/cmds/init.js +125 -0
  6. package/cmds/log.js +103 -0
  7. package/cmds/publish.js +116 -0
  8. package/lib/api.js +21 -7
  9. package/lib/app/index.js +92 -0
  10. package/lib/app/lpk_build.js +229 -0
  11. package/lib/app/lpk_create.js +201 -0
  12. package/lib/app/lpk_devshell.js +621 -0
  13. package/lib/app/lpk_installer.js +67 -0
  14. package/lib/archiver.js +0 -42
  15. package/lib/autologin.js +84 -0
  16. package/lib/box/check_qemu.js +50 -0
  17. package/lib/box/hportal.js +8 -1
  18. package/lib/box/index.js +35 -15
  19. package/lib/box/qemu_vm_mgr.js +142 -56
  20. package/lib/builder.js +14 -3
  21. package/lib/dev.js +12 -6
  22. package/lib/env.js +10 -6
  23. package/lib/generator.js +2 -2
  24. package/lib/git/git-commit.sh +3 -3
  25. package/lib/sdk.js +29 -5
  26. package/lib/utils.js +69 -46
  27. package/package.json +13 -6
  28. package/scripts/cli.js +92 -13
  29. package/template/_lazycat/debug/shell/Dockerfile +5 -3
  30. package/template/_lazycat/debug/shell/docker-compose.override.yml.in +2 -12
  31. package/template/_lazycat/debug/shell/entrypoint.sh +3 -1
  32. package/template/_lpk/Dockerfile.in +8 -0
  33. package/template/_lpk/devshell/Dockerfile +18 -0
  34. package/template/_lpk/devshell/build.sh +5 -0
  35. package/template/_lpk/devshell/entrypoint.sh +8 -0
  36. package/template/_lpk/devshell/sshd_config +117 -0
  37. package/template/_lpk/manifest.yml.in +17 -0
  38. package/template/_lpk/sync/Dockerfile +16 -0
  39. package/template/_lpk/sync/build.sh +5 -0
  40. package/template/_lpk/sync/entrypoint.sh +8 -0
  41. package/template/_lpk/sync/sshd_config +117 -0
  42. package/template/_lpk/sync.manifest.yml.in +3 -0
  43. package/template/golang/build.sh +6 -0
  44. package/template/golang/lzc-build.yml +52 -0
  45. package/template/ionic_vue3/lzc-build.yml +53 -0
  46. package/template/ionic_vue3/vite.config.ts +1 -1
  47. package/template/release/golang/build.sh +1 -1
  48. package/template/release/ionic_vue3/Dockerfile +1 -1
  49. package/template/release/ionic_vue3/build.sh +1 -3
  50. package/template/release/ionic_vue3/docker-compose.yml.in +0 -5
  51. package/template/release/vue/Dockerfile +1 -1
  52. package/template/release/vue/build.sh +2 -3
  53. package/template/release/vue/docker-compose.yml.in +0 -5
  54. package/template/vue/lzc-build.yml +53 -0
  55. package/template/vue/src/main.js +3 -14
  56. package/template/vue/vue.config.js +16 -0
  57. package/scripts/auto-completion.sh +0 -46
  58. package/template/_lazycat/debug/shell/nginx.conf.template +0 -64
  59. package/template/vue/src/lzc.js +0 -110
package/cmds/app.js ADDED
@@ -0,0 +1,133 @@
1
+ import process from "process";
2
+ import path from "path";
3
+ import {
4
+ archiveFolder,
5
+ isValidApp,
6
+ getMetaInfo,
7
+ ensureDir,
8
+ } from "../lib/utils.js";
9
+ import fs from "fs";
10
+ import chalk from "chalk";
11
+ import Builder, { execPreBuild } from "../lib/builder.js";
12
+ import env, { sdkEnv } from "../lib/env.js";
13
+ import BoxAPI from "../lib/api.js";
14
+ import Archiver from "../lib/archiver.js";
15
+ import Key from "../lib/key.js";
16
+
17
+ // this set of api target box
18
+ class App {
19
+ constructor(context) {
20
+ this.context = context;
21
+
22
+ const { appDir, isTemplate } = isValidApp(process.cwd());
23
+ if (!appDir) {
24
+ console.log(chalk.red("未识别懒猫云应用"));
25
+ process.exit(1);
26
+ }
27
+ this.appPath = appDir;
28
+ this.isTemplate = isTemplate;
29
+ env.load(process.cwd());
30
+ }
31
+
32
+ async deploy() {
33
+ try {
34
+ if (this.isTemplate) {
35
+ const outDir = await this.prepare();
36
+
37
+ await this.archiveAndInstall(outDir);
38
+ } else {
39
+ await sdkEnv.ensure();
40
+
41
+ await this.archiveAndInstall(this.appPath);
42
+ }
43
+ } catch (e) {
44
+ throw e;
45
+ }
46
+ }
47
+
48
+ async uninstall() {
49
+ let api;
50
+ const sdkUrl = sdkEnv.sdkUrl;
51
+ if (this.isTemplate) {
52
+ api = new BoxAPI(env.get("APP_ID"), sdkUrl);
53
+ } else {
54
+ const metaInfo = getMetaInfo(
55
+ path.join(this.appPath, "docker-compose.yml")
56
+ );
57
+ api = new BoxAPI(metaInfo.id, sdkUrl);
58
+ }
59
+
60
+ try {
61
+ await api.uninstall();
62
+ } catch (e) {
63
+ throw e;
64
+ }
65
+ }
66
+
67
+ async prepare() {
68
+ await this.prepareBuildEnv();
69
+
70
+ const outputDir = path.join(this.appPath, "output/release/");
71
+ const buildDir = path.join(process.cwd(), env.get("BUILD_CONTEXT"));
72
+
73
+ ensureDir(outputDir);
74
+
75
+ await new Key().ensure(sdkEnv.sdkUrl);
76
+
77
+ const envAll = {
78
+ ...env.all,
79
+ APP_IMAGE_NAME: env.get("APP_ID"),
80
+ };
81
+
82
+ // exec pre build
83
+ await execPreBuild(buildDir, process.cwd(), envAll);
84
+
85
+ // do image build
86
+ await new Builder(envAll).buildImage(process.cwd(), buildDir);
87
+
88
+ // do archive
89
+ const archiver = new Archiver(outputDir);
90
+ console.log("appPath", this.appPath);
91
+ await archiver.load(this.appPath);
92
+ await archiver.add(buildDir);
93
+
94
+ await archiver.finalize();
95
+
96
+ return outputDir;
97
+ }
98
+
99
+ async prepareBuildEnv() {
100
+ await sdkEnv.ensure();
101
+ await env.ensure([{ name: "HTTP_SERVICE_PORT" }]);
102
+ }
103
+
104
+ // archive current dir and install
105
+ async archiveAndInstall(dir) {
106
+ const metaInfo = getMetaInfo(path.join(dir, "docker-compose.yml"));
107
+ const api = new BoxAPI(metaInfo.id, sdkEnv.sdkUrl);
108
+ let out;
109
+ try {
110
+ const out = await archiveFolder(dir);
111
+ console.log(chalk.green("开始部署应用"));
112
+ await api.applyZip(out.path);
113
+ await api.checkStatus();
114
+
115
+ console.log(
116
+ `请在浏览器中访问 ${chalk.green(
117
+ sdkEnv.sdkUrl.replace(/sdk/, metaInfo.id)
118
+ )}`
119
+ );
120
+ } catch (e) {
121
+ throw e;
122
+ } finally {
123
+ if (out) fs.rmSync(out.path, { recursive: true });
124
+ }
125
+ }
126
+ }
127
+
128
+ const app = new App();
129
+
130
+ export default {
131
+ deploy: () => app.deploy(),
132
+ uninstall: () => app.uninstall(),
133
+ };
package/cmds/config.js ADDED
@@ -0,0 +1,55 @@
1
+ import process from "process";
2
+ import { isValidApp } from "../lib/utils.js";
3
+ import env, { sdkEnv } from "../lib/env.js";
4
+ import { debuglog } from "util";
5
+
6
+ const debug = debuglog("cmd/config");
7
+
8
+ class Config {
9
+ constructor(context) {
10
+ this.context = context;
11
+ }
12
+ async exec() {
13
+ const [key, value, options] = this.context;
14
+
15
+ isValidApp(process.cwd());
16
+ env.load(process.cwd());
17
+
18
+ // // lzc-cli config
19
+ if (!key && !value) {
20
+ console.log(env.stringify());
21
+ return;
22
+ }
23
+
24
+ // lzc-cli config SDK_URL
25
+ if (key && !value) {
26
+ if (env.get(key)) {
27
+ console.log(env.get(key));
28
+ }
29
+ return;
30
+ }
31
+
32
+ if (key && value) {
33
+ const pair = Object.fromEntries([[key, value]]);
34
+ // if (pair["SDK_URL"]) {
35
+ // pair["SDK_URL"] = pair["SDK_URL"].replace(/\/$/, "");
36
+ //
37
+ // try {
38
+ // // 这里只需要测试sdk的合法性即可, 不需要处理安装的逻辑
39
+ // await sdkEnv.checkConnection(pair["SDK_URL"]);
40
+ // } catch (e) {
41
+ // console.log(e.message);
42
+ // debug("got error when check connection %s", e);
43
+ // process.exit();
44
+ // }
45
+ // }
46
+ env.set(pair, options.global);
47
+ }
48
+ }
49
+ }
50
+
51
+ export default {
52
+ config: async (context) => {
53
+ return await new Config(context).exec();
54
+ },
55
+ };
package/cmds/create.js ADDED
@@ -0,0 +1,55 @@
1
+ import chalk from "chalk";
2
+ import { Init, chooseTemplate } from "./init.js";
3
+ import path from "path";
4
+ import Generator, { TemplateConfig } from "../lib/generator.js";
5
+ import fs from "fs";
6
+ import inquirer from "inquirer";
7
+ import { parse2CorrectName } from "../lib/utils.js";
8
+ const fsPromises = fs.promises;
9
+ class Create {
10
+ constructor({ name }) {
11
+ this.name = name;
12
+ }
13
+
14
+ async exec() {
15
+ const type = await chooseTemplate();
16
+ this.init = new Init({
17
+ cwd: path.join(process.cwd(), this.name),
18
+ type: type,
19
+ });
20
+ if (TemplateConfig[type]) {
21
+ const config = TemplateConfig[type];
22
+ const init = this.init;
23
+ const answers = await init.askQuestions();
24
+ console.log(chalk.green(`初始化项目 ${this.name}`));
25
+ await Generator().generate(type, this.name);
26
+ console.log(chalk.green("设置懒猫云应用"));
27
+ await init.createTemplates(answers);
28
+ await init.createReleaseFolder();
29
+ await config.after(this.name);
30
+ }
31
+ }
32
+ }
33
+
34
+ export default async (context) => {
35
+ context.name = parse2CorrectName(context.name);
36
+ await fsPromises.access(process.cwd() + "/" + context.name).then(
37
+ async () => {
38
+ const questions = [
39
+ {
40
+ name: "override",
41
+ type: "input",
42
+ default: "n",
43
+ message: "项目已存在,是否覆盖(y/n): ",
44
+ },
45
+ ];
46
+ const answers = await inquirer.prompt(questions);
47
+ if (answers.override.toLowerCase() === "y") {
48
+ return new Create(context).exec();
49
+ }
50
+ },
51
+ () => {
52
+ return new Create(context).exec();
53
+ }
54
+ );
55
+ };
package/cmds/dev.js ADDED
@@ -0,0 +1,130 @@
1
+ import process from "process";
2
+ import { isValidApp } from "../lib/utils.js";
3
+ import chalk from "chalk";
4
+ import Dev from "../lib/dev.js";
5
+ import env, { sdkEnv } from "../lib/env.js";
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";
10
+
11
+ // this set of api target box
12
+ class Develop {
13
+ constructor(context) {
14
+ this.context = context;
15
+
16
+ const { appDir, isTemplate } = isValidApp(process.cwd());
17
+ if (!appDir) {
18
+ console.log(chalk.red("未识别懒猫云应用"));
19
+ process.exit(1);
20
+ }
21
+
22
+ this.appPath = appDir;
23
+ this.isTemplate = isTemplate;
24
+ env.load(process.cwd());
25
+ }
26
+
27
+ async devShell({ build }) {
28
+ let appdev = new Dev(env.get("APP_ID"), this.appPath);
29
+
30
+ await sdkEnv.ensure();
31
+
32
+ // 确保 sdk 能通过公钥正常访问
33
+ await new Key().ensure(sdkEnv.sdkUrl);
34
+
35
+ if (build) {
36
+ appdev.reset();
37
+ await dockerPullLzcAppsImage(sdkEnv.sdkHostName);
38
+ }
39
+
40
+ // 当如果已经连接。直接连接ssh
41
+ if (appdev.hasShellStatus()) {
42
+ try {
43
+ await appdev.directShell();
44
+ return;
45
+ } catch (e) {
46
+ return Promise.reject(e);
47
+ }
48
+ }
49
+
50
+ // 重新部署dev app container
51
+ try {
52
+ const { APP_ID: appId } = await env.ensure([{ name: "APP_ID" }]);
53
+
54
+ logger.debug(sdkEnv.sdkUrl);
55
+ const api = new BoxAPI(appId, sdkEnv.sdkUrl);
56
+
57
+ let out = await appdev.fakeApp("shell");
58
+
59
+ logger.debug("设置端口转发");
60
+ await api.applyZip(out.path, { build: true });
61
+ await api.checkStatus();
62
+
63
+ await appdev.shell();
64
+ return;
65
+ } catch (e) {
66
+ return Promise.reject(e);
67
+ }
68
+ }
69
+
70
+ async devPortForward(localaddr) {
71
+ console.log("逻辑需要重新梳理");
72
+ process.exit();
73
+ // try {
74
+ // await this.ensureEnv([
75
+ // {
76
+ // name: "DEV_PORT",
77
+ // type: "input",
78
+ // message: "开发暴露端口",
79
+ // default: "8080",
80
+ // },
81
+ // {
82
+ // name: "DEV_CMD",
83
+ // type: "input",
84
+ // default: "npm run serve",
85
+ // message: "开发运行命令",
86
+ // },
87
+ // ]);
88
+ //
89
+ // await this.ensureSDK();
90
+ //
91
+ // console.debug(env.get("SDK_URL"));
92
+ // const api = new BoxAPI(env.get("APP_ID"), env.get("SDK_URL"));
93
+ //
94
+ // let appdev = new Dev(env.get("APP_ID"), this.appPath);
95
+ //
96
+ // let out = await appdev.fakeApp("devforward");
97
+ //
98
+ // console.debug("设置端口转发");
99
+ // await api.install(out.path, { build: true });
100
+ // await api.checkStatus();
101
+ //
102
+ // console.log(
103
+ // `请在浏览器中访问 ${chalk.green(
104
+ // env.get("SDK_URL").replace(/sdk/, env.get("APP_ID"))
105
+ // )}`
106
+ // );
107
+ //
108
+ // await appdev.portForward(localaddr);
109
+ // } catch (e) {
110
+ // throw e;
111
+ // }
112
+ }
113
+ }
114
+
115
+ const dev = new Develop();
116
+
117
+ export default {
118
+ dev: (addr) => dev.devPortForward(addr),
119
+ devShell: async (options) => {
120
+ try {
121
+ await dev.devShell(options);
122
+ } catch (e) {
123
+ console.log(chalk.red(e));
124
+ process.exit(1);
125
+ }
126
+
127
+ // 需要显示的退出,因为当shell退出后,rsync和watch的进程还在执行
128
+ // process.exit();
129
+ },
130
+ };
package/cmds/init.js ADDED
@@ -0,0 +1,125 @@
1
+ import path from "path";
2
+ import inquirer from "inquirer";
3
+ import generator, { TemplateConfig } from "../lib/generator.js";
4
+ import { APP_FOLDER } from "../lib/utils.js";
5
+ import env from "../lib/env.js";
6
+ import fs from "fs";
7
+ let fsPromises = fs.promises;
8
+ import { debuglog } from "util";
9
+
10
+ const debug = debuglog("cmd/init");
11
+
12
+ export class Init {
13
+ constructor(context) {
14
+ this.context = context;
15
+ this.type = context.type;
16
+ }
17
+
18
+ async askQuestions() {
19
+ const questions = [
20
+ {
21
+ name: "APP_ID",
22
+ type: "input",
23
+ default: () => {
24
+ const name = path.basename(this.context.cwd);
25
+ return name;
26
+ },
27
+ message: "应用名称",
28
+ validate: (input) => {
29
+ if (!/^([a-z]|[0-9]|[\-])+$/.test(input)) {
30
+ return "应用名称只能包含(减号,小写字母,数字),请重新输入";
31
+ }
32
+ return true;
33
+ },
34
+ },
35
+ {
36
+ name: "APP_DESCRIPTION",
37
+ type: "input",
38
+ default: (answers) => {
39
+ return answers["APP_ID"];
40
+ },
41
+ message: "应用描述",
42
+ },
43
+ ];
44
+ return inquirer.prompt(questions);
45
+ }
46
+
47
+ async createTemplates(answers) {
48
+ answers["APP_VERSION"] = "0.0.1";
49
+ env.load(path.join(this.context.cwd));
50
+ await generator(answers).generate(
51
+ APP_FOLDER.replace(".", "_"),
52
+ this.context.cwd,
53
+ {
54
+ prefix: APP_FOLDER,
55
+ }
56
+ );
57
+ debug(answers);
58
+ env.set(answers);
59
+ this.setDefaultEnvs(env);
60
+ }
61
+
62
+ async createReleaseFolder() {
63
+ await generator({}).generate(
64
+ "release/" + this.type,
65
+ path.join(this.context.cwd, APP_FOLDER),
66
+ {
67
+ prefix: "release",
68
+ }
69
+ );
70
+ }
71
+
72
+ async setDefaultEnvs(env) {
73
+ debug(TemplateConfig[this.type].defaultEnvs);
74
+ env.set(TemplateConfig[this.type].defaultEnvs);
75
+ }
76
+
77
+ async create() {
78
+ if (!this.type) {
79
+ this.type = await chooseTemplate();
80
+ }
81
+ const createConfig = async () => {
82
+ const answers = await this.askQuestions();
83
+ await this.createTemplates(answers);
84
+ await this.createReleaseFolder();
85
+ };
86
+ // .lazycat文件已存在时提示用户是否需要覆盖
87
+ await fsPromises
88
+ .access(
89
+ this.context.cwd + "/.lazycat/",
90
+ fs.constants.F_OK | fs.constants.W_OK
91
+ )
92
+ .then(
93
+ async () => {
94
+ const questions = [
95
+ {
96
+ name: "override",
97
+ type: "input",
98
+ default: "n",
99
+ message: "懒猫云已配置,是否覆盖(y/n): ",
100
+ },
101
+ ];
102
+ const answers = await inquirer.prompt(questions);
103
+ if (answers.override.toLowerCase() === "y") {
104
+ createConfig();
105
+ }
106
+ },
107
+ async () => {
108
+ createConfig();
109
+ }
110
+ );
111
+ }
112
+ }
113
+
114
+ export async function chooseTemplate() {
115
+ return (
116
+ await inquirer.prompt([
117
+ {
118
+ name: "type",
119
+ message: "选择项目构建模板",
120
+ type: "list",
121
+ choices: ["vue", "golang", "ionic_vue3"],
122
+ },
123
+ ])
124
+ )["type"];
125
+ }
package/cmds/log.js ADDED
@@ -0,0 +1,103 @@
1
+ import { readFileSync } from "fs";
2
+ import { Client } from "ssh2";
3
+ import os from "os";
4
+ import path from "path";
5
+ import process from "process";
6
+ import { isValidApp, META_MARK } from "../lib/utils.js";
7
+ import yaml from "js-yaml";
8
+ import env, { sdkEnv } from "../lib/env.js";
9
+ import chalk from "chalk";
10
+ import { SSHClient, connectOptions } from "../lib/sdk.js";
11
+
12
+ function StreamPromise(stream) {
13
+ let data = "";
14
+ return new Promise((resolve, reject) => {
15
+ stream
16
+ .on("data", (d) => {
17
+ data = d;
18
+ })
19
+ .on("close", (code, signal) => {
20
+ resolve(String(data));
21
+ })
22
+ .stderr.on("data", (data) => {
23
+ reject(String(data));
24
+ });
25
+ });
26
+ }
27
+
28
+ export async function monitor(projectName, options = {}) {
29
+ env.load(process.cwd());
30
+
31
+ await sdkEnv.ensure();
32
+
33
+ const sdk_url = sdkEnv.sdkUrl;
34
+
35
+ if (!sdk_url) {
36
+ console.log(chalk.red("无法连接SDK, 请确保已设置 DEFAULT_BOXNAME"));
37
+ process.exit(1);
38
+ }
39
+
40
+ const host = new URL(sdk_url).hostname;
41
+ if (projectName) {
42
+ await new Log(host, projectName).start();
43
+ } else {
44
+ const name = tryGuessProject(options.user);
45
+ // console.log(chalk.green("guess project name " + name));
46
+ await new Log(host, name).start();
47
+ }
48
+ }
49
+
50
+ function tryGuessProject(user) {
51
+ const { appDir, isTemplate } = isValidApp(process.cwd());
52
+ if (!appDir) {
53
+ console.log(chalk.red("无法识别为懒猫云应用"));
54
+ process.exit(1);
55
+ }
56
+
57
+ let info;
58
+ let appId;
59
+ if (isTemplate) {
60
+ info = yaml.load(readFileSync(path.join(appDir, "docker-compose.yml.in")));
61
+ appId = env.get("APP_ID");
62
+ } else {
63
+ info = yaml.load(readFileSync(path.join(appDir, "docker-compose.yml")));
64
+ appId = info[META_MARK]["id"];
65
+ }
66
+ return `lzc-${user || ""}-${appId}`;
67
+ }
68
+
69
+ class Log {
70
+ constructor(host, project) {
71
+ this.project = project;
72
+ this.host = host;
73
+ }
74
+
75
+ async start() {
76
+ const opts = await connectOptions(this.host);
77
+ const client = new SSHClient(opts);
78
+ try {
79
+ await client.connect();
80
+ let stream = await client.exec(
81
+ `docker-compose ls | grep -wE '^${this.project}'`
82
+ );
83
+
84
+ const result = await StreamPromise(stream);
85
+ if (result != "") {
86
+ stream = await client.exec(
87
+ `docker-compose -p ${this.project} logs -f`,
88
+ { pty: true }
89
+ );
90
+ stream.stdout.pipe(process.stdout);
91
+ stream.stderr.pipe(process.stdout);
92
+ } else {
93
+ client.close();
94
+ }
95
+ } catch (e) {
96
+ client.close();
97
+ }
98
+ }
99
+ }
100
+
101
+ export default {
102
+ monitor,
103
+ };