jsir 2.4.5 → 2.4.8

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/cmd/oaa.js CHANGED
@@ -30,9 +30,8 @@ const _libDataDir = getLibDataDir()
30
30
  const _fileWatcherMap = {}
31
31
  const _types = setting.fileType
32
32
  const console = createConsole();
33
- const room = require('../deps/room');
34
- const server = require('../deps/server')
35
- const {reqNode} = require("../deps/room");
33
+ const Room = require('../deps/room');
34
+ const Server = require('../deps/server')
36
35
 
37
36
  let lastFilterArg = '';
38
37
  let _cmdMapFile = setting.name + 'CmdMap.json'
@@ -675,9 +674,12 @@ async function joinServer(uniqueName) {
675
674
  let pair = parseUniqueName(uniqueName)
676
675
  for (let key of Object.keys(exportLib)) {
677
676
  let val = exportLib[key];
678
- server.setRoute('post', `/${pair[0]}/${trimJsirFileName(pair[1])}/${key}`,async (req, res) => {
677
+ Server.setRoute('get', `/${pair[0]}/${trimJsirFileName(pair[1])}/${key}`,async (req, res) => {
679
678
  res.result = await val(...req);
680
- }, server.jsonHandle)
679
+ })
680
+ Server.setRoute('post', `/${pair[0]}/${trimJsirFileName(pair[1])}/${key}`,async (req, res) => {
681
+ res.result = await val(...req);
682
+ })
681
683
  }
682
684
  console.msg(uniqueName, 'has become a service.');
683
685
  }
@@ -1032,7 +1034,6 @@ async function showRooms() {
1032
1034
  let room = setting.rooms[i]
1033
1035
  let isLocal = setting.selfRoom.selfNode === room.selfNode;
1034
1036
  results.push({
1035
- mid: i,
1036
1037
  name: (isLocal ? "*" : " ") + room.name,
1037
1038
  node: room.selfNode,
1038
1039
  active: room.active,
@@ -1041,17 +1042,18 @@ async function showRooms() {
1041
1042
  let jsir = room.jsirs[key]
1042
1043
  let pidStr = (process.pid === jsir.pid ? "*" : " ") + jsir.pid;
1043
1044
  results.push({
1044
- mid: i,
1045
1045
  name: (isLocal ? "*" : " ") + room.name,
1046
1046
  node: room.selfNode,
1047
1047
  active: room.active,
1048
1048
  pid: jsir.newError ? errorStr(pidStr):pidStr,
1049
+ up: jsir.upTime || '',
1050
+ version: jsir.version || '',
1049
1051
  space: jsir.space,
1050
1052
  running: jsir.active,
1051
1053
  port: jsir.port,
1052
1054
  back: jsir.back,
1053
1055
  busy: jsir.busy,
1054
- tips: Object.keys(jsir.tips).map(i => i + ': ' + jsir.tips[i]).join('\n'),
1056
+ tips: Object.keys(jsir.tips).map(i => infoStr(i) + ': ' + jsir.tips[i]).join('\n'),
1055
1057
  services: Object.keys(jsir.services || []).map(msgStr).join('\n')
1056
1058
  });
1057
1059
  }
@@ -1187,11 +1189,13 @@ const keywordDef = {
1187
1189
  setting: {
1188
1190
  comment: 'Global settings',
1189
1191
  exeFn: async (args) => {
1190
- let uniqueName = _cmdMap[args[0]];
1191
- if (uniqueName) {
1192
- let configDir = `${getLibDataDir()}/config`;
1193
- mkdir(configDir)
1194
- await eia(getEditor(), [`${configDir}/${md5(uniqueName)}.json`])
1192
+ if (args[0]) {
1193
+ if (_cmdMap[args[0]]){
1194
+ let uniqueName = _cmdMap[args[0]];
1195
+ await eia(getEditor(), [`${getConfigDir()}/${md5(uniqueName)}.json`])
1196
+ } else {
1197
+ console.warn('invalid args')
1198
+ }
1195
1199
  } else {
1196
1200
  await eia(getEditor(), [`${getLibDataDir()}/config.json`])
1197
1201
  }
@@ -1428,7 +1432,7 @@ const keywordDef = {
1428
1432
  quit: {
1429
1433
  comment: 'Exit',
1430
1434
  exeFn: (args) => {
1431
- room.offRoom();
1435
+ Room.offRoom();
1432
1436
  delTips();
1433
1437
  console.log(infoStr("Bye!"));
1434
1438
  _noAppendNextLine = true;
@@ -1483,14 +1487,18 @@ const keywordDef = {
1483
1487
  suffix = 'error'
1484
1488
  }
1485
1489
  if (type.indexOf("t") !== -1) {
1486
- cmdStr = 'less -R +F'
1490
+ cmdStr = 'tail -n 50 -f'
1487
1491
  }
1488
1492
  path = path + '.' + suffix;
1489
1493
  if (!fs.existsSync(path)) {
1490
1494
  console.warn('log file not found')
1491
1495
  return;
1492
1496
  }
1493
- await eia(cmdStr, [`"${path}"`], true)
1497
+ if (isRunningInBackground()) {
1498
+ console.log(await e(`tail -n 500 "${path}"`))
1499
+ } else {
1500
+ await eia(cmdStr, [`"${path}"`], true)
1501
+ }
1494
1502
  },
1495
1503
  short: 'l'
1496
1504
  },
@@ -1509,7 +1517,7 @@ const keywordDef = {
1509
1517
  room: {
1510
1518
  comment: 'manage room ([node])',
1511
1519
  exeFn: async (args) => {
1512
- await room.syncSetting();
1520
+ await Room.syncSetting();
1513
1521
  if (args.length > 0) {
1514
1522
  let have = false;
1515
1523
  let tasks = []
@@ -1525,7 +1533,7 @@ const keywordDef = {
1525
1533
  tasks.push(async (input) => {
1526
1534
  let jsir = room.jsirs[pid];
1527
1535
  console.log(infoStr(`[${pid}]`))
1528
- let resp = await reqNode(room.selfNode, "post", "/enter", jsir.port, JSON.stringify({input}))
1536
+ let resp = await Room.reqNode(room.selfNode, "post", "/enter", jsir.port, JSON.stringify({input}))
1529
1537
  console.log(JSON.parse(resp).output)
1530
1538
  })
1531
1539
  }
@@ -2162,20 +2170,7 @@ async function _requireSource(currSpace, cmdMatchStr, force = false) {
2162
2170
  let serviceObj = {};
2163
2171
  for (let fn of setting.services[serviceKey]) {
2164
2172
  serviceObj[fn] = async (...args) => {
2165
- let key = `${serviceKey}/${fn}`;
2166
- let targets = setting.serviceFns[key] || []
2167
- targets = targets
2168
- .filter(i => i.active)
2169
- .filter(i => !room.isDisableConnect(i.node, i.port));
2170
- let target = null;
2171
- if (targets.length > 0) {
2172
- target = room.busyPick(targets);
2173
- } else {
2174
- throw `service function ${key} not found`;
2175
- }
2176
- let resp = await room.reqNode(target.node, 'post',
2177
- `/${pair[0]}/${encodeURIComponent(trimJsirFileName(pair[1]))}/${fn}`, target.port, JSON.stringify(args))
2178
- return JSON.parse(resp).result;
2173
+ return await Room.reqFn(`${serviceKey}/${fn}`, args);
2179
2174
  }
2180
2175
  }
2181
2176
  return serviceObj
@@ -2271,7 +2266,7 @@ process.on('beforeExit', function () {
2271
2266
  delTips();
2272
2267
  } else {
2273
2268
  nextLine();
2274
- room.onRoom(wrapperInput)
2269
+ Room.onRoom(wrapperInput)
2275
2270
  }
2276
2271
  });
2277
2272
 
package/deps/room.js CHANGED
@@ -1,20 +1,23 @@
1
1
  const os = require('os');
2
2
  const {fileJson, fileLock, vl, createConsole, getKeyTips, getValTips,
3
3
  getRoomsDir, e, regEach, isRunningInBackground,
4
- batchAsync, debugStr, trim, getOr, errorTag, warnStr, getConfig
4
+ batchAsync, debugStr, trim, getOr, errorTag, warnStr, getConfig, getConfigDir,
5
+ fileExist, formatUptime
5
6
  } = require('./util');
6
7
  const {setRoute, createSign} = require('../deps/server')
7
8
  const roomDataFile = "jsirRoom.json"
8
9
  const roomsDirLockKey = "RW_" + getRoomsDir();
9
10
  const syncRoomsLockKey = "SyncRooms_" + getRoomsDir();
11
+ const syncConfigsLockKey = "SyncConfigs_" + getConfigDir();
10
12
  const updateRoomInfoLockKey = "UPDATE_" + roomDataFile
11
13
  const console = createConsole();
12
- const net = require("net");
13
14
  const setting = require('../deps/setting')
14
15
  const fp = require('fs').promises
15
16
  const http = require('http');
16
- let tailscalePath = os.platform() === 'darwin' ?
17
+ const tailscalePath = os.platform() === 'darwin' ?
17
18
  '/Applications/Tailscale.app/Contents/MacOS/Tailscale':'tailscale';
19
+ const configMain = getConfig("configMain");
20
+ const packageJson = require("../package.json");
18
21
 
19
22
  function debug(...args) {
20
23
  if (global.$DEBUG) {
@@ -35,6 +38,19 @@ function isPidAlive(pid) {
35
38
  }
36
39
  }
37
40
 
41
+ async function localConfigs() {
42
+ let configDir = getConfigDir();
43
+ let configFiles = await fp.readdir(configDir)
44
+ let fns = configFiles
45
+ .map(i => () => fp.readFile(`${configDir}/${i}`));
46
+ let buffers = await batchAsync(fns, 33);
47
+ let configs = {}
48
+ for (let i = 0; i < configFiles.length; i++) {
49
+ configs[configFiles[i]] = String(buffers[i]);
50
+ }
51
+ return configs;
52
+ }
53
+
38
54
  function onRoom(wrapperInput) {
39
55
  if (!setting.roomTid[0]) {
40
56
  try {
@@ -42,12 +58,12 @@ function onRoom(wrapperInput) {
42
58
  setRoute("get", "/", (req, res) => setting.selfRoom)
43
59
  setRoute("post", "/enter", async (req, res) => {
44
60
  if (!getConfig("enableRemote", true)) {
45
- res.output = warnStr("Disable Remote!")
61
+ res.output = warnStr("Disable Remote")
46
62
  return;
47
63
  }
48
64
  if (vl(req.input)) {
49
65
  if (!isRunningInBackground() && ['.nsir', '.N'].indexOf(req.input.trim()) === -1) {
50
- res.output = warnStr("Disable Remote!")
66
+ res.output = warnStr("Disable Remote")
51
67
  return;
52
68
  }
53
69
  setting.enterOutputs = []
@@ -57,6 +73,11 @@ function onRoom(wrapperInput) {
57
73
  res.output = outputs.join('');
58
74
  }
59
75
  })
76
+ if (configMain) {
77
+ setRoute("post", "/" + setting.configMainFnKey, async (req, res) => {
78
+ res.result = await localConfigs();
79
+ })
80
+ }
60
81
  } catch (e) {
61
82
  debug("initRoute failed", e)
62
83
  }
@@ -252,17 +273,50 @@ async function initRoom() {
252
273
  if (roomUpdateTouchTime) {
253
274
  await fileLock(updateRoomInfoLockKey, updateRoomInfo, false)
254
275
  fileLock(syncRoomsLockKey, syncRooms, false);
276
+ if (!configMain && setting.serviceFns.hasOwnProperty(setting.configMainFnKey)) {
277
+ fileLock(syncConfigsLockKey, syncConfigs, false);
278
+ }
255
279
  }
256
280
 
257
281
  await syncSetting();
258
282
  }
259
283
 
284
+ async function syncConfigs() {
285
+ let configs;
286
+ try {
287
+ configs = await reqFn(setting.configMainFnKey);
288
+ } catch (e) {
289
+ debug("syncConfigs failed", e)
290
+ return;
291
+ }
292
+ let configDir = getConfigDir();
293
+ let fns = [];
294
+ for (let key of Object.keys(configs)) {
295
+ let path = configDir + "/" + key;
296
+ let text = configs[key];
297
+ fns.push(async () => {
298
+ if (await fileExist(path)) {
299
+ let currText = String(await fp.readFile(path));
300
+ if (currText !== text) {
301
+ await fp.writeFile(path, text)
302
+ debug(`sync ${key} success`);
303
+ }
304
+ } else {
305
+ await fp.writeFile(path, text)
306
+ debug(`sync ${key} success`);
307
+ }
308
+ });
309
+ }
310
+ await batchAsync(fns, 33)
311
+ debug('syncConfigs done')
312
+ }
313
+
260
314
  async function cleanRoom(room) {
261
315
  // 清理进程
262
316
  for (let pid of Object.keys(room.jsirs || {})) {
263
317
  let jsir = room.jsirs[pid]
264
318
  let active = Date.now() - (jsir.lastUpdateTime || 0) <= setting.roomHeartbeatExpire;
265
- if (!active && isPidAlive(pid)) {
319
+ if (jsir.back && !active && isPidAlive(pid)) {
266
320
  try {
267
321
  debug(`kill inactive jsir ${pid}`)
268
322
  process.kill(parseInt(pid), 'SIGKILL'); // 强制终止进程
@@ -297,7 +351,11 @@ async function initRoomJsir(room) {
297
351
  for (let key of Object.keys(setting.routes)) {
298
352
  let ss = key.split("/").map(trim).filter(i => i);
299
353
  if (ss.length > 3) {
354
+ // 脚本空间service
300
355
  getOr(services, ss[1] + '/' + ss[2], []).push(ss[3])
356
+ } else if (ss.length > 2) {
357
+ // jsir service
358
+ getOr(services, ss[1], []).push(ss[2])
301
359
  }
302
360
  }
303
361
  // 初始化jsir
@@ -314,7 +372,9 @@ async function initRoomJsir(room) {
314
372
  lastUpdateTime: Date.now(),
315
373
  port: setting.server ? setting.server.address().port:null,
316
374
  services: services,
317
- newError: global.$newError
375
+ newError: global.$newError,
376
+ version: packageJson.version,
377
+ upTime: formatUptime()
318
378
  }
319
379
  }
320
380
 
@@ -374,53 +434,57 @@ async function reqNode(node, method, url, port, body) {
374
434
  'sign': createSign()
375
435
  }
376
436
  };
377
- debug('reqRoom', JSON.stringify(opt))
378
- return await new Promise((resolve, reject) => {
379
- try {
380
- let timeoutMs = setting.reqNodeTimeouts[url] || setting.reqNodeDefaultTimeout;
381
- let timeout = null
382
-
383
- const req = http.request(opt, (res) => {
384
- let data = '';
385
- // 数据块接收
386
- res.on('data', (chunk) => {
387
- data += chunk;
437
+ let time = Date.now();
438
+ try {
439
+ return await new Promise((resolve, reject) => {
440
+ try {
441
+ let timeoutMs = setting.reqNodeTimeouts[url] || setting.reqNodeDefaultTimeout;
442
+ let timeout = null
443
+
444
+ const req = http.request(opt, (res) => {
445
+ let data = '';
446
+ // 数据块接收
447
+ res.on('data', (chunk) => {
448
+ data += chunk;
449
+ });
450
+ // 响应结束
451
+ res.on('end', () => {
452
+ clearTimeout(timeout)
453
+ // 检查响应状态码
454
+ if (res.statusCode !== 200) {
455
+ reject(errorTag(new Error(data), decodeURIComponent(url)));
456
+ return;
457
+ }
458
+ resolve(data)
459
+ });
388
460
  });
389
- // 响应结束
390
- res.on('end', () => {
391
- clearTimeout(timeout)
392
- // 检查响应状态码
393
- if (res.statusCode !== 200) {
394
- reject(errorTag(new Error(data), decodeURIComponent(url)));
395
- return;
461
+
462
+ // 设置超时逻辑
463
+ timeout = setTimeout(() => {
464
+ req.destroy(); // 中断请求
465
+ }, timeoutMs); // 10秒超时
466
+
467
+ // 错误处理
468
+ req.on('error', (e) => {
469
+ clearTimeout(timeout); // 清除超时定时器
470
+ if (room) {
471
+ setting.disableConnect[node + ":" + port] = Date.now() + setting.disableConnectLimit
396
472
  }
397
- resolve(data)
473
+ reject(e)
398
474
  });
399
- });
400
475
 
401
- // 设置超时逻辑
402
- timeout = setTimeout(() => {
403
- req.destroy(); // 中断请求
404
- }, timeoutMs); // 10秒超时
405
-
406
- // 错误处理
407
- req.on('error', (e) => {
408
- clearTimeout(timeout); // 清除超时定时器
409
- if (room) {
410
- setting.disableConnect[node + ":" + port] = Date.now() + setting.disableConnectLimit
476
+ if (body) {
477
+ req.write(body)
411
478
  }
479
+ // 结束请求
480
+ req.end();
481
+ } catch (e) {
412
482
  reject(e)
413
- });
414
-
415
- if (body) {
416
- req.write(body)
417
483
  }
418
- // 结束请求
419
- req.end();
420
- } catch (e) {
421
- reject(e)
422
- }
423
- })
484
+ });
485
+ } finally {
486
+ debug(`reqRoom cost ${Date.now() - time}ms`, JSON.stringify(opt), )
487
+ }
424
488
  }
425
489
 
426
490
  function busyPick(busyItems) {
@@ -441,11 +505,26 @@ function busyPick(busyItems) {
441
505
  }
442
506
  }
443
507
 
508
+ async function reqFn(fnKey, args = []) {
509
+ let targets = setting.serviceFns[fnKey] || []
510
+ targets = targets
511
+ .filter(i => i.active)
512
+ .filter(i => !isDisableConnect(i.node, i.port));
513
+ let target = null;
514
+ if (targets.length > 0) {
515
+ target = busyPick(targets);
516
+ } else {
517
+ throw `service function ${fnKey} not found`;
518
+ }
519
+ let resp = await reqNode(target.node, 'post',
520
+ `/${encodeURI(fnKey)}`, target.port, JSON.stringify(args || []))
521
+ return JSON.parse(resp).result;
522
+ }
523
+
444
524
  module.exports = {
445
525
  onRoom,
446
526
  offRoom,
447
527
  syncSetting,
448
528
  reqNode,
449
- isDisableConnect,
450
- busyPick
529
+ reqFn
451
530
  }
package/deps/server.js CHANGED
@@ -7,6 +7,7 @@ const preferredPort = setting.defaultPort;
7
7
  // 路由存储
8
8
  const routes = setting.routes;
9
9
  let invokeStart = false;
10
+ const apiReg = /[^a-zA-Z]api[^a-zA-Z]|[^a-zA-Z]api$/i;
10
11
 
11
12
  function debug(...args) {
12
13
  if (global.$DEBUG) {
@@ -36,7 +37,15 @@ function createSign() {
36
37
  async function createServer(port) {
37
38
  const server = http.createServer(async (req, res) => {
38
39
  const method = req.method.toLowerCase();
39
- const url = decodeURIComponent(req.url);
40
+ const originUrl = decodeURI(req.url);
41
+
42
+ let url = originUrl;
43
+ let params = '[]'
44
+ let sepIndex = originUrl.indexOf("?");
45
+ if (sepIndex !== -1) {
46
+ url = originUrl.substring(0, sepIndex).trim()
47
+ params = JSON.stringify([...new URLSearchParams(originUrl.substring(sepIndex).trim()).values()]);
48
+ }
40
49
 
41
50
  // 获取客户端的 IP 地址
42
51
  const clientIp = req.socket.remoteAddress || req.connection.remoteAddress;
@@ -45,22 +54,25 @@ async function createServer(port) {
45
54
  debug(`Received ${routeKey} from ${clientIp}`);
46
55
 
47
56
  if (routes[routeKey]) {
48
- // 取header里的sign字段
49
- const sign = req.headers['sign'];
50
- if (sign) {
51
- const decodedTime = verifySign(sign);
52
- const currentTime = Date.now();
57
+ if (!apiReg.test(routeKey)) {
58
+ // 如果不是api接口,则校验签名
59
+ // 取header里的sign字段
60
+ const sign = req.headers['sign'];
61
+ if (sign) {
62
+ const decodedTime = verifySign(sign);
63
+ const currentTime = Date.now();
53
64
 
54
- if (decodedTime === null || Math.abs(currentTime - decodedTime) > setting.serverSignExpire) {
65
+ if (decodedTime === null || Math.abs(currentTime - decodedTime) > setting.serverSignExpire) {
66
+ res.writeHead(403, { 'Content-Type': 'text/plain' });
67
+ res.end('Forbidden: Invalid Sign');
68
+ return;
69
+ }
70
+ } else {
71
+ // 如果没有sign字段,返回403
55
72
  res.writeHead(403, { 'Content-Type': 'text/plain' });
56
- res.end('Forbidden: Invalid Sign');
73
+ res.end('Forbidden: Missing Sign');
57
74
  return;
58
75
  }
59
- } else {
60
- // 如果没有sign字段,返回403
61
- res.writeHead(403, { 'Content-Type': 'text/plain' });
62
- res.end('Forbidden: Missing Sign');
63
- return;
64
76
  }
65
77
  // 如果签名通过,继续处理请求
66
78
  try {
@@ -77,7 +89,7 @@ async function createServer(port) {
77
89
  resolve()
78
90
  })
79
91
  })
80
- await routes[routeKey](body, res);
92
+ await routes[routeKey](body || params, res);
81
93
  } catch (e) {
82
94
  res.writeHead(500, { 'Content-Type': 'text/plain' });
83
95
  res.end(String(isError(e) ? e.message:e));
package/deps/setting.js CHANGED
@@ -39,5 +39,5 @@ module.exports = {
39
39
  roomTimer: 1000,
40
40
  serverSignExpire: 6000,
41
41
  roomHeartbeatExpire: 9000,
42
- consoleMap: {}
42
+ configMainFnKey: "config/main"
43
43
  }
package/deps/util.js CHANGED
@@ -256,7 +256,7 @@ function createConsole(uniqueName) {
256
256
  if ('debug' === key && !global.$DEBUG) {
257
257
  return;
258
258
  }
259
- if (uniqueName && (quite || isRunningInBackground())) {
259
+ if (uniqueName && quite) {
260
260
  if ((key === 'table' || key === 'nable') && typeof args[0] === "object") {
261
261
  args = ['', ...args]
262
262
  }
@@ -272,9 +272,6 @@ function createConsole(uniqueName) {
272
272
  }
273
273
  }
274
274
  }
275
- if (uniqueName) {
276
- setting.consoleMap[uniqueName] = result;
277
- }
278
275
  return result;
279
276
  }
280
277
 
@@ -1017,9 +1014,6 @@ function _getConfig(key, defaultVal, uniqueName) {
1017
1014
  if (key === undefined) {
1018
1015
  return config
1019
1016
  }
1020
- if (uniqueName && setting.consoleMap[uniqueName]) {
1021
- setting.consoleMap[uniqueName].msg(`require ${uniqueName} config "${key}"`)
1022
- }
1023
1017
  let writeFlag = false
1024
1018
  if (!(key in config)) {
1025
1019
  writeFlag = true
@@ -1175,14 +1169,7 @@ async function _fileLock(key, fn, expireMs = 49000) {
1175
1169
  const expireAt = expireMs > 0 ? now + expireMs : 0;
1176
1170
 
1177
1171
  // 1. 尝试判断锁目录是否已存在
1178
- let lockExists = false;
1179
- try {
1180
- await fp.access(lockKeyDir); // 若存在则不抛错
1181
- lockExists = true;
1182
- } catch (_) {
1183
- // 不存在时会抛ENOENT错误,说明还没有锁
1184
- lockExists = false;
1185
- }
1172
+ let lockExists = await fileExist(lockKeyDir);
1186
1173
 
1187
1174
  if (lockExists) {
1188
1175
  // 目录存在时,读取文件名中的过期时间戳
@@ -1569,6 +1556,9 @@ async function eia(cmd, args = [], shell = false) {
1569
1556
  `
1570
1557
  当前进程不会卡住,输入输出由cmd进程持有
1571
1558
  `
1559
+ if (isRunningInBackground()) {
1560
+ throw 'Unsupported Operation';
1561
+ }
1572
1562
  setting.enableNextLine = false;
1573
1563
  let child = spawn(cmd, args, {stdio:"inherit", shell});
1574
1564
  return new Promise((resolve, reject) => {
@@ -1895,7 +1885,7 @@ function md5(message) {
1895
1885
  return crypto.createHash('md5').update(message).digest('hex');
1896
1886
  }
1897
1887
 
1898
- async function batchAsync(fns = [], asyncNum = 1, limitMs = 49000) {
1888
+ async function batchAsync(fns = [], asyncNum = 9, limitMs = 0) {
1899
1889
  if (fns.length <= 0 || asyncNum < 1) {
1900
1890
  return []
1901
1891
  }
@@ -1905,7 +1895,12 @@ async function batchAsync(fns = [], asyncNum = 1, limitMs = 49000) {
1905
1895
  for(let i = 0; i< fns.length; i++) {
1906
1896
  let pro = new Promise(async (resolve, reject) => {
1907
1897
  try {
1908
- let resp = await timeLimit([fns[i]()], limitMs);
1898
+ let resp;
1899
+ if (limitMs) {
1900
+ resp = await timeLimit([fns[i]()], limitMs);
1901
+ } else {
1902
+ resp = [await fns[i]()];
1903
+ }
1909
1904
  doneMap[i] = pro;
1910
1905
  resolve(resp[0])
1911
1906
  } catch (e) {
@@ -2053,7 +2048,7 @@ function getValTips() {
2053
2048
  }
2054
2049
  return item;
2055
2050
  }).join("|");
2056
- items.push(val)
2051
+ items.push(val || trim(key))
2057
2052
  }
2058
2053
  return items
2059
2054
  }
@@ -2082,6 +2077,8 @@ function interceptStdStreams() {
2082
2077
  process.stdout.write = (chunk, ...args) => {
2083
2078
  if(setting.enterOutputs) {
2084
2079
  setting.enterOutputs.push(chunk.toString());
2080
+ } else if (isRunningInBackground()) {
2081
+ console.$log(process.pid, "stdout", "\n" + chunk.toString().trimEnd());
2085
2082
  } else {
2086
2083
  originalStdoutWrite(chunk, ...args); // 保留原始行为
2087
2084
  }
@@ -2091,13 +2088,35 @@ function interceptStdStreams() {
2091
2088
  process.stderr.write = (chunk, ...args) => {
2092
2089
  if(setting.enterOutputs) {
2093
2090
  setting.enterOutputs.push(chunk.toString());
2091
+ } else if (isRunningInBackground()) {
2092
+ console.$error(process.pid, errorStr("stderr"), "\n" + chunk.toString().trimEnd());
2094
2093
  } else {
2095
2094
  originalStderrWrite(chunk, ...args); // 保留原始行为
2096
2095
  }
2097
2096
  };
2098
2097
  }
2099
2098
 
2099
+ function formatUptime() {
2100
+ const uptimeSeconds = process.uptime();
2101
+
2102
+ const days = Math.floor(uptimeSeconds / (3600 * 24)); // 计算天数
2103
+ const hours = Math.floor((uptimeSeconds % (3600 * 24)) / 3600); // 计算小时
2104
+ const minutes = Math.floor((uptimeSeconds % 3600) / 60); // 计算分钟
2105
+ const seconds = Math.floor(uptimeSeconds % 60); // 计算秒数
2106
+
2107
+ if (days > 0) {
2108
+ return `${days}d${hours}h`;
2109
+ } else if (hours > 0) {
2110
+ return `${hours}h${minutes}m`;
2111
+ } else if (minutes > 0) {
2112
+ return `${minutes}m${seconds}s`;
2113
+ } else {
2114
+ return `${seconds}s`;
2115
+ }
2116
+ }
2117
+
2100
2118
  module.exports = {
2119
+ formatUptime,
2101
2120
  wrapperJsirText,
2102
2121
  run,
2103
2122
  reget,
@@ -2206,5 +2225,6 @@ module.exports = {
2206
2225
  isRunningInBackground,
2207
2226
  createDetachedProcess,
2208
2227
  interceptStdStreams,
2209
- draftModify
2228
+ draftModify,
2229
+ fileExist
2210
2230
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsir",
3
- "version": "2.4.5",
3
+ "version": "2.4.8",
4
4
  "description": "JavaScript Script Management Tool",
5
5
  "main": "index.js",
6
6
  "scripts": {