@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.
Files changed (105) hide show
  1. package/README.md +69 -11
  2. package/lib/api.js +71 -39
  3. package/lib/app/index.js +76 -21
  4. package/lib/app/lpk_build.js +95 -63
  5. package/lib/app/lpk_create.js +63 -41
  6. package/lib/app/lpk_create_generator.js +202 -0
  7. package/lib/app/lpk_devshell.js +393 -328
  8. package/lib/app/lpk_devshell_docker.js +211 -0
  9. package/lib/app/lpk_installer.js +63 -26
  10. package/lib/app/lpk_log.js +68 -0
  11. package/lib/app/lpk_status.js +18 -0
  12. package/lib/app/lpk_uninstall.js +19 -0
  13. package/lib/appstore/index.js +37 -0
  14. package/lib/appstore/login.js +96 -93
  15. package/lib/appstore/publish.js +62 -0
  16. package/lib/autologin.js +0 -78
  17. package/lib/box/api/clientapi.js +1322 -0
  18. package/lib/box/api/empty.js +35 -0
  19. package/lib/box/check_qemu.js +1 -0
  20. package/lib/box/index.js +41 -94
  21. package/lib/box/qemu_vm_mgr.js +208 -239
  22. package/lib/box/schemes/vm_box_system_debian.json +1 -1
  23. package/lib/docker-compose.js +1 -2
  24. package/lib/env.js +19 -101
  25. package/lib/key.js +1 -0
  26. package/lib/sdk.js +10 -25
  27. package/lib/utils.js +156 -132
  28. package/package.json +19 -10
  29. package/scripts/cli.js +14 -135
  30. package/template/_lpk/README.md +31 -0
  31. package/template/_lpk/exec.sh +19 -0
  32. package/template/_lpk/golang.manifest.yml.in +16 -0
  33. package/template/_lpk/lazycat.png +0 -0
  34. package/template/_lpk/lite.manifest.yml.in +19 -0
  35. package/template/_lpk/local_devshell/Dockerfile +16 -0
  36. package/template/_lpk/local_devshell/build.sh +5 -0
  37. package/template/_lpk/local_devshell/entrypoint.sh +87 -0
  38. package/template/{_lazycat/debug/shell → _lpk/local_devshell}/sshd_config +8 -8
  39. package/template/_lpk/manifest.yml.in +0 -1
  40. package/template/{vue/lzc-build.yml → _lpk/vue.lzc-build.yml.in} +9 -1
  41. package/template/golang/README.md +0 -2
  42. package/template/golang/_gitignore +2 -0
  43. package/template/golang/build.sh +6 -0
  44. package/template/golang/lazycat.png +0 -0
  45. package/template/golang/lzc-build.yml +9 -1
  46. package/template/golang/rego.go +15 -16
  47. package/template/golang/rego_test.go +13 -0
  48. package/template/ionic_vue3/lazycat.png +0 -0
  49. package/template/ionic_vue3/lzc-build.yml +9 -1
  50. package/template/lite/error_pages/502.html.tpl +13 -0
  51. package/template/lite/lazycat.png +0 -0
  52. package/template/lite/lzc-build.yml +60 -0
  53. package/cmds/app.js +0 -133
  54. package/cmds/config.js +0 -55
  55. package/cmds/create.js +0 -55
  56. package/cmds/dev.js +0 -130
  57. package/cmds/init.js +0 -125
  58. package/cmds/log.js +0 -103
  59. package/cmds/publish.js +0 -116
  60. package/lib/archiver.js +0 -105
  61. package/lib/box/hportal.js +0 -120
  62. package/lib/builder.js +0 -313
  63. package/lib/dev.js +0 -314
  64. package/lib/generator.js +0 -146
  65. package/template/_lazycat/_gitignore +0 -1
  66. package/template/_lazycat/app-config +0 -1
  67. package/template/_lazycat/debug/devforward/50x.html +0 -30
  68. package/template/_lazycat/debug/devforward/Dockerfile +0 -16
  69. package/template/_lazycat/debug/devforward/docker-compose.override.yml.in +0 -11
  70. package/template/_lazycat/debug/devforward/entrypoint.sh +0 -10
  71. package/template/_lazycat/debug/devforward/nginx.conf.template +0 -56
  72. package/template/_lazycat/debug/devforward/sshd_config +0 -116
  73. package/template/_lazycat/debug/shell/50x.html +0 -32
  74. package/template/_lazycat/debug/shell/Dockerfile +0 -18
  75. package/template/_lazycat/debug/shell/build.sh +0 -15
  76. package/template/_lazycat/debug/shell/docker-compose.override.yml.in +0 -13
  77. package/template/_lazycat/debug/shell/entrypoint.sh +0 -12
  78. package/template/_lazycat/docker-compose.yml.in +0 -15
  79. package/template/_lazycat/icon.svg +0 -1
  80. package/template/_lazycat/screenshot.png +0 -0
  81. package/template/_lpk/sync/Dockerfile +0 -16
  82. package/template/_lpk/sync/build.sh +0 -5
  83. package/template/_lpk/sync/entrypoint.sh +0 -8
  84. package/template/_lpk/sync/sshd_config +0 -117
  85. package/template/_lpk/sync.manifest.yml.in +0 -3
  86. package/template/release/golang/Dockerfile +0 -18
  87. package/template/release/golang/build.sh +0 -13
  88. package/template/release/ionic_vue3/Dockerfile +0 -10
  89. package/template/release/ionic_vue3/build.sh +0 -7
  90. package/template/release/ionic_vue3/docker-compose.yml.in +0 -3
  91. package/template/release/vue/Dockerfile +0 -10
  92. package/template/release/vue/build.sh +0 -10
  93. package/template/release/vue/docker-compose.yml.in +0 -3
  94. package/template/vue/README.md +0 -29
  95. package/template/vue/_dockerignore +0 -1
  96. package/template/vue/babel.config.js +0 -3
  97. package/template/vue/package.json +0 -43
  98. package/template/vue/public/favicon.ico +0 -0
  99. package/template/vue/public/index.html +0 -33
  100. package/template/vue/src/App.vue +0 -39
  101. package/template/vue/src/main.js +0 -8
  102. package/template/vue/src/todo.vue +0 -640
  103. package/template/vue/src/top-bar.vue +0 -100
  104. package/template/vue/src/webdav.vue +0 -183
  105. package/template/vue/vue.config.js +0 -21
package/lib/env.js CHANGED
@@ -6,14 +6,13 @@ import {
6
6
  GLOBAL_CONFIG_DIR,
7
7
  APP_FOLDER,
8
8
  } from "./utils.js";
9
- import { debuglog } from "util";
10
9
  import inquirer from "inquirer";
11
10
  import chalk from "chalk";
12
- import { request } from "./autologin.js";
13
- const debug = debuglog("lib/env");
11
+ import logger from "loglevel";
12
+ import fetch from "node-fetch";
14
13
 
15
14
  const GLOBAL_CONFIG_NAME = "box-config.json";
16
- const permitGlobalEnv = [
15
+ const allPermitEnv = [
17
16
  {
18
17
  name: "DEFAULT_BOXNAME",
19
18
  type: "input",
@@ -24,91 +23,6 @@ const permitGlobalEnv = [
24
23
  },
25
24
  ];
26
25
 
27
- const _permitLocalEnv = [
28
- {
29
- name: "APP_ID",
30
- type: "input",
31
- message: "应用名称",
32
- },
33
- {
34
- name: "APP_DESCRIPTION",
35
- type: "input",
36
- message: "应用描述",
37
- },
38
- {
39
- name: "BUILD_CONTEXT",
40
- message: "",
41
- type: "input",
42
- },
43
- {
44
- name: "DEV_CONTEXT",
45
- message: "",
46
- type: "input",
47
- },
48
- {
49
- name: "APP_VERSION",
50
- message: "应用版本号",
51
- type: "input",
52
- },
53
- {
54
- name: "HTTP_SERVICE_PORT",
55
- message: "服务暴露端口",
56
- type: "input",
57
- validate: (input) => {
58
- if (!/^[^0]\d+$/.test(input)) {
59
- return "端口必须为数字";
60
- }
61
- return true;
62
- },
63
- },
64
- ];
65
-
66
- const permitLocalEnv = _permitLocalEnv.concat(permitGlobalEnv);
67
-
68
- const allPermits = _permitLocalEnv.concat(permitGlobalEnv);
69
-
70
- const controller = new AbortController();
71
-
72
- /**
73
- * 检查URL是否能被ping通
74
- * @param url 要检查的URL
75
- * @param suffix 可选,与URL拼接的后缀
76
- **/
77
- async function checkURL(url, suffix = "") {
78
- const timeout = setTimeout(() => {
79
- controller.abort();
80
- }, 5000);
81
-
82
- try {
83
- const resp = await request(url + suffix, { signal: controller.signal });
84
- if (resp.status != 200) {
85
- // 设备可以访问 (client 有正常连接), 但是sdk 这个服务无法访问
86
- throw new Error(
87
- chalk.red(
88
- `无法连接 sdk 服务, 请确保 ${chalk.yellow(
89
- new URL(url).origin
90
- )} 可以访问或者在应用商店安装 sdk`
91
- )
92
- );
93
- }
94
- } catch (error) {
95
- switch (error.name) {
96
- case "FetchError":
97
- console.log(chalk.red("盒子入口地址有误,请核对后再试"));
98
- process.exit();
99
- default:
100
- throw error;
101
- }
102
- } finally {
103
- clearTimeout(timeout);
104
- }
105
- }
106
-
107
- const allPermitEnv = permitGlobalEnv
108
- .concat(permitLocalEnv)
109
- .map((e) => e.name)
110
- .filter((value, index, self) => self.indexOf(value) == index);
111
-
112
26
  class Env {
113
27
  constructor(cwd) {
114
28
  // 只加载
@@ -132,7 +46,7 @@ class Env {
132
46
  this.localEnv = JSON.parse(fs.readFileSync(this.localEnvPath));
133
47
  this.existance.local = true;
134
48
  } catch (e) {
135
- debug("local env fail to fetch %s", e.message);
49
+ logger.debug("local env fail to fetch ", e.message);
136
50
  this.localEnv = {};
137
51
  }
138
52
 
@@ -167,7 +81,7 @@ class Env {
167
81
 
168
82
  stringify() {
169
83
  let content = "";
170
- Object.keys(this.all).forEach((key) => {
84
+ Object.keys(this.allEnv).forEach((key) => {
171
85
  content += `${key}="${this.allEnv[key]}"\n`;
172
86
  });
173
87
  return content.trimEnd();
@@ -221,7 +135,7 @@ class Env {
221
135
  this.globalEnv = JSON.parse(fs.readFileSync(this.globalEnvPath));
222
136
  this.existance.global = true;
223
137
  } catch (e) {
224
- debug("global env fail to fetch %s", e.message);
138
+ logger.debug("global env fail to fetch ", e.message);
225
139
  this.globalEnv = {};
226
140
  }
227
141
  }
@@ -249,10 +163,18 @@ class SDKEnv {
249
163
 
250
164
  async ensure() {
251
165
  try {
252
- const resp = await request(`${this.sdkUrl}/api/v1/ping`);
253
- if (resp.status != 200) throw new Error(chalk.red("debug.bridge服务未正常运行"));
166
+ const resp = await fetch(`${this.sdkUrl}/api/v1/ping`);
167
+ if (resp.status != 200)
168
+ throw new Error(chalk.red("debug.bridge服务未正常运行"));
254
169
  } catch (e) {
255
- console.log(chalk.yellow("检测到SDK尚未安装,请将盒子开启开发者模式"));
170
+ logger.error(e);
171
+ console.log(
172
+ chalk.yellow(
173
+ `检测到SDK尚未安装,请将盒子"${env.get(
174
+ "DEFAULT_BOXNAME"
175
+ )}"开启开发者模式`
176
+ )
177
+ );
256
178
  process.exit(-1);
257
179
  }
258
180
  }
@@ -266,11 +188,7 @@ class SDKEnv {
266
188
  }
267
189
 
268
190
  buildSdkUrl(boxname) {
269
- return `https://sdk.${boxname}.heiyu.space`;
270
- }
271
-
272
- async checkConnection(host) {
273
- return await checkURL(host, "/api/v1/ping");
191
+ return `http://sdk.${boxname}.heiyu.space`;
274
192
  }
275
193
 
276
194
  /**
@@ -279,7 +197,7 @@ class SDKEnv {
279
197
  */
280
198
  async _promptAnswers() {
281
199
  const answers = await inquirer.prompt(
282
- permitGlobalEnv.filter((a) => a.name == "DEFAULT_BOXNAME")
200
+ allPermitEnv.filter((a) => a.name == "DEFAULT_BOXNAME")
283
201
  );
284
202
  return answers["DEFAULT_BOXNAME"];
285
203
  }
package/lib/key.js CHANGED
@@ -83,6 +83,7 @@ export default class Key {
83
83
  key: fs.readFileSync(pair["private"]),
84
84
  },
85
85
  ],
86
+ keepaliveInterval: 5000,
86
87
  });
87
88
  });
88
89
  }
package/lib/sdk.js CHANGED
@@ -7,6 +7,14 @@ import { Client } from "ssh2";
7
7
  import Key from "./key.js";
8
8
  import logger from "loglevel";
9
9
 
10
+ const sshDebug = function () {
11
+ if (process.env["SSH2_DEBUG"]) {
12
+ return logger.debug;
13
+ } else {
14
+ return function () {};
15
+ }
16
+ };
17
+
10
18
  export async function connectOptions(host) {
11
19
  const pairs = await new Key().getKeyPair();
12
20
  return {
@@ -27,6 +35,8 @@ export async function connectOptions(host) {
27
35
  agent: process.env.SSH_AUTH_SOCK,
28
36
  },
29
37
  ],
38
+ keepaliveInterval: 5000, // 5s
39
+ debug: sshDebug(),
30
40
  };
31
41
  }
32
42
 
@@ -123,28 +133,3 @@ export class SSHClient {
123
133
  );
124
134
  }
125
135
  }
126
-
127
- export async function dockerPullLzcAppsImage(host) {
128
- logger.warn("* 更新lzcapp镜像, 未来可能会移除此操作");
129
- const opts = await connectOptions(host);
130
- const client = new SSHClient(opts);
131
- await client.connect();
132
- try {
133
- const stream = await client.exec(
134
- "docker pull registry.lazycat.cloud/lzc/lzcapp:0.1"
135
- );
136
-
137
- return new Promise((resolve, reject) => {
138
- stream.stdout.pipe(process.stdout);
139
- stream.stderr.pipe(process.stdout);
140
- stream.on("close", () => {
141
- client.close();
142
- resolve();
143
- });
144
- stream.on("error", (e) => reject(e));
145
- });
146
- } catch (e) {
147
- client.close();
148
- throw e;
149
- }
150
- }
package/lib/utils.js CHANGED
@@ -1,26 +1,23 @@
1
1
  import path from "path";
2
2
  import fs from "fs";
3
- import { mkdtemp } from "fs/promises";
4
3
  import os from "os";
5
- import chalk from "chalk";
6
- import archiver from "archiver";
7
4
  import glob from "fast-glob";
8
5
  import yaml from "js-yaml";
9
6
  import mergeWith from "lodash.mergewith";
10
- import isArray from "lodash.isarray";
11
- import { request } from "./autologin.js";
12
7
  import { dirname } from "path";
13
8
  import { fileURLToPath } from "url";
14
9
  import ignore from "ignore";
15
- import ora from "ora";
16
- import { desireStatusTimer } from "../lib/api.js";
17
- import { warn } from "console";
18
- import { createHash } from "node:crypto";
19
-
20
- const META_MARK = "x-lazycat-app";
21
- const APP_FOLDER = ".lazycat";
22
- const APP_CONFIG_FILE = "app-config";
23
- const APP_SDK_HOSTNAME = "box";
10
+ import { createHash, randomBytes } from "node:crypto";
11
+ import https from "node:https";
12
+ import http from "node:http";
13
+ import zlib from "node:zlib";
14
+ import process from "node:process";
15
+ import { spawnSync } from "node:child_process";
16
+ import logger from "loglevel";
17
+
18
+ export const APP_FOLDER = ".lazycat";
19
+ export const APP_CONFIG_FILE = "app-config";
20
+ export const APP_SDK_HOSTNAME = "box";
24
21
  export const GLOBAL_CONFIG_DIR = path.join(os.homedir(), "/.config/lazycat");
25
22
 
26
23
  export const envsubstr = async (templateContents, args) => {
@@ -33,7 +30,7 @@ export const envsubstr = async (templateContents, args) => {
33
30
  * @param filePath {string} 如果为文件路径 确保其文件夹存在; 如果为文件夹, 则确保该文件夹存在
34
31
  *
35
32
  */
36
- function ensureDir(filePath) {
33
+ export function ensureDir(filePath) {
37
34
  let dirPath;
38
35
  if (filePath.endsWith("/")) {
39
36
  dirPath = filePath;
@@ -45,11 +42,11 @@ function ensureDir(filePath) {
45
42
  }
46
43
  }
47
44
 
48
- function loadFromYaml(file) {
45
+ export function loadFromYaml(file) {
49
46
  return yaml.load(fs.readFileSync(file, "utf8"));
50
47
  }
51
48
 
52
- function dumpToYaml(template, target) {
49
+ export function dumpToYaml(template, target) {
53
50
  fs.writeFileSync(
54
51
  target,
55
52
  yaml.dump(template, {
@@ -60,42 +57,10 @@ function dumpToYaml(template, target) {
60
57
  );
61
58
  }
62
59
 
63
- function isValidApp(appDir) {
64
- function doCheck(next = true) {
65
- const files = ["docker-compose.yml", "docker-compose.yml.in"];
66
- for (let f of files) {
67
- const composeFile = path.join(appDir, f);
68
- if (fs.existsSync(composeFile)) {
69
- const doc = yaml.load(fs.readFileSync(composeFile, "utf8"));
70
- if (doc[META_MARK]) {
71
- return { appDir, isTemplate: f == "docker-compose.yml.in" };
72
- }
73
- }
74
- }
75
- if (next) {
76
- appDir = path.join(appDir, APP_FOLDER);
77
- return doCheck(false);
78
- }
79
- return { appDir: false, isTemplate: false };
80
- }
81
- return doCheck();
82
- }
83
-
84
- // find any valid app path till root folder
85
- function findAppRootPath(aPath) {
86
- const sep = "/";
87
- const folders = aPath.split(sep);
88
- for (let i = folders.length; i >= 0; i--) {
89
- if (folders[i] == APP_FOLDER) {
90
- return folders.slice(0, i + 1).join(sep);
91
- }
92
- }
93
- return false;
94
- }
95
-
96
- function toPair(object) {
60
+ export function toPair(object) {
97
61
  return Object.keys(object).map((key) => {
98
62
  let value = object[key] ? object[key].toString() : "";
63
+ key = key.replaceAll("-", "_");
99
64
  return {
100
65
  name: key,
101
66
  value,
@@ -103,12 +68,7 @@ function toPair(object) {
103
68
  });
104
69
  }
105
70
 
106
- function getMetaInfo(composeFile) {
107
- const doc = yaml.load(fs.readFileSync(composeFile, "utf8"));
108
- return doc[META_MARK];
109
- }
110
-
111
- async function envTemplateFile(templateFile, env) {
71
+ export async function envTemplateFile(templateFile, env) {
112
72
  const template = yaml.load(fs.readFileSync(templateFile, "utf8"));
113
73
  const options = {
114
74
  envs: toPair(env),
@@ -125,12 +85,12 @@ async function envTemplateFile(templateFile, env) {
125
85
  );
126
86
  }
127
87
 
128
- async function createTemplateFile(templateFile, outputFile, env) {
88
+ export async function createTemplateFile(templateFile, outputFile, env) {
129
89
  let output = await envTemplateFile(templateFile, env);
130
90
  fs.writeFileSync(outputFile, output);
131
91
  }
132
92
 
133
- async function createTemplateFileCommon(templateFile, outputFile, env) {
93
+ export async function createTemplateFileCommon(templateFile, outputFile, env) {
134
94
  const template = yaml.load(fs.readFileSync(templateFile, "utf8"));
135
95
  const options = {
136
96
  envs: toPair(env),
@@ -143,8 +103,17 @@ async function createTemplateFileCommon(templateFile, outputFile, env) {
143
103
  fs.writeFileSync(outputFile, output);
144
104
  }
145
105
 
106
+ export async function envsubstrDefault(templateContents, env) {
107
+ const options = {
108
+ envs: toPair(env),
109
+ syntax: "default",
110
+ protect: false,
111
+ };
112
+ return envsubstr(templateContents, { options });
113
+ }
114
+
146
115
  // this will copy current app to a tmp dir
147
- async function copyDotAppDir(from, to, opts = {}) {
116
+ export async function copyDotAppDir(from, to, opts = {}) {
148
117
  const {
149
118
  include = [],
150
119
  ignore = ["app-config", "output", "box-config.json"],
@@ -181,7 +150,7 @@ async function copyDotAppDir(from, to, opts = {}) {
181
150
  }
182
151
  }
183
152
 
184
- function mergeYamlInMemory(args) {
153
+ export function mergeYamlInMemory(args) {
185
154
  if (args.length == 0) {
186
155
  return {};
187
156
  } else if (args.length == 1) {
@@ -189,7 +158,7 @@ function mergeYamlInMemory(args) {
189
158
  }
190
159
  return args.reduce((prev, curr) => {
191
160
  let result = mergeWith(prev, curr, (objValue, srcValue) => {
192
- if (isArray(objValue)) {
161
+ if (Array.isArray(objValue)) {
193
162
  return objValue.concat(srcValue);
194
163
  }
195
164
  });
@@ -198,14 +167,14 @@ function mergeYamlInMemory(args) {
198
167
  }
199
168
 
200
169
  // override yaml, notice this will change target file
201
- function mergeYaml(target, source) {
170
+ export function mergeYaml(target, source) {
202
171
  const targetContent = yaml.load(fs.readFileSync(target, "utf8"));
203
172
  const sourceContent = yaml.load(fs.readFileSync(source, "utf8"));
204
173
  const merged = mergeWith(
205
174
  targetContent,
206
175
  sourceContent,
207
176
  (objValue, srcValue) => {
208
- if (isArray(objValue)) {
177
+ if (Array.isArray(objValue)) {
209
178
  return objValue.concat(srcValue);
210
179
  }
211
180
  }
@@ -213,44 +182,16 @@ function mergeYaml(target, source) {
213
182
  dumpToYaml(merged, target);
214
183
  }
215
184
 
216
- function archiveFolder(appDir, format = "zip") {
217
- return new Promise(async (resolve, reject) => {
218
- if (!fs.existsSync(appDir)) {
219
- reject(new Error("folder does not exist"));
220
- return;
221
- }
222
-
223
- const tempDir = await mkdtemp(path.join(os.tmpdir(), "hc-"));
224
- const out = path.join(tempDir, "app." + format);
225
-
226
- // console.log(chalk.green("start archive app ..."));
227
- const output = fs.createWriteStream(out);
228
- const archive = archiver(format);
229
-
230
- archive.on("error", (e) => {
231
- reject(e);
232
- // console.log(e);
233
- });
234
- archive.on("end", () => {
235
- resolve(output);
236
- });
237
- archive.pipe(output);
238
-
239
- archive.directory(appDir, false);
240
- archive.finalize();
241
- });
242
- }
243
-
244
- function contextDirname(url = import.meta.url) {
185
+ export function contextDirname(url = import.meta.url) {
245
186
  return dirname(fileURLToPath(url));
246
187
  }
247
188
 
248
- async function importDefault(pkgPath) {
189
+ export async function importDefault(pkgPath) {
249
190
  let mod = await import(pkgPath);
250
191
  return mod.default;
251
192
  }
252
193
 
253
- class GitIgnore {
194
+ export class GitIgnore {
254
195
  constructor(root) {
255
196
  this.root = root;
256
197
  this.ignores = [];
@@ -288,12 +229,12 @@ class GitIgnore {
288
229
  }
289
230
  }
290
231
 
291
- function isDirSync(path) {
232
+ export function isDirSync(path) {
292
233
  let stat = fs.statSync(path);
293
234
  return stat.isDirectory();
294
235
  }
295
236
 
296
- function isDirExist(path) {
237
+ export function isDirExist(path) {
297
238
  try {
298
239
  return isDirSync(path);
299
240
  } catch {
@@ -301,7 +242,7 @@ function isDirExist(path) {
301
242
  }
302
243
  }
303
244
 
304
- function isFileExist(path) {
245
+ export function isFileExist(path) {
305
246
  try {
306
247
  fs.accessSync(
307
248
  path,
@@ -313,56 +254,139 @@ function isFileExist(path) {
313
254
  }
314
255
  }
315
256
 
316
- function urlHostname(url) {
257
+ export function urlHostname(url) {
317
258
  let u = new URL(url);
318
259
  return u.hostname;
319
260
  }
320
261
 
321
262
  // REVIEW: 用户输入的app-id 空格替换为减号并且转换为全小写。
322
263
  // 这会影响默认的app-name,因为app-name的默认值依赖app-id,但是如果特定输入了app-name则正常
323
- function parse2CorrectName(name) {
264
+ export function parse2CorrectName(name) {
324
265
  return name.replaceAll(" ", "-").toLowerCase();
325
266
  }
326
267
 
327
- async function sleep(ms) {
268
+ export async function sleep(ms) {
328
269
  return new Promise((resolve) => {
329
270
  setTimeout(resolve, ms);
330
271
  });
331
272
  }
332
273
 
333
- async function md5String(str) {
274
+ export async function md5String(str) {
334
275
  const hash = createHash("md5");
335
276
  hash.update(str);
336
277
  return hash.digest("hex");
337
278
  }
338
279
 
339
- export {
340
- ensureDir,
341
- copyDotAppDir,
342
- isValidApp,
343
- getMetaInfo,
344
- findAppRootPath,
345
- archiveFolder,
346
- mergeYaml,
347
- mergeYamlInMemory,
348
- loadFromYaml,
349
- dumpToYaml,
350
- importDefault,
351
- APP_FOLDER,
352
- APP_CONFIG_FILE,
353
- APP_SDK_HOSTNAME,
354
- META_MARK,
355
- toPair,
356
- contextDirname,
357
- isDirSync,
358
- isDirExist,
359
- GitIgnore,
360
- isFileExist,
361
- urlHostname,
362
- envTemplateFile,
363
- createTemplateFile,
364
- parse2CorrectName,
365
- sleep,
366
- md5String,
367
- createTemplateFileCommon
368
- };
280
+ export async function md5File(filepath) {
281
+ const hash = createHash("md5");
282
+ const input = fs.createReadStream(filepath);
283
+ return new Promise((resolve) => {
284
+ input.on("readable", () => {
285
+ const data = input.read();
286
+ if (data) {
287
+ hash.update(data);
288
+ } else {
289
+ resolve(hash.digest("hex"));
290
+ }
291
+ });
292
+ });
293
+ }
294
+
295
+ export class Downloader {
296
+ constructor() {}
297
+
298
+ showDownloadingProgress(received, total) {
299
+ let percentage = ((received * 100) / total).toFixed(2);
300
+ process.stdout.write("\r");
301
+ process.stdout.write(
302
+ percentage +
303
+ "% | " +
304
+ received +
305
+ " bytes downloaded out of " +
306
+ total +
307
+ " bytes."
308
+ );
309
+ }
310
+
311
+ download(url, savePath, enableGzip = false) {
312
+ let tmpPath = savePath + ".tmp";
313
+ const options = new URL(url);
314
+ let request = url.startsWith("https") ? https.request : http.request;
315
+
316
+ return new Promise((resolve, reject) => {
317
+ const req = request(options, (res) => {
318
+ if (res.statusCode != 200) {
319
+ reject(`下载 ${url} 失败`);
320
+ return;
321
+ }
322
+
323
+ let total = parseInt(res.headers["content-length"]);
324
+ let recive = 0;
325
+ res.on("data", (chunk) => {
326
+ recive += chunk.length;
327
+ this.showDownloadingProgress(recive, total);
328
+ });
329
+
330
+ let outputFile = fs.createWriteStream(tmpPath, { flags: "w+" });
331
+ if (enableGzip) {
332
+ let decoder = zlib.createUnzip();
333
+ res.pipe(decoder).pipe(outputFile);
334
+ } else {
335
+ res.pipe(outputFile);
336
+ }
337
+
338
+ outputFile.on("error", reject);
339
+ outputFile.on("finish", () => {
340
+ fs.renameSync(tmpPath, savePath);
341
+ process.stdout.write("\n");
342
+ resolve();
343
+ });
344
+ });
345
+ req.on("error", reject);
346
+ req.end();
347
+ });
348
+ }
349
+ }
350
+
351
+ export function randomString(length = 4) {
352
+ return randomBytes(length).toString("hex");
353
+ }
354
+
355
+ export class FileLocker {
356
+ constructor(id) {
357
+ this.filename = undefined;
358
+ this.fd = undefined;
359
+ this.id = id;
360
+ }
361
+ lock() {
362
+ this.filename = path.resolve(os.tmpdir(), "lzc-cli-file-lock", this.id);
363
+ ensureDir(this.filename);
364
+
365
+ try {
366
+ this.fd = fs.openSync(this.filename, "wx+");
367
+ } catch (e) {
368
+ if (e.code == "EEXIST" && !this.withOpen(this.filename)) {
369
+ logger.debug("filelock exist open with w+");
370
+ this.fd = fs.openSync(this.filename, "w+");
371
+ } else {
372
+ throw e;
373
+ }
374
+ }
375
+ }
376
+ unlock() {
377
+ if (this.fd) {
378
+ fs.closeSync(this.fd);
379
+ fs.rmSync(this.filename);
380
+ this.fd = undefined;
381
+ this.filename = undefined;
382
+ }
383
+ }
384
+ withOpen(filename) {
385
+ const p = spawnSync("lsof", ["-w", "-t", filename]);
386
+ if (p.status === 0) {
387
+ return true;
388
+ } else {
389
+ return false;
390
+ }
391
+ }
392
+ }