@lazycatcloud/lzc-cli 1.3.12 → 1.3.13

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