@lazycatcloud/lzc-cli 1.3.12 → 1.3.14

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/lib/utils.js CHANGED
@@ -1,101 +1,108 @@
1
- import path from "path"
2
- import fs from "fs"
3
- import os from "os"
4
- import glob from "fast-glob"
5
- import yaml from "js-yaml"
6
- import mergeWith from "lodash.mergewith"
7
- import { dirname } from "path"
8
- import { fileURLToPath } from "url"
9
- import ignore from "ignore"
10
- import { createHash } 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 spawn from "cross-spawn"
16
- import logger from "loglevel"
17
- import * as tar from "tar"
18
- import dns from "node:dns"
19
- import AdmZip from "adm-zip"
20
- import commandExists from "command-exists"
21
- import fetch from "node-fetch"
22
- import semver from "semver"
23
- import inquirer from "inquirer"
1
+ import path from 'path';
2
+ import fs from 'fs';
3
+ import os from 'os';
4
+ import glob from 'fast-glob';
5
+ import yaml from 'js-yaml';
6
+ import mergeWith from 'lodash.mergewith';
7
+ import { dirname } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import ignore from 'ignore';
10
+ import { createHash } 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 spawn from 'cross-spawn';
16
+ import logger from 'loglevel';
17
+ import * as tar from 'tar';
18
+ import dns from 'node:dns';
19
+ import AdmZip from 'adm-zip';
20
+ import commandExists from 'command-exists';
21
+ import fetch from 'node-fetch';
22
+ import semver from 'semver';
23
+ import inquirer from 'inquirer';
24
+ import { t } from './i18n/index.js';
24
25
 
25
26
  // lzc-cli 包的 pkgInfo 信息
26
- export const pkgInfo = JSON.parse(
27
- fs.readFileSync(
28
- path.join(contextDirname(import.meta.url), "..", "package.json")
29
- )
30
- )
27
+ export const pkgInfo = JSON.parse(fs.readFileSync(path.join(contextDirname(import.meta.url), '..', 'package.json')));
31
28
 
32
29
  export function checkNodejsVersion() {
33
- const requiredVersion = pkgInfo.engines.node
34
- if (!semver.satisfies(process.version, requiredVersion)) {
35
- logger.error(
36
- `检测到您当前的 nodejs 版本 ${process.version} 不匹配,lzc-cli 最低要求 nodejs 版本为 ${requiredVersion}, 请升级`
37
- )
38
- process.exit(1)
39
- }
40
-
41
- // 如果版本小于 21.0.0 且大于等于 16.15.0,禁用 Fetch API 实验性警告
42
- if (
43
- semver.lt(process.version, "21.0.0") &&
44
- semver.gte(process.version, "16.15.0")
45
- ) {
46
- process.removeAllListeners("warning")
47
- }
30
+ const requiredVersion = pkgInfo.engines.node;
31
+ if (!semver.satisfies(process.version, requiredVersion)) {
32
+ logger.error(
33
+ t('lzc_cli.lib.utils.check_nodejs_version_no_match_tips', `检测到您当前的 nodejs 版本 {{ version }} 不匹配,lzc-cli 最低要求 nodejs 版本为 {{ requiredVersion }}, 请升级`, {
34
+ version: process.version,
35
+ requiredVersion,
36
+ }),
37
+ );
38
+ process.exit(1);
39
+ }
40
+
41
+ // 如果版本小于 21.0.0 且大于等于 16.15.0,禁用 Fetch API 实验性警告
42
+ if (semver.lt(process.version, '21.0.0') && semver.gte(process.version, '16.15.0')) {
43
+ process.removeAllListeners('warning');
44
+ }
48
45
  }
49
46
 
50
47
  export async function getLatestVersion(controller, pkgName = pkgInfo.name) {
51
- const url = `https://data.jsdelivr.com/v1/package/npm/${pkgName}`
52
- try {
53
- logger.debug("check latest lzc-cli version...")
54
- const data = await fetch(url, {
55
- signal: controller.signal
56
- })
57
- const content = await data.json()
58
- const latestVersion = content["tags"]["latest"]
59
- if (semver.lt(pkgInfo.version, latestVersion)) {
60
- logger.warn(
61
- `检测到 ${pkgName} 最新版本为 ${latestVersion},使用 'npm i -g ${pkgName}@${latestVersion}' 升级到最新版本!`
62
- )
63
- } else {
64
- logger.debug(`已经在最新版本: ${latestVersion}`)
65
- }
66
- } catch (err) {
67
- logger.debug(`请求 ${url} 失败, error: ${err}`)
68
- }
69
- }
70
-
71
- export const isMacOs = process.platform == "darwin"
72
- export const isLinux = process.platform == "linux"
73
- export const isWindows = process.platform == "win32"
48
+ const url = `https://data.jsdelivr.com/v1/package/npm/${pkgName}`;
49
+ try {
50
+ logger.debug('check latest lzc-cli version...');
51
+ const data = await fetch(url, {
52
+ signal: controller.signal,
53
+ });
54
+ const content = await data.json();
55
+ const latestVersion = content['tags']['latest'];
56
+ if (semver.lt(pkgInfo.version, latestVersion)) {
57
+ logger.warn(
58
+ t('lzc_cli.lib.utils.get_latest_version_tips', `检测到 {{ pkgName }} 最新版本为 {{ latestVersion }},使用 'npm i -g {{ pkgName }}@{{ latestVersion }}' 升级到最新版本!`, {
59
+ pkgName,
60
+ latestVersion,
61
+ interpolation: { escapeValue: false }
62
+ }),
63
+ );
64
+ } else {
65
+ logger.debug(t('lzc_cli.lib.utils.get_latest_version_success_tips', `已经在最新版本: {{ latestVersion }}`, { latestVersion }));
66
+ }
67
+ } catch (err) {
68
+ logger.debug(
69
+ t('lzc_cli.lib.utils.get_latest_version_fail_tips', `请求 {{ url }} 失败, error: {{ err }}`, {
70
+ url,
71
+ err,
72
+ interpolation: { escapeValue: false }
73
+ }),
74
+ );
75
+ }
76
+ }
77
+
78
+ export const isMacOs = process.platform == 'darwin';
79
+ export const isLinux = process.platform == 'linux';
80
+ export const isWindows = process.platform == 'win32';
74
81
 
75
82
  export const envsubstr = async (templateContents, args) => {
76
- const parse = await importDefault("envsub/js/envsub-parser.js")
77
- return parse(templateContents, args)
78
- }
83
+ const parse = await importDefault('envsub/js/envsub-parser.js');
84
+ return parse(templateContents, args);
85
+ };
79
86
 
80
87
  // 比较当前的版本和want的大小,如果
81
88
  // - 当前版本大于指定的版本 => 1
82
89
  // - 当前版本等于指定的版本 => 0
83
90
  // - 当前版本小于指定的版本 => -1
84
91
  export function compareVersions(want, current = pkgInfo.version) {
85
- const wantArrs = want.split(".")
86
- const currArrs = current.split(".")
92
+ const wantArrs = want.split('.');
93
+ const currArrs = current.split('.');
87
94
 
88
- // Compare each part
89
- for (let i = 0; i < Math.max(wantArrs.length, currArrs.length); i++) {
90
- // Convert to integers, default to 0 if part doesn't exist
91
- const w = parseInt(wantArrs[i] || 0)
92
- const c = parseInt(currArrs[i] || 0)
95
+ // Compare each part
96
+ for (let i = 0; i < Math.max(wantArrs.length, currArrs.length); i++) {
97
+ // Convert to integers, default to 0 if part doesn't exist
98
+ const w = parseInt(wantArrs[i] || 0);
99
+ const c = parseInt(currArrs[i] || 0);
93
100
 
94
- if (c > w) return 1
95
- if (c < w) return -1
96
- }
101
+ if (c > w) return 1;
102
+ if (c < w) return -1;
103
+ }
97
104
 
98
- return 0 // Versions are equal
105
+ return 0; // Versions are equal
99
106
  }
100
107
 
101
108
  /**
@@ -104,354 +111,348 @@ export function compareVersions(want, current = pkgInfo.version) {
104
111
  *
105
112
  */
106
113
  export function ensureDir(filePath) {
107
- let dirPath
108
- if (filePath.endsWith("/")) {
109
- dirPath = filePath
110
- } else {
111
- dirPath = path.dirname(filePath)
112
- }
113
- if (!fs.existsSync(dirPath)) {
114
- fs.mkdirSync(dirPath, { recursive: true })
115
- }
114
+ let dirPath;
115
+ if (filePath.endsWith('/')) {
116
+ dirPath = filePath;
117
+ } else {
118
+ dirPath = path.dirname(filePath);
119
+ }
120
+ if (!fs.existsSync(dirPath)) {
121
+ fs.mkdirSync(dirPath, { recursive: true });
122
+ }
116
123
  }
117
124
 
118
125
  export function ensureDirectoryExists(filePath, isRenameOrClear) {
119
- let result = { isExists: false, renamedFileName: undefined }
120
- try {
121
- const stats = fs.statSync(filePath) // 获取文件或目录的状态
122
- result.isExists = true
123
- if (isRenameOrClear) {
124
- if (stats.isDirectory()) {
125
- const newPath = getUniquePath(filePath)
126
- result.renamedFileName = path.basename(newPath)
127
- fs.renameSync(filePath, newPath)
128
- } else {
129
- fs.rmSync(filePath)
130
- }
131
- fs.mkdirSync(filePath, { recursive: true })
132
- }
133
- } catch (err) {
134
- if (err.code === "ENOENT") {
135
- fs.mkdirSync(filePath, { recursive: true })
136
- } else {
137
- throw `创建文件夹${filePath}错误: ${err}`
138
- }
139
- }
140
- return result
126
+ let result = { isExists: false, renamedFileName: undefined };
127
+ try {
128
+ const stats = fs.statSync(filePath); // 获取文件或目录的状态
129
+ result.isExists = true;
130
+ if (isRenameOrClear) {
131
+ if (stats.isDirectory()) {
132
+ const newPath = getUniquePath(filePath);
133
+ result.renamedFileName = path.basename(newPath);
134
+ fs.renameSync(filePath, newPath);
135
+ } else {
136
+ fs.rmSync(filePath);
137
+ }
138
+ fs.mkdirSync(filePath, { recursive: true });
139
+ }
140
+ } catch (err) {
141
+ if (err.code === 'ENOENT') {
142
+ fs.mkdirSync(filePath, { recursive: true });
143
+ } else {
144
+ throw t('lzc_cli.lib.utils.ensure_directory_exists_fail', `创建文件夹{{ filePath }}错误: {{ err }}`, {
145
+ filePath,
146
+ err,
147
+ interpolation: { escapeValue: false }
148
+ });
149
+ }
150
+ }
151
+ return result;
141
152
  }
142
153
 
143
154
  // 如果该路径已存在,则返回带递增数字的路径
144
155
  function getUniquePath(basePath) {
145
- let dirName = path.basename(basePath)
146
- let dirPath = basePath
147
- let index = 1
148
- // 检查是否存在同名目录
149
- while (fs.existsSync(dirPath)) {
150
- dirName = `${path.basename(basePath, path.extname(basePath))}_${index}`
151
- dirPath = path.join(path.dirname(basePath), dirName)
152
- index++
153
- }
154
- return dirPath
156
+ let dirName = path.basename(basePath);
157
+ let dirPath = basePath;
158
+ let index = 1;
159
+ // 检查是否存在同名目录
160
+ while (fs.existsSync(dirPath)) {
161
+ dirName = `${path.basename(basePath, path.extname(basePath))}_${index}`;
162
+ dirPath = path.join(path.dirname(basePath), dirName);
163
+ index++;
164
+ }
165
+ return dirPath;
155
166
  }
156
167
 
157
168
  export async function createTemplateFileCommon(templateFile, outputFile, env) {
158
- const options = {
159
- envs: toPair(env),
160
- syntax: "default",
161
- protect: false
162
- }
163
- const output = await envsubstr(fs.readFileSync(templateFile, "utf-8"), {
164
- options
165
- })
166
- fs.writeFileSync(outputFile, output)
169
+ const options = {
170
+ envs: toPair(env),
171
+ syntax: 'default',
172
+ protect: false,
173
+ };
174
+ const output = await envsubstr(fs.readFileSync(templateFile, 'utf-8'), {
175
+ options,
176
+ });
177
+ fs.writeFileSync(outputFile, output);
167
178
  }
168
179
 
169
180
  export function loadFromYaml(file) {
170
- return yaml.load(fs.readFileSync(file, "utf8"))
181
+ return yaml.load(fs.readFileSync(file, 'utf8'));
171
182
  }
172
183
 
173
184
  // lzc-manifest.yml 要支持模板,目前无法直接用yaml库解释,手动读出必要字段
174
185
  export function fakeLoadManifestYml(file) {
175
- const res = fs.readFileSync(file, "utf8")
176
- let obj = {
177
- application: {
178
- subdomain: undefined
179
- }
180
- }
181
-
182
- res.split("\n").forEach((v) => {
183
- let line = v.trim()
184
- const arr = line.split(":")
185
- if (arr.length != 2) {
186
- return
187
- }
188
- let [key, value] = arr
189
- value = value.trim()
190
- if (!obj.package && key == "package") {
191
- obj.package = value
192
- }
193
- if (!obj.application.subdomain && key == "subdomain") {
194
- obj.application.subdomain = value
195
- }
196
- if (!obj.version && key == "version") {
197
- obj.version = value
198
- }
199
- })
200
- return obj
186
+ const res = fs.readFileSync(file, 'utf8');
187
+ let obj = {
188
+ application: {
189
+ subdomain: undefined,
190
+ },
191
+ };
192
+
193
+ res.split('\n').forEach((v) => {
194
+ let line = v.trim();
195
+ const arr = line.split(':');
196
+ if (arr.length != 2) {
197
+ return;
198
+ }
199
+ let [key, value] = arr;
200
+ value = value.trim();
201
+ if (!obj.package && key == 'package') {
202
+ obj.package = value;
203
+ }
204
+ if (!obj.application.subdomain && key == 'subdomain') {
205
+ obj.application.subdomain = value;
206
+ }
207
+ if (!obj.version && key == 'version') {
208
+ obj.version = value;
209
+ }
210
+ });
211
+ return obj;
201
212
  }
202
213
 
203
214
  export function dumpToYaml(template, target) {
204
- fs.writeFileSync(
205
- target,
206
- yaml.dump(template, {
207
- styles: {
208
- "!!null": "empty" // dump null as ""
209
- }
210
- })
211
- )
215
+ fs.writeFileSync(
216
+ target,
217
+ yaml.dump(template, {
218
+ styles: {
219
+ '!!null': 'empty', // dump null as ""
220
+ },
221
+ }),
222
+ );
212
223
  }
213
224
 
214
225
  export function toPair(object) {
215
- return Object.keys(object).map((key) => {
216
- let value = object[key] ? object[key].toString() : ""
217
- key = key.replace(/-/g, "_")
218
- return {
219
- name: key,
220
- value
221
- }
222
- })
226
+ return Object.keys(object).map((key) => {
227
+ let value = object[key] ? object[key].toString() : '';
228
+ key = key.replace(/-/g, '_');
229
+ return {
230
+ name: key,
231
+ value,
232
+ };
233
+ });
223
234
  }
224
235
 
225
236
  export async function envTemplateFile(templateFile, env) {
226
- const template = yaml.load(fs.readFileSync(templateFile, "utf8"))
227
- const options = {
228
- envs: toPair(env),
229
- syntax: "default",
230
- protect: false
231
- }
232
- return await envsubstr(
233
- yaml.dump(template, {
234
- styles: {
235
- "!!null": "empty" // dump null as ""
236
- }
237
- }),
238
- { options }
239
- )
237
+ const template = yaml.load(fs.readFileSync(templateFile, 'utf8'));
238
+ const options = {
239
+ envs: toPair(env),
240
+ syntax: 'default',
241
+ protect: false,
242
+ };
243
+ return await envsubstr(
244
+ yaml.dump(template, {
245
+ styles: {
246
+ '!!null': 'empty', // dump null as ""
247
+ },
248
+ }),
249
+ { options },
250
+ );
240
251
  }
241
252
 
242
253
  export function mergeYamlInMemory(args) {
243
- if (args.length == 0) {
244
- return {}
245
- } else if (args.length == 1) {
246
- return args[0]
247
- }
248
- return args.reduce((prev, curr) => {
249
- let result = mergeWith(prev, curr, (objValue, srcValue) => {
250
- if (Array.isArray(objValue)) {
251
- return objValue.concat(srcValue)
252
- }
253
- })
254
- return result
255
- }, {})
254
+ if (args.length == 0) {
255
+ return {};
256
+ } else if (args.length == 1) {
257
+ return args[0];
258
+ }
259
+ return args.reduce((prev, curr) => {
260
+ let result = mergeWith(prev, curr, (objValue, srcValue) => {
261
+ if (Array.isArray(objValue)) {
262
+ return objValue.concat(srcValue);
263
+ }
264
+ });
265
+ return result;
266
+ }, {});
256
267
  }
257
268
 
258
269
  export function contextDirname(url = import.meta.url) {
259
- return dirname(fileURLToPath(url))
270
+ return dirname(fileURLToPath(url));
260
271
  }
261
272
 
262
273
  export async function importDefault(pkgPath) {
263
- let mod = await import(pkgPath)
264
- return mod.default
274
+ let mod = await import(pkgPath);
275
+ return mod.default;
265
276
  }
266
277
 
267
278
  export class GitIgnore {
268
- constructor(root) {
269
- this.root = root
270
- this.ignores = []
271
- }
272
-
273
- async collect() {
274
- const files = await glob(["**/.gitignore"], {
275
- cwd: this.root,
276
- dot: true,
277
- deep: 3
278
- })
279
-
280
- files.forEach((f) => this._add(f))
281
- }
282
-
283
- _add(ignoreFile) {
284
- let data = fs.readFileSync(ignoreFile, "utf8")
285
- let ig = ignore({ allowRelativePaths: true })
286
- ig.add(data.split("\n"))
287
- this.ignores.push({
288
- ig,
289
- dir: path.dirname(ignoreFile)
290
- })
291
- }
292
-
293
- contain(filepath) {
294
- return this.ignores.some(({ ig, dir }) => {
295
- // 去除不应该计算ignore 的文件
296
- if (!filepath.startsWith(dir)) {
297
- return false
298
- }
299
-
300
- return ig.ignores(path.relative(dir, filepath))
301
- })
302
- }
279
+ constructor(root) {
280
+ this.root = root;
281
+ this.ignores = [];
282
+ }
283
+
284
+ async collect() {
285
+ const files = await glob(['**/.gitignore'], {
286
+ cwd: this.root,
287
+ dot: true,
288
+ deep: 3,
289
+ });
290
+
291
+ files.forEach((f) => this._add(f));
292
+ }
293
+
294
+ _add(ignoreFile) {
295
+ let data = fs.readFileSync(ignoreFile, 'utf8');
296
+ let ig = ignore({ allowRelativePaths: true });
297
+ ig.add(data.split('\n'));
298
+ this.ignores.push({
299
+ ig,
300
+ dir: path.dirname(ignoreFile),
301
+ });
302
+ }
303
+
304
+ contain(filepath) {
305
+ return this.ignores.some(({ ig, dir }) => {
306
+ // 去除不应该计算ignore 的文件
307
+ if (!filepath.startsWith(dir)) {
308
+ return false;
309
+ }
310
+
311
+ return ig.ignores(path.relative(dir, filepath));
312
+ });
313
+ }
303
314
  }
304
315
 
305
316
  export function isDirSync(path) {
306
- let stat = fs.statSync(path)
307
- return stat.isDirectory()
317
+ let stat = fs.statSync(path);
318
+ return stat.isDirectory();
308
319
  }
309
320
 
310
321
  export function isDirExist(path) {
311
- try {
312
- return isDirSync(path)
313
- } catch {
314
- return false
315
- }
322
+ try {
323
+ return isDirSync(path);
324
+ } catch {
325
+ return false;
326
+ }
316
327
  }
317
328
 
318
329
  export function isFileExist(path) {
319
- try {
320
- fs.accessSync(path, fs.constants.R_OK | fs.constants.F_OK)
321
- return true
322
- } catch (err) {
323
- logger.debug(`access ${path} error: ${err}`)
324
- return false
325
- }
330
+ try {
331
+ fs.accessSync(path, fs.constants.R_OK | fs.constants.F_OK);
332
+ return true;
333
+ } catch (err) {
334
+ logger.debug(`access ${path} error: ${err}`);
335
+ return false;
336
+ }
326
337
  }
327
338
 
328
339
  export async function sleep(ms) {
329
- return new Promise((resolve) => {
330
- setTimeout(resolve, ms)
331
- })
340
+ return new Promise((resolve) => {
341
+ setTimeout(resolve, ms);
342
+ });
332
343
  }
333
344
 
334
345
  export async function md5String(str) {
335
- const hash = createHash("md5")
336
- hash.update(str)
337
- return hash.digest("hex")
346
+ const hash = createHash('md5');
347
+ hash.update(str);
348
+ return hash.digest('hex');
338
349
  }
339
350
 
340
351
  export async function md5File(filepath) {
341
- const hash = createHash("md5")
342
- const input = fs.createReadStream(filepath)
343
- return new Promise((resolve) => {
344
- input.on("readable", () => {
345
- const data = input.read()
346
- if (data) {
347
- hash.update(data)
348
- } else {
349
- resolve(hash.digest("hex"))
350
- }
351
- })
352
- })
352
+ const hash = createHash('md5');
353
+ const input = fs.createReadStream(filepath);
354
+ return new Promise((resolve) => {
355
+ input.on('readable', () => {
356
+ const data = input.read();
357
+ if (data) {
358
+ hash.update(data);
359
+ } else {
360
+ resolve(hash.digest('hex'));
361
+ }
362
+ });
363
+ });
353
364
  }
354
365
 
355
366
  export class Downloader {
356
- constructor() {}
357
-
358
- showDownloadingProgress(received, total) {
359
- let percentage = ((received * 100) / total).toFixed(2)
360
- process.stdout.write("\r")
361
- process.stdout.write(
362
- percentage +
363
- "% | " +
364
- received +
365
- " bytes downloaded out of " +
366
- total +
367
- " bytes."
368
- )
369
- }
370
-
371
- download(url, savePath, enableGzip = false) {
372
- let tmpPath = savePath + ".tmp"
373
- const options = new URL(url)
374
- let request = url.startsWith("https") ? https.request : http.request
375
-
376
- return new Promise((resolve, reject) => {
377
- const req = request(options, (res) => {
378
- if (res.statusCode != 200) {
379
- reject(`下载 ${url} 失败`)
380
- return
381
- }
382
-
383
- let total = parseInt(res.headers["content-length"])
384
- let recive = 0
385
- res.on("data", (chunk) => {
386
- recive += chunk.length
387
- this.showDownloadingProgress(recive, total)
388
- })
389
-
390
- let outputFile = fs.createWriteStream(tmpPath, { flags: "w+" })
391
- if (enableGzip) {
392
- let decoder = zlib.createUnzip()
393
- res.pipe(decoder).pipe(outputFile)
394
- } else {
395
- res.pipe(outputFile)
396
- }
397
-
398
- outputFile.on("error", reject)
399
- outputFile.on("finish", () => {
400
- fs.renameSync(tmpPath, savePath)
401
- process.stdout.write("\n")
402
- resolve()
403
- })
404
- })
405
- req.on("error", reject)
406
- req.end()
407
- })
408
- }
367
+ constructor() { }
368
+
369
+ showDownloadingProgress(received, total) {
370
+ let percentage = ((received * 100) / total).toFixed(2);
371
+ process.stdout.write('\r');
372
+ process.stdout.write(percentage + '% | ' + received + ' bytes downloaded out of ' + total + ' bytes.');
373
+ }
374
+
375
+ download(url, savePath, enableGzip = false) {
376
+ let tmpPath = savePath + '.tmp';
377
+ const options = new URL(url);
378
+ let request = url.startsWith('https') ? https.request : http.request;
379
+
380
+ return new Promise((resolve, reject) => {
381
+ const req = request(options, (res) => {
382
+ if (res.statusCode != 200) {
383
+ reject(`download ${url} fail`);
384
+ return;
385
+ }
386
+
387
+ let total = parseInt(res.headers['content-length']);
388
+ let recive = 0;
389
+ res.on('data', (chunk) => {
390
+ recive += chunk.length;
391
+ this.showDownloadingProgress(recive, total);
392
+ });
393
+
394
+ let outputFile = fs.createWriteStream(tmpPath, { flags: 'w+' });
395
+ if (enableGzip) {
396
+ let decoder = zlib.createUnzip();
397
+ res.pipe(decoder).pipe(outputFile);
398
+ } else {
399
+ res.pipe(outputFile);
400
+ }
401
+
402
+ outputFile.on('error', reject);
403
+ outputFile.on('finish', () => {
404
+ fs.renameSync(tmpPath, savePath);
405
+ process.stdout.write('\n');
406
+ resolve();
407
+ });
408
+ });
409
+ req.on('error', reject);
410
+ req.end();
411
+ });
412
+ }
409
413
  }
410
414
 
411
415
  export function isValidAppId(appId) {
412
- const regex = new RegExp("^(?!-)(?!.*--)[a-z][a-z0-9]*([-][a-z0-9]+)*$")
413
- return regex.test(appId)
416
+ const regex = new RegExp('^(?!-)(?!.*--)[a-z][a-z0-9]*([-][a-z0-9]+)*$');
417
+ return regex.test(appId);
414
418
  }
415
419
 
416
420
  export function isValidPackageName(packageName) {
417
- const regex = new RegExp(
418
- "^[a-z][a-z0-9]*([-][a-z0-9]+)*(?:\\.[a-z][a-z0-9]*([-][a-z0-9]+)*)*$"
419
- )
420
- return regex.test(packageName)
421
+ const regex = new RegExp('^[a-z][a-z0-9]*([-][a-z0-9]+)*(?:\\.[a-z][a-z0-9]*([-][a-z0-9]+)*)*$');
422
+ return regex.test(packageName);
421
423
  }
422
424
 
423
425
  export function isUserApp(manifest) {
424
- return !!manifest["application"]["user_app"]
425
- }
426
-
427
- export async function tarContentDir(from, to, cwd = "./") {
428
- return new Promise((resolve, reject) => {
429
- const dest = fs.createWriteStream(to)
430
- tar
431
- .c(
432
- {
433
- cwd: cwd,
434
- filter: (filePath) => {
435
- logger.debug(`tar gz ${filePath}`)
436
- return true
437
- },
438
- sync: true,
439
- portable: {
440
- uid: 0,
441
- gid: 0
442
- }
443
- },
444
- from
445
- )
446
- .pipe(dest)
447
- .on("close", () => {
448
- logger.debug(`pack: ${dest.path}`)
449
- resolve(dest.path)
450
- })
451
- .on("error", (err) => {
452
- reject(err)
453
- })
454
- })
426
+ return !!manifest['application']['user_app'];
427
+ }
428
+
429
+ export async function tarContentDir(from, to, cwd = './') {
430
+ return new Promise((resolve, reject) => {
431
+ const dest = fs.createWriteStream(to);
432
+ tar.c(
433
+ {
434
+ cwd: cwd,
435
+ filter: (filePath) => {
436
+ logger.debug(`tar gz ${filePath}`);
437
+ return true;
438
+ },
439
+ sync: true,
440
+ portable: {
441
+ uid: 0,
442
+ gid: 0,
443
+ },
444
+ },
445
+ from,
446
+ )
447
+ .pipe(dest)
448
+ .on('close', () => {
449
+ logger.debug(`pack: ${dest.path}`);
450
+ resolve(dest.path);
451
+ })
452
+ .on('error', (err) => {
453
+ reject(err);
454
+ });
455
+ });
455
456
  }
456
457
 
457
458
  /**
@@ -461,228 +462,214 @@ export async function tarContentDir(from, to, cwd = "./") {
461
462
  * @returns {boolean}
462
463
  */
463
464
  function isPngWithBuffer(buffer) {
464
- if (!buffer || buffer.length < 8) {
465
- return false
466
- }
467
- return (
468
- buffer[0] === 0x89 &&
469
- buffer[1] === 0x50 &&
470
- buffer[2] === 0x4e &&
471
- buffer[3] === 0x47 &&
472
- buffer[4] === 0x0d &&
473
- buffer[5] === 0x0a &&
474
- buffer[6] === 0x1a &&
475
- buffer[7] === 0x0a
476
- )
465
+ if (!buffer || buffer.length < 8) {
466
+ return false;
467
+ }
468
+ return (
469
+ buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47 && buffer[4] === 0x0d && buffer[5] === 0x0a && buffer[6] === 0x1a && buffer[7] === 0x0a
470
+ );
477
471
  }
478
472
 
479
473
  export function isPngWithFile(filepath) {
480
- if (!isFileExist(filepath)) return false
481
- const buf = fs.readFileSync(filepath)
482
- return isPngWithBuffer(buf)
474
+ if (!isFileExist(filepath)) return false;
475
+ const buf = fs.readFileSync(filepath);
476
+ return isPngWithBuffer(buf);
483
477
  }
484
478
 
485
479
  export function isDebugMode() {
486
- return logger.getLevel() <= logger.levels.DEBUG
480
+ return logger.getLevel() <= logger.levels.DEBUG;
487
481
  }
488
482
 
489
483
  export function isTraceMode() {
490
- return logger.getLevel() <= logger.levels.TRACE
484
+ return logger.getLevel() <= logger.levels.TRACE;
491
485
  }
492
486
 
493
487
  // 没有直接使用 dns/promise,因为这个 dns/promise 是在 15.0.0 后加入,如果低版本
494
488
  // 会直接报错,连版本检测都到不了(包导入的太前面了)
495
489
  async function __resolveDomain(domain, ipv6 = false) {
496
- return new Promise((resolve, reject) => {
497
- const callback = function (err, addresses) {
498
- if (addresses && addresses.length > 0) {
499
- resolve(addresses[0])
500
- } else {
501
- logger.trace("dns resolve error: ", err)
502
- reject(err)
503
- }
504
- }
505
- if (ipv6) {
506
- dns.resolve6(domain, callback)
507
- } else {
508
- dns.resolve4(domain, callback)
509
- }
510
- })
490
+ return new Promise((resolve, reject) => {
491
+ const callback = function (err, addresses) {
492
+ if (addresses && addresses.length > 0) {
493
+ resolve(addresses[0]);
494
+ } else {
495
+ logger.trace('dns resolve error: ', err);
496
+ reject(err);
497
+ }
498
+ };
499
+ if (ipv6) {
500
+ dns.resolve6(domain, callback);
501
+ } else {
502
+ dns.resolve4(domain, callback);
503
+ }
504
+ });
511
505
  }
512
506
 
513
507
  export async function resolveDomain(domain, quiet = false) {
514
- try {
515
- // Set machine's dns server as defalut dns server
516
- dns.setServers(["[fc03:1136:3800::1]"])
517
- const [ipv6Addresses, ipv4Addresses] = await Promise.allSettled([
518
- __resolveDomain(domain, true),
519
- __resolveDomain(domain, false)
520
- ])
521
- if (ipv6Addresses.status == "fulfilled") {
522
- return ipv6Addresses.value
523
- } else if (ipv4Addresses.status == "fulfilled") {
524
- return ipv4Addresses.value
525
- } else {
526
- throw ipv6Addresses.reason
527
- }
528
- } catch (error) {
529
- if (!quiet) {
530
- logger.error(`无法解析域名 ${domain}: `, error)
531
- }
532
- throw error
533
- }
508
+ try {
509
+ // Set machine's dns server as defalut dns server
510
+ dns.setServers(['[fc03:1136:3800::1]']);
511
+ const [ipv6Addresses, ipv4Addresses] = await Promise.allSettled([__resolveDomain(domain, true), __resolveDomain(domain, false)]);
512
+ if (ipv6Addresses.status == 'fulfilled') {
513
+ return ipv6Addresses.value;
514
+ } else if (ipv4Addresses.status == 'fulfilled') {
515
+ return ipv4Addresses.value;
516
+ } else {
517
+ throw ipv6Addresses.reason;
518
+ }
519
+ } catch (error) {
520
+ if (!quiet) {
521
+ logger.error(t('lzc_cli.lib.utils.resolve_domain_fail_tips', `无法解析域名 {{ domain }}: `, { domain, interpolation: { escapeValue: false } }), error);
522
+ }
523
+ throw error;
524
+ }
534
525
  }
535
526
 
536
527
  export function unzipSync(zipPath, destPath, entries = []) {
537
- if (!isFileExist(zipPath)) {
538
- throw `${zipPath} 找不到该文件`
539
- }
540
-
541
- // 确保目标目录存在
542
- fs.mkdirSync(destPath, { recursive: true })
543
- // 创建 zip 实例
544
- const zip = new AdmZip(zipPath)
545
-
546
- if (entries.length > 0) {
547
- entries.forEach((entry) => {
548
- try {
549
- zip.extractEntryTo(entry, destPath, false, true)
550
- } catch {
551
- logger.debug(`压缩包中没有找到 ${entry} 文件`)
552
- }
553
- })
554
- } else {
555
- // 同步解压所有文件
556
- zip.extractAllTo(destPath, true)
557
- }
528
+ if (!isFileExist(zipPath)) {
529
+ throw t('lzc_cli.lib.utils.unzip_sync_not_exist_fail', `{{ zipPath }} 找不到该文件`, { zipPath, interpolation: { escapeValue: false } });
530
+ }
531
+
532
+ // 确保目标目录存在
533
+ fs.mkdirSync(destPath, { recursive: true });
534
+ // 创建 zip 实例
535
+ const zip = new AdmZip(zipPath);
536
+
537
+ if (entries.length > 0) {
538
+ entries.forEach((entry) => {
539
+ try {
540
+ zip.extractEntryTo(entry, destPath, false, true);
541
+ } catch {
542
+ logger.debug(t('lzc_cli.lib.utils.unzip_sync_not_exist_file_log', `压缩包中没有找到 {{ entry }} 文件`, { entry, interpolation: { escapeValue: false } }));
543
+ }
544
+ });
545
+ } else {
546
+ // 同步解压所有文件
547
+ zip.extractAllTo(destPath, true);
548
+ }
558
549
  }
559
550
 
560
551
  export async function selectSshPublicKey(avaiableKeys) {
561
- const keyNames = avaiableKeys.map((k) => k.path)
562
- const selected = (
563
- await inquirer.prompt([
564
- {
565
- name: "type",
566
- message: "选择使用的公钥",
567
- type: "list",
568
- choices: keyNames
569
- }
570
- ])
571
- )["type"]
572
- return {
573
- path: selected,
574
- content: fs.readFileSync(selected, "utf8")
575
- }
552
+ const keyNames = avaiableKeys.map((k) => k.path);
553
+ const selected = (
554
+ await inquirer.prompt([
555
+ {
556
+ name: 'type',
557
+ message: t('lzc_cli.lib.utils.select_ssh_public_key_prompt', '选择使用的公钥'),
558
+ type: 'list',
559
+ choices: keyNames,
560
+ },
561
+ ])
562
+ )['type'];
563
+ return {
564
+ path: selected,
565
+ content: fs.readFileSync(selected, 'utf8'),
566
+ };
576
567
  }
577
568
 
578
569
  export function findSshPublicKey() {
579
- // 获取用户 HOME 目录
580
- let homeDir
581
- switch (process.platform) {
582
- case "win32":
583
- // Windows: 通常是 C:\Users\username
584
- homeDir = process.env.USERPROFILE || os.homedir()
585
- break
586
- case "darwin":
587
- case "linux":
588
- // macOS 和 Linux: 通常是 /Users/username 或 /home/username
589
- homeDir = process.env.HOME || os.homedir()
590
- break
591
- default:
592
- throw new Error("Unsupported operating system")
593
- }
594
-
595
- const sshDir = path.join(homeDir, ".ssh")
596
-
597
- // 检查 .ssh 目录是否存在
598
- try {
599
- const dirStat = fs.statSync(sshDir)
600
- if (!dirStat.isDirectory()) {
601
- throw new Error(
602
- ".ssh 目前存在,但不是一个正常的目录,请确保 ssh 已经安装,以及 ssh-keygen 生成公钥"
603
- )
604
- }
605
- } catch (error) {
606
- if (error.code === "ENOENT") {
607
- throw new Error(
608
- ".ssh 目录不存在, 请确保 ssh 已经安装,以及 ssh-keygen 生成公钥"
609
- )
610
- }
611
- throw error
612
- }
613
-
614
- let avaiableKeys = []
615
- try {
616
- // 获取 .ssh 目录下所有文件
617
- const files = fs.readdirSync(sshDir)
618
- files.forEach((keyName) => {
619
- if (keyName.endsWith(".pub")) {
620
- const keyPath = path.join(sshDir, keyName)
621
- // 验证文件是否可读
622
- try {
623
- fs.accessSync(keyPath, fs.constants.R_OK)
624
- avaiableKeys.push({
625
- keyName,
626
- path: keyPath
627
- })
628
- } catch (error) {
629
- console.warn(`Found ${keyName} but cannot read it:`, error.message)
630
- }
631
- }
632
- })
633
-
634
- if (avaiableKeys.length == 0) {
635
- throw new Error(
636
- ".ssh 目录没有没有找到任何 .pub 公钥. 请使用 ssh-keygen 生成"
637
- )
638
- } else {
639
- return avaiableKeys
640
- }
641
- } catch (error) {
642
- if (error.code === "ENOENT") {
643
- throw new Error("不能查询 .ssh 目录")
644
- }
645
- throw error
646
- }
570
+ // 获取用户 HOME 目录
571
+ let homeDir;
572
+ switch (process.platform) {
573
+ case 'win32':
574
+ // Windows: 通常是 C:\Users\username
575
+ homeDir = process.env.USERPROFILE || os.homedir();
576
+ break;
577
+ case 'darwin':
578
+ case 'linux':
579
+ // macOS 和 Linux: 通常是 /Users/username 或 /home/username
580
+ homeDir = process.env.HOME || os.homedir();
581
+ break;
582
+ default:
583
+ throw new Error('Unsupported operating system');
584
+ }
585
+
586
+ const sshDir = path.join(homeDir, '.ssh');
587
+
588
+ // 检查 .ssh 目录是否存在
589
+ try {
590
+ const dirStat = fs.statSync(sshDir);
591
+ if (!dirStat.isDirectory()) {
592
+ throw new Error(t('lzc_cli.lib.utils.find_ssh_public_key_not_is_dir_fail', '.ssh 目前存在,但不是一个正常的目录,请确保 ssh 已经安装,以及 ssh-keygen 生成公钥'));
593
+ }
594
+ } catch (error) {
595
+ if (error.code === 'ENOENT') {
596
+ throw new Error(t('lzc_cli.lib.utils.find_ssh_public_key_dir_not_exist_fail', '.ssh 目录不存在, 请确保 ssh 已经安装,以及 ssh-keygen 生成公钥'));
597
+ }
598
+ throw error;
599
+ }
600
+
601
+ let avaiableKeys = [];
602
+ try {
603
+ // 获取 .ssh 目录下所有文件
604
+ const files = fs.readdirSync(sshDir);
605
+ files.forEach((keyName) => {
606
+ if (keyName.endsWith('.pub')) {
607
+ const keyPath = path.join(sshDir, keyName);
608
+ // 验证文件是否可读
609
+ try {
610
+ fs.accessSync(keyPath, fs.constants.R_OK);
611
+ avaiableKeys.push({
612
+ keyName,
613
+ path: keyPath,
614
+ });
615
+ } catch (error) {
616
+ console.warn(`Found ${keyName} but cannot read it:`, error.message);
617
+ }
618
+ }
619
+ });
620
+
621
+ if (avaiableKeys.length == 0) {
622
+ throw new Error(t('lzc_cli.lib.utils.find_ssh_public_key_files_not_exits_fail', '.ssh 目录没有没有找到任何 .pub 公钥. 请使用 ssh-keygen 生成'));
623
+ } else {
624
+ return avaiableKeys;
625
+ }
626
+ } catch (error) {
627
+ if (error.code === 'ENOENT') {
628
+ throw new Error(t('lzc_cli.lib.utils.find_ssh_public_key_dir_info_get_fail', '不能查询 .ssh 目录'));
629
+ }
630
+ throw error;
631
+ }
647
632
  }
648
633
 
649
634
  export function checkRsync() {
650
- return new Promise((resolve, reject) => {
651
- if (isLinux || isMacOs) {
652
- // 检查rsync工具是否存在:提示用户
653
- const rsyncExisted = commandExists.sync("rsync")
654
- if (!rsyncExisted) {
655
- reject("请检查 rsync 是否安装,路径是否正确!")
656
- }
657
-
658
- const check = spawn.sync("rsync", ["--version"], {
659
- shell: true,
660
- encoding: "utf-8",
661
- stdio: ["pipe", "pipe", "pipe"]
662
- })
663
- logger.debug(`执行命令 rsync --version`)
664
- if (check.status == 0) {
665
- const versionMatch = check.stdout.match(
666
- /rsync\s+version\s+(\d+\.\d+\.\d+)/i
667
- )
668
- if (!versionMatch || compareVersions("3.2.0", versionMatch[1]) < 0) {
669
- reject(`当前rsync版本为:${versionMatch[1]}, 要求rsync版本为: 3.2.0+`)
670
- }
671
- logger.debug(`当前rsync版本为:${versionMatch[1]}`)
672
- } else {
673
- reject("请检查 rsync 安装是否正确,指定 rsync --version 错误")
674
- }
675
- }
676
- resolve()
677
- })
635
+ return new Promise((resolve, reject) => {
636
+ if (isLinux || isMacOs) {
637
+ // 检查rsync工具是否存在:提示用户
638
+ const rsyncExisted = commandExists.sync('rsync');
639
+ if (!rsyncExisted) {
640
+ reject(t('lzc_cli.lib.utils.check_rsync_not_install_fail', '请检查 rsync 是否安装,路径是否正确!'));
641
+ }
642
+
643
+ const check = spawn.sync('rsync', ['--version'], {
644
+ shell: true,
645
+ encoding: 'utf-8',
646
+ stdio: ['pipe', 'pipe', 'pipe'],
647
+ });
648
+ logger.debug(t('lzc_cli.lib.utils.check_rsync_exec_version_log', `执行命令 rsync --version`));
649
+ if (check.status == 0) {
650
+ const versionMatch = check.stdout.match(/rsync\s+version\s+(\d+\.\d+\.\d+)/i);
651
+ if (!versionMatch || compareVersions('3.2.0', versionMatch[1]) < 0) {
652
+ reject(
653
+ t('lzc_cli.lib.utils.check_rsync_version_no_match_fail', `当前 rsync 版本为:{{ versionMatch }}, 要求 rsync 版本为: 3.2.0+`, {
654
+ versionMatch: versionMatch[1],
655
+ }),
656
+ );
657
+ }
658
+ logger.debug(t('lzc_cli.lib.utils.check_rsync_version_print_log', `当前 rsync 版本为:{{ versionMatch }}`, { versionMatch: versionMatch[1] }));
659
+ } else {
660
+ reject(t('lzc_cli.lib.utils.check_rsync_version_fail', '请检查 rsync 安装是否正确,指定 rsync --version 错误'));
661
+ }
662
+ }
663
+ resolve();
664
+ });
678
665
  }
679
666
 
680
667
  export function getLanguageForLocale(locale) {
681
- locale = locale.replace("_", "-")
682
- try {
683
- let l = new Intl.Locale(locale)
684
- return l.language
685
- } catch (error) {
686
- return locale
687
- }
668
+ locale = locale.replace('_', '-');
669
+ try {
670
+ let l = new Intl.Locale(locale);
671
+ return l.language;
672
+ } catch (error) {
673
+ return locale;
674
+ }
688
675
  }