@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/changelog.md +21 -0
- package/lib/app/apkshell.js +37 -40
- package/lib/app/index.js +187 -186
- package/lib/app/lpk_build.js +345 -358
- package/lib/app/lpk_create.js +136 -155
- package/lib/app/lpk_create_generator.js +77 -66
- package/lib/app/lpk_devshell.js +444 -533
- package/lib/app/lpk_devshell_docker.js +48 -47
- package/lib/app/lpk_installer.js +120 -123
- package/lib/appstore/index.js +245 -214
- package/lib/appstore/login.js +146 -143
- package/lib/appstore/prePublish.js +101 -100
- package/lib/appstore/publish.js +256 -256
- package/lib/box/index.js +82 -77
- package/lib/config/index.js +59 -54
- package/lib/debug_bridge.js +282 -330
- package/lib/docker/index.js +84 -86
- package/lib/i18n/README.md +25 -0
- package/lib/i18n/index.js +37 -0
- package/lib/i18n/locales/en/translation.json +252 -0
- package/lib/i18n/locales/zh/translation.json +252 -0
- package/lib/shellapi.js +122 -146
- package/lib/utils.js +539 -552
- package/package.json +6 -8
- package/scripts/cli.js +81 -77
- package/scripts/lzc-docker-compose.js +34 -33
- package/scripts/lzc-docker.js +34 -33
package/lib/utils.js
CHANGED
|
@@ -1,101 +1,108 @@
|
|
|
1
|
-
import path from
|
|
2
|
-
import fs from
|
|
3
|
-
import os from
|
|
4
|
-
import glob from
|
|
5
|
-
import yaml from
|
|
6
|
-
import mergeWith from
|
|
7
|
-
import { dirname } from
|
|
8
|
-
import { fileURLToPath } from
|
|
9
|
-
import ignore from
|
|
10
|
-
import { createHash } from
|
|
11
|
-
import https from
|
|
12
|
-
import http from
|
|
13
|
-
import zlib from
|
|
14
|
-
import process from
|
|
15
|
-
import spawn from
|
|
16
|
-
import logger from
|
|
17
|
-
import * as tar from
|
|
18
|
-
import dns from
|
|
19
|
-
import AdmZip from
|
|
20
|
-
import commandExists from
|
|
21
|
-
import fetch from
|
|
22
|
-
import semver from
|
|
23
|
-
import inquirer from
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
77
|
-
|
|
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
|
-
|
|
86
|
-
|
|
92
|
+
const wantArrs = want.split('.');
|
|
93
|
+
const currArrs = current.split('.');
|
|
87
94
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
101
|
+
if (c > w) return 1;
|
|
102
|
+
if (c < w) return -1;
|
|
103
|
+
}
|
|
97
104
|
|
|
98
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
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
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
270
|
+
return dirname(fileURLToPath(url));
|
|
260
271
|
}
|
|
261
272
|
|
|
262
273
|
export async function importDefault(pkgPath) {
|
|
263
|
-
|
|
264
|
-
|
|
274
|
+
let mod = await import(pkgPath);
|
|
275
|
+
return mod.default;
|
|
265
276
|
}
|
|
266
277
|
|
|
267
278
|
export class GitIgnore {
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
307
|
-
|
|
317
|
+
let stat = fs.statSync(path);
|
|
318
|
+
return stat.isDirectory();
|
|
308
319
|
}
|
|
309
320
|
|
|
310
321
|
export function isDirExist(path) {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
322
|
+
try {
|
|
323
|
+
return isDirSync(path);
|
|
324
|
+
} catch {
|
|
325
|
+
return false;
|
|
326
|
+
}
|
|
316
327
|
}
|
|
317
328
|
|
|
318
329
|
export function isFileExist(path) {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
|
|
340
|
+
return new Promise((resolve) => {
|
|
341
|
+
setTimeout(resolve, ms);
|
|
342
|
+
});
|
|
332
343
|
}
|
|
333
344
|
|
|
334
345
|
export async function md5String(str) {
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
413
|
-
|
|
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
|
-
|
|
418
|
-
|
|
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
|
-
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
export async function tarContentDir(from, to, cwd =
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
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
|
-
|
|
481
|
-
|
|
482
|
-
|
|
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
|
-
|
|
480
|
+
return logger.getLevel() <= logger.levels.DEBUG;
|
|
487
481
|
}
|
|
488
482
|
|
|
489
483
|
export function isTraceMode() {
|
|
490
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
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
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
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
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
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
|
}
|