jsir 2.4.0 → 2.4.1

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
@@ -11,7 +11,8 @@ const {
11
11
  createConsole, setTips, delTips,
12
12
  getEditor, errorStr, getConfigDir,
13
13
  getFullPath, parseUniqueName, toUniqueName, isJsirFileName, toJsirFileName,
14
- getAlias, wrapperJsirText, eia, getKeyTips, getValTips, getJsirTypeKey
14
+ getAlias, wrapperJsirText, eia, getKeyTips, getValTips, getJsirTypeKey,
15
+ createDetachedProcess, interceptStdStreams
15
16
  } = $lib;
16
17
  const _args = process.argv.slice(2).map(trim);
17
18
  const evalCode = require('../deps/evalCode')
@@ -29,6 +30,8 @@ const _fileWatcherMap = {}
29
30
  const _types = setting.fileType
30
31
  const console = createConsole();
31
32
  const room = require('../deps/room');
33
+ const server = require('../deps/server')
34
+ const {reqNode} = require("../deps/room");
32
35
 
33
36
  let lastFilterArg = '';
34
37
  let _cmdMapFile = setting.name + 'CmdMap.json'
@@ -157,6 +160,7 @@ function checkWorkspaces() {
157
160
  }
158
161
 
159
162
  async function start() {
163
+ interceptStdStreams()
160
164
  checkWorkspaces()
161
165
  initWorkspace()
162
166
 
@@ -391,7 +395,7 @@ function _nextLine(callback, promptStr, hidden, resolve, end, isText) {
391
395
  _haveWrapperInput = true;
392
396
  closeRl()
393
397
  inputStr = inputStr.replace(/\s+$/, '');
394
- if (!isText && /^\d+$/.test(inputStr) && /^\s+/.test(textLine)) {
398
+ if (!isText && /^\d+$/.test(inputStr) && /^\s+/.test(textLine) && (!callback || callback === wrapperInput)) {
395
399
  inputStr = '.e ' + inputStr;
396
400
  }
397
401
  let pro = (callback || wrapperInput)(inputStr);
@@ -580,11 +584,11 @@ function wrapperAlias(str) {
580
584
  async function _wrapperInput(str) {
581
585
  _haveWrapperInput = true;
582
586
  global.$newInput = true;
583
-
584
587
  str = trim(str)
585
588
  if (!str) {
586
589
  return;
587
590
  }
591
+ global.$newError = false;
588
592
  str = wrapperAlias(str);
589
593
 
590
594
  if (/^[`'"]/.test(str)) {
@@ -624,10 +628,16 @@ async function _wrapperInput(str) {
624
628
  let firstName = getJsirTypeKey(fileName)
625
629
  if (firstName === setting.fileKey) {
626
630
  await workFile(uniqueName)
627
- } else if (firstName !== setting.exeKey) {
628
- console.log(String(fs.readFileSync(path)));
629
- } else {
631
+ } else if (firstName === setting.exeKey) {
630
632
  await runCmd(str)
633
+ } else if (firstName === setting.initKey) {
634
+ if (strs[1] === '-') {
635
+ await offServer(uniqueName)
636
+ } else {
637
+ await joinServer(uniqueName)
638
+ }
639
+ } else {
640
+ console.log(String(fs.readFileSync(path)));
631
641
  }
632
642
  } else {
633
643
  await runCmd(str)
@@ -635,6 +645,44 @@ async function _wrapperInput(str) {
635
645
  }
636
646
  }
637
647
 
648
+ async function joinServer(uniqueName) {
649
+ let exportLib = await _requireSource(setting.defaultSpace, uniqueName, true);
650
+ let asyncFnSize = 0
651
+ for (let key of Object.keys(exportLib)) {
652
+ let val = exportLib[key];
653
+ if (getType(val) !== 'AsyncFunction') {
654
+ throw `service function must be async`;
655
+ }
656
+ asyncFnSize ++
657
+ }
658
+ if (asyncFnSize <= 0) {
659
+ console.warn('no items');
660
+ return;
661
+ }
662
+ let pair = parseUniqueName(uniqueName)
663
+ for (let key of Object.keys(exportLib)) {
664
+ let val = exportLib[key];
665
+ server.setRoute('post', `/${pair[0]}/${trimJsirFileName(pair[1])}/${key}`,async (req, res) => {
666
+ res.result = await val(...req);
667
+ }, server.jsonHandle)
668
+ }
669
+ console.msg(uniqueName, 'has become a service.');
670
+ }
671
+
672
+ async function offServer(uniqueName) {
673
+ let pair = parseUniqueName(uniqueName)
674
+ let service = `${pair[0]}/${trimJsirFileName(pair[1])}`;
675
+
676
+ for (let key of Object.keys(setting.routes)) {
677
+ let ss = key.split("/").map(trim).filter(i => i);
678
+ let keyService = ss[1] + '/' + ss[2]
679
+ if (keyService === service) {
680
+ delete setting.routes[key]
681
+ }
682
+ }
683
+ console.msg(uniqueName, 'service has been offline.');
684
+ }
685
+
638
686
  async function dealStartGlobalMode(rows, items, text) {
639
687
  console.info('global search mode');
640
688
  rows.push(...await dealStarCase(await evalText('return $context'), items, '$context'))
@@ -978,18 +1026,20 @@ async function showRooms() {
978
1026
  })
979
1027
  for (let key of Object.keys(room.jsirs)) {
980
1028
  let jsir = room.jsirs[key]
1029
+ let pidStr = (process.pid === jsir.pid ? "*" : " ") + jsir.pid;
981
1030
  results.push({
982
1031
  mid: i,
983
1032
  name: (isLocal ? "*" : " ") + room.name,
984
1033
  node: room.selfNode,
985
1034
  active: room.active,
986
- pid: (process.pid === jsir.pid ? "*" : " ") + jsir.pid,
1035
+ pid: jsir.newError ? errorStr(pidStr):pidStr,
987
1036
  space: jsir.space,
988
1037
  running: jsir.active,
989
1038
  port: jsir.port,
990
1039
  back: jsir.back,
991
1040
  busy: jsir.busy,
992
- tips: Object.keys(jsir.tips).map(i => i + ': ' + jsir.tips[i]).join('\n')
1041
+ tips: Object.keys(jsir.tips).map(i => i + ': ' + jsir.tips[i]).join('\n'),
1042
+ services: Object.keys(jsir.services || []).map(msgStr).join('\n')
993
1043
  });
994
1044
  }
995
1045
  }
@@ -1443,17 +1493,48 @@ const keywordDef = {
1443
1493
  comment: 'manage room ([node])',
1444
1494
  exeFn: async (args) => {
1445
1495
  await room.syncSetting();
1446
- if (args[0]) {
1447
- if (setting.selfRoom.nodes.indexOf(args[0]) !== -1) {
1448
- await eia("ssh", [args[0]])
1449
- } else {
1450
- console.warn("invalid node")
1496
+ if (args.length > 0) {
1497
+ let have = false;
1498
+ let tasks = []
1499
+ for (let room of setting.rooms) {
1500
+ for (let pid of Object.keys(room.jsirs || {})) {
1501
+ if (args.indexOf(room.selfNode) === -1 && args.indexOf(pid) === -1) {
1502
+ continue;
1503
+ }
1504
+ if (setting.selfRoom.selfNode === room.selfNode && String(process.pid) === pid) {
1505
+ continue;
1506
+ }
1507
+ have = true;
1508
+ tasks.push(async (input) => {
1509
+ let jsir = room.jsirs[pid];
1510
+ console.log(infoStr(`[${pid}]`))
1511
+ let resp = await reqNode(room.selfNode, "post", "/enter", jsir.port, JSON.stringify({input}))
1512
+ console.log(JSON.parse(resp).output)
1513
+ })
1514
+ }
1515
+ }
1516
+ let input = have ? await nextLine(i => i):''
1517
+ if (vl(input)) {
1518
+ for (let task of tasks) {
1519
+ try {
1520
+ await task(input)
1521
+ } catch (e) {
1522
+ console.error(e)
1523
+ }
1524
+ }
1451
1525
  }
1452
1526
  } else {
1453
1527
  await showRooms();
1454
1528
  }
1455
1529
  },
1456
1530
  short: 'm'
1531
+ },
1532
+ nsir: {
1533
+ comment: 'new jsir',
1534
+ exeFn: async (args) => {
1535
+ await createDetachedProcess('jsir', ['.p'])
1536
+ },
1537
+ short: 'N'
1457
1538
  }
1458
1539
  }
1459
1540
 
@@ -2050,7 +2131,7 @@ function filterRequire(currSpace, cmdMatchStr) {
2050
2131
  return cmds[0];
2051
2132
  }
2052
2133
 
2053
- async function _requireSource(currSpace, cmdMatchStr) {
2134
+ async function _requireSource(currSpace, cmdMatchStr, force = false) {
2054
2135
  let result
2055
2136
  let uniqueName = filterRequire(currSpace, cmdMatchStr);
2056
2137
  let path = getFullPath(uniqueName)
@@ -2058,7 +2139,32 @@ async function _requireSource(currSpace, cmdMatchStr) {
2058
2139
  let pair = parseUniqueName(uniqueName)
2059
2140
  let typeKey = getJsirTypeKey(pair[1]);
2060
2141
  if (typeKey === setting.initKey) {
2061
- result = await evalText(text, uniqueName)
2142
+ let pair = parseUniqueName(uniqueName)
2143
+ let serviceKey = `${pair[0]}/${trimJsirFileName(pair[1])}`
2144
+ if (setting.defaultSpace !== 'local' && !force && setting.services[serviceKey]) {
2145
+ let serviceObj = {};
2146
+ for (let fn of setting.services[serviceKey]) {
2147
+ serviceObj[fn] = async (...args) => {
2148
+ let key = `${serviceKey}/${fn}`;
2149
+ let targets = setting.serviceFns[key] || []
2150
+ targets = targets
2151
+ .filter(i => i.active)
2152
+ .filter(i => !room.isDisableConnect(i.node, i.port));
2153
+ let target = null;
2154
+ if (targets.length > 0) {
2155
+ target = room.busyPick(targets);
2156
+ } else {
2157
+ throw `service function ${key} not found`;
2158
+ }
2159
+ let resp = await room.reqNode(target.node, 'post',
2160
+ `/${pair[0]}/${encodeURIComponent(trimJsirFileName(pair[1]))}/${fn}`, target.port, JSON.stringify(args))
2161
+ return JSON.parse(resp).result;
2162
+ }
2163
+ }
2164
+ return serviceObj
2165
+ } else {
2166
+ result = await evalText(text, uniqueName)
2167
+ }
2062
2168
  } else if (typeKey === setting.exeKey) {
2063
2169
  result = async (argsStr) => {
2064
2170
  return await runCmd(trim(argsStr), uniqueName, text)
@@ -2148,7 +2254,7 @@ process.on('beforeExit', function () {
2148
2254
  delTips();
2149
2255
  } else {
2150
2256
  nextLine();
2151
- room.onRoom()
2257
+ room.onRoom(wrapperInput)
2152
2258
  }
2153
2259
  });
2154
2260
 
package/deps/evalCode.js CHANGED
@@ -37,10 +37,6 @@ module.exports = async ($text = '', $cmdName = '', $args = [],
37
37
  $homeDir, $lib, $cmdMap
38
38
  }
39
39
  let console = $lib.createConsole($cmdName);
40
- try {
41
- return await eval(`(async ()=>{${$text};
42
- })()`)
43
- } catch (e) {
44
- console.error(e)
45
- }
40
+ return await eval(`(async ()=>{${$text};
41
+ })()`)
46
42
  }
package/deps/room.js CHANGED
@@ -1,7 +1,7 @@
1
1
  const os = require('os');
2
2
  const {fileJson, fileLock, vl, createConsole, getKeyTips, getValTips,
3
3
  getRoomsDir, e, regEach, isRunningInBackground,
4
- batchAsync, debugStr
4
+ batchAsync, debugStr, trim, getOr, errorTag, warnStr, getConfig
5
5
  } = require('./util');
6
6
  const {setRoute, createSign} = require('../deps/server')
7
7
  const roomDataFile = "jsirRoom.json"
@@ -35,11 +35,28 @@ function isPidAlive(pid) {
35
35
  }
36
36
  }
37
37
 
38
- function onRoom() {
38
+ function onRoom(wrapperInput) {
39
39
  if (!setting.roomTid[0]) {
40
40
  try {
41
41
  setRoute("post", "/", (req, res) => setting.selfRoom)
42
42
  setRoute("get", "/", (req, res) => setting.selfRoom)
43
+ setRoute("post", "/enter", async (req, res) => {
44
+ if (!getConfig("enableRemote", true)) {
45
+ res.output = warnStr("Disable Remote!")
46
+ return;
47
+ }
48
+ if (vl(req.input)) {
49
+ if (!isRunningInBackground() && ['.nsir', '.N'].indexOf(req.input.trim()) === -1) {
50
+ res.output = warnStr("Disable Remote!")
51
+ return;
52
+ }
53
+ setting.enterOutputs = []
54
+ await wrapperInput(req.input)
55
+ let outputs = setting.enterOutputs || [];
56
+ setting.enterOutputs = null;
57
+ res.output = outputs.join('\n');
58
+ }
59
+ })
43
60
  } catch (e) {
44
61
  debug("initRoute failed", e)
45
62
  }
@@ -128,7 +145,7 @@ async function syncRooms() {
128
145
  }
129
146
  })
130
147
  }
131
- await batchAsync(fns, 9, 3000);
148
+ await batchAsync(fns, 33);
132
149
  await fileLock(roomsDirLockKey, async () => {
133
150
  for (let syncRoom of [...syncRooms]) {
134
151
  if (syncRoom.selfNode) {
@@ -186,6 +203,30 @@ async function _syncSetting(room) {
186
203
  jsir.active = Date.now() - (jsir.lastUpdateTime || 0) <= 9000)
187
204
  return room;
188
205
  });
206
+
207
+ setting.services = {}
208
+ setting.serviceFns = {}
209
+ for (const room of setting.rooms) {
210
+ for (const pid of Object.keys(room.jsirs || [])) {
211
+ let jsir = room.jsirs[pid];
212
+ for (const service of Object.keys(jsir.services || [])) {
213
+ let serviceFns = jsir.services[service] || [];
214
+
215
+ let existFns = setting.services[service] || [];
216
+ existFns.push(...serviceFns)
217
+ setting.services[service] = [...new Set(existFns)]
218
+
219
+ for (const fn of serviceFns) {
220
+ getOr(setting.serviceFns, service + '/' + fn, []).push({
221
+ node: room.selfNode,
222
+ port: jsir.port,
223
+ busy: jsir.busy,
224
+ active: jsir.active
225
+ });
226
+ }
227
+ }
228
+ }
229
+ }
189
230
  }
190
231
 
191
232
  async function initRoom() {
@@ -205,6 +246,10 @@ async function initRoom() {
205
246
  await syncSetting();
206
247
  }
207
248
 
249
+ /*
250
+ 最小为0
251
+ 100 为1 毫秒
252
+ */
208
253
  async function getEventLoopDelay() {
209
254
  const start = process.hrtime.bigint();
210
255
  const delay = 1; // 用于测量的短暂延迟(单位:毫秒)
@@ -219,6 +264,15 @@ async function initRoomJsir(room) {
219
264
  room.jsirs = Object.fromEntries(
220
265
  Object.entries(room.jsirs || {}).filter(([pid]) => isPidAlive(pid))
221
266
  );
267
+
268
+ let services = {}
269
+ for (let key of Object.keys(setting.routes)) {
270
+ let ss = key.split("/").map(trim).filter(i => i);
271
+ if (ss.length > 3) {
272
+ getOr(services, ss[1] + '/' + ss[2], []).push(ss[3])
273
+ }
274
+ }
275
+ let lastJsir = room.jsirs[process.pid]
222
276
  room.jsirs[process.pid] = {
223
277
  pid: process.pid,
224
278
  space: setting.defaultSpace,
@@ -226,10 +280,12 @@ async function initRoomJsir(room) {
226
280
  obj[key] = getValTips()[index];
227
281
  return obj;
228
282
  }, {}),
229
- busy: await getEventLoopDelay(),
283
+ busy: await getEventLoopDelay() || (lastJsir ? lastJsir.busy:0),
230
284
  back: isRunningInBackground(),
231
285
  lastUpdateTime: Date.now(),
232
- port: setting.server ? setting.server.address().port:null
286
+ port: setting.server ? setting.server.address().port:null,
287
+ services: services,
288
+ newError: global.$newError
233
289
  }
234
290
  }
235
291
 
@@ -263,62 +319,21 @@ function getLocalIPs() {
263
319
  return result;
264
320
  }
265
321
 
266
- async function sshEnables(ips) {
267
- // 检查开放 22 端口的 IP
268
- const sshEnabledIPs = [];
269
- const portCheckPromises = ips.map((ip) =>
270
- checkPortOpen(ip, 22).then((isOpen) => {
271
- if (isOpen) {
272
- sshEnabledIPs.push(ip);
273
- }
274
- })
275
- );
276
-
277
- await Promise.all(portCheckPromises);
278
- return sshEnabledIPs;
279
- }
280
-
281
- /**
282
- * 检查指定 IP 的指定端口是否开放
283
- * @param {string} ip - 目标 IP 地址
284
- * @param {number} port - 目标端口
285
- * @returns {Promise<boolean>} - 是否开放
286
- */
287
- function checkPortOpen(ip, port) {
288
- return new Promise((resolve) => {
289
- const socket = new net.Socket();
290
- socket.setTimeout(1000); // 设置超时时间
291
-
292
- socket
293
- .connect(port, ip, () => {
294
- socket.destroy(); // 成功连接后关闭 socket
295
- resolve(true);
296
- })
297
- .on("error", () => {
298
- socket.destroy(); // 遇到错误时关闭 socket
299
- resolve(false);
300
- })
301
- .on("timeout", () => {
302
- socket.destroy(); // 超时时关闭 socket
303
- resolve(false);
304
- });
305
- });
306
- }
307
-
308
322
  function isDisableConnect(node, port) {
309
323
  let disableTime = setting.disableConnect[node + ":" + port] || 0;
310
324
  return disableTime > Date.now()
311
325
  }
312
326
 
313
- async function reqNode(node, method, url) {
314
- let port = 52108;
327
+ async function reqNode(node, method, url, port, body) {
315
328
  let room = setting.rooms.filter(i => i.selfNode === node)[0]
316
- if (room) {
317
- let activeJsir = Object.values(room.jsirs)
318
- .filter(i => !isDisableConnect(node, i.port))
319
- .sort((a,b) => a.busy - b.busy);
320
- if (activeJsir.length > 0) {
321
- port = activeJsir[0].port;
329
+ if (!port) {
330
+ port = 52108;
331
+ if (room) {
332
+ let activeJsir = Object.values(room.jsirs)
333
+ .filter(i => !isDisableConnect(node, i.port));
334
+ if (activeJsir.length > 0) {
335
+ port = busyPick(activeJsir).port;
336
+ }
322
337
  }
323
338
  }
324
339
  let opt = {
@@ -332,33 +347,76 @@ async function reqNode(node, method, url) {
332
347
  };
333
348
  debug('reqRoom', JSON.stringify(opt))
334
349
  return await new Promise((resolve, reject) => {
335
- const req = http.request(opt, (res) => {
336
- let data = '';
337
- // 数据块接收
338
- res.on('data', (chunk) => {
339
- data += chunk;
350
+ try {
351
+ let timeoutMs = url === '/' ? 3000:49000;
352
+ let timeout = null
353
+
354
+ const req = http.request(opt, (res) => {
355
+ let data = '';
356
+ // 数据块接收
357
+ res.on('data', (chunk) => {
358
+ data += chunk;
359
+ });
360
+ // 响应结束
361
+ res.on('end', () => {
362
+ clearTimeout(timeout)
363
+ // 检查响应状态码
364
+ if (res.statusCode !== 200) {
365
+ reject(errorTag(new Error(data), decodeURIComponent(url)));
366
+ return;
367
+ }
368
+ resolve(data)
369
+ });
340
370
  });
341
- // 响应结束
342
- res.on('end', () => {
343
- resolve(data)
371
+
372
+ // 设置超时逻辑
373
+ timeout = setTimeout(() => {
374
+ req.destroy(); // 中断请求
375
+ }, timeoutMs); // 10秒超时
376
+
377
+ // 错误处理
378
+ req.on('error', (e) => {
379
+ clearTimeout(timeout); // 清除超时定时器
380
+ if (room) {
381
+ setting.disableConnect[node + ":" + port] = Date.now() + 6000
382
+ }
383
+ reject(e)
344
384
  });
345
- });
346
385
 
347
- // 错误处理
348
- req.on('error', (e) => {
349
- if (room) {
350
- setting.disableConnect[node + ":" + port] = Date.now() + 9000
386
+ if (body) {
387
+ req.write(body)
351
388
  }
389
+ // 结束请求
390
+ req.end();
391
+ } catch (e) {
352
392
  reject(e)
353
- });
354
-
355
- // 结束请求
356
- req.end();
393
+ }
357
394
  })
358
395
  }
359
396
 
397
+ function busyPick(busyItems) {
398
+ if (busyItems.length === 1) {
399
+ return busyItems[0]
400
+ }
401
+ // 反转权重:计算 1 / busy 值作为权重
402
+ const weights = busyItems.map(item => 1 / (item.busy || 1));
403
+ const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
404
+ const random = Math.random() * totalWeight;
405
+
406
+ let cumulativeWeight = 0;
407
+ for (let i = 0; i < busyItems.length; i++) {
408
+ cumulativeWeight += weights[i];
409
+ if (random < cumulativeWeight) {
410
+ return busyItems[i]; // 选中反转权重命中的对象
411
+ }
412
+ }
413
+ }
414
+
360
415
  module.exports = {
361
416
  onRoom,
362
417
  offRoom,
363
- syncSetting
418
+ syncSetting,
419
+ reqNode,
420
+ isDisableConnect,
421
+ busyPick
364
422
  }
package/deps/server.js CHANGED
@@ -1,11 +1,11 @@
1
1
  const http = require('http');
2
- const {createConsole, vl, aesDecipher, md5, aesCipher, debugStr} = require('./util');
2
+ const {createConsole, vl, aesDecipher, md5, aesCipher, debugStr, isError} = require('./util');
3
3
  const console = createConsole();
4
4
  const setting = require('../deps/setting')
5
5
  // 尝试监听的端口
6
6
  const preferredPort = 52108;
7
7
  // 路由存储
8
- const routes = {};
8
+ const routes = setting.routes;
9
9
  let invokeStart = false;
10
10
 
11
11
  function debug(...args) {
@@ -36,10 +36,14 @@ function createSign() {
36
36
  async function createServer(port) {
37
37
  const server = http.createServer(async (req, res) => {
38
38
  const method = req.method.toLowerCase();
39
- const url = req.url;
39
+ const url = decodeURIComponent(req.url);
40
40
 
41
+ // 获取客户端的 IP 地址
42
+ const clientIp = req.socket.remoteAddress || req.connection.remoteAddress;
41
43
  // 查找对应的路由处理函数
42
44
  const routeKey = `${method} ${url}`;
45
+ debug(`Received ${routeKey} from ${clientIp}`);
46
+
43
47
  if (routes[routeKey]) {
44
48
  // 取header里的sign字段
45
49
  const sign = req.headers['sign'];
@@ -47,24 +51,40 @@ async function createServer(port) {
47
51
  const decodedTime = verifySign(sign);
48
52
  const currentTime = Date.now();
49
53
 
50
- if (decodedTime === null || Math.abs(currentTime - decodedTime) > 3000) {
51
- // 解码失败或者时间差大于9秒,返回403
54
+ if (decodedTime === null || Math.abs(currentTime - decodedTime) > 6000) {
52
55
  res.writeHead(403, { 'Content-Type': 'text/plain' });
53
- res.end('Forbidden: Invalid Sign\n');
56
+ res.end('Forbidden: Invalid Sign');
54
57
  return;
55
58
  }
56
59
  } else {
57
60
  // 如果没有sign字段,返回403
58
61
  res.writeHead(403, { 'Content-Type': 'text/plain' });
59
- res.end('Forbidden: Missing Sign\n');
62
+ res.end('Forbidden: Missing Sign');
60
63
  return;
61
64
  }
62
-
63
65
  // 如果签名通过,继续处理请求
64
- await routes[routeKey](req, res);
66
+ try {
67
+ let body = '';
68
+ await new Promise((resolve, reject) => {
69
+ // 监听请求数据
70
+ req.on('data', chunk => {
71
+ body += chunk;
72
+ });
73
+ req.on('error', (err) => {
74
+ reject(err)
75
+ });
76
+ req.on('end', async () => {
77
+ resolve()
78
+ })
79
+ })
80
+ await routes[routeKey](body, res);
81
+ } catch (e) {
82
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
83
+ res.end(String(isError(e) ? e.message:e));
84
+ }
65
85
  } else {
66
86
  res.writeHead(404, { 'Content-Type': 'text/plain' });
67
- res.end('Not Found\n');
87
+ res.end('Not Found');
68
88
  }
69
89
  });
70
90
 
@@ -95,6 +115,12 @@ async function startServer() {
95
115
  }
96
116
 
97
117
  // 设置路由的方法
118
+ /*
119
+ exam:
120
+ method, url, fn, handler(req, res, fn, err)
121
+ method, url, fn(req, res, err), null
122
+ err: {code, msg}
123
+ */
98
124
  function setRoute(method, url, fn, handler = jsonHandle) {
99
125
  startServer();
100
126
 
@@ -106,51 +132,29 @@ function setRoute(method, url, fn, handler = jsonHandle) {
106
132
  } else {
107
133
  routes[routeKey] = fn;
108
134
  }
109
- debug(`AddRoute: ${method.toUpperCase()} ${url} ${handler ? handler.constructor.name:''}`);
135
+ debug(`setRoute: ${method.toUpperCase()} ${url} ${handler ? handler.name:''}`);
110
136
  return setting.server;
111
137
  }
112
138
 
113
- function delRoute(method, url) {
114
- const routeKey = `${method.toLowerCase()} ${url}`;
115
- delete routes[routeKey];
116
- debug(`DelRoute: ${method.toUpperCase()} ${url}`);
117
- }
118
-
119
- function jsonHandle(req, res, fn) {
120
- let body = '';
121
-
122
- // 监听请求数据
123
- req.on('data', chunk => {
124
- body += chunk;
125
- });
126
-
127
- req.on('end', async () => {
128
- try {
129
- // 解析 JSON 请求体
130
- const requestData = body ? JSON.parse(body):{};
139
+ async function jsonHandle(req, res, fn) {
140
+ // 解析 JSON 请求体
141
+ const requestData = req ? JSON.parse(req):{};
131
142
 
132
- // 创建响应 JSON 数据
133
- let responseData = {};
134
- let result = await fn(requestData, responseData);
135
- if (vl(result)) {
136
- responseData = result;
137
- }
143
+ // 创建响应 JSON 数据
144
+ let responseData = {};
145
+ let result = await fn(requestData, responseData);
146
+ if (vl(result)) {
147
+ responseData = result;
148
+ }
138
149
 
139
- // 设置响应头
140
- res.writeHead(200, { 'Content-Type': 'application/json' });
141
- // 返回 JSON 响应
142
- res.end(JSON.stringify(responseData));
143
- } catch (error) {
144
- // 错误处理:如果 JSON 解析失败
145
- res.writeHead(400, { 'Content-Type': 'application/json' });
146
- res.end(JSON.stringify({ error: 'Invalid JSON format' }));
147
- }
148
- });
150
+ // 设置响应头
151
+ res.writeHead(200, { 'Content-Type': 'application/json' });
152
+ // 返回 JSON 响应
153
+ res.end(JSON.stringify(responseData));
149
154
  }
150
155
 
151
156
  module.exports = {
152
157
  setRoute,
153
- delRoute,
154
158
  jsonHandle,
155
159
  createSign
156
160
  };
package/deps/setting.js CHANGED
@@ -27,5 +27,9 @@ module.exports = {
27
27
  selfRoom: {},
28
28
  rooms: [],
29
29
  server: null,
30
- disableConnect: {}
30
+ disableConnect: {},
31
+ routes: {},
32
+ services: {},
33
+ serviceFns: {},
34
+ enterOutputs: null
31
35
  }
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 (isRunningInBackground() || (uniqueName && quite)) {
259
+ if (uniqueName && (quite || isRunningInBackground())) {
260
260
  if ((key === 'table' || key === 'nable') && typeof args[0] === "object") {
261
261
  args = ['', ...args]
262
262
  }
@@ -702,7 +702,7 @@ function createLimitLogger(fileName, {
702
702
  if (error) {
703
703
  global.$newError = true;
704
704
  }
705
- text = `${pad(3, String(process.pid%1000), ' ')}> ${text}`
705
+ text = `${pad(3, String(process.pid%1000), process.pid >= 1000 ? '0':' ')}> ${text}`
706
706
  if (time) {
707
707
  text = `${timeStr('YYYY-MM-DD HH:mm:ss.SSS')} ${text}`
708
708
  }
@@ -1854,7 +1854,7 @@ function md5(message) {
1854
1854
  return crypto.createHash('md5').update(message).digest('hex');
1855
1855
  }
1856
1856
 
1857
- async function batchAsync(fns = [], asyncNum = 1, limitMs = 9000) {
1857
+ async function batchAsync(fns = [], asyncNum = 1, limitMs = 49000) {
1858
1858
  if (fns.length <= 0 || asyncNum < 1) {
1859
1859
  return []
1860
1860
  }
@@ -1920,12 +1920,8 @@ function errorTag(e, tag) {
1920
1920
  if (!tag) {
1921
1921
  return e;
1922
1922
  }
1923
- let newError = new Error(getVl(e.message, e))
1923
+ let newError = isError(e) ? e:new Error(e);
1924
1924
  let stack = newError.stack
1925
- if (isError(e)) {
1926
- newError.name = e.name;
1927
- stack = e.stack;
1928
- }
1929
1925
  newError.stack = stack + `\n at ${tag}`
1930
1926
  return newError
1931
1927
  }
@@ -2025,6 +2021,41 @@ function getJsirTypeKey(fileName) {
2025
2021
  return fileName.split(/[^a-zA-Z]+/)[0] || ''
2026
2022
  }
2027
2023
 
2024
+ function createDetachedProcess(cmd, args = []) {
2025
+ // 创建子进程
2026
+ const subprocess = spawn(cmd, [...args], {
2027
+ detached: true, // 独立运行
2028
+ stdio: ['ignore']
2029
+ });
2030
+ // 让主进程不再等待子进程
2031
+ subprocess.unref();
2032
+ console.log(`New ${cmd} process started with PID:`, subprocess.pid);
2033
+ }
2034
+
2035
+ function interceptStdStreams() {
2036
+ // 保存原始的 stdout 和 stderr 写入方法
2037
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
2038
+ const originalStderrWrite = process.stderr.write.bind(process.stderr);
2039
+
2040
+ // 重写 process.stdout.write
2041
+ process.stdout.write = (chunk, ...args) => {
2042
+ if(setting.enterOutputs) {
2043
+ setting.enterOutputs.push(chunk.toString());
2044
+ } else {
2045
+ originalStdoutWrite(chunk, ...args); // 保留原始行为
2046
+ }
2047
+ };
2048
+
2049
+ // 重写 process.stderr.write
2050
+ process.stderr.write = (chunk, ...args) => {
2051
+ if(setting.enterOutputs) {
2052
+ setting.enterOutputs.push(chunk.toString());
2053
+ } else {
2054
+ originalStderrWrite(chunk, ...args); // 保留原始行为
2055
+ }
2056
+ };
2057
+ }
2058
+
2028
2059
  module.exports = {
2029
2060
  wrapperJsirText,
2030
2061
  run,
@@ -2131,5 +2162,7 @@ module.exports = {
2131
2162
  getRoomsDir,
2132
2163
  getDataDir,
2133
2164
  getJsirTypeKey,
2134
- isRunningInBackground
2165
+ isRunningInBackground,
2166
+ createDetachedProcess,
2167
+ interceptStdStreams
2135
2168
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsir",
3
- "version": "2.4.0",
3
+ "version": "2.4.1",
4
4
  "description": "JavaScript Script Management Tool",
5
5
  "main": "index.js",
6
6
  "scripts": {