mihomo-cli 1.2.3 → 1.2.4
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 +18 -0
- package/index.js +15 -40
- package/package.json +1 -1
- package/src/config.js +1 -1
- package/src/overwrite.js +8 -8
- package/src/process.js +18 -36
- package/src/subscription.js +2 -35
- package/src/utils.js +101 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.4] - 2026-04-07
|
|
4
|
+
|
|
5
|
+
### 修复
|
|
6
|
+
|
|
7
|
+
- **路径安全**:`getLogPathByName()` 增加 `isPathUnderDir()` 校验,防止潜在的路径遍历风险
|
|
8
|
+
- **错误提示**:覆写配置文件解析失败时显示警告日志,不再静默忽略
|
|
9
|
+
|
|
10
|
+
### 重构
|
|
11
|
+
|
|
12
|
+
- **代码结构**:工具函数(`sleepSync`、`formatBytes`、`isProcessRunning` 等)提取到 `utils.js` 模块,简化各模块依赖
|
|
13
|
+
- **命名规范**:统一函数命名为全称单数
|
|
14
|
+
- `autoUpdateStaleSubscriptions` → `autoUpdateStaleSubscription`
|
|
15
|
+
- `applyOverwrites` → `applyOverwrite`
|
|
16
|
+
- `loadOverwriteFiles` → `loadOverwriteFile`
|
|
17
|
+
- `listOverwriteFiles` → `listOverwriteFile`
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
3
21
|
## [1.2.3] - 2026-04-07
|
|
4
22
|
|
|
5
23
|
### 优化
|
package/index.js
CHANGED
|
@@ -8,6 +8,7 @@ const kernel = require('./src/kernel');
|
|
|
8
8
|
const subscription = require('./src/subscription');
|
|
9
9
|
const processMgr = require('./src/process');
|
|
10
10
|
const overwrite = require('./src/overwrite');
|
|
11
|
+
const utils = require('./src/utils');
|
|
11
12
|
|
|
12
13
|
const VERSION = require('./package.json').version;
|
|
13
14
|
|
|
@@ -127,7 +128,7 @@ function printStatus() {
|
|
|
127
128
|
const status = processMgr.getStatus();
|
|
128
129
|
const info = config.getConfigInfo();
|
|
129
130
|
const owEnabled = overwrite.isOverwriteEnabled();
|
|
130
|
-
const owFiles = overwrite.
|
|
131
|
+
const owFiles = overwrite.listOverwriteFile().files;
|
|
131
132
|
const activeSub = getActiveSubscription();
|
|
132
133
|
|
|
133
134
|
console.log('');
|
|
@@ -221,32 +222,6 @@ function pickSingleSubscription(subs, pattern) {
|
|
|
221
222
|
process.exit(1);
|
|
222
223
|
}
|
|
223
224
|
|
|
224
|
-
function hasFlag(args, short, long) {
|
|
225
|
-
return args && (args.includes(short) || args.includes(long));
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function parseIntArg(args, short, long, defaultValue) {
|
|
229
|
-
if (!args) return defaultValue;
|
|
230
|
-
for (let i = 0; i < args.length; i++) {
|
|
231
|
-
if (args[i] === short || args[i] === long) {
|
|
232
|
-
if (i + 1 < args.length) {
|
|
233
|
-
const val = parseInt(args[i + 1]);
|
|
234
|
-
return isNaN(val) ? defaultValue : val;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
return defaultValue;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
function getNonFlagArg(args, startIdx) {
|
|
242
|
-
if (!args) return null;
|
|
243
|
-
for (let i = startIdx; i < args.length; i++) {
|
|
244
|
-
if (!args[i].startsWith('-')) {
|
|
245
|
-
return args[i];
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
return null;
|
|
249
|
-
}
|
|
250
225
|
|
|
251
226
|
function openLogFile(logPath, label) {
|
|
252
227
|
const displayLabel = label || logPath;
|
|
@@ -305,7 +280,7 @@ async function cmdStart(args) {
|
|
|
305
280
|
process.exit(1);
|
|
306
281
|
}
|
|
307
282
|
|
|
308
|
-
await subscription.
|
|
283
|
+
await subscription.autoUpdateStaleSubscription();
|
|
309
284
|
|
|
310
285
|
// 每次 start 都先确保完全干净的状态(停止进程 + 清理运行时文件)
|
|
311
286
|
const status = processMgr.getStatus();
|
|
@@ -390,7 +365,7 @@ function cmdUI(args) {
|
|
|
390
365
|
function cmdLog(args) {
|
|
391
366
|
const logPath = processMgr.getLogPath();
|
|
392
367
|
|
|
393
|
-
if (hasFlag(args, '-o', '--open')) {
|
|
368
|
+
if (utils.hasFlag(args, '-o', '--open')) {
|
|
394
369
|
openLogFile(logPath);
|
|
395
370
|
return;
|
|
396
371
|
}
|
|
@@ -399,9 +374,9 @@ function cmdLog(args) {
|
|
|
399
374
|
}
|
|
400
375
|
|
|
401
376
|
function cmdLogs(args) {
|
|
402
|
-
const targetName = getNonFlagArg(args, 1);
|
|
403
|
-
const lines = parseIntArg(args, '-n', '--lines', 100);
|
|
404
|
-
const openInViewer = hasFlag(args, '-o', '--open');
|
|
377
|
+
const targetName = utils.getNonFlagArg(args, 1);
|
|
378
|
+
const lines = utils.parseIntArg(args, '-n', '--lines', 100);
|
|
379
|
+
const openInViewer = utils.hasFlag(args, '-o', '--open');
|
|
405
380
|
|
|
406
381
|
if (targetName) {
|
|
407
382
|
let logPath;
|
|
@@ -466,8 +441,8 @@ function cmdLogs(args) {
|
|
|
466
441
|
archiveCounter++;
|
|
467
442
|
num = archiveCounter < 10 ? ' ' + archiveCounter : '' + archiveCounter;
|
|
468
443
|
}
|
|
469
|
-
const time =
|
|
470
|
-
const size =
|
|
444
|
+
const time = utils.formatDate(log.mtime);
|
|
445
|
+
const size = utils.formatBytes(log.size);
|
|
471
446
|
const name = log.isCurrent ? 'mihomo.log (当前运行中)' : log.name;
|
|
472
447
|
|
|
473
448
|
console.log(' ' + num + '. ' + name);
|
|
@@ -586,7 +561,7 @@ async function cmdKernel(args) {
|
|
|
586
561
|
}
|
|
587
562
|
|
|
588
563
|
async function printSubscriptionList() {
|
|
589
|
-
const updateResult = await subscription.
|
|
564
|
+
const updateResult = await subscription.autoUpdateStaleSubscription();
|
|
590
565
|
if (updateResult.total > 0) {
|
|
591
566
|
console.log('');
|
|
592
567
|
}
|
|
@@ -601,7 +576,7 @@ async function printSubscriptionList() {
|
|
|
601
576
|
}
|
|
602
577
|
console.log('订阅列表:');
|
|
603
578
|
subs.forEach((s, i) => {
|
|
604
|
-
const time =
|
|
579
|
+
const time = utils.formatDate(s.updated_at);
|
|
605
580
|
const defaultMark = i === 0 ? ' [默认]' : '';
|
|
606
581
|
const interval = s.update_interval || subscription.DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
607
582
|
console.log(' ' + (i + 1) + '. ' + s.name + defaultMark);
|
|
@@ -612,8 +587,8 @@ async function printSubscriptionList() {
|
|
|
612
587
|
}
|
|
613
588
|
if (s.download !== undefined || s.total !== undefined) {
|
|
614
589
|
const used = (s.upload || 0) + (s.download || 0);
|
|
615
|
-
const usedStr =
|
|
616
|
-
const totalStr =
|
|
590
|
+
const usedStr = utils.formatBytes(used);
|
|
591
|
+
const totalStr = utils.formatBytes(s.total);
|
|
617
592
|
let percentStr = '';
|
|
618
593
|
if (s.total && s.total > 0) {
|
|
619
594
|
const percent = Math.min((used / s.total) * 100, 100);
|
|
@@ -622,7 +597,7 @@ async function printSubscriptionList() {
|
|
|
622
597
|
console.log(' 流量: ' + usedStr + ' / ' + totalStr + percentStr);
|
|
623
598
|
}
|
|
624
599
|
if (s.expire !== undefined) {
|
|
625
|
-
console.log(' 到期: ' +
|
|
600
|
+
console.log(' 到期: ' + utils.formatTimestamp(s.expire));
|
|
626
601
|
}
|
|
627
602
|
if (s.web_page_url) {
|
|
628
603
|
console.log(' 页面: ' + s.web_page_url);
|
|
@@ -853,7 +828,7 @@ async function cmdReset(args) {
|
|
|
853
828
|
}
|
|
854
829
|
|
|
855
830
|
function printOverwriteList() {
|
|
856
|
-
const info = overwrite.
|
|
831
|
+
const info = overwrite.listOverwriteFile();
|
|
857
832
|
console.log('状态: ' + (info.enabled ? '已启用' : '已禁用'));
|
|
858
833
|
console.log('目录: ' + info.dir);
|
|
859
834
|
console.log('');
|
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -315,7 +315,7 @@ function buildConfig(subRawContent, mode) {
|
|
|
315
315
|
const overwrite = require('./overwrite');
|
|
316
316
|
|
|
317
317
|
// 应用覆写配置
|
|
318
|
-
const withOverwrites = overwrite.
|
|
318
|
+
const withOverwrites = overwrite.applyOverwrite(baseConfig);
|
|
319
319
|
|
|
320
320
|
// 合并 BASE_CONFIG(优先级高于覆写)
|
|
321
321
|
const merged = { ...withOverwrites, ...BASE_CONFIG };
|
package/src/overwrite.js
CHANGED
|
@@ -171,7 +171,7 @@ function setOverwriteEnabled(enabled) {
|
|
|
171
171
|
* 读取 overwrites 目录下的所有 yaml 文件
|
|
172
172
|
* 按文件名排序返回
|
|
173
173
|
*/
|
|
174
|
-
function
|
|
174
|
+
function loadOverwriteFile() {
|
|
175
175
|
const dir = getOverwritesDir();
|
|
176
176
|
|
|
177
177
|
if (!fs.existsSync(dir)) {
|
|
@@ -197,7 +197,7 @@ function loadOverwriteFiles() {
|
|
|
197
197
|
});
|
|
198
198
|
}
|
|
199
199
|
} catch (e) {
|
|
200
|
-
|
|
200
|
+
console.warn('警告: 覆写文件 "' + file + '" 解析失败: ' + e.message);
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
|
|
@@ -207,12 +207,12 @@ function loadOverwriteFiles() {
|
|
|
207
207
|
/**
|
|
208
208
|
* 应用所有覆写配置到基础配置
|
|
209
209
|
*/
|
|
210
|
-
function
|
|
210
|
+
function applyOverwrite(baseConfig) {
|
|
211
211
|
if (!isOverwriteEnabled()) {
|
|
212
212
|
return baseConfig;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
const overwriteFiles =
|
|
215
|
+
const overwriteFiles = loadOverwriteFile();
|
|
216
216
|
|
|
217
217
|
if (overwriteFiles.length === 0) {
|
|
218
218
|
return baseConfig;
|
|
@@ -230,8 +230,8 @@ function applyOverwrites(baseConfig) {
|
|
|
230
230
|
/**
|
|
231
231
|
* 列出覆写文件信息
|
|
232
232
|
*/
|
|
233
|
-
function
|
|
234
|
-
const files =
|
|
233
|
+
function listOverwriteFile() {
|
|
234
|
+
const files = loadOverwriteFile();
|
|
235
235
|
const enabled = isOverwriteEnabled();
|
|
236
236
|
const dir = getOverwritesDir();
|
|
237
237
|
|
|
@@ -249,6 +249,6 @@ function listOverwriteFiles() {
|
|
|
249
249
|
module.exports = {
|
|
250
250
|
isOverwriteEnabled,
|
|
251
251
|
setOverwriteEnabled,
|
|
252
|
-
|
|
253
|
-
|
|
252
|
+
applyOverwrite,
|
|
253
|
+
listOverwriteFile,
|
|
254
254
|
};
|
package/src/process.js
CHANGED
|
@@ -2,11 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { spawn, execSync } = require('child_process');
|
|
4
4
|
const config = require('./config');
|
|
5
|
-
|
|
6
|
-
const _sharedBuf = new Int32Array(new SharedArrayBuffer(4));
|
|
7
|
-
function sleepSync(ms) {
|
|
8
|
-
Atomics.wait(_sharedBuf, 0, 0, ms);
|
|
9
|
-
}
|
|
5
|
+
const utils = require('./utils');
|
|
10
6
|
|
|
11
7
|
function clearRuntime() {
|
|
12
8
|
if (fs.existsSync(config.DIRS.runtime)) {
|
|
@@ -27,21 +23,9 @@ function getPid() {
|
|
|
27
23
|
}
|
|
28
24
|
}
|
|
29
25
|
|
|
30
|
-
function isProcessRunning(pid) {
|
|
31
|
-
if (!pid) return false;
|
|
32
|
-
try {
|
|
33
|
-
const output = execSync('ps -p ' + pid + ' -o pid= 2>/dev/null || true', {
|
|
34
|
-
encoding: 'utf8',
|
|
35
|
-
}).trim();
|
|
36
|
-
return output.length > 0;
|
|
37
|
-
} catch (e) {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
26
|
function isRunning() {
|
|
43
27
|
const pid = getPid();
|
|
44
|
-
return pid ? isProcessRunning(pid) : false;
|
|
28
|
+
return pid ? utils.isProcessRunning(pid) : false;
|
|
45
29
|
}
|
|
46
30
|
|
|
47
31
|
function getAllMihomoPids() {
|
|
@@ -61,17 +45,6 @@ function getAllMihomoPids() {
|
|
|
61
45
|
}
|
|
62
46
|
}
|
|
63
47
|
|
|
64
|
-
function isProcessRoot(pid) {
|
|
65
|
-
try {
|
|
66
|
-
const uidOutput = execSync('ps -p ' + pid + ' -o uid= 2>/dev/null || true', {
|
|
67
|
-
encoding: 'utf8',
|
|
68
|
-
}).trim();
|
|
69
|
-
return uidOutput === '0';
|
|
70
|
-
} catch (e) {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
48
|
function isPidFileOwnedByRoot() {
|
|
76
49
|
if (!fs.existsSync(config.PATHS.pidFile)) {
|
|
77
50
|
return false;
|
|
@@ -86,7 +59,7 @@ function isPidFileOwnedByRoot() {
|
|
|
86
59
|
|
|
87
60
|
function checkStaleState() {
|
|
88
61
|
const allPids = getAllMihomoPids();
|
|
89
|
-
const hasRootProcess = allPids.some(p => isProcessRoot(p));
|
|
62
|
+
const hasRootProcess = allPids.some(p => utils.isProcessRoot(p));
|
|
90
63
|
const hasRootPidFile = isPidFileOwnedByRoot();
|
|
91
64
|
|
|
92
65
|
return {
|
|
@@ -184,7 +157,7 @@ function cleanupAll(forceSudo) {
|
|
|
184
157
|
return { killed: 0, failed: 0, remaining: [] };
|
|
185
158
|
}
|
|
186
159
|
|
|
187
|
-
const hasRootProcess = pids.some(p => isProcessRoot(p));
|
|
160
|
+
const hasRootProcess = pids.some(p => utils.isProcessRoot(p));
|
|
188
161
|
const hasRootPidFile = isPidFileOwnedByRoot();
|
|
189
162
|
const needsSudo = hasRootProcess;
|
|
190
163
|
const allowSudo = forceSudo || hasRootProcess || hasRootPidFile;
|
|
@@ -216,7 +189,7 @@ function cleanupAll(forceSudo) {
|
|
|
216
189
|
|
|
217
190
|
for (let i = 0; i < 50; i++) {
|
|
218
191
|
if (getAllMihomoPids().length === 0) break;
|
|
219
|
-
sleepSync(100);
|
|
192
|
+
utils.sleepSync(100);
|
|
220
193
|
}
|
|
221
194
|
|
|
222
195
|
clearPid();
|
|
@@ -306,7 +279,7 @@ function getProcessInfo(pid) {
|
|
|
306
279
|
pid,
|
|
307
280
|
memory: rss ? (rss / 1024).toFixed(1) + ' MB' : '未知',
|
|
308
281
|
cpu: pcpu ? pcpu.toFixed(1) + '%' : '未知',
|
|
309
|
-
isRoot: isProcessRoot(pid),
|
|
282
|
+
isRoot: utils.isProcessRoot(pid),
|
|
310
283
|
};
|
|
311
284
|
} catch (e) {
|
|
312
285
|
return { pid, memory: '未知', cpu: '未知', isRoot: false };
|
|
@@ -606,6 +579,12 @@ function listLogs() {
|
|
|
606
579
|
return result;
|
|
607
580
|
}
|
|
608
581
|
|
|
582
|
+
function isPathUnderDir(filePath, baseDir) {
|
|
583
|
+
const resolvedPath = path.resolve(filePath);
|
|
584
|
+
const resolvedBase = path.resolve(baseDir);
|
|
585
|
+
return resolvedPath === resolvedBase || resolvedPath.startsWith(resolvedBase + path.sep);
|
|
586
|
+
}
|
|
587
|
+
|
|
609
588
|
function getLogPathByName(name) {
|
|
610
589
|
const logsDir = config.DIRS.logs;
|
|
611
590
|
|
|
@@ -619,16 +598,19 @@ function getLogPathByName(name) {
|
|
|
619
598
|
}
|
|
620
599
|
|
|
621
600
|
const filePath = path.join(logsDir, targetName);
|
|
622
|
-
if (fs.existsSync(filePath)) {
|
|
601
|
+
if (fs.existsSync(filePath) && isPathUnderDir(filePath, logsDir)) {
|
|
623
602
|
return filePath;
|
|
624
603
|
}
|
|
625
604
|
|
|
626
|
-
//
|
|
605
|
+
// 尝试模糊匹配(readdirSync 返回的文件名已是安全的,但为了一致性仍校验)
|
|
627
606
|
if (fs.existsSync(logsDir)) {
|
|
628
607
|
const files = fs.readdirSync(logsDir);
|
|
629
608
|
for (const file of files) {
|
|
630
609
|
if (file.includes(name)) {
|
|
631
|
-
|
|
610
|
+
const candidatePath = path.join(logsDir, file);
|
|
611
|
+
if (isPathUnderDir(candidatePath, logsDir)) {
|
|
612
|
+
return candidatePath;
|
|
613
|
+
}
|
|
632
614
|
}
|
|
633
615
|
}
|
|
634
616
|
}
|
package/src/subscription.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
|
-
const yaml = require('js-yaml');
|
|
3
2
|
const config = require('./config');
|
|
4
3
|
|
|
5
4
|
const DEFAULT_UPDATE_INTERVAL_HOURS = 12;
|
|
@@ -38,35 +37,6 @@ function parseUsernameFromContentDisposition(header) {
|
|
|
38
37
|
return parts[parts.length - 1] || null;
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
function formatBytes(bytes) {
|
|
42
|
-
if (bytes === undefined || bytes === null) return '未知';
|
|
43
|
-
if (bytes === 0) return '0 B';
|
|
44
|
-
const k = 1024;
|
|
45
|
-
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
46
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
47
|
-
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function formatTimestamp(ts) {
|
|
51
|
-
if (!ts) return '未知';
|
|
52
|
-
try {
|
|
53
|
-
return new Date(ts * 1000).toLocaleString('zh-CN');
|
|
54
|
-
} catch {
|
|
55
|
-
return '未知';
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function formatDate(dateOrIso) {
|
|
60
|
-
if (!dateOrIso) return '未知';
|
|
61
|
-
try {
|
|
62
|
-
const d = dateOrIso instanceof Date ? dateOrIso : new Date(dateOrIso);
|
|
63
|
-
if (isNaN(d.getTime())) return '未知';
|
|
64
|
-
return d.toLocaleString('zh-CN');
|
|
65
|
-
} catch {
|
|
66
|
-
return '未知';
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
40
|
function formatProxySummary(info) {
|
|
71
41
|
const parts = [];
|
|
72
42
|
if (info && info.proxyGroups > 0) parts.push(info.proxyGroups + ' 组');
|
|
@@ -178,7 +148,7 @@ async function tryUpdateOne(sub) {
|
|
|
178
148
|
}
|
|
179
149
|
}
|
|
180
150
|
|
|
181
|
-
async function
|
|
151
|
+
async function autoUpdateStaleSubscription() {
|
|
182
152
|
const allSubs = config.getSubscriptionsWithCache();
|
|
183
153
|
const staleSubs = allSubs.filter(needsAutoUpdate);
|
|
184
154
|
|
|
@@ -217,10 +187,7 @@ module.exports = {
|
|
|
217
187
|
DEFAULT_UPDATE_INTERVAL_HOURS,
|
|
218
188
|
downloadSubscription,
|
|
219
189
|
prepareConfigForStart,
|
|
220
|
-
formatBytes,
|
|
221
|
-
formatTimestamp,
|
|
222
|
-
formatDate,
|
|
223
190
|
formatProxySummary,
|
|
224
191
|
tryUpdateOne,
|
|
225
|
-
|
|
192
|
+
autoUpdateStaleSubscription,
|
|
226
193
|
};
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
|
|
3
|
+
const _sleepBuf = new Int32Array(1);
|
|
4
|
+
|
|
5
|
+
function sleepSync(ms) {
|
|
6
|
+
Atomics.wait(_sleepBuf, 0, 0, ms);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function formatBytes(bytes) {
|
|
10
|
+
if (bytes === undefined || bytes === null) return '未知';
|
|
11
|
+
const num = Number(bytes);
|
|
12
|
+
if (isNaN(num) || num < 0) return '未知';
|
|
13
|
+
if (num === 0) return '0 B';
|
|
14
|
+
const k = 1024;
|
|
15
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
16
|
+
const i = Math.floor(Math.log(num) / Math.log(k));
|
|
17
|
+
return parseFloat((num / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatTimestamp(ts) {
|
|
21
|
+
if (ts === undefined || ts === null) return '未知';
|
|
22
|
+
try {
|
|
23
|
+
return new Date(ts * 1000).toLocaleString('zh-CN');
|
|
24
|
+
} catch {
|
|
25
|
+
return '未知';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function formatDate(dateOrIso) {
|
|
30
|
+
if (dateOrIso === undefined || dateOrIso === null) return '未知';
|
|
31
|
+
try {
|
|
32
|
+
const d = dateOrIso instanceof Date ? dateOrIso : new Date(dateOrIso);
|
|
33
|
+
if (isNaN(d.getTime())) return '未知';
|
|
34
|
+
return d.toLocaleString('zh-CN');
|
|
35
|
+
} catch {
|
|
36
|
+
return '未知';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function hasFlag(args, short, long) {
|
|
41
|
+
return args && (args.includes(short) || args.includes(long));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseIntArg(args, short, long, defaultValue) {
|
|
45
|
+
if (!args) return defaultValue;
|
|
46
|
+
for (let i = 0; i < args.length; i++) {
|
|
47
|
+
if (args[i] === short || args[i] === long) {
|
|
48
|
+
if (i + 1 < args.length) {
|
|
49
|
+
const val = parseInt(args[i + 1]);
|
|
50
|
+
return isNaN(val) ? defaultValue : val;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return defaultValue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function getNonFlagArg(args, startIdx) {
|
|
58
|
+
if (!args) return null;
|
|
59
|
+
for (let i = startIdx; i < args.length; i++) {
|
|
60
|
+
if (!args[i].startsWith('-')) {
|
|
61
|
+
return args[i];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function isProcessRunning(pid) {
|
|
68
|
+
if (!pid) return false;
|
|
69
|
+
try {
|
|
70
|
+
const output = execSync('ps -p ' + pid + ' -o pid= 2>/dev/null || true', {
|
|
71
|
+
encoding: 'utf8',
|
|
72
|
+
}).trim();
|
|
73
|
+
return output.length > 0;
|
|
74
|
+
} catch (e) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isProcessRoot(pid) {
|
|
80
|
+
if (!pid) return false;
|
|
81
|
+
try {
|
|
82
|
+
const uidOutput = execSync('ps -p ' + pid + ' -o uid= 2>/dev/null || true', {
|
|
83
|
+
encoding: 'utf8',
|
|
84
|
+
}).trim();
|
|
85
|
+
return uidOutput === '0';
|
|
86
|
+
} catch (e) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = {
|
|
92
|
+
sleepSync,
|
|
93
|
+
formatBytes,
|
|
94
|
+
formatTimestamp,
|
|
95
|
+
formatDate,
|
|
96
|
+
hasFlag,
|
|
97
|
+
parseIntArg,
|
|
98
|
+
getNonFlagArg,
|
|
99
|
+
isProcessRunning,
|
|
100
|
+
isProcessRoot,
|
|
101
|
+
};
|