mihomo-cli 1.5.0 → 2.0.0
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 +27 -0
- package/README.md +3 -2
- package/dist/index.js +5141 -0
- package/package.json +23 -16
- package/index.js +0 -1176
- package/src/config.js +0 -516
- package/src/kernel.js +0 -250
- package/src/overwrite.js +0 -258
- package/src/process.js +0 -691
- package/src/subscription.js +0 -257
- package/src/utils.js +0 -202
package/src/kernel.js
DELETED
|
@@ -1,250 +0,0 @@
|
|
|
1
|
-
// 内置模块
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const { execSync, spawnSync } = require('child_process');
|
|
5
|
-
|
|
6
|
-
// 第三方模块
|
|
7
|
-
const { compareVersions } = require('compare-versions');
|
|
8
|
-
|
|
9
|
-
// 本地模块
|
|
10
|
-
const config = require('./config');
|
|
11
|
-
const utils = require('./utils');
|
|
12
|
-
|
|
13
|
-
const GITHUB_REPO = 'MetaCubeX/mihomo';
|
|
14
|
-
const KERNEL_HTTP_TIMEOUT = 120000;
|
|
15
|
-
const KERNEL_MAX_CONTENT_LENGTH = 200 * 1024 * 1024;
|
|
16
|
-
const KERNEL_DOWNLOAD_TIMEOUT = 180000;
|
|
17
|
-
|
|
18
|
-
const HTTP_CLIENT = utils.createHttpClient({
|
|
19
|
-
timeout: KERNEL_HTTP_TIMEOUT,
|
|
20
|
-
maxContentLength: KERNEL_MAX_CONTENT_LENGTH,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
function withMirror(url, mirror) {
|
|
24
|
-
if (mirror && (url.startsWith('https://github.com/') || url.startsWith('https://api.github.com/'))) {
|
|
25
|
-
return mirror + url;
|
|
26
|
-
}
|
|
27
|
-
return url;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function getArch() {
|
|
31
|
-
const arch = process.arch;
|
|
32
|
-
if (arch === 'arm64') return 'arm64';
|
|
33
|
-
if (arch === 'x64') return 'amd64';
|
|
34
|
-
return arch;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function findMatchingAsset(assets, platform, arch) {
|
|
38
|
-
const prefix = 'mihomo-' + platform + '-' + arch;
|
|
39
|
-
const matchingAssets = assets.filter(a => {
|
|
40
|
-
return (a.name.startsWith(prefix) && a.name.endsWith('.gz')) || (a.name.startsWith(prefix + '-') && a.name.endsWith('.gz'));
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
if (matchingAssets.length === 0) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (matchingAssets.length === 1) {
|
|
48
|
-
return matchingAssets[0];
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const standardAsset = matchingAssets.find(a => {
|
|
52
|
-
const nameWithoutGz = a.name.slice(0, -3);
|
|
53
|
-
const parts = nameWithoutGz.split('-');
|
|
54
|
-
const lastPart = parts[parts.length - 1];
|
|
55
|
-
return /^v?\d+\.\d+\.\d+/.test(lastPart) && !nameWithoutGz.includes('-go');
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
if (standardAsset) {
|
|
59
|
-
return standardAsset;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return matchingAssets[0];
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function getLatestRelease(repo, mirror) {
|
|
66
|
-
const url = withMirror('https://api.github.com/repos/' + repo + '/releases', mirror);
|
|
67
|
-
const response = await HTTP_CLIENT.get(url);
|
|
68
|
-
|
|
69
|
-
const releases = response.data;
|
|
70
|
-
|
|
71
|
-
if (!Array.isArray(releases) || releases.length === 0) {
|
|
72
|
-
throw new Error('无法获取版本信息');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const stableReleases = releases.filter(
|
|
76
|
-
r =>
|
|
77
|
-
!r.prerelease &&
|
|
78
|
-
!r.tag_name.toLowerCase().includes('alpha') &&
|
|
79
|
-
!r.tag_name.toLowerCase().includes('beta') &&
|
|
80
|
-
!r.tag_name.toLowerCase().includes('prerelease'),
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
if (stableReleases.length > 0) {
|
|
84
|
-
return stableReleases[0];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return releases[0];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function checkUpdate(mirror) {
|
|
91
|
-
const currentVersion = config.getKernelVersion();
|
|
92
|
-
const latest = await getLatestRelease(GITHUB_REPO, mirror);
|
|
93
|
-
const latestVersion = latest.tag_name;
|
|
94
|
-
|
|
95
|
-
let needsUpdate = false;
|
|
96
|
-
let currentDisplay = currentVersion || '未安装';
|
|
97
|
-
|
|
98
|
-
if (!currentVersion) {
|
|
99
|
-
needsUpdate = true;
|
|
100
|
-
} else {
|
|
101
|
-
try {
|
|
102
|
-
needsUpdate = compareVersions(latestVersion.replace(/^v/, ''), currentVersion.replace(/^v/, '')) > 0;
|
|
103
|
-
} catch (_e) {
|
|
104
|
-
needsUpdate = latestVersion !== currentVersion;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
current: currentDisplay,
|
|
110
|
-
latest: latestVersion,
|
|
111
|
-
needsUpdate,
|
|
112
|
-
assets: latest.assets,
|
|
113
|
-
htmlUrl: latest.html_url,
|
|
114
|
-
release: latest,
|
|
115
|
-
};
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function findBinaryInDir(dir) {
|
|
119
|
-
const files = fs.readdirSync(dir);
|
|
120
|
-
|
|
121
|
-
for (const f of files) {
|
|
122
|
-
const fullPath = path.join(dir, f);
|
|
123
|
-
const stat = fs.statSync(fullPath);
|
|
124
|
-
|
|
125
|
-
if (stat.isDirectory()) {
|
|
126
|
-
const found = findBinaryInDir(fullPath);
|
|
127
|
-
if (found) return found;
|
|
128
|
-
continue;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (f === 'mihomo') {
|
|
132
|
-
return fullPath;
|
|
133
|
-
}
|
|
134
|
-
if (f.includes('mihomo') && !f.endsWith('.gz')) {
|
|
135
|
-
return fullPath;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
143
|
-
config.ensureDirs();
|
|
144
|
-
|
|
145
|
-
const latest = releaseInfo || (await getLatestRelease(GITHUB_REPO, mirror));
|
|
146
|
-
const arch = getArch();
|
|
147
|
-
const platform = process.platform;
|
|
148
|
-
|
|
149
|
-
const asset = findMatchingAsset(latest.assets, platform, arch);
|
|
150
|
-
|
|
151
|
-
if (!asset) {
|
|
152
|
-
const available = latest.assets.map(a => a.name).join(', ');
|
|
153
|
-
let hint = '';
|
|
154
|
-
if (available) {
|
|
155
|
-
hint = '\n 可用版本: ' + available;
|
|
156
|
-
}
|
|
157
|
-
throw new Error('未找到匹配的内核文件\n 平台: ' + platform + ', 架构: ' + arch + hint);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const downloadUrl = withMirror(asset.browser_download_url, mirror);
|
|
161
|
-
const tempPath = path.join(config.DIRS.core, asset.name);
|
|
162
|
-
const sizeMB = (asset.size / 1024 / 1024).toFixed(2);
|
|
163
|
-
|
|
164
|
-
if (progressCallback) {
|
|
165
|
-
progressCallback('下载内核: ' + asset.name + ' (' + sizeMB + ' MB)');
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const curlResult = spawnSync(
|
|
169
|
-
'curl',
|
|
170
|
-
['-L', '--progress-bar', '--connect-timeout', '30', '--max-time', String(Math.floor(KERNEL_DOWNLOAD_TIMEOUT / 1000)), '-o', tempPath, downloadUrl],
|
|
171
|
-
{ stdio: 'inherit' },
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
if (curlResult.status !== 0) {
|
|
175
|
-
try {
|
|
176
|
-
fs.unlinkSync(tempPath);
|
|
177
|
-
} catch {}
|
|
178
|
-
throw new Error('下载失败 (curl 退出码 ' + curlResult.status + ')');
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (!fs.existsSync(tempPath)) {
|
|
182
|
-
throw new Error('下载失败: 文件未生成');
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (progressCallback) {
|
|
186
|
-
progressCallback('解压内核...');
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const extractPath = config.DIRS.core;
|
|
190
|
-
let extractedBinary = null;
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
if (tempPath.endsWith('.tar.gz') || tempPath.endsWith('.tgz')) {
|
|
194
|
-
execSync('tar -xzf "' + tempPath + '" -C "' + extractPath + '"', {
|
|
195
|
-
stdio: ['pipe', 'pipe', 'inherit'],
|
|
196
|
-
});
|
|
197
|
-
} else if (tempPath.endsWith('.gz')) {
|
|
198
|
-
const baseName = path.basename(tempPath, '.gz');
|
|
199
|
-
const outputPath = path.join(extractPath, baseName);
|
|
200
|
-
execSync('gzip -dc "' + tempPath + '" > "' + outputPath + '"', {
|
|
201
|
-
stdio: ['pipe', 'pipe', 'inherit'],
|
|
202
|
-
});
|
|
203
|
-
extractedBinary = outputPath;
|
|
204
|
-
}
|
|
205
|
-
} catch (e) {
|
|
206
|
-
try {
|
|
207
|
-
fs.unlinkSync(tempPath);
|
|
208
|
-
} catch {}
|
|
209
|
-
throw new Error('解压失败: ' + e.message);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const foundBinary = extractedBinary || findBinaryInDir(extractPath);
|
|
213
|
-
|
|
214
|
-
if (!foundBinary) {
|
|
215
|
-
try {
|
|
216
|
-
fs.unlinkSync(tempPath);
|
|
217
|
-
} catch {}
|
|
218
|
-
throw new Error('解压后未找到可执行文件');
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const targetPath = config.PATHS.mihomoBinary;
|
|
222
|
-
|
|
223
|
-
if (foundBinary !== targetPath) {
|
|
224
|
-
if (fs.existsSync(targetPath)) {
|
|
225
|
-
fs.chmodSync(targetPath, 0o755);
|
|
226
|
-
try {
|
|
227
|
-
fs.unlinkSync(targetPath);
|
|
228
|
-
} catch {}
|
|
229
|
-
}
|
|
230
|
-
fs.renameSync(foundBinary, targetPath);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
fs.chmodSync(targetPath, 0o755);
|
|
234
|
-
|
|
235
|
-
try {
|
|
236
|
-
fs.unlinkSync(tempPath);
|
|
237
|
-
} catch (_e) {}
|
|
238
|
-
|
|
239
|
-
config.clearKernelVersionCache();
|
|
240
|
-
|
|
241
|
-
return {
|
|
242
|
-
version: latest.tag_name,
|
|
243
|
-
path: targetPath,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
module.exports = {
|
|
248
|
-
checkUpdate,
|
|
249
|
-
downloadKernel,
|
|
250
|
-
};
|
package/src/overwrite.js
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
// 内置模块
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
|
|
5
|
-
// 第三方模块
|
|
6
|
-
const yaml = require('js-yaml');
|
|
7
|
-
|
|
8
|
-
// 本地模块
|
|
9
|
-
const config = require('./config');
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* 解析覆写键名
|
|
13
|
-
* 支持的格式:
|
|
14
|
-
* - key! → 强制覆盖整个对象
|
|
15
|
-
* - +key → 数组前置插入
|
|
16
|
-
* - key+ → 数组追加
|
|
17
|
-
* - <+key> → 实际键名是 +key(当键名以 + 开头/结尾时)
|
|
18
|
-
* - +<+key> → 为键名 +key 执行前置插入
|
|
19
|
-
* - <+key>+ → 为键名 +key 执行追加
|
|
20
|
-
*
|
|
21
|
-
* 返回: { key: string, forceOverwrite: boolean, arrayPrepend: boolean, arrayAppend: boolean }
|
|
22
|
-
*/
|
|
23
|
-
function parseOverrideKey(key) {
|
|
24
|
-
let actualKey = key;
|
|
25
|
-
let forceOverwrite = false;
|
|
26
|
-
let arrayPrepend = false;
|
|
27
|
-
let arrayAppend = false;
|
|
28
|
-
|
|
29
|
-
// 1. 检查强制覆盖标记 (! 后缀,不在 <> 内时)
|
|
30
|
-
// 只有当 ! 是最后一个字符,且前面没有未闭合的 < 时才是标记
|
|
31
|
-
const lastChar = key[key.length - 1];
|
|
32
|
-
const openAngleCount = (key.match(/</g) || []).length;
|
|
33
|
-
const closeAngleCount = (key.match(/>/g) || []).length;
|
|
34
|
-
|
|
35
|
-
if (lastChar === '!' && openAngleCount === closeAngleCount) {
|
|
36
|
-
forceOverwrite = true;
|
|
37
|
-
actualKey = key.slice(0, -1);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// 2. 解析 <> 包裹的键名和 + 操作符
|
|
41
|
-
// 支持的模式:
|
|
42
|
-
// +<key> → prepend, key
|
|
43
|
-
// <key>+ → append, key
|
|
44
|
-
// <+key> → 无操作, 实际键名是 +key
|
|
45
|
-
// +<+key> → prepend, 实际键名是 +key
|
|
46
|
-
// <+key>+ → append, 实际键名是 +key
|
|
47
|
-
|
|
48
|
-
const wrappedMatch = actualKey.match(/^(\+)?(<[^>]+>)(\+)?$/);
|
|
49
|
-
if (wrappedMatch) {
|
|
50
|
-
const prefixPlus = wrappedMatch[1] === '+';
|
|
51
|
-
const wrappedPart = wrappedMatch[2];
|
|
52
|
-
const suffixPlus = wrappedMatch[3] === '+';
|
|
53
|
-
|
|
54
|
-
// 提取 <> 内的内容作为实际键名
|
|
55
|
-
const unwrapped = wrappedPart.slice(1, -1);
|
|
56
|
-
|
|
57
|
-
if (prefixPlus || suffixPlus) {
|
|
58
|
-
// 有 + 操作符,<> 内是实际键名
|
|
59
|
-
actualKey = unwrapped;
|
|
60
|
-
if (prefixPlus) arrayPrepend = true;
|
|
61
|
-
if (suffixPlus) arrayAppend = true;
|
|
62
|
-
} else {
|
|
63
|
-
// 没有 + 操作符,<key> 形式本身就是为了表示键名含特殊字符
|
|
64
|
-
// 这种情况下不需要额外处理,actualKey 已经是 wrappedPart,但我们需要 unwrap
|
|
65
|
-
// 例如:<+.google.cn> 的实际键名就是 +.google.cn
|
|
66
|
-
actualKey = unwrapped;
|
|
67
|
-
}
|
|
68
|
-
} else {
|
|
69
|
-
// 没有被 <> 完整包裹,检查开头和结尾的 +
|
|
70
|
-
if (actualKey.startsWith('+')) {
|
|
71
|
-
arrayPrepend = true;
|
|
72
|
-
actualKey = actualKey.slice(1);
|
|
73
|
-
}
|
|
74
|
-
if (actualKey.endsWith('+')) {
|
|
75
|
-
arrayAppend = true;
|
|
76
|
-
actualKey = actualKey.slice(0, -1);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return { key: actualKey, forceOverwrite, arrayPrepend, arrayAppend };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* 深度合并带覆写规则
|
|
85
|
-
*/
|
|
86
|
-
function deepMergeWithOverrides(target, override) {
|
|
87
|
-
if (target === null || target === undefined) {
|
|
88
|
-
target = Array.isArray(override) ? [] : {};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (override === null || override === undefined) {
|
|
92
|
-
return target;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// 如果 override 不是对象,直接返回 override(覆盖)
|
|
96
|
-
if (typeof override !== 'object') {
|
|
97
|
-
return override;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// 如果 override 是数组,target 也必须是数组
|
|
101
|
-
if (Array.isArray(override)) {
|
|
102
|
-
return override;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 此时 override 是普通对象
|
|
106
|
-
|
|
107
|
-
const result = { ...target };
|
|
108
|
-
|
|
109
|
-
for (const [rawKey, value] of Object.entries(override)) {
|
|
110
|
-
const { key, forceOverwrite, arrayPrepend, arrayAppend } = parseOverrideKey(rawKey);
|
|
111
|
-
|
|
112
|
-
const existingValue = result[key];
|
|
113
|
-
|
|
114
|
-
// 处理数组操作
|
|
115
|
-
if (arrayPrepend || arrayAppend) {
|
|
116
|
-
const existingArr = Array.isArray(existingValue) ? existingValue : [];
|
|
117
|
-
const overrideArr = Array.isArray(value) ? value : [value];
|
|
118
|
-
|
|
119
|
-
if (arrayPrepend) {
|
|
120
|
-
result[key] = [...overrideArr, ...existingArr];
|
|
121
|
-
} else {
|
|
122
|
-
result[key] = [...existingArr, ...overrideArr];
|
|
123
|
-
}
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// 处理强制覆盖
|
|
128
|
-
if (forceOverwrite) {
|
|
129
|
-
result[key] = value;
|
|
130
|
-
continue;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// 递归合并对象
|
|
134
|
-
if (
|
|
135
|
-
value !== null &&
|
|
136
|
-
typeof value === 'object' &&
|
|
137
|
-
!Array.isArray(value) &&
|
|
138
|
-
existingValue !== null &&
|
|
139
|
-
typeof existingValue === 'object' &&
|
|
140
|
-
!Array.isArray(existingValue)
|
|
141
|
-
) {
|
|
142
|
-
result[key] = deepMergeWithOverrides(existingValue, value);
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// 其他情况直接覆盖
|
|
147
|
-
result[key] = value;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
return result;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* 检查覆写功能是否启用
|
|
155
|
-
*/
|
|
156
|
-
function isOverwriteEnabled() {
|
|
157
|
-
const settings = config.readSettings();
|
|
158
|
-
return settings.overwrite_enabled !== false; // 默认启用
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* 启用/禁用覆写功能
|
|
163
|
-
*/
|
|
164
|
-
function setOverwriteEnabled(enabled) {
|
|
165
|
-
config.writeSettings({ overwrite_enabled: enabled });
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* 读取根目录下的覆写文件
|
|
170
|
-
* 匹配 overwrite.yaml 和 overwrite.*.yaml,按文件名排序
|
|
171
|
-
* overwrite.yaml 始终第一
|
|
172
|
-
*/
|
|
173
|
-
function loadOverwriteFile() {
|
|
174
|
-
const dir = config.USER_DATA_DIR;
|
|
175
|
-
|
|
176
|
-
if (!fs.existsSync(dir)) {
|
|
177
|
-
return [];
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const files = fs
|
|
181
|
-
.readdirSync(dir)
|
|
182
|
-
.filter(f => f === 'overwrite.yaml' || /^overwrite\..+\.ya?ml$/.test(f))
|
|
183
|
-
.sort((a, b) => {
|
|
184
|
-
if (a === 'overwrite.yaml') return -1;
|
|
185
|
-
if (b === 'overwrite.yaml') return 1;
|
|
186
|
-
return a.localeCompare(b);
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
const results = [];
|
|
190
|
-
|
|
191
|
-
for (const file of files) {
|
|
192
|
-
const filePath = path.join(dir, file);
|
|
193
|
-
try {
|
|
194
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
195
|
-
const parsed = yaml.load(content);
|
|
196
|
-
if (parsed && typeof parsed === 'object') {
|
|
197
|
-
results.push({
|
|
198
|
-
name: file,
|
|
199
|
-
path: filePath,
|
|
200
|
-
config: parsed,
|
|
201
|
-
});
|
|
202
|
-
}
|
|
203
|
-
} catch (e) {
|
|
204
|
-
console.warn('警告: 覆写文件 "' + file + '" 解析失败: ' + e.message);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
return results;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* 应用所有覆写配置到基础配置
|
|
213
|
-
*/
|
|
214
|
-
function applyOverwrite(baseConfig) {
|
|
215
|
-
if (!isOverwriteEnabled()) {
|
|
216
|
-
return baseConfig;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
const overwriteFiles = loadOverwriteFile();
|
|
220
|
-
|
|
221
|
-
if (overwriteFiles.length === 0) {
|
|
222
|
-
return baseConfig;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
let result = { ...baseConfig };
|
|
226
|
-
|
|
227
|
-
for (const file of overwriteFiles) {
|
|
228
|
-
result = deepMergeWithOverrides(result, file.config);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
return result;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* 列出覆写文件信息
|
|
236
|
-
*/
|
|
237
|
-
function listOverwriteFile() {
|
|
238
|
-
const files = loadOverwriteFile();
|
|
239
|
-
const enabled = isOverwriteEnabled();
|
|
240
|
-
|
|
241
|
-
return {
|
|
242
|
-
enabled,
|
|
243
|
-
dir: config.USER_DATA_DIR,
|
|
244
|
-
files: files.map(f => ({
|
|
245
|
-
name: f.name,
|
|
246
|
-
path: f.path,
|
|
247
|
-
keys: Object.keys(f.config || {}),
|
|
248
|
-
})),
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
module.exports = {
|
|
253
|
-
isOverwriteEnabled,
|
|
254
|
-
setOverwriteEnabled,
|
|
255
|
-
applyOverwrite,
|
|
256
|
-
listOverwriteFile,
|
|
257
|
-
loadOverwriteFile,
|
|
258
|
-
};
|