@lazycatcloud/lzc-cli 1.1.1 → 1.1.4

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 (67) hide show
  1. package/cmds/app.js +137 -0
  2. package/cmds/config.js +55 -0
  3. package/cmds/create.js +55 -0
  4. package/cmds/dev.js +122 -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 +34 -36
  9. package/lib/archiver.js +50 -31
  10. package/lib/box/check_qemu.js +27 -0
  11. package/lib/box/hportal.js +114 -0
  12. package/lib/box/index.js +152 -0
  13. package/lib/box/qemu_vm_mgr.js +625 -0
  14. package/lib/box/schemes/vm_box_system_debian.json +47 -0
  15. package/lib/builder.js +154 -35
  16. package/lib/dev.js +51 -32
  17. package/lib/env.js +276 -57
  18. package/lib/generator.js +31 -0
  19. package/lib/git/git-commit.sh +7 -0
  20. package/lib/git/git-reset.sh +15 -0
  21. package/lib/key.js +14 -11
  22. package/lib/sdk.js +7 -10
  23. package/lib/utils.js +149 -53
  24. package/package.json +18 -5
  25. package/scripts/cli.js +134 -70
  26. package/template/_lazycat/app-config +1 -0
  27. package/template/_lazycat/docker-compose.yml.in +3 -5
  28. package/template/golang/README.md +3 -4
  29. package/template/golang/assets/css/bootstrap-responsive.css +26 -23
  30. package/template/golang/assets/css/bootstrap-responsive.min.css +1065 -1
  31. package/template/golang/assets/css/bootstrap.css +733 -362
  32. package/template/golang/assets/css/bootstrap.min.css +5299 -1
  33. package/template/golang/assets/css/rego.css +17 -17
  34. package/template/golang/assets/js/bootstrap.js +1340 -1311
  35. package/template/golang/assets/js/bootstrap.min.js +1240 -5
  36. package/template/golang/assets/js/rego.js +80 -69
  37. package/template/golang/index.html +61 -59
  38. package/template/ionic_vue3/README.md +46 -0
  39. package/template/ionic_vue3/_eslintrc.cjs +24 -0
  40. package/template/ionic_vue3/_gitignore +29 -0
  41. package/template/ionic_vue3/_vscode/extensions.json +6 -0
  42. package/template/ionic_vue3/capacitor.config.ts +10 -0
  43. package/template/ionic_vue3/env.d.ts +1 -0
  44. package/template/ionic_vue3/index.html +13 -0
  45. package/template/ionic_vue3/ionic.config.json +7 -0
  46. package/template/ionic_vue3/package.json +52 -0
  47. package/template/ionic_vue3/postcss.config.js +6 -0
  48. package/template/ionic_vue3/public/favicon.ico +0 -0
  49. package/template/ionic_vue3/src/App.vue +11 -0
  50. package/template/ionic_vue3/src/assets/logo.svg +1 -0
  51. package/template/ionic_vue3/src/index.css +3 -0
  52. package/template/ionic_vue3/src/main.ts +35 -0
  53. package/template/ionic_vue3/src/router/index.ts +15 -0
  54. package/template/ionic_vue3/src/theme/variables.css +231 -0
  55. package/template/ionic_vue3/src/views/Home.vue +38 -0
  56. package/template/ionic_vue3/tailwind.config.js +7 -0
  57. package/template/ionic_vue3/tsconfig.json +16 -0
  58. package/template/ionic_vue3/tsconfig.vite-config.json +8 -0
  59. package/template/ionic_vue3/vite.config.ts +28 -0
  60. package/template/release/golang/build.sh +1 -2
  61. package/template/release/ionic_vue3/Dockerfile +10 -0
  62. package/template/release/ionic_vue3/build.sh +9 -0
  63. package/template/release/ionic_vue3/docker-compose.yml.in +8 -0
  64. package/template/release/vue/Dockerfile +3 -2
  65. package/template/release/vue/build.sh +4 -2
  66. package/template/vue/README.md +5 -0
  67. package/template/vue/babel.config.js +2 -4
package/cmds/app.js ADDED
@@ -0,0 +1,137 @@
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
+ import { debuglog } from "util";
18
+
19
+ const debug = debuglog("cmd/app");
20
+
21
+ // this set of api target box
22
+ class App {
23
+ constructor(context) {
24
+ this.context = context;
25
+
26
+ const { appDir, isTemplate } = isValidApp(process.cwd());
27
+ if (!appDir) {
28
+ console.log(chalk.red("未识别懒猫云应用"));
29
+ process.exit(1);
30
+ }
31
+ this.appPath = appDir;
32
+ this.isTemplate = isTemplate;
33
+ env.load(process.cwd());
34
+ }
35
+
36
+ async deploy() {
37
+ try {
38
+ if (this.isTemplate) {
39
+ const outDir = await this.prepare();
40
+
41
+ await this.archiveAndInstall(outDir);
42
+ } else {
43
+ await sdkEnv.ensure();
44
+
45
+ await this.archiveAndInstall(this.appPath);
46
+ }
47
+ } catch (e) {
48
+ throw e;
49
+ }
50
+ }
51
+
52
+ async uninstall() {
53
+ let api;
54
+ const sdkUrl = sdkEnv.sdkUrl;
55
+ if (this.isTemplate) {
56
+ api = new BoxAPI(env.get("APP_ID"), sdkUrl);
57
+ } else {
58
+ const metaInfo = getMetaInfo(
59
+ path.join(this.appPath, "docker-compose.yml")
60
+ );
61
+ api = new BoxAPI(metaInfo.id, sdkUrl);
62
+ }
63
+
64
+ try {
65
+ await api.uninstall();
66
+ } catch (e) {
67
+ throw e;
68
+ }
69
+ }
70
+
71
+ async prepare() {
72
+ await this.prepareBuildEnv();
73
+
74
+ const outputDir = path.join(this.appPath, "output/release/");
75
+ const buildDir = path.join(process.cwd(), env.get("BUILD_CONTEXT"));
76
+
77
+ ensureDir(outputDir);
78
+
79
+ await new Key().ensure(sdkEnv.sdkUrl);
80
+
81
+ const envAll = {
82
+ ...env.all,
83
+ APP_IMAGE_NAME: env.get("APP_ID"),
84
+ };
85
+
86
+ // exec pre build
87
+ await execPreBuild(buildDir, process.cwd(), envAll);
88
+
89
+ // do image build
90
+ await new Builder(envAll).buildImage(process.cwd(), buildDir);
91
+
92
+ // do archive
93
+ const archiver = new Archiver(outputDir);
94
+ console.log("appPath", this.appPath);
95
+ await archiver.load(this.appPath);
96
+ await archiver.add(buildDir);
97
+
98
+ await archiver.finalize();
99
+
100
+ return outputDir;
101
+ }
102
+
103
+ async prepareBuildEnv() {
104
+ await sdkEnv.ensure();
105
+ await env.ensure([{ name: "HTTP_SERVICE_PORT" }]);
106
+ }
107
+
108
+ // archive current dir and install
109
+ async archiveAndInstall(dir) {
110
+ const metaInfo = getMetaInfo(path.join(dir, "docker-compose.yml"));
111
+ const api = new BoxAPI(metaInfo.id, sdkEnv.sdkUrl);
112
+ let out;
113
+ try {
114
+ const out = await archiveFolder(dir);
115
+ console.log(chalk.green("开始部署应用"));
116
+ await api.install(out.path);
117
+ await api.checkStatus();
118
+
119
+ console.log(
120
+ `请在浏览器中访问 ${chalk.green(
121
+ sdkEnv.sdkUrl.replace(/sdk/, metaInfo.id)
122
+ )}`
123
+ );
124
+ } catch (e) {
125
+ throw e;
126
+ } finally {
127
+ if (out) fs.rmSync(out.path, { recursive: true });
128
+ }
129
+ }
130
+ }
131
+
132
+ const app = new App();
133
+
134
+ export default {
135
+ deploy: () => app.deploy(),
136
+ uninstall: () => app.uninstall(),
137
+ };
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,122 @@
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
+
8
+ // this set of api target box
9
+ class Develop {
10
+ constructor(context) {
11
+ this.context = context;
12
+
13
+ const { appDir, isTemplate } = isValidApp(process.cwd());
14
+ if (!appDir) {
15
+ console.log(chalk.red("未识别懒猫云应用"));
16
+ process.exit(1);
17
+ }
18
+
19
+ this.appPath = appDir;
20
+ this.isTemplate = isTemplate;
21
+ env.load(process.cwd());
22
+ }
23
+
24
+ async devShell({ build }) {
25
+ let appdev = new Dev(env.get("APP_ID"), this.appPath);
26
+
27
+ if (build) {
28
+ appdev.reset();
29
+ }
30
+
31
+ // 当如果已经连接。直接连接ssh
32
+ if (appdev.hasShellStatus()) {
33
+ try {
34
+ await appdev.directShell();
35
+ return;
36
+ } catch (e) {
37
+ return Promise.reject(e);
38
+ }
39
+ }
40
+
41
+ // 重新部署dev app container
42
+ try {
43
+ await sdkEnv.ensure();
44
+ const { APP_ID: appId } = await env.ensure([{ name: "APP_ID" }]);
45
+
46
+ console.debug(sdkEnv.sdkUrl);
47
+ const api = new BoxAPI(appId, sdkEnv.sdkUrl);
48
+
49
+ let out = await appdev.fakeApp("shell");
50
+
51
+ console.debug("设置端口转发");
52
+ await api.install(out.path, { build: true });
53
+ await api.checkStatus();
54
+
55
+ await appdev.shell();
56
+ return;
57
+ } catch (e) {
58
+ return Promise.reject(e);
59
+ }
60
+ }
61
+
62
+ async devPortForward(localaddr) {
63
+ console.log("逻辑需要重新梳理");
64
+ process.exit();
65
+ // try {
66
+ // await this.ensureEnv([
67
+ // {
68
+ // name: "DEV_PORT",
69
+ // type: "input",
70
+ // message: "开发暴露端口",
71
+ // default: "8080",
72
+ // },
73
+ // {
74
+ // name: "DEV_CMD",
75
+ // type: "input",
76
+ // default: "npm run serve",
77
+ // message: "开发运行命令",
78
+ // },
79
+ // ]);
80
+ //
81
+ // await this.ensureSDK();
82
+ //
83
+ // console.debug(env.get("SDK_URL"));
84
+ // const api = new BoxAPI(env.get("APP_ID"), env.get("SDK_URL"));
85
+ //
86
+ // let appdev = new Dev(env.get("APP_ID"), this.appPath);
87
+ //
88
+ // let out = await appdev.fakeApp("devforward");
89
+ //
90
+ // console.debug("设置端口转发");
91
+ // await api.install(out.path, { build: true });
92
+ // await api.checkStatus();
93
+ //
94
+ // console.log(
95
+ // `请在浏览器中访问 ${chalk.green(
96
+ // env.get("SDK_URL").replace(/sdk/, env.get("APP_ID"))
97
+ // )}`
98
+ // );
99
+ //
100
+ // await appdev.portForward(localaddr);
101
+ // } catch (e) {
102
+ // throw e;
103
+ // }
104
+ }
105
+ }
106
+
107
+ const dev = new Develop();
108
+
109
+ export default {
110
+ dev: (addr) => dev.devPortForward(addr),
111
+ devShell: async (options) => {
112
+ try {
113
+ await dev.devShell(options);
114
+ } catch (e) {
115
+ console.log(chalk.red(e));
116
+ process.exit(1);
117
+ }
118
+
119
+ // 需要显示的退出,因为当shell退出后,rsync和watch的进程还在执行
120
+ // process.exit();
121
+ },
122
+ };
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
+ };
@@ -0,0 +1,116 @@
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
+ }